From ce304f93d27ea4114494d9598fc0fc630dc5615f Mon Sep 17 00:00:00 2001 From: Melissa Linkert Date: Mon, 23 Mar 2026 11:59:58 -0500 Subject: [PATCH 1/3] Imaris HDF: allow paths to be in a subdirectory See https://forum.image.sc/t/ims-files-cannot-open-in-bio-formats-again/118877/10 --- .../src/loci/formats/in/ImarisHDFReader.java | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java index 13827c8c2f0..c8ff7272851 100644 --- a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java +++ b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java @@ -61,6 +61,7 @@ public class ImarisHDFReader extends SubResolutionFormatReader { // -- Fields -- + private String pathPrefix = ""; private double pixelSizeX, pixelSizeY, pixelSizeZ; private double minX, minY, minZ, maxX, maxY, maxZ; private int seriesCount; @@ -229,6 +230,7 @@ else if (image instanceof double[][]) { public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { + pathPrefix = ""; seriesCount = 0; pixelSizeX = pixelSizeY = pixelSizeZ = 0; minX = minY = minZ = maxX = maxY = maxZ = 0; @@ -288,7 +290,7 @@ protected void initFile(String id) throws FormatException, IOException { for (int i=1; i table = netcdf.getVariableAttributes(datasetPath); String chunkSizesString = (String) table.get("_ChunkSizes"); String[] sizes = chunkSizesString.split(" "); @@ -492,7 +494,8 @@ private Object getImageData(int no, int x, int y, int width, int height) // cache dataset blocks to avoid multiple reads. // use caching for 3D datasets and only if the size of the required buffer is < maxBufferSize - if (getSizeZ() > 1 && getSizeX() * getSizeY() * blockSizeZPerResolution[resolutionIndex] * getSizeC() * FormatTools.getBytesPerPixel(getPixelType()) < maxBufferSize) { + long blockSize = (long) getSizeX() * getSizeY() * blockSizeZPerResolution[resolutionIndex] * getSizeC() * FormatTools.getBytesPerPixel(getPixelType()); + if (getSizeZ() > 1 && blockSize < maxBufferSize) { // update buffer if needed if (zct[0] < lastMinZ || zct[0] > lastMaxZ || zct[2] != lastT || resolutionIndex != lastRes || buffer == null) { buffer = new Object[getSizeC()]; @@ -510,7 +513,7 @@ private Object getImageData(int no, int x, int y, int width, int height) try { String path; for (int ch = 0; ch < getSizeC(); ch++) { - path = "/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + ch + "/Data"; + path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + ch + "/Data"); buffer[ch] = netcdf.getArray(path, idcs, dims); } } @@ -568,7 +571,7 @@ else if (buffer[zct[1]] instanceof float[][][]) { int[] dimensions = new int[] {1, height, width}; int[] indices = new int[] {zct[0], y, x}; try { - String path = "/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + zct[1] + "/Data"; + String path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + zct[1] + "/Data"); image = netcdf.getArray(path, indices, dimensions); } catch (ServiceException e) { @@ -587,7 +590,7 @@ private Object getSampleData() int[] dimensions = new int[] {1, 2, 2}; int[] indices = new int[] {0, 0, 0}; try { - String path = "/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_0/Channel_0/Data"; + String path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_0/Channel_0/Data"); image = netcdf.getArray(path, indices, dimensions); } catch (ServiceException e) { @@ -607,7 +610,10 @@ private void parseAttributes() { if (value == null) continue; value = value.trim(); - if (name.equals("X") || (attr.startsWith("DataSet/ResolutionLevel_0") && name.equals("ImageSizeX"))) { + if (name.equals("ImarisVersion")) { + pathPrefix = attr.substring(0, attr.lastIndexOf("/")); + } + else if (name.equals("X") || (attr.startsWith(getPath("DataSet/ResolutionLevel_0")) && name.equals("ImageSizeX"))) { try { ms0.sizeX = Integer.parseInt(value); } @@ -615,7 +621,7 @@ private void parseAttributes() { LOGGER.trace("Failed to parse '" + name + "'", e); } } - else if (name.equals("Y") || (attr.startsWith("DataSet/ResolutionLevel_0") && name.equals("ImageSizeY"))) { + else if (name.equals("Y") || (attr.startsWith(getPath("DataSet/ResolutionLevel_0")) && name.equals("ImageSizeY"))) { try { ms0.sizeY = Integer.parseInt(value); } @@ -623,7 +629,7 @@ else if (name.equals("Y") || (attr.startsWith("DataSet/ResolutionLevel_0") && na LOGGER.trace("Failed to parse '" + name + "'", e); } } - else if (name.equals("Z") || (attr.startsWith("DataSet/ResolutionLevel_0") && name.equals("ImageSizeZ"))) { + else if (name.equals("Z") || (attr.startsWith(getPath("DataSet/ResolutionLevel_0")) && name.equals("ImageSizeZ"))) { try { ms0.sizeZ = Integer.parseInt(value); } @@ -653,14 +659,16 @@ else if (name.equals("RecordingEntryPlaneSpacing")) { else if (name.equals("ExtMin1")) minY = Double.parseDouble(value); else if (name.equals("ExtMin2")) minZ = Double.parseDouble(value); - if (attr.startsWith("DataSet/ResolutionLevel_")) { - int slash = attr.indexOf("/", 24); - int n = Integer.parseInt(attr.substring(24, slash == -1 ? - attr.length() : slash)); + String resolutionCheck = getPath("DataSet/ResolutionLevel_"); + if (attr.startsWith(resolutionCheck)) { + int slash = attr.indexOf("/", resolutionCheck.length()); + String resIndex = attr.substring(resolutionCheck.length(), + slash == -1 ? attr.length() : slash); + int n = Integer.parseInt(resIndex); if (n >= seriesCount) seriesCount = n + 1; } - if (attr.startsWith("DataSetInfo/Channel_")) { + if (attr.startsWith(getPath("DataSetInfo/Channel_"))) { String originalValue = value; for (String d : DELIMITERS) { if (value.indexOf(d) != -1) { @@ -724,4 +732,11 @@ private void addValue(List l, Object value, int index) { } } + private String getPath(String path) { + if (pathPrefix.isEmpty()) { + return path; + } + return pathPrefix + "/" + path; + } + } From b417b7bc901cf2829d5e3f7734eec33e5d548de6 Mon Sep 17 00:00:00 2001 From: Melissa Linkert Date: Wed, 25 Mar 2026 12:55:21 -0500 Subject: [PATCH 2/3] Use `ImarisDataSet` attribute to determine path to image data --- .../formats-gpl/src/loci/formats/in/ImarisHDFReader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java index c8ff7272851..b314e091d80 100644 --- a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java +++ b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java @@ -610,8 +610,9 @@ private void parseAttributes() { if (value == null) continue; value = value.trim(); - if (name.equals("ImarisVersion")) { + if (name.equals("ImarisDataSet")) { pathPrefix = attr.substring(0, attr.lastIndexOf("/")); + LOGGER.debug("Set path prefix to {}", pathPrefix); } else if (name.equals("X") || (attr.startsWith(getPath("DataSet/ResolutionLevel_0")) && name.equals("ImageSizeX"))) { try { From c0e8d88b205fc71f3f3932aa235a41b129c0dbea Mon Sep 17 00:00:00 2001 From: Melissa Linkert Date: Thu, 26 Mar 2026 13:10:43 -0500 Subject: [PATCH 3/3] Simplify plane path retrieval --- .../src/loci/formats/in/ImarisHDFReader.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java index b314e091d80..34b1952fd64 100644 --- a/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java +++ b/components/formats-gpl/src/loci/formats/in/ImarisHDFReader.java @@ -289,8 +289,7 @@ protected void initFile(String id) throws FormatException, IOException { if (seriesCount > 1) { for (int i=1; i table = netcdf.getVariableAttributes(datasetPath); String chunkSizesString = (String) table.get("_ChunkSizes"); String[] sizes = chunkSizesString.split(" "); blockSizeZPerResolution[res] = Integer.parseInt(sizes[0]); } - + // determine pixel type - this isn't stored in the metadata, so we need // to check the pixels themselves @@ -513,7 +512,7 @@ private Object getImageData(int no, int x, int y, int width, int height) try { String path; for (int ch = 0; ch < getSizeC(); ch++) { - path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + ch + "/Data"); + path = getPlaneDataPath(resolutionIndex, zct[2], ch); buffer[ch] = netcdf.getArray(path, idcs, dims); } } @@ -571,14 +570,14 @@ else if (buffer[zct[1]] instanceof float[][][]) { int[] dimensions = new int[] {1, height, width}; int[] indices = new int[] {zct[0], y, x}; try { - String path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_" + zct[2] + "/Channel_" + zct[1] + "/Data"); + String path = getPlaneDataPath(resolutionIndex, zct[2], zct[1]); image = netcdf.getArray(path, indices, dimensions); } catch (ServiceException e) { throw new FormatException(e); } } - + return image; } @@ -590,7 +589,7 @@ private Object getSampleData() int[] dimensions = new int[] {1, 2, 2}; int[] indices = new int[] {0, 0, 0}; try { - String path = getPath("/DataSet/ResolutionLevel_" + resolutionIndex + "/TimePoint_0/Channel_0/Data"); + String path = getPlaneDataPath(resolutionIndex, 0, 0); image = netcdf.getArray(path, indices, dimensions); } catch (ServiceException e) { @@ -733,6 +732,14 @@ private void addValue(List l, Object value, int index) { } } + private String getPlanePath(int res, int t, int c) { + return getPath("DataSet/ResolutionLevel_" + res + "/TimePoint_" + t + "/Channel_" + c); + } + + private String getPlaneDataPath(int res, int t, int c) { + return getPlanePath(res, t, c) + "/Data"; + } + private String getPath(String path) { if (pathPrefix.isEmpty()) { return path;