Skip to content

tests(jpk): Adds sample .jpk-qi-image to test suite#155

Merged
ns-rse merged 2 commits intomainfrom
ns-rse/jpk-qu-image-tests
Jun 17, 2025
Merged

tests(jpk): Adds sample .jpk-qi-image to test suite#155
ns-rse merged 2 commits intomainfrom
ns-rse/jpk-qu-image-tests

Conversation

@ns-rse
Copy link
Copy Markdown
Collaborator

@ns-rse ns-rse commented Jun 17, 2025

Closes #154

AFMReader appears to already be able to read files with .jpk-qi-image as the AFMReader.jpk.load_jpk() function loads these fine.

@sebastienjanel provided a sample file so its been added to the test suite and parameterised tests for the following channels created...

  • measuredHeight_trace
  • vDeflection_trace
  • adhesion_trace
  • height_trace
  • slope_trace

Closes #154

AFMReader appears to already be able to read files with `.jpk-qi-image` as the `AFMReader.jpk.load_jpk()` function loads these fine.

@sebastienjanel provided a sample file so its been added to the test suite and parameterised tests for the following channels created...

- `measuredHeight_trace`
- `vDeflection_trace`
- `adhesion_trace`
- `height_trace`
- `slope_trace`
@sebastienjanel
Copy link
Copy Markdown

The two important channels for TopoStats are "Height measured" and "Contact Point Offset". The measured height comes directly along with the QI file in a separate image, and the contact point offset is computed from the force curves (zero-force topography).

@derollins
Copy link
Copy Markdown
Member

derollins commented Jun 17, 2025

The two important channels for TopoStats are "Height measured" and "Contact Point Offset". The measured height comes directly along with the QI file in a separate image, and the contact point offset is computed from the force curves (zero-force topography).

You can access the "Height Measured" channel from the standard image by using measuredHeight_trace or measuredHeight_retrace as the channel in the TopoStats config file (or as the channel argument when using AFMReader directly). "Contact Point Offset" will be, as before, 3-contact-point-offset_trace although maybe with a different number at the start. The _trace is a quirk of how JPK records scan direction and how AFMReader reads and distinguishes trace and retrace in standard images.

@ns-rse
Copy link
Copy Markdown
Collaborator Author

ns-rse commented Jun 17, 2025

When I loaded the image the channels I listed above were the only ones available which is why I've only been able to add tests for those.

NB the file is in jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image sub-directory from where I am running Python

from pathlib import Path
from AFMReader.jpk import load_jpk

BASE_DIR = Path.cwd()
JPK_DIR = BASE_DIR / "jpk-qi"

file = "2025.05.20-17.48.42.479.jpk-qi-image"
jpk_file = load_jpk(file_path=JPK_DIR / file, channel="Height (measured)")

14:43:29 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:43:29 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:43:29 | ERROR |jpk.py:jpk:load_jpk:234 | 'Height (measured)' not in .jpk-qi-image channel list: {'measuredHeight_trace': 3, 'vDeflection_trace': 2, 'adhesion_trace': 4, 'height_trace': 5, 'slope_trace': 6}
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/jpk.py:232, in load_jpk(file_path, channel, config_path, flip_image)
    231 try:
--> 232     channel_idx = channel_list[channel]
    233 except KeyError as e:

KeyError: 'Height (measured)'

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
Cell In[11], line 1
----> 1 jpk_file = load_jpk(file_path=JPK_DIR / file, channel="Height (measured)")

File ~/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/jpk.py:235, in load_jpk(file_path, channel, config_path, flip_image)
    233 except KeyError as e:
    234     logger.error(f"'{channel}' not in {file_path.suffix} channel list: {channel_list}")
--> 235     raise ValueError(f"'{channel}' not in {file_path.suffix} channel list: {channel_list}") from e
    237 # Get image and if applicable, scale it
    238 channel_page = tif.pages[channel_idx]

ValueError: 'Height (measured)' not in .jpk-qi-image channel list: {'measuredHeight_trace': 3, 'vDeflection_trace': 2, 'adhesion_trace': 4, 'height_trace': 5, 'slope_trace': 6}

The channels that are reported as being present are work fine though...

jpk_file = load_jpk(file_path=JPK_DIR / file, channel="measuredHeight_trace")

14:45:33 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:45:34 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:45:34 | INFO |jpk.py:jpk:load_jpk:251 | [2025.05.20-17.48.42.479] : Extracted image.

jpk_file = load_jpk(file_path=JPK_DIR / file, channel="vDeflection_trace")

14:45:34 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:45:34 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:45:34 | INFO |jpk.py:jpk:load_jpk:251 | [2025.05.20-17.48.42.479] : Extracted image.

jpk_file = load_jpk(file_path=JPK_DIR / file, channel="adhesion_trace")

14:45:34 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:45:34 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:45:34 | INFO |jpk.py:jpk:load_jpk:251 | [2025.05.20-17.48.42.479] : Extracted image.

jpk_file = load_jpk(file_path=JPK_DIR / file, channel="height_trace")

14:45:34 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:45:34 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:45:34 | INFO |jpk.py:jpk:load_jpk:251 | [2025.05.20-17.48.42.479] : Extracted image.

jpk_file = load_jpk(file_path=JPK_DIR / file, channel="slope_trace")

14:45:34 | INFO |jpk.py:jpk:load_jpk:213 | Loading image from : /home/neil/work/git/hub/AFM-SPM/AFMReader/tmp/jpk-qi/2025.05.20-17.48.42.479.jpk-qi-image
14:45:34 | INFO |jpk.py:jpk:_load_jpk_tags:274 | Configuration loaded from : /home/neil/.virtualenvs/topostats-afmreader/lib/python3.11/site-packages/AFMReader/default_config.yaml
14:45:34 | INFO |jpk.py:jpk:load_jpk:251 | [2025.05.20-17.48.42.479] : Extracted image.

The Height (measured) channel simply isn't in this file so I can't test it. 🤷

Tests were failing, hopefully this fixes that by using `pytest.approx()`.
@sebastienjanel
Copy link
Copy Markdown

sebastienjanel commented Jun 17, 2025

That's because these channels come from 2 different kind of files. @derollins is right in the channel names. But i must also add that in some situation JPK DP write the contact point topography as "contact point", not "contact point offset". These are channel 3 and 5.
More channel in this file :
2025.06.12-16.29.37_3-contact-point-offset-extend.jpk-qi-image.zip
and this is different :
2025.05.26-15.49.28.188_processed-2025.06.17-15.59.52.jpk-qi-image.zip

@derollins
Copy link
Copy Markdown
Member

Thanks, @sebastienjanel . Like in all AFM files, the channel content will depend on which channels you choose to save (or export from JPK DP). AFMreader provides an error message with the list of available channels if the one inputted isn't discovered. It doesn't necessarily matter what they are called and each manufacture and even instrument have different names for channels.

As long as you discover the name of the channel you are looking for you should be able to load it with AFMReader.

@ns-rse ns-rse merged commit a12bd2d into main Jun 17, 2025
10 checks passed
@ns-rse ns-rse deleted the ns-rse/jpk-qu-image-tests branch June 17, 2025 15:35
@ns-rse
Copy link
Copy Markdown
Collaborator Author

ns-rse commented Jun 17, 2025

Sounds as though this could be very open-ended with regards to channels!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support loading of .jpk-qi files

3 participants