Skip to content

Instrument View: Fix picking when detector shapes are drawn#41127

Open
GuiMacielPereira wants to merge 7 commits intomainfrom
fix-det-shape-picking
Open

Instrument View: Fix picking when detector shapes are drawn#41127
GuiMacielPereira wants to merge 7 commits intomainfrom
fix-det-shape-picking

Conversation

@GuiMacielPereira
Copy link
Copy Markdown
Contributor

Description of work

Drawing the detector shapes causes the projection in 2d to often overlap shapes, leading to inconsistent picking. This PR uses a trick to assign each detector shape in 2d a small height in Z, with the detectors closer to the centre of the mesh having the lowest Z height and the detectors away from the centre have increasing Z height.

The idea is to make tiles overlap like a roof, so that picking will always choose the tile that is in front of another.

I also did some general cleaning of the tiling implementation during assembling the mesh.

Closes #xxxx.

To test:

  • Draw shapes in 3d, projections and side-by-side
  • Pick detectors in different zones and for different projections
  • Check that the highlighted detectors are the ones picked.

Reviewer

Your comments will be used as part of the gatekeeper process. Comment clearly on what you have checked and tested during your review. Provide an audit trail for any changes requested.

As per the review guidelines:

  • Is the code of an acceptable quality? (Code standards/GUI standards)
  • Has a thorough functional test been performed? Do the changes handle unexpected input/situations?
  • Are appropriately scoped unit and/or system tests provided?
  • Do the release notes conform to the guidelines and describe the changes appropriately?
  • Has the relevant (user and developer) documentation been added/updated?
  • If the PR author isn’t in the mantid-developers or mantid-contributors teams, add a review comment rerun ci to authorize/rerun the CI

Gatekeeper

As per the gatekeeping guidelines:

  • Has a thorough first line review been conducted, including functional testing?
  • At a high-level, is the code quality sufficient?
  • Are the base, milestone and labels correct?

@GuiMacielPereira GuiMacielPereira added the Epic Used for issues and PRs that are managed under the ISIS Epic Workstream label Mar 25, 2026
@github-actions github-actions bot added this to the Release 6.16 milestone Mar 25, 2026
@GuiMacielPereira
Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

✅ Actions performed

Full review triggered.

@GuiMacielPereira GuiMacielPereira marked this pull request as ready for review March 25, 2026 12:50
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Refactor
    • Restructured the instrument view component's initialization process and workspace data handling to improve code efficiency and maintainability. Enhanced the visualization system's lifecycle management and renderer initialization patterns to provide better performance characteristics and cleaner internal architecture while maintaining all existing user-facing functionality and behavior.

Walkthrough

Renderer initialization in the instrument view was refactored from lazy/cached construction to eager instantiation. ShapeRenderer and SideBySideShapeRenderer now accept workspace at construction rather than via precompute(). FullInstrumentViewPresenter builds renderers immediately instead of deferring creation through factory methods. Cached renderer factory methods were removed and replaced with _reload_renderers() that reconstructs renderers with updated workspace data. The precompute() method signature changed to accept no parameters. Tests were updated to align with the new initialization pattern.

Possibly related PRs

  • #40397: Modifies renderer construction signatures and projection-aware behavior in ShapeRenderer and FullInstrumentViewPresenter.
  • #40850: Updates ShapeRenderer, SideBySideShapeRenderer, and FullInstrumentViewPresenter with related renderer management logic.
  • #40687: Changes workspace replacement and renderer reload sequencing in FullInstrumentViewPresenter.

Suggested labels

ISIS: Core

