Skip to content
Draft
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: 6 additions & 0 deletions src/wfi_reference_pipeline/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
REF_TYPE_DARKDECAYSIGNAL = "DARKDECAYSIGNAL"
REF_TYPE_DETECTORSTATUS = "DETECTORSTATUS"
REF_TYPE_DISTORTION = "DISTORTION"
REF_TYPE_EPSF = "EPSF"
REF_TYPE_ETC = "ETC"
REF_TYPE_FLAT = "FLAT"
REF_TYPE_GAIN = "GAIN"
Expand All @@ -110,6 +111,7 @@
REF_TYPE_REF_OPTICAL_ELEMENT = "REF_OPTICAL_ELEMENT"
REF_TYPE_REFPIX = "REFPIX"
REF_TYPE_SATURATION = "SATURATION"
REF_TYPE_SIMPSF = "SIMPSF"
REF_TYPE_SUPERBIAS = "SUPERBIAS"


Expand All @@ -121,6 +123,7 @@
REF_TYPE_DETECTORSTATUS,
REF_TYPE_DISTORTION,
REF_TYPE_ETC,
REF_TYPE_EPSF,
REF_TYPE_FLAT,
REF_TYPE_GAIN,
REF_TYPE_INTEGRALNONLINEARITY,
Expand All @@ -137,6 +140,7 @@
REF_TYPE_REF_OPTICAL_ELEMENT,
REF_TYPE_REFPIX,
REF_TYPE_SATURATION,
REF_TYPE_SIMPSF,
REF_TYPE_SUPERBIAS
}

Expand All @@ -145,9 +149,11 @@
REF_TYPE_APERTURECORRECTION,
REF_TYPE_DARKDECAYSIGNAL,
REF_TYPE_DETECTORSTATUS,
REF_TYPE_EPSF,
REF_TYPE_ETC,
REF_TYPE_PHOTOM,
REF_TYPE_PIXELAREA,
REF_TYPE_SIMPSF,
}

WFI_TYPE_IMAGE = "WFI_IMAGE"
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import asdf
import numpy as np
import roman_datamodels.stnode as rds

from ..reference_type import ReferenceType
from wfi_reference_pipeline.resources.wfi_meta_empirical_psf import WFIMetaEPSF

class EmpiricalPSF(ReferenceType):
"""
Class EmpiricalPSF() inherits the ReferenceType() base class methods.
This class builds Roman WFI EPSF reference files.

The empirical point spread function reference file shall be made
to do the following....
"""

def __init__(
self,
meta_data,
psf=None,
extended_psf=None,
outfile='roman_epsf_file.asdf',
clobber=False,
):
"""
Parameters
-------
meta_data : dict
Metadata dictionary formatted per EPSF schema.
psf : np.ndarray
5D array (defocus, spectral_type, grid, y, x)
extended_psf : np.ndarray, optional
2D extended PSF
"""

# Initialize base class
super().__init__(psf, meta_data, clobber=clobber)

# Ensure required metadata
if 'description' not in self.meta:
self.meta['description'] = 'Roman WFI Empirical PSF reference file.'

if not isinstance(meta_data, WFIMetaEPSF):
raise TypeError(
f"Meta Data has reftype {type(meta_data)}, expecting WFIMetaEPSF"
)

# Assign attributes
self.outfile = outfile
self.psf = psf
self.extended_psf = extended_psf

def make_psf_library(self):
"""
Method to populate arrays
"""

self._update_meta_data()

def _update_meta_data(self):
"""
Update the meta data with how the psf library was constructed.
"""

# ------------------------------------------------------------------
# Populate ASDF datamodel
# ------------------------------------------------------------------
def populate_datamodel_tree(self):
"""
Create data model from DMS and populate tree.
"""

epsf_datamodel_tree = rds.EpsfRef()

# Required
epsf_datamodel_tree['meta'] = self.meta
epsf_datamodel_tree['psf'] = self.psf

# Optional arrays
if self.extended_psf is not None:
epsf_datamodel_tree['extended_psf'] = self.extended_psf

