Skip to content

Commit c299a24

Browse files
authored
Modified SensorData and TapData to be instances of named_arrays.FunctionArray. (#8)
1 parent 7a106b6 commit c299a24

File tree

12 files changed

+344
-381
lines changed

12 files changed

+344
-381
lines changed

msfc_ccd/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"abc",
77
"samples",
88
"TeledyneCCD230",
9+
"ImageHeader",
910
"SensorData",
1011
"TapData",
1112
"fits",
@@ -14,5 +15,5 @@
1415
from . import abc
1516
from . import samples
1617
from ._sensors import TeledyneCCD230
17-
from ._images import SensorData, TapData
18+
from ._images import ImageHeader, SensorData, TapData
1819
from . import fits

msfc_ccd/_images/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
"""
44

55
__all__ = [
6+
"ImageHeader",
67
"SensorData",
78
"TapData",
89
]
910

11+
from ._vectors import ImageHeader
1012
from ._sensor_images import SensorData
1113
from ._tap_images import TapData

msfc_ccd/_images/_images.py

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import abc
22
import dataclasses
3-
import astropy.units as u
4-
import astropy.time
53
import named_arrays as na
6-
import optika
74
from .._sensors import AbstractSensor
5+
from ._vectors import ImageHeader
86

97
__all__ = []
108

119

1210
@dataclasses.dataclass(eq=False, repr=False)
1311
class AbstractImageData(
14-
optika.mixins.Printable,
12+
na.FunctionArray[
13+
ImageHeader,
14+
na.AbstractScalarArray,
15+
],
1516
):
1617
"""
1718
An interface for image-like data.
@@ -20,18 +21,6 @@ class AbstractImageData(
2021
sensor and for data from only a single tap on the sensor.
2122
"""
2223

23-
@property
24-
@abc.abstractmethod
25-
def data(self) -> na.AbstractScalar:
26-
"""The underlying array storing the image data."""
27-
28-
@property
29-
@abc.abstractmethod
30-
def pixel(self) -> dict[str, na.AbstractScalarArray]:
31-
"""
32-
The 2-dimensional index of each pixel in the image.
33-
"""
34-
3524
@property
3625
@abc.abstractmethod
3726
def axis_x(self) -> str:
@@ -53,88 +42,18 @@ def num_x(self) -> int:
5342
"""
5443
The number of pixels along the x-axis.
5544
"""
56-
return self.data.shape[self.axis_x]
45+
return self.outputs.shape[self.axis_x]
5746

5847
@property
5948
def num_y(self) -> int:
6049
"""
6150
The number of pixels along the y-axis.
6251
"""
63-
return self.data.shape[self.axis_y]
64-
65-
@property
66-
@abc.abstractmethod
67-
def time(self) -> astropy.time.Time | na.AbstractScalar:
68-
"""The time in UTC at the midpoint of the exposure."""
69-
70-
@property
71-
@abc.abstractmethod
72-
def timedelta(self) -> u.Quantity | na.AbstractScalar:
73-
"""The measured exposure time of each image."""
74-
75-
@property
76-
@abc.abstractmethod
77-
def timedelta_requested(self) -> u.Quantity | na.AbstractScalar:
78-
"""The requested exposure time of each image."""
52+
return self.outputs.shape[self.axis_y]
7953

8054
@property
8155
@abc.abstractmethod
8256
def sensor(self) -> AbstractSensor:
8357
"""
8458
A model of the sensor used to capture these images.
8559
"""
86-
87-
@property
88-
@abc.abstractmethod
89-
def serial_number(self) -> str | na.AbstractScalar:
90-
"""The serial number of the camera that captured each image."""
91-
92-
@property
93-
@abc.abstractmethod
94-
def run_mode(self) -> str | na.AbstractScalar:
95-
"""The Run Mode of the camera when each image was captured."""
96-
97-
@property
98-
@abc.abstractmethod
99-
def status(self) -> str | na.AbstractScalar:
100-
"""The status of the camera while each image was being captured."""
101-
102-
@property
103-
@abc.abstractmethod
104-
def voltage_fpga_vccint(self) -> u.Quantity | na.AbstractScalar:
105-
"""The VCCINT voltage of the FPGA when each image was captured."""
106-
107-
@property
108-
@abc.abstractmethod
109-
def voltage_fpga_vccaux(self) -> u.Quantity | na.AbstractScalar:
110-
"""The VCCAUX voltage of the FPGA when each image was captured."""
111-
112-
@property
113-
@abc.abstractmethod
114-
def voltage_fpga_vccbram(self) -> u.Quantity | na.AbstractScalar:
115-
"""The VCCBRAM voltage of the FPGA when each image was captured."""
116-
117-
@property
118-
@abc.abstractmethod
119-
def temperature_fpga(self) -> u.Quantity | na.AbstractScalar:
120-
"""The temperature of the FPGA when each image was captured."""
121-
122-
@property
123-
@abc.abstractmethod
124-
def temperature_adc_1(self) -> u.Quantity | na.AbstractScalar:
125-
"""Temperature 1 of the ADC when each image was captured."""
126-
127-
@property
128-
@abc.abstractmethod
129-
def temperature_adc_2(self) -> u.Quantity | na.AbstractScalar:
130-
"""Temperature 2 of the ADC when each image was captured."""
131-
132-
@property
133-
@abc.abstractmethod
134-
def temperature_adc_3(self) -> u.Quantity | na.AbstractScalar:
135-
"""Temperature 3 of the ADC when each image was captured."""
136-
137-
@property
138-
@abc.abstractmethod
139-
def temperature_adc_4(self) -> u.Quantity | na.AbstractScalar:
140-
"""Temperature 4 of the ADC when each image was captured."""

msfc_ccd/_images/_sensor_images.py

Lines changed: 60 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import named_arrays as na
1010
import msfc_ccd
1111
from .._sensors import AbstractSensor
12+
from ._vectors import ImageHeader
1213
from ._images import AbstractImageData
1314

1415
__all__ = [
@@ -24,17 +25,6 @@ class AbstractSensorData(
2425
An interface for representing data captured by an entire image sensor.
2526
"""
2627

27-
@property
28-
def pixel(self) -> dict[str, na.AbstractScalarArray]:
29-
axis_x = self.axis_x
30-
axis_y = self.axis_y
31-
shape = self.data.shape
32-
shape_img = {
33-
axis_x: shape[axis_x],
34-
axis_y: shape[axis_y],
35-
}
36-
return na.indices(shape_img)
37-
3828
def taps(
3929
self,
4030
axis_tap_x: str = "tap_x",
@@ -61,57 +51,37 @@ def taps(
6151
num_tap_x = self.sensor.num_tap_x
6252
num_tap_y = self.sensor.num_tap_y
6353

64-
shape_img = {axis_x: num_x, axis_y: num_y}
65-
6654
num_x_new = num_x // num_tap_x
6755
num_y_new = num_y // num_tap_y
6856

69-
slice_left_x = slice(None, num_x_new)
70-
slice_left_y = slice(None, num_y_new)
57+
range_left_x = na.arange(0, num_x_new, axis=axis_x)
58+
range_left_y = na.arange(0, num_y_new, axis=axis_y)
7159

72-
slice_right_x = slice(None, num_x_new - 1, -1)
73-
slice_right_y = slice(None, num_y_new - 1, -1)
60+
range_right_x = na.arange(num_x - 1, num_x_new - 1, axis=axis_x, step=-1)
61+
range_right_y = na.arange(num_y - 1, num_y_new - 1, axis=axis_y, step=-1)
7462

75-
slices_x = [slice_left_x, slice_right_x]
76-
slices_y = [slice_left_y, slice_right_y]
63+
ranges_x = [range_left_x, range_right_x]
64+
ranges_y = [range_left_y, range_right_y]
7765

78-
pixel = self.pixel
66+
indices_x = na.stack(ranges_x, axis=axis_tap_x)
67+
indices_y = na.stack(ranges_y, axis=axis_tap_y)
7968

80-
for ax in pixel:
81-
p = pixel[ax].broadcast_to(shape_img)
82-
pixel[ax] = na.stack(
83-
arrays=[
84-
na.stack(
85-
arrays=[p[{axis_x: sx, axis_y: sy}] for sy in slices_y],
86-
axis=axis_tap_y,
87-
)
88-
for sx in slices_x
89-
],
90-
axis=axis_tap_x,
91-
)
69+
indices = {
70+
axis_x: indices_x,
71+
axis_y: indices_y,
72+
}
9273

9374
return msfc_ccd.TapData(
94-
data=self.data[pixel],
95-
pixel=pixel,
75+
inputs=dataclasses.replace(
76+
self.inputs,
77+
pixel=self.inputs.pixel[indices],
78+
),
79+
outputs=self.outputs[indices],
9680
axis_x=self.axis_x,
9781
axis_y=self.axis_y,
9882
axis_tap_x=axis_tap_x,
9983
axis_tap_y=axis_tap_y,
100-
time=self.time,
101-
timedelta=self.timedelta,
102-
timedelta_requested=self.timedelta_requested,
10384
sensor=self.sensor,
104-
serial_number=self.serial_number,
105-
run_mode=self.run_mode,
106-
status=self.status,
107-
voltage_fpga_vccint=self.voltage_fpga_vccint,
108-
voltage_fpga_vccaux=self.voltage_fpga_vccaux,
109-
voltage_fpga_vccbram=self.voltage_fpga_vccbram,
110-
temperature_fpga=self.temperature_fpga,
111-
temperature_adc_1=self.temperature_adc_1,
112-
temperature_adc_2=self.temperature_adc_2,
113-
temperature_adc_3=self.temperature_adc_3,
114-
temperature_adc_4=self.temperature_adc_4,
11585
)
11686

11787

@@ -151,15 +121,22 @@ class SensorData(
151121
constrained_layout=True,
152122
)
153123
im = na.plt.imshow(
154-
image.data,
124+
image.outputs,
155125
axis_x=axis_x,
156126
axis_y=axis_y,
157127
ax=ax,
158128
);
159129
"""
160130

161-
data: na.AbstractScalar = dataclasses.MISSING
162-
"""The underlying array storing the image data."""
131+
inputs: ImageHeader = dataclasses.MISSING
132+
"""
133+
A vector which contains the time and index of each pixel in the set of images.
134+
"""
135+
136+
outputs: na.ScalarArray = dataclasses.MISSING
137+
"""
138+
The underlying array storing the image data
139+
"""
163140

164141
axis_x: str = dataclasses.MISSING
165142
"""
@@ -173,51 +150,9 @@ class SensorData(
173150
the images.
174151
"""
175152

176-
time: astropy.time.Time | na.AbstractScalar = dataclasses.MISSING
177-
"""The time in UTC at the midpoint of the exposure."""
178-
179-
timedelta: u.Quantity | na.AbstractScalar = dataclasses.MISSING
180-
"""The measured exposure time of each image."""
181-
182-
timedelta_requested: u.Quantity | na.AbstractScalar = dataclasses.MISSING
183-
"""The requested exposure time of each image."""
184-
185153
sensor: AbstractSensor = dataclasses.MISSING
186154
"""A model of the sensor used to capture these images."""
187155

188-
serial_number: None | str | na.AbstractScalar = None
189-
"""The serial number of the camera that captured each image."""
190-
191-
run_mode: None | str | na.AbstractScalar = None
192-
"""The Run Mode of the camera when each image was captured."""
193-
194-
status: None | str | na.AbstractScalar = None
195-
"""The status of the camera while each image was being captured."""
196-
197-
voltage_fpga_vccint: u.Quantity | na.AbstractScalar = 0
198-
"""The VCCINT voltage of the FPGA when each image was captured."""
199-
200-
voltage_fpga_vccaux: u.Quantity | na.AbstractScalar = 0
201-
"""The VCCAUX voltage of the FPGA when each image was captured."""
202-
203-
voltage_fpga_vccbram: u.Quantity | na.AbstractScalar = 0
204-
"""The VCCBRAM voltage of the FPGA when each image was captured."""
205-
206-
temperature_fpga: u.Quantity | na.AbstractScalar = 0
207-
"""The temperature of the FPGA when each image was captured."""
208-
209-
temperature_adc_1: u.Quantity | na.AbstractScalar = 0
210-
"""Temperature 1 of the ADC when each image was captured."""
211-
212-
temperature_adc_2: u.Quantity | na.AbstractScalar = 0
213-
"""Temperature 2 of the ADC when each image was captured."""
214-
215-
temperature_adc_3: u.Quantity | na.AbstractScalar = 0
216-
"""Temperature 3 of the ADC when each image was captured."""
217-
218-
temperature_adc_4: u.Quantity | na.AbstractScalar = 0
219-
"""Temperature 4 of the ADC when each image was captured."""
220-
221156
@classmethod
222157
def _calibrate_timedelta(cls, value: int) -> u.Quantity:
223158
return value * 0.000000025 * u.s
@@ -338,23 +273,40 @@ def from_fits(
338273
temperature_adc_3 = cls._calibrate_temperature_adc_234(temperature_adc_3)
339274
temperature_adc_4 = cls._calibrate_temperature_adc_234(temperature_adc_4)
340275

276+
shape = data.shape
277+
278+
shape_img = {
279+
axis_x: shape[axis_x],
280+
axis_y: shape[axis_y],
281+
}
282+
283+
pixel = na.indices(shape_img)
284+
285+
pixel = na.Cartesian2dVectorArray(
286+
x=pixel[axis_x],
287+
y=pixel[axis_y],
288+
)
289+
341290
return cls(
342-
data=data,
291+
inputs=ImageHeader(
292+
pixel=pixel,
293+
time=time,
294+
timedelta=timedelta,
295+
timedelta_requested=timedelta_requested,
296+
serial_number=serial_number,
297+
run_mode=run_mode,
298+
status=status,
299+
voltage_fpga_vccint=voltage_fpga_vccint,
300+
voltage_fpga_vccaux=voltage_fpga_vccaux,
301+
voltage_fpga_vccbram=voltage_fpga_vccbram,
302+
temperature_fpga=temperature_fpga,
303+
temperature_adc_1=temperature_adc_1,
304+
temperature_adc_2=temperature_adc_2,
305+
temperature_adc_3=temperature_adc_3,
306+
temperature_adc_4=temperature_adc_4,
307+
),
308+
outputs=data,
343309
axis_x=axis_x,
344310
axis_y=axis_y,
345-
time=time,
346-
timedelta=timedelta,
347-
timedelta_requested=timedelta_requested,
348311
sensor=sensor,
349-
serial_number=serial_number,
350-
run_mode=run_mode,
351-
status=status,
352-
voltage_fpga_vccint=voltage_fpga_vccint,
353-
voltage_fpga_vccaux=voltage_fpga_vccaux,
354-
voltage_fpga_vccbram=voltage_fpga_vccbram,
355-
temperature_fpga=temperature_fpga,
356-
temperature_adc_1=temperature_adc_1,
357-
temperature_adc_2=temperature_adc_2,
358-
temperature_adc_3=temperature_adc_3,
359-
temperature_adc_4=temperature_adc_4,
360312
)

0 commit comments

Comments
 (0)