Source code for jdaviz.configs.default.plugins.metadata_viewer.metadata_viewer
from traitlets import Any, Bool, List, observe
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin, DatasetSelectMixin
from jdaviz.core.user_api import PluginUserApi
from jdaviz.utils import PRIHDR_KEY, COMMENTCARD_KEY
__all__ = ['MetadataViewer']
[docs]
@tray_registry('g-metadata-viewer', label="Metadata")
class MetadataViewer(PluginTemplateMixin, DatasetSelectMixin):
"""
See the :ref:`Metadata Viewer Plugin Documentation <imviz_metadata-viewer>` for more details.
Only the following attributes and methods are available through the
:ref:`public plugin API <plugin-apis>`:
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray`
* ``dataset`` (:class:`~jdaviz.core.template_mixin.DatasetSelect`):
Dataset to expose the metadata.
* :attr:`show_primary`:
Whether to show MEF primary header metadata instead.
* :attr:`metadata`:
Read-only metadata. If the data is loaded from a multi-extension FITS file,
this can be the extension header or the primary header, depending on
``show_primary`` setting.
"""
template_file = __file__, "metadata_viewer.vue"
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)
metadata_filter = Any().tag(sync=True) # string or None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# override the default filters on dataset entries to require metadata in entries
self.dataset.add_filter('not_from_plugin')
@property
def user_api(self):
return PluginUserApi(self, expose=('dataset', 'show_primary'), readonly=('metadata',))
[docs]
def reset(self):
self.has_metadata = False
self.has_primary = False
self.show_primary = False
self.has_comments = False
self.metadata = []
@observe("dataset_selected")
def show_metadata(self, event):
if not hasattr(self, 'dataset'): # pragma: no cover
# plugin not fully initialized
return
data = self.dataset.selected_dc_item
if (data is None or not hasattr(data, 'meta') or not isinstance(data.meta, dict)
or len(data.meta) < 1):
self.reset()
return
if PRIHDR_KEY in data.meta:
self.has_primary = True
else:
self.has_primary = False
self.show_primary = False
self.find_public_metadata(data.meta, primary_only=self.show_primary)
@observe("show_primary")
def handle_show_primary(self, event):
if not self.show_primary:
self.show_metadata(event)
return
data = self.dataset.selected_dc_item
if (data is None or not hasattr(data, 'meta') or not isinstance(data.meta, dict)
or len(data.meta) < 1):
self.reset()
return
self.find_public_metadata(data.meta, primary_only=True)
[docs]
def find_public_metadata(self, meta, primary_only=False):
if primary_only:
if PRIHDR_KEY in meta:
meta = meta[PRIHDR_KEY]
else:
self.reset()
return
d = flatten_nested_dict(meta)
# Some FITS keywords cause "# ipykernel cannot clean for JSON" messages.
# Also, we want to hide internal metadata that starts with underscore.
badkeys = ['COMMENT', 'HISTORY', ''] + [k for k in d if k.startswith('_')]
for badkey in badkeys:
if badkey in d:
del d[badkey]
if COMMENTCARD_KEY in meta:
has_comments = True
def get_comment(key):
if key in meta[COMMENTCARD_KEY]._header:
val = meta[COMMENTCARD_KEY][key]
else:
val = ''
return val
else:
has_comments = False
def get_comment(key):
return ''
# TODO: Option to not sort?
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
self.has_comments = has_comments
else:
self.reset()
# TODO: If this generalized in stdatamodels in the future, replace with native function.
# See https://github.com/spacetelescope/stdatamodels/issues/131
# This code below is taken code from stdatamodels/model_base.py, and the method to_flat_dict()
def flatten_nested_dict(asdfnode, include_arrays=True):
"""
Returns a dictionary of all of the schema items as a flat dictionary.
Each dictionary key is a dot-separated name. For example, the
schema element ``meta.observation.date`` at the root node
will end up in the dictionary as::
{ "meta.observation.date": "2012-04-22T03:22:05.432" }
"""
import datetime
import numpy as np
from astropy.time import Time
def convert_val(val):
if isinstance(val, datetime.datetime): # pragma: no cover
return val.isoformat()
elif isinstance(val, Time): # pragma: no cover
return str(val)
return val
if include_arrays:
return dict((key, convert_val(val)) for (key, val) in _iteritems(asdfnode))
else: # pragma: no cover
return dict((key, convert_val(val)) for (key, val) in _iteritems(asdfnode)
if not isinstance(val, np.ndarray))
def _iteritems(asdfnode):
"""
Iterates over all of the schema items in a flat way.
Each element is a pair (`key`, `value`). Each `key` is a
dot-separated name. For example, the schema element
`meta.observation.date` will end up in the result as::
("meta.observation.date": "2012-04-22T03:22:05.432")
"""
def recurse(asdfnode, path=[]):
if isinstance(asdfnode, dict):
for key, val in asdfnode.items():
for x in recurse(val, path + [key]):
yield x
elif isinstance(asdfnode, (list, tuple)):
for i, val in enumerate(asdfnode):
for x in recurse(val, path + [i]):
yield x
else:
yield ('.'.join(str(x) for x in path), asdfnode)
for x in recurse(asdfnode):
yield x