com.hfg.automation.PlateLayout 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;
import java.util.*;
import com.hfg.util.CompareUtil;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLNode;
import com.hfg.xml.XMLTag;
//------------------------------------------------------------------------------
/**
Rules/pattern for the allocation of samples or controls into a sample plate.
@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 PlateLayout implements Comparator, Comparable
{
private PlateType mPlateType;
private String mPlateNamePrefix = sDefaultPlateNamePrefix;
private Order mOrder = sDefaultOrder;
private WellRef mFirstLocation = sDefaultFirstLocation;
private Continuation mFirstLocationContinuation = sDefaultContinuation;
private WellRef mLastLocation;
private Continuation mLastLocationContinuation = sDefaultContinuation;
private Set mPositiveControlLocations;
private Set mNegativeControlLocations;
private Set mBlankLocations;
private Set mLocationsToKeepEmpty;
private List mAllWellRefs;
private List mRemainingRandomWellRefs;
public static enum Order
{
byCol,
byRow,
byQuadrantColRow,
byQuadrantRowCol,
random
}
public static enum Continuation
{
firstPlateOnly,
allPlates
}
private static Order sDefaultOrder = Order.byCol;
private static WellRef sDefaultFirstLocation = new WellRef("A01");
private static Continuation sDefaultContinuation = Continuation.allPlates;
private static String sDefaultPlateNamePrefix = "PLATE_";
//###########################################################################
// CONSTRUCTORS
//###########################################################################
//---------------------------------------------------------------------------
public PlateLayout(PlateType inPlateType)
{
mPlateType = inPlateType;
mLastLocation = new WellRef().setColIndex(mPlateType.getColumns()).setRowIndex(mPlateType.getRows());
}
//--------------------------------------------------------------------------
public PlateLayout(XMLNode inXML)
{
inXML.verifyTagName(AutomationXML.PLATE_LAYOUT);
mPlateType = (PlateType.valueOf(inXML.getAttributeValue(AutomationXML.PLATE_TYPE_ATT)));
setPlateNamePrefix(inXML.getAttributeValue(AutomationXML.PLATE_NAME_PREFIX_ATT));
if (inXML.hasAttribute(AutomationXML.WELL_ORDER_ATT))
{
setOrder(Order.valueOf(inXML.getAttributeValue(AutomationXML.WELL_ORDER_ATT)));
}
if (inXML.hasAttribute(AutomationXML.FIRST_WELL_ATT))
{
setFirstSampleWellRef(new WellRef(inXML.getAttributeValue(AutomationXML.FIRST_WELL_ATT)),
Continuation.valueOf(inXML.getAttributeValue(AutomationXML.CONTINUATION_ATT)));
}
if (inXML.hasAttribute(AutomationXML.LAST_WELL_ATT))
{
setLastSampleWellRef(new WellRef(inXML.getAttributeValue(AutomationXML.LAST_WELL_ATT)),
Continuation.valueOf(inXML.getAttributeValue(AutomationXML.CONTINUATION_ATT)));
}
XMLNode positiveControlWellsTag = inXML.getOptionalSubtagByName(AutomationXML.POSITIVE_CONTROL_WELLS);
if (positiveControlWellsTag != null)
{
String[] wellRefs = positiveControlWellsTag.getContent().split("[\\s,]+");
for (String wellRef : wellRefs)
{
specifyPositiveControlWell(new WellRef(wellRef));
}
}
XMLNode negativeControlWellsTag = inXML.getOptionalSubtagByName(AutomationXML.NEGATIVE_CONTROL_WELLS);
if (negativeControlWellsTag != null)
{
String[] wellRefs = negativeControlWellsTag.getContent().split("[\\s,]+");
for (String wellRef : wellRefs)
{
specifyNegativeControlWell(new WellRef(wellRef));
}
}
XMLNode blankWellsTag = inXML.getOptionalSubtagByName(AutomationXML.BLANK_WELLS);
if (blankWellsTag != null)
{
String[] wellRefs = blankWellsTag.getContent().split("[\\s,]+");
for (String wellRef : wellRefs)
{
specifyBlankWell(new WellRef(wellRef));
}
}
XMLNode wellsToKeepEmptyTag = inXML.getOptionalSubtagByName(AutomationXML.WELLS_TO_KEEP_EMPTY);
if (wellsToKeepEmptyTag != null)
{
String[] wellRefs = wellsToKeepEmptyTag.getContent().split("[\\s,]+");
for (String wellRef : wellRefs)
{
specifyWellToKeepEmpty(new WellRef(wellRef));
}
}
}
//###########################################################################
// PUBLIC METHODS
//###########################################################################
//--------------------------------------------------------------------------
public XMLNode toXMLNode()
{
XMLNode node = new XMLTag(AutomationXML.PLATE_LAYOUT);
node.setAttribute(AutomationXML.PLATE_TYPE_ATT, getPlateType());
node.setAttribute(AutomationXML.PLATE_NAME_PREFIX_ATT, mPlateNamePrefix);
node.setAttribute(AutomationXML.WELL_ORDER_ATT, getOrder());
if (getFirstSampleWellRef() != null)
{
node.setAttribute(AutomationXML.FIRST_WELL_ATT, getFirstSampleWellRef());
node.setAttribute(AutomationXML.CONTINUATION_ATT, mFirstLocationContinuation);
}
if (getLastSampleWellRef() != null)
{
node.setAttribute(AutomationXML.LAST_WELL_ATT, getLastSampleWellRef());
node.setAttribute(AutomationXML.CONTINUATION_ATT, mLastLocationContinuation);
}
if (CollectionUtil.hasValues(getPositiveControlWells()))
{
XMLNode subtag = new XMLTag(AutomationXML.POSITIVE_CONTROL_WELLS);
subtag.setContent(StringUtil.join(getPositiveControlWells(), ", "));
node.addSubtag(subtag);
}
if (CollectionUtil.hasValues(getNegativeControlWells()))
{
XMLNode subtag = new XMLTag(AutomationXML.NEGATIVE_CONTROL_WELLS);
subtag.setContent(StringUtil.join(getNegativeControlWells(), ", "));
node.addSubtag(subtag);
}
if (CollectionUtil.hasValues(getBlankLocations()))
{
XMLNode subtag = new XMLTag(AutomationXML.BLANK_WELLS);
subtag.setContent(StringUtil.join(getBlankLocations(), ", "));
node.addSubtag(subtag);
}
if (CollectionUtil.hasValues(getWellsToKeepEmpty()))
{
XMLNode subtag = new XMLTag(AutomationXML.WELLS_TO_KEEP_EMPTY);
subtag.setContent(StringUtil.join(getWellsToKeepEmpty(), ", "));
node.addSubtag(subtag);
}
return node;
}
//---------------------------------------------------------------------------
@Override
public int hashCode()
{
int hashCode = getPlateType().hashCode();
hashCode += 31 * getOrder().hashCode();
if (getFirstSampleWellRef() != null)
{
hashCode += 31 * getFirstSampleWellRef().hashCode();
}
if (getLastSampleWellRef() != null)
{
hashCode += 31 * getLastSampleWellRef().hashCode();
}
// TODO: Use other fields
return hashCode;
}
//---------------------------------------------------------------------------
@Override
public boolean equals(Object inObj2)
{
return (0 == compareTo(inObj2));
}
//---------------------------------------------------------------------------
@Override
public int compareTo(Object inObj2)
{
int result = -1;
if (inObj2 != null
&& inObj2 instanceof PlateLayout)
{
result = 0;
if (this != inObj2)
{
PlateLayout plateLayout2 = (PlateLayout) inObj2;
result = CompareUtil.compare(getPlateType(), plateLayout2.getPlateType());
if (0 == result)
{
result = CompareUtil.compare(getOrder(), plateLayout2.getOrder());
}
if (0 == result)
{
result = CompareUtil.compare(getFirstSampleWellRef(), plateLayout2.getFirstSampleWellRef());
}
if (0 == result)
{
result = CompareUtil.compare(getLastSampleWellRef(), plateLayout2.getLastSampleWellRef());
}
// TODO: Use other fields
}
}
return result;
}
//---------------------------------------------------------------------------
@Override
public int compare(Well inWell1, Well inWell2)
{
int result = 0;
if (! inWell1.equals(inWell2))
{
result = compare(inWell1.getRef(), inWell2.getRef());
}
return result;
}
//---------------------------------------------------------------------------
public int compare(WellRef inWellRef1, WellRef inWellRef2)
{
int result = 0;
if (! inWellRef1.equals(inWellRef2))
{
if (getOrder().equals(Order.byCol))
{
result = CompareUtil.compare(inWellRef1.getRowIndex() + (inWellRef1.getColIndex() - 1) * getPlateType().getRows(),
inWellRef2.getRowIndex() + (inWellRef2.getColIndex() - 1) * getPlateType().getRows());
}
else if (getOrder().equals(Order.byRow))
{
result = CompareUtil.compare(inWellRef1.getColIndex() + (inWellRef1.getRowIndex() - 1) * getPlateType().getColumns(),
inWellRef2.getColIndex() + (inWellRef2.getRowIndex() - 1) * getPlateType().getColumns());
}
}
return result;
}
//---------------------------------------------------------------------------
public List allocateSamples(List extends Comparable> inSamples, PlateFactory inPlateFactory)
{
List plates = null;
T currentPlate = null;
WellRef lastWellRef = null;
if (CollectionUtil.hasValues(inSamples))
{
plates = new ArrayList<>((inSamples.size() / getPlateType().size()) + 1);
for (Comparable sample : inSamples)
{
if (null == currentPlate)
{
currentPlate = (T) inPlateFactory.createPlate(getPlateType()).setLayout(this).setName(mPlateNamePrefix + (plates.size() + 1));
plates.add(currentPlate);
lastWellRef = null;
}
WellRef wellRef;
if (null == lastWellRef
&& ! getOrder().equals(Order.random))
{
if (! mFirstLocationContinuation.equals(Continuation.allPlates)
&& plates.size() > 1)
{
wellRef = sDefaultFirstLocation;
}
else
{
wellRef = getFirstSampleWellRef();
}
}
else
{
// Determine the next well to allocate
wellRef = nextWellRef(lastWellRef);
}
currentPlate.allocateWell(wellRef).addSample(sample);
if ((! mOrder.equals(Order.random)
&& getLastSampleWellRef() != null
&& wellRef.equals(getLastSampleWellRef())
&& (mLastLocationContinuation.equals(Continuation.allPlates)
|| 1 == plates.size()))
|| currentPlate.isFull())
{
currentPlate = null;
if (mOrder.equals(Order.random))
{
mRemainingRandomWellRefs = null;
}
}
lastWellRef = wellRef;
}
}
return plates;
}
//---------------------------------------------------------------------------
public PlateType getPlateType()
{
return mPlateType;
}
//---------------------------------------------------------------------------
public PlateLayout setPlateNamePrefix(String inValue)
{
mPlateNamePrefix = inValue;
return this;
}
//---------------------------------------------------------------------------
public PlateLayout setOrder(Order inValue)
{
mOrder = inValue;
if (mOrder.equals(Order.random))
{
mFirstLocation = null;
}
return this;
}
//---------------------------------------------------------------------------
public Order getOrder()
{
return mOrder;
}
//---------------------------------------------------------------------------
public PlateLayout setFirstSampleWellRef(WellRef inValue, Continuation inContinuation)
{
mFirstLocation = inValue;
mFirstLocationContinuation = inContinuation;
return this;
}
//---------------------------------------------------------------------------
public WellRef getFirstSampleWellRef()
{
return mFirstLocation;
}
//---------------------------------------------------------------------------
public PlateLayout setLastSampleWellRef(WellRef inValue, Continuation inContinuation)
{
mLastLocation = inValue;
mLastLocationContinuation = inContinuation;
return this;
}
//---------------------------------------------------------------------------
public PlateLayout specifyWells(WellRange inValues, WellType inType)
{
switch (inType)
{
case EMPTY:
if (null == mLocationsToKeepEmpty)
{
mLocationsToKeepEmpty = new HashSet<>(10);
}
mLocationsToKeepEmpty.addAll(Arrays.asList(inValues.toArray()));
break;
case BLANK:
if (null == mBlankLocations)
{
mBlankLocations = new HashSet<>(10);
}
mBlankLocations.addAll(Arrays.asList(inValues.toArray()));
break;
case POSITIVE_CONTROL:
if (null == mPositiveControlLocations)
{
mPositiveControlLocations = new HashSet<>(10);
}
mPositiveControlLocations.addAll(Arrays.asList(inValues.toArray()));
break;
case NEGATIVE_CONTROL:
if (null == mNegativeControlLocations)
{
mNegativeControlLocations = new HashSet<>(10);
}
mNegativeControlLocations.addAll(Arrays.asList(inValues.toArray()));
break;
default:
throw new RuntimeException(inType + " wells not yest supported for this method!");
}
return this;
}
//---------------------------------------------------------------------------
public WellRef getLastSampleWellRef()
{
return mLastLocation;
}
//---------------------------------------------------------------------------
public PlateLayout specifyBlankWell(WellRef inValue)
{
if (null == mBlankLocations)
{
mBlankLocations = new HashSet<>(5);
}
mBlankLocations.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public Set getBlankLocations()
{
return mBlankLocations;
}
//---------------------------------------------------------------------------
public PlateLayout specifyWellToKeepEmpty(WellRef inValue)
{
if (null == mLocationsToKeepEmpty)
{
mLocationsToKeepEmpty = new HashSet<>(5);
}
mLocationsToKeepEmpty.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public Set getWellsToKeepEmpty()
{
return mLocationsToKeepEmpty;
}
//---------------------------------------------------------------------------
public PlateLayout specifyPositiveControlWell(WellRef inValue)
{
if (null == mPositiveControlLocations)
{
mPositiveControlLocations = new HashSet<>(5);
}
mPositiveControlLocations.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public Set getPositiveControlWells()
{
return mPositiveControlLocations;
}
//---------------------------------------------------------------------------
public PlateLayout specifyNegativeControlWell(WellRef inValue)
{
if (null == mNegativeControlLocations)
{
mNegativeControlLocations = new HashSet<>(5);
}
mNegativeControlLocations.add(inValue);
return this;
}
//---------------------------------------------------------------------------
public Set getNegativeControlWells()
{
return mNegativeControlLocations;
}
//---------------------------------------------------------------------------
public List getWellRefs()
{
return getWellRefs(true);
}
//---------------------------------------------------------------------------
public List getWellRefs(boolean inReturnOnlySampleWells)
{
List wellRefs = new ArrayList<>(getPlateType().size());
WellRef wellRef = null;
mRemainingRandomWellRefs = null;
if (mOrder.equals(Order.random))
{
wellRef = nextWellRef(null, inReturnOnlySampleWells);
wellRefs.add(wellRef);
}
/*
else
{
wellRef = getFirstSampleWellRef();
}
wellRefs.add(wellRef);
*/
while ((wellRef = nextWellRef(wellRef, inReturnOnlySampleWells)) != null)
{
wellRefs.add(wellRef);
if (mOrder.equals(Order.random)
&& ! CollectionUtil.hasValues(mRemainingRandomWellRefs))
{
break;
}
}
return wellRefs;
}
//---------------------------------------------------------------------------
public WellRef getWellRefFromIndex(int inIndex)
{
if (null == mAllWellRefs)
{
mAllWellRefs = getWellRefs(false);
}
return mAllWellRefs.get(inIndex);
}
//---------------------------------------------------------------------------
public ListIterator wellRefIterator()
{
return getWellRefs().listIterator();
}
//---------------------------------------------------------------------------
private WellRef nextWellRef(WellRef inPrevWellRef)
{
return nextWellRef(inPrevWellRef, true);
}
//---------------------------------------------------------------------------
private WellRef nextWellRef(WellRef inPrevWellRef, boolean inReturnOnlySampleWells)
{
if (mOrder.equals(Order.random)
&& null == mRemainingRandomWellRefs)
{
mRemainingRandomWellRefs = new ArrayList<>(getPlateType().size());
for (int row = 1; row <= getPlateType().getRows(); row++)
{
for (int col = 1; col <= getPlateType().getColumns(); col++)
{
WellRef wellRef = new WellRef().setColIndex(col).setRowIndex(row);
if ((null == mLocationsToKeepEmpty
|| ! mLocationsToKeepEmpty.contains(wellRef))
&& (null == mBlankLocations
|| ! mBlankLocations.contains(wellRef))
&& (null == mPositiveControlLocations
|| ! mPositiveControlLocations.contains(wellRef))
&& (null == mNegativeControlLocations
|| mNegativeControlLocations.contains(wellRef)))
{
mRemainingRandomWellRefs.add(wellRef);
}
}
}
Collections.shuffle(mRemainingRandomWellRefs);
}
WellRef prevWellRef = inPrevWellRef;
WellRef wellRef = null;
while (null == wellRef)
{
if (mOrder.equals(Order.random))
{
if (! CollectionUtil.hasValues(mRemainingRandomWellRefs))
{
break;
}
wellRef = mRemainingRandomWellRefs.remove(0);
}
else
{
if (null == prevWellRef)
{
wellRef = getFirstSampleWellRef();
}
else
{
int col = prevWellRef.getColIndex();
int row = prevWellRef.getRowIndex();
switch (mOrder)
{
case byCol:
if (row == getPlateType().getRows())
{
row = 1;
col++;
}
else
{
row++;
}
break;
case byRow:
if (col == getPlateType().getColumns())
{
col = 1;
row++;
}
else
{
col++;
}
break;
case byQuadrantColRow:
if (row == getPlateType().getRows()/2)
{
if (col == getPlateType().getColumns())
{
row++;
col = 1;
}
else
{
row = 1;
col++;
}
}
else if (row == getPlateType().getRows())
{
row = 1 + getPlateType().getRows()/2;
col++;
}
else if (col == getPlateType().getColumns()
&& row == getPlateType().getRows()/2)
{
row++;
col = 1;
}
else
{
row++;
}
break;
case byQuadrantRowCol:
if (col == getPlateType().getColumns()/2)
{
if (row == getPlateType().getRows()/2
|| row == getPlateType().getRows())
{
row -= getPlateType().getRows()/2 - 1;
col++;
}
else
{
row++;
col = 1;
}
}
else if (col == getPlateType().getColumns())
{
if (row == getPlateType().getRows()/2)
{
col = 1;
}
else
{
col = 1 + getPlateType().getColumns() / 2;
}
row++;
}
else
{
col++;
}
break;
}
if (col > getPlateType().getColumns()
|| row > getPlateType().getRows())
{
break;
}
wellRef = new WellRef().setColIndex(col).setRowIndex(row);
}
// Was this location reserved?
if (inReturnOnlySampleWells)
{
if ((mLocationsToKeepEmpty != null
&& mLocationsToKeepEmpty.contains(wellRef))
|| (mBlankLocations != null
&& mBlankLocations.contains(wellRef))
|| (mPositiveControlLocations != null
&& mPositiveControlLocations.contains(wellRef))
|| (mNegativeControlLocations != null
&& mNegativeControlLocations.contains(wellRef)))
{
prevWellRef = wellRef;
wellRef = null;
}
}
}
}
return wellRef;
}
}