Ejemplo n.º 1
0
    def __init__(self,
                 file_name: str,
                 reverse_axes: Union[None, int, Sequence[int]] = None,
                 transpose_axes: Optional[Tuple[int, ...]] = None):
        """

        Parameters
        ----------
        file_name : str
            file name for a NITF file containing a complex SICD
        reverse_axes : None|Sequence[int]
            Any entries should be restricted to `{0, 1}`. The presence of
            `0` means to reverse the rows (in the raw sense), and the presence
            of `1` means to reverse the columns (in the raw sense).
        transpose_axes : None|Tuple[int, ...]
            If presented this should be only `(1, 0)`.
        """

        self._reverse_axes = reverse_axes
        self._transpose_axes = transpose_axes
        self._segment_status = None
        self._sicd_meta = None
        self._segment_bands = None
        NITFDetails.__init__(self, file_name)
        self._find_complex_image_segments()
        if len(self.sicd_meta) == 0:
            raise SarpyIOError(
                'No complex valued image segments found in file {}'.format(
                    file_name))
Ejemplo n.º 2
0
    def __init__(self, file_object: Union[str, BinaryIO]):
        """

        Parameters
        ----------
        file_object : str|BinaryIO
            file name or file like object for a NITF 2.1 or 2.0 containing a SICD.
        """

        self._des_index = None
        self._des_header = None
        self._img_headers = None
        self._is_sicd = False
        self._sicd_meta = None
        NITFDetails.__init__(self, file_object)

        if self._nitf_header.ImageSegments.subhead_sizes.size == 0:
            raise SarpyIOError('There are no image segments defined.')
        if self._nitf_header.GraphicsSegments.item_sizes.size > 0:
            raise SarpyIOError('A SICD file does not allow for graphics segments.')
        if self._nitf_header.DataExtensions.subhead_sizes.size == 0:
            raise SarpyIOError(
                'A SICD file requires at least one data extension, containing the '
                'SICD xml structure.')

        # define the sicd metadata
        self._find_sicd()
        if not self.is_sicd:
            raise SarpyIOError('Could not find the SICD XML des.')
