Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow BioTek plates to be split across multiple subdirectories #223

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
189 changes: 170 additions & 19 deletions src/main/java/com/glencoesoftware/bioformats2raw/BioTekReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public class BioTekReader extends FormatReader {
private static final String TIFF_REGEX_Z =
WELL_REGEX + "_.+\\[(.+)_" +
ALPHANUM + "\\]_(\\d+)_(\\d+)_([0-9-]+)?" + SUFFIX;
private static final String TIFF_REGEX_ROI =
"([A-Z]{1,2})(\\d{1,2})ROI(\\d+)_(\\d+)_(\\d+)_(\\d+)(Z(\\d+))?_" +
ALPHANUM + "_(-?\\d+)" + SUFFIX;
private static final String DATE_FORMAT = "MM/dd/yy HH:mm:ss";

// -- Fields --
Expand Down Expand Up @@ -211,22 +214,81 @@ protected void initFile(String id) throws FormatException, IOException {
findXPTFiles(parent);

String[] files = parent.list(true);
for (int i=0; i<files.length; i++) {
files[i] = new Location(parent, files[i]).getAbsolutePath();
}
Arrays.sort(files);

// is there only one well in the directory?
// compare the well identifiers (relative file name up to first _)
boolean sameWell = true;
int endIndex = files[0].indexOf("_");
if (endIndex > 0) {
String wellCheck = files[0].substring(0, endIndex);
LOGGER.debug("well check string = {}", wellCheck);
for (int i=0; i<files.length; i++) {
if (!files[i].startsWith(wellCheck)) {
sameWell = false;
break;
}
}
}
LOGGER.debug("single well in {}: {}", parent, sameWell);
// if only one well exists, look in other subdirectories of the parent
if (sameWell) {
Location plateDir = parent.getParentFile();
LOGGER.debug("plate directory = {}", plateDir);
String[] wellDirs = plateDir.list(true);
ArrayList<String> allFiles = new ArrayList<String>();
boolean multipleDirs = true;
for (String well : wellDirs) {
Location wellDir = new Location(plateDir, well).getAbsoluteFile();
LOGGER.debug("looking in well directory = {}", wellDir);
if (wellDir == null) {
multipleDirs = false;
}
else {
String[] f = wellDir.list(true);
if (f == null) {
multipleDirs = false;
}
else {
for (String file : f) {
LOGGER.debug(" adding well file {}", file);
allFiles.add(new Location(wellDir, file).getAbsolutePath());
}
}
}
}
if (multipleDirs) {
LOGGER.debug("found files = {}", allFiles);
files = allFiles.toArray(new String[allFiles.size()]);
Arrays.sort(files);
}
}

Pattern regexA = Pattern.compile(TIFF_REGEX_A);
Pattern regexB = Pattern.compile(TIFF_REGEX_B);
Pattern regexZ = Pattern.compile(TIFF_REGEX_Z);
Pattern regexROI = Pattern.compile(TIFF_REGEX_ROI);
ArrayList<WellIndex> validWellRowCol = new ArrayList<WellIndex>();
int maxRow = 0;
int minRow = Integer.MAX_VALUE;
int maxCol = 0;
int minCol = Integer.MAX_VALUE;
int maxPlateAcq = 0;
Map<Integer, Integer> maxField = new HashMap<Integer, Integer>();

for (String f : files) {
String matchingPath = new Location(currentId).getAbsolutePath();
LOGGER.trace("matching path = {}", matchingPath);

ArrayList<String> foundWellSamples = new ArrayList<String>();

for (String absolutePath : files) {
String f = new Location(absolutePath).getName();
Matcher m = regexA.matcher(f);
int rowIndex = -1;
int colIndex = -1;
int fieldIndex = -1;
int roiIndex = -1;
int z = 0;
int t = 0;
String channelName = "";
Expand Down Expand Up @@ -261,28 +323,68 @@ protected void initFile(String id) throws FormatException, IOException {
t = (int) Math.max(0, Integer.parseInt(m.group(8)) - 1);
channelName += m.group(6);
}
else {
m = regexROI.matcher(f);
if (m.matches()) {
rowIndex = getWellRow(m.group(1));
colIndex = Integer.parseInt(m.group(2)) - 1;
roiIndex = Integer.parseInt(m.group(3)) - 1;

LOGGER.trace("absolutePath = {}, roiIndex = {}",
absolutePath, roiIndex);

int channelIndex = Integer.parseInt(m.group(5)) - 1;
fieldIndex = Integer.parseInt(m.group(6)) - 1;
try {
z = Integer.parseInt(m.group(8));
// can have two channels with same name
// one with Z stack and one without
channelName = "Z";
}
catch (NumberFormatException e) {
}
channelName += m.group(9);
// recorded T index may be negative if no timepoints
t = (int) Math.max(0, Integer.parseInt(m.group(10)) - 1);
}
}
}

if (rowIndex >= 0 && colIndex >= 0 && fieldIndex >= 0) {
if (rowIndex >= 0 && colIndex >= 0 &&
(fieldIndex >= 0 || roiIndex >= 0))
{
// collapse field and ROI index into single field index
if (roiIndex >= 0) {
if (fieldIndex < 0) {
// only ROIs, no field
fieldIndex = roiIndex;
}
else {
// both fields and ROIs
String key = fieldIndex + "," + roiIndex;
if (!foundWellSamples.contains(key)) {
foundWellSamples.add(key);
}
fieldIndex = foundWellSamples.indexOf(key);
}
}

BioTekWell well = lookupWell(0, rowIndex, colIndex);
if (fieldIndex >= well.getFieldCount()) {
well.setFieldCount(fieldIndex + 1);
}
int c = well.addChannelName(fieldIndex, channelName);
well.addFile(new PlaneIndex(fieldIndex, z, c, t),
new Location(parent, f).getAbsolutePath());
well.addFile(new PlaneIndex(fieldIndex, z, c, t), absolutePath);

if (rowIndex > maxRow) {
maxRow = rowIndex;
}
if (rowIndex < minRow) {
minRow = rowIndex;
}
if (colIndex > maxCol) {
maxCol = colIndex;
}
if (colIndex < minCol) {
minCol = colIndex;
WellIndex rowColPair = new WellIndex(rowIndex, colIndex);
if (!validWellRowCol.contains(rowColPair)) {
validWellRowCol.add(rowColPair);
}
Integer maxFieldIndex = maxField.get(0);
if (maxFieldIndex == null) {
Expand All @@ -292,6 +394,7 @@ protected void initFile(String id) throws FormatException, IOException {
}
}
wells.sort(null);
validWellRowCol.sort(null);

// split brightfield channels into a separate plate acquisition
maxField.put(1, -1);
Expand All @@ -303,6 +406,8 @@ protected void initFile(String id) throws FormatException, IOException {
for (int f=0; f<w.getFieldCount(); f++) {
String[] fieldFiles = w.getFiles(f);
for (String file : fieldFiles) {
LOGGER.trace("found file {} for well index {}, field index {}",
file, well, f);
Element root = getXMLRoot(file);
boolean brightfield = isBrightField(root);

Expand Down Expand Up @@ -401,14 +506,26 @@ protected void initFile(String id) throws FormatException, IOException {
setSeries(s);

int wellIndex = getWellIndex(s);
int field = getFieldIndex(s) + 1;
int field = getFieldIndex(s);
BioTekWell well = wells.get(wellIndex);
int row = well.getRowIndex();
int column = well.getColumnIndex();
store.setImageName(
getWellName(row, column) + " #" + field, s);

List<Channel> channels = well.getChannels(field - 1);
String fieldName = " #" + (field + 1);
if (field < foundWellSamples.size()) {
// unpack the field/ROI indexes and store both
// in the image name

String[] indexes = foundWellSamples.get(field).split(",");
if (indexes.length == 2) {
fieldName = " Field #" + (Integer.parseInt(indexes[0]) + 1) +
", ROI #" + (Integer.parseInt(indexes[1]) + 1);
}
}

store.setImageName(getWellName(row, column) + fieldName, s);

List<Channel> channels = well.getChannels(field);
for (int c=0; c<getEffectiveSizeC(); c++) {
if (channels != null && c < channels.size()) {
Channel channel = channels.get(c);
Expand Down Expand Up @@ -443,12 +560,14 @@ else if (pa == 1) {

int nextImage = 0;
int[] nextWellSample = new int[(maxRow + 1) * (maxCol + 1)];
int totalColumns = (maxCol - minCol) + 1;
for (int w=0; w<wells.size(); w++) {
BioTekWell well = wells.get(w);
int effectiveRow = well.getRowIndex() - minRow;
int effectiveColumn = well.getColumnIndex() - minCol;
int wellIndex = effectiveRow * totalColumns + effectiveColumn;
int wellIndex = validWellRowCol.indexOf(
new WellIndex(well.getRowIndex(), well.getColumnIndex()));
LOGGER.debug(
"well #{}, row = {}, col = {}, index = {}",
w, well.getRowIndex(), well.getColumnIndex(), wellIndex);

well.fillMetadataStore(store, 0, well.getPlateAcquisition(), wellIndex,
nextWellSample[wellIndex], nextImage);

Expand Down Expand Up @@ -976,4 +1095,36 @@ public Channel(String name) {
}
}

class WellIndex implements Comparable<WellIndex> {
public int row;
public int col;

public WellIndex(int r, int c) {
this.row = r;
this.col = c;
}

@Override
public int compareTo(WellIndex w) {
if (this.row != w.row) {
return this.row - w.row;
}
return this.col - w.col;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof WellIndex)) {
return false;
}
return compareTo((WellIndex) o) == 0;
}

@Override
public int hashCode() {
// this would need fixing if we had more than 65535 rows or columns
return (row & 0xffff) << 16 | (col & 0xffff);
}
}

}
Loading