Skip to content

fix Nd2 wellinfo missing#4448

Open
maartenpaul wants to merge 3 commits into
ome:developfrom
maartenpaul:nd2_wellinfo
Open

fix Nd2 wellinfo missing#4448
maartenpaul wants to merge 3 commits into
ome:developfrom
maartenpaul:nd2_wellinfo

Conversation

@maartenpaul

Copy link
Copy Markdown

Problem

Users in our facility generate ND2 files containing multiple well positions from multiwell plate acquisitions. When loaded into Fiji or imported into OMERO, the well names are lost and series appear as generic (series 01), (series 02), etc. ; annoying when working with HCS plate data
The Python nd2 library parses the well metadata correctly, so I investigated whether this could be fixed in Bio-Formats as well.

The root cause turned out to be two separate issues depending on the file structure of the ND2 files:

  1. Files with a flat XY position loop (e.g. Z-stack per well): for the ND2 files available, the position names are stored in the binary metadata under pPosName, which the XML parsing path (ND2Handler) already captured. However, the binary parsing path did not extract them at all, so files without XML metadata fell back to (series 01).

  2. Files with a nested TimeLoop → XYPosLoop structure: position names are stored under dPosName. The binary CLX stream contains empty-string placeholder entries for each outer-loop level, followed by the actual names — causing a misalignment between name indices and series indices. Additionally, ND2Handler only captured pPosName, not dPosName.

Position names were also not accessible in series metadata, as reported in #3449. The same problem, position names not read per-position, with every series taking the name/metadata of the last position, was reported on image.sc: Position info in .nd2 file can not be retrieved correctly by Bioformat.

I would highly appreciate feedback on this PR. I am not an advanced Java programmer and received help from Claude, but given that this would be highly valuable for our users I created this PR. I have tested with ND2 files from our facility and the fix works as expected in Fiji and when imported into OMERO.

Changes

  • Extract position names from binary ND2 blocks: Position names can appear under different keys depending on the NIS-Elements version (pPosName, sPosition, or dPosName). The binary parsing path (iterateIn) now captures all three key variants, skipping empty-string placeholders that appear in nested loop structures.
  • Capture dPosName in ND2Handler XML path: Files with a TimeLoop → XYPosLoop structure store position names under dPosName as a direct XML attribute. ND2Handler now captures these alongside the existing pPosName list-variant handling.
  • Fix setSeries() call placement: setSeries(i) was previously called inside a conditional block (if (tsT.size() > 0)), meaning it was skipped when no timestamps were present. This caused subsequent series metadata operations (position X/Y/Z, position name) to run in the wrong series context.
  • Expose the position name via OME StageLabel and series metadata: each series' OME-XML now carries a <StageLabel Name="…"> (matching the convention used by ZeissCZIReader), and a "Position name" entry is added to the series metadata table. The raw pPosName/dPosName keys already appear in the global (original) metadata via the standard binary dump, so no separate global-list entry is added.
  • Drop redundant global X/Y/Z entries in ND2Handler: the dPosX/dPosY/dPosZ items were written to global metadata here and again by ND2Reader's per-series loop (which already emits them as both series and global metadata). The duplicate ND2Handler entries are removed; posX/posY/posZ are still collected for plane positions, so no position data is lost.

Binary and XML sources are combined so that XML-derived names take precedence when present, falling back to the binary-derived names otherwise.

The position naming format matches the Python nd2 library — raw position names from the metadata (e.g. A10_0000, A3[H2Ax_KRT19]) are used as-is.

Relates to #3449 and #4287. This PR partially addresses #4287 by storing the position name as series metadata and by removing the duplicate global X/Y/Z entries in ND2Handler. A full migration of the remaining per-position keys from global to series metadata (ND2Reader still also emits X/Y/Z into global) is left out of scope here.

Testing (showinf / ImageInfo)

Flat XY position loop — Seq0000.nd2 (24 series):

<Image ID="Image:0" Name="Seq0000.nd2 A10_0000"
<StageLabel Name="A10_0000"
<Image ID="Image:1" Name="Seq0000.nd2 A11_0000"
<StageLabel Name="A11_0000"
<Image ID="Image:2" Name="Seq0000.nd2 A12_0000"
<StageLabel Name="A12_0000"

Nested TimeLoop → XYPosLoop (dPosName):

<Image ID="Image:0" Name="…1002.nd2 A3[H2Ax_KRT19]"
<StageLabel Name="A3[H2Ax_KRT19]"
<Image ID="Image:1" Name="…1002.nd2 A2[NegatieCtr]"
<StageLabel Name="A2[NegatieCtr]"

Names match the Python nd2 library's Point.name.

Verification checklist

  • Branch rebased cleanly onto current develop
  • Correct authorship and signed-off-by line
  • Compiles with Maven
  • Unit tests pass Maven and Ant
  • showinf on flat and nested ND2 files shows correct well names + StageLabel (see Testing section)
  • Tested in Fiji: series names correctly show well names (e.g. A10_0000, A3[H2Ax_KRT19]) for both flat and nested loop ND2 files

maartenpaul and others added 3 commits July 1, 2026 22:03
- Add position name extraction from binary ND2 blocks (pPosName/sPosition/dPosName)
- Use position names for series image names and series metadata
- Move per-series keys (position X/Y/Z, position name) out of global metadata
- Fix setSeries() call placement to ensure correct series context when reading timestamps
- Remove duplicate global metadata entries for X/Y/Z positions

Signed-off-by: Maarten Paul <maarten.paul@gmail.com>
For ND2 files with a TimeLoop → XYPosLoop structure, the binary CLX
metadata contains empty-string dPosName placeholder entries (one per
position per outer loop level) followed by the actual position name
strings. Previously all entries were added to posNames, misaligning
the real names (at indices 24-47) with the series (indices 0-23).

Fix: skip empty strings when populating posNames from binary metadata.

Also capture dPosName as a direct XML attribute in ND2Handler, to
handle files that encode position names this way rather than as a
CLxListVariant list.

Signed-off-by: Maarten Paul <maarten.paul@gmail.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the well/position name as an OME <StageLabel Name="..."> for each
series (matching ZeissCZIReader) and as a "Position name" series
metadata entry. The raw pPosName/dPosName keys are already dumped to
the global metadata, so the previous addGlobalMetaList call is dropped
to avoid duplication.

Signed-off-by: Maarten Paul <maarten.paul@gmail.com>
@melissalinkert

Copy link
Copy Markdown
Member

Thanks, @maartenpaul. Before we can consider including this, we do need:

See also https://ome-contributing.readthedocs.io/en/latest/code-contributions.html#procedure-for-accepting-code-contributions. Please let us know when both the CLA and test data have been submitted.

@maartenpaul

Copy link
Copy Markdown
Author

@melissalinkert I've submitted the CLA and uploaded test data to OME Zenodo

@melissalinkert

Copy link
Copy Markdown
Member

Thanks @maartenpaul, I confirm we have received the CLA and the test data is https://zenodo.org/records/21162526.

@melissalinkert melissalinkert self-requested a review July 3, 2026 13:56
@melissalinkert

Copy link
Copy Markdown
Member

Workflows approved and include label added so that we can see how this impacts existing tests on Monday. I fully expect a variety of test failures on the ImageName test in particular, which is completely normal for a change that updates image names.

@melissalinkert melissalinkert left a comment

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.

As expected, this causes some test failures on the ImageName tests: https://bf-testing-results.s3.amazonaws.com/2026/2026-07-06/nd2.log. For the most part, these test failures are expected and match up with the position names reported in NIS Elements.

A few immediate comments when looking through the affected files though:

  • In some files, all position names are identical. This change means it is no longer easy to distinguish by name which series is which. Maybe when a position name is present, formatting the Image name like <position name> (series <index>) instead of <file name> <position name> is a compromise? https://downloads.openmicroscopy.org/images/ND2/zenodo-16921650/ is a public example with identical position names (but we have at least one non-public example with > 100 positions).
  • When empty position names are stored, this results in an incorrect mapping between the series index and position name. For example, if there are 3 series (positions) with stored names of A, empty string, and B, the reported Image names will be file.nd2 A, file.nd2 B, and file.nd2 (series 3). I don't have a public example of this unfortunately.

Removing the include label for now, but can add it again once the above points are addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants