atom-detection / app /tiff_utils.py
Romain Graux
New extractor for physical metadata
60fece7 unverified
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author : Romain Graux
@date : 2023 April 25, 11:59:06
@last modified : 2023 September 19, 11:18:36
"""
from typing import Callable, Optional
import re
import imageio
from collections import namedtuple
import numpy as np
PhysicalMetadata = namedtuple(
"PhysicalMetadata", ["width", "height", "pixel_width", "pixel_height", "unit"]
)
MetadataExtractor = Callable[[dict, int, int], Optional[PhysicalMetadata]]
def extract_imagej_metadata(
metadata: dict, width: int, height: int
) -> Optional[PhysicalMetadata]:
try:
ipw, iph, _ = metadata["resolution"]
result = re.search(r"unit=(.+)", metadata["description"])
if not result:
return None
unit = result.group(1)
return PhysicalMetadata(width, height, 1.0 / ipw, 1.0 / iph, unit.lower())
except (KeyError, AttributeError):
return None
def extract_resolution_metadata(
metadata: dict, width: int, height: int
) -> Optional[PhysicalMetadata]:
try:
ipw, iph, _ = metadata["resolution"]
# It looks like the resolution unit is not really reliable, so let's just assume nm
unit = "nm"
return PhysicalMetadata(width, height, 1.0 / ipw, 1.0 / iph, unit)
except (KeyError, AttributeError):
return None
METADATA_EXTRACTORS: list[MetadataExtractor] = [
extract_imagej_metadata,
extract_resolution_metadata,
]
def normalize_metadata(metadata: PhysicalMetadata) -> PhysicalMetadata:
conversion_factor = {
"inch": 2.54e7,
"m": 1e9,
"dm": 1e8,
"cm": 1e7,
"mm": 1e6,
"µm": 1e3,
"nm": 1,
}
if metadata.unit not in conversion_factor:
raise ValueError(f"Unknown unit: {metadata.unit}")
factor = conversion_factor[metadata.unit]
return PhysicalMetadata(
metadata.width,
metadata.height,
metadata.pixel_width * factor,
metadata.pixel_height * factor,
"nm",
)
def extract_physical_metadata(image_path: str, strict: bool = True) -> PhysicalMetadata:
"""
Extracts the physical metadata of an image by trying all available extractors.
Raises ValueError if no extractor succeeds.
"""
with open(image_path, "rb") as f:
data = f.read()
reader = imageio.get_reader(data)
metadata = reader.get_meta_data()
h, w = reader.get_next_data().shape
for extractor in METADATA_EXTRACTORS:
result = extractor(metadata, w, h)
if result is not None:
return normalize_metadata(result)
raise ValueError(
"Failed to extract metadata from the image using any available method."
)
def tiff_to_png(image, inplace=True):
img = image if inplace else image.copy()
if np.array(img.getdata()).max() <= 1:
img = img.point(lambda p: p * 255)
return img.convert("RGB")