Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c748cd4
Added `ctis.inverters.MartInverter`, an implementation of the Multipl…
roytsmart Apr 14, 2026
ce54305
added tutorial
roytsmart Apr 14, 2026
56f2003
added merit to results
roytsmart Apr 14, 2026
f975000
Updates to the tutorial
roytsmart Apr 14, 2026
f44513d
references
roytsmart Apr 14, 2026
995a310
adding description to the tutorial
roytsmart Apr 14, 2026
4e1ca50
More updates to tutorial
roytsmart Apr 14, 2026
a05f43c
More updates to tutorial
roytsmart Apr 14, 2026
be8cb5c
black
roytsmart Apr 15, 2026
ff852a5
ruff
roytsmart Apr 15, 2026
cfd4ede
default guess
roytsmart Apr 15, 2026
0995514
added testing infrastructure
roytsmart Apr 15, 2026
36caf1f
tutorial
roytsmart Apr 15, 2026
8c2061e
black
roytsmart Apr 15, 2026
3084026
testing
roytsmart Apr 15, 2026
c625921
black
roytsmart Apr 15, 2026
147efef
Added beginnings of mart discussion
roytsmart Apr 24, 2026
d56c0a7
update discussion.
roytsmart Apr 24, 2026
e91d1da
more tweaks to the discussion.
roytsmart Apr 24, 2026
ca10157
refs
roytsmart Apr 24, 2026
453859a
even more changes to discussion
roytsmart Apr 24, 2026
de50aef
compute correlation of residual with predicted images as a function o…
roytsmart Apr 25, 2026
0820fc5
black
roytsmart Apr 25, 2026
0138068
ruff
roytsmart Apr 25, 2026
c6b9425
tests
roytsmart Apr 25, 2026
63934f8
bump named-arrays version
roytsmart Apr 25, 2026
04982c7
coverage
roytsmart Apr 25, 2026
030472d
sphinx link
roytsmart Apr 25, 2026
6bb5ac4
use Pearson's r for now
roytsmart Apr 25, 2026
44741c7
coverage
roytsmart Apr 25, 2026
ead610e
black
roytsmart Apr 25, 2026
658a931
doc tweaks
roytsmart Apr 25, 2026
6a5e048
docs
roytsmart Apr 25, 2026
a84170f
docs
roytsmart Apr 25, 2026
0f570fa
added an iris tutorial
roytsmart Apr 25, 2026
e71aa25
add iris to deps
roytsmart Apr 25, 2026
b59ae2f
try with smaller obs
roytsmart Apr 25, 2026
7c9a7ad
try to use less memory
roytsmart Apr 26, 2026
a78c9a1
lots of fixes
roytsmart May 22, 2026
dfaae02
fix tutorial
roytsmart May 25, 2026
412b689
fix tests
roytsmart May 25, 2026
7d5c99b
black
roytsmart May 25, 2026
fbb7576
docs
roytsmart May 25, 2026
97341af
nbstripout
roytsmart May 25, 2026
3bb69ca
tests
roytsmart May 25, 2026
24b0687
docs
roytsmart May 25, 2026
6d261b9
docs
roytsmart May 25, 2026
597a475
tests
roytsmart May 25, 2026
155ab57
coverage
roytsmart May 25, 2026
4419a50
black
roytsmart May 25, 2026
495d69c
memory
roytsmart May 25, 2026
fe8b82f
nbstripout
roytsmart May 25, 2026
13bcd97
fixed mean-chi squared calculation
roytsmart May 26, 2026
0ca6546
tweak notebook
roytsmart May 26, 2026
cf1b449
ruff
roytsmart May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
pip install setuptools wheel
pip install -e .[test]
- name: Test with pytest
env:
MPLBACKEND: "agg"
run: |
pip install pytest pytest-cov
pytest --cov=. --cov-report=xml
Expand Down
2 changes: 2 additions & 0 deletions ctis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

from . import scenes
from . import instruments
from . import inverters

__all__ = [
"scenes",
"instruments",
"inverters",
]
30 changes: 19 additions & 11 deletions ctis/instruments/_instruments.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,19 @@ def uncertainty(self) -> Callable[[na.ScalarArray], na.ScalarArray]:
for a given number of photons.
"""

@property
@abc.abstractmethod
def channel(self):
"""
Human-readable name of each independent CTIS channel.
"""

@property
@abc.abstractmethod
def axis_channel(self) -> str | tuple[str, ...]:
"""
The logical axis or axes of this instrument corresponding to
the different dispersion magnitudes and angles.
the different CTIS channels.
"""

@property
Expand Down Expand Up @@ -391,6 +398,11 @@ class IdealInstrument(
A grid of wavelength and position coordinates on the sensor plane.
"""

channel: str | na.AbstractScalar = dataclasses.MISSING
"""
Human-readable name of each independent CTIS channel.
"""

