Source code for sunpy.io._jp2

"""
This module provides a JPEG 2000 file reader for internal use.

It was developed to read JPEG 2000 files created by the Helioviewer Project
and not as a general JPEG 2000 file reader.
"""
import os

# We have to use lxml as lxml can not  serialize xml from the standard library
import lxml.etree as ET
import numpy as np

from sunpy.io.header import FileHeader
from sunpy.util.io import HDPair, string_is_float

__all__ = ['read', 'get_header', 'write']


def _sanative_value(value):
    if value is None:
        return
    if value.isdigit() or value.isnumeric():
        value = int(value)
    elif string_is_float(value):
        value = float(value)
    # We replace newlines with spaces so when we join the history
    # it won't be so ugly
    elif "\n" in value:
        value = value.replace("\n", " ")
    return value

def _parse_xml_metadata(xml_data):
    keycomments = {}
    final_dict = {}
    history = []
    for node in xml_data.xml.getroot().iter():
        if node.tag in ['HISTORY']:
            history.append(node.attrib.get('comment', ''))
            continue
        if node.text is not None:
            final_dict[node.tag] = _sanative_value(node.text)
            keycomments[node.tag] = node.attrib.get('comment', '')
    return {**final_dict, "HISTORY": "".join(history), 'KEYCOMMENTS': keycomments}

[docs] def read(filepath, **kwargs): """ Reads a JPEG2000 file. Parameters ---------- filepath : `str` The file to be read. **kwargs : `dict` Unused. Returns ------- `list` A list of (data, header) tuples. """ # Put import here to speed up sunpy.io import time from glymur import Jp2k header = get_header(filepath) data = Jp2k(filepath)[...][::-1] return [HDPair(data, header[0])]
[docs] def get_header(filepath): """ Reads the header from the file. Parameters ---------- filepath : `str` The file to be read. Returns ------- `list` A list of one header read from the file. """ # Put import here to speed up sunpy.io import time from glymur import Jp2k jp2 = Jp2k(filepath) # We assume that the header is the first XMLBox in the file xml_box = [box for box in jp2.box if box.box_id == 'xml '][0] pydict = _parse_xml_metadata(xml_box) return [FileHeader(pydict)]
def header_to_xml(header): """ Converts image header metadata into an XML Tree that can be inserted into a JPEG2000 file header. Parameters ---------- header : `MetaDict` A header dictionary to convert to xml. Returns ---------- `lxml.etree._Element` A fits element where each child is an xml element in the form <key>value</key> derived from the key/value pairs in the given header dictionary """ fits = ET.Element("fits") already_added = set() for key in header: # Some headers span multiple lines and get duplicated as keys # header.get will appropriately return all data, so if we see # a key again, we can assume it was already added to the xml tree. if (key in already_added): continue # Add to the set so we don't duplicate entries already_added.add(key) el = ET.SubElement(fits, key) data = header.get(key) if isinstance(data, bool): data = "1" if data else "0" else: data = str(data) el.text = data return fits def generate_jp2_xmlbox(header): """ Generates the JPEG2000 XML box to be inserted into the JPEG2000 file. Parameters ---------- header : `MetaDict` A header dictionary. Returns ---------- `XMLBox` XML box containing FITS metadata to be used in JPEG2000 headers """ from glymur import jp2box header_xml = header_to_xml(header) meta = ET.Element("meta") meta.append(header_xml) tree = ET.ElementTree(meta) return jp2box.XMLBox(xml=tree)
[docs] def write(fname, data, header, **kwargs): """ Take a data header pair and write a JPEG2000 file. Parameters ---------- fname : `str` File name, with extension. data : `numpy.ndarray` N-Dimensional data array. header : `dict` A header dictionary. kwargs : Additional keyword args are passed to glymur's Jp2k constructor. Notes ----- Saving as a JPEG2000 will cast the data array to uint8 values to support the JPEG2000 format. """ from glymur import Jp2k tmp_filename = f"{fname}tmp.jp2" jp2_data = np.uint8(data) # The jp2 data is flipped when read in, so we have to flip it back before # saving. See https://github.com/sunpy/sunpy/pull/768 for context. flipped = np.flip(jp2_data, 0) jp2 = Jp2k(tmp_filename, flipped, **kwargs) # Append the XML data to the header information stored in jp2.box meta_boxes = jp2.box target_index = len(meta_boxes) - 1 fits_box = generate_jp2_xmlbox(header) meta_boxes.insert(target_index, fits_box) # Rewrites the jp2 file on disk with the xml data in the header jp2.wrap(fname, boxes=meta_boxes) os.remove(tmp_filename)