Suggested reviewers

  • jclarkeSTFC
  • RichardWaiteSTFC
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: resolving picking issues when detector shapes are drawn by addressing overlapping shapes in 2D projections.
Description check ✅ Passed The description is directly related to the changeset, explaining the picking problem, the Z-offset solution, mesh assembly cleaning, and providing testing instructions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-det-shape-picking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@qt/python/instrumentview/instrumentview/FullInstrumentViewPresenter.py`:
- Around line 81-82: Recreate the SideBySideShapeRenderer after the model's
projection/ workspace state is synchronized so it captures current
bank_groups_by_detector_id instead of the empty initial value: replace
persistent construction sites that set self._sbs_shape_renderer =
SideBySideShapeRenderer(...) (e.g. in FullInstrumentViewPresenter where
_shape_renderer and _sbs_shape_renderer are built) with lazy re-creation logic
and call that re-creation from the projection-sync / workspace-replacement path
(the code that runs after FullInstrumentViewModel.setup() and in the same place
_on_show_shapes_toggled() reads the renderer) so that SideBySideShapeRenderer is
re-instantiated with the up-to-date self._model.bank_groups_by_detector_id
whenever the active projection or workspace is updated.

In `@qt/python/instrumentview/instrumentview/renderers/shape_renderer.py`:
- Around line 391-398: In the SIDE_BY_SIDE branch the group_scales are taken
from per_detector_scales and thus omit each detector's native scale
(ComponentInfo.scaleFactor from self._det_scales), causing incorrect vertex
sizes and overlap math; modify the SIDE_BY_SIDE branch so group_scales combines
the per-detector component scales with the per-detector projection scales (the
same way non-side-by-side uses scales[det_indices[group_indices]]), i.e. use
self._det_scales (or the appropriate per-detector scale source)
multiplied/applied to per_detector_scales when building group_scales for
ProjectionType.SIDE_BY_SIDE so _compute_bank_projection_scales and rendered
vertices get the detector native scale. Ensure rotate_mask, group_pos
(display_positions), and indexing by det_indices/group_indices remain
consistent.

In
`@qt/python/instrumentview/instrumentview/renderers/side_by_side_shape_renderer.py`:
- Around line 140-142: When bank_local_groups is empty, do NOT set
per_det_scale[:] = 1.0; instead implement a uniform nearest-neighbour fallback
so overlap-avoidance remains active: for each detector index in per_det_scale,
find the nearest detector that has a valid computed scale (using detector
positions / bank membership available in the renderer) and copy that scale into
per_det_scale; leave per_det_rotate unchanged. Update the logic in
side_by_side_shape_renderer (around the bank_local_groups check referencing
per_det_scale and per_det_rotate) so that if no local groups exist you populate
per_det_scale from nearest-valid neighbours and only if no neighbours exist use
a safe default, rather than unconditionally assigning 1.0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 995dc9b1-d862-4b8d-9bc5-3be3a35a1c3b

📥 Commits

Reviewing files that changed from the base of the PR and between 25090ed and 3e79eda.

📒 Files selected for processing (6)
  • qt/python/instrumentview/instrumentview/FullInstrumentViewPresenter.py
  • qt/python/instrumentview/instrumentview/renderers/shape_renderer.py
  • qt/python/instrumentview/instrumentview/renderers/side_by_side_shape_renderer.py
  • qt/python/instrumentview/instrumentview/renderers/test/test_renderers.py
  • qt/python/instrumentview/instrumentview/renderers/test/test_side_by_side_shape_renderer.py
  • qt/python/instrumentview/instrumentview/test/test_presenter.py

Comment on lines 140 to 142
if not bank_local_groups:
uniform = self._compute_projection_scale(det_indices, projected_positions)
per_det_scale[:] = uniform
per_det_scale[:] = 1.0
return per_det_scale, per_det_rotate
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 25, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't fall back to unit-size tiles when bank grouping is unavailable.

Returning 1.0 here bypasses the overlap-avoidance logic and restores full-size detector shapes whenever bank groups are missing, stale, or incomplete. That path is reachable from the new eager presenter construction, so side-by-side picking can still regress to the old ambiguous overlap behaviour unless this keeps a uniform nearest-neighbour fallback instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@qt/python/instrumentview/instrumentview/renderers/side_by_side_shape_renderer.py`
around lines 140 - 142, When bank_local_groups is empty, do NOT set
per_det_scale[:] = 1.0; instead implement a uniform nearest-neighbour fallback
so overlap-avoidance remains active: for each detector index in per_det_scale,
find the nearest detector that has a valid computed scale (using detector
positions / bank membership available in the renderer) and copy that scale into
per_det_scale; leave per_det_rotate unchanged. Update the logic in
side_by_side_shape_renderer (around the bank_local_groups check referencing
per_det_scale and per_det_rotate) so that if no local groups exist you populate
per_det_scale from nearest-valid neighbours and only if no neighbours exist use
a safe default, rather than unconditionally assigning 1.0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jclarkeSTFC I removed the function to calculate the nearest-neighbour scaling, mostly because I thought that scaling only affects the size of the detector shapes, so it shouldn't be that important? Do you know more about these cases where banks are missing and bank_local_groups is an empty list?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think that it shouldn't be possible for bank_local_groups to be empty, because in the side-by-side projection, any detectors not in a grid in the IDF are put into a custom flat bank that arranges them in a grid, i.e. if there are some detectors then there will always be at least one bank.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

Unit test results

2 863 tests   2 863 ✅  12h 28m 27s ⏱️
    1 suites      0 💤
    1 files        0 ❌

Results for commit db160d9.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

System test results

808 tests   792 ✅  2h 47m 58s ⏱️
 11 suites   16 💤
 11 files      0 ❌

Results for commit db160d9.

♻️ This comment has been updated with latest results.

Took out bank_groups_by_detector_id out of initializer because it should
be called every time that build_mesh is called, otherwise risk freezing
it for one projection and it's wrong if the projection gets updated.

Also reduced the dependency of renderes on model
Easier to call and to use, better encapsulation of the different
projections now all under the same class Projection
Copy link
Copy Markdown
Contributor

@jclarkeSTFC jclarkeSTFC left a comment

Choose a reason for hiding this comment

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

This looks good, the picked detectors are highlighted in a better way and the code is simpler, was working fine for me.

root_position: np.ndarray,
detector_positions: list[DetectorPosition] | np.ndarray,
axis: np.ndarray,
**kwargs,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Did you leave these comments here deliberately, to see what kwargs is supposed to have?

Comment on lines 327 to +329
workspace = self._create_mock_workspace(n_detectors=1)
self.renderer.precompute(workspace)
self.renderer = ShapeRenderer(workspace)
self.renderer.precompute()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think these three lines can go into a method that takes n_detectors as an argument, they appear quite a few times.

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

Labels

Epic Used for issues and PRs that are managed under the ISIS Epic Workstream

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants