Conversation
|
@ngladitz thanks for opening the PR and starting this conversation. Could you upload your sample file to the Zenodo Bio-Formats community so that it can be used with the proper licensing/attribution? As you already found out, It would be useful to know more about the workflow of how you would expect this data to be read and displayed in a downstream application. Is your target one of the built-in tools, either the Bio-Formats command-line utility or the ImageJ/Fiji plugin or are you using the API in your own library? |
|
@sbesson thanks I've re-uploaded the file via Zenodo (this is my first such upload so let me know if my submission looks OK. I hope I triggered the process correctly and you actually see my submission. I am unsure if there is a link I could / should have shared while the file is still in the review process). Workflow wise I think most important might be that the presence of complex data within an OBF does not prevent users from accessing any non-complex data within the file. The sample file contains both regular / already supported intensity data as well as complex data but as-is the file can not be opened in ImageJ / Fiji at all due to the reader failing on complex data. Beyond that I ideally would like users to be able to access the complex data as well of course. Any way they like or need for their own use cases or at least as far as is possible with the tools as they are. We don't currently have any internal workflows where this would play a direct role. Our own software implements this independently but is C++ and proprietary. |
|
Thanks, @ngladitz, we have received and approved https://zenodo.org/records/17039369. |
|
@sbesson Is there anything else I should do to move this forward? |
|
@ngladitz we discussed this PR as part of our Formats meeting 2 weeks ago and I hadn't updated this PR with the summary. Thanks for reminding me. From the perspective of Bio-Formats and generally the OME stack, we discussed various options, which are ordered from the most expensive to the cheapest:
Option 1 is mostly listed for completeness, this is a stack wide effort that we are not planning currently. Options 2 and 3 could additionally be activated using reader options to enable some partial support of OBF files with complex images. |
|
@sbesson thank you for the update. I am not sure I understand why a reader option would be desirable in this context. Assuming you'd use it to opt into the complex data workaround anyone not using the option would not be able to open the dataset by default. Presumably nobody gains anything from not being able to open the dataset so why wouldn't that be the default. The other way around I can't imagine anyone wanting to opt into failure either. In the foreseeable future some of our systems will produce acquisitions with complex data by default so I'd really like them to be openable by default too. Option 1 would obviously be ideal but I can certainly see that not being trivial or realistically on the horizon nor would it help short term. Option 3 I don't really like because I assume it would just add to confusion and might break stack identification / indexing. Assuming a dataset with 3 stacks where the middle one is complex I'd prefer if index 1 would refer to the same stack when using Bioformats vs. other readers. Also presumably some customers might genuinely want to access the data. Option 2 I would like to reiterate that metadata wise this can and is already being properly encoded by using the TLDR If I have a vote I'd prefer option 2 without a reader option |
|
Thanks @ngladitz. Our next step is going to evaluate the current proposal in the context of OMERO/OMERO Plus and assess how OBF datasets with different data types including complex would be ingested and rendered with the new proposed behavior. We will report our findings probably by the end of October. If you have additional sample files that would capture the diversity of data that can be generated by the instrument and could be used for this evaluation, do not hesitate to upload them to Zenodo. |
|
|
||
| private int getPixelType(int type) throws FormatException { | ||
| switch (type) { | ||
| switch (type & 0xFFFFFFF) { |
There was a problem hiding this comment.
I don't understand why type & 0xFFFFFFF. Is it worth adding a comment to make it clear? :)
There was a problem hiding this comment.
@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.
|
@ngladitz I got back to this and tried to used this patch to import the sample file uploaded to https://zenodo.org/records/17039369 in an OMERO environment with a modified version of OBFReader. Unfortunately, the import failed server-side side while storing the OME metadata the database with the following exception: Looking at the metadata of the file with the command-line utilities, I believe the above is unrelated to the complex type support but instead due to the usage of <Folder ID="Folder:Sample" Name="Lifetime3">
<FolderRef ID="Folder:Region"/>
</Folder>
<Folder ID="Folder:Region" Name="IMG0005_Lifetime3">
<FolderRef ID="Folder:Timepoint"/>
</Folder>
<Folder ID="Folder:Timepoint">
<ImageRef ID="Image:3"/>
<ImageRef ID="Image:4"/>
<ImageRef ID="Image:5"/>
<ImageRef ID="Image:0"/>
<ImageRef ID="Image:1"/>
<ImageRef ID="Image:2"/>
</Folder>Using this metadata in OME-XML sample files suggests the issue comes with the fact that |
|
@sbesson Thank you for testing. I've uploaded / submitted a new dataset to Zenodo with the Name attribute set for all Folders. |
| case 0x20: return 32; | ||
| case 0x40: return 32; | ||
| case 0x80: return 64; | ||
| case 0x40000040: return 64; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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:

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

(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:

I would be OK with this. Assuming it also resolves the OMERO import issue.
There was a problem hiding this comment.
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)
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
bioformats/components/formats-api/src/loci/formats/IFormatReader.java
Lines 118 to 122 in 5a443de
|
@sbesson happy new year! Gentle poke ... any updates? |
|
Thanks @ngladitz - we still have a couple of things to investigate here, and will update as soon as we've had time to finish that investigation. This pull request is now scheduled for the 9.0.0 release later this year. |
This is intended to add support for OBF complex number image pixel data.
I've attached a corresponding simulated test dataset here:
IMG0005_Lifetime3.zip
The OME XML schema has a
PixelType"complex" which the dataset does use but bioformats and imagej presumably have no direct support for it. I tried to work around that here by making it look like each pixel has two interleaved float samples.It beats not being able to open the file at all but I am not entirely happy with the result.
I would appreciate any insights into how this might be improved or done differently.