Skip to content

Commit

Permalink
Add support of Plate Info Metadata in Bio-Formats reader
Browse files Browse the repository at this point in the history
- Removes unnecessary reader argument in BioFormatsHelper
- Removes model reader which was created for each opener: this had a huge performance impact
- The model is memoized
- If the bioformats reader is associated to a plate, the plate info is added as entities (Plate, Well, Field (= WellSample))
- Adds the required entities and serializers
  • Loading branch information
NicoKiaru committed Jul 24, 2024
1 parent f31ff60 commit e66f0c8
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 40 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
<!-- only used in tests! -->
<bigdataviewer-playground.version>0.8.1</bigdataviewer-playground.version>
<bigdataviewer-biop-tools.version>0.7.3</bigdataviewer-biop-tools.version>
<bf.version>8.0.0-SNAPSHOT</bf.version>
<bf.version>7.3.1</bf.version>

<!-- <scijava.app.directory>C:/Fiji_template</scijava.app.directory> -->
<!-- <scijava.app.subdirectory>plugins/BIOP</scijava.app.subdirectory> -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ public static Length[] getSeriesVoxelSizeAsLengths(IMetadata omeMeta,
}

protected static AffineTransform3D getSeriesRootTransform(IMetadata omeMeta,
IFormatReader reader,
int iSerie, Unit<Length> u,
// Bioformats location fix
double[] positionPreTransformMA, double[] positionPostTransformMA,
Expand Down Expand Up @@ -236,7 +235,7 @@ protected static AffineTransform3D getSeriesRootTransform(IMetadata omeMeta,
voxSizePostTransform.set(voxSizePostTransformMA);
}

return getSeriesRootTransform(omeMeta, reader, iSerie, u,
return getSeriesRootTransform(omeMeta, iSerie, u,
// Bioformats location fix
positionPreTransform, positionPostTransform, positionReferenceFrameLength,
positionIsImageCenter,
Expand All @@ -247,7 +246,6 @@ protected static AffineTransform3D getSeriesRootTransform(IMetadata omeMeta,
}

public static AffineTransform3D getSeriesRootTransform(IMetadata omeMeta,
IFormatReader reader,
int iSerie, Unit<Length> u,
// Bioformats location fix
AffineTransform3D positionPreTransform,
Expand Down Expand Up @@ -284,7 +282,7 @@ else if (voxSize[iDimension].unit().getSymbol().equals(
Length[] pos = getSeriesPositionAsLengths(omeMeta, iSerie);
double[] p = new double[3];

Dimensions dims = getSeriesDimensions(omeMeta, reader, iSerie);
Dimensions dims = getSeriesDimensions(omeMeta, iSerie);

for (int iDimension = 0; iDimension < 3; iDimension++) { // X:0; Y:1; Z:2
if ((pos[iDimension].unit() != null) && (pos[iDimension].unit()
Expand Down Expand Up @@ -406,16 +404,14 @@ public int numDimensions() {
return voxelDimensions;
}

public static Dimensions getSeriesDimensions(IMetadata omeMeta, IFormatReader reader, int iSerie) {
public static Dimensions getSeriesDimensions(IMetadata omeMeta, int iSerie) {
// Always set 3d to allow for Big Stitcher compatibility

int numDimensions = 3;
omeMeta.getPixelsSizeX(iSerie);
reader.setSeries(iSerie);

int sX = reader.getSizeX();
int sY = reader.getSizeY();
int sZ = reader.getSizeZ();
int sX = omeMeta.getPixelsSizeX(iSerie).getValue();//reader.getSizeX();
int sY = omeMeta.getPixelsSizeY(iSerie).getValue();//reader.getSizeY();
int sZ = omeMeta.getPixelsSizeZ(iSerie).getValue();//reader.getSizeZ();

long[] dims = new long[3];

Expand Down
127 changes: 99 additions & 28 deletions src/main/java/ch/epfl/biop/bdv/img/bioformats/BioFormatsOpener.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

import bdv.img.cache.VolatileGlobalCellCache;
import ch.epfl.biop.bdv.img.OpenerSetupLoader;
import ch.epfl.biop.bdv.img.entity.Field;
import ch.epfl.biop.bdv.img.entity.Plate;
import ch.epfl.biop.bdv.img.opener.ChannelProperties;
import ch.epfl.biop.bdv.img.opener.Opener;
import ch.epfl.biop.bdv.img.ResourcePool;
Expand Down Expand Up @@ -57,6 +59,9 @@
import ome.units.UNITS;
import ome.units.quantity.Length;
import ome.units.unit.Unit;
import ome.xml.meta.OMEXMLMetadataRoot;
import ome.xml.model.Well;
import ome.xml.model.WellSample;
import org.apache.commons.io.FilenameUtils;
import org.scijava.Context;
import org.slf4j.Logger;
Expand All @@ -66,9 +71,11 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static ch.epfl.biop.bdv.img.opener.OpenerHelper.memoize;
Expand Down Expand Up @@ -125,8 +132,6 @@ public class BioFormatsOpener implements Opener<IFormatReader> {

private final Map<String, String> readerOptions;

IFormatReader model;

/**
*
* @param context
Expand Down Expand Up @@ -214,11 +219,17 @@ public BioFormatsOpener(
return currentIndexFilename;
});

this.model = this.getNewReader();
this.pool = memoize("opener.bioformats."+splitRGBChannels+"."+dataLocation+"."+options,
cachedObjects,
() -> new ReaderPool(poolSize, true,
this::getNewReader, model));
() -> {
try {
return new ReaderPool(poolSize, true,
this::getNewReader, dataLocation.toUpperCase().trim().endsWith(".CZI") ); // Create base reader only for czi files
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
});
int pixelType;
{ // Indentation just for the pool / recycle operation -> force limiting the scope of reader
IFormatReader reader = pool.takeOrCreate();
Expand Down Expand Up @@ -262,7 +273,6 @@ public BioFormatsOpener(

AffineTransform3D rootTransform = BioFormatsHelper.getSeriesRootTransform(
this.omeMeta, //metadata
model,
iSerie, // serie
BioFormatsHelper.getUnitFromString(unit), // unit
positionPreTransformMatrixArray, // AffineTransform3D for positionPreTransform,
Expand Down Expand Up @@ -294,6 +304,7 @@ public List<Entity> getEntities(int iChannel) {
ArrayList<Entity> entityList = new ArrayList<>();
entityList.add(new FileName(idxFilename, filename));
entityList.add(new SeriesIndex(iSerie));
addPlateInfo(entityList, options, iSerie, cachedObjects);
return entityList;
}

Expand All @@ -311,6 +322,67 @@ public AffineTransform3D getTransform() {
} else meta = null;
}

private void addPlateInfo(ArrayList<Entity> entityList,
String options,
int iSerie,
Map<String, Object> cachedObjects) {
if (omeMeta.getPlateCount() == 0 ) return; // No plate information

// Check that we can read information
if (omeMeta.getPlateCount() > 1) {
logger.warn("Plate information ignored: only bio-formats containing single wells are supported");
return;
}

if (!(omeMeta.getRoot() instanceof OMEXMLMetadataRoot)) {
logger.warn("Can't detect plate information since ome meta root is not of class OMEXMLMetadataRoot");
return;
}

OMEXMLMetadataRoot r = (OMEXMLMetadataRoot) omeMeta.getRoot();
ome.xml.model.Plate plate = r.getPlate(0);

// Gets a unique identifier for the plate

Integer currentPlateIndex = memoize("opener.bioformats.currentplateindex", cachedObjects, () -> 0);
int idxPlate = memoize("opener.bioformats.plateIndex."+dataLocation+"."+options, cachedObjects, () -> {
cachedObjects.put("opener.bioformats.currentplateindex", currentPlateIndex + 1 );
return currentPlateIndex;
});

entityList.add(new Plate(idxPlate, plate.getName()));

Map<Integer, WellSample> idToWellSample = memoize("opener.bioformats.idtowell."+dataLocation+"."+options, cachedObjects, () -> {
Map<Integer, WellSample> idToWS = new HashMap<>();
plate.copyWellList().forEach(well -> well.copyWellSampleList().forEach(ws -> {
if (ws.getLinkedImage()!=null) {
if (ws.getLinkedImage().getID()!=null) {
// "Image:0"
idToWS.put(Integer.parseInt(ws.getLinkedImage().getID().split(":")[1]), ws);
}
}
}));
return idToWS;
});

if (idToWellSample.containsKey(iSerie)) {
WellSample ws = idToWellSample.get(iSerie);
System.out.println("ws="+ws.getID());
// WellSample:0:0:2
int id = Integer.parseInt(ws.getID().split(":")[3]);
entityList.add(new Field(id));
if (ws.getWell()!=null) {
Well w = ws.getWell();
entityList.add(
new ch.epfl.biop.bdv.img.entity.Well(
Integer.parseInt(w.getID().split(":")[2]),
(char)(w.getRow().getValue()+'A')+Integer.toString(w.getColumn().getValue()+1),
w.getRow().getValue(), w.getColumn().getValue()));
}
}

}

/**
* Build a channelProperties object for each image channel.
* @param omeMeta = image metadata
Expand Down Expand Up @@ -435,7 +507,7 @@ public IFormatReader getNewReader() {
reader = new ChannelSeparator(reader);
}

if (memoize) { // Can't memoize with bf Options
if (memoize) {
Memoizer memo = new Memoizer(reader);
try {
memo.setId(dataLocation);
Expand Down Expand Up @@ -590,14 +662,6 @@ public void close() {
e.printStackTrace();
}
});
if (model!=null) {
try {
model.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}

/**
Expand All @@ -620,22 +684,15 @@ private static class ReaderPool extends ResourcePool<IFormatReader> {
final IFormatReader model;

public ReaderPool(int size, Boolean dynamicCreation,
Supplier<IFormatReader> readerSupplier)
{
super(size, dynamicCreation);
createPool();
this.readerSupplier = readerSupplier;
model = null;
}

public ReaderPool(int size, Boolean dynamicCreation,
Supplier<IFormatReader> readerSupplier,
IFormatReader model)
{
Supplier<IFormatReader> readerSupplier, boolean createBase) throws Exception {
super(size, dynamicCreation);
this.model = model;
createPool();
this.readerSupplier = readerSupplier;
if (createBase) {
model = this.takeOrCreate();
} else {
model = null;
}
}

@Override
Expand All @@ -648,5 +705,19 @@ public IFormatReader createObject() {
}
return readerSupplier.get();
}

@Override
public synchronized void shutDown(Consumer<IFormatReader> closer) {
if (model!=null) {
try {
recycle(model);
} catch (Exception e) {
e.printStackTrace();
}
closer.accept(model);
}
super.shutDown(closer);
}

}
}
47 changes: 47 additions & 0 deletions src/main/java/ch/epfl/biop/bdv/img/entity/Field.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*-
* #%L
* Various image loaders for bigdataviewer (Bio-Formats, Omero, QuPath)
* %%
* Copyright (C) 2022 - 2024 ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, BioImaging And Optics Platform (BIOP)
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

package ch.epfl.biop.bdv.img.entity;

import mpicbg.spim.data.generic.base.Entity;

public class Field extends Entity implements
Comparable<Field>
{

public Field(final int id) {
super(id);
}

/**
* Compares the {@link #getId() ids}.
*/
@Override
public int compareTo(final Field o) {
return getId() - o.getId();
}

/**
* Empty constructor strictly necessary for dataset deserialization
*/
protected Field() {}
}
47 changes: 47 additions & 0 deletions src/main/java/ch/epfl/biop/bdv/img/entity/Plate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*-
* #%L
* Various image loaders for bigdataviewer (Bio-Formats, Omero, QuPath)
* %%
* Copyright (C) 2022 - 2024 ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, BioImaging And Optics Platform (BIOP)
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/

package ch.epfl.biop.bdv.img.entity;

import mpicbg.spim.data.generic.base.NamedEntity;

public class Plate extends NamedEntity implements
Comparable<Plate>
{

public Plate(final int id, final String name) {
super(id, name);
}

/**
* Compares the {@link #getId() ids}.
*/
@Override
public int compareTo(final Plate o) {
return getId() - o.getId();
}

/**
* Empty constructor strictly necessary for dataset deserialization
*/
protected Plate() {}
}
Loading

0 comments on commit e66f0c8

Please sign in to comment.