OIR: fix two parse failures on Olympus LEXT (OLS) acquisitions - #4465
OIR: fix two parse failures on Olympus LEXT (OLS) acquisitions#4465PEEKPerformer wants to merge 2 commits into
Conversation
parseImageProperties assumed every commonphase:channel under
commonimage:imageInfo carries an 'order' attribute:
int index = Integer.parseInt(channelNode.getAttribute("order")) - 1;
Element.getAttribute returns an empty string rather than null for an
absent attribute, so on files that omit it this throws
NumberFormatException from setId and the file cannot be opened at all.
Olympus/Evident LEXT (OLS) acquisitions do omit it: their imageInfo
channel elements carry only an id, and the ordering is recorded on the
lsmimage:LSMChannel elements elsewhere in the document. Those ids appear
under imageInfo in the same sequence as their recorded order, so
document order is the correct index when the attribute is missing.
Fall back to the loop counter in that case. Files that do carry 'order'
are parsed exactly as before, so this cannot affect any dataset that
opens today.
| // 'depth' is normally the total byte count across all components, but | ||
| // some RGB frames record the per-component count instead; only divide | ||
| // when the result would still be a valid byte depth. | ||
| if (rgb && bytes >= 3) { |
There was a problem hiding this comment.
Here and in the other places where a check for >= 3 is introduced, would it make more sense to check for something like bytes % 3 == 0?
There was a problem hiding this comment.
Yes, that is much better, thanks Melissa! I changed at all four sites in 503f590. Appreciate your speedy feedback!
76eb916 added component channel support, splitting an RGB channel into one OME channel per component and dividing the recorded depth by three to get bytes per component: int bytes = Integer.parseInt(depth.getTextContent()); if (rgb) { bytes /= 3; } m.pixelType = FormatTools.pixelTypeFromBytes(bytes, false, false); That is correct where 'depth' is the total across all three components. Olympus/Evident LEXT (OLS) camera images record it per component instead, so the integer division yields 0 and pixelTypeFromBytes throws: FormatException: Unsupported byte depth: 0 The file is inconsistent about which convention it uses. The channel level imageDefinition gives depth 1 for a channel that carries three ElementChannelInfo components, while the frame level definitions for the same data give depth 3 with bitCounts 24. A total across three components is always a multiple of three, so only divide when the value divides evenly, and apply the same test to bitCounts. This also corrects bitsPerPixel on the same files: with bitCounts 8 and colorType RGB, dividing unconditionally reported 2 valid bits per pixel instead of 8.
3555002 to
503f590
Compare
Are you able to provide a wider range of |
|
Yeah, the instrument manual describes .poir as the 'OLS5000/OLS5100/OLS5500 standard file format' and the save dialogs default to it. Stitching acquisitions produce .mpoir so it's what these instruments emit unless SDF/JPEG/BMP/PNG is manually exported by the user. On breadth, more than happy to spend more time on the instrument. I'll check which modes our instrument supports and will deposit as much variation as I can. Happy to try and fill special requests if you have any! Edit: will ping you when i get the files :) |
|
@melissalinkert submitted, pending approval. 10.5281/zenodo.22017632 I found some files that trigger the bug from the description above. Four snapshot tiles in the set hit the unguarded elementChannel order parseInt (no order attribute on their ElementChannelInfo). A near-identical passing file is included. Happy to fix it like the imageInfo site, here or as a follow-up. Just let me know :) Would be happy to take a stab at the reader as well with your blessing. Nothing has a SizeT > 1 because the instrument lacks a time series mode. |
Refs: Opening POIR Olympus files on image.sc.
OIRReadercannot open.oirfiles produced by Olympus/Evident LEXT (OLS) instruments. Nothing structural is wrong: the reader already locates the pixel blocks, resolves the channels and reads the metadata correctly once it gets past twoNumberFormatException/FormatExceptionsites during initialisation.Neither case is LEXT-specific in principle. Both are places where a field that is present, or scaled a particular way, in every FV-series file behaves differently elsewhere.
No open issue tracks either of these. #4132, #3808 and #3748 were the recent OIR reports and all were closed by #4360.
1. Missing
orderattribute inparseImagePropertiesIntroduced in bc3a949 (2017), "Use
orderattribute to set channel ordering":Element.getAttributereturns"", notnull, for an absent attribute, so this throwsNumberFormatExceptionout ofsetIdand the file cannot be opened at all.LEXT files omit it: the
commonphase:channelelements undercommonimage:imageInfocarry only anid, and the ordering lives on thelsmimage:LSMChannelelements elsewhere in the document. Those ids appear underimageInfoin the same sequence as their recorded order, so document order is the correct index when the attribute is missing.For comparison, in the two public sample sets under
downloads.openmicroscopy.org/images/Olympus-OIR/the same elements readorder="2"andorder="3", so the new branch is unreachable for them.2. RGB depth divided below one byte, in
parseFramePropertiesandparseImagePropertiesThis one extends recent work. 76eb916 ("OIR: support data with component channels", #4360, fixes #4132) added component channel handling, which splits an RGB channel into one OME channel per component and divides the recorded depth by three:
That is right where
depthis the total across all three components. The LEXTcamera image is a genuine component channel case, one
commonphase:channelwith
order="1"and threeElementChannelInfochildren, but it records depthper component, so the division yields 0:
Correcting something I said earlier in this description: I had claimed the file was self-inconsistent here. It is not. The
commonframe:imageDefinitionfor the 1024x1024 image is flat and unambiguous,colorType RGBwithdepth 1andbitCounts 8. Thedepth 3/bitCounts 24I was comparing it against belongs to a different, 1024x755 image in the same file. So the file uses the per-component convention for one image and the total convention for another, and divisibility tells them apart correctly.A total across three components is always a multiple of three, so the fix only divides when the value divides evenly, with the same test applied to
bitCounts. Adepththat is not a multiple of three cannot be a total, so this also stopsbitCounts 8on an RGB component being reported as 2 valid bits per pixel.This is a heuristic. Since it sits on top of very recent work, I would rather defer on it: if there is a better way to tell the two conventions apart (component count,
bitCounts, an explicit variant check) I am happy to rework this half.One limit of the divisibility test worth recording. It is sound for byte depths, since the valid per-component values are 1, 2 and 4 and none of those divide by three. It is weaker for
bitCounts, where 12 is both a plausible per-component depth and a multiple of three, so a 12-bit-per-component RGB file recorded per-component would be halved to 4. I have no such file to test against, so I have not tried to special-case it.Code
+15 -5 in
OIRReader.java, three hunks, split across two commits so each bug can be reviewed and reverted independently. Both commits build on their own.Validation
Against the README PR testing checklist:
develop(git merge-tree, 0 conflicts).ant clean jars tools: BUILD SUCCESSFUL.mvn -DskipTests installcompletes successfully.ant test: 1897 tests, 0 failures, 0 skips, 5m01s.if/String.trim/isEmpty).showinfon every affected format, below.Regression against the public corpus
showinf -nopix -novalidoutput on both public Olympus-OIR sample sets isbyte-identical before and after this branch (1343 and 857 lines
respectively,
diffclean):imagesc-105684/1202-interval_10sec_sequence_frame.oirdevelopetienne/amy slice z stack_0001.oirdevelopThis is the expected outcome given the analysis above: fix 1's fallback is unreachable when
orderis present, and fix 2 only alters inputs that currently raiseFormatException.LEXT files, previously unopenable
These come from a single OLS5500 session in our lab on 2026-08-03. Both
.poirfiles below are deposited under CC-BY 4.0, see the note at the end, so this is reproducible once the deposit clears review. Each row is a distinct case rather than a distinct dataset: two of the three come out of the same.poir..oir260803_1437_000_LSM3D^3D_LSM.oir001.poirNumberFormatException260803_1448_000_LSM3D^3D_LSM.oir002.poirNumberFormatException260803_1448_000_COLOR3D^XY_Camera.oir002.poirUnsupported byte depth: 0LSM3Dis the laser confocal result, three channels of colour, height and intensity for a single plane.COLOR3Dis the companion colour camera image, and is the component channel case discussed above. Every.poirhere except001.poircontains one of each.Metadata now resolves as expected, e.g.
5 µm/px at 10× over 256 px is a 1.28 mm field, which matches the objective.
End-to-end,
bfconvertproduced OME-TIFF for 79 inner.oirfiles with 0 failures: 9 from five single acquisitions, and 70 from one 35-field mosaic. All from the same instrument and session, so this is depth of coverage on one LEXT variant rather than breadth across the family.One thing that looks wrong but is not:
showinfreportsValid bits per pixel = 7on one LSM3D channel. The file really does recordbitCounts 7there, and that channel's pixel values top out at 90.Notes
The instrument here is an OLS5500; files came off it as
.poirand.mpoir. Those are Zip64 wrappers..poircontains one or more.oir, and.mpoircontainsmatl.omp2infoplus one.poirper field (entries stored, not deflated). This PR does not add readers for either wrapper; it only makes the inner.oirreadable once extracted, which is what the image.sc thread ran aground on six years ago.If a
.poir/.mpoirreader would be welcome I'm happy to work on it, but per the external-readers guidance I'd rather ask first whether you'd want it in core or as an external reader. Happy to go either way.I don't have access to the curated data repo, so
ant test-automatedis needed.Five
.poirfiles from this session, including the two used above, are deposited under CC-BY 4.0 as 10.5281/zenodo.21792018 and submitted to the Bio-Formats Zenodo community, where they are awaiting approval.Between them they cover both parse paths this PR touches, so the change should be testable against the curated corpus if you want to add them. Polymer composite surface metrology, no biological content. Happy to deposit the
.mpoirmosaic as well if that case is useful, though it is 323 MiB and exercises nothing the single acquisitions do not.One sibling worth noting, not changed here:
parseImagePropertiesalso does an unguardedInteger.parseInt(component.getAttribute("order"))onelementChannelcomponents, added by the same commit as case 2. The LEXT files do populateorderthere, so it is not reachable for this data, but it is the same pattern as case 1.