axis_channel: str | tuple[str, ...] = dataclasses.MISSING
"""
The logical axis or axes of this instrument corresponding to
Expand Down Expand Up @@ -492,10 +504,6 @@ def _coordinates_output(self) -> na.AbstractSpectralPositionalVectorArray:

coordinates_output = coordinates_output.cell_centers(self.axis_wavelength)

p = coordinates_output.position
coordinates_output.position.x = na.random.normal(p.x, 1e-3 * u.pix)
coordinates_output.position.y = na.random.normal(p.y, 1e-3 * u.pix)

return coordinates_output

@functools.cached_property
Expand All @@ -518,12 +526,12 @@ def weights_transpose(self):
coordinates_input = self._coordinates_input
coordinates_output = self._coordinates_output

return na.regridding.weights(
coordinates_input=coordinates_output.position,
coordinates_output=coordinates_input.position,
axis_input=self.axis_sensor_xy,
axis_output=self.axis_scene_xy,
method="conservative",
return na.regridding.transpose_weights_conservative(
weights=self.weights,
coordinates_input=coordinates_input.position,
coordinates_output=coordinates_output.position,
axis_input=self.axis_scene_xy,
axis_output=self.axis_sensor_xy,
)

def image(
Expand Down
24 changes: 16 additions & 8 deletions ctis/instruments/_instruments_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

velocity = na.linspace(-500, 500, axis="wavelength", num=21) * u.km / u.s

wavelength_rest = 171 * u.AA

position_scene = na.Cartesian2dVectorLinearSpace(
start=-20 * u.arcsec,
stop=20 * u.arcsec,
Expand All @@ -19,15 +21,18 @@
y=na.arange(0, 64, axis="sensor_y") * u.pix,
)

coordinates_scene = na.SpectralPositionalVectorArray(velocity, position_scene)
coordinates_sensor = na.SpectralPositionalVectorArray(velocity, position_sensor)

gaussians = ctis.scenes.gaussians(
inputs=coordinates_scene,
width=na.SpectralPositionalVectorArray(30 * u.km / u.s, 1 * u.arcsec),
coordinates_scene = na.DopplerPositionalVectorArray.from_velocity(
velocity=velocity,
wavelength_rest=wavelength_rest,
position=position_scene,
)
coordinates_sensor = na.DopplerPositionalVectorArray.from_velocity(
velocity=velocity,
wavelength_rest=wavelength_rest,
position=position_sensor,
)

wavelength_rest = 171 * u.AA
gaussians = ctis.scenes.gaussians(coordinates_scene)

AA = dict(
unit=u.AA,
Expand All @@ -40,16 +45,19 @@
dispersion = 200 * u.km / u.s
dispersion = (dispersion.to(**AA) - wavelength_rest) / u.pix

angle = na.linspace(0, 360, axis="channel", num=3, endpoint=False)

instrument_ideal = ctis.instruments.IdealInstrument(
area_effective=1 * u.cm**2,
timedelta_exposure=10 * u.s,
plate_scale=2 * u.arcsec / u.pix,
dispersion=dispersion,
angle=na.linspace(0, 360, axis="channel", num=3, endpoint=False),
angle=angle,
wavelength_ref=wavelength_rest,
position_ref=32 * u.pix,
coordinates_scene=coordinates_scene,
coordinates_sensor=coordinates_sensor,
channel=angle,
axis_channel="channel",
axis_wavelength="wavelength",
axis_scene_xy=("scene_x", "scene_y"),
Expand Down
20 changes: 20 additions & 0 deletions ctis/inverters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Inversion algorithms which can reconstruct scenes from observed images."""

from . import merit
from ._results import AbstractInversionResult, InversionResult
from ._inverters import AbstractInverter
from ._iterative import (
AbstractIterativeInverter,
MartInverter,
IterativeInversionResult,
)

__all__ = [
"merit",
"AbstractInverter",
"AbstractIterativeInverter",
"MartInverter",
"AbstractInversionResult",
"InversionResult",
"IterativeInversionResult",
]
48 changes: 48 additions & 0 deletions ctis/inverters/_inverters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import abc
import dataclasses
import named_arrays as na
import ctis
from ._results import InversionResult

__all__ = [
"AbstractInverter",
]


@dataclasses.dataclass
class AbstractInverter(
abc.ABC,
):
"""
An interface describing an algorithm which can invert CTIS observations
to yield a reconstruction of the observed scene.
"""

@property
@abc.abstractmethod
def instrument(self) -> ctis.instruments.AbstractInstrument:
"""
A model of a CTIS instrument which transforms the radiance of an observed
scene to photons measured by the sensors.
"""

@abc.abstractmethod
def __call__(
self,
images: na.FunctionArray[na.SpectralPositionalVectorArray, na.ScalarArray],
**kwargs,
) -> InversionResult:
"""
Reconstruct a scene using the observed images.

Parameters
----------
images
The observed images used to calculate the reconstruction.
Must be evaluated on the same coordinates as
:attr:`~ctis.instruments.AbstractInstrument.coordinates_sensor`
attribute of :attr:`instrument`.
kwargs
Additional keyword arguments which can be used by subclass
implementations.
"""
31 changes: 31 additions & 0 deletions ctis/inverters/_inverters_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import abc
import numpy as np
import named_arrays as na
import ctis


class AbstractTestAbstractInverter(
abc.ABC,
):

def test_instrument(self, a: ctis.inverters.AbstractInverter):
result = a.instrument
assert isinstance(result, ctis.instruments.AbstractInstrument)

def test__call__(
self,
a: ctis.inverters.AbstractInverter,
images: na.FunctionArray[na.SpectralPositionalVectorArray, na.ScalarArray],
**kwargs,
) -> ctis.inverters.AbstractInversionResult:
result = a(images, **kwargs)

assert isinstance(result, ctis.inverters.AbstractInversionResult)

assert result.solution.sum() > 0
assert isinstance(result.success, bool)
assert isinstance(result.message, str)
assert np.all(result.images == images)
assert result.inverter == a

return result
8 changes: 8 additions & 0 deletions ctis/inverters/_iterative/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from ._iterative import AbstractIterativeInverter, IterativeInversionResult
from ._mart import MartInverter

__all__ = [
"AbstractIterativeInverter",
"IterativeInversionResult",
"MartInverter",
]
Loading
Loading