Skip to content

Orientation: make RAW the single source of truth for image + mask (one Orientation helper) #109

Description

@pecomyint

Problem

Detector orientation (transpose + rotate) logic is duplicated across ~20 sites with no single "raw vs display" boundary. Each site re-derives the transpose/rot math on its own, which is fragile and has already caused bugs.

Concretely:

  • pva_reader.py:411 pre-transposes reader.image (.T when image_is_transposed), and the viewer transposes again at area_det_viewer.py:1795-1796 → a double-transpose (the live Transpose checkbox is nearly a display no-op in scan/alignment modes).
  • ROI coords are hand-swapped for transpose only, not rotation: area_det_viewer.py:1235-1238, :1535-1542.
  • RSM q-maps .T'd: :1759-1761. Mouse-pixel readout indexes ad-hoc: :1714-1717.
  • mask_viewer.py re-implements the transpose+rot transform and its inverse (_get_display_mask, _transform_data_for_display, _display_to_native, orientation buttons).
  • The mask is stored in the reader/display orientation, not raw — which made the EPICS NDPluginBadPixel JSON export write swapped [X,Y] (fixed surgically at export; the underlying inconsistency remains).

Target design

Single source of truth = raw for both image and mask; one Orientation value object maps raw↔display; every display/export path derives from raw through it.

@dataclass(frozen=True)
class Orientation:
    transposed: bool = False
    rot_k: int = 0                      # CCW 90° steps; convention: transpose THEN rotate
    def apply(self, arr): ...           # raw -> display
    def invert(self, arr): ...          # display -> raw
    def display_index_to_raw(self, i, j, raw_shape): ...   # clicks / ROIs
    def raw_index_to_display(self, r, c, raw_shape): ...

Rules of the road:

  • Reader emits raw only (drop the .T at pva_reader.py:411).
  • Viewer holds one Orientation; Transpose/Rotate controls mutate only it.
  • Display derives: orientation.apply(raw). Masking = apply raw mask to raw image, then apply once.
  • ROIs, q-maps, mouse readout all go through orientation (also fixes ROI-under-rotation, currently unhandled).
  • Mask always raw in mask_manager (detection from raw frames; import/export raw; overlay via orientation.apply).
  • mask_viewer consumes the same Orientation; delete its duplicate transform code.

Phased rollout

  • Phase 1 — add orientation.py (+ unit tests); make mask_manager.mask always raw (dead-pixel detection from the raw frame; overlay via orientation.apply). Low blast radius; no RSM/HKL-3D impact.
  • Phase 2 — reader → raw; route viewer display, ROIs, q-maps, mouse readout, and mask_viewer through the one Orientation; delete the double-transpose + duplicated code. Needs live RSM/HKL-3D + ROI verification.

Context

Follow-up to the surgical bad-pixel JSON export fix (un-transpose the mask at export so the file is raw [X,Y]=[col,row]). This issue tracks the proper architectural fix so orientation is centralized and raw is authoritative.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions