Skip to content

Commit

Permalink
Added working skeleton code for the TFS-specific TIFF parser, which f…
Browse files Browse the repository at this point in the history
…ishes successfully metadata and converts into standardized python dict, ods document added to collect IKZ feedback how to map specific TFS using assumptions onto specific concepts in NXem to consume in e.g. OASIS
  • Loading branch information
atomprobe-tc committed Dec 8, 2023
1 parent 00f4556 commit 020dea0
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 8 deletions.
Binary file added image_tiff_tfs_to_nexus.ods
Binary file not shown.
2 changes: 1 addition & 1 deletion imgs.batch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

datasource="../../../../paper_paper_paper/scidat_nomad_ebsd/bb_analysis/data/production_imgs/"

examples="ALN_baoh_021.tif FeMoOx_AntiA_04_1k5x_CN.tif"
examples="ALN_baoh_021.tif" # FeMoOx_AntiA_04_1k5x_CN.tif"

for example in $examples; do
echo $example
Expand Down
7 changes: 5 additions & 2 deletions imgs.dev.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"outputs": [],
"source": [
"fnm = \"/home/kaiobach/Research/paper_paper_paper/scidat_nomad_ebsd/bb_analysis/data/production_imgs/ALN_baoh_021.tif\"\n",
"fnm = \"/home/kaiobach/Research/paper_paper_paper/scidat_nomad_ebsd/bb_analysis/data/production_imgs/FeMoOx_AntiA_04_1k5x_CN.tif\""
"# fnm = \"/home/kaiobach/Research/paper_paper_paper/scidat_nomad_ebsd/bb_analysis/data/production_imgs/FeMoOx_AntiA_04_1k5x_CN.tif\""
]
},
{
Expand All @@ -18,6 +18,7 @@
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from PIL import Image\n",
"from PIL.TiffTags import TAGS\n",
"# print(TAGS)"
Expand Down Expand Up @@ -58,14 +59,16 @@
" czi_keys = [34118, 34119]\n",
" for czi_key in czi_keys:\n",
" if czi_key in fp.tag_v2:\n",
" print(f\"Found czi_key {tfs_key}...\")\n",
" utf = fp.tag[czi_key]\n",
" print(type(utf))\n",
" if len(utf) == 1:\n",
" print(utf[0])\n",
" exit(1)\n",
" # exit(1)\n",
" tfs_keys = [34682]\n",
" for tfs_key in tfs_keys:\n",
" if tfs_key in fp.tag_v2:\n",
" print(f\"Found tfs_key {tfs_key}...\")\n",
" utf = fp.tag[tfs_key]\n",
" print(type(utf))\n",
" if len(utf) == 1:\n",
Expand Down
130 changes: 130 additions & 0 deletions pynxtools/dataconverter/readers/em/subparsers/image_tiff_tfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Subparser for harmonizing ThermoFisher-specific content in TIFF files."""

import mmap
import numpy as np
from typing import Dict
from PIL import Image
from PIL.TiffTags import TAGS

from pynxtools.dataconverter.readers.em.subparsers.image_tiff import TiffSubParser
from pynxtools.dataconverter.readers.em.subparsers.image_tiff_tfs_cfg import \
tfs_section_names, tfs_section_details
from pynxtools.dataconverter.readers.em.utils.image_utils import \
sort_tuple, if_str_represents_float


class TfsTiffSubParser(TiffSubParser):
def __init__(self, file_path: str = ""):
super().__init__(file_path)
self.prfx = None
self.tmp: Dict = {}
self.supported_version: Dict = {}
self.version: Dict = {}
self.tags: Dict = {}
self.supported = False
self.check_if_tiff()
self.tfs: Dict = {}

def check_if_tiff_tfs(self):
"""Check if resource behind self.file_path is a TaggedImageFormat file."""
self.supported = 0 # voting-based
with open(self.file_path, 'rb', 0) as file:
s = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
magic = s.read(4)
if magic == b'II*\x00': # https://en.wikipedia.org/wiki/TIFF
self.supported += 1

with Image.open(self.fiele_path, mode="r") as fp:
tfs_keys = [34682]
for tfs_key in tfs_keys:
if tfs_key in fp.tag_v2:
if len(fp.tag[tfs_key]) == 1:
self.supported += 1 # found TFS-specific tag
if self.supported == 2:
self.supported = True
else:
self.supported = False

def get_metadata(self):
"""Extract metadata in TFS specific tags if present."""
print("Reporting the tags found in this TIFF file...")
# for an overview of tags
# https://www.loc.gov/preservation/digital/formats/content/tiff_tags.shtml
# with Image.open(self.file_path, mode="r") as fp:
# self.tags = {TAGS[key] : fp.tag[key] for key in fp.tag_v2}
# for key, val in self.tags.items():
# print(f"{key}, {val}")
tfs_section_offsets = {}
with open(self.file_path, 'rb', 0) as fp:
s = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
for section_name in tfs_section_names:
pos = s.find(bytes(section_name, "utf8")) # != -1
tfs_section_offsets[section_name] = pos
print(tfs_section_offsets)

# define search offsets
tpl = []
for key, value in tfs_section_offsets.items():
tpl.append((key, value))
tpl = sort_tuple(tpl)
print(tpl)

# exemplar parsing of specific TFS section content into a dict
# here for section_name == "[System]":
pos_s = None
pos_e = None
for idx in np.arange(0, len(tpl)):
if tpl[idx][0] != "[System]":
continue
else:
pos_s = tpl[idx][1]
if idx <= len(tpl) - 1:
pos_e = tpl[idx + 1][1]
break
print(f"Search for [System] in between byte offsets {pos_s} and {pos_e}")
if pos_s is None or pos_e is None:
raise ValueError(f"Search for [System] was unsuccessful !")

# fish metadata of e.g. the system section
for term in tfs_section_details["[System]"]:
s.seek(pos_s, 0)
pos = s.find(bytes(term, "utf8"))
if pos < pos_e: # check if pos_e is None
s.seek(pos, 0)
value = f"{s.readline().strip().decode('utf8').replace(f'{term}=', '')}"
if value != "":
if if_str_represents_float(value) is True:
self.tfs[f"system/{term}"] = np.float64(value)
elif value.isdigit() is True:
self.tfs[f"system/{term}"] = np.int64(value)
else:
self.tfs[f"system/{term}"] = None
else:
pass
print(self.tfs)

def parse_and_normalize(self):
"""Perform actual parsing filling cache self.tmp."""
if self.supported is True:
print(f"Parsing via ThermoFisher-specific metadata...")
self.get_metadata()
else:
print(f"{self.file_path} is not a ThermoFisher-specific "
f"TIFF file that this parser can process !")
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Configuration of the image_tiff_tfs subparser."""


tfs_section_names = ["[User]",
"[System]",
"[Beam]",
"[EBeam]",
"[GIS]",
"[Scan]",
"[EScan]",
"[Stage]",
"[Image]",
"[Vacuum]",
"[Specimen]",
"[Detectors]",
"[T2]",
"[Accessories]",
"[EBeamDeceleration]",
"[CompoundLensFilter]",
"[PrivateFei]",
"[HiResIllumination]",
"[EasyLift]",
"[HotStageMEMS]",
"[HotStage]",
"[HotStageHVHS]",
"[ColdStage]"]

tfs_section_details = {"[System]": ["Type",
"Dnumber",
"Software",
"BuildNr",
"Source",
"Column",
"FinalLens",
"Chamber",
"Stage",
"Pump",
"ESEM",
"Aperture",
"Scan",
"Acq",
"EucWD",
"SystemType",
"DisplayWidth",
"DisplayHeight"]}
10 changes: 5 additions & 5 deletions pynxtools/dataconverter/readers/em/subparsers/nxs_imgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import numpy as np
# from typing import Dict, Any, List

from pynxtools.dataconverter.readers.em.subparsers.image_tiff import TiffSubParser
from pynxtools.dataconverter.readers.em.subparsers.image_tiff_tfs import TfsTiffSubParser


class NxEmImagesSubParser:
Expand All @@ -38,9 +38,9 @@ def __init__(self, entry_id: int = 1, input_file_name: str = ""):
def identify_image_type(self):
"""Identify if image matches known mime type and has content for which subparser exists."""
# tech partner formats used for measurement
img = TiffSubParser(f"{self.file_path}")
img = TfsTiffSubParser(f"{self.file_path}")
if img.supported is True:
return "tiff"
return "tiff_tfs"
return None

def parse(self, template: dict) -> dict:
Expand All @@ -52,8 +52,8 @@ def parse(self, template: dict) -> dict:
# see also comments for respective nxs_pyxem parser
# and its interaction with tech-partner-specific hfive_* subparsers

if image_parser_type == "tiff":
tiff = TiffSubParser(self.file_path)
if image_parser_type == "tiff_tfs":
tiff = TfsTiffSubParser(self.file_path)
tiff.parse_and_normalize()
self.process_into_template(tiff.tmp, template)
else: # none or something unsupported
Expand Down
40 changes: 40 additions & 0 deletions pynxtools/dataconverter/readers/em/utils/image_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD. See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import numpy as np


# https://www.geeksforgeeks.org/python-program-to-sort-a-list-of-tuples-by-second-item/
def sort_tuple(tup):
# convert the list of tuples to a numpy array with data type (object, int)
arr = np.array(tup, dtype=[('col1', object), ('col2', int)])
# get the indices that would sort the array based on the second column
indices = np.argsort(arr['col2'])
# use the resulting indices to sort the array
sorted_arr = arr[indices]
# convert the sorted numpy array back to a list of tuples
sorted_tup = [(row['col1'], row['col2']) for row in sorted_arr]
return sorted_tup


def if_str_represents_float(s):
try:
float(s)
return str(float(s)) == s
except ValueError:
return False

0 comments on commit 020dea0

Please sign in to comment.