Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies {
implementation 'info.picocli:picocli:4.7.5'
implementation 'me.tongfei:progressbar:0.9.0'
implementation 'ome:formats-bsd:8.3.0'
implementation 'com.glencoesoftware:bioformats2raw:0.9.4'
implementation 'com.glencoesoftware:bioformats2raw:0.11.0'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.3.15'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.3.15'
testImplementation 'junit:junit:4.12'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,9 +668,9 @@ private byte[] getInputTileBytes(PyramidSeries s, int resolution,
realHeight = region.height;
}

int[] gridPosition = new int[] {pos[2], pos[1], pos[0],
y * descriptor.tileSizeY, x * descriptor.tileSizeX};
int[] shape = new int[] {1, 1, 1, realHeight, realWidth};
int[] gridPosition = s.getArray(pos[2], pos[1], pos[0],
y * descriptor.tileSizeY, x * descriptor.tileSizeX);
int[] shape = s.getArray(1, 1, 1, realHeight, realWidth);

ZarrArray block = reader.openArray(descriptor.path);

Expand Down Expand Up @@ -1098,53 +1098,48 @@ else if (imageFile != null && !imageFile.isEmpty()) {
ZarrGroup imgGroup = getZarrGroup(s.path);
ZarrArray imgArray = imgGroup.openArray("0");
int[] dims = imgArray.getShape();
// allow mismatch in channel count...
for (int d=0; d<dims.length; d++) {
char dimension = s.dimensionOrder.charAt(dims.length - d - 1);
int check = 0;
switch (dimension) {
case 'X':
check = x;
break;
case 'Y':
check = y;
break;
case 'Z':
check = s.z;
break;
case 'C':
check = dims[1];
break;
case 'T':
check = s.t;
break;
default:
throw new FormatException("Unrecognized dimension: " + dimension);
}
if (dims[d] != check) {
throw new FormatException("Dimension mismatch: " +
dimension + ": Zarr (" + dims[d] + "), OME-XML: (" +
check + ")");

List<Map<String, Object>> imgMultiscales =
(List<Map<String, Object>>) imgGroup.getAttributes().get("multiscales");
List<Map<String, Object>> imgAxes = null;
int channelIndex = -1;
if (imgMultiscales != null) {
Map<String, Object> multiscale = imgMultiscales.get(0);
imgAxes = (List<Map<String, Object>>) multiscale.get("axes");
if (imgAxes != null) {
for (int a=0; a<imgAxes.size(); a++) {
if (imgAxes.get(a).get("name").toString().equalsIgnoreCase("c")) {
channelIndex = a;
break;
}
}
}
}
else {
channelIndex = 1;
}

// ...but if the channel count mismatches, metadata needs to be corrected
if (s.c > dims[1]) {
LOG.debug("OME-XML has {} channels; using {} Zarr channels instead",
s.c, dims[1]);
mergeChannels(seriesIndex, s.c, false);
s.c = dims[1];
}
else if (s.c < dims[1]) {
LOG.debug(
"OME-XML has {} channels; adding {} to match {} Zarr channels",
s.c, dims[1] - s.c + 1, dims[1]);
for (int channel=s.c; channel<dims[1]; channel++) {
metadata.setChannelID(
MetadataTools.createLSID("Channel", seriesIndex, channel),
seriesIndex, channel);
if (channelIndex >= 0) {
if (s.c > dims[channelIndex]) {
LOG.debug("OME-XML has {} channels; using {} Zarr channels instead",
s.c, dims[channelIndex]);
mergeChannels(seriesIndex, s.c, false);
s.c = dims[channelIndex];
}
else if (s.c < dims[channelIndex]) {
LOG.debug(
"OME-XML has {} channels; adding {} to match {} Zarr channels",
s.c, dims[channelIndex] - s.c + 1, dims[channelIndex]);
for (int channel=s.c; channel<dims[channelIndex]; channel++) {
metadata.setChannelID(
MetadataTools.createLSID("Channel", seriesIndex, channel),
seriesIndex, channel);
}
metadata.setPixelsSizeC(
new PositiveInteger(dims[channelIndex]), seriesIndex);
s.c = dims[channelIndex];
}
metadata.setPixelsSizeC(new PositiveInteger(dims[1]), seriesIndex);
s.c = dims[1];
}

s.dimensionLengths[s.dimensionOrder.indexOf("C") - 2] = s.c;
Expand Down
135 changes: 122 additions & 13 deletions src/main/java/com/glencoesoftware/pyramid/PyramidSeries.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.ome.OMEPyramidStore;
Expand Down Expand Up @@ -54,6 +55,71 @@ public class PyramidSeries {
/** Description of each resolution in the pyramid. */
List<ResolutionDescriptor> resolutions;

/** Axes in the underlying array, in order. */
ArrayList<String> axes = new ArrayList<String>();

/**
* Add named axis to ordered list of axes in this resolution.
* Names are stored as upper-case only.
*
* @param axis name e.g. "x"
*/
public void addAxis(String axis) {
axes.add(axis.toUpperCase());
}

/**
* Find the index in the ordered list of the named axis.
*
* @param axis name e.g. "x"
* @return index into list of axes
*/
public int getIndex(String axis) {
return axes.indexOf(axis.toUpperCase());
}

/**
* Create an indexing array (e.g. shape or offset) for this resolution,
* which represents the given 5D values.
* Since the resolution's underlying array may have less than 5 dimensions,
* this is mapping from the 5D space of the OME data model to the
* ND space of this resolution's array.
*
* @param ti T index
* @param ci C index
* @param zi Z index
* @param yi Y index
* @param xi X index
* @return array representing the given indexes, in this resolution's
* dimensional space
*/
public int[] getArray(int ti, int ci, int zi, int yi, int xi) {
int[] returnArray = new int[axes.size()];
for (int i=0; i<axes.size(); i++) {
char axis = axes.get(i).charAt(0);
switch (axis) {
case 'X':
returnArray[i] = xi;
break;
case 'Y':
returnArray[i] = yi;
break;
case 'Z':
returnArray[i] = zi;
break;
case 'C':
returnArray[i] = ci;
break;
case 'T':
returnArray[i] = ti;
break;
default:
throw new IllegalArgumentException("Unexpected axis: " + axis);
}
}
return returnArray;
}

/**
* Calculate image width and height for each resolution.
* Uses the first tile in the resolution to find the tile size.
Expand All @@ -65,6 +131,29 @@ public void describePyramid(ZarrGroup reader, OMEPyramidStore metadata)
throws FormatException, IOException
{
LOG.info("Number of resolution levels: {}", numberOfResolutions);

List<Map<String, Object>> multiscales =
(List<Map<String, Object>>) reader.openSubGroup(path).getAttributes().get(
"multiscales");
Map<String, Object> multiscale = multiscales.get(0);
List<Map<String, Object>> storedAxes = null;
if (multiscales != null) {
storedAxes = (List<Map<String, Object>>) multiscale.get("axes");
}

if (storedAxes != null) {
for (Map<String, Object> axis : storedAxes) {
addAxis(axis.get("name").toString());
}
}
else {
addAxis("T");
addAxis("C");
addAxis("Z");
addAxis("Y");
addAxis("X");
}

resolutions = new ArrayList<ResolutionDescriptor>();
for (int resolution = 0; resolution < numberOfResolutions; resolution++) {
ResolutionDescriptor descriptor = new ResolutionDescriptor();
Expand All @@ -75,10 +164,13 @@ public void describePyramid(ZarrGroup reader, OMEPyramidStore metadata)
int[] dimensions = array.getShape();
int[] blockSizes = array.getChunks();

descriptor.sizeX = dimensions[dimensions.length - 1];
descriptor.sizeY = dimensions[dimensions.length - 2];
descriptor.tileSizeX = blockSizes[blockSizes.length - 1];
descriptor.tileSizeY = blockSizes[blockSizes.length - 2];
int xIndex = getIndex("X");
int yIndex = getIndex("Y");

descriptor.sizeX = dimensions[xIndex];
descriptor.sizeY = dimensions[yIndex];
descriptor.tileSizeX = blockSizes[xIndex];
descriptor.tileSizeY = blockSizes[yIndex];

if (descriptor.tileSizeX % 16 != 0) {
LOG.debug("Tile width ({}) not a multiple of 16; correcting",
Expand Down Expand Up @@ -116,15 +208,32 @@ public void describePyramid(ZarrGroup reader, OMEPyramidStore metadata)
}
}

if (dimensions.length != 5) {
throw new FormatException(String.format(
"Expected 5 dimensions in series %d, found %d",
index, dimensions.length));
}
for (int i=0; i<dimensions.length-2; i++) {
if (dimensions[i] != dimensionLengths[2 - i]) {
throw new FormatException(
"Dimension order mismatch in series " + index);
for (int i=0; i<dimensionLengths.length; i++) {
// dimensionLengths is in ZCT order, independent of dimensionOrder
// the two orders may be different if the --rgb flag was used
String axis = "ZCT".substring(i, i + 1);
int axisIndex = getIndex(axis);
LOG.debug("Checking axis {} with index {}, position {}",
axis, axisIndex, i);

if (axisIndex < 0 && dimensionLengths[i] > 1) {
throw new FormatException(axis + " axis expected but not defined");
}
else if (axisIndex >= 0 &&
dimensions[axisIndex] != dimensionLengths[i])
{
// a mismatch on C is usually OK (due to --rgb flag),
// but log it anyway
// a mismatch anywhere else is a problem
if (axis.equalsIgnoreCase("c")) {
LOG.debug("Mismatch on dimension {}; expected {} got {}",
axis, dimensions[axisIndex], dimensionLengths[i]);
}
else {
throw new FormatException(
"Mismatch on dimension " + axis + "; expected " +
dimensions[axisIndex] + ", got " + dimensionLengths[i]);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ public class ResolutionDescriptor {

/** Number of tiles along Y axis. */
Integer numberOfTilesY;

}
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,51 @@ public void testRGBLabelImageDifferentC() throws Exception {
checkRGBIFDs();
}

/**
* Test conversion of 2D (instead of 5D) Zarr.
*/
@Test
public void testCompact2D() throws Exception {
input = fake();
assertBioFormats2Raw("--compact");
assertTool();
iteratePixels();
}

/**
* Test conversion of 3D (instead of 5D) Zarr.
*/
@Test
public void testCompact3D() throws Exception {
input = fake("sizeZ", "10");
assertBioFormats2Raw("--compact");
assertTool();
iteratePixels();
}

/**
* Test conversion of 3D (instead of 5D) Zarr with RGB.
*/
@Test
public void testCompact3DRGB() throws Exception {
input = fake("sizeC", "3", "rgb", "3");
assertBioFormats2Raw("--compact");
assertTool("--rgb");
iteratePixels();
checkRGBIFDs();
}

/**
* Test conversion of 4D (instead of 5D) Zarr.
*/
@Test
public void testCompact4D() throws Exception {
input = fake("sizeT", "4", "sizeZ", "2");
assertBioFormats2Raw("--compact");
assertTool();
iteratePixels();
}

private void checkRGBIFDs() throws FormatException, IOException {
try (TiffParser parser = new TiffParser(outputOmeTiff.toString())) {
IFDList mainIFDs = parser.getMainIFDs();
Expand Down