Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
50128a8
Image is reflected vertically if grid-reflect value in the common fil…
derollins Mar 18, 2025
0ffa11b
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 18, 2025
c89320f
Merge branch 'main' into derollins/verticallyflipped_jpk
derollins Mar 26, 2025
a70b3b4
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 26, 2025
29aa597
corrected typo in examples notebook
derollins Mar 27, 2025
f15ac21
add option to turn off flip_image
derollins Mar 27, 2025
1c3b4ec
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 27, 2025
08db5c7
restore notebook
derollins Mar 27, 2025
9906b04
Merge branch 'derollins/verticallyflipped_jpk' of https://github.com/…
derollins Mar 27, 2025
1de0968
update notebook
derollins Mar 27, 2025
7b9579d
add error handling to z scalling funciton
derollins Mar 27, 2025
71db81b
simplify z scaling function to placate pre-commit
derollins Mar 27, 2025
bf3884b
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 27, 2025
84a5666
imporve documentation of z scalling and get_tag_value functions
derollins Mar 27, 2025
0429764
Merge branch 'derollins/verticallyflipped_jpk' of https://github.com/…
derollins Mar 27, 2025
95b8e82
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 27, 2025
52f7f8e
Add Any typing to placate pre-commit
derollins Mar 27, 2025
8968b3b
Removed the GIF created from the ASD example in the examples notebook.
derollins Mar 27, 2025
bd1193a
Remove print statement from testing
derollins Mar 27, 2025
b4826f2
Make function signature more specific
derollins Mar 27, 2025
a8e5ab7
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 27, 2025
6f12b5c
Format with reStructuredText
derollins Mar 27, 2025
b8f79f3
Specify and dictate data types for consistancy and pre commit
derollins Mar 27, 2025
f441942
Specify and dictate data types for consistancy and pre commit
derollins Mar 27, 2025
2412e4d
merge pre commit chnages
derollins Mar 27, 2025
b21f123
merge pre commit changes
derollins Mar 27, 2025
ec2caa4
Merge pre commit changes
derollins Mar 27, 2025
d8d14d7
Seperate out _get_number_of_slots funciton
derollins Mar 27, 2025
5ae6246
[pre-commit.ci] Fixing issues with pre-commit
pre-commit-ci[bot] Mar 27, 2025
b4370e9
n_slots is integer, make this clear in docs
derollins Mar 28, 2025
a9829bf
Include None in type-hint
ns-rse Apr 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 80 additions & 21 deletions AFMReader/jpk.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@

logger.enable(__package__)

# pylint: disable=possibly-used-before-assignment
Comment thread
derollins marked this conversation as resolved.
# pylint: disable=too-many-locals


def _jpk_pixel_to_nm_scaling(tiff_page: tifffile.tifffile.TiffPage, jpk_tags: dict[str, int]) -> float:
"""
Expand Down Expand Up @@ -42,6 +39,65 @@ def _jpk_pixel_to_nm_scaling(tiff_page: tifffile.tifffile.TiffPage, jpk_tags: di
return px_to_nm * 1e9


def _get_tag_value(page: tifffile.TiffPage, tag_name: str) -> str | int | float:
"""
Retrieve the value of a specified tag from a TIFF page of a JPK file.

Parameters
----------
page : tifffile.TiffPage
The TIFF page from which to retrieve the tag value.
tag_name : str
The name of the tag to retrieve.

Returns
-------
Any
The value of the specified tag.

Raises
------
KeyError
If the tag is not found in the TIFF page.
"""
try:
return page.tags[tag_name].value
except KeyError:
logger.error(f"Missing tag in JPK file: {tag_name}")
raise


def _get_number_of_slots(tif: tifffile.tifffile, channel_idx: int, jpk_tags: dict[str, int]) -> int:
"""
Retrieve the number of slots from a JPK image channel.

Parameters
----------
tif : tifffile.TiffFile
A TIFF file containing JPK images.
channel_idx : int
Numerical channel identifier used to navigate the TIFF file pages.
jpk_tags : dict[str, int]
Dictionary of JPK tags.

Returns
-------
int
The number of slots in the specified JPK image channel.

