Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
python-version: ["3.11"]
name: ${{ matrix.os }}, Python ${{ matrix.python-version }} tests
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
lfs: true
- name: Pull LFS objects
Expand All @@ -36,10 +36,10 @@ jobs:
pip install pytest pytest-cov
pytest --cov=. --cov-report=xml --cov-report=html
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: coverage.xml
files: coverage.xml
flags: unittests
env_vars: OS,PYTHON
name: codecov-umbrella
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ESIS

![tests](https://github.com/Kankelborg-Group/ESIS/workflows/tests/badge.svg)
[![tests](https://github.com/Kankelborg-Group/ESIS/actions/workflows/tests.yml/badge.svg)](https://github.com/Kankelborg-Group/ESIS/actions/workflows/tests.yml)
[![codecov](https://codecov.io/gh/Kankelborg-Group/ESIS/graph/badge.svg?token=CALT5W6YG3)](https://codecov.io/gh/Kankelborg-Group/ESIS)
[![Black](https://github.com/Kankelborg-Group/ESIS/actions/workflows/black.yml/badge.svg)](https://github.com/Kankelborg-Group/ESIS/actions/workflows/black.yml)
[![Ruff](https://github.com/Kankelborg-Group/ESIS/actions/workflows/ruff.yml/badge.svg)](https://github.com/Kankelborg-Group/ESIS/actions/workflows/ruff.yml)
Expand Down
4 changes: 2 additions & 2 deletions docs/make.bat
Git LFS file not shown
2 changes: 2 additions & 0 deletions esis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

from . import optics
from . import nsroc
from . import data
from . import flights

__all__ = [
"optics",
"nsroc",
"data",
"flights",
]
18 changes: 18 additions & 0 deletions esis/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Represent and process ESIS observations into spatial-spectral cubes.

Description of the ESIS Data Levels
===================================

Level 0
-------

* The raw data gathered by the ESIS instrument, saved as FITS files.

"""

from ._level_0 import Level_0

__all__ = [
"Level_0",
]
5 changes: 5 additions & 0 deletions esis/data/_level_0/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ._level_0 import Level_0

__all__ = [
"Level_0",
]
71 changes: 71 additions & 0 deletions esis/data/_level_0/_level_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import numpy as np
from typing_extensions import Self
import dataclasses
import pathlib
import numpy.typing as npt
import named_arrays as na
import msfc_ccd
import esis

__all__ = [
"Level_0",
]


@dataclasses.dataclass(eq=False, repr=False)
class Level_0(
msfc_ccd.SensorData,
):
"""
Representation of ESIS Level-0 images, the raw images gathered by the
Data Acquisition and Control System (DACS).
"""

timeline: None | esis.nsroc.Timeline = None
"""
The sequence of NSROC events associated with these images.
"""

@classmethod
def from_fits(
cls,
path: str | pathlib.Path | na.AbstractScalarArray,
sensor: msfc_ccd.abc.AbstractSensor,
axis_x: str = "detector_x",
axis_y: str = "detector_y",
timeline: None | esis.nsroc.Timeline = None,
) -> Self:

self = super().from_fits(
path=path,
sensor=sensor,
axis_x=axis_x,
axis_y=axis_y,
)

self.timeline = timeline

self.inputs

return self

@property
def channel(self) -> na.ScalarArray[npt.NDArray[str]]:
"""
The name of each ESIS channel in a human-readable format.
"""

sn = self.inputs.serial_number
where_1 = sn == "6"
where_2 = sn == "7"
where_3 = sn == "9"
where_4 = sn == "1"

result = np.empty_like(sn, dtype=object)

result[where_1] = "Channel 1"
result[where_2] = "Channel 2"
result[where_3] = "Channel 3"
result[where_4] = "Channel 4"

return result
18 changes: 18 additions & 0 deletions esis/data/_level_0/_level_0_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
from msfc_ccd._images._tests.test_sensor_images import AbstractTestAbstractSensorData
import esis


@pytest.mark.parametrize(
argnames="a",
argvalues=[
esis.flights.f1.data.level_0(),
],
)
class TestLevel_0(
AbstractTestAbstractSensorData,
):
def test_timeline(self, a: esis.data.Level_0):
result = a.timeline
if result is not None:
assert isinstance(result, esis.nsroc.Timeline)
2 changes: 2 additions & 0 deletions esis/flights/f1/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"""

from ._fits import path_fits
from ._level_0 import level_0

__all__ = [
"path_fits",
"level_0",
]
5 changes: 5 additions & 0 deletions esis/flights/f1/data/_level_0/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ._level_0 import level_0

__all__ = [
"level_0",
]
158 changes: 158 additions & 0 deletions esis/flights/f1/data/_level_0/_level_0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import msfc_ccd
import esis
from ... import nsroc
from .. import path_fits

__all__ = [
"level_0",
]


def level_0(
axis_time: str = "time",
axis_channel: str = "channel",
axis_x: str = "detector_x",
axis_y: str = "detector_y",
) -> esis.data.Level_0:
"""
All the raw images captured by ESIS during the 2019 flight.

Parameters
----------
axis_time
The name of the logical axis representing time.
axis_channel
The name of the logical axis representing the different ESIS channels.
axis_x
The name of the logical axis representing the detector's long axis.
axis_y
The name of the logical axis representing the detector's short axis.

Examples
--------

Load the Level-0 dataset into a :class:`esis.data.Level_0` instance.

.. jupyter-execute::

import IPython.display
import matplotlib.pyplot as plt
import astropy.visualization
import named_arrays as na
import esis

# Define the names of the logical axes
# to use for constructing the Level-0 dataset
axis_time = "time"
axis_channel = "channel"

# Load the Level-0 dataset into memory
level_0 = esis.flights.f1.data.level_0(
axis_time=axis_time,
axis_channel=axis_channel,
)

Make a movie of three frames of the Level-0 dataset.

.. jupyter-execute::

# Define a slice of three frames near apogee
index = {axis_time: slice(20, 23)}

# Create a figure
fig, axs = na.plt.subplots(
axis_rows="rows",
axis_cols="cols",
nrows=level_0.shape[axis_channel] // 2,
ncols=2,
sharex=True,
sharey=True,
constrained_layout=True,
figsize=(10, 5),
)

# Reorganize the axes into a flat array
ax = axs.combine_axes(("rows", "cols"), axis_channel)
ax = ax[{axis_channel: slice(None, None, -1)}]

# Define the colormap
colorizer = plt.Colorizer(
norm=plt.Normalize(
vmin=level_0.outputs.percentile(1).ndarray,
vmax=level_0.outputs.percentile(99).ndarray,
),
)

# Animate the Level-0 dataset frames
ani = na.plt.pcolormovie(
level_0.inputs.time[index].mean(axis_channel),
level_0.inputs.pixel.x,
level_0.inputs.pixel.y,
C=level_0.outputs[index],
axis_time=axis_time,
ax=ax,
kwargs_pcolormesh=dict(
colorizer=colorizer,
),
)

# Create labels for each axis
na.plt.text(
x=0.5,
y=1.01,
s=level_0.channel,
transform=na.plt.transAxes(ax),
ax=ax,
ha="center",
va="bottom",
)
na.plt.set_aspect("equal", ax=ax)
na.plt.set_xlabel("detector $x$ (pix)", ax=axs[dict(rows=0)])
na.plt.set_ylabel("detector $y$ (pix)", ax=axs[dict(cols=0)])

# Plot the colorbar using the colormap
plt.colorbar(
mappable=plt.cm.ScalarMappable(colorizer=colorizer),
ax=ax.ndarray,
label="signal (DN)"
)

# Render the movie as a javascript animation
plt.close(fig)
IPython.display.HTML(ani.to_jshtml())

Plot the FPGA temperatures over the flight.

.. jupyter-execute::

# Convert the time array from ISO to a Python :class:`datetime.datetime`
# instance
time = level_0.inputs.time
time = time.replace(ndarray=time.ndarray.datetime)

# Plot the result as a line plot
with astropy.visualization.quantity_support():
fig, ax = plt.subplots()
na.plt.plot(
time,
level_0.inputs.temperature_fpga,
axis=axis_time,
ax=ax,
label=level_0.channel,
)
ax.set_ylabel(f"FPGA temperature ({ax.get_ylabel()})")
ax.legend()
"""

path = path_fits(
axis_time=axis_time,
axis_channel=axis_channel,
)

return esis.data.Level_0.from_fits(
path=path,
sensor=msfc_ccd.TeledyneCCD230(),
axis_x=axis_x,
axis_y=axis_y,
timeline=nsroc.timeline(),
)
7 changes: 7 additions & 0 deletions esis/flights/f1/data/_level_0/_level_0_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import esis

def test_level_0():

lvl0 = esis.flights.f1.data.level_0()

assert isinstance(lvl0, esis.data.Level_0)
Empty file removed esis/flights/f1/data/level_0.py
Empty file.
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ dependencies = [
"numpy",
"matplotlib",
"astropy",
"optika==0.11.0",
"aastex==0.3.1",
"optika>=0.12.0",
"msfc-ccd>=0.2.0",
"aastex>=0.3.1",
]
dynamic = ["version"]

Expand Down Expand Up @@ -49,6 +50,7 @@ packages = ["esis"]
testpaths = [
"esis/optics/_tests",
"esis/flights",
"esis/data",
"esis/science/papers/instrument/_tests",
]

Expand Down
Loading