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

com.hfg.automation.tecan.TecanProtocol Maven / Gradle / Ivy

package com.hfg.automation.tecan;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.hfg.automation.LabwareContainer;
import com.hfg.automation.PlateLocation;
import com.hfg.automation.PlateUtil;
import com.hfg.automation.VolumeCheckStringency;
import com.hfg.automation.WellRef;
import com.hfg.automation.platelayer.PlateDataLayer;
import com.hfg.automation.plateop.AddReagentPlateOp;
import com.hfg.automation.plateop.LiquidTransferPlateOp;
import com.hfg.automation.plateop.PlateOp;
import com.hfg.automation.plateop.VolumeMask;
import com.hfg.automation.tecan.plateop.TecanAddReagentPlateOp;
import com.hfg.automation.tecan.plateop.TecanCopyPlateOp;
import com.hfg.automation.tecan.plateop.TecanPlateOp;
import com.hfg.automation.tecan.plateop.TecanRearrayPlateOp;
import com.hfg.automation.tecan.worklistcmd.TecanWorklistCommentCommand;
import com.hfg.datetime.DateUtil;
import com.hfg.units.Quantity;
import com.hfg.util.CompareUtil;
import com.hfg.util.FileUtil;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.collection.OrderedMap;
import com.hfg.util.io.FileBytes;


//------------------------------------------------------------------------------
/**
 A collection of Tecan Plate Operations intended to be executed as a set.
 
@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 TecanProtocol { private String mName; private TecanDeckConfig mDeckConfig; private List mPlateOps; private Mode mMode = Mode.worklist_per_plate_op; private String mComment; private boolean mAddTimestampToComment = true; private VolumeCheckStringency mVolumeCheckStringency = VolumeCheckStringency.error; private List mWarnings; private boolean mGenerateTransferSummaryTSV; private Map> mTransferMap; public enum Mode { single_worklist , worklist_per_plate_op // , worklist_per_source_plate } private static final String REAGENT_DISPENSE_VOL_DATA_LAYER_NAME = "Reagent Dispense Volume"; //########################################################################### // CONSTRUCTORS //########################################################################### //--------------------------------------------------------------------------- public TecanProtocol() { } //--------------------------------------------------------------------------- public TecanProtocol(String inValue) { this(); setName(inValue); } //########################################################################### // PUBLIC METHODS //########################################################################### //--------------------------------------------------------------------------- public TecanProtocol setDeckConfig(TecanDeckConfig inValue) { mDeckConfig = inValue; return this; } //--------------------------------------------------------------------------- public TecanDeckConfig getDeckConfig() { return mDeckConfig; } //--------------------------------------------------------------------------- public TecanProtocol setVolumeCheckStringency(VolumeCheckStringency inValue) { mVolumeCheckStringency = inValue; return this; } //--------------------------------------------------------------------------- public VolumeCheckStringency getVolumeCheckStringency() { return mVolumeCheckStringency; } //--------------------------------------------------------------------------- public boolean producedWarnings() { return CollectionUtil.hasValues(mWarnings); } //--------------------------------------------------------------------------- public List getWarnings() { return mWarnings; } //--------------------------------------------------------------------------- public TecanProtocol setAddTimestampToComment(boolean inValue) { mAddTimestampToComment = inValue; return this; } //--------------------------------------------------------------------------- public TecanProtocol setGenerateRearrayTSV(boolean inValue) { mGenerateTransferSummaryTSV = inValue; return this; } //--------------------------------------------------------------------------- public TecanProtocol setComment(String inValue) { mComment = inValue; return this; } //--------------------------------------------------------------------------- public TecanProtocol setName(String inValue) { mName = inValue; return this; } //--------------------------------------------------------------------------- public String name() { return mName; } //--------------------------------------------------------------------------- @Override public String toString() { return name(); } //--------------------------------------------------------------------------- public TecanProtocol setMode(Mode inValue) { mMode = inValue; return this; } //--------------------------------------------------------------------------- public Mode getMode() { return mMode; } //--------------------------------------------------------------------------- public TecanProtocol addPlateOp(TecanPlateOp inValue) { if (null == mPlateOps) { mPlateOps = new ArrayList<>(5); } mPlateOps.add(inValue); return this; } //--------------------------------------------------------------------------- public List getPlateOps() { return mPlateOps; } //-------------------------------------------------------------------------- public List generateWorklists(String inWorklistFileBaseName) throws IOException { Map opWorklistMap = new HashMap<>(getPlateOps().size()); List outputFiles = new ArrayList<>(5); /* This shouldn't be necessary any more since the rearray is now based on a particular sample layer Map> origOccupiedWellMap = new HashMap<>(); for (int i = 0; i < mPlateOps.size(); i++) { PlateOp plateOp = mPlateOps.get(i); if (TecanRearrayPlateOp.class.isAssignableFrom(plateOp.getClass())) { for (TecanPlate destPlate : (Collection) plateOp.getDispenseDestinations()) { origOccupiedWellMap.put(destPlate, destPlate.getOccupiedWells()); } } } */ mTransferMap = new OrderedMap<>(); for (int i = 0; i < mPlateOps.size(); i++) { TecanPlateOp plateOp = mPlateOps.get(i); if (getDeckConfig() != null) { plateOp.setDeckConfig(getDeckConfig()); } /* This shouldn't be necessary any more since the rearray is now based on a particular sample layer if (TecanRearrayPlateOp.class.isAssignableFrom(plateOp.getClass())) { for (TecanPlate destPlate : (List) plateOp.getDispenseDestinations()) { destPlate.setWells(origOccupiedWellMap.get(destPlate)); } } */ if (plateOp instanceof AddReagentPlateOp) { TecanAddReagentPlateOp addReagentPlateOp = (TecanAddReagentPlateOp) plateOp; // Do we need to populate volume masks? if (null == addReagentPlateOp.getAspirateVolumeMask() && null == addReagentPlateOp.getDispenseVolumeMask() && (addReagentPlateOp.getTotalVolumeTarget() != null || (StringUtil.isSet(addReagentPlateOp.getDispenseVolumeSrcDataLayerName())))) { if (addReagentPlateOp.getTotalVolumeTarget() != null) { // Use the total volume target to calculate a dispense data layer per-destination plate for the reagent transfer for (TecanPlate destPlate : (List) addReagentPlateOp.getDispenseDestinations()) { VolumeMask volumeMask = new VolumeMask(); for (PlateOp otherPlateOp : mPlateOps) { if (otherPlateOp != addReagentPlateOp && otherPlateOp instanceof LiquidTransferPlateOp) { // TODO: Check aspirate sources as well for (LabwareContainer otherPlate : ((LiquidTransferPlateOp) otherPlateOp).getDispenseDestinations()) { if (otherPlate.equals(destPlate)) { VolumeMask otherPlateVolumeMask = ((LiquidTransferPlateOp) otherPlateOp).calculatePostOpTotalVolumeMask(destPlate); volumeMask.addVolume(otherPlateVolumeMask); break; } } } } // The reagent transfer volume is the target total volume minus the combined total volume from the other operations volumeMask.subtractFrom(addReagentPlateOp.getTotalVolumeTarget()); PlateDataLayer dispenseVolumeDataLayer = new PlateDataLayer(REAGENT_DISPENSE_VOL_DATA_LAYER_NAME, Quantity.class); if (CollectionUtil.hasValues(volumeMask.getOccupiedWellRefs())) { for (WellRef wellRef : volumeMask.getOccupiedWellRefs()) { dispenseVolumeDataLayer.addData(wellRef, volumeMask.getVolume(wellRef)); } } destPlate.addLayer(dispenseVolumeDataLayer); } } else if (StringUtil.isSet(addReagentPlateOp.getDispenseVolumeSrcDataLayerName())) { for (TecanPlate destPlate : (List) addReagentPlateOp.getDispenseDestinations()) { PlateDataLayer dispenseVolumeDataLayer = new PlateDataLayer("Reagent Dispense Volume", Quantity.class); for (WellRef wellRef : destPlate.getLayout().getWellRefs()) { PlateLocation destPlateLoc = new PlateLocation(destPlate, wellRef); for (PlateOp otherPlateOp : mPlateOps) { if (otherPlateOp != addReagentPlateOp && otherPlateOp instanceof LiquidTransferPlateOp) { Quantity transferVolume = ((LiquidTransferPlateOp) otherPlateOp).getDestinationVolumeFromSrcDataLayer(destPlateLoc, addReagentPlateOp.getDispenseVolumeSrcDataLayerName()); if (transferVolume != null) { dispenseVolumeDataLayer.addData(wellRef, transferVolume); } } } } destPlate.addLayer(dispenseVolumeDataLayer); } } addReagentPlateOp.setDispenseVolumeDataLayerName(REAGENT_DISPENSE_VOL_DATA_LAYER_NAME); } } // TODO: Handle other plateOp types ByteArrayOutputStream byteStream = new ByteArrayOutputStream(50 * 1024); plateOp.generateWorklist(byteStream); // Were there any warnings that we need to bubble up? if (plateOp.producedWarnings()) { addWarnings(plateOp.getWarnings()); } String worklistName = inWorklistFileBaseName + " " + (i + 1) + ".gwl"; FileBytes fileBytes = new FileBytes(worklistName); fileBytes.setData(new ByteArrayInputStream(byteStream.toByteArray())); opWorklistMap.put(plateOp, fileBytes); byteStream.reset(); if (TecanRearrayPlateOp.class.isAssignableFrom(plateOp.getClass())) { mTransferMap.putAll(((TecanRearrayPlateOp)plateOp).getTransferMap()); } else if (TecanCopyPlateOp.class.isAssignableFrom(plateOp.getClass())) { mTransferMap.putAll(((TecanCopyPlateOp)plateOp).getTransferMap()); } } if (mGenerateTransferSummaryTSV) { outputFiles.add(PlateUtil.generateTransferSummaryTSV(inWorklistFileBaseName + ".tsv", mTransferMap)); } if (getMode().equals(Mode.single_worklist)) { List sortedPlateOps = new ArrayList<>(mPlateOps); Collections.sort(sortedPlateOps, new OpOutputOrderComparator()); // TODO: Sanity check the output ordered ops. // Ex: there shouldn't be 2 set tip operations next to ea. other FileBytes fileBytes = new FileBytes(inWorklistFileBaseName + ".gwl"); if (StringUtil.isSet(mComment)) { StringBuilderPlus commentBlock = new StringBuilderPlus(); commentBlock.appendln(new TecanWorklistCommentCommand(mComment).toString()); if (mAddTimestampToComment) { commentBlock.appendln(new TecanWorklistCommentCommand("Generated " + DateUtil.getYYYY_MM_DD_HH_mm_ss()).toString()); } commentBlock.appendln(new TecanWorklistCommentCommand("*****************************************************").toString()); fileBytes.appendData(commentBlock.toString().getBytes()); } for (TecanPlateOp plateOp : sortedPlateOps) { fileBytes.appendData(opWorklistMap.get(plateOp)); } outputFiles.add(fileBytes); } else { outputFiles.addAll(opWorklistMap.values()); } return outputFiles; } //-------------------------------------------------------------------------- public void writeWorklists(File inWorklistFile) throws IOException { List worklists = generateWorklists(FileUtil.getNameMinusExtension(inWorklistFile)); if (1 == worklists.size()) { FileUtil.write(inWorklistFile, worklists.get(0).getDataStream()); } else { createZipFile(inWorklistFile, worklists); } } //--------------------------------------------------------------------------- public Map> getTransferMap() { return mTransferMap; } //--------------------------------------------------------------------------- protected void addWarnings(Collection inValues) { if (null == mWarnings) { mWarnings = new ArrayList<>(10); } mWarnings.addAll(inValues); } //-------------------------------------------------------------------------- private void createZipFile(File inWorklistFile, List inWorklists) throws IOException { File zipFile = new File(FileUtil.getNameMinusExtension(inWorklistFile) + ".zip"); ZipOutputStream zipStream = null; try { zipStream = new ZipOutputStream(new FileOutputStream(zipFile)); for (FileBytes worklist : inWorklists) { ZipEntry zipEntry = new ZipEntry(FileUtil.getNameMinusExtension(zipFile) + "/" + worklist.name()); zipStream.putNextEntry(zipEntry); worklist.writeData(zipStream); zipStream.closeEntry(); } } finally { if (zipStream != null) { zipStream.finish(); } } } private class OpOutputOrderComparator implements Comparator { //----------------------------------------------------------------------- @Override public int compare(TecanPlateOp inObj1, TecanPlateOp inObj2) { return CompareUtil.compare(inObj1.getOutputOrder(), inObj2.getOutputOrder()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy