Skip to content

Commit 5382161

Browse files
authored
Merge pull request #303 from melissalinkert/biotek-brightfield-only
Update BioTek reader to allow brightfield-only plates
2 parents 9bd8744 + 13749b2 commit 5382161

File tree

3 files changed

+130
-20
lines changed

3 files changed

+130
-20
lines changed

src/main/java/com/glencoesoftware/bioformats2raw/BioTekReader.java

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,16 @@ public class BioTekReader extends FormatReader {
7878
// -- Fields --
7979

8080
private MinimalTiffReader helperReader;
81-
private Location parent;
8281
private List<BioTekWell> wells = new ArrayList<BioTekWell>();
8382

8483
// seriesMap[seriesIndex] = {wellIndex, fieldIndex}
8584
private int[][] seriesMap;
8685

8786
private List<String> xptFiles = new ArrayList<String>();
8887

88+
private transient Map<String, Element> xmlRoots =
89+
new HashMap<String, Element>();
90+
8991
// -- Constructor --
9092

9193
/** Constructs a new BioTek reader. */
@@ -163,10 +165,10 @@ public void close(boolean fileOnly) throws IOException {
163165
}
164166
if (!fileOnly) {
165167
helperReader = null;
166-
parent = null;
167168
wells.clear();
168169
xptFiles.clear();
169170
seriesMap = null;
171+
xmlRoots.clear();
170172
}
171173
}
172174