Ejemplo n.º 3
0
def check_sicd_file(nitf_details):
    """
    Check the validity of the given NITF file as a SICD file.

    Parameters
    ----------
    nitf_details : str|NITFDetails
        The path to the NITF file, or a `NITFDetails` object.

    Returns
    -------
    bool
    """

    def check_data_extension_headers():
        # type: () -> (str, Union[DataExtensionHeader, DataExtensionHeader0])
        sicd_des = []

        for i in range(nitf_details.des_subheader_offsets.size):
            subhead_bytes = nitf_details.get_des_subheader_bytes(i)
            des_bytes = None
            if subhead_bytes.startswith(b'DEXML_DATA_CONTENT'):
                des_bytes = nitf_details.get_des_bytes(i)

            elif subhead_bytes.startswith(b'DESIDD_XML'):
                raise ValueError(
                    'This file contains an old format SIDD DES, and should be a SIDD file')
            elif subhead_bytes.startswith(b'DESICD_XML'):
                des_bytes = nitf_details.get_des_bytes(i)

            if des_bytes is None:
                continue

            # compare the SICD structure and the des header structure
            if nitf_details.nitf_version == '02.00':
                des_header = DataExtensionHeader0.from_bytes(subhead_bytes, start=0)
            elif nitf_details.nitf_version == '02.10':
                des_header = DataExtensionHeader.from_bytes(subhead_bytes, start=0)
            else:
                raise ValueError('Got unhandled NITF version {}'.format(nitf_details.nitf_version))

            try:
                des_string = des_bytes.decode('utf-8').strip()
                root_node, xml_ns = parse_xml_from_string(des_string)
                # namespace makes this ugly
                if 'SIDD' in root_node.tag:
                    raise ValueError(
                        'This file contains a SIDD DES, and should be a SIDD file')
                elif 'SICD' in root_node.tag:
                    sicd_des.append((i, des_string, des_header))
            except Exception as e:
                logger.error('Failed parsing the xml DES entry {} as xml'.format(i))
                raise e

        if len(sicd_des) == 0:
            raise ValueError('No SICD DES values found, so this is not a viable SICD file')
        elif len(sicd_des) > 1:
            raise ValueError(
                'Multiple SICD DES values found at indices {},\n'
                'so this is not a viable SICD file'.format([entry[0] for entry in sicd_des]))

        return sicd_des[0][1], sicd_des[0][2]

    def check_image_data():
        # type: () -> bool

        # get pixel type
        pixel_type = the_sicd.ImageData.PixelType
        if pixel_type == 'RE32F_IM32F':
            exp_nbpp = 32
            exp_pvtype = 'R'
        elif pixel_type == 'RE16I_IM16I':
            exp_nbpp = 16
            exp_pvtype = 'SI'
        elif pixel_type == 'AMP8I_PHS8I':
            exp_nbpp = 8
            exp_pvtype = 'INT'
        else:
            raise ValueError('Got unexpected pixel type {}'.format(pixel_type))

        valid_images = True
        # verify that all images have the correct pixel type
        for i, img_header in enumerate(nitf_details.img_headers):
            if img_header.ICAT.strip() != 'SAR':
                valid_images = False
                logger.error(
                    'SICD: image segment at index {} of {} has ICAT = `{}`,\n\texpected to be `SAR`'.format(
                        i, len(nitf_details.img_headers), img_header.ICAT.strip()))

            if img_header.PVTYPE.strip() != exp_pvtype:
                valid_images = False
                logger.error(
                    'SICD: image segment at index {} of {} has PVTYPE = `{}`,\n\t'
                    'expected to be `{}` based on pixel type {}'.format(
                        i, len(nitf_details.img_headers), img_header.PVTYPE.strip(), exp_pvtype, pixel_type))
            if img_header.NBPP != exp_nbpp:
                valid_images = False
                logger.error(
                    'SICD: image segment at index {} of {} has NBPP = `{}`,\n\t'
                    'expected to be `{}` based on pixel type {}'.format(
                        i, len(nitf_details.img_headers), img_header.NBPP, exp_nbpp, pixel_type))

            if len(img_header.Bands) != 2:
                valid_images = False
                logger.error('SICD: image segment at index {} of {} does not have two (I/Q or M/P) bands'.format(
                    i, len(nitf_details.img_headers)))
                continue

            if pixel_type == 'AMP8I_PHS8I':
                if img_header.Bands[0].ISUBCAT.strip() != 'M' and img_header.Bands[1].ISUBCAT.strip() != 'P':
                    valid_images = False
                    logger.error(
                        'SICD: pixel_type is {}, image segment at index {} of {}\n\t'
                        'has bands with ISUBCAT {}, expected ("M", "P")'.format(
                            pixel_type, i, len(nitf_details.img_headers),
                            (img_header.Bands[0].ISUBCAT.strip(), img_header.Bands[1].ISUBCAT.strip())))
            else:
                if img_header.Bands[0].ISUBCAT.strip() != 'I' and img_header.Bands[1].ISUBCAT.strip() != 'Q':
                    valid_images = False
                    logger.error(
                        'SICD: pixel_type is {}, image segment at index {} of {}\n\t'
                        'has bands with ISUBCAT {}, expected ("I", "Q")'.format(
                            pixel_type, i, len(nitf_details.img_headers),
                            (img_header.Bands[0].ISUBCAT.strip(), img_header.Bands[1].ISUBCAT.strip())))

        return valid_images

    if isinstance(nitf_details, string_types):
        if not os.path.isfile(nitf_details):
            raise ValueError('Got string input, but it is not a valid path')
        nitf_details = NITFDetails(nitf_details)

    if not isinstance(nitf_details, NITFDetails):
        raise TypeError(
            'Input is expected to be a path to a NITF file, or a NITFDetails object instance')

    # find the sicd header
    sicd_xml_string, des_header = check_data_extension_headers()
    # check that the sicd and header are valid
    valid_sicd_des, the_sicd = check_sicd_data_extension(nitf_details, des_header, sicd_xml_string)
    # check that the image segments all make sense compared to the sicd structure
    valid_img = check_image_data()
    all_valid = valid_sicd_des & valid_img

    if valid_img:
        try:
            reader = SICDReader(nitf_details.file_name)
        except Exception as e:
            logger.exception(
                'SICD: All image segments appear viable for the SICD,\n\t'
                'but SICDReader construction failed')
    return all_valid
