diff --git a/src/wfi_reference_pipeline/reference_types/photom/make_pam.py b/src/wfi_reference_pipeline/reference_types/photom/make_pam.py new file mode 100644 index 00000000..c885489a --- /dev/null +++ b/src/wfi_reference_pipeline/reference_types/photom/make_pam.py @@ -0,0 +1,480 @@ +""" +Tools for creating pixel area maps of the Roman WFI detectors. + +Some of this code has been adapted from the JWST NIRCam code for +making their calibration reference files. +""" + +import datetime +import getpass +import importlib +#import logging +from math import hypot, atan2, sin +import os +import pysiaf +import scipy + +import asdf +from astropy import units as u, __version__ as astropy_version +from astropy.time import Time +import matplotlib.pyplot as plt +import numpy as np + +from roman_datamodels import stnode as rds, __version__ as rdm_version + +#from .utils import logging_functions +#from .utils import matrix +#from .utils.read_siaf import get_distortion_coeffs +#from . import data as wfidata + +# Automatic versioning +#from wfitools._version import version as __version__ + + +class PixelArea: + """ + Class for computing a pixel area map for the Roman WFI detectors. + + Inputs + ------ + detector (integer): + WFI detector ID number. + + siaf_file (string; default=None): + Path to the science instrument aperture file (SIAF) containing + the geometric distortion polynomial coefficients. If None, then + the code will look up the copy of the SIAF included in the + package data. + + verbose (boolean; default=False): + Optional argument to enable logging messages with additional + information. + + Examples + -------- + To compute the pixel area map of the science pixels (4088 x 4088) of + detector WFI07 that does not include the reference pixel border: + + >>> from wfitools import pixel_area_map + >>> pam07 = pixel_area_map.PixelArea(7) + >>> pam07.compute() + >>> pam07.save_asdf() + """ + + def __init__(self, detector, siaf_file=None, verbose=False): + + # Set up verbosity + self.verbose = verbose + #if self.verbose: + # logging_functions.configure_logging(level=print) + #else: + # logging_functions.configure_logging(level=logging.WARNING) + + # Some record-keeping attributes. + #if not siaf_file: + # with importlib.resources.path(wfidata, 'roman_siaf.xml') as siaf_file: + # self.siaf_file = siaf_file + #else: + # self.siaf_file = siaf_file + self.siaf_file = pysiaf.Siaf('Roman') + self.detector = f'WFI{detector:02d}' + + # Get the distortion coefficients from the SIAF. + self.x_coeffs, self.y_coeffs = self.get_coeffs() + + self.pixel_area_map = None + self.nominal_pixel_area = None + + print(f'Set up to create pixel area map for {self.detector}.') + #print(f'wfitools version = {__version__}') + print(f'roman_datamodels version = {rdm_version}') + print(f'astropy version = {astropy_version}') + print(f'numpy version = {np.__version__}') + + # @logging_functions.timeit + def compute(self, include_border=False, refpix_area=False): + """ + Purpose + ------- + Perform the steps necessary to construct the pixel area map. + + Inputs + ------ + include_border (boolean; default=False): + Include the 4 pixel reference pixel border around the science + pixel array. This makes the pixel area map have dimensions + of 4096 x 4096. It is recommended to leave this set to False. + + refpix_area (boolean; default=False): + If the reference pixel border is included, then this parameter + indicates whether or not to set the area of the reference + pixels to zero. A value of True will compute the area of the + reference pixels. Default is False. + + Returns + ------- + None + """ + + pixel_area_a2 = self.get_nominal_area() + + # Make a grid of pixel positions from from -det_size to +det_size. + # Really this is half of the detector size, so that it's the full + # detector, but centered at 0 (the reference pixel position). + # + # If for some reason the user wants to include the border of reference + # pixels, we can do that here...might be useful if someone skipped + # trimming them. + if include_border: + #logging.info('Including reference pixel border in pixel area ' + # 'map. Pixel area map will have dimensions 4096 x ' + # '4096.') + #logging.warning('!!') + #logging.warning('Calibrated Roman WFI data have the reference ' + # 'pixel border trimmed, and dimensions of 4088 x ' + # '4088. Do not use this pixel area map unless ' + # 'the calibrated data include the reference ' + # 'pixel border.') + #logging.warning('!!') + det_size = 2048 + else: + det_size = 2044 + pixels = np.mgrid[-det_size:det_size, -det_size:det_size] + y = pixels[0, :, :] + x = pixels[1, :, :] + + self.pixel_area_map = jacob(self.x_coeffs, + self.y_coeffs, x, y, order=5).astype(np.float32) + + # Sanity check. + ratio = self.pixel_area_map[det_size, det_size] / pixel_area_a2.value + logging.info(f'Jacobian determinant at (x, y) = (2044, 2044) is ' + f'{self.pixel_area_map[det_size, det_size]} arcsec^2') + logging.info(f'Nominal pixel area is {pixel_area_a2.value} ' + f'arcsec^2') + logging.info(f'Ratio (Jacobian / nominal) = {ratio}') + + # Normalize the pixel area map to the nominal pixel area. + # Both are in units of arcseconds before the normalization. + self.pixel_area_map /= pixel_area_a2.value + + # If the reference pixel border was included, check if we're + # setting the area of the reference pixels to zero. This is the + # default behavior...someone might override it. + if include_border: + if not refpix_area: + logging.info(f'Reference pixel border was included, but ' + f'refpix_area = {refpix_area}. Setting area ' + f'of reference pixels to zero.') + self.pixel_area_map[:4, :] = 0 + self.pixel_area_map[-4:, :] = 0 + self.pixel_area_map[:, :4] = 0 + self.pixel_area_map[:, -4:] = 0 + + # Save the nominal pixel area in units of steradians. + self.nominal_pixel_area = pixel_area_a2.to(u.sr) + + def save_asdf(self, filename=None, meta_data_override={}): + """ + Purpose + ------- + Write the pixel area map to an ASDF file using the + AREA reference file datamodel. + + Inputs + ------ + filename (string; default=None): + Name of the output ASDF file. If None, then + construct a file name of the form + 'roman_{detector}_YYYYMMDD_hhmmss_area.asdf'. + For example: + roman_wfi16_20220211_222140_area.asdf + is a pixel area map of detector WFI16 that + was made on February 11, 2022 at 22:21:40. + + meta_data_override (dictionary; default=None): + A dictionary of values to override default + entries in the output ASDF file meta data. + + Returns + ------- + None + + Examples + -------- + To generate a pixel area map of WFI16 and save the output + to an ASDF file, while overriding the meta data to set the + origin to STScI and the useafter to 2022-01-01 00:00:00: + + >>> from datetime import datetime + >>> from wfitools import pixel_area_map + >>> from astropy.time import Time + >>> pam16 = pixel_area_map.PixelArea(16) + >>> pam16.compute() + >>> useafter = Time(datetime(2022, 1, 1, 0, 0, 0)) + >>> pam16.save_asdf(meta_data_override={'origin': 'STScI', 'useafter': useafter}) + """ + + if not filename: + date = datetime.datetime.today().strftime('%Y%m%d_%H%M%S') + filename = f'roman_{self.detector.lower()}_{date}_area.asdf' + + dm = rds.PixelareaRef() + + meta = {'reftype': 'AREA', + 'description': 'Roman WFI pixel area map.', + 'pedigree': 'GROUND', + 'telescope': 'ROMAN', + 'origin': 'STSCI/SOC', + 'author': f'wfitools version {__version__}', + 'useafter': Time(datetime.datetime(2020, 1, 1, 0, 0, 0)), + 'photometry': + {'pixelarea_arcsecsq': self.nominal_pixel_area.to(u.arcsec * u.arcsec).value, + 'pixelarea_steradians': self.nominal_pixel_area.value}, + 'instrument': + {'optical_element': 'F158', + 'detector': self.detector.upper()}, + 'pixel_scale': pixel_scale, + 'x_offset': x_offset, + 'y_offset': y_offset + } + + # If the user wants to override any of these defaults, then do so now. + meta.update(meta_data_override) + + #logging.info('Saving ASDF file.') + print(f'ASDF meta data: {meta}') + + dm['data'] = self.pixel_area_map + dm['meta'] = meta + + # Add additional layers here + # dm['x_corrected'] = x_corrected_array + # dm['y_corrected'] = y_corrected_array + # And so on... + + asdf_file = asdf.AsdfFile() + asdf_file.tree = {'roman': dm} + asdf_file.write_to(filename) + + print(f'Pixel area map saved to file {filename}') + + def show_map(self, filename=None): + """ + Purpose + ------- + Display the pixel area map. + + Inputs + ------ + filename (string; default=None): + Name of the file to save the pixel area map image. If None, + the image will be displayed on the screen instead. + + Returns + ------- + None + """ + + fig = plt.figure('Pixel Area Map') + ax = fig.add_subplot() + img = ax.imshow(self.pixel_area_map, origin='lower', + vmin=np.min(self.pixel_area_map[self.pixel_area_map > 0])) + ax.set_xlabel('X science coordinate (pixels)') + ax.set_ylabel('Y science coordinate (pixels)') + ax.set_title(f'Pixel Area Map for {self.detector}') + plt.colorbar(img) + + if filename: + plt.savefig(filename) + else: + plt.show() + + def get_nominal_area(self): + """ + Purpose + ------- + Compute the nominal pixel area at the reference position. + + Inputs + ------ + None + + Returns + ------- + pixel_area (`~astropy.units.Quantity`): + The area of the nominal reference pixel in units of square + arcseconds. + """ + + x_scale = hypot(self.x_coeffs[1], self.y_coeffs[1]) + y_scale = hypot(self.x_coeffs[2], self.y_coeffs[2]) + bx = atan2(self.x_coeffs[1], self.y_coeffs[1]) + + pixel_area = x_scale * y_scale * sin(bx) * u.arcsec * u.arcsec + + return pixel_area + + def get_coeffs(self): + """ + Purpose + ------- + Get the geometric distortion polynomial coefficients from the SIAF. + + Inputs + ------ + None + + Returns + x_coeffs (`~numpy.ndarray`): + Array of polynomial coefficients describing the geometric distortion + in the X direction. + + y_coeffs (`~numpy.ndarray`): + Array of polynomial coefficients describing the geometric distortion + in the Y direction. + """ + + det_name = f'{self.detector}_FULL' + coeffs = self.siaf_file[det_name].get_polynomial_coefficients() + x_coeffs = coeffs['Sci2IdlX'] + y_coeffs = coeffs['Sci2IdlY'] + + return x_coeffs, y_coeffs + + + def dpdx(a, x, y, order=5): + """ + Purpose + ------- + Compute the differential of a 2D polynomial with respect to the X variable. + Assumes that coefficients are ordered as described in the JWST and Roman + science instrument aperture files (SIAFs). + + Inputs + ------ + a (iterable of floats): + An iterable (list or array) of float values giving the coefficients of + a 2D polynomial as a function of X and Y. + + x (float or array of floats): + The X coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + y (float or array of floats): + The Y coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + order (integer; default = 5): + The order of the 2D polynomial. Note that for Roman WFI, this should + always be 5. + + Returns + ------- + partial_x (float): + The partial derivative with respect to X. + """ + + partial_x = 0.0 + k = 1 # index for coefficients + for i in range(1, order + 1): + for j in range(i + 1): + if i - j > 0: + partial_x += (i - j) * a[k] * x**(i - j - 1) * y**j + k += 1 + return partial_x + + + def dpdy(a, x, y, order=5): + """ + Purpose + ------- + Compute the differential of a 2D polynomial with respect to the Y variable. + Assumes that coefficients are ordered as described in the JWST and Roman + science instrument aperture files (SIAFs). + + Inputs + ------ + a (iterable of floats): + An iterable (list or array) of float values giving the coefficients of + a 2D polynomial as a function of X and Y. + + x (float or array of floats): + The X coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + y (float or array of floats): + The Y coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + order (integer; default = 5): + The order of the 2D polynomial. Note that for Roman WFI, this should + always be 5. + + Returns + ------- + partial_x (float): + The partial derivative with respect to X. + """ + + partial_y = 0.0 + k = 1 # index for coefficients + for i in range(1, order + 1): + for j in range(i + 1): + if j > 0: + partial_y += j * a[k] * x**(i - j) * y**(j - 1) + k += 1 + return partial_y + + + def jacob(a, b, x, y, order=5): + """ + Purpose + ------- + Compute the Jacobian determinant of a 2D polynomial. In principal, + this is used to compute the area of each pixel on the sky given + a polynomial that describes the geometric distortion. + + Note that the functions called (dpdx and dpdy) assume that the + order of the polynomial coefficients is the order used in the + JWST and Roman science instrument aperture files (SIAFs). + + Inputs + ------ + a (iterable of floats): + An iterable (list or array) of float values giving the coefficients of + a 2D polynomial as a function of X and Y that fit the X pixel positions, + i.e., X - Xsci = f(X, Y). + + b (iterable of floats): + An iterable (list or array) of float values giving the coefficients of + a 2D polynomial as a function of X and Y that fit the Y pixel positions, + i.e., Y - Ysci = f(X, Y). + + x (float or iterable of floats): + The X coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + y (float or iterable of floats): + The Y coordinate(s) (with respect to the reference pixel position) + at which to evaluate the partial derivative. + + order (integer; default = 5): + The order of the 2D polynomial. Note that for Roman WFI, this should + always be 5. + + Returns + ------- + jacobian (float or iterable of floats): + The Jacobian determinant of the 2D polynomial evaluated at one or more + input positions. The shape of the result will match the shape of the + input x and y variables. + + This is the area on the sky of a pixel at position (x, y) given a + geometric distortion described by a 2D polynomial with coefficients + a and b as further described in the JWST and Roman SIAF documentation. + """ + jacobian = dpdx(a, x, y, order=order) * dpdy(b, x, y, order=order) - \ + dpdx(b, x, y, order=order) * dpdy(a, x, y, order=order) + jacobian = scipy.fabs(jacobian) + return jacobian diff --git a/src/wfi_reference_pipeline/reference_types/photom/make_wfi_photom_ref.py b/src/wfi_reference_pipeline/reference_types/photom/make_wfi_photom_ref.py new file mode 100644 index 00000000..3c3c2c8f --- /dev/null +++ b/src/wfi_reference_pipeline/reference_types/photom/make_wfi_photom_ref.py @@ -0,0 +1,311 @@ +import os +os.environ['CRDS_SERVER_URL'] = 'https://roman-crds.stsci.edu' +os.environ['CRDS_PATH'] = '/Users/calamida/crds_cache' +os.environ['CRDS_CONTEXT'] = 'roman_0046.pmap' + + +import make_pam as pam +import synphot as syn +from roman_datamodels import stnode as rds # First Roman Data Models Call +from astropy import units as u +from astropy.time import Time +import datetime +import asdf +import numpy as np +from astropy import units as u +from synphot import SpectralElement +from synphot.models import Empirical1D +from astropy.table import Table +from astropy.io import ascii +from crds import getreferences +from astropy.stats import sigma_clipped_stats +import roman_datamodels as rdm # Second Roman Data Models Call +import rad +print('rad version:') +rad.__version__ + +meta = {'ROMAN.META.INSTRUMENT.NAME': 'WFI', + 'ROMAN.META.EXPOSURE.START_TIME': '2024-01-01 00:00:00'} + +stats = {} + +for i in range(18): + meta.update({'ROMAN.META.INSTRUMENT.DETECTOR': f'WFI{i+1:02d}'}) + gain = getreferences(meta, reftypes=['gain'], observatory='roman', context='roman_0046.pmap', ignore_cache=False) + with asdf.config_context() as cfg: + cfg.validate_on_read = False + gfile = asdf.open(gain['gain']) + garr = gfile['roman']['data'] #.value.copy() + _, med, std = sigma_clipped_stats(garr, sigma=4, maxiters=3) + stats[f'WFI{i+1:02d}'] = {'median': med, 'stddev': std} + + +""" +Create Roman WFI PHOTOM reference file that validates against the +wfi_img_photom.yaml schema. + +- Imaging filters (e.g., F062) use wavelength + effective area from the ECSV, currently Roman_effarea_v8_SCA01_20240301.ecsv +- GRISM/PRISM get valid placeholder arrays (zeros for effective area). +- DARK entry uses all nulls. +""" + +# Detector index +DET_INDEX = 11 # produces meta.instrument.detector = 'WFIDET_INDEX' + +# Collecting area in m^2 (scalar allowed by schema) +COLLECTING_AREA_M2 = 3.60767 + +# Imaging filters (must exist as columns in the ECSV table) +#IMAGING_FILTERS = ['F106'] #['F062', 'F087', 'F106', 'F129', 'F146', 'F158', 'F184', 'F213'] +IMAGING_FILTERS = ['F062', 'F087', 'F106', 'F129', 'F146', 'F158', 'F184', 'F213'] +# Dispersers to include without curves +INCLUDE_DISPERSERS = True +DISPERSERS = ['GRISM', 'PRISM'] # 'GRISM_0' is also accepted by the schema + +# Input throughput table (effective area curves) +#ECSV_PATH = '/grp/roman/calamida/photom/Roman_effarea_v8_SCA01_20240301.ecsv' +ECSV_PATH = f'/grp/roman/calamida/photom/Roman_effarea_tables_20240327/Roman_effarea_v8_SCA{DET_INDEX:02d}_20240301.ecsv' + +# Does the ECSV contain EFFECTIVE AREA (m^2) instead of throughput? +# For Roman_effarea_v8_SCA01_20240301.ecsv this is True. +ECSV_HAS_EFFECTIVE_AREA_M2 = True + +# Output ASDF reference file +#OUTPUT_ASDF = '/grp/roman/calamida/photom/roman_WFI02_photom.asdf' +OUTPUT_ASDF = f'roman_wfi{DET_INDEX:02d}_photom.asdf' + +# Pixel Area Map +import make_pam as pam +def get_pixel_area_sr(det_index: int) -> float: + """ + Returns pixel area in steradians for detector `det_index` (1-based). + Uses your pam provider. + """ + return pam.PixelArea(det_index).get_nominal_area().to(u.sr).value + +# Gain info +def get_gain_stats(det_index: int) -> dict: + key = f"WFI{det_index:02d}" + if key not in stats: + raise KeyError(f"gain stats dict missing key '{key}'") + return { + 'median': float(stats[key]['median']), + 'stddev': float(stats[key]['stddev']), + } + +# ----------------------------- +# HELPER FUNCTIONS +# ----------------------------- + +def build_filter_entry( + waves_micron, # Quantity [micron] + eff_area_m2, # Quantity [m^2] + collecting_area_m2: float, + det_index: int, + gain_median: float, + gain_stddev: float, +) -> dict: + + # Quantities used for physics/calculations only: + wq = waves_micron.to(u.micron) # Quantity [micron] + eaq = eff_area_m2.to(u.m**2) # Quantity [m^2] + + # Throughput = A_eff / A_coll (dimensionless ndarray) + T = (eaq / (collecting_area_m2 * u.m**2)).decompose().value.astype(np.float32) + + # Build bandpass from throughput usign STsynphot function + band = syn.SpectralElement(Empirical1D, points=wq, lookup_table=T) + pivot = band.pivot() + unit_resp = band.unit_response(collecting_area_m2 * u.m**2) + + pixel_area_sr = get_pixel_area_sr(det_index) + + # Convertion to MJy/sr + mjy_per_dnps_per_sr = ( + syn.units.convert_flux(pivot, unit_resp, u.MJy) / (pixel_area_sr * u.sr) + ).value + + # Getting Gain value for the detector + g = float(gain_median) + g_rerr = float(gain_stddev) / g if g != 0 else 0.0 + + photmjsr = np.float32(mjy_per_dnps_per_sr * g) + uncertainty = np.float32(mjy_per_dnps_per_sr * g * g_rerr) + + # >>> CRITICAL: store plain ndarrays (no units), float32, 1-D + wavelength_arr = wq.to_value(u.micron).astype(np.float32) # ndarray + effective_area_arr = eaq.to_value(u.m**2).astype(np.float32) # ndarray + + + return { + 'photmjsr': float(photmjsr), + 'uncertainty': float(uncertainty), + 'pixelareasr': float(np.float32(pixel_area_sr)), + 'collecting_area': float(np.float32(collecting_area_m2)), + 'wavelength': wavelength_arr, # ndarray float32, 1-D + 'effective_area': effective_area_arr, # ndarray float32, 1-D + } + + +def build_disperser_entry( + waves_micron, # Quantity [micron] + collecting_area_m2: float, +) -> dict: + + + # >>> CRITICAL: convert to ndarray + wavelength_arr = waves_micron.to_value(u.micron).astype(np.float32) # ndarray + effective_area_arr = np.zeros_like(wavelength_arr, dtype=np.float32) # ndarray + + return { + 'photmjsr': None, + 'uncertainty': None, + 'pixelareasr': None, + 'collecting_area': float(np.float32(collecting_area_m2)), + 'wavelength': wavelength_arr, # ndarray float32, 1-D + 'effective_area': effective_area_arr, # ndarray float32, 1-D + } + + + +# ---------- Parameterized builder ---------- + +def build_photom_ref( + det_index: int, + output_asdf: str, + imaging_filters: list, + include_dispersers: bool, + ecsv_path: str, + collecting_area_m2: float, + ecsv_has_effective_area_m2: bool, +): + """ + Build one PHOTOM reference file for a single detector. + """ + # --- Load ECSV with wavelength + curves --- + thru_tab = ascii.read(ecsv_path) + + if 'Wave' not in thru_tab.colnames: + raise ValueError(f"'Wave' column not found in {ecsv_path}") + + # Wavelength grid (as Quantity for calcs; will be converted to ndarray in helpers) + waves_q = (thru_tab['Wave'].data * u.micron) + + # Detector-specific gain stats + gstats = get_gain_stats(det_index) + g_med = float(gstats['median']) + g_std = float(gstats['stddev']) + + phot_table = {} + + + + # --- Imaging filters --- + for se in imaging_filters: + if se not in thru_tab.colnames: + raise ValueError(f"Filter column '{se}' not found in {ecsv_path}") + + col = thru_tab[se].data.astype(np.float64) # safe math + if ecsv_has_effective_area_m2: + eff_q = (col * u.m**2) # Quantity + else: + # Throughput -> effective area + eff_q = (col.astype(np.float32) * (collecting_area_m2 * u.m**2)) + + entry = build_filter_entry( + waves_micron=waves_q, + eff_area_m2=eff_q, + collecting_area_m2=collecting_area_m2, + det_index=det_index, + gain_median=g_med, + gain_stddev=g_std, + ) + phot_table[se] = entry + + # --- Dispersers --- + if include_dispersers: + for disp in ('GRISM', 'PRISM'): + phot_table[disp] = build_disperser_entry( + waves_micron=waves_q, + collecting_area_m2=collecting_area_m2, + ) + + + # --- DARK entry (all None) --- + phot_table['DARK'] = { + 'photmjsr': None, + 'uncertainty': None, + 'pixelareasr': None, + 'collecting_area': None, + 'wavelength': None, + 'effective_area': None, + } + + + # --- Build the datamodel and meta --- +# dm = rds.WfiImgPhotomRef() +# dm.phot_table = phot_table + + + phot_meta = {'reftype': 'PHOTOM', + 'description': 'Roman WFI absolute photometric calibration information. Throughput information comes from the Roman Technical Information Repository (https://github.com/spacetelescope/roman-technical-information) version 1.0 with a data of 2024 March 27. Gain has been accounted for to correctly transform from input count rates of DN/s to physical units. Gains have been measured as sigma-clipped (sigma=4, iterations=3) medians from the first pass TVAC1 gain reference files. Uncertainty in the zeropoint reflects the 1-sigma standard deviation in the sigma-clipped gain.', + 'pedigree': 'GROUND', + 'telescope': 'ROMAN', + 'origin': 'STSCI/SOC', + 'author': 'A. Calamida', + 'useafter': Time(datetime.datetime(2020, 1, 1, 0, 0, 0)), + 'instrument': + {'detector': f'WFI{DET_INDEX:02d}', + 'name': 'WFI', + 'median_gain': float(g_med), + 'sigma_gain': float(g_std)} + } + + dm = rds.WfiImgPhotomRef() + dm['phot_table'] = phot_table + dm['meta'] = phot_meta + + + + # Optional: embed versions for reproducibility + try: + import roman_datamodels, astropy, synphot, asdf as _asdf + dm.meta.software = { + "roman_datamodels": roman_datamodels.__version__, + "astropy": astropy.__version__, + "numpy": np.__version__, + "synphot": synphot.__version__, + "asdf": _asdf.__version__, + } + except Exception: + pass + + with asdf.AsdfFile() as af: + af.tree = {'roman': dm} + af.write_to(output_asdf) + + print(f"✓ Wrote {output_asdf}") + + +if __name__ == "__main__": + # Configuration you already have in your script: + collecting_area_m2 = COLLECTING_AREA_M2 + ecsv_path = ECSV_PATH + ecsv_has_effective_area_m2 = ECSV_HAS_EFFECTIVE_AREA_M2 + imaging_filters = IMAGING_FILTERS + include_dispersers = INCLUDE_DISPERSERS + + for det in range(1, 19): # WFI01..WFI18 + output_asdf = f"roman_wfi{det:02d}_photom.asdf" + build_photom_ref( + det_index=det, + output_asdf=output_asdf, + imaging_filters=imaging_filters, + include_dispersers=include_dispersers, + ecsv_path=ecsv_path, + collecting_area_m2=collecting_area_m2, + ecsv_has_effective_area_m2=ecsv_has_effective_area_m2, + ) + + + diff --git a/src/wfi_reference_pipeline/reference_types/photom/photom_env_mac.yml b/src/wfi_reference_pipeline/reference_types/photom/photom_env_mac.yml new file mode 100644 index 00000000..b853c179 --- /dev/null +++ b/src/wfi_reference_pipeline/reference_types/photom/photom_env_mac.yml @@ -0,0 +1,22 @@ +name: photom_rfp +channels: + - conda-forge +dependencies: + - pip>=23.3 + - astropy>=5.3.4 + - matplotlib>=3.8 + - seaborn>=0.13.2 + - numpy>=1.26.0 + - python>=3.12 + - scipy>=1.11.4 + - jupyterlab>=4.0.11 + - pandas>=2.1.4 + - asdf + - pip: + - roman-datamodels>=0.29.0 + - crds==12.0.9 + - pysiaf>=0.24.1 + - python-dateutil + - rad>=0.23.0 + - synphot>=1.5.0 +