@@ -209,7 +211,7 @@ protected void initFile(String id) throws FormatException, IOException {
209211
super.initFile(id);
210212

211213
Location currentPath = new Location(id).getAbsoluteFile();
212-
parent = currentPath.getParentFile();
214+
Location parent = currentPath.getParentFile();
213215

214216
findXPTFiles(parent);
215217

@@ -282,6 +284,9 @@ protected void initFile(String id) throws FormatException, IOException {
282284

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

287+
boolean foundBrightfield = false;
288+
boolean foundFluorescence = false;
289+
285290
for (String absolutePath : files) {
286291
String f = new Location(absolutePath).getName();
287292
Matcher m = regexA.matcher(f);
@@ -292,17 +297,19 @@ protected void initFile(String id) throws FormatException, IOException {
292297
int z = 0;
293298
int t = 0;
294299
String channelName = "";
300+
int channelNameGroupIndex = 6;
295301

296302
if (m.matches()) {
297303
rowIndex = getWellRow(m.group(1));
298304
colIndex = Integer.parseInt(m.group(2)) - 1;
299305
fieldIndex = Integer.parseInt(m.group(5)) - 1;
300-
channelName = m.group(6);
306+
channelName = m.group(channelNameGroupIndex);
301307
}
302308
else {
303309
m = regexB.matcher(f);
304310
if (!m.matches()) {
305311
m = regexZ.matcher(f);
312+
channelNameGroupIndex = 5;
306313
}
307314

308315
if (m.matches()) {
@@ -321,7 +328,7 @@ protected void initFile(String id) throws FormatException, IOException {
321328
}
322329
// recorded T index may be negative if no timepoints
323330
t = (int) Math.max(0, Integer.parseInt(m.group(8)) - 1);
324-
channelName += m.group(6);
331+
channelName += m.group(channelNameGroupIndex);
325332
}
326333
else {
327334
m = regexROI.matcher(f);
@@ -374,7 +381,16 @@ protected void initFile(String id) throws FormatException, IOException {
374381
well.setFieldCount(fieldIndex + 1);
375382
}
376383
int c = well.addChannelName(fieldIndex, channelName);
377-
well.addFile(new PlaneIndex(fieldIndex, z, c, t), absolutePath);
384+
Element root = getXMLRoot(absolutePath);
385+
boolean brightfield = isBrightField(root);
386+
if (!foundBrightfield) {
387+
foundBrightfield = brightfield;
388+
}
389+
if (!foundFluorescence) {
390+
foundFluorescence = !brightfield;
391+
}
392+
well.addFile(
393+
new PlaneIndex(fieldIndex, z, c, t, brightfield), absolutePath);
378394

379395
if (rowIndex > maxRow) {
380396
maxRow = rowIndex;
@@ -397,6 +413,7 @@ protected void initFile(String id) throws FormatException, IOException {
397413
validWellRowCol.sort(null);
398414

399415
// split brightfield channels into a separate plate acquisition
416+
// if both brightfield and fluorescence data was found
400417
maxField.put(1, -1);
401418
int originalWellCount = wells.size();
402419
Set<Integer> removedChannels = new HashSet<Integer>();
@@ -409,11 +426,10 @@ protected void initFile(String id) throws FormatException, IOException {
409426
LOGGER.trace("found file {} for well index {}, field index {}",
410427
file, well, f);
411428
Element root = getXMLRoot(file);
412-
boolean brightfield = isBrightField(root);
413429

414430
PlaneIndex index = w.getIndex(file);
415431

416-
if (brightfield) {
432+
if (index.brightfield && foundBrightfield && foundFluorescence) {
417433
LOGGER.trace("found brightfield file: {}", file);
418434
w.removeFile(index, file);
419435
removedChannels.add(index.c);
@@ -444,7 +460,7 @@ protected void initFile(String id) throws FormatException, IOException {
444460
}
445461
}
446462
}
447-
// correct the channel indexes in the non-brightfield plate acquisition
463+
// correct the channel indexes in the first plate acquisition
448464
for (BioTekWell well : wells) {
449465
if (well.getPlateAcquisition() == 0) {
450466
Map<PlaneIndex, Integer> planeMap = well.getFilePlaneMap();
@@ -550,10 +566,10 @@ protected void initFile(String id) throws FormatException, IOException {
550566
FormatTools.getMaxFieldCount(maxField.get(pa) + 1);
551567
store.setPlateAcquisitionMaximumFieldCount(fieldCount, 0, pa);
552568

553-
if (pa == 0) {
569+
if (pa == 0 && foundFluorescence) {
554570
store.setPlateAcquisitionName("Fluorescence", 0, pa);
555571
}
556-
else if (pa == 1) {
572+
else {
557573
store.setPlateAcquisitionName("Bright-field", 0, pa);
558574
}
559575
}
@@ -652,6 +668,9 @@ private int getFieldIndex(int seriesIndex) {
652668
}
653669

654670
private Element getXMLRoot(String file) throws FormatException, IOException {
671+
if (xmlRoots.containsKey(file)) {
672+
return xmlRoots.get(file);
673+
}
655674
try (TiffParser p = new TiffParser(file)) {
656675
String xml = p.getComment();
657676
if (xml == null) {
@@ -665,6 +684,7 @@ private Element getXMLRoot(String file) throws FormatException, IOException {
665684
catch (ParserConfigurationException|SAXException e) {
666685
throw new FormatException(e);
667686
}
687+
xmlRoots.put(file, root);
668688
return root;
669689
}
670690
}
@@ -1011,16 +1031,24 @@ public int getSizeT(int field) {
10111031
}
10121032

10131033
public String getFile(int fieldIndex, int[] zct) {
1034+
PlaneIndex[] indexes = filePlaneMap.keySet().toArray(new PlaneIndex[0]);
1035+
Arrays.sort(indexes);
10141036
if (zct == null) {
1015-
for (PlaneIndex p : filePlaneMap.keySet()) {
1037+
for (PlaneIndex p : indexes) {
10161038
if (p.fieldIndex == fieldIndex) {
10171039
return getAllFiles().get(filePlaneMap.get(p));
10181040
}
10191041
}
10201042
}
1021-
PlaneIndex p = new PlaneIndex(fieldIndex, zct[0], zct[1], zct[2]);
1022-
Integer index = filePlaneMap.get(p);
1023-
return index == null ? null : getAllFiles().get(index);
1043+
for (PlaneIndex p : indexes) {
1044+
if (p.fieldIndex == fieldIndex &&
1045+
p.z == zct[0] && p.c == zct[1] && p.t == zct[2])
1046+
{
1047+
Integer index = filePlaneMap.get(p);
1048+
return index == null ? null : getAllFiles().get(index);
1049+
}
1050+
}
1051+
return null;
10241052
}
10251053

10261054
public PlaneIndex getIndex(String file) {
@@ -1038,48 +1066,76 @@ public PlaneIndex getIndex(String file) {
10381066
}
10391067
}
10401068

1041-
class PlaneIndex {
1069+
class PlaneIndex implements Comparable<PlaneIndex> {
10421070
public int fieldIndex;
10431071
public int z;
10441072
public int c;
10451073
public int t;
1074+
public boolean brightfield = false;
10461075

10471076
public PlaneIndex(int f, int z, int c, int t) {
1077+
this(f, z, c, t, false);
1078+
}
1079+
1080+
public PlaneIndex(int f, int z, int c, int t, boolean brightfield) {
10481081
this.fieldIndex = f;
10491082
this.z = z;
10501083
this.c = c;
10511084
this.t = t;
1085+
this.brightfield = brightfield;
10521086
}
10531087

10541088
public PlaneIndex(PlaneIndex copy) {
10551089
this.fieldIndex = copy.fieldIndex;
10561090
this.z = copy.z;
10571091
this.c = copy.c;
10581092
this.t = copy.t;
1093+
this.brightfield = copy.brightfield;
10591094
}
10601095

10611096
@Override
10621097
public int hashCode() {
1063-
int fc = (fieldIndex & 0xff) << 24;
1098+
int fc = (fieldIndex & 0x7f) << 24;
10641099
int zc = (z & 0xff) << 16;
10651100
int cc = (c & 0xff) << 8;
10661101
int tc = (t & 0xff);
1067-
return fc | zc | cc | tc;
1102+
int coord = fc | zc | cc | tc;
1103+
return brightfield ? coord | 0x80000000 : 0;
10681104
}
10691105

10701106
@Override
10711107
public boolean equals(Object o) {
10721108
if (o instanceof PlaneIndex) {
10731109
PlaneIndex p = (PlaneIndex) o;
10741110
return p.fieldIndex == fieldIndex && p.z == z &&
1075-
p.c == c && p.t == t;
1111+
p.c == c && p.t == t && p.brightfield == brightfield;
10761112
}
10771113
return false;
10781114
}
10791115

1116+
@Override
1117+
public int compareTo(PlaneIndex o) {
1118+
if (this.equals(o)) {
1119+
return 0;
1120+
}
1121+
if (this.brightfield != o.brightfield) {
1122+
return this.brightfield ? 1 : -1;
1123+
}
1124+
if (this.fieldIndex != o.fieldIndex) {
1125+
return this.fieldIndex - o.fieldIndex;
1126+
}
1127+
if (this.z != o.z) {
1128+
return this.z - o.z;
1129+
}
1130+
if (this.c != o.c) {
1131+
return this.c - o.c;
1132+
}
1133+
return this.t - o.t;
1134+
}
1135+
10801136
public String toString() {
10811137
return "fieldIndex=" + fieldIndex + ", z=" + z +
1082-
", c=" + c + ", t=" + t;
1138+
", c=" + c + ", t=" + t + ", brightfield=" + brightfield;
10831139
}
10841140

10851141
}

src/test/java/com/glencoesoftware/bioformats2raw/test/BioTekTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,60 @@ public void testInvalidPath() throws IOException {
169169
}
170170
}
171171

172+
/**
173+
* Test plate with only brightfield data.
174+
*/
175+
@Test
176+
public void testBrightfieldOnly() throws Exception {
177+
Path testTiff = getTestFile("test-brightfield.tiff");
178+
179+
createPlate(new String[] {"D5_1_Stitched[A_b]_3_001_1.tif",
180+
"D5_1_Stitched[A_g]_2_001_1.tif",
181+
"D5_1_Stitched[A_r]_1_001_1.tif"
182+
});
183+
184+
String[] channelName = new String[] {"b", "g", "r"};
185+
int wellRow = 3;
186+
int wellColumn = 4;
187+
int fields = 1;
188+
int sizeC = 3;
189+
int sizeZ = 1;
190+
int sizeT = 1;
191+
192+
try (BioTekReader reader = new BioTekReader()) {
193+
OMEXMLMetadata metadata = getOMEMetadata();
194+
reader.setMetadataStore(metadata);
195+
reader.setId(input.toString());
196+
197+
// this should be the same as the series count
198+
assertEquals(metadata.getImageCount(), fields);
199+
assertEquals(metadata.getImageCount(), reader.getSeriesCount());
200+
// there should be exactly one plate, with exactly one well
201+
assertEquals(metadata.getPlateCount(), 1);
202+
assertEquals(metadata.getWellCount(0), 1);
203+
// the well's row and column indexes should match expectations
204+
// this is especially important for "sparse" plates where the first
205+
// row and/or column in the plate are missing
206+
assertEquals(metadata.getWellRow(0, 0).getValue(), wellRow);
207+
assertEquals(metadata.getWellColumn(0, 0).getValue(), wellColumn);
208+
// all of the Images should be linked to the well
209+
assertEquals(metadata.getWellSampleCount(0, 0), fields);
210+
for (int f=0; f<fields; f++) {
211+
// sanity check that the Images are linked to the
212+
// well in the correct order
213+
assertEquals(metadata.getWellSampleImageRef(0, 0, f), "Image:" + f);
214+
// check that the number of Z sections, channels, and timepoints
215+
// all match expectations
216+
assertEquals(metadata.getPixelsSizeZ(f).getValue(), sizeZ);
217+
assertEquals(metadata.getPixelsSizeC(f).getValue(), sizeC);
218+
assertEquals(metadata.getPixelsSizeT(f).getValue(), sizeT);
219+
for (int c=0; c<sizeC; c++) {
220+
assertEquals(metadata.getChannelName(f, c), channelName[c]);
221+
}
222+
}
223+
}
224+
}
225+
172226
/**
173227
* Generate test cases that are given to testBioTek(...) above.
174228
* A list of file names, the well row and column index, number of fields,
Binary file not shown.

0 commit comments

Comments
 (0)