All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.automation.tecan.plateop.TecanAddReagentPlateOp Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.automation.tecan.plateop;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.*;

import com.hfg.automation.*;
import com.hfg.automation.platelayer.PlateDataLayer;
import com.hfg.automation.platelayer.PlateReagentLayer;
import com.hfg.automation.plateop.AddReagentPlateOp;
import com.hfg.automation.plateop.VolumeMask;
import com.hfg.automation.tecan.TecanDeckPosition;
import com.hfg.automation.tecan.TecanLabwareContainer;
import com.hfg.automation.tecan.TecanLiquidClass;
import com.hfg.automation.tecan.TecanPlate;
import com.hfg.automation.tecan.TecanTipConfig;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistAspirateCommand;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistCommand;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistCommentCommand;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistDispenseCommand;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistReagentDistributionCommand;
import com.hfg.math.Counter;
import com.hfg.math.Range;
import com.hfg.units.Quantity;
import com.hfg.units.Unit;
import com.hfg.util.CompareUtil;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedMap;


//------------------------------------------------------------------------------
/**
 Tecan-specific implementation of the Add Reagent Plate Operation.
 
@author J. Alex Taylor, hairyfatguy.com
*/ //------------------------------------------------------------------------------ // com.hfg XML/HTML Coding Library // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com // [email protected] //------------------------------------------------------------------------------ /* Scenarios: 1. One aspiration per dispense 2. One aspiration per multiple dispenses 3. Multiple aspirations and dispenses per well */ public class TecanAddReagentPlateOp extends TecanPerPlateLiquidTransferPlateOp implements AddReagentPlateOp { private R mReagent; private TecanLabwareContainer mAspirateSrc; private List mDispenseDestinations; private TecanTipConfig mTipConfig; private int mMaxTipCycles = 1; private boolean mAllowMultiDispense = false; private Quantity mTotalVolumeTarget; private TecanWorklistCommand mWashType = TecanWorklistCommand.WASH; private String mDispenseVolumeSrcDataLayerName; // Tecan expects volumes to be specified as microliters private static final Unit MICROLITES = Unit.valueOf("μL"); private static Set sAspirateAllowedGroups = new HashSet<>(); private static Set sDispenseAllowedGroups = new HashSet<>(); static { sAspirateAllowedGroups.add(LabwareTypeGroup.Plate); sAspirateAllowedGroups.add(LabwareTypeGroup.Trough); sAspirateAllowedGroups.add(LabwareTypeGroup.Tube); sDispenseAllowedGroups.add(LabwareTypeGroup.Plate); sDispenseAllowedGroups.add(LabwareTypeGroup.Trough); sDispenseAllowedGroups.add(LabwareTypeGroup.Tube); sDispenseAllowedGroups.add(LabwareTypeGroup.Wash_and_Waste); } //########################################################################### // CONSTRUCTORS //########################################################################### //--------------------------------------------------------------------------- public TecanAddReagentPlateOp(R inReagent) { setReagent(inReagent); } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- public R getReagent() { return mReagent; } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setTipConfig(TecanTipConfig inValue) { mTipConfig = inValue; return this; } //--------------------------------------------------------------------------- public TecanTipConfig getTipConfig() { return mTipConfig; } //--------------------------------------------------------------------------- /** * Specifies the maximum number of aspirate/dispense cycles that a tip can be used for. * @param inValue the maximum number of aspirate/dispense cycles that a tip can be used for * @return this plate operation object to facilitate method chaining. */ public TecanAddReagentPlateOp setMaxTipCycles(int inValue) { if (inValue <= 0) { throw new AutomationConfigurationException("The max tip cycles cannot be set <= 0!"); } mMaxTipCycles = inValue; return this; } //--------------------------------------------------------------------------- public int getMaxTipCycles() { return mMaxTipCycles; } //--------------------------------------------------------------------------- /** * Specifies whether to allow multiple dispenses for a single aspirate. * @param inValue whether to allow multiple dispenses for a single aspirate * @return this plate operation object to facilitate method chaining. */ public TecanAddReagentPlateOp setAllowMultiDispense(boolean inValue) { mAllowMultiDispense = inValue; return this; } //--------------------------------------------------------------------------- public boolean getAllowMultiDispense() { return mAllowMultiDispense; } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setOutputOrder(Integer inValue) { return (TecanAddReagentPlateOp) super.setOutputOrder(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setVolumeCheckStringency(VolumeCheckStringency inValue) { return (TecanAddReagentPlateOp) super.setVolumeCheckStringency(inValue); } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setAspirateSrc(TecanLabwareContainer inValue) { aspirateGroupCheck(inValue); mAspirateSrc = inValue; return this; } //--------------------------------------------------------------------------- public TecanLabwareContainer getAspirateSrc() { return mAspirateSrc; } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setDispenseDestinations(Collection inValues) { if (null == mDispenseDestinations) { mDispenseDestinations = new ArrayList<>(10); } mDispenseDestinations.addAll(inValues); return this; } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp addDispenseDestination(TecanLabwareContainer inValue) { dispenseGroupCheck(inValue); if (null == mDispenseDestinations) { mDispenseDestinations = new ArrayList<>(10); } mDispenseDestinations.add(inValue); return this; } //--------------------------------------------------------------------------- public List getDispenseDestinations() { return mDispenseDestinations; } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setDispenseVolumeSrcDataLayerName(String inSrcDataLayerName) { mDispenseVolumeSrcDataLayerName = inSrcDataLayerName; return this; } //--------------------------------------------------------------------------- public String getDispenseVolumeSrcDataLayerName() { return mDispenseVolumeSrcDataLayerName; } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setDispenseVolumeDataLayerName(String inValue) { return (TecanAddReagentPlateOp) super.setDispenseVolumeDataLayerName(inValue); } //--------------------------------------------------------------------------- public VolumeMask calculatePostOpTotalVolumeMask(Plate inPlate) { VolumeMask opVolumeMask = getDispenseDestinationVolumeMask((TecanPlate) inPlate); if (null == opVolumeMask) { throw new AutomationConfigurationException(name() + " could not calculate post operation volumes!"); } return opVolumeMask; } //--------------------------------------------------------------------------- private void aspirateGroupCheck(TecanLabwareContainer inValue) { if (! sAspirateAllowedGroups.contains(inValue.getTecanLabwareType().getGroup())) { throw new AutomationConfigurationException("Labware " + StringUtil.singleQuote(inValue.getTecanLabel()) + " is not a type allowed to be used as an aspiration source!"); } } //--------------------------------------------------------------------------- private void dispenseGroupCheck(TecanLabwareContainer inValue) { if (! sDispenseAllowedGroups.contains(inValue.getTecanLabwareType().getGroup())) { throw new AutomationConfigurationException("Labware " + StringUtil.singleQuote(inValue.getTecanLabel()) + " is not a type allowed to be used as a dispense destination!"); } } //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setTotalVolumeTarget(Quantity inValue) { mTotalVolumeTarget = inValue; return this; } //--------------------------------------------------------------------------- public Quantity getTotalVolumeTarget() { return mTotalVolumeTarget; } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setAspirateVolume(Quantity inValue) { return (TecanAddReagentPlateOp) super.setAspirateVolume(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setDispenseVolume(Quantity inValue) { return (TecanAddReagentPlateOp) super.setDispenseVolume(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setAspirateVolumeMask(VolumeMask inValue) { return (TecanAddReagentPlateOp) super.setAspirateVolumeMask(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setLiquidClass(TecanLiquidClass inValue) { return (TecanAddReagentPlateOp) super.setLiquidClass(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setDispenseVolumeMask(VolumeMask inValue) { return (TecanAddReagentPlateOp) super.setDispenseVolumeMask(inValue); } //--------------------------------------------------------------------------- @Override public Quantity getDestinationVolumeFromSrcDataLayer(PlateLocation inDestinationLocation, String inDataLayerName) { // Doesn't really make sense for this class return null; } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setAspirateLiquidClass(TecanLiquidClass inValue) { return (TecanAddReagentPlateOp) super.setAspirateLiquidClass(inValue); } //--------------------------------------------------------------------------- @Override public TecanAddReagentPlateOp setComment(String inValue) { return (TecanAddReagentPlateOp) super.setComment(inValue); } /* //--------------------------------------------------------------------------- public TecanAddReagentPlateOp setWashTypeAndFreq(TecanWorklistRecord inWashType, Integer inAfterNDispenses) { mWashType = inWashType; mWashFreq = inAfterNDispenses; return this; } //--------------------------------------------------------------------------- public Integer getWashFreq() { return mWashFreq; } */ //--------------------------------------------------------------------------- public TecanWorklistCommand getWashType() { return mWashType; } //--------------------------------------------------------------------------- public void generateWorklist(OutputStream inStream) throws IOException { checkConfiguration(); PrintStream printStream = new PrintStream(inStream); if (StringUtil.isSet(getComment())) { printStream.println(new TecanWorklistCommentCommand(getComment())); } if (getDeckConfig() != null) { TecanLabwareContainer currentContainerAtReagentPosition = getDeckConfig().getDeckPosition(mAspirateSrc.getTecanLabel()).getContainer(); if (null == currentContainerAtReagentPosition || !currentContainerAtReagentPosition.equals(mAspirateSrc)) { String comment = "Place reagent " + (StringUtil.isSet(mAspirateSrc.getRackId()) ? mAspirateSrc.getRackId() + " " : "") + "at deck position " + mAspirateSrc.getTecanLabel(); printStream.println(new TecanWorklistCommentCommand(comment).toString()); getDeckConfig().getDeckPosition(mAspirateSrc.getTecanLabel()).setContainer(mAspirateSrc); } } int tip = 1; // TODO: How does the Tecan deal with worklists for multiple plates? for (int destIndex = 0; destIndex < getDispenseDestinations().size(); destIndex++) { TecanLabwareContainer container = getDispenseDestinations().get(destIndex); if (! container.getTecanLabwareType().getGroup().equals(LabwareTypeGroup.Plate)) { throw new AutomationConfigurationException("Destinations must currently be plates for this PlateOp!"); } TecanPlate plate = (TecanPlate) container; if (getDeckConfig() != null) { TecanLabwareContainer currentContainerAtDestPosition = getDeckConfig().getDeckPosition(container.getTecanLabel()).getContainer(); if (null == currentContainerAtDestPosition || ! currentContainerAtDestPosition.equals(container)) { String comment = "Place dest " + (StringUtil.isSet(container.getRackId()) ? container.getRackId() + " " : "") + "at deck position " + container.getTecanLabel(); printStream.println(new TecanWorklistCommentCommand(comment).toString()); getDeckConfig().getDeckPosition(container.getTecanLabel()).setContainer(container); } } // TODO: Test for a plate type match between the plate and the plate layout // Special case if all the dispense volumes are the same if (getDispenseVolume() != null || (getDispenseVolumeMask() != null && CollectionUtil.hasValues(getDispenseVolumeMask().getOccupiedWellRefs()) && getDispenseVolumeMask().allVolumesAreIdentical()) || (StringUtil.isSet(getDispenseVolumeDataLayerName()) && CollectionUtil.hasValues(plate.getDataLayer(getDispenseVolumeDataLayerName()).getOccupiedWellRefs()) && new VolumeMask(plate.getDataLayer(getDispenseVolumeDataLayerName())).allVolumesAreIdentical())) { // Use the Reagent Distribution (R) record WellRange wellRange; VolumeMask dispenseVolumeMask = null; Quantity dispenseVolume = getDispenseVolume(); if (dispenseVolume != null) { wellRange = plate.getType().getWellRange(); } else { dispenseVolumeMask = getDispenseVolumeMask(); if (null == dispenseVolumeMask) { dispenseVolumeMask = new VolumeMask(plate.getDataLayer(getDispenseVolumeDataLayerName())); } dispenseVolume = dispenseVolumeMask.getVolume(dispenseVolumeMask.getOccupiedWellRefs().iterator().next()); wellRange = dispenseVolumeMask.getOccupiedWellRange(plate.getLayout()); } if (null == dispenseVolume || 0 == dispenseVolume.floatValue()) { continue; } dispenseVolume = dispenseVolume.convertTo(MICROLITES); // Deal with the scenario where the amount to dispense exceeds the max capacity of the tip Quantity volumeRemainingToDispense = dispenseVolume; do { if (getTipConfig() != null && volumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity().multiplyBy(2))) { dispenseVolume = getTipConfig().getTipType().getMaxCapacity(); } else if (getTipConfig() != null && volumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity())) { // We don't want the remaining volume to get below the minimum capacity, so divide the remainder in half dispenseVolume = volumeRemainingToDispense.divideBy(2); } else { dispenseVolume = volumeRemainingToDispense; } int multiDispense = 1; if (getTipConfig() != null) { multiDispense = getTipConfig().getTipType().getMaxCapacity().intValue() / dispenseVolume.intValue(); } TecanWorklistReagentDistributionCommand reagentDistributionRecord = new TecanWorklistReagentDistributionCommand() .setSourceRackLabel(getAspirateSrc().getTecanLabel()) .setSourceRackId(getAspirateSrc().getRackId()) .setSourceRackName(getAspirateSrc().getTecanLabwareType().name()) .setSourcePositionRange(new Range<>(1, getTipConfig().getNumTips())) .setDestinationRackLabel(plate.getTecanLabel()) .setDestinationRackId(plate.getRackId()) .setDestinationRackName(plate.getTecanLabwareType().name()) .setDestinationPositionRange(plate.getType(), wellRange) .setMultiDispense(multiDispense) // TODO: should this be adjusted based on the max tip volume or a fixed number like 12? .setVolume(dispenseVolume) .setLiquidClass(getAspirateLiquidClass()); // Excluded wells? if (getDispenseVolume() != null) { reagentDistributionRecord.excludeWells(plate.getType(), plate.getLayout().getWellsToKeepEmpty()); } else { Iterator iterator = plate.getLayout().wellRefIterator(); while (iterator.hasNext()) { WellRef wellRef = iterator.next(); Quantity quantity = dispenseVolumeMask.getVolume(wellRef); if (null == quantity || 0 == quantity.floatValue()) { reagentDistributionRecord.excludeWell(plate.getType(), wellRef); } } } printStream.println(reagentDistributionRecord.toString()); volumeRemainingToDispense = volumeRemainingToDispense.subtract(dispenseVolume); } while (volumeRemainingToDispense.floatValue() > 0); } else { // Use Aspirate and Dispense ops for ea. well Map> tipPositionMap = new OrderedMap<>(getTipConfig().getNumTips()); Map tipCycleMap = new HashMap<>(10); Iterator iterator = plate.getLayout().wellRefIterator(); while (iterator.hasNext()) { WellRef wellRef = iterator.next(); // What is the volume of reagent to be aspirated? Quantity aspirateVolume = getAspirateVolume(); if (null == aspirateVolume && getAspirateVolumeMask() != null) { aspirateVolume = getAspirateVolumeMask().getVolume(wellRef); } // What is the volume of reagent to be dispensed? Quantity dispenseVolume = getDispenseVolume(); if (null == dispenseVolume) { if (getDispenseVolumeMask() != null) { dispenseVolume = getDispenseVolumeMask().getVolume(wellRef); } else if (StringUtil.isSet(getDispenseVolumeDataLayerName())) { PlateDataLayer dispenseVolumeLayer = plate.getDataLayer(getDispenseVolumeDataLayerName()); if (dispenseVolumeLayer != null) { dispenseVolume = dispenseVolumeLayer.getData(wellRef); } } else { // Does the plate have a reagent layer defined? PlateReagentLayer reagentLayer = plate.getReagentLayer(getReagent()); if (reagentLayer != null) { dispenseVolume = reagentLayer.getVolume(wellRef); } } } if (null == dispenseVolume) { if (StringUtil.isSet(getDispenseVolumeDataLayerName())) { continue; } else if (mTotalVolumeTarget != null) { Well well = plate.getWell(wellRef); if (well != null && well.getVolume() != null && well.getVolume().floatValue() > 0) { aspirateVolume = mTotalVolumeTarget.subtract(well.getVolume()); } else { aspirateVolume = new Quantity(0, Unit.valueOf("μL")); } } } // Deal with partially supplied information. If they specified aspiration volumes // but not dispense volumes or vice versa, assume that the two volumes are the same. if (null == aspirateVolume && dispenseVolume != null) { aspirateVolume = dispenseVolume; } else if (null == dispenseVolume && aspirateVolume != null) { dispenseVolume = aspirateVolume; } if (aspirateVolume != null && aspirateVolume.floatValue() > 0) { // Is the aspirate volume within capacity specs for the tip type? Quantity tipMinCapacity = mTipConfig.getTipType().getMinCapacity(); // TODO: Should there be other ways to handle volumes less than the minimum capacity? if (aspirateVolume.lessThan(tipMinCapacity)) { String msg = getReagent() + " aspiration volume for well " + wellRef + " (" + aspirateVolume + ") is less than the minimum capacity for the " + mTipConfig.getTipType().name() + " tip (" + tipMinCapacity + ")!"; if (getVolumeCheckStringency().equals(VolumeCheckStringency.warning)) { addWarning(msg); } else if (getVolumeCheckStringency().equals(VolumeCheckStringency.error)) { throw new AutomationConfigurationException(msg); } } // Tecan expects volumes to be specified as microliters aspirateVolume = aspirateVolume.convertTo(MICROLITES); dispenseVolume = dispenseVolume.convertTo(MICROLITES); if (dispenseVolume.greaterThan(aspirateVolume)) { String msg = "The dispense volume for well " + wellRef + " is larger than the " + getReagent() + " aspirate volume!"; if (getVolumeCheckStringency().equals(VolumeCheckStringency.warning)) { addWarning(msg); } else if (getVolumeCheckStringency().equals(VolumeCheckStringency.error)) { throw new AutomationConfigurationException(msg); } } // Deal with the scenario where the amount to dispense exceeds the max capacity of the tip Quantity volumeRemainingToDispense = dispenseVolume; do { if (getTipConfig() != null && volumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity().multiplyBy(2))) { aspirateVolume = getTipConfig().getTipType().getMaxCapacity(); } else if (getTipConfig() != null && volumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity())) { // We don't want the remaining volume to get below the minimum capacity, so divide the remainder in half aspirateVolume = volumeRemainingToDispense.divideBy(2); } else { aspirateVolume = volumeRemainingToDispense; } dispenseVolume = aspirateVolume; Counter cycleCounter = tipCycleMap.get(tip); if (null == cycleCounter) { cycleCounter = new Counter(); tipCycleMap.put(tip, cycleCounter); } cycleCounter.increment(); tipPositionMap.computeIfAbsent(tip, value -> new ArrayList<>(50)) .add(new Transfer() .setTipCycle(cycleCounter.intValue()) .setTipPosition(tip) .addAspirateVolume(aspirateVolume) .addDispense(new PlateLocation(plate, wellRef), dispenseVolume)); volumeRemainingToDispense = volumeRemainingToDispense.subtract(dispenseVolume); // Update the volume in the destination plate Well well = plate.getWell(wellRef); if (null == well) { well = plate.allocateWell(wellRef); } well.addVolume(dispenseVolume); } while (volumeRemainingToDispense.floatValue() > 0); tip++; if (getTipConfig().getNumTips() != null && tip > getTipConfig().getNumTips()) { tip = 1; } } } // Can cycles be combined? if (getAllowMultiDispense()) { combineCycles(tipPositionMap); } // Output ops while (CollectionUtil.hasValues(tipPositionMap)) { for (int i = 1; i <= getTipConfig().getNumTips(); i++) { List tipPositionTransfers = tipPositionMap.get(i); if (CollectionUtil.hasValues(tipPositionTransfers)) { for (int cycle = 0; cycle < getMaxTipCycles(); cycle++) { if (0 == tipPositionTransfers.size()) { break; } Transfer transfer = tipPositionTransfers.remove(0); // Aspirate TecanWorklistAspirateCommand aspirateRecord = new TecanWorklistAspirateCommand() .setRackLabel(getAspirateSrc().getTecanLabel()) .setRackId(getAspirateSrc().getRackId()) .setRackName(getAspirateSrc().getTecanLabwareType().name()) .setVolume(Math.round(transfer.getAspirateVolume().floatValue())) .setLiquidClass(getAspirateLiquidClass()); if (getTipConfig().getNumTips() != null) { aspirateRecord.setPosition(transfer.getTipPosition()); } printStream.println(aspirateRecord.toString()); for (PlateLocation plateLocation : transfer.getDispenseMap().keySet()) { // Dispense TecanWorklistDispenseCommand dispenseRecord = new TecanWorklistDispenseCommand() .setRackLabel(plate.getTecanLabel()) .setRackId(plate.getRackId()) .setRackName(plate.getTecanLabwareType().name()) .setPosition(TecanPlate.getTecanWellPosition(plate.getType(), plateLocation.getWellRef())) .setVolume(Math.round(transfer.getDispenseMap().get(plateLocation).floatValue())) .setLiquidClass(getDispenseLiquidClass()); printStream.println(dispenseRecord.toString()); } } // Tip wash if (getWashType() != null) { printStream.println(getWashType().toString()); } if (0 == tipPositionTransfers.size()) { tipPositionMap.remove(i); } } } } } } printStream.close(); } //########################################################################### // PRIVATE METHODS //########################################################################### //--------------------------------------------------------------------------- private TecanAddReagentPlateOp setReagent(R inValue) { mReagent = inValue; return this; } //--------------------------------------------------------------------------- private VolumeMask getDispenseDestinationVolumeMask(TecanPlate inPlate) { VolumeMask plateDispenseVolumeMask = null; if (StringUtil.isSet(getDispenseVolumeDataLayerName())) { PlateDataLayer dispenseVolumeDataLayer = inPlate.getDataLayer(getDispenseVolumeDataLayerName()); if (dispenseVolumeDataLayer != null) { plateDispenseVolumeMask = new VolumeMask(dispenseVolumeDataLayer); } } if (null == plateDispenseVolumeMask) { // There wasn't a data layer to specify the dispense volumes. // Do we have enough info to calculate one? if (null == getDispenseVolume() && null == getDispenseVolumeMask() && null == inPlate.getReagentLayer(getReagent()) && null == getAspirateVolume() && null == getAspirateVolumeMask() && (null == getTotalVolumeTarget() && inPlate.containsVolumes())) { throw new AutomationConfigurationException("No enough information for PlateOp " + name() + " to calculate destination volumes!"); } plateDispenseVolumeMask = new VolumeMask(); Iterator iterator = inPlate.getLayout().wellRefIterator(); while (iterator.hasNext()) { WellRef wellRef = iterator.next(); // What is the volume of reagent to be dispensed? Quantity dispenseVolume = getDispenseVolume(); if (null == dispenseVolume) { if (getDispenseVolumeMask() != null) { dispenseVolume = getDispenseVolumeMask().getVolume(wellRef); } else { // Does the plate have a reagent layer defined? PlateReagentLayer reagentLayer = inPlate.getReagentLayer(getReagent()); if (reagentLayer != null) { dispenseVolume = reagentLayer.getVolume(wellRef); } } } if (mTotalVolumeTarget != null) { // What is the volume of reagent to be aspirated? Assume that the dispense amount should be the same as the aspirate amount Quantity aspirateVolume = getAspirateVolume(); if (null == aspirateVolume && getAspirateVolumeMask() != null) { aspirateVolume = getAspirateVolumeMask().getVolume(wellRef); } Well well = inPlate.getWell(wellRef); if (well != null && well.getVolume() != null && well.getVolume().floatValue() > 0) { aspirateVolume = mTotalVolumeTarget.subtract(well.getVolume()); } else { aspirateVolume = new Quantity(0, Unit.valueOf("μL")); } dispenseVolume = aspirateVolume; } plateDispenseVolumeMask.setVolume(wellRef, dispenseVolume); } } return plateDispenseVolumeMask; } //--------------------------------------------------------------------------- private void checkConfiguration() { if (null == mTipConfig) { throw new AutomationConfigurationException("No tip configuration has been specified!"); } if (null == mAspirateSrc) { throw new AutomationConfigurationException("No aspiration source has been specified!"); } if (! CollectionUtil.hasValues(mDispenseDestinations)) { throw new AutomationConfigurationException("No dispense destinations have been specified!"); } if (! StringUtil.isSet(mAspirateSrc.getTecanLabel())) { throw new AutomationConfigurationException("Reagent container without a label (deck position)!"); } if (getDeckConfig() != null) { TecanDeckPosition deckPosition = getDeckConfig().getDeckPosition(mAspirateSrc.getTecanLabel()); if (null == deckPosition) { throw new AutomationConfigurationException("Reagent container label " + StringUtil.singleQuote(mAspirateSrc.getTecanLabel()) + " is not valid for the specified deck configuration!"); } else if (! mAspirateSrc.getTecanLabwareType().getClass().isAssignableFrom(deckPosition.getContainerType().getClass())) { throw new AutomationConfigurationException("Reagent container is incompatible with deck position " + StringUtil.singleQuote(mAspirateSrc.getTecanLabel()) + "!"); } } // Ensure that all destinations have valid labels for (TecanLabwareContainer destContainer : getDispenseDestinations()) { if (! StringUtil.isSet(destContainer.getTecanLabel())) { throw new AutomationConfigurationException("Destination container without a label (deck position)!"); } if (getDeckConfig() != null) { TecanDeckPosition deckPosition = getDeckConfig().getDeckPosition(destContainer.getTecanLabel()); if (null == deckPosition) { throw new AutomationConfigurationException("Destination container label " + StringUtil.singleQuote(destContainer.getTecanLabel()) + " is not valid for the specified deck configuration!"); } else if (! destContainer.getTecanLabwareType().getClass().isAssignableFrom(deckPosition.getContainerType().getClass())) { throw new AutomationConfigurationException("Destination container is incompatible with deck position " + StringUtil.singleQuote(destContainer.getTecanLabel()) + "!"); } } } if (StringUtil.isSet(getDispenseVolumeDataLayerName())) { for (TecanPlate destPlate : (List) (Object) getDispenseDestinations()) { if (null == destPlate.getDataLayer(getDispenseVolumeDataLayerName())) { throw new AutomationConfigurationException("Destination container " + StringUtil.singleQuote(destPlate.getTecanLabel()) + " doesn't have the expected data layer " + StringUtil.singleQuote(getDispenseVolumeDataLayerName()) + "!"); } } } if (null == getTipConfig()) { throw new AutomationConfigurationException("No tip configuration specified!"); } } //--------------------------------------------------------------------------- private void combineCycles(Map> inTipPositionMap) { Quantity maxVolume = getTipConfig().getTipType().getMaxCapacity().subtract(new Quantity("5 μL")); for (List tipList : inTipPositionMap.values()) { for (int i = 0; i < tipList.size() - 1; i++) { Transfer transfer1 = tipList.get(i); for (int j = i + 1; j < tipList.size(); j++) { Transfer transfer2 = tipList.get(j); // Can the two transfers be combined? if (transfer1.getAspirateVolume().add(transfer2.getAspirateVolume()).floatValue() < maxVolume.floatValue()) { transfer1.addAspirateVolume(transfer2.getAspirateVolume()); for (PlateLocation plateLocation : transfer2.getDispenseMap().keySet()) { transfer1.addDispense(plateLocation, transfer2.getDispenseMap().get(plateLocation)); } tipList.remove(j--); } } } } } private class Transfer implements Comparable { private int mTipCycle; private int mTipPosition; private Quantity mAspriateVolume; private Map mDispenseMap; //------------------------------------------------------------------------ public String toString() { return "Cycle: " + mTipCycle + "; Position: " + mTipPosition; } //------------------------------------------------------------------------ public Transfer setTipCycle(int inValue) { mTipCycle = inValue; return this; } //------------------------------------------------------------------------ public int getTipCycle() { return mTipCycle; } //------------------------------------------------------------------------ public Transfer setTipPosition(int inValue) { mTipPosition = inValue; return this; } //------------------------------------------------------------------------ public int getTipPosition() { return mTipPosition; } //------------------------------------------------------------------------ public Transfer addAspirateVolume(Quantity inValue) { mAspriateVolume = (mAspriateVolume != null ? mAspriateVolume.add(inValue) : inValue); return this; } //------------------------------------------------------------------------ public Quantity getAspirateVolume() { return mAspriateVolume; } //------------------------------------------------------------------------ public Transfer addDispense(PlateLocation inLocation, Quantity inVol) { if (null == mDispenseMap) { mDispenseMap = new OrderedMap<>(5); } mDispenseMap.put(inLocation, inVol); return this; } //------------------------------------------------------------------------ public Map getDispenseMap() { return mDispenseMap; } //------------------------------------------------------------------------ @Override public int compareTo(Transfer inObj2) { int result = -1; if (inObj2 != null) { result = CompareUtil.compare(getTipPosition(), inObj2.getTipPosition()); if (0 == result) { result = CompareUtil.compare(getDispenseMap().keySet(), inObj2.getDispenseMap().keySet()); } if (0 == result) { result = CompareUtil.compare(getTipCycle(), inObj2.getTipCycle()); } if (0 == result) { result = - CompareUtil.compare(getAspirateVolume(), inObj2.getAspirateVolume()); } } return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy