Skip to content

Electron-based instrument interface and OptikaInstrument - #18

Draft
roytsmart wants to merge 7 commits into
mainfrom
feature/electron-based-instruments
Draft

Electron-based instrument interface and OptikaInstrument#18
roytsmart wants to merge 7 commits into
mainfrom
feature/electron-based-instruments

Conversation

@roytsmart

@roytsmart roytsmart commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Draft: depends on unreleased optika changes on sun-data/optika #152 (the merged sensor PRs #191/#192/#193 are folded into it). Not mergeable until #152 lands in an optika release.

Makes the CTIS instrument interface electron-based and adds a real forward/inverse model backed by optika.

Interface

  • image() returns the electrons measured by the sensor; backproject() accepts electrons.
  • The per-instrument uncertainty callable is gone, replaced by an image(..., uncertainty=True) flag (see below).

IdealInstrument

  • Gains a quantum_yield parameter (electron/photon) and converts photons↔electrons around its regridding model.
  • Gains a read_noise parameter, applied once per readout after the wavelength integration.

OptikaInstrument (new)

  • Wraps an optika.systems.LinearSystem and delegates the forward/transpose — including the wavelength integration and read noise — to system.image_from_weights / backproject_from_weights, reusing the cached regridding weights/transpose so the operators aren't rebuilt on every call (important for the iterative MART solve).
  • Its sensor coordinates are in pixel units, matching the IdealInstrument (u.pix) convention.

Uncertainty

  • image(..., uncertainty=True) attaches the measurement-noise standard deviation as a NormalUncertainScalarArray.
  • Shot/Fano noise is computed per wavelength (exact, and correct for large EUV quantum yields) and summed in quadrature during the wavelength integration; read noise is applied once at the integrated readout — fixing a √N_wavelength over-count.
  • MartInverter weights its χ² with the attached width.

Tests

  • A shared AbstractTestAbstractInstrument suite (image, backproject, flux conservation, uncertainty, read-noise-once) runs for both IdealInstrument and OptikaInstrument.

All ctis tests pass locally (instruments + inverters).

🤖 Generated with Claude Code

@roytsmart roytsmart changed the title Electron-based instrument interface and LinearOptikaInstrument Electron-based instrument interface and OptikaInstrument Jul 24, 2026
@roytsmart
roytsmart force-pushed the feature/electron-based-instruments branch from cb78062 to dc1097c Compare July 25, 2026 20:23
roytsmart and others added 5 commits July 25, 2026 20:34
Make the CTIS instrument interface electron-based: `image()` returns the
electrons measured by the sensor and `backproject()` accepts electrons.

`IdealInstrument` gains a `quantum_yield` parameter and converts
photons<->electrons around its regridding model. `LinearOptikaInstrument`
wraps an `optika.systems.LinearSystem` and delegates to its
`image_from_weights`/`backproject_from_weights`, reusing the cached
regridding weights so the forward/transpose model (including the sensor's
`expose`/`photons_absorbed`) is not rebuilt each call.

`image()` gains an `uncertainty` flag: when set, it attaches the standard
deviation of the measurement noise as a `NormalUncertainScalarArray`. The
variance is computed per wavelength and summed in quadrature during the
wavelength integration, so it is exact even for the integrated image. The
MART inverter uses this to weight its chi-squared, replacing the old
per-instrument `uncertainty` callable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename `LinearOptikaInstrument` to `OptikaInstrument` ("Linear" is already
implied by `AbstractLinearInstrument`; the distinguishing trait is that it
is backed by an `optika` system).

Generalize `AbstractTestAbstractInstrument` so the shared tests derive the
scene and coordinates from the instrument under test, and add
`TestOptikaInstrument` with a channel-aware `LinearSystem` fixture whose
scene grid spans the field of view. A new `test_backproject_conserves_flux`
asserts that re-imaging a backprojection preserves the total measured
electrons (the forward model's adjoint property) for every instrument.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delegate the wavelength integration to optika: `OptikaInstrument` now
forwards `integrate` to `system.image_from_weights`/`backproject_from_weights`
(which sum over wavelength and apply the sensor's read noise once), instead
of integrating in ctis. This fixes the sqrt(N_wavelength) over-count that
resulted from the sensor applying read noise per wavelength.

`IdealInstrument` gains a `read_noise` field, applied once (Gaussian on the
nominal, in quadrature on the uncertainty) after its own wavelength
integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a shared `test_read_noise` (with a per-instrument `_with_read_noise`
hook) verifying that a nonzero read noise raises the integrated uncertainty
by exactly `read_noise` in quadrature, guarding against the
sqrt(N_wavelength) over-count.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`LinearSystem.coordinates_sensor` is now in pixel units, so the test
fixture's distortion is expressed in arcsec/pix and nm/pix to match.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@roytsmart
roytsmart force-pushed the feature/electron-based-instruments branch from dc1097c to a99bd94 Compare July 26, 2026 02:34
roytsmart and others added 2 commits July 27, 2026 14:50
The forward model accepts a scene in photon or energy units, so let
`backproject` return the spectral radiance in whichever the caller requests,
so an inversion can be expressed in the same units as its input scene:

    instrument.backproject(image, unit=scene.outputs.unit)

`OptikaInstrument` forwards `unit` to optika's `backproject`, where the
wavelength and sensor physics live. `IdealInstrument`, a standalone analytic
model with no optika system, converts locally: its backprojection is in energy
units and is divided by the energy per photon when a photon unit is requested.
The default (`unit=None`) preserves the previous behavior of each instrument.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
Replace the hand-written `wavelength.volume_cell(...) * position.volume_cell(...)`
(with the manual cell-center alignment) in `AbstractLinearInstrument._volume_scene`
with `coordinates.volume_cell(...)` from named-arrays 2.3. The scene coordinates
are coerced to a spectral-positional vector first so that `IdealInstrument`'s
Doppler scene also gets the method. Bump the named-arrays pin to ~=2.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtdKmedevWkDab6BWupXqQ
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.

1 participant