Ejemplo n.º 4
0
def check_sidd_file(nitf_details):
    """
    Check the validity of the given NITF file as a SICD file.

    Parameters
    ----------
    nitf_details : str|NITFDetails

    Returns
    -------
    bool
    """
    def find_des():
        for i in range(nitf_details.des_subheader_offsets.size):
            subhead_bytes = nitf_details.get_des_subheader_bytes(i)
            if nitf_details.nitf_version == '02.00':
                des_header = DataExtensionHeader0.from_bytes(subhead_bytes,
                                                             start=0)
            elif nitf_details.nitf_version == '02.10':
                des_header = DataExtensionHeader.from_bytes(subhead_bytes,
                                                            start=0)
            else:
                raise ValueError('Got unhandled NITF version {}'.format(
                    nitf_details.nitf_version))

            if subhead_bytes.startswith(b'DEXML_DATA_CONTENT'):
                des_bytes = nitf_details.get_des_bytes(i).decode(
                    'utf-8').strip().encode()
                # noinspection PyBroadException
                try:
                    root_node, xml_ns = parse_xml_from_string(des_bytes)
                    if 'SIDD' in root_node.tag:  # namespace makes this ugly
                        sidd_des.append(
                            (i, des_bytes, root_node, xml_ns, des_header))
                    elif 'SICD' in root_node.tag:
                        sicd_des.append(
                            (i, des_bytes, root_node, xml_ns, des_header))
                except Exception:
                    continue
            elif subhead_bytes.startswith(b'DESIDD_XML'):
                # This is an old format SIDD
                des_bytes = nitf_details.get_des_bytes(i).decode(
                    'utf-8').strip().encode()
                try:
                    root_node, xml_ns = parse_xml_from_string(des_bytes)
                    if 'SIDD' in root_node.tag:  # namespace makes this ugly
                        sidd_des.append(
                            (i, des_bytes, root_node, xml_ns, des_header))
                except Exception as e:
                    logger.exception(
                        'SIDD: Old-style SIDD DES header at index {}, but failed parsing'
                        .format(i))
                    continue
            elif subhead_bytes.startswith(b'DESICD_XML'):
                # This is an old format SICD
                des_bytes = nitf_details.get_des_bytes(i).decode(
                    'utf-8').strip().encode()
                try:
                    root_node, xml_ns = parse_xml_from_string(des_bytes)
                    if 'SICD' in root_node.tag:  # namespace makes this ugly
                        sicd_des.append(
                            (i, des_bytes, root_node, xml_ns, des_header))
                except Exception as e:
                    logger.exception(
                        'SIDD: Old-style SICD DES header at index {}, but failed parsing'
                        .format(i))
                    continue

    def check_image_data():
        valid_images = True
        # verify that all images have the correct pixel type
        for i, img_header in enumerate(nitf_details.img_headers):
            if img_header.ICAT.strip() != 'SAR':
                continue

            iid1 = img_header.IID1.strip()
            if re.match(r'^SIDD\d\d\d\d\d\d', iid1) is None:
                valid_images = False
                logger.error(
                    'SIDD: image segment at index {} of {} has IID1 = `{}`,\n\t'
                    'expected to be of the form `SIDDXXXYYY`'.format(
                        i, len(nitf_details.img_headers), iid1))
                continue

            sidd_index = int(iid1[4:7])
            if not (0 < sidd_index <= len(sidd_des)):
                valid_images = False
                logger.error(
                    'SIDD: image segment at index {} of {} has IID1 = `{}`,\n\t'
                    'it is unclear with which of the {} SIDDs '
                    'this is associated'.format(i,
                                                len(nitf_details.img_headers),
                                                iid1, len(sidd_des)))
                continue

            has_image[sidd_index - 1] = True

            type_information = sidd_nitf_details[sidd_index - 1]
            pixel_type = type_information['pixel_type']
            if pixel_type is None:
                continue  # we already noted the failure here

            exp_nbpp, exp_pvtype = type_information['nbpp'], type_information[
                'pvtype']
            if img_header.PVTYPE.strip() != exp_pvtype:
                valid_images = False
                logger.error(
                    'SIDD: image segment at index {} of {} has PVTYPE = `{}`,\n\t'
                    'expected to be `{}` based on pixel type {}'.format(
                        i, len(nitf_details.img_headers),
                        img_header.PVTYPE.strip(), exp_pvtype, pixel_type))
            if img_header.NBPP != exp_nbpp:
                valid_images = False
                logger.error(
                    'SIDD: image segment at index {} of {} has NBPP = `{}`,\n\t'
                    'expected to be `{}` based on pixel type {}'.format(
                        i, len(nitf_details.img_headers), img_header.NBPP,
                        exp_nbpp, pixel_type))

        for sidd_index, entry in enumerate(has_image):
            if not entry:
                logger.error(
                    'SIDD: No image segments appear to be associated with the sidd at index {}'
                    .format(sidd_index))
                valid_images = False

        return valid_images

    if isinstance(nitf_details, str):
        if not os.path.isfile(nitf_details):
            raise ValueError('Got string input, but it is not a valid path')
        nitf_details = NITFDetails(nitf_details)

    if not isinstance(nitf_details, NITFDetails):
        raise TypeError(
            'Input is expected to be a path to a NITF file, or a NITFDetails object instance'
        )

    sidd_des = []
    sicd_des = []
    sidd_nitf_details = []
    has_image = []

    find_des()
    if len(sidd_des) < 1:
        logger.error('SIDD: No SIDD DES found, this is not a valid SIDD file.')
        return False

    valid_sidd_des = True
    for entry in sidd_des:
        this_sidd_valid, the_sidd = check_sidd_data_extension(
            nitf_details, entry[4], entry[1])
        valid_sidd_des &= this_sidd_valid
        has_image.append(False)
        if the_sidd.Display is None or the_sidd.Display.PixelType is None:
            valid_sidd_des = False
            logger.error(
                'SIDD: SIDD.Display.PixelType is not populated, and can not be compared to NITF image details'
            )
            sidd_nitf_details.append({'pixel_type': None})
        elif the_sidd.Display.PixelType in ['MONO8I', 'MONO8LU', 'RGB8LU']:
            sidd_nitf_details.append({
                'nbpp': 8,
                'pvtype': 'INT',
                'pixel_type': the_sidd.Display.PixelType
            })
        elif the_sidd.Display.PixelType == 'MONO16I':
            sidd_nitf_details.append({
                'nbpp': 16,
                'pvtype': 'INT',
                'pixel_type': the_sidd.Display.PixelType
            })
        elif the_sidd.Display.PixelType == 'RGB24I':
            sidd_nitf_details.append({
                'nbpp': 24,
                'pvtype': 'INT',
                'pixel_type': the_sidd.Display.PixelType
            })
        else:
            raise ValueError('Got unhandled pixel type {}'.format(
                the_sidd.Display.PixelType))

    valid_sicd_des = True
    for entry in sicd_des:
        this_sicd_valid, _ = check_sicd_data_extension(nitf_details, entry[4],
                                                       entry[1])
        valid_sicd_des &= this_sicd_valid
    valid_image = check_image_data()

    return valid_sidd_des & valid_sicd_des & valid_image