Raises
------
ValueError
If the slot count cannot be converted to an integer.
"""
try:
n_slots = int(_get_tag_value(tif.pages[channel_idx], str(jpk_tags["n_slots"])))
except ValueError as e:
logger.error(f"n_slots is not a integer: {e}")
raise
return n_slots


def _get_z_scaling(tif: tifffile.tifffile, channel_idx: int, jpk_tags: dict[str, int]) -> tuple[float, float]:
"""
Extract the z scaling factor and offset for a JPK image channel.
Expand All @@ -62,36 +118,33 @@ def _get_z_scaling(tif: tifffile.tifffile, channel_idx: int, jpk_tags: dict[str,
tuple[float, float]
A tuple contains values used to scale and offset raw data.
"""
n_slots = tif.pages[channel_idx].tags[jpk_tags["n_slots"]].value
default_slot = tif.pages[channel_idx].tags[jpk_tags["default_slot"]]

# Create a dictionary of list for the differnt slots
n_slots = _get_number_of_slots(tif, channel_idx, jpk_tags)
slots: dict[int, list[str]] = {slot: [] for slot in range(n_slots)}

# Extract the tags with numerical names in each slot
while n_slots >= 0:
for tag in tif.pages[channel_idx].tags:
try:
tag_name_float = float(tag.name)
if tag_name_float >= ( # pylint: disable=chained-comparison
int(jpk_tags["first_slot_tag"]) + (n_slots * jpk_tags["slot_size"])
) and tag_name_float < (int(jpk_tags["first_slot_tag"]) + ((n_slots + 1) * jpk_tags["slot_size"])):
slots[(n_slots)].append(tag.name)
tag_name_int = int(tag.name)
if tag_name_int >= ( # pylint: disable=chained-comparison
int(jpk_tags["first_slot_tag"]) + (n_slots * int(jpk_tags["slot_size"]))
) and tag_name_int < (int(jpk_tags["first_slot_tag"]) + ((n_slots + 1) * jpk_tags["slot_size"])):
slots[n_slots].append(tag.name)
except ValueError:
continue
n_slots -= 1

# Find the number of the default slot (selected in the instrument GUI)
default_slot = tif.pages[channel_idx].tags[jpk_tags["default_slot"]]
for slot, values in slots.items():
for value in values:
if tif.pages[channel_idx].tags[str(value)].value == default_slot.value:
_default_slot = slot

# Determine if the default slot requires scaling and find scaling and offset values
scaling_type = (
tif.pages[channel_idx]
.tags[str(int(jpk_tags["first_scaling_type"]) + (jpk_tags["slot_size"] * (_default_slot)))]
.value
scaling_type = _get_tag_value(
tif.pages[channel_idx], str(int(jpk_tags["first_scaling_type"]) + (jpk_tags["slot_size"] * (_default_slot)))
)
if scaling_type == "LinearScaling":
scaling_name = (
Expand All @@ -105,17 +158,19 @@ def _get_z_scaling(tif: tifffile.tifffile, channel_idx: int, jpk_tags: dict[str,
.name
)

scaling = tif.pages[channel_idx].tags[scaling_name].value
offset = tif.pages[channel_idx].tags[offset_name].value
scaling = float(_get_tag_value(tif.pages[channel_idx], scaling_name))
offset = float(_get_tag_value(tif.pages[channel_idx], offset_name))
elif scaling_type == "NullScaling":
scaling = 1
offset = 0
scaling = 1.0
offset = 0.0
else:
raise ValueError(f"Scaling type {scaling_type} is not 'NullScaling' or 'LinearScaling'")
return scaling, offset


def load_jpk(file_path: Path | str, channel: str, config_path: Path | str | None = None) -> tuple[np.ndarray, float]:
def load_jpk(
file_path: Path | str, channel: str, config_path: Path | str | None = None, flip_image: bool | None = True
) -> tuple[np.ndarray, float]:
"""
Load image from JPK Instruments .jpk files.

Expand All @@ -128,6 +183,8 @@ def load_jpk(file_path: Path | str, channel: str, config_path: Path | str | None
config_path : Path | str | None
Path to a configuration file. If ''None'' (default) then the packages default configuration is loaded from
''default_config.yaml''.
flip_image : bool, optional
Whether to flip the image vertically. Default is ``True``.

Returns
-------
Expand All @@ -146,7 +203,7 @@ def load_jpk(file_path: Path | str, channel: str, config_path: Path | str | None
Load height trace channel from the .jpk file. 'height_trace' is the default channel name.

>>> from AFMReader.jpk import load_jpk
>>> image, pixel_to_nanometre_scaling_factor = load_jpk(file_path="./my_jpk_file.jpk", channel="height_trace")
>>> image, pixel_to_nanometre_scaling_factor = load_jpk(file_path="./my_jpk_file.jpk", channel="height_trace", flip_image=True)
"""
logger.info(f"Loading image from : {file_path}")
file_path = Path(file_path)
Expand Down Expand Up @@ -177,6 +234,8 @@ def load_jpk(file_path: Path | str, channel: str, config_path: Path | str | None
image = channel_page.asarray()
scaling, offset = _get_z_scaling(tif, channel_idx, jpk_tags)
image = (image * scaling) + offset
if flip_image is True:
image = np.flipud(image)

if channel_page.tags[jpk_tags["channel_name"]].value in ("height", "measuredHeight", "amplitude"):
image = image * 1e9
Expand Down
8 changes: 4 additions & 4 deletions examples/example_01.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@
"source": [
"# Load the JPK file as an image and pixel to nm scaling factor\n",
"FILE = \"../tests/resources/sample_0.jpk\"\n",
"image, pixel_to_nm_scaling = load_jpk(file_path=FILE, channel=\"height_trac\")"
"image, pixel_to_nm_scaling = load_jpk(file_path=FILE, channel=\"height_trace\", flip_image=True)"
]
},
{
Expand Down Expand Up @@ -314,7 +314,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "afmreader",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -328,9 +328,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.19"
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}