From 17f542c5c0433bf8a407d0325d7f0950e3247656 Mon Sep 17 00:00:00 2001
From: "Pey Lian Lim (Github)" <2090236+pllim@users.noreply.github.com>
Date: Wed, 18 May 2022 17:04:19 -0400
Subject: [PATCH] Inherit FITS header comment cards.
Have show_primary be more sticky.
---
jdaviz/configs/cubeviz/plugins/parsers.py | 16 +++---
.../metadata_viewer/metadata_viewer.py | 28 +++++++---
.../metadata_viewer/metadata_viewer.vue | 2 +
.../tests/test_metadata_viewer.py | 53 ++++++++++++++-----
jdaviz/configs/imviz/plugins/parsers.py | 10 ++--
jdaviz/configs/mosviz/plugins/parsers.py | 31 +++++------
jdaviz/configs/mosviz/tests/test_parsers.py | 7 ++-
jdaviz/configs/specviz/plugins/parsers.py | 9 ++--
jdaviz/configs/specviz2d/plugins/parsers.py | 6 +--
jdaviz/tests/test_metadata.py | 52 ++++++++++++++++++
jdaviz/utils.py | 27 +++++++++-
11 files changed, 179 insertions(+), 62 deletions(-)
create mode 100644 jdaviz/tests/test_metadata.py
diff --git a/jdaviz/configs/cubeviz/plugins/parsers.py b/jdaviz/configs/cubeviz/plugins/parsers.py
index b0aaf07ef8..bce4ed9277 100644
--- a/jdaviz/configs/cubeviz/plugins/parsers.py
+++ b/jdaviz/configs/cubeviz/plugins/parsers.py
@@ -9,7 +9,7 @@
from specutils import Spectrum1D
from jdaviz.core.registries import data_parser_registry
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import standardize_metadata, PRIHDR_KEY
__all__ = ['parse_data']
@@ -117,9 +117,9 @@ def _parse_hdulist(app, hdulist, file_name=None):
flux = hdu.data << flux_unit
- metadata = dict(hdu.header)
+ metadata = standardize_metadata(hdu.header)
if hdu.name != 'PRIMARY' and 'PRIMARY' in hdulist:
- metadata[PRIHDR_KEY] = dict(hdulist['PRIMARY'].header)
+ metadata[PRIHDR_KEY] = standardize_metadata(hdulist['PRIMARY'].header)
try:
sc = Spectrum1D(flux=flux, wcs=wcs, meta=metadata)
@@ -167,9 +167,9 @@ def _parse_jwst_s3d(app, hdulist, data_label, ext='SCI', viewer_name='flux-viewe
flux = hdulist[ext].data << unit
wcs = WCS(hdulist['SCI'].header, hdulist) # Everything uses SCI WCS
- metadata = dict(hdulist[ext].header)
+ metadata = standardize_metadata(hdulist[ext].header)
if hdulist[ext].name != 'PRIMARY' and 'PRIMARY' in hdulist:
- metadata[PRIHDR_KEY] = dict(hdulist['PRIMARY'].header)
+ metadata[PRIHDR_KEY] = standardize_metadata(hdulist['PRIMARY'].header)
data = Spectrum1D(flux, wcs=wcs, meta=metadata)
@@ -205,10 +205,10 @@ def _parse_esa_s3d(app, hdulist, data_label, ext='DATA', viewer_name='flux-viewe
flux = np.moveaxis(flux, 0, -1)
flux = np.swapaxes(flux, 0, 1)
- metadata = dict(hdulist[ext].header)
+ metadata = standardize_metadata(hdulist[ext].header)
metadata.update(wcs_dict) # To be internally consistent
if hdulist[ext].name != 'PRIMARY' and 'PRIMARY' in hdulist:
- metadata[PRIHDR_KEY] = dict(hdulist['PRIMARY'].header)
+ metadata[PRIHDR_KEY] = standardize_metadata(hdulist['PRIMARY'].header)
data = Spectrum1D(flux, wcs=wcs, meta=metadata)
@@ -241,7 +241,7 @@ def _parse_spectrum1d_3d(app, file_obj, data_label=None):
flux = np.moveaxis(flux, 1, 0)
- s1d = Spectrum1D(flux=flux, wcs=file_obj.wcs, meta=file_obj.meta)
+ s1d = Spectrum1D(flux=flux, wcs=file_obj.wcs, meta=standardize_metadata(file_obj.meta))
cur_data_label = f"{data_label}[{attr.upper()}]"
app.add_data(s1d, cur_data_label)
diff --git a/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.py b/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.py
index a8e368e731..f683217bfe 100644
--- a/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.py
+++ b/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.py
@@ -2,7 +2,7 @@
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import TemplateMixin, DatasetSelectMixin
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import PRIHDR_KEY, COMMENTCARD_KEY
__all__ = ['MetadataViewer']
@@ -13,6 +13,7 @@ class MetadataViewer(TemplateMixin, DatasetSelectMixin):
has_metadata = Bool(False).tag(sync=True)
has_primary = Bool(False).tag(sync=True)
show_primary = Bool(False).tag(sync=True)
+ has_comments = Bool(False).tag(sync=True)
metadata = List([]).tag(sync=True)
def __init__(self, *args, **kwargs):
@@ -24,6 +25,7 @@ def reset(self):
self.has_metadata = False
self.has_primary = False
self.show_primary = False
+ self.has_comments = False
self.metadata = []
@observe("dataset_selected")
@@ -38,9 +40,9 @@ def show_metadata(self, event):
self.has_primary = True
else:
self.has_primary = False
+ self.show_primary = False
- self.show_primary = False
- self.find_public_metadata(data.meta, primary_only=False)
+ self.find_public_metadata(data.meta, primary_only=self.show_primary)
@observe("show_primary")
def handle_show_primary(self, event):
@@ -61,8 +63,7 @@ def find_public_metadata(self, meta, primary_only=False):
if PRIHDR_KEY in meta:
meta = meta[PRIHDR_KEY]
else:
- self.metadata = []
- self.has_metadata = False
+ self.reset()
return
d = flatten_nested_dict(meta)
@@ -73,8 +74,23 @@ def find_public_metadata(self, meta, primary_only=False):
if badkey in d:
del d[badkey]
+ if COMMENTCARD_KEY in meta:
+ self.has_comments = True
+
+ def get_comment(key):
+ if key in meta[COMMENTCARD_KEY]._header:
+ val = meta[COMMENTCARD_KEY][key]
+ else:
+ val = ''
+ return val
+ else:
+ self.has_comments = False
+
+ def get_comment(key):
+ return ''
+
# TODO: Option to not sort?
- public_meta = sorted(zip(d.keys(), map(str, d.values())))
+ public_meta = sorted(zip(d.keys(), map(str, d.values()), map(get_comment, d.keys())))
if len(public_meta) > 0:
self.metadata = public_meta
self.has_metadata = True
diff --git a/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.vue b/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.vue
index c59495c35e..394c6050cb 100644
--- a/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.vue
+++ b/jdaviz/configs/default/plugins/metadata_viewer/metadata_viewer.vue
@@ -28,6 +28,7 @@
Key
Value
+ Comment
{{ item[0] }}
{{ item[1] }}
+ {{ item[2] }}
diff --git a/jdaviz/configs/default/plugins/metadata_viewer/tests/test_metadata_viewer.py b/jdaviz/configs/default/plugins/metadata_viewer/tests/test_metadata_viewer.py
index 0f6875a515..33ec26697e 100644
--- a/jdaviz/configs/default/plugins/metadata_viewer/tests/test_metadata_viewer.py
+++ b/jdaviz/configs/default/plugins/metadata_viewer/tests/test_metadata_viewer.py
@@ -1,56 +1,82 @@
import numpy as np
import pytest
+from astropy.io import fits
from astropy.nddata import NDData
from jdaviz.configs.default.plugins.metadata_viewer.metadata_viewer import MetadataViewer
-from jdaviz.utils import PRIHDR_KEY
def test_view_dict(imviz_helper):
mv = MetadataViewer(app=imviz_helper.app)
- arr = np.zeros((2, 2))
+ arr = np.zeros((2, 2), dtype=np.float32)
ndd_1 = NDData(arr, meta={
'EXTNAME': 'SCI', 'EXTVER': 1, 'BAR': 10.0, '_hidden': 'no show',
'HISTORY': 'Hmm', '': 'Invalid', 'FOO': '', 'COMMENT': 'a test', 'BOZO': None})
ndd_2 = NDData(arr, meta={
'EXTNAME': 'ASDF', 'REF': {'bar': 10.0, 'foo': {'1': '', '2': [1, 2]}}})
- ndd_3 = NDData(arr, meta={
- 'EXTVER': 1, 'EXTNAME': 'SCI',
- PRIHDR_KEY: {'EXTNAME': 'PRIMARY', 'APERTURE': '#TODO', 'COMMENT': 'a test'}})
+
+ # MEF
+ ndd_3 = fits.HDUList([fits.PrimaryHDU(), fits.ImageHDU(arr)])
+ ndd_3[1].name = 'DATA'
+ ndd_4 = fits.HDUList([fits.PrimaryHDU(), fits.ImageHDU(arr)])
+ ndd_4[0].header['APERTURE'] = ('#TODO', 'Aperture')
+ ndd_4[1].name = 'DATA'
+
imviz_helper.load_data(ndd_1, data_label='has_simple_meta')
imviz_helper.load_data(ndd_2, data_label='has_nested_meta')
imviz_helper.load_data(ndd_3, data_label='has_primary')
+ imviz_helper.load_data(ndd_4, data_label='has_primary_2')
imviz_helper.load_data(arr, data_label='no_meta')
assert mv.dataset.labels == ['has_simple_meta[DATA]', 'has_nested_meta[DATA]',
- 'has_primary[DATA]', 'no_meta']
+ 'has_primary[DATA,1]', 'has_primary_2[DATA,1]', 'no_meta']
mv.dataset_selected = 'has_simple_meta[DATA]'
assert not mv.has_primary
assert not mv.show_primary
+ assert not mv.has_comments
assert mv.has_metadata
assert mv.metadata == [
- ('BAR', '10.0'), ('BOZO', 'None'), ('EXTNAME', 'SCI'),
- ('EXTVER', '1'), ('FOO', '')], mv.metadata
+ ('BAR', '10.0', ''), ('BOZO', 'None', ''), ('EXTNAME', 'SCI', ''),
+ ('EXTVER', '1', ''), ('FOO', '', '')]
mv.dataset_selected = 'has_nested_meta[DATA]'
assert not mv.has_primary
assert not mv.show_primary
+ assert not mv.has_comments
assert mv.has_metadata
assert mv.metadata == [
- ('EXTNAME', 'ASDF'), ('REF.bar', '10.0'),
- ('REF.foo.1', ''), ('REF.foo.2.0', '1'), ('REF.foo.2.1', '2')], mv.metadata
+ ('EXTNAME', 'ASDF', ''), ('REF.bar', '10.0', ''),
+ ('REF.foo.1', '', ''), ('REF.foo.2.0', '1', ''), ('REF.foo.2.1', '2', '')]
- mv.dataset_selected = 'has_primary[DATA]'
+ mv.dataset_selected = 'has_primary[DATA,1]'
assert mv.has_primary
assert not mv.show_primary
+ assert mv.has_comments
assert mv.has_metadata
- assert mv.metadata == [('EXTNAME', 'SCI'), ('EXTVER', '1')]
+ assert mv.metadata == [('BITPIX', '-32', 'array data type'),
+ ('EXTNAME', 'DATA', 'extension name'),
+ ('GCOUNT', '1', 'number of groups'),
+ ('NAXIS', '2', 'number of array dimensions'),
+ ('NAXIS1', '2', ''), ('NAXIS2', '2', ''),
+ ('PCOUNT', '0', 'number of parameters'),
+ ('XTENSION', 'IMAGE', 'Image extension')]
mv.show_primary = True
- assert mv.metadata == [('APERTURE', '#TODO'), ('EXTNAME', 'PRIMARY')]
+ assert mv.metadata == [('BITPIX', '8', 'array data type'), ('EXTEND', 'True', ''),
+ ('NAXIS', '0', 'number of array dimensions'),
+ ('SIMPLE', 'True', 'conforms to FITS standard')]
+
+ mv.dataset_selected = 'has_primary_2[DATA,1]'
+ assert mv.show_primary # Make sure it sticks if possible
+ assert mv.has_comments
+ assert mv.metadata == [('APERTURE', '#TODO', 'Aperture'),
+ ('BITPIX', '8', 'array data type'), ('EXTEND', 'True', ''),
+ ('NAXIS', '0', 'number of array dimensions'),
+ ('SIMPLE', 'True', 'conforms to FITS standard')]
mv.dataset_selected = 'no_meta'
assert not mv.has_primary
assert not mv.show_primary
+ assert not mv.has_comments
assert not mv.has_metadata
assert mv.metadata == []
@@ -65,5 +91,6 @@ def test_view_invalid(imviz_helper):
assert mv.dataset_selected == ''
assert not mv.has_primary
assert not mv.show_primary
+ assert not mv.has_comments
assert not mv.has_metadata
assert mv.metadata == []
diff --git a/jdaviz/configs/imviz/plugins/parsers.py b/jdaviz/configs/imviz/plugins/parsers.py
index 56c291ec3f..f08a4115e0 100644
--- a/jdaviz/configs/imviz/plugins/parsers.py
+++ b/jdaviz/configs/imviz/plugins/parsers.py
@@ -12,7 +12,7 @@
from jdaviz.core.registries import data_parser_registry
from jdaviz.core.events import SnackbarMessage
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import standardize_metadata, PRIHDR_KEY
__all__ = ['parse_data']
@@ -230,7 +230,7 @@ def _jwst2data(file_obj, ext, data_label):
with AsdfInFits.open(file_obj) as af:
dm = af.tree
dm_meta = af.tree["meta"]
- data.meta.update(dm_meta)
+ data.meta.update(standardize_metadata(dm_meta))
if unit_attr in dm_meta:
bunit = _validate_bunit(dm_meta[unit_attr], raise_error=False)
@@ -287,8 +287,8 @@ def _hdu2data(hdu, data_label, hdulist, include_wcs=True):
data = Data(label=new_data_label)
if hdulist is not None and hdu.name != 'PRIMARY' and 'PRIMARY' in hdulist:
- data.meta[PRIHDR_KEY] = dict(hdulist['PRIMARY'].header)
- data.meta.update(dict(hdu.header))
+ data.meta[PRIHDR_KEY] = standardize_metadata(hdulist['PRIMARY'].header)
+ data.meta.update(standardize_metadata(hdu.header))
if include_wcs:
data.coords = WCS(hdu.header, hdulist)
component = Component.autotyped(hdu.data, units=bunit)
@@ -310,7 +310,7 @@ def _nddata_to_glue_data(ndd, data_label):
comp_label = attrib.upper()
cur_label = f'{data_label}[{comp_label}]'
cur_data = Data(label=cur_label)
- cur_data.meta.update(ndd.meta)
+ cur_data.meta.update(standardize_metadata(ndd.meta))
if ndd.wcs is not None:
cur_data.coords = ndd.wcs
raw_arr = arr
diff --git a/jdaviz/configs/mosviz/plugins/parsers.py b/jdaviz/configs/mosviz/plugins/parsers.py
index ee8bdf9e3a..ba2bc639cf 100644
--- a/jdaviz/configs/mosviz/plugins/parsers.py
+++ b/jdaviz/configs/mosviz/plugins/parsers.py
@@ -17,7 +17,7 @@
from jdaviz.configs.imviz.plugins.parsers import get_image_data_iterator
from jdaviz.core.registries import data_parser_registry
from jdaviz.core.events import SnackbarMessage
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import standardize_metadata, PRIHDR_KEY
__all__ = ['mos_spec1d_parser', 'mos_spec2d_parser', 'mos_image_parser']
@@ -223,9 +223,7 @@ def mos_spec1d_parser(app, data_obj, data_labels=None):
for cur_data, cur_label in zip(data_obj, data_labels):
# Make metadata layout conform with other viz.
- if 'header' in cur_data.meta:
- cur_data.meta.update(cur_data.meta['header'])
- del cur_data.meta['header']
+ cur_data.meta = standardize_metadata(cur_data.meta)
app.add_data(cur_data, cur_label, notify_done=False)
@@ -259,8 +257,8 @@ def _parse_as_spectrum1d(path):
data = hdulist[1].data
header = hdulist[1].header
wcs = WCS(header)
- metadata = dict(header)
- metadata[PRIHDR_KEY] = dict(hdulist[0].header)
+ metadata = standardize_metadata(header)
+ metadata[PRIHDR_KEY] = standardize_metadata(hdulist[0].header)
return Spectrum1D(data, wcs=wcs, meta=metadata)
# Coerce into list-like object
@@ -292,9 +290,7 @@ def _parse_as_spectrum1d(path):
data = _parse_as_spectrum1d(data)
# Make metadata layout conform with other viz.
- if 'header' in data.meta:
- data.meta.update(data.meta['header'])
- del data.meta['header']
+ data.meta = standardize_metadata(data.meta)
# Set the instrument
# TODO: this should not be set to nirspec for all datasets
@@ -500,10 +496,10 @@ def _get_source_identifiers_by_hdu(hdus, filepaths=None, header_keys=['SOURCEID'
# Fallback 1: filepath if only one is given
# Fallback 2: filepath at indx, if list of files given
# Fallback 3: If nothing else, just our fallback value
- src_name = \
- os.path.basename(filepaths) if type(filepaths) is str \
- else os.path.basename(filepaths[indx]) if type(filepaths) is list \
- else FALLBACK_NAME
+ src_name = (
+ os.path.basename(filepaths) if type(filepaths) is str
+ else os.path.basename(filepaths[indx]) if type(filepaths) is list
+ else FALLBACK_NAME)
src_names.append(src_name)
except Exception:
# Source ID lookup shouldn't ever prevent target from loading. Downgrade all errors to
@@ -639,7 +635,8 @@ def mos_niriss_parser(app, data_dir, obs_label=""):
if temp[sci].header["SPORDER"] == 1:
data = temp[sci].data
- meta = temp[sci].header
+ meta = standardize_metadata(temp[sci].header)
+ meta[PRIHDR_KEY] = standardize_metadata(temp[0].header)
# The wavelength is stored in a WAVELENGTH HDU. This is
# a 2D array, but in order to be able to use Spectrum1D
@@ -671,7 +668,7 @@ def mos_niriss_parser(app, data_dir, obs_label=""):
# TODO: Remove this once valid SRCTYPE values are present in all headers
for hdu in temp:
if ("SRCTYPE" in hdu.header and
- (hdu.header["SRCTYPE"] in ["POINT", "EXTENDED"])):
+ (hdu.header["SRCTYPE"] in ("POINT", "EXTENDED"))):
pass
else:
hdu.header["SRCTYPE"] = "EXTENDED"
@@ -686,9 +683,7 @@ def mos_niriss_parser(app, data_dir, obs_label=""):
for spec in specs:
# Make metadata layout conform with other viz.
- if 'header' in spec.meta:
- spec.meta.update(spec.meta['header'])
- del spec.meta['header']
+ spec.meta = standardize_metadata(spec.meta)
if spec.meta['SPORDER'] == 1 and spec.meta['EXTNAME'] == "EXTRACT1D":
label = f"{filter_name} Source {spec.meta['SOURCEID']} spec1d {orientation}"
diff --git a/jdaviz/configs/mosviz/tests/test_parsers.py b/jdaviz/configs/mosviz/tests/test_parsers.py
index b8ede93f58..d48d8c5d75 100644
--- a/jdaviz/configs/mosviz/tests/test_parsers.py
+++ b/jdaviz/configs/mosviz/tests/test_parsers.py
@@ -4,7 +4,7 @@
import pytest
from astropy.utils.data import download_file
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import PRIHDR_KEY, COMMENTCARD_KEY
# This is another version of test_niriss_loader in test_data_loading.py
@@ -27,16 +27,19 @@ def test_niriss_parser(mosviz_helper, tmpdir):
dc_0 = mosviz_helper.app.data_collection[0]
assert dc_0.label == "Image canucs F150W"
assert PRIHDR_KEY not in dc_0.meta
+ assert COMMENTCARD_KEY not in dc_0.meta
assert dc_0.meta['bunit_data'] == 'MJy/sr' # ASDF metadata
dc_1 = mosviz_helper.app.data_collection[1]
assert dc_1.label == 'F150W Source 1 spec2d C'
- assert PRIHDR_KEY not in dc_1.meta
+ assert PRIHDR_KEY in dc_1.meta
+ assert COMMENTCARD_KEY in dc_1.meta
assert dc_1.meta['SOURCEID'] == 1
dc_40 = mosviz_helper.app.data_collection[40]
assert dc_40.label == 'F150W Source 1 spec1d C'
assert PRIHDR_KEY not in dc_40.meta
+ assert COMMENTCARD_KEY in dc_40.meta
assert 'header' not in dc_40.meta
assert dc_40.meta['FILTER'] == 'GR150C'
diff --git a/jdaviz/configs/specviz/plugins/parsers.py b/jdaviz/configs/specviz/plugins/parsers.py
index 2315ea7a39..cf58384d75 100644
--- a/jdaviz/configs/specviz/plugins/parsers.py
+++ b/jdaviz/configs/specviz/plugins/parsers.py
@@ -8,6 +8,7 @@
from specutils import Spectrum1D, SpectrumList, SpectrumCollection
from jdaviz.core.registries import data_parser_registry
+from jdaviz.utils import standardize_metadata
__all__ = ["specviz_spectrum1d_parser"]
@@ -99,9 +100,7 @@ def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_v
spectral_axis=spec.spectral_axis.to(current_unit))
# Make metadata layout conform with other viz.
- if 'header' in spec.meta:
- spec.meta.update(spec.meta['header'])
- del spec.meta['header']
+ spec.meta = standardize_metadata(spec.meta)
app.add_data(spec, data_label[i])
@@ -136,9 +135,7 @@ def specviz_spectrum1d_parser(app, data, data_label=None, format=None, show_in_v
uncertainty=unc)
# Make metadata layout conform with other viz.
- if 'header' in spec.meta:
- spec.meta.update(spec.meta['header'])
- del spec.meta['header']
+ spec.meta = standardize_metadata(spec.meta)
# needs perhaps a better way to label the combined spectrum
label = "Combined " + data_label[0]
diff --git a/jdaviz/configs/specviz2d/plugins/parsers.py b/jdaviz/configs/specviz2d/plugins/parsers.py
index 5a04b2f418..df99a7fcfc 100644
--- a/jdaviz/configs/specviz2d/plugins/parsers.py
+++ b/jdaviz/configs/specviz2d/plugins/parsers.py
@@ -6,7 +6,7 @@
import numpy as np
from jdaviz.core.registries import data_parser_registry
-from jdaviz.utils import PRIHDR_KEY
+from jdaviz.utils import standardize_metadata, PRIHDR_KEY
__all__ = ['spec2d_1d_parser']
@@ -59,8 +59,8 @@ def spec2d_1d_parser(app, data_obj, data_label=None, show_in_viewer=True):
# we use it here as well, even though the actual unit is pixels
spectral_axis = np.arange(1, flux.size + 1, 1) * u.m
- metadata = dict(header)
- metadata[PRIHDR_KEY] = dict(prihdr)
+ metadata = standardize_metadata(header)
+ metadata[PRIHDR_KEY] = standardize_metadata(prihdr)
data_obj = Spectrum1D(flux, spectral_axis=spectral_axis, meta=metadata)
diff --git a/jdaviz/tests/test_metadata.py b/jdaviz/tests/test_metadata.py
new file mode 100644
index 0000000000..ef618a6939
--- /dev/null
+++ b/jdaviz/tests/test_metadata.py
@@ -0,0 +1,52 @@
+import pytest
+from astropy.io import fits
+
+from jdaviz.utils import standardize_metadata, COMMENTCARD_KEY
+
+
+def test_metadata_plain_dict():
+ metadata = {'a': 1, 'b': 2, 'c': {'d': 42}}
+ out_meta = standardize_metadata(metadata)
+ assert out_meta == metadata
+
+ # Make sure input is unchanged.
+ del out_meta['c']
+ assert out_meta == {'a': 1, 'b': 2}
+ assert 'c' in metadata
+
+
+def test_metadata_nested_fits_header():
+ hdu = fits.PrimaryHDU()
+ metadata = {'a': 1, 'header': hdu.header}
+ out_meta = standardize_metadata(metadata)
+ assert 'header' not in out_meta
+ assert sorted(out_meta.keys()) == ['BITPIX', 'EXTEND', 'NAXIS', 'SIMPLE', COMMENTCARD_KEY, 'a']
+ assert out_meta[COMMENTCARD_KEY]['BITPIX'] == 'array data type'
+ assert 'BITPIX' in out_meta[COMMENTCARD_KEY]._header
+ assert 'a' not in out_meta[COMMENTCARD_KEY]._header
+
+ # Make sure input is unchanged.
+ assert 'header' in metadata
+
+
+def test_metadata_fits_header():
+ hdu = fits.PrimaryHDU()
+ out_meta = standardize_metadata(hdu.header)
+ assert sorted(out_meta.keys()) == ['BITPIX', 'EXTEND', 'NAXIS', 'SIMPLE', COMMENTCARD_KEY]
+ assert out_meta[COMMENTCARD_KEY]['BITPIX'] == 'array data type'
+ assert 'BITPIX' in out_meta[COMMENTCARD_KEY]._header
+
+ # Make sure input is unchanged.
+ del out_meta['BITPIX']
+ assert 'BITPIX' in hdu.header
+
+ # Make sure you can still nest it afterwards if you want.
+ # Similar logic is used to separate primary header in Metadata Viewer plugin.
+ hdu_2 = fits.ImageHDU()
+ out_meta['image_meta'] = standardize_metadata(hdu_2.header)
+ assert out_meta['image_meta']['XTENSION'] == 'IMAGE'
+
+
+def test_metadata_invalid():
+ with pytest.raises(TypeError, match='metadata must be dictionary or FITS header'):
+ standardize_metadata([1, 2, 3])
diff --git a/jdaviz/utils.py b/jdaviz/utils.py
index 5a3521f716..741bab6077 100644
--- a/jdaviz/utils.py
+++ b/jdaviz/utils.py
@@ -1,13 +1,17 @@
+import os
import time
import threading
from collections import deque
-import os
+
+from astropy.io import fits
from ipyvue import watch
+
__all__ = []
# For Metadata Viewer plugin internal use only.
PRIHDR_KEY = '_primary_header'
+COMMENTCARD_KEY = '_fits_comment_card'
class SnackbarQueue:
@@ -126,3 +130,24 @@ def bqplot_clear_figure(fig):
fig.marks = []
fig.axes = []
setattr(fig, 'axis_registry', {})
+
+
+def standardize_metadata(metadata):
+ """Standardize given metadata so it can be viewed in
+ Metadata Viewer plugin. The input can be plain
+ dictionary or FITS header object. Output is just a plain
+ dictionary.
+ """
+ if isinstance(metadata, fits.Header):
+ out_meta = dict(metadata)
+ out_meta[COMMENTCARD_KEY] = metadata.comments
+ elif isinstance(metadata, dict):
+ out_meta = metadata.copy()
+ # specutils nests it but we do not want nesting
+ if 'header' in metadata and isinstance(metadata['header'], fits.Header):
+ out_meta.update(standardize_metadata(metadata['header']))
+ del out_meta['header']
+ else:
+ raise TypeError('metadata must be dictionary or FITS header')
+
+ return out_meta