Ejemplo n.º 5
0
def generic_nitf_header_test(instance, test_file):
    assert isinstance(instance, unittest.TestCase)

    # can we parse it at all? how long does it take?
    with instance.subTest(msg="header parsing"):
        start = time.time()
        details = NITFDetails(test_file)
        # how long does it take?
        logging.info('unpacked nitf details in {}'.format(time.time() - start))
        # how does it look?
        logging.debug(details.nitf_header)

    # is the output as long as it should be?
    with instance.subTest(msg="header length match"):
        header_string = details.nitf_header.to_bytes()
        equality = (len(header_string) == details.nitf_header.HL)
        if not equality:
            logging.error(
                'len(produced header) = {}, nitf_header.HL = {}'.format(
                    len(header_string), details.nitf_header.HL))
        instance.assertTrue(equality)

    # is the output what it should be?
    with instance.subTest(msg="header content match"):
        with open(test_file, 'rb') as fi:
            file_header = fi.read(details.nitf_header.HL)

        equality = (file_header == header_string)
        if not equality:
            chunk_size = 80
            start_chunk = 0
            while start_chunk < len(header_string):
                end_chunk = min(start_chunk + chunk_size, len(header_string))
                logging.error('real[{}:{}] = {}'.format(
                    start_chunk, end_chunk,
                    file_header[start_chunk:end_chunk]))
                logging.error('prod[{}:{}] = {}'.format(
                    start_chunk, end_chunk,
                    header_string[start_chunk:end_chunk]))
                start_chunk = end_chunk
        instance.assertTrue(equality)

    # is each image subheader working?
    if details.img_segment_offsets is not None:
        for i in range(details.img_segment_offsets.size):
            with instance.subTest('image subheader {} match'.format(i)):
                img_bytes = details.get_image_subheader_bytes(i)
                img_sub = ImageSegmentHeader.from_bytes(img_bytes, start=0)
                instance.assertEqual(len(img_bytes),
                                     img_sub.get_bytes_length(),
                                     msg='image subheader as long as expected')
                instance.assertEqual(
                    img_bytes,
                    img_sub.to_bytes(),
                    msg=
                    'image subheader serializes and deserializes as expected')

    # is each text segment working?
    if details.text_segment_offsets is not None:
        for i in range(details.text_segment_offsets.size):
            with instance.subTest('text subheader {} match'.format(i)):
                txt_bytes = details.get_text_subheader_bytes(i)
                txt_sub = TextSegmentHeader.from_bytes(txt_bytes, start=0)
                instance.assertEqual(len(txt_bytes),
                                     txt_sub.get_bytes_length(),
                                     msg='text subheader as long as expected')
                instance.assertEqual(
                    txt_bytes,
                    txt_sub.to_bytes(),
                    msg='text subheader serializes and deserializes as expected'
                )

    # is each graphics segment working?
    if details.graphics_segment_offsets is not None:
        for i in range(details.graphics_segment_offsets.size):
            with instance.subTest('graphics subheader {} match'.format(i)):
                graphics_bytes = details.get_graphics_subheader_bytes(i)
                graphics_sub = GraphicsSegmentHeader.from_bytes(graphics_bytes,
                                                                start=0)
                instance.assertEqual(
                    len(graphics_bytes),
                    graphics_sub.get_bytes_length(),
                    msg='graphics subheader as long as expected')
                instance.assertEqual(
                    graphics_bytes,
                    graphics_sub.to_bytes(),
                    msg=
                    'graphics subheader serializes and deserializes as expected'
                )

    # is each data extenson subheader working?
    if details.des_segment_offsets is not None:
        for i in range(details.des_segment_offsets.size):
            with instance.subTest('des subheader {} match'.format(i)):
                des_bytes = details.get_des_subheader_bytes(i)
                des_sub = DataExtensionHeader.from_bytes(des_bytes, start=0)
                instance.assertEqual(len(des_bytes),
                                     des_sub.get_bytes_length(),
                                     msg='des subheader as long as expected')
                instance.assertEqual(
                    des_bytes,
                    des_sub.to_bytes(),
                    msg='des subheader serializes and deserializes as expected'
                )
