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

com.hfg.automation.tecan.plateop.TecanRearrayPlateOp 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.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.hfg.automation.AutomationConfigurationException;
import com.hfg.automation.LabwareTypeGroup;
import com.hfg.automation.Plate;
import com.hfg.automation.PlateLocation;
import com.hfg.automation.PlateRole;
import com.hfg.automation.VolumeCheckStringency;
import com.hfg.automation.Well;
import com.hfg.automation.WellRef;
import com.hfg.automation.platelayer.LayerPlate;
import com.hfg.automation.platelayer.PlateDataLayer;
import com.hfg.automation.platelayer.PlateSampleLayer;
import com.hfg.automation.plateop.RearrayPlateOp;
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.TecanOpStep;
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.TecanWorklistUserPromptCommand;
import com.hfg.exception.ProgrammingException;
import com.hfg.units.Quantity;
import com.hfg.units.Unit;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedMap;


//------------------------------------------------------------------------------
/**
 Tecan-specific implementation of the Rearray 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] //------------------------------------------------------------------------------ public class TecanRearrayPlateOp extends TecanPerPlateLiquidTransferPlateOp implements RearrayPlateOp { private List mAspirateSources; private List mDispenseDestinations; private Class mSampleClass; private String mSampleLayerName; private Integer mSourcePlateBatchSize; private List mOpSteps; // Optional reagent to (pre)include with the transfer private TecanLabwareContainer mReagentContainer; private Quantity mFixedReagentQuantity; private String mReagentQuantityDataLayerName; private TecanLiquidClass mReagentAspirateLiquidClass; private Map> mTransferMap; // Tecan expects volumes to be specified as microliters private static final Unit MICROLITERS = Unit.valueOf("μL"); private static final Quantity ZERO_VOLUME = new Quantity(0, MICROLITERS); private static Set sAspirateAllowedGroups = new HashSet<>(); private static Set sDispenseAllowedGroups = new HashSet<>(); static { sAspirateAllowedGroups.add(LabwareTypeGroup.Plate); sAspirateAllowedGroups.add(LabwareTypeGroup.Tube); sDispenseAllowedGroups.add(LabwareTypeGroup.Plate); sDispenseAllowedGroups.add(LabwareTypeGroup.Tube); } private enum Mode { dryRun, normal } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setAspirateVolumeDataLayerName(String inValue) { return (TecanRearrayPlateOp) super.setAspirateVolumeDataLayerName(inValue); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setSampleClass(Class inValue) { mSampleClass = inValue; return this; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setSampleLayerName(String inValue) { mSampleLayerName = inValue; return this; } //--------------------------------------------------------------------------- public Class getSampleClass() { return mSampleClass; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setSourcePlateBatchSize(Integer inValue) { mSourcePlateBatchSize = inValue; return this; } //--------------------------------------------------------------------------- public Integer getSourcePlateBatchSize() { return mSourcePlateBatchSize; } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setOutputOrder(Integer inValue) { return (TecanRearrayPlateOp) super.setOutputOrder(inValue); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setAspirateSources(Collection inValues) { mAspirateSources = null; if (CollectionUtil.hasValues(inValues)) { for (TecanPlate plate : inValues) { addAspirateSource(plate); } } return this; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp addAspirateSource(TecanPlate inValue) { aspirateGroupCheck(inValue); if (null == mAspirateSources) { mAspirateSources = new ArrayList<>(10); } mAspirateSources.add(inValue); // If the sample class hasn't been specified yet and there is a single sample layer in the source plate, assume that that is the sample class if (null == getSampleClass()) { Collection sampleLayers = inValue.getSampleLayers(); if (CollectionUtil.hasSingleValue(sampleLayers)) { setSampleClass(sampleLayers.iterator().next().getSampleClass()); } } return this; } //--------------------------------------------------------------------------- public List getAspirateSources() { return mAspirateSources; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setDispenseDestinations(Collection inValues) { if (null == mDispenseDestinations) { mDispenseDestinations = new ArrayList<>(10); } mDispenseDestinations.addAll(inValues); return this; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp addDispenseDestination(TecanPlate inValue) { dispenseGroupCheck(inValue); if (null == mDispenseDestinations) { mDispenseDestinations = new ArrayList<>(10); } mDispenseDestinations.add(inValue); return this; } //--------------------------------------------------------------------------- public List getDispenseDestinations() { return mDispenseDestinations; } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setAspirateVolume(Quantity inValue) { return (TecanRearrayPlateOp) super.setAspirateVolume(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setDispenseVolume(Quantity inValue) { return (TecanRearrayPlateOp) super.setDispenseVolume(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setAspirateVolumeMask(VolumeMask inValue) { return (TecanRearrayPlateOp) super.setAspirateVolumeMask(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setDispenseVolumeMask(VolumeMask inValue) { throw new AutomationConfigurationException("A dispense volume mask should not be specified on a Re-array operation!"); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setAspirateLiquidClass(TecanLiquidClass inValue) { return (TecanRearrayPlateOp) super.setAspirateLiquidClass(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setLiquidClass(TecanLiquidClass inValue) { return (TecanRearrayPlateOp) super.setLiquidClass(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setTipConfig(TecanTipConfig inValue) { return (TecanRearrayPlateOp) super.setTipConfig(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setComment(String inValue) { return (TecanRearrayPlateOp) super.setComment(inValue); } //--------------------------------------------------------------------------- @Override public TecanRearrayPlateOp setVolumeCheckStringency(VolumeCheckStringency inValue) { return (TecanRearrayPlateOp) super.setVolumeCheckStringency(inValue); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp includeReagentInAspirate(TecanLabwareContainer inReagentContainer, TecanLiquidClass inReagentAspirateLiquidClass) { return includeReagentInAspirate(inReagentContainer, (Quantity) null, inReagentAspirateLiquidClass); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp includeReagentInAspirate(TecanLabwareContainer inReagentContainer, Quantity inQuantity) { return includeReagentInAspirate(inReagentContainer, inQuantity, null); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp includeReagentInAspirate(TecanLabwareContainer inReagentContainer, Quantity inQuantity, TecanLiquidClass inReagentAspirateLiquidClass) { mReagentContainer = inReagentContainer; mFixedReagentQuantity = inQuantity; mReagentAspirateLiquidClass = inReagentAspirateLiquidClass; return this; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp includeReagentInAspirate(TecanLabwareContainer inReagentContainer, String inReagentQuantityDataLayerName) { return includeReagentInAspirate(inReagentContainer, inReagentQuantityDataLayerName, null); } //--------------------------------------------------------------------------- public TecanRearrayPlateOp includeReagentInAspirate(TecanLabwareContainer inReagentContainer, String inReagentQuantityDataLayerName, TecanLiquidClass inReagentAspirateLiquidClass) { mReagentContainer = inReagentContainer; mReagentQuantityDataLayerName = inReagentQuantityDataLayerName; mReagentAspirateLiquidClass = inReagentAspirateLiquidClass; return this; } //--------------------------------------------------------------------------- public TecanRearrayPlateOp setOpSteps(List inSteps) { mOpSteps = inSteps; return this; } //--------------------------------------------------------------------------- public Quantity getDestinationVolumeFromSrcDataLayer(PlateLocation inDestinationLocation, String inDataLayerName) { Quantity volume = null; Map> dryRunTransferMap = getTransferMap(); for (PlateLocation srcPlateLocation : dryRunTransferMap.keySet()) { if (dryRunTransferMap.get(srcPlateLocation).containsKey(inDestinationLocation)) { PlateDataLayer dataLayer = ((LayerPlate) srcPlateLocation.getPlate()).getDataLayer(inDataLayerName); if (dataLayer != null) { volume = dataLayer.getData(srcPlateLocation.getWellRef()); break; } } } return volume; } //--------------------------------------------------------------------------- public void generateWorklist(OutputStream inStream) throws IOException { generateWorklist(inStream, Mode.normal); } //--------------------------------------------------------------------------- public Map> getTransferMap() { if (null == mTransferMap) { try { generateWorklist(null, Mode.dryRun); } catch (IOException e) { throw new ProgrammingException(e); } } return mTransferMap; } //--------------------------------------------------------------------------- // TODO: What to do if the requested plate is a source location instead of a destination? public VolumeMask calculatePostOpTotalVolumeMask(Plate inPlate) throws IOException { generateWorklist(null, Mode.dryRun); VolumeMask postOpVolumeMask = new VolumeMask(); for (Map destVolumeMap : mTransferMap.values()) { for (PlateLocation loc : destVolumeMap.keySet()) { if (loc.getPlate().equals(inPlate)) { postOpVolumeMask.addVolume(loc.getWellRef(), destVolumeMap.get(loc)); } } } return postOpVolumeMask; } //########################################################################### // PRIVATE METHODS //########################################################################### //--------------------------------------------------------------------------- private void preflightChecks() { if (! CollectionUtil.hasValues(getAspirateSources())) { throw new AutomationConfigurationException("No aspiration sources have been specified!"); } if (! CollectionUtil.hasValues(getDispenseDestinations())) { throw new AutomationConfigurationException("No dispense destinations have been specified!"); } if (getDeckConfig() != null) { // Make sure that the src plate labels refer to position labels defined in the deck config. int srcSlot = 0; for (TecanPlate srcPlate : getAspirateSources()) { if (! StringUtil.isSet(srcPlate.getTecanLabel())) { // Try to auto-assign a Tecan label List srcPositions = getDeckConfig().getDeckPositionsByRole(PlateRole.SOURCE); if (CollectionUtil.hasValues(srcPositions)) { srcPlate.setTecanLabel(srcPositions.get(srcSlot++).getLabel()); if (srcSlot >= srcPositions.size()) { srcSlot = 0; } } else { throw new AutomationConfigurationException("No Tecan label applied to plate " + srcPlate.name() + " and no deck positions defined with SOURCE role to allow auto-assignment!"); } } getDeckConfig().validateDeckPosition(srcPlate.getTecanLabel()); } // Make sure that the destination plate labels refer to position labels defined in the deck config. int destSlot = 0; for (TecanPlate destPlate : getDispenseDestinations()) { if (! StringUtil.isSet(destPlate.getTecanLabel())) { // Try to auto-assign a Tecan label List destPositions = getDeckConfig().getDeckPositionsByRole(PlateRole.DESTINATION); if (CollectionUtil.hasValues(destPositions)) { destPlate.setTecanLabel(destPositions.get(destSlot++).getLabel()); if (destSlot >= destPositions.size()) { destSlot = 0; } } else { throw new AutomationConfigurationException("No Tecan label applied to plate " + destPlate.name() + " and no deck positions defined with DESTINATION role to allow auto-assignment!"); } } getDeckConfig().validateDeckPosition(destPlate.getTecanLabel()); } // If defined, make sure that the reagent label refers to a position label defined in the deck config. if (mReagentContainer != null) { getDeckConfig().validateDeckPosition(mReagentContainer.getTecanLabel()); } if (CollectionUtil.hasValues(mOpSteps) && getTipConfig() != null) { // Is the volume within capacity specs for the tip type? for (TecanOpStep step : mOpSteps) { Quantity tipMinCapacity = getTipConfig().getTipType().getMinCapacity(); // TODO: Should there be other ways to handle volumes less than the minimum capacity? if (step.getQuantity().lessThan(tipMinCapacity)) { String msg = "Transfer volume for step (" + step.getQuantity() + ") is less than the minimum capacity for the " + getTipConfig().getTipType().name() + " tip (" + tipMinCapacity + ")!"; if (getVolumeCheckStringency().equals(VolumeCheckStringency.warning)) { addWarning(msg); } else if (getVolumeCheckStringency().equals(VolumeCheckStringency.error)) { throw new AutomationConfigurationException(msg); } } } } } if (null == getSampleClass()) { setSampleClass(String.class); // throw new AutomationConfigurationException("No sample class has been specified!"); } } //--------------------------------------------------------------------------- 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!"); } } //--------------------------------------------------------------------------- private String getNameForPlate(TecanPlate inPlate) { // What should we call the src plate in case of an exception? String plateName = inPlate.name(); if (! StringUtil.isSet(plateName)) { if (! StringUtil.isSet(inPlate.getBarcode())) { plateName = inPlate.getBarcode(); } else { plateName = inPlate.getTecanLabel(); } } return plateName; } //--------------------------------------------------------------------------- public void generateWorklist(OutputStream inStream, Mode inMode) throws IOException { preflightChecks(); PrintStream printStream = null; if (inMode.equals(Mode.normal)) { printStream = new PrintStream(inStream); if (StringUtil.isSet(getComment())) { printStream.println(new TecanWorklistCommentCommand(getComment())); } String tipConfigMsg; if (getTipConfig() != null) { tipConfigMsg = "Tip config: " + getTipConfig().toString(); } else { tipConfigMsg = "No tip configuration specified."; } printStream.println(new TecanWorklistCommentCommand(tipConfigMsg).toString()); } mTransferMap = new OrderedMap<>(); // TODO: How does the Tecan deal with worklists for multiple plates? int destPlateIndex = 0; TecanPlate actualDestPlate = getDispenseDestinations().get(destPlateIndex); TecanPlate currentDestPlate = (inMode.equals(Mode.normal) ? actualDestPlate : actualDestPlate.clone()); int numTips = 1; if (getTipConfig() != null && getTipConfig().getNumTips() != null) { numTips = getTipConfig().getNumTips(); } int srcPlateBatchSize = 1; if (getDeckConfig() != null) { if (getSourcePlateBatchSize() != null) { srcPlateBatchSize = getSourcePlateBatchSize(); } else { List srcPositions = getDeckConfig().getDeckPositionsByRole(PlateRole.SOURCE); if (CollectionUtil.hasValues(srcPositions)) { srcPlateBatchSize = srcPositions.size(); } } } // Set up destination plate(s) if (inMode.equals(Mode.normal) && getDeckConfig() != null) { int initialDestPlateBatchSize = 1; if (getDeckConfig() != null) { List destPositions = getDeckConfig().getDeckPositionsByRole(PlateRole.DESTINATION); if (CollectionUtil.hasValues(destPositions)) { initialDestPlateBatchSize = destPositions.size(); } if (getDispenseDestinations().size() < initialDestPlateBatchSize) { initialDestPlateBatchSize = getDispenseDestinations().size(); } } StringBuilderPlus comments = new StringBuilderPlus().setDelimiter(" "); for (int i = 0; i < initialDestPlateBatchSize; i++) { TecanPlate destPlate = getDispenseDestinations().get(i); TecanLabwareContainer currentContainerAtDeckPosition = getDeckConfig().getDeckPosition(destPlate.getTecanLabel()).getContainer(); if (null == currentContainerAtDeckPosition || !currentContainerAtDeckPosition.equals(destPlate)) { String comment = "Place dest " + (StringUtil.isSet(destPlate.getRackId()) ? destPlate.getRackId() : destPlate.name()) + " at deck position " + destPlate.getTecanLabel() + "."; comments.delimitedAppend(comment); // Tell the deck config that this plate is now at this deck position getDeckConfig().getDeckPosition(destPlate.getTecanLabel()).setContainer(destPlate); } } printStream.println(new TecanWorklistUserPromptCommand(comments.toString()).toString()); } // Start by looping over ea. batch of source plates List> srcPlateBatches = CollectionUtil.chunkList(getAspirateSources(), srcPlateBatchSize); for (List srcPlateBatch : srcPlateBatches) { // TODO: Test for a plate type match between the plate and the plate layout int tip = 1; if (inMode.equals(Mode.normal) && getDeckConfig() != null) { StringBuilderPlus comments = new StringBuilderPlus().setDelimiter(" "); for (TecanPlate srcPlate : srcPlateBatch) { if (CollectionUtil.hasValues(srcPlate.getSelectedWells())) // Skip plates that don't need to be operated on { getDeckConfig().validateDeckPosition(srcPlate.getTecanLabel()); TecanLabwareContainer currentContainerAtSrcPosition = getDeckConfig().getDeckPosition(srcPlate.getTecanLabel()).getContainer(); if (null == currentContainerAtSrcPosition || !currentContainerAtSrcPosition.equals(srcPlate)) { String comment = "Place src " + (StringUtil.isSet(srcPlate.getRackId()) ? srcPlate.getRackId() : srcPlate.name()) + " at deck position " + srcPlate.getTecanLabel() + "."; comments.delimitedAppend(comment); // Tell the deck config that this plate is now at this deck position getDeckConfig().getDeckPosition(srcPlate.getTecanLabel()).setContainer(srcPlate); } } } printStream.println(new TecanWorklistUserPromptCommand(comments.toString()).toString()); } for (TecanPlate srcPlate : srcPlateBatch) { VolumeMask reagentVolumeMask = null; if (mReagentContainer != null && StringUtil.isSet(mReagentQuantityDataLayerName)) { reagentVolumeMask = new VolumeMask(srcPlate.getDataLayer(mReagentQuantityDataLayerName)); if (null == reagentVolumeMask) { throw new AutomationConfigurationException("Reagent data layer " + StringUtil.singleQuote(mReagentQuantityDataLayerName) + " wasn't found on source plate " + StringUtil.singleQuote(srcPlate.name()) + "!"); } } if (inMode.equals(Mode.normal) && getDeckConfig() != null) { getDeckConfig().validateDeckPosition(srcPlate.getTecanLabel()); TecanLabwareContainer currentContainerAtSrcPosition = getDeckConfig().getDeckPosition(srcPlate.getTecanLabel()).getContainer(); if (null == currentContainerAtSrcPosition || !currentContainerAtSrcPosition.equals(srcPlate)) { String comment = "Place src " + (StringUtil.isSet(srcPlate.getRackId()) ? srcPlate.getRackId() + " " : "") + "at deck position " + srcPlate.getTecanLabel(); printStream.println(new TecanWorklistCommentCommand(comment).toString()); getDeckConfig().getDeckPosition(srcPlate.getTecanLabel()).setContainer(srcPlate); } TecanLabwareContainer currentContainerAtDestPosition = getDeckConfig().getDeckPosition(currentDestPlate.getTecanLabel()).getContainer(); if (null == currentContainerAtDestPosition || !currentContainerAtDestPosition.equals(currentDestPlate)) { String comment = "Place dest " + (StringUtil.isSet(currentDestPlate.getRackId()) ? currentDestPlate.getRackId() + " " : "") + "at deck position " + currentDestPlate.getTecanLabel(); printStream.println(new TecanWorklistCommentCommand(comment).toString()); getDeckConfig().getDeckPosition(currentDestPlate.getTecanLabel()).setContainer(currentDestPlate); } } for (WellRef srcWellRef : srcPlate.getLayout().getWellRefs(false)) { PlateLocation srcPlateLoc = new PlateLocation(srcPlate, srcWellRef); Map destMap = mTransferMap.get(srcPlateLoc); if (null == destMap) { destMap = new OrderedMap<>(2); mTransferMap.put(srcPlateLoc, destMap); } Well srcWell = srcPlate.getWell(srcWellRef); if (srcWell.isSelected()) { WellRef destWellRef = currentDestPlate.getNextAvailableSampleWellRef(getSampleClass(), mSampleLayerName); if (null == destWellRef) { destPlateIndex++; if (destPlateIndex == getDispenseDestinations().size()) { throw new AutomationConfigurationException("Not enough destination plates for the operation to complete!"); } actualDestPlate = getDispenseDestinations().get(destPlateIndex); currentDestPlate = (inMode.equals(Mode.normal) ? actualDestPlate : actualDestPlate.clone()); destWellRef = currentDestPlate.getNextAvailableSampleWellRef(getSampleClass(), mSampleLayerName); } PlateLocation destPlateLoc = new PlateLocation(actualDestPlate, destWellRef); // Individual steps can be specified to handle more complex operations if (CollectionUtil.hasValues(mOpSteps)) { //TODO: Still fragile - add more checks and handling for volumes exceeding tip capacities for (TecanOpStep step : mOpSteps) { TecanWorklistCommand record; switch (step.getWorklistCommandType()) { case ASPIRATE: switch (step.getPlateRole()) { case REAGENT: if (null == mReagentContainer) { throw new AutomationConfigurationException("No Reagent specified via includeReagentInAspirate() to support the reagent ASPIRATE step!"); } record = new TecanWorklistAspirateCommand() .setRackLabel(mReagentContainer.getTecanLabel()) .setRackId(mReagentContainer.getRackId()) .setRackName(mReagentContainer.getTecanLabwareType().name()) .setPosition(tip) .setVolume(step.getQuantity().floatValue()) .setLiquidClass(step.getLiquidClass() != null ? step.getLiquidClass() : mReagentAspirateLiquidClass != null ? mReagentAspirateLiquidClass : getAspirateLiquidClass()); break; case SOURCE: record = new TecanWorklistAspirateCommand() .setRackLabel(srcPlate.getTecanLabel()) .setRackId(srcPlate.getRackId()) .setRackName(srcPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(srcPlate.getType(), srcWell.getRef())) .setVolume(step.getQuantity().floatValue()) .setLiquidClass(step.getLiquidClass() != null ? step.getLiquidClass() : getAspirateLiquidClass()); break; case DESTINATION: record = new TecanWorklistAspirateCommand() .setRackLabel(currentDestPlate.getTecanLabel()) .setRackId(currentDestPlate.getRackId()) .setRackName(currentDestPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(currentDestPlate.getType(), destWellRef)) .setVolume(step.getQuantity().floatValue()) .setLiquidClass(step.getLiquidClass() != null ? step.getLiquidClass() : getAspirateLiquidClass()); updateDestinationVolume(destMap, srcWell, currentDestPlate, destWellRef, step.getQuantity().invert()); break; default: throw new AutomationConfigurationException("PlateRole " + StringUtil.singleQuote(step.getPlateRole()) + " not currently supported as a ASPIRATE step!"); } break; case DISPENSE: switch (step.getPlateRole()) { case SOURCE: record = new TecanWorklistDispenseCommand() .setRackLabel(srcPlate.getTecanLabel()) .setRackId(srcPlate.getRackId()) .setRackName(srcPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(srcPlate.getType(), srcWell.getRef())) .setVolume(step.getQuantity().floatValue()) .setLiquidClass(step.getLiquidClass() != null ? step.getLiquidClass() : getDispenseLiquidClass()); updateDestinationVolume(destMap, srcWell, currentDestPlate, destWellRef, step.getQuantity()); break; case DESTINATION: record = new TecanWorklistDispenseCommand() .setRackLabel(currentDestPlate.getTecanLabel()) .setRackId(currentDestPlate.getRackId()) .setRackName(currentDestPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(currentDestPlate.getType(), destWellRef)) .setVolume(step.getQuantity().floatValue()) .setLiquidClass(step.getLiquidClass() != null ? step.getLiquidClass() : getDispenseLiquidClass()); break; default: throw new AutomationConfigurationException("PlateRole " + StringUtil.singleQuote(step.getPlateRole()) + " not currently supported as a DISPENSE step!"); } break; default: throw new AutomationConfigurationException("Command type " + StringUtil.singleQuote(step.getWorklistCommandType()) + " not currently supported as a step!"); } if (inMode.equals(Mode.normal)) { printStream.println(record.toString()); } } if (inMode.equals(Mode.normal)) { // Tip wash printStream.println(TecanWorklistCommand.WASH.toString()); } tip++; if (tip > numTips) { tip = 1; } } else { // What is the volume of sample to be aspirated? Quantity aspirateVolume = getAspirateVolume(); if (null == aspirateVolume && getAspirateVolumeMask() != null) { aspirateVolume = getAspirateVolumeMask().getVolume(srcWell.getRef()); } else if (StringUtil.isSet(getAspirateVolumeDataLayerName())) { PlateDataLayer aspirateSrcLayer = srcPlate.getDataLayer(getAspirateVolumeDataLayerName()); if (aspirateSrcLayer != null) { aspirateVolume = aspirateSrcLayer.getData(srcWell.getRef()); if (null == aspirateVolume) { // A zero-volume well is a indicator that the well should be skipped aspirateVolume = ZERO_VOLUME; } } } // What is the volume of sample to be dispensed? Quantity dispenseVolume = getDispenseVolume(); if (null == dispenseVolume && getDispenseVolumeMask() != null) { dispenseVolume = getDispenseVolumeMask().getVolume(destWellRef); } else if (srcWell.getVolume() != null && 0 == srcWell.getVolume().floatValue()) { // A zero-volume well is a indicator that the well should be skipped dispenseVolume = ZERO_VOLUME; } // 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; } if (null == aspirateVolume) { if (srcWell.getVolume() != null) { // Assume that the entire volume should be transferred aspirateVolume = srcWell.getVolume(); } else { throw new RuntimeException("No " + getNameForPlate(srcPlate) + " " + srcWell.getRef() + " volume or volume mask value specified for the operation!"); } } if (null == dispenseVolume) { dispenseVolume = aspirateVolume; } // Tecan expects volumes to be specified as microliters aspirateVolume = aspirateVolume.convertTo(MICROLITERS); dispenseVolume = dispenseVolume.convertTo(MICROLITERS); if (dispenseVolume.floatValue() > aspirateVolume.floatValue()) { String msg = "The dispense volume for well " + destWellRef + " is larger than the aspirate volume!"; if (getVolumeCheckStringency().equals(VolumeCheckStringency.warning)) { addWarning(msg); } else if (getVolumeCheckStringency().equals(VolumeCheckStringency.error)) { throw new AutomationConfigurationException(msg); } } // Reagent to include in the aspirate with the sample transfer? Quantity reagentDispenseVolumeToInclude = null; if (inMode.equals(Mode.normal) && mReagentContainer != null) { Quantity reagentAspriateVol = mFixedReagentQuantity; if (null == reagentAspriateVol) { // Was the reagent quantity specified on the src plate? reagentAspriateVol = reagentVolumeMask.getVolume(srcWellRef); } if (null == reagentAspriateVol) { throw new AutomationConfigurationException("Reagent " + mReagentContainer.getTecanLabel() + " was specified without a quantity!"); } // Is the aspirate volume within capacity specs for the tip type? if (getTipConfig() != null) { Quantity tipMinCapacity = getTipConfig().getTipType().getMinCapacity(); // TODO: Should there be other ways to handle volumes less than the minimum capacity? if (aspirateVolume.lessThan(tipMinCapacity)) { String msg = "Aspiration volume for well " + srcWell + " (" + aspirateVolume + ") is less than the minimum capacity for the " + getTipConfig().getTipType().name() + " tip (" + tipMinCapacity + ")!"; 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 reagentVolumeRemainingToDispense = reagentAspriateVol; do { if (getTipConfig() != null && reagentVolumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity().multiplyBy(2))) { reagentAspriateVol = getTipConfig().getTipType().getMaxCapacity(); } else if (getTipConfig() != null && reagentVolumeRemainingToDispense.greaterThan(getTipConfig().getTipType().getMaxCapacity())) { // We don't want the remaining volume to get below the minimum capacity, so divide the remainder in half reagentAspriateVol = reagentVolumeRemainingToDispense.divideBy(2); } Quantity reagentDispenseVolume = reagentAspriateVol; // Tecan expects volumes to be specified as microliters reagentAspriateVol = reagentAspriateVol.convertTo(MICROLITERS); // Reagent aspirate TecanWorklistAspirateCommand reagentAspirateRecord = new TecanWorklistAspirateCommand() .setRackLabel(mReagentContainer.getTecanLabel()) .setRackId(mReagentContainer.getRackId()) .setRackName(mReagentContainer.getTecanLabwareType().name()) .setPosition(tip) .setVolume(reagentAspriateVol.floatValue()) .setLiquidClass(mReagentAspirateLiquidClass != null ? mReagentAspirateLiquidClass : getAspirateLiquidClass()); printStream.println(reagentAspirateRecord.toString()); reagentVolumeRemainingToDispense = reagentVolumeRemainingToDispense.subtract(reagentDispenseVolume); // Do we need to dispense the reagent or can we combine the aspirate with the sample aspirate? if (getTipConfig() != null && (reagentVolumeRemainingToDispense.floatValue() > 0 || reagentAspriateVol.add(aspirateVolume).greaterThan(getTipConfig().getTipType().getMaxCapacity()))) { // Reagent Dispense TecanWorklistDispenseCommand reagentDispenseRecord = new TecanWorklistDispenseCommand() .setRackLabel(currentDestPlate.getTecanLabel()) .setRackId(currentDestPlate.getRackId()) .setRackName(currentDestPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(currentDestPlate.getType(), destWellRef)) .setVolume(reagentDispenseVolume.floatValue()) .setLiquidClass(getDispenseLiquidClass()); printStream.println(reagentDispenseRecord.toString()); } else { // Let the sample dispense know we have already aspirated some reagent that needs to be included reagentDispenseVolumeToInclude = reagentDispenseVolume; } } while (reagentVolumeRemainingToDispense.floatValue() > 0); } if (dispenseVolume.floatValue() > 0) { // 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; if (inMode.equals(Mode.normal)) { if (reagentDispenseVolumeToInclude != null) { dispenseVolume = dispenseVolume.add(reagentDispenseVolumeToInclude); } // Aspirate TecanWorklistAspirateCommand aspirateRecord = new TecanWorklistAspirateCommand() .setRackLabel(srcPlate.getTecanLabel()) .setRackId(srcPlate.getRackId()) .setRackName(srcPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(srcPlate.getType(), srcWell.getRef())) .setVolume(aspirateVolume.floatValue()) .setLiquidClass(getAspirateLiquidClass()); printStream.println(aspirateRecord.toString()); // Dispense TecanWorklistDispenseCommand dispenseRecord = new TecanWorklistDispenseCommand() .setRackLabel(currentDestPlate.getTecanLabel()) .setRackId(currentDestPlate.getRackId()) .setRackName(currentDestPlate.getTecanLabwareType().name()) .setPosition(getTecanWellPosition(currentDestPlate.getType(), destWellRef)) .setVolume(dispenseVolume.floatValue()) .setLiquidClass(getDispenseLiquidClass()); printStream.println(dispenseRecord.toString()); if (srcWell.getVolume() != null) { srcWell.subtractVolume(aspirateVolume); } } // Update the volume in the destination plate updateDestinationVolume(destMap, srcWell, currentDestPlate, destWellRef, dispenseVolume); /* PlateSampleLayer sampleLayer; if (StringUtil.isSet(mSampleLayerName)) { sampleLayer = currentDestPlate.getSampleLayer(mSampleLayerName); if (null == sampleLayer) { sampleLayer = new PlateSampleLayer(getSampleClass()); sampleLayer.setName(mSampleLayerName); currentDestPlate.addLayer(sampleLayer); } } else { sampleLayer = currentDestPlate.getSampleLayer(getSampleClass()); if (!CollectionUtil.hasValues(sampleLayer.getSamples(destWellRef)) && srcWell.getSamples() != null) { for (Comparable sample : srcWell.getSamples()) { sampleLayer.addSample(destWellRef, sample); } } } sampleLayer.addVolume(destWellRef, dispenseVolume); destMap.put(destPlateLoc, currentDestPlate.getWell(destWellRef).getSampleVolume()); */ volumeRemainingToDispense = volumeRemainingToDispense.subtract(dispenseVolume); } while (volumeRemainingToDispense.floatValue() > 0); if (inMode.equals(Mode.normal)) { // Tip wash printStream.println(TecanWorklistCommand.WASH.toString()); } tip++; if (tip > numTips) { tip = 1; } } else { // No sample volume to transfer but there might be reagent to transfer whose well we need to reserve // Update the volume in the destination plate updateDestinationVolume(destMap, srcWell, currentDestPlate, destWellRef, dispenseVolume); /* PlateSampleLayer sampleLayer; if (StringUtil.isSet(mSampleLayerName)) { sampleLayer = currentDestPlate.getSampleLayer(mSampleLayerName); if (null == sampleLayer) { sampleLayer = new PlateSampleLayer(getSampleClass()); sampleLayer.setName(mSampleLayerName); currentDestPlate.addLayer(sampleLayer); } } else { sampleLayer = currentDestPlate.getSampleLayer(getSampleClass()); if (null == sampleLayer) { sampleLayer = new PlateSampleLayer(getSampleClass()); sampleLayer.setName(mSampleLayerName); currentDestPlate.addLayer(sampleLayer); } } sampleLayer.addVolume(destWellRef, dispenseVolume); destMap.put(destPlateLoc, dispenseVolume); */ } } } } } } if (inMode.equals(Mode.normal)) { printStream.close(); } } //--------------------------------------------------------------------------- // Updates the volume in the destination plate and in the destination map private void updateDestinationVolume(Map inDestMap, Well inSrcWell, TecanPlate inDestPlate, WellRef inDestWellRef, Quantity inVolume) { PlateSampleLayer sampleLayer; if (StringUtil.isSet(mSampleLayerName)) { sampleLayer = inDestPlate.getSampleLayer(mSampleLayerName); if (null == sampleLayer) { sampleLayer = new PlateSampleLayer(getSampleClass()); sampleLayer.setName(mSampleLayerName); inDestPlate.addLayer(sampleLayer); } } else { sampleLayer = inDestPlate.getSampleLayer(getSampleClass()); if (null == sampleLayer) { sampleLayer = new PlateSampleLayer(getSampleClass()); sampleLayer.setName(mSampleLayerName); inDestPlate.addLayer(sampleLayer); } else if (! CollectionUtil.hasValues(sampleLayer.getSamples(inDestWellRef)) && inSrcWell.getSamples() != null && inVolume.intValue() > 0) // Only transfer samples if some volume was transferred { for (Comparable sample : inSrcWell.getSamples()) { sampleLayer.addSample(inDestWellRef, sample); } } } sampleLayer.addVolume(inDestWellRef, inVolume); inDestMap.put(new PlateLocation(inDestPlate, inDestWellRef), inDestPlate.getWell(inDestWellRef).getSampleVolume()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy