Skip to content

fix: assign fov names for plate positions with grid_plan#136

Merged
tlambert03 merged 5 commits into
pymmcore-plus:mainfrom
ieivanov:fix/plate-position-naming
Apr 20, 2026
Merged

fix: assign fov names for plate positions with grid_plan#136
tlambert03 merged 5 commits into
pymmcore-plus:mainfrom
ieivanov:fix/plate-position-naming

Conversation

@ieivanov

@ieivanov ieivanov commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • When stage_positions have plate_row/plate_col but no explicit name, generate fov-indexed names consistent with WellPlatePlan convention
  • With grid: each grid sub-position gets fov0, fov1, ... per well
  • Without grid: each position is fov0 in its well
  • Explicit user-provided names are always preserved
  • Existing naming conventions for non-plate positions are unchanged (numeric index, RandomPoints suffixes, etc.)
  • Refactors naming logic into a dedicated _grid_position_name helper; _pos_with_grid_point now only handles coordinate merging

Closes #131
Supersedes #135

What this does NOT change

  • Non-plate position naming ("0", "1", etc.)
  • RandomPoints naming (rp_g0000, rp_p0000_g0001, etc.)
  • WellPlatePlan naming (fov0, fov1, etc.)
  • All existing tests pass unmodified

Test plan

  • All 681 tests pass (3 new, 678 existing unmodified)
  • plate_positions_with_global_grid — plate + grid, no names → fov0/fov1 per well
  • plate_positions_no_grid — plate without grid, no names → fov0 per well
  • plate_positions_with_explicit_names — user names preserved
  • End-to-end verified with shrimpy CLI (3 plate positions + 3x3 grid)

🤖 Generated with Claude Code

When stage_positions have plate_row/plate_col but no explicit name,
generate fov-indexed names consistent with WellPlatePlan:
- With grid: fov0, fov1, ... per grid point within each well
- Without grid: fov0 (single FOV per well)
- Explicit names are always preserved

Closes pymmcore-plus#131

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.52%. Comparing base (b319cd0) to head (d37601e).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #136      +/-   ##
==========================================
- Coverage   97.63%   97.52%   -0.11%     
==========================================
  Files          21       21              
  Lines        2534     2630      +96     
==========================================
+ Hits         2474     2565      +91     
- Misses         60       65       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ieivanov and others added 4 commits April 16, 2026 20:12
Extract _grid_position_name helper that centralizes all position naming:
- Explicit user name (with RandomPoints suffix when needed)
- fov{index} for plate positions
- str(position_index) fallback

_pos_with_grid_point now only handles coordinate merging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_grid_position_name now checks pos.plate_row/plate_col directly
instead of taking a pre-computed has_plate flag. Same for the
no-grid branch in _build_stage_positions_plan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use _grid_position_name for both grid-expanded and standalone positions,
eliminating duplicated naming logic. Update docstring with full parameter
docs and examples.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ieivanov

ieivanov commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

@tlambert03 I think I'm happy with this PR. To clarify again in my own words, this PR fixes the bug described in #131 and organizes the naming logic for positions with grid in _grid_position_name. I've added tests and have not changed the current naming conventions. Let me know what you think, happy to discuss more.

P.S. Not sure why that test fails, it's not related to the work in this PR I think

@ieivanov

Copy link
Copy Markdown
Contributor Author

But to me honest, the issue is still complicated and a bit confusing to me. For example, useq.WellPlatePlan names positions generates position names like A1_0000, B2_0001 but these don't get used in ome-writers - we generate f"fov{idx} or in pymmcore-plus as event.pos_name. Similarly, within useq grid points are named 0000, 0001, but ome-writers and pymmcore-plus don't use them - or at least not consistently.

I still think it would be cleanest if naming logic lives in useq - once a name is set by the user or assigned based on some logic, it should not be changed. This is what I has started in pymmcore-plus/useq-schema#268, but maybe that went a little too far.

Let me know what you think here. One clean option may be to remove auto-naming from useq, and handle that in pymmcore-plus and ome-writers as these may have different naming requirements. For example, in pymmcore-plus we may chose to set event.pos_name to f"{plate_row}{plate_col}_{fov_name}" and in ome-writers we'll need to create a zarr group called f"{plate_row}/{plate_col}/{fov_name}". Happy to discuss ideas

@tlambert03

Copy link
Copy Markdown
Member

thanks for this!

I think my main question here would be how you intend to use this name. Would you be "depending" on the specifics of the name format to, for example, parse the name to extract grid information? Or is the goal here just to give a name that is better than we currently have, for visual/human consumption, but not as an implicit API contract?

if it's the former – if we're establishing a naming format that downstream code could parse to recover indices, then I would be a bit worried. I don't think name should be load bearing for things like grid position. That said, I don't mind just populating it to something better-than-nothing as a visual aid.

if you do need the index programmatically, and if the coordinateTransform added in #134 is insufficient (and indeed it doesn't store actual grid indices), then maybe we should discuss establishing a new field in the metadata somewhere?

@ieivanov

Copy link
Copy Markdown
Contributor Author

Sorry, I think I took the discussion off-course.

I think we should merge this PR - it fixes a bug in the writer, which is currently preventing us from acquiring data with the latest codebase, and adheres to the current naming standards.

I'll open a separate issue where we can discuss Position naming more broadly.

@tlambert03

Copy link
Copy Markdown
Member

ok, I'm happy to merge this, but I want to be clear on why:
if you had added a test that had asserted a specific name format (e.g. assert name == 'name_p0000_g0000') then I would have had more issue. I don't want anyone to be parsing position names to extract integers like position or grid index. On the other hand, if we have to use names like that to avoid uniqueness constraints, then it's fine.

In other words, if you ever find yourself parsing position names written by ome-writers to get at things like grid_index or position_number, then please open another issue to discuss, ok?

@tlambert03 tlambert03 merged commit bac0f39 into pymmcore-plus:main Apr 20, 2026
89 of 90 checks passed
@ieivanov

Copy link
Copy Markdown
Contributor Author

Thanks! Just opened pymmcore-plus/useq-schema#272. I absolutely agree with you that our code shouldn't bake in metadata in the position names.

I also think that if the user choses to assign a position name that carries information to them, we should allow for that. I think you'd agree with me on that. In pymmcore-plus/useq-schema#272 I explain how that's currently not the case.

I also think that if the user doesn't provide a position name and we need to pick one - for display in logs / UI or when writing data to disk, it helpful (not required) if we pick something meaningful. In pymmcore-plus/useq-schema#272 I explain how it's currently hard for us to monitor acquisition progress from logs as the position names don't carry through. It would be nice to get something like "Imaging position A1_0000" for example.

Both of these use cases are largely separate from our discussion in #134. I am with you that we shouldn't encourage users to parse position names.

@tlambert03

Copy link
Copy Markdown
Member

I also think that if the user choses to assign a position name that carries information to them, we should allow for that.

100%, that's the only thing I want names to be. I know we are, in places, assigning position names when they are not previously user-assigned. my main thing is that I want to resist the urge to go any deeper in that pattern (i.e. i want us to, in general, avoid doing anything with position names except for simply passing through whatever the user names them). I know we're not strictly doing that just yet, I'm just trying to make sure we don't go deeper in the wrong direction. And yes, I agree that, in the absence of a user name, a better-than-nothing string is preferred!

I'll have a look at the related useq-issues soon, thanks!

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.

zarr writer fails when combining grid_plan with well positions declared individually using plate_row/plate_col stuff

2 participants