Ejemplo n.º 6
0
def print_nitf(file_name, dest=sys.stdout):
    """
    Worker function to dump the NITF header and various subheader details to the
    provided destination.

    Parameters
    ----------
    file_name : str|BinaryIO
    dest : TextIO
    """

    # Configure print function for desired destination
    #    - e.g., stdout, string buffer, file
    global print_func
    print_func = functools.partial(print, file=dest)

    details = NITFDetails(file_name)

    if isinstance(file_name, string_types):
        print_func('')
        print_func('Details for file {}'.format(file_name))
        print_func('')

    print_func('----- File Header -----')
    _print_file_header(details.nitf_header)
    print_func('')

    if details.img_subheader_offsets is not None:
        for img_subhead_num in range(details.img_subheader_offsets.size):
            print_func('----- Image {} -----'.format(img_subhead_num))
            hdr = details.parse_image_subheader(img_subhead_num)
            _print_image_header(hdr)
            print_func('')

    if details.graphics_subheader_offsets is not None:
        for graphics_subhead_num in range(
                details.graphics_subheader_offsets.size):
            print_func('----- Graphic {} -----'.format(graphics_subhead_num))
            hdr = details.parse_graphics_subheader(graphics_subhead_num)
            _print_graphics_header(hdr)
            data = details.get_graphics_bytes(graphics_subhead_num)
            print_func('GSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.symbol_subheader_offsets is not None:
        for symbol_subhead_num in range(details.symbol_subheader_offsets.size):
            print_func('----- Symbol {} -----'.format(symbol_subhead_num))
            hdr = details.parse_symbol_subheader(symbol_subhead_num)
            _print_symbol_header(hdr)
            data = details.get_symbol_bytes(symbol_subhead_num)
            print_func('SSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.label_subheader_offsets is not None:
        for label_subhead_num in range(details.label_subheader_offsets.size):
            print_func('----- Label {} -----'.format(label_subhead_num))
            hdr = details.parse_label_subheader(label_subhead_num)
            _print_label_header(hdr)
            data = details.get_label_bytes(label_subhead_num)
            print_func('LSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.text_subheader_offsets is not None:
        for text_subhead_num in range(details.text_subheader_offsets.size):
            print_func('----- Text {} -----'.format(text_subhead_num))
            hdr = details.parse_text_subheader(text_subhead_num)
            _print_text_header(hdr)
            data = details.get_text_bytes(text_subhead_num)
            print_func('TSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.des_subheader_offsets is not None:
        for des_subhead_num in range(details.des_subheader_offsets.size):
            print_func('----- DES {} -----'.format(des_subhead_num))
            hdr = details.parse_des_subheader(des_subhead_num)
            _print_des_header(hdr)
            data = details.get_des_bytes(des_subhead_num)

            des_id = hdr.DESID if details.nitf_version == '02.10' else hdr.DESTAG

            if des_id.strip() in ['XML_DATA_CONTENT', 'SICD_XML', 'SIDD_XML']:
                xml_str = minidom.parseString(data.decode()).toprettyxml(
                    indent='    ', newl='\n')
                # NB: this may or not exhibit platform dependent choices in which codec (i.e. latin-1 versus utf-8)
                print_func('DESDATA =')
                for line_num, xml_entry in enumerate(xml_str.splitlines()):
                    if line_num == 0:
                        # Remove xml that gets inserted by minidom, if it's not actually there
                        if (not data.startswith(b'<?xml version')
                            ) and xml_entry.startswith('<?xml version'):
                            continue
                        print_func(xml_entry)
                    elif xml_entry.strip() != '':
                        # Remove extra new lines if XML is already formatted
                        print_func(xml_entry)
            elif des_id.strip() in [
                    'TRE_OVERFLOW', 'Registered Extensions',
                    'Controlled Extensions'
            ]:
                tres = TREList.from_bytes(data, 0)
                print_func('DESDATA = ')
                _print_tres(tres)
            else:
                # Unknown user-defined data
                print_func('DESDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.res_subheader_offsets is not None:
        for res_subhead_num in range(details.res_subheader_offsets.size):
            print_func('----- RES {} -----'.format(res_subhead_num))
            hdr = details.parse_res_subheader(res_subhead_num)
            _print_res_header(hdr)
            data = details.get_res_bytes(res_subhead_num)
            print_func('RESDATA = {}'.format(_decode_effort(data)))
            print_func('')
Ejemplo n.º 7
0
def print_nitf(file_name, dest=sys.stdout):
    # Configure print function for desired destination
    #    - e.g., stdout, string buffer, file
    global print_func
    print_func = functools.partial(print, file=dest)

    details = NITFDetails(file_name)

    print_func('----- File Header -----')
    print_file_header(details.nitf_header)
    print_func('')

    if details.img_subheader_offsets is not None:
        for i in range(details.img_subheader_offsets.size):
            print_func('----- Image {} -----'.format(i))
            hdr = details.parse_image_subheader(i)
            print_image_header(hdr)
            print_func('')

    if details.graphics_subheader_offsets is not None:
        for i in range(details.graphics_subheader_offsets.size):
            print_func('----- Graphic {} -----'.format(i))
            hdr = details.parse_graphics_subheader(i)
            print_graphics_header(hdr)
            data = details.get_graphics_bytes(i)
            print_func('GSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.text_subheader_offsets is not None:
        for i in range(details.text_subheader_offsets.size):
            print_func('----- Text {} -----'.format(i))
            hdr = details.parse_text_subheader(i)
            print_text_header(hdr)
            data = details.get_text_bytes(i)
            print_func('TSDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.des_subheader_offsets is not None:
        for i in range(details.des_subheader_offsets.size):
            print_func('----- DES {} -----'.format(i))
            hdr = details.parse_des_subheader(i)
            print_des_header(hdr)
            data = details.get_des_bytes(i)
            if hdr.DESID.strip() == 'XML_DATA_CONTENT':
                xml_str = xml.dom.minidom.parseString(
                    data.decode()).toprettyxml(indent='    ')
                # Remove extra new lines if XML is already formatted
                xml_str = os.linesep.join(
                    [s for s in xml_str.splitlines() if s.strip()])
                print_func('DESDATA = {}'.format(xml_str))
            elif hdr.DESID.strip() == 'TRE_OVERFLOW':
                tres = TREList.from_bytes(data, 0)
                print_func('DESDATA = ')
                print_tres(tres)
            else:
                # Unknown user-defined data
                print_func('DESDATA = {}'.format(_decode_effort(data)))
            print_func('')

    if details.res_subheader_offsets is not None:
        for i in range(details.res_subheader_offsets.size):
            print_func('----- RES {} -----'.format(i))
            hdr = details.parse_res_subheader(i)
            print_res_header(hdr)
            data = details.get_res_bytes(i)
            print_func('RESDATA = {}'.format(_decode_effort(data)))
            print_func('')