com.hfg.automation.tecan.plateop.TecanCopyPlateOp Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
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.VolumeCheckStringency;
import com.hfg.automation.WellRef;
import com.hfg.automation.platelayer.LayerPlate;
import com.hfg.automation.platelayer.LayerWell;
import com.hfg.automation.platelayer.PlateDataLayer;
import com.hfg.automation.plateop.VolumeMask;
import com.hfg.automation.tecan.TecanDeckPosition;
import com.hfg.automation.tecan.TecanLabwareContainer;
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.exception.ProgrammingException;
import com.hfg.units.Quantity;
import com.hfg.units.Unit;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedMap;
//------------------------------------------------------------------------------
/**
Tecan-specific implementation of the Copy 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 TecanCopyPlateOp extends TecanPerPlateLiquidTransferPlateOp
{
private List mAspirateSources;
private List mDispenseDestinations;
private Integer mNumTips;
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, Unit.valueOf("μL"));
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 TecanCopyPlateOp setAspirateVolumeDataLayerName(String inValue)
{
return (TecanCopyPlateOp) super.setAspirateVolumeDataLayerName(inValue);
}
//---------------------------------------------------------------------------
public TecanCopyPlateOp setAspirateSources(Collection inValues)
{
mAspirateSources = null;
if (CollectionUtil.hasValues(inValues))
{
for (TecanPlate plate : inValues)
{
addAspirateSource(plate);
}
}
return this;
}
//---------------------------------------------------------------------------
public TecanCopyPlateOp addAspirateSource(TecanPlate inValue)
{
aspirateGroupCheck(inValue);
if (null == mAspirateSources)
{
mAspirateSources = new ArrayList<>(10);
}
mAspirateSources.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public List getAspirateSources()
{
return mAspirateSources;
}
//---------------------------------------------------------------------------
public TecanCopyPlateOp setDispenseDestinations(Collection inValues)
{
if (null == mDispenseDestinations)
{
mDispenseDestinations = new ArrayList<>(10);
}
mDispenseDestinations.addAll(inValues);
return this;
}
//---------------------------------------------------------------------------
public TecanCopyPlateOp addDispenseDestination(TecanLabwareContainer inValue)
{
dispenseGroupCheck(inValue);
if (null == mDispenseDestinations)
{
mDispenseDestinations = new ArrayList<>(10);
}
mDispenseDestinations.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public List getDispenseDestinations()
{
return mDispenseDestinations;
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setTipConfig(TecanTipConfig inValue)
{
return (TecanCopyPlateOp) super.setTipConfig(inValue);
}
//---------------------------------------------------------------------------
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!");
}
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setAspirateVolume(Quantity inValue)
{
return (TecanCopyPlateOp) super.setAspirateVolume(inValue);
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setDispenseVolume(Quantity inValue)
{
return (TecanCopyPlateOp) super.setDispenseVolume(inValue);
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setAspirateVolumeMask(VolumeMask inValue)
{
return (TecanCopyPlateOp) super.setAspirateVolumeMask(inValue);
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setDispenseVolumeMask(VolumeMask inValue)
{
return (TecanCopyPlateOp) super.setDispenseVolumeMask(inValue);
}
//---------------------------------------------------------------------------
public TecanCopyPlateOp setNumTips(Integer inValue)
{
mNumTips = inValue;
return this;
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setOutputOrder(Integer inValue)
{
return (TecanCopyPlateOp) super.setOutputOrder(inValue);
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setVolumeCheckStringency(VolumeCheckStringency inValue)
{
return (TecanCopyPlateOp) super.setVolumeCheckStringency(inValue);
}
//---------------------------------------------------------------------------
@Override
public TecanCopyPlateOp setComment(String inValue)
{
return (TecanCopyPlateOp) super.setComment(inValue);
}
//---------------------------------------------------------------------------
public void generateWorklist(OutputStream inStream)
throws IOException
{
generateWorklist(inStream, Mode.normal);
}
/*
//---------------------------------------------------------------------------
public VolumeMask calculatePostOpTotalVolumeMask(Plate inPlate)
{
VolumeMask postOpVolumeMask = null;
if (getAspirateSources().contains(inPlate))
{
// TODO
// What will the src volumes be after the copy operation?
}
else if (getDispenseDestinations().contains(inPlate))
{
postOpVolumeMask = getDispenseDestinationVolumeMask((TecanPlate) inPlate);
if (null == postOpVolumeMask)
{
throw new AutomationConfigurationException(name() + " could not calculate post operation volumes!");
}
}
return postOpVolumeMask;
}
*/
//---------------------------------------------------------------------------
// TODO: What to do if the requested plate is a source location instead of a destination?
public VolumeMask calculatePostOpTotalVolumeMask(Plate inPlate)
throws IOException
{
Map> transferMap = getTransferMap();
VolumeMask postOpVolumeMask = new VolumeMask();
for (Map destVolumeMap : transferMap.values())
{
for (PlateLocation loc : destVolumeMap.keySet())
{
if (loc.getPlate().equals(inPlate))
{
postOpVolumeMask.addVolume(loc.getWellRef(), destVolumeMap.get(loc));
}
}
}
return postOpVolumeMask;
}
//---------------------------------------------------------------------------
public Map> getTransferMap()
{
if (null == mTransferMap)
{
try
{
generateWorklist(null, Mode.dryRun);
}
catch (IOException e)
{
throw new ProgrammingException(e);
}
}
return mTransferMap;
}
//---------------------------------------------------------------------------
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;
}
//---------------------------------------------------------------------------
private void preflightChecks()
{
if (!CollectionUtil.hasValues(mAspirateSources))
{
throw new AutomationConfigurationException("No aspiration sources has been specified!");
}
if (! CollectionUtil.hasValues(mDispenseDestinations))
{
throw new AutomationConfigurationException("No dispense destinations have been specified!");
}
// Ensure that all sources are plates
for (TecanLabwareContainer srcContainer : getAspirateSources())
{
if (! srcContainer.getTecanLabwareType().getGroup().equals(LabwareTypeGroup.Plate))
{
throw new AutomationConfigurationException("Source containers must currently be plates for this PlateOp! "
+ "Source " + srcContainer.getTecanLabel() + " is a " + srcContainer.getTecanLabwareType() + ".");
}
}
// Ensure that all destinations are plates
for (TecanLabwareContainer destContainer : getDispenseDestinations())
{
if (! destContainer.getTecanLabwareType().getGroup().equals(LabwareTypeGroup.Plate))
{
throw new AutomationConfigurationException("Destination containers must currently be plates for this PlateOp! "
+ "Dest " + destContainer.getTecanLabel() + " is a " + destContainer.getTecanLabwareType() + ".");
}
}
// Ensure that all sources have valid Tecan labels
for (TecanLabwareContainer srcContainer : getAspirateSources())
{
if (null == srcContainer.getTecanLabel())
{
throw new AutomationConfigurationException("Src container without a plate label (deck position)!");
}
if (getDeckConfig() != null)
{
TecanDeckPosition deckPosition = getDeckConfig().getDeckPosition(srcContainer.getTecanLabel());
if (null == deckPosition)
{
throw new AutomationConfigurationException("Src container label " + StringUtil.singleQuote(srcContainer.getTecanLabel()) + " is not valid for the specified deck configuration!");
}
else if (! srcContainer.getTecanLabwareType().getClass().isAssignableFrom(deckPosition.getContainerType().getClass()))
{
throw new AutomationConfigurationException("Src container is incompatible with deck position " + StringUtil.singleQuote(srcContainer.getTecanLabel()) + "!");
}
}
}
// Ensure that all destinations have valid Tecan labels
for (TecanLabwareContainer destContainer : getDispenseDestinations())
{
if (null == destContainer.getTecanLabel())
{
throw new AutomationConfigurationException("Destination container without a plate 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()) + "!");
}
}
}
// Ensure that either a constant volume or a volume mask has been specified for the operation.
if (null == getDispenseVolume()
&& null == getDispenseVolumeMask()
&& null == getAspirateVolumeMask()
&& null == getAspirateVolumeDataLayerName()
&& null == getDispenseVolumeDataLayerName()
&& null == ((TecanPlate)getAspirateSources().get(0)).getOccupiedWells().iterator().next().getVolume())
{
throw new AutomationConfigurationException("No dispense volume, VolumeMask, or volume DataLayer has been specified for the plate operation!");
}
}
//---------------------------------------------------------------------------
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 weren't per-plate volume masks.
if (getDispenseVolumeMask() != null)
{
// TODO: Clone before returning?
plateDispenseVolumeMask = getDispenseVolumeMask();
}
else if (getDispenseVolume() != null)
{
plateDispenseVolumeMask = new VolumeMask().addVolume(inPlate.getLayout().getWellRefs(), getDispenseVolume());
}
else if (StringUtil.isSet(getDispenseVolumeDataLayerName()))
{
PlateDataLayer dispenseVolumeDataLayer = inPlate.getDataLayer(getDispenseVolumeDataLayerName());
if (dispenseVolumeDataLayer != null)
{
plateDispenseVolumeMask = new VolumeMask(dispenseVolumeDataLayer);
}
}
else if (getAspirateVolumeMask() != null)
{
// TODO: Clone before returning?
plateDispenseVolumeMask = getAspirateVolumeMask();
}
else if (getAspirateVolume() != null)
{
// Assume aspirate vol = dispense vol
plateDispenseVolumeMask = new VolumeMask().addVolume(inPlate.getLayout().getWellRefs(), getAspirateVolume());
}
}
if (null == plateDispenseVolumeMask)
{
// Don't have enough info
throw new AutomationConfigurationException("No enough information for PlateOp " + name() + " to calculate destination volumes!");
}
return plateDispenseVolumeMask;
}
//---------------------------------------------------------------------------
private 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()));
}
}
mTransferMap = new OrderedMap<>();
int tip = 1;
// TODO: How does the Tecan deal with worklists for multiple plates?
int destPlateIndex = 0;
for (int i = 0; i < getAspirateSources().size(); i++)
{
TecanLabwareContainer srcContainer = getAspirateSources().get(i);
TecanPlate srcPlate = (TecanPlate) srcContainer;
TecanPlate actualDestPlate = (TecanPlate) getDispenseDestinations().get(destPlateIndex);
TecanPlate currentDestPlate = (inMode.equals(Mode.normal) ? actualDestPlate : actualDestPlate.clone());
int destWellIndex = 0;
if (inMode.equals(Mode.normal)
&& getDeckConfig() != null)
{
TecanLabwareContainer currentContainerAtSrcPosition = getDeckConfig().getDeckPosition(srcContainer.getTecanLabel()).getContainer();
if (null == currentContainerAtSrcPosition
|| ! currentContainerAtSrcPosition.equals(srcContainer))
{
String comment = "Place src " + (StringUtil.isSet(srcContainer.getRackId()) ? srcContainer.getRackId() + " " : "")
+ "at deck position " + srcContainer.getTecanLabel();
printStream.println(new TecanWorklistCommentCommand(comment).toString());
getDeckConfig().getDeckPosition(srcContainer.getTecanLabel()).setContainer(srcContainer);
}
TecanLabwareContainer currentContainerAtDestPosition = getDeckConfig().getDeckPosition(actualDestPlate.getTecanLabel()).getContainer();
if (null == currentContainerAtDestPosition
|| ! currentContainerAtDestPosition.equals(actualDestPlate))
{
String comment = "Place dest " + (StringUtil.isSet(actualDestPlate.getRackId()) ? actualDestPlate.getRackId() + " " : "")
+ "at deck position " + actualDestPlate.getTecanLabel();
printStream.println(new TecanWorklistCommentCommand(comment).toString());
getDeckConfig().getDeckPosition(actualDestPlate.getTecanLabel()).setContainer(actualDestPlate);
}
}
if (! srcContainer.getTecanLabwareType().getGroup().equals(LabwareTypeGroup.Plate))
{
throw new AutomationConfigurationException("Source containers must currently be plates for this PlateOp!");
}
// TODO: Test for a plate type match between the plate and the plate layout
for (WellRef srcWellRef : srcPlate.getLayout().getWellRefs(false))
// for (LayerWell srcWell : srcPlate.getOccupiedWells())
{
PlateLocation srcPlateLoc = new PlateLocation(srcPlate, srcWellRef);
Map destMap = mTransferMap.get(srcPlateLoc);
if (null == destMap)
{
destMap = new OrderedMap<>(2);
mTransferMap.put(srcPlateLoc, destMap);
}
WellRef destWellRef;
if (destWellIndex < currentDestPlate.getType().size())
{
destWellRef = currentDestPlate.getLayout().getWellRefFromIndex(destWellIndex++);
}
else
{
destPlateIndex++;
if (destPlateIndex == getDispenseDestinations().size())
{
throw new AutomationConfigurationException("Not enough destination plates for the operation to complete!");
}
actualDestPlate = (TecanPlate) getDispenseDestinations().get(destPlateIndex);
currentDestPlate = (inMode.equals(Mode.normal) ? actualDestPlate : actualDestPlate.clone());
destWellIndex = 0; // Reset the well index
destWellRef = currentDestPlate.getLayout().getWellRefFromIndex(destWellIndex++);
}
PlateLocation destPlateLoc = new PlateLocation(actualDestPlate, destWellRef);
destMap.put(destPlateLoc, null);
LayerWell srcWell = srcPlate.getWell(srcWellRef);
if (null == srcWell)
{
continue;
}
// Honor the selection layer if present
if (srcWell.getSelectionLayer() != null
&& ! srcWell.getSelectionLayer().isSelected())
{
// This well wasn't selected. Skip it.
continue;
}
// What is the volume to be aspirated?
Quantity aspirateVolume = getAspirateVolume();
if (null == aspirateVolume
&& getAspirateVolumeMask() != null)
{
aspirateVolume = getAspirateVolumeMask().getVolume(srcWell.getRef());
}
else if (StringUtil.isSet(getAspirateVolumeDataLayerName()))
{
PlateDataLayer aspirateVolumeDataLayer = srcPlate.getDataLayer(getAspirateVolumeDataLayerName());
if (aspirateVolumeDataLayer != null)
{
aspirateVolume = aspirateVolumeDataLayer.getData(srcWellRef);
if (null == aspirateVolume)
{
aspirateVolume = ZERO_VOLUME;
}
}
}
else if (srcWell.getVolume() != null)
{
aspirateVolume = srcWell.getVolume();
}
// What is the volume to be dispensed?
Quantity dispenseVolume = getDispenseVolume();
if (null == dispenseVolume)
{
if (getDispenseVolumeMask() != null)
{
dispenseVolume = getDispenseVolumeMask().getVolume(srcWell.getRef());
}
else if (StringUtil.isSet(getDispenseVolumeDataLayerName()))
{
PlateDataLayer dispenseVolumeDataLayer = currentDestPlate.getDataLayer(getDispenseVolumeDataLayerName());
if (dispenseVolumeDataLayer != null)
{
dispenseVolume = dispenseVolumeDataLayer.getData(destWellRef);
if (null == aspirateVolume)
{
dispenseVolume = ZERO_VOLUME;
}
}
}
}
/*
if (CollectionUtil.hasValues(getPerPlateVolumeMasks())
&& null == dispenseVolume)
{
continue;
}
*/
// 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 (null == aspirateVolume)
{
throw new RuntimeException("No " + srcWell.getRef() + " volume or volume mask value specified for the operation!");
}
// 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 " + srcWell.getRef() + " is larger than the aspirate volume!";
if (getVolumeCheckStringency().equals(VolumeCheckStringency.warning))
{
addWarning(msg);
}
else if (getVolumeCheckStringency().equals(VolumeCheckStringency.error))
{
throw new AutomationConfigurationException(msg);
}
}
if (aspirateVolume.floatValue() > 0)
{
// 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 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))
{
// Aspirate
TecanWorklistAspirateCommand aspirateRecord = new TecanWorklistAspirateCommand()
.setRackLabel(srcContainer.getTecanLabel())
.setRackId(srcContainer.getRackId())
.setRackName(srcContainer.getTecanLabwareType().name())
.setPosition(getTecanWellPosition(srcPlate.getType(), srcWell.getRef()))
.setVolume(Math.round(aspirateVolume.floatValue()))
.setLiquidClass(getAspirateLiquidClass());
if (mNumTips != null)
{
aspirateRecord.setPosition(tip++);
}
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(Math.round(dispenseVolume.floatValue()))
.setLiquidClass(getDispenseLiquidClass());
printStream.println(dispenseRecord.toString());
// Tip wash
printStream.println(TecanWorklistCommand.WASH.toString());
if (mNumTips != null
&& tip > mNumTips)
{
tip = 1;
}
}
// TODO: We are not currently removing the aspirate volume from the source well
// Update the volume in the destination plate
LayerWell well = currentDestPlate.getWell(destWellRef);
if (null == well)
{
well = currentDestPlate.allocateWell(destWellRef);
}
if (srcWell.getSamples() != null)
{
boolean volumeAdded = false;
for (Comparable sample : srcWell.getSamples())
{
if (! volumeAdded)
{
well.addSample(sample, dispenseVolume);
}
else
{
well.addSample(sample);
}
}
}
else
{
well.addVolume(dispenseVolume);
}
destMap.put(destPlateLoc, well.getSampleVolume());
volumeRemainingToDispense = volumeRemainingToDispense.subtract(dispenseVolume);
} while (volumeRemainingToDispense.floatValue() > 0);
}
else
{
destMap.put(destPlateLoc, ZERO_VOLUME);
}
}
destPlateIndex++;
}
if (inMode.equals(Mode.normal))
{
printStream.close();
}
}
}