-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add migrate_data script to make older x1d files readable by later ver…
…sions of jwst
- Loading branch information
Ed Slavich
committed
May 28, 2021
1 parent
22a204e
commit 3e4f4fe
Showing
7 changed files
with
238 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
Migrating deprecated products | ||
----------------------------- | ||
|
||
On rare occasion, the model schemas are changed in such a way as to | ||
break compatibility with data products produced by earlier versions | ||
of this package. When these older files are opened the software | ||
will report validation errors: | ||
|
||
.. doctest-skip:: | ||
|
||
>>> from jwst import datamodels | ||
>>> datamodels.open("jw95115001001_02102_00001_nrs1_x1d.fits") | ||
... | ||
ValueError: Column names don't match schema... | ||
|
||
In some cases it will be possible to update the file to the | ||
new format using the ``migrate_data`` tool included with this package: | ||
:: | ||
|
||
$ migrate_data jw95115001001_02102_00001_nrs1_x1d.fits --in-place | ||
|
||
It can also be run on multiple files: | ||
:: | ||
|
||
$ migrate_data *_x1d.fits --in-place | ||
|
||
Or configured to write updated files to a separate output directory: | ||
:: | ||
|
||
$ migrate_data *_x1d.fits --output-dir some/other/directory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
""" | ||
Tests of the migrate_data script, which attempts to update | ||
files that have become invalid due to changes in model schemas | ||
between versions of this package. | ||
Obtains examples of files from artifactory truth data for | ||
older releases. | ||
""" | ||
import subprocess | ||
|
||
from astropy.io import fits | ||
import pytest | ||
|
||
from jwst import datamodels | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def strict_validation(monkeypatch): | ||
monkeypatch.setenv("STRICT_VALIDATION", "true") | ||
yield | ||
|
||
|
||
@pytest.mark.bigdata | ||
@pytest.mark.parametrize("truth_path", [ | ||
"truth/test_miri_lrs_slit_spec2/jw00623032001_03102_00001_mirimage_x1d.fits", | ||
"truth/test_nirspec_mos_spec2/f170lp-g235m_mos_observation-6-c0e0_001_dn_nrs1_mod_x1d.fits", | ||
], ids=["miri-lrs-x1d", "nirspec-mos-x1d"]) | ||
def test_x1d_spec_table(truth_path, rtdata): | ||
rtdata.env = "1.1.0" | ||
rtdata.get_truth(truth_path) | ||
|
||
# Confirm that the file doesn't initially validate | ||
# (open with fits first so that the failed call to open doesn't leave behind an open file) | ||
with fits.open(rtdata.truth, memmap=False) as hdu: | ||
with pytest.raises(ValueError, match="Column names don't match schema"): | ||
with datamodels.open(hdu): | ||
pass | ||
|
||
subprocess.check_call(["migrate_data", rtdata.truth, "--in-place"]) | ||
|
||
# Now the model should validate | ||
with datamodels.open(rtdata.truth): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
#!/usr/bin/env python3 | ||
""" | ||
Migrate .fits files whose format has changed between jwst package versions. | ||
""" | ||
import argparse | ||
from datetime import datetime | ||
import os | ||
import re | ||
import traceback | ||
import warnings | ||
|
||
import asdf | ||
from astropy.io import fits | ||
import numpy as np | ||
from packaging.specifiers import SpecifierSet | ||
|
||
import jwst | ||
from jwst import datamodels | ||
|
||
|
||
def parse_args(): | ||
parser = argparse.ArgumentParser("migrate_data", "migrate .fits files whose format has changed between jwst package versions") | ||
|
||
parser.add_argument("files", nargs="+", help="one or more .fits files") | ||
|
||
output_group = parser.add_mutually_exclusive_group(required=True) | ||
output_group.add_argument("--output-dir", help="write modified files to an output directory") | ||
output_group.add_argument("--in-place", help="modify files in-place", action="store_true") | ||
|
||
return parser.parse_args() | ||
|
||
|
||
# If there get to be many of these we may want to move | ||
# them to jwst.datamodels somewhere: | ||
|
||
def migrate_spec_table_1_1_0(hdul): | ||
""" | ||
spectable.schema added additional columns and renamed | ||
two columns. | ||
""" | ||
schema = asdf.schema.load_schema("http://stsci.edu/schemas/jwst_datamodel/spectable.schema") | ||
dtype = asdf.tags.core.ndarray.asdf_datatype_to_numpy_dtype(schema["datatype"]) | ||
renamed_columns = { | ||
"ERROR": "FLUX_ERROR", | ||
"BERROR": "BKGD_ERROR", | ||
} | ||
|
||
for hdu in hdul: | ||
if hdu.name == "EXTRACT1D": | ||
new_data = np.zeros(hdu.data.shape, dtype=dtype) | ||
for column_name in hdu.data.dtype.names: | ||
new_data[renamed_columns.get(column_name, column_name)] = hdu.data[column_name] | ||
hdu.data = new_data | ||
|
||
|
||
# The first key is a model class name, the second | ||
# a jwst package version specifier. The value | ||
# is a method that accepts an HDUList and modifies | ||
# it in-place. | ||
_MIGRATE_METHODS = { | ||
"SpecModel": { | ||
"> 0.13.1, <= 1.1.0": migrate_spec_table_1_1_0, | ||
}, | ||
"MultiSpecModel": { | ||
"> 0.13.1, <= 1.1.0": migrate_spec_table_1_1_0, | ||
}, | ||
} | ||
|
||
|
||
def migrate_file(filename, args): | ||
if args.in_place: | ||
mode = "update" | ||
else: | ||
mode = "readonly" | ||
|
||
with fits.open(filename, memmap=False, mode=mode) as hdul: | ||
model_type = hdul[0].header.get("DATAMODL") | ||
jwst_version = hdul[0].header.get("CAL_VER") | ||
|
||
if not (model_type and jwst_version): | ||
print(f"Unable to migrate {filename}: DATAMODL and CAL_VER keywords are required") | ||
return | ||
|
||
match = re.match(r'^[0-9]+\.[0-9]+\.[0-9]+', jwst_version) | ||
if match is None: | ||
print(f"Unable to migrate {filename}: CAL_VER not understood") | ||
return | ||
jwst_version = match.group(0) | ||
|
||
if model_type not in _MIGRATE_METHODS: | ||
print(f"Migration for {filename} DATAMODL {model_type} not implemented") | ||
return | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("ignore") | ||
exception_raised = False | ||
try: | ||
getattr(datamodels, model_type)(hdul, strict_validation=True) | ||
except Exception: | ||
exception_raised = True | ||
if not exception_raised: | ||
print(f"{filename} is already valid") | ||
return | ||
|
||
migrate_method = next((m for s, m in _MIGRATE_METHODS[model_type].items() if jwst_version in SpecifierSet(s)), None) | ||
if migrate_method is None: | ||
print(f"Migration for {filename} CAL_VER {jwst_version} not implemented") | ||
return | ||
|
||
migrate_method(hdul) | ||
hdul[0].header["HISTORY"] = f"Migrated with jwst {jwst.__version__} migrate_data script {datetime.utcnow().isoformat()}" | ||
|
||
try: | ||
getattr(datamodels, model_type)(hdul, strict_validation=True) | ||
except Exception: | ||
print(f"Migration for {filename} failed to produce a valid model:\n") | ||
traceback.print_exc() | ||
return | ||
|
||
if args.in_place: | ||
hdul.flush() | ||
else: | ||
output_filename = os.path.join(args.output_dir, os.path.basename(filename)) | ||
hdul.writeto(output_filename, checksum=True, overwrite=True) | ||
|
||
|
||
def main(): | ||
args = parse_args() | ||
|
||
if args.output_dir: | ||
os.makedirs(args.output_dir, exist_ok=True) | ||
|
||
for file in args.files: | ||
try: | ||
migrate_file(file, args) | ||
except Exception: | ||
print(f"Error migrating {file}:\n") | ||
traceback.print_exc() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |