Skip to content
Open
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
15 changes: 12 additions & 3 deletions components/formats-bsd/src/loci/formats/in/OBFReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -374,12 +374,19 @@ private long initStack(long current) throws FormatException, IOException {
final int type = in.readInt();
meta_data.pixelType = getPixelType(type);
meta_data.bitsPerPixel = getBitsPerPixel(type);
meta_data.interleaved = false;


if((type & 0x40000000) != 0)
{
meta_data.interleaved = true;
meta_data.sizeC *= 2;
}

stack.bytesPerSample = meta_data.bitsPerPixel / 8;

meta_data.indexed = false;
meta_data.rgb = false;
meta_data.interleaved = false;
meta_data.rgb = meta_data.interleaved;

final int compression = in.readInt();
stack.compression = getCompression(compression);
Expand Down Expand Up @@ -610,7 +617,7 @@ private boolean isFLIMLabel(String label) {
}

private int getPixelType(int type) throws FormatException {
switch (type) {
switch (type & 0xFFFFFFF) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why type & 0xFFFFFFF. Is it worth adding a comment to make it clear? :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gerring In theory any non-complex OBF pixel type (e.g. "float") can be turned into a complex pixel type (e.g. "complex float") by additionally setting the complex bit (0x40000000). The mask is intended to return the underlying pixel type without the complex bit (both "complex float" and "float" become "float").

There are unused / undefined bits between the complex bit and the currently defined types so right now the mask could also be e.g. 0x3FFFFFFF.

Also I say in theory because I assume we'll never see e.g. "complex bool".
Currently I only really care about "complex float" though I also covered "complex double".

The values and their meaning is documented here https://imspectordocs.readthedocs.io/en/latest/fileformat.html#the-obf-file-format

A reference to that documentation is also included at the top of the source file.

case 0x01: return FormatTools.UINT8;
case 0x02: return FormatTools.INT8;
case 0x04: return FormatTools.UINT16;
Expand All @@ -633,6 +640,8 @@ private int getBitsPerPixel(int type) throws FormatException {
case 0x20: return 32;
case 0x40: return 32;
case 0x80: return 64;
case 0x40000040: return 64;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation creates a mismatch between the pixeltype returned by getPixelType (float or double as per the previous API) and the number of bits per pixel returned by this API.

This results in buffer size issues when calling openBytes e.g. when importing into OMERO:

java.lang.RuntimeException: Failure response on import!
Category: ::omero::grid::ImportRequest
Name: import-file-exception
Parameters: {filename=import.user_2/2025-10/28/08-37-25.231/IMG0002_Lifetime3.obf, stacktrace=loci.formats.FormatException: Buffer too small (got 75012, expected 150024).
	at loci.formats.FormatTools.checkBufferSize(FormatTools.java:1048)
	at loci.formats.FormatTools.checkPlaneParameters(FormatTools.java:1004)
	at loci.formats.in.OBFReader.openBytes(OBFReader.java:1006)
	at loci.formats.ImageReader.openBytes(ImageReader.java:466)
	at loci.formats.ChannelFiller.openBytes(ChannelFiller.java:169)
	at loci.formats.ChannelSeparator.openBytes(ChannelSeparator.java:231)
	at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:350)
	at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:350)
	at loci.formats.MinMaxCalculator.openBytes(MinMaxCalculator.java:269)
	at ome.services.blitz.repo.ManagedImportRequestI.parseDataByPlane(ManagedImportRequestI.java:872)
	at ome.services.blitz.repo.ManagedImportRequestI.parseData(ManagedImportRequestI.java:803)
	at ome.services.blitz.repo.ManagedImportRequestI.pixelData(ManagedImportRequestI.java:676)
	at ome.services.blitz.repo.ManagedImportRequestI.step(ManagedImportRequestI.java:525)

Either these lines need to be updated to 32 and 64 respectively or the check on type on line 634 needs to be updated to switch(type & 0xFFFFFFF) as in getPixelType

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbesson Thank you. I am not sure I understand. It did seem to work as-is (at least in this context) with ImageJ. The correct buffer size seems to get allocated in that case. The images can be opened and inspected.

The values returned feel correct. A single complex float pixel is 64 bit (2 components with 32bit each).
Maybe not all call paths calculate the require buffer size the same way?

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the primary difference is the addition of the ChannelSeparator API in the stack. The exception above can be reproduced more simply using the Bio-Formats command-line tools with the -separate option:

sbesson@Sebastien-GS-MacBook-Pro-2025 bioformats % ./tools/showinf -series 2 ~/Downloads/IMG0002_Lifetime3.obf -separate
Checking file format [OBF]
Initializing reader
OBFReader initializing /Users/sbesson/Downloads/IMG0002_Lifetime3.obf
Parsing schema path
http://www.openmicroscopy.org/Schemas/OME/2016-06/ome.xsd
Validating OME-XML
No validation errors found.
Initialization took 0.27s

Reading core metadata
filename = /Users/sbesson/Downloads/IMG0002_Lifetime3.obf
Series count = 9
Series #0 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #1 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #2 :
	Image count = 1
	RGB = false (2) (separated)
	************ RGB mismatch ************
	Interleaved = true
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 2 (effectively 1)
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = float
	Valid bits per pixel = 64
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #3 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #4 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #5 :
	Image count = 1
	RGB = false (2) (separated)
	************ RGB mismatch ************
	Interleaved = true
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 2 (effectively 1)
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = float
	Valid bits per pixel = 64
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #6 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #7 :
	Image count = 1
	RGB = false (1) (separated)
	Interleaved = false
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 1
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = uint16
	Valid bits per pixel = 16
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0

Series #8 :
	Image count = 1
	RGB = false (2) (separated)
	************ RGB mismatch ************
	Interleaved = true
	Indexed = false (false color)
	Width = 141
	Height = 133
	SizeZ = 1
	SizeT = 1
	SizeC = 2 (effectively 1)
	Tile size = 141 x 133
	Thumbnail size = 128 x 120
	Endianness = intel (little)
	Dimension order = XYZTC (certain)
	Pixel type = float
	Valid bits per pixel = 64
	Metadata complete = false
	Thumbnail series = false
	-----
	Plane #0 <=> Z 0, C 0, T 0


Reading series #2 pixel data (0-0)
Exception in thread "main" loci.formats.FormatException: Buffer too small (got 75012, expected 150024).
	at loci.formats.FormatTools.checkBufferSize(FormatTools.java:1048)
	at loci.formats.FormatTools.checkPlaneParameters(FormatTools.java:1004)
	at loci.formats.in.OBFReader.openBytes(OBFReader.java:1006)
	at loci.formats.ImageReader.openBytes(ImageReader.java:466)
	at loci.formats.ChannelSeparator.openBytes(ChannelSeparator.java:231)
	at loci.formats.ChannelSeparator.openBytes(ChannelSeparator.java:163)
	at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:336)
	at loci.formats.gui.BufferedImageReader.openImage(BufferedImageReader.java:86)
	at loci.formats.tools.ImageInfo.readPixels(ImageInfo.java:840)
	at loci.formats.tools.ImageInfo.testRead(ImageInfo.java:1074)
	at loci.formats.tools.ImageInfo.main(ImageInfo.java:1165)

Note also the RGB mismatch warnings in the output above which suggests the value of the CoreMetadata.rgb flag should be conditional depending on the value of CoreMetadata.interleaved with this strategy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbesson Thank you! Does this work for you as well?

diff --git a/components/formats-bsd/src/loci/formats/in/OBFReader.java b/components/formats-bsd/src/loci/formats/in/OBFReader.java
index 5cc5b87e3d..ccadbacf3d 100644
--- a/components/formats-bsd/src/loci/formats/in/OBFReader.java
+++ b/components/formats-bsd/src/loci/formats/in/OBFReader.java
@@ -386,7 +386,7 @@ public class OBFReader extends FormatReader {
       stack.bytesPerSample = meta_data.bitsPerPixel / 8;
 
       meta_data.indexed = false;
-      meta_data.rgb = false;
+      meta_data.rgb = meta_data.interleaved;
 
       final int compression = in.readInt();
       stack.compression = getCompression(compression);

As you suggested it does seem to make ChannelSeparator happy:
image

Of course the data isn't actually RGB and without ChannelSeparator the results are a little less desirable:
image

image

(The channel slider does nothing here)

I see I can opt into the channel separation from the import dialog in ImageJ and that results in something perhaps slightly more desirable:
image

I would be OK with this. Assuming it also resolves the OMERO import issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ngladitz. From a round of testing, setting the rgb flag as you proposed yields sensible results both in the command-line tools as well and the image import into OMERO (the channel color is off which will warrant separate investigation)

Screenshot 2025-11-03 at 09 02 56

Feel free to push your change to this PR so that we can check the impact on already configured OBF datasets in the nightly CI repositories.

Discussing with @melissalinkert, the change to getBitsPerPixel to return 64/128 for complex data still comes a surprise as it contradicts the API expectation as defined in

/**
* Gets the number of valid bits per pixel. The number of valid bits per
* pixel is always less than or equal to the number of bits per pixel
* that correspond to {@link #getPixelType()}.
*/
. This will need additional investigation on our end.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbesson Thank you! I pushed the change.

case 0x40000080: return 128;
default: throw new FormatException("Unsupported data type " + type);
}
}
Expand Down