diff --git a/setup.py b/setup.py index e45462f..c0fb08b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md", "r") as fh: setuptools.setup( name="tiffexplore", packages=["tiffexplore"], - version="2021.07.0", + version="2021.07.1", author="Wim Pomp", author_email="wimpomp@gmail.com", description="Explore a tiff structure.", diff --git a/tiffexplore/__init__.py b/tiffexplore/__init__.py index 47a3a2c..b24d01d 100644 --- a/tiffexplore/__init__.py +++ b/tiffexplore/__init__.py @@ -89,10 +89,10 @@ class PaintBox(QtWidgets.QWidget): class Legend(PaintBox): def __init__(self, parent): self.parent = parent - self.color = {'header': 'red', 'ifd': 'cyan', 'tagdata': 'lightgreen', 'image': 'yellow', 'empty': 'white', + self.color = {'header': 'red', '(sub)ifd': 'cyan', 'tagdata': 'lightgreen', 'image': 'yellow', 'empty': 'white', 'shared tagdata': 'green', 'shared image': 'orange', 'unknown': 'gray'} super().__init__() - self.setFixedHeight(120) + self.setFixedHeight(15 * len(self.color)) self.show() def paintEvent(self, *args, **kwargs): @@ -107,8 +107,9 @@ class Legend(PaintBox): class Bar(PaintBox): def __init__(self, parent): self.parent = parent - self.color = {'header': 'red', 'ifd': 'cyan', 'tagdata': 'lightgreen', 'image': 'yellow', 'empty': 'white', - 'HEADER': 'red', 'IFD': 'blue', 'TAGDATA': 'green', 'IMAGE': 'orange', 'EMPTY': 'white'} + self.color = {'header': 'red', 'ifd': 'cyan', 'subifd': 'cyan', 'tagdata': 'lightgreen', 'image': 'yellow', + 'empty': 'white', 'HEADER': 'red', 'IFD': 'blue', 'SUBIFD': 'blue', 'TAGDATA': 'green', + 'IMAGE': 'orange', 'EMPTY': 'white'} self.tiff = None self.bar = tiffread.assignments() super().__init__() @@ -128,7 +129,8 @@ class Bar(PaintBox): self.parent.verticalLayoutWidget.setFixedHeight(self.bar.max_addr) for key, value in self.bar.items(): self.drawRectangle(qp, (0, value[0], 125, value[1]), self.color.get(key[0], "gray")) - self.drawText(qp, (0, value[0], 125, value[1]), ('_'.join(('{}',) * len(key))).format(*key).lower()) + self.drawText(qp, (0, value[0], 125, value[1]), + key[0].lower() + ('\n' if value[1] > 20 else ' ') + ' '.join([f'{k}' for k in key[1]])) qp.end() def get_bar(self, scale=100, min_size=10, max_size=1000): @@ -141,7 +143,7 @@ class Bar(PaintBox): size = min_size if size < min_size else max_size if not (key[0].lower() == 'empty' and value[1] == 1): if key[0].lower() == 'empty': - bar[('empty', value[0] + value[1] // 2)] = (pos, size) + bar[('empty', (value[0] + value[1] // 2,))] = (pos, size) else: if len(item) > 1: bar[(key[0].upper(),) + key[1:]] = (pos, size) @@ -152,47 +154,43 @@ class Bar(PaintBox): return bar def mousePressEvent(self, event): - keys, vals = zip(*self.bar.get_assignment(event.localPos().y())) - key, val = keys[0], vals[0] - if key[0].lower() == 'empty': - addr = key[1] + ((code, key), *_), _ = zip(*self.bar.get_assignment(event.localPos().y())) + if code.lower() == 'empty': + addr = key[0] else: - addr = self.tiff.addresses[(key[0].lower(),) + key[1:]] + addr = self.tiff.addresses[(code.lower(), key)] addr = addr[0] + addr[1] // 2 - keys, addrs = zip(*self.parent.tiff.addresses.get_assignment(addr)) - addr = addrs[0] + keys, (addr, *_) = zip(*self.parent.tiff.addresses.get_assignment(addr)) - text = [('_'.join(('{}',) * len(key))).format(*key) for key in keys] + text = [' '.join([f'{k}' for k in (c.lower(), *k)]) for c, *k in keys] text.append('') text.append(f'Adresses: {addr[0]} - {sum(addr)}') text.append(f'Length: {addr[1]}') - if key[0].lower() == 'header': + if code.lower() == 'header': text.append(f'\nFile size: {len(self.tiff)}') + text.append(f'Unused bytes in file: {self.tiff.get_empty()}') text.append(f'Byte order: {self.tiff.byteorder}') text.append(f'Big tiff: {self.tiff.bigtiff}') text.append(f'Tag size: {self.tiff.tagsize}') text.append(f'Tag number format: {self.tiff.tagnoformat}') text.append(f'Offset size: {self.tiff.offsetsize}') text.append(f'Offset format: {self.tiff.offsetformat}') - text.append(f'First ifd offset: {self.tiff.offsets[0]}') - if key[0].lower() == 'ifd': - text.append(f'Number of tags: {self.tiff.nTags[key[1]]}\n') - text.extend([self.tiff.fmt_tag(k, v)+'\n' for k, v in self.tiff.tags[key[1]].items()]) - if not isinstance(key[1], str): - text.append(f'Next ifd offset: {self.tiff.offsets[key[1] + 1]}') - if key[0].lower() == 'tagdata': - text.append('\n' + self.tiff.fmt_tag(key[2], self.tiff.tags[key[1]][key[2]])) - if key[0].lower() == 'image': - try: - im = self.tiff.asarray(key[1], key[2]) - if im is not None: - text.append(f'\nStrip size: {im.shape}') - text.append(f'Data type: {im.dtype}') - text.append(f'Min, max: {im.min()}, {im.max()}') - text.append(f'Mean, std: {im.mean()}, {im.std()}') - self.parent.setImage(im) - except Exception: - pass + text.append(f'First ifd offset: {self.tiff.offsets[(0,)]}') + if code.lower() in ('ifd', 'subifd'): + text.append(f'Number of tags: {self.tiff.nTags[key]}') + text.append(f'Unused bytes in ifd: {self.tiff.get_empty(key)}\n') + text.extend([self.tiff.fmt_tag(k, v) + '\n' for k, v in self.tiff.tags[key].items()]) + text.append(f'Next ifd offset: {self.tiff.offsets.get(key[:-1] + (key[-1] + 1,))}') + if code.lower() == 'tagdata': + text.append('\n' + self.tiff.fmt_tag(key[-1], self.tiff.tags[key[:-1]][key[-1]])) + if code.lower() == 'image' and len(key) == 2: + im = self.tiff.asarray(key[0], key[1]) + if im is not None: + text.append(f'\nStrip size: {im.shape}') + text.append(f'Data type: {im.dtype}') + text.append(f'Min, max: {im.min()}, {im.max()}') + text.append(f'Mean, std: {im.mean()}, {im.std()}') + self.parent.setImage(im) else: self.parent.setImage() self.parent.properties.setText('\n'.join(text)) diff --git a/tiffexplore/tiffread.py b/tiffexplore/tiffread.py index 57e3231..ac4b039 100644 --- a/tiffexplore/tiffread.py +++ b/tiffexplore/tiffread.py @@ -3,8 +3,6 @@ import tifffile import numpy as np from traceback import format_exc -ifdcodes = {330: 'sub', 34665: 'exif', 34853: 'gps', 40965: 'inter'} - class tiff(): def __init__(self, file): self.file = file @@ -16,34 +14,37 @@ class tiff(): self.tags = {} self.get_file_len() self.addresses = assignments(len(self)) - self.offsets = [] + self.offsets = {} self.nTags = {} self.tagsread = set() try: - self.offsets = [self.read_header()] - idx = 0 - while 0 < self.offsets[-1] < len(self): - self.offsets.append(self.read_ifd(self.offsets[-1], idx)) - idx += 1 - self.readtags() + self.read_ifd_offsets(self.read_header()) + self.read_tags() except Exception: print(format_exc()) - def readtags(self): + def read_ifd_offsets(self, offset, ifdtype=tuple()): + idx = 0 + self.offsets[ifdtype + (idx,)] = offset + while 0 < self.offsets[ifdtype + (idx,)] < len(self): + self.offsets[ifdtype + (idx + 1,)] = self.read_ifd(ifdtype + (idx,)) + idx += 1 + + def read_tags(self): while len(set(self.tags.keys()) - self.tagsread): for idx in set(self.tags.keys()) - self.tagsread: tags = self.tags[idx] if idx not in self.tagsread: if 273 in tags and 279 in tags: for i, a in enumerate(zip(tags[273][-1], tags[279][-1])): - self.addresses[('image', idx, i)] = a - for code, id in ifdcodes.items(): + self.addresses[('image', (*idx, i))] = a + for code in (330, 400, 34665, 34853, 40965): if code in tags: if len(tags[code][3]) == 1: - self.offsets.append(self.read_ifd(tags[code][3][0], f'{id}_{idx}')) + self.read_ifd_offsets(tags[code][3][0], (*idx, code)) else: for i, offset in enumerate(tags[code][3]): - self.offsets.append(self.read_ifd(offset, f'{id}_{idx}_{i}')) + self.read_ifd_offsets(tags[code][3][0], (*idx, code, i)) self.tagsread.add(idx) @staticmethod @@ -92,13 +93,14 @@ class tiff(): self.offsetformat = 'I' self.offsetsize = 4 self.offset = struct.unpack(self.byteorder + self.offsetformat, self.fh.read(self.offsetsize))[0] - self.addresses[('header',)] = (0, 4 + self.bigtiff * 4 + self.offsetsize) + self.addresses[('header', (0,))] = (0, 4 + self.bigtiff * 4 + self.offsetsize) return self.offset - def read_ifd(self, offset, idx): + def read_ifd(self, idx): """ Reads an IFD of the tiff file wp@tl20200214 """ + offset = self.offsets[idx] self.fh.seek(offset) nTags = struct.unpack(self.byteorder + self.tagnoformat, self.fh.read(struct.calcsize(self.tagnoformat)))[0] self.nTags[idx] = nTags @@ -121,7 +123,7 @@ class tiff(): toolong = struct.calcsize(dtype) * count > self.offsetsize if toolong: caddr = struct.unpack(self.byteorder + self.offsetformat, self.fh.read(self.offsetsize))[0] - self.addresses[('tagdata', idx, code)] = (caddr, dtypelen * count) + self.addresses[('tagdata', (*idx, code))] = (caddr, dtypelen * count) cp = self.fh.tell() self.fh.seek(caddr) else: @@ -144,9 +146,22 @@ class tiff(): self.fh.seek(offset + struct.calcsize(self.tagnoformat) + self.tagsize * nTags) nifd = struct.unpack(self.byteorder + self.offsetformat, self.fh.read(self.offsetsize))[0] - self.addresses[('ifd', idx)] = (offset, 2 * struct.calcsize(self.tagnoformat) + nTags * self.tagsize) + self.addresses[('sub' * (len(idx) > 1) + 'ifd', idx)] = (offset, 2 * struct.calcsize(self.tagnoformat) + + nTags * self.tagsize) return nifd + def get_empty(self, ifd=None): + empty = 0 + if ifd is None: + for ((code, *key), value), *_ in self.addresses.get_assignments(): + if code == 'empty': + empty += value[-1] + elif code in ('ifd', 'subifd'): + empty += self.get_empty(key[0]) + else: + empty = sum([self.offsetsize - v[2] if v[2] < self.offsetsize else 0 for v in self.tags[ifd].values()]) + return empty + def __getitem__(self, item): if not isinstance(item, slice): item = slice(item, item+1) @@ -163,7 +178,7 @@ class tiff(): self.tiff.close() self.close() - def close(self, *args, **kwargs): + def close(self): self.fh.close() def get_bytes(self, part):