if self.psf_noipc is not None:
epsf_datamodel_tree['psf_noipc'] = self.psf_noipc

if self.extended_psf_noipc is not None:
epsf_datamodel_tree['extended_psf_noipc'] = self.extended_psf_noipc

return epsf_datamodel_tree
38 changes: 38 additions & 0 deletions src/wfi_reference_pipeline/resources/make_dev_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from wfi_reference_pipeline.resources.wfi_meta_exposure_time_calculator import (
WFIMetaETC,
)
from wfi_reference_pipeline.resources.wfi_meta_empirical_psf import (WFIMetaEPSF)
from wfi_reference_pipeline.resources.wfi_meta_flat import WFIMetaFlat
from wfi_reference_pipeline.resources.wfi_meta_gain import WFIMetaGain
from wfi_reference_pipeline.resources.wfi_meta_integral_non_linearity import (
Expand Down Expand Up @@ -77,6 +78,40 @@ def _create_dev_meta_dark_decay_signal(self, meta_data):
def _create_dev_meta_detector_status(self, meta_data):
self.meta_detector_status = WFIMetaDetectorStatus(*meta_data)

def _create_dev_meta_epsf(self, meta_data):
ref_optical_element = ["F062"]

oversample = 4
spectral_type = ["A0V", "G2V", "M5V"]
defocus = [0, 1, 2]

pixel_x = [4.0, 2047.5, 4091.0,
4.0, 2047.5, 4091.0,
4.0, 2047.5, 4091.0]

pixel_y = [4.0, 4.0, 4.0,
2047.5, 2047.5, 2047.5,
4091.0, 4091.0, 4091.0]

# Required by schema but missing in file
jitter_major = 0.0
jitter_minor = 0.0
jitter_position_angle = 0.0

epsf_meta_data = [
ref_optical_element,
oversample,
spectral_type,
defocus,
pixel_x,
pixel_y,
jitter_major,
jitter_minor,
jitter_position_angle,
]

self.meta_epsf = WFIMetaEPSF(*meta_data, *epsf_meta_data)

def _create_dev_meta_etc(self, meta_data):
self.meta_etc = WFIMetaETC(*meta_data)

Expand Down Expand Up @@ -213,6 +248,9 @@ def __init__(self, ref_type):
if ref_type == "DETECTORSTATUS":
self._create_dev_meta_detector_status(meta_data_params)

if ref_type == "EPSF":
self._create_dev_meta_epsf(meta_data_params)

if ref_type == "ETC":
self._create_dev_meta_etc(meta_data_params)

Expand Down
41 changes: 41 additions & 0 deletions src/wfi_reference_pipeline/resources/make_test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
REF_TYPE_DARK,
REF_TYPE_DARKDECAYSIGNAL,
REF_TYPE_DETECTORSTATUS,
REF_TYPE_EPSF,
REF_TYPE_ETC,
REF_TYPE_FLAT,
REF_TYPE_GAIN,
Expand All @@ -30,6 +31,9 @@
from wfi_reference_pipeline.resources.wfi_meta_detector_status import (
WFIMetaDetectorStatus,
)
from wfi_reference_pipeline.resources.wfi_meta_empirical_psf import (
WFIMetaEPSF,
)
from wfi_reference_pipeline.resources.wfi_meta_exposure_time_calculator import (
WFIMetaETC,
)
Expand Down Expand Up @@ -77,6 +81,40 @@ def _create_test_meta_dark_decay_signal(self, meta_data):
def _create_test_meta_detector_status(self, meta_data):
self.meta_detector_status = WFIMetaDetectorStatus(*meta_data)

def _create_test_meta_epsf(self, meta_data):
ref_optical_element = ["F062"]

oversample = 4
spectral_type = ["A0V", "G2V", "M5V"]
defocus = [0, 1, 2]

pixel_x = [4.0, 2047.5, 4091.0,
4.0, 2047.5, 4091.0,
4.0, 2047.5, 4091.0]

pixel_y = [4.0, 4.0, 4.0,
2047.5, 2047.5, 2047.5,
4091.0, 4091.0, 4091.0]

# Required by schema but missing in file
jitter_major = 1.0
jitter_minor = 1.0
jitter_position_angle = 1.0

epsf_meta_data = [
ref_optical_element,
oversample,
spectral_type,
defocus,
pixel_x,
pixel_y,
jitter_major,
jitter_minor,
jitter_position_angle,
]

self.meta_epsf = WFIMetaEPSF(*meta_data, *epsf_meta_data)

def _create_test_meta_etc(self, meta_data):
self.meta_etc = WFIMetaETC(*meta_data)

Expand Down Expand Up @@ -201,6 +239,9 @@ def __init__(self, ref_type):
if ref_type == REF_TYPE_DETECTORSTATUS:
self._create_test_meta_detector_status(meta_data_params)

if ref_type == REF_TYPE_EPSF:
self._create_test_meta_epsf(meta_data_params)

if ref_type == REF_TYPE_ETC:
self._create_test_meta_etc(meta_data_params)

Expand Down
101 changes: 101 additions & 0 deletions src/wfi_reference_pipeline/resources/wfi_meta_empirical_psf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from dataclasses import InitVar, dataclass, field
from typing import List, Optional

import wfi_reference_pipeline.constants as constants
from wfi_reference_pipeline.resources.wfi_metadata import WFIMetadata


@dataclass
class WFIMetaEPSF(WFIMetadata):

ref_optical_element: InitVar[Optional[List[str]]] = None

# --- EPSF-specific fields ---
oversample: int = 4
spectral_type: List[str] = field(default_factory=list)
defocus: List[int] = field(default_factory=list)
pixel_x: List[float] = field(default_factory=list)
pixel_y: List[float] = field(default_factory=list)

jitter_major: float = 0.0
jitter_minor: float = 0.0
jitter_position_angle: float = 0.0

def __post_init__(self, ref_optical_element):
super().__post_init__()

self.reference_type = constants.REF_TYPE_EPSF
self.optical_element = []

# Normalize
self.spectral_type = list(self.spectral_type)
self.defocus = list(self.defocus)
self.pixel_x = list(self.pixel_x)
self.pixel_y = list(self.pixel_y)

# --- Jitter validation ---
for name, val in {
"jitter_major": self.jitter_major,
"jitter_minor": self.jitter_minor,
"jitter_position_angle": self.jitter_position_angle,
}.items():
if val is None:
raise ValueError(f"{name} is required")
if not isinstance(val, (int, float)):
raise TypeError(f"{name} must be a number")

if self.jitter_major < 0 or self.jitter_minor < 0:
raise ValueError("jitter_major and jitter_minor must be non-negative")

if not (0.0 <= self.jitter_position_angle <= 360.0):
raise ValueError("jitter_position_angle must be in [0, 360]")

# --- Field validation ---
if not self.pixel_x or not self.pixel_y:
raise ValueError("pixel_x and pixel_y are required and cannot be empty")

if len(self.pixel_x) != len(self.pixel_y):
raise ValueError("pixel_x and pixel_y must have the same length")

if not self.spectral_type:
raise ValueError("spectral_type must not be empty")

if not self.defocus:
raise ValueError("defocus must not be empty")

# --- Oversample validation ---
if not isinstance(self.oversample, int):
raise TypeError("oversample must be an integer")

if self.oversample <= 0:
raise ValueError("oversample must be a positive integer")

def export_asdf_meta(self):
asdf_meta = {
# Common meta
'reftype': self.reference_type,
'pedigree': self.pedigree,
'description': self.description,
'author': self.author,
'useafter': self.use_after,
'telescope': self.telescope,
'origin': self.origin,

# Instrument block
'instrument': {
'name': self.instrument,
'detector': self.instrument_detector,
'optical_element': self.optical_element
},

# --- EPSF-specific ---
'oversample': self.oversample,
'spectral_type': self.spectral_type,
'defocus': self.defocus,
'pixel_x': self.pixel_x,
'pixel_y': self.pixel_y,
'jitter_major': self.jitter_major,
'jitter_minor': self.jitter_minor,
'jitter_position_angle': self.jitter_position_angle,
}
return asdf_meta