-
Notifications
You must be signed in to change notification settings - Fork 3
Adding support for .jpk-qi-data and .bin files #190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d25abaf
9d28dec
4bd6bb3
28f7633
a67b11f
37e1ae2
16a827c
c780de5
a764ca6
405a95d
3198567
c25fb75
085b252
06ec0de
78c54a7
3c327e9
e776c66
3e0b8b1
b8ec43a
10c1050
b510141
afc65cc
b1ec527
1fd1eed
4e90188
66f388a
f286e57
5391179
f0235ad
62481da
588c725
4c7ea88
62637c2
b9104cc
0a1f459
58a3a0f
c0a2924
e646205
a6388fd
b88ee26
be2840e
a1764fe
603d7bf
43640ac
0146542
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,9 @@ __pycache__/ | |
| *.py[cod] | ||
| *$py.class | ||
|
|
||
| AFMReader/data/* | ||
| AFMReader/notebooks/* | ||
|
|
||
| # C extensions | ||
| *.so | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,17 @@ | ||
| """Switchboard for input files.""" | ||
|
|
||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import numpy.typing as npt | ||
|
|
||
| from AFMReader import asd, gwy, h5_jpk, ibw, jpk, spm, stp, top, topostats | ||
| from AFMReader import asd, gwy, h5_jpk, ibw, jpk, raw_bin, spm, stp, top, topostats, jpk_qi | ||
| from AFMReader.logging import logger | ||
|
|
||
| logger.enable(__package__) | ||
|
|
||
|
|
||
| # pylint: disable=too-few-public-methods | ||
| # pylint: disable=too-few-public-methods,too-many-branches,too-many-statements,fixme | ||
| class LoadFile: | ||
| """ | ||
| Class to handle the general loading of an AFM file. | ||
|
|
@@ -21,9 +22,11 @@ class LoadFile: | |
| Path to the AFM image. | ||
| channel : str | ||
| Channel to extract from the AFM image. | ||
| kwargs : dict, optional | ||
| Additional keyword arguments to pass to the specific loaders. | ||
| """ | ||
|
|
||
| def __init__(self, filepath: str | Path, channel: str): | ||
| def __init__(self, filepath: str | Path, channel: str, kwargs: dict | None = None): | ||
| """ | ||
| Initialise the general LoadFile class with a filepath and channel. | ||
|
|
||
|
|
@@ -33,39 +36,82 @@ def __init__(self, filepath: str | Path, channel: str): | |
| Path to the AFM image. | ||
| channel : str | ||
| Channel to extract from the AFM image. | ||
| kwargs : dict, optional | ||
| Additional keyword arguments to pass to the specific loaders. | ||
| """ | ||
| self.filepath = Path(filepath) | ||
| self.channel = channel | ||
| self.suffix = self.filepath.suffix | ||
| self.loaded_curves = False | ||
| self.kwargs = kwargs if kwargs else {} | ||
|
|
||
| def load(self) -> tuple[npt.NDArray | str, float | None]: # noqa: C901 | ||
| # Store heavy loaded data in a dict to avoid having to reload it | ||
| self.cached_data: dict[str, Any] = {} | ||
|
|
||
| def load( # noqa: C901 | ||
| self, channel: str | None = None, kwargs: dict | None = None | ||
| ) -> tuple[npt.NDArray | str, float | None] | tuple[npt.NDArray | str, float | None, Any]: | ||
| """ | ||
| Generally loads a file type that can be handled by AFMReader. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| channel : str, optional | ||
| Overriding channel to extract from the AFM image. | ||
| kwargs : dict, optional | ||
| Additional keyword arguments to pass to the specific loaders. | ||
|
|
||
| Returns | ||
| ------- | ||
| tuple | ||
| The image data (stack if ''.asd'' or ''.h5-jpk'') and the pixel to nanometre scaling ratio. | ||
| If curve data is found, also return the curve data (a large dict of all the curves). | ||
|
|
||
| Raises | ||
| ------ | ||
| ValueError | ||
| Where the channel is not found, returned as a tuple of "error message" and "None" so that this can be | ||
| propagated to Napari without outright failing. | ||
| """ | ||
| if channel: | ||
| self.channel = channel | ||
| if kwargs: | ||
| self.kwargs = kwargs | ||
| try: | ||
| if self.suffix == ".asd": | ||
| image, pixel_to_nanometre_scaling_factor, _ = asd.load_asd(self.filepath, self.channel) | ||
| elif self.suffix == ".gwy": | ||
| image, pixel_to_nanometre_scaling_factor = gwy.load_gwy(self.filepath, self.channel) | ||
| elif self.suffix == ".ibw": | ||
| image, pixel_to_nanometre_scaling_factor = ibw.load_ibw(self.filepath, self.channel) | ||
| elif self.suffix == ".jpk": | ||
| elif self.suffix in [".jpk", ".jpk-qi-image"]: | ||
| image, pixel_to_nanometre_scaling_factor = jpk.load_jpk(self.filepath, self.channel) | ||
| elif self.suffix == ".spm": | ||
| image, pixel_to_nanometre_scaling_factor = spm.load_spm(self.filepath, self.channel) | ||
| elif self.suffix == ".h5-jpk": | ||
| image, pixel_to_nanometre_scaling_factor, _ = h5_jpk.load_h5jpk(self.filepath, self.channel) | ||
| h5_returned = h5_jpk.load_h5jpk(self.filepath, self.channel, load_curves=not self.loaded_curves) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this entire block be absorbed into the I'll have a go too.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, is definitely a bit messy. My thinking was preventing the need to make changes to topostats by not changing what each load function returns if it isn't reading the curve data I'm adding support for. Though I think h5jpk is not used in TopoStats anyway? And it might be necessary to make changes to topostats reading anyway due to the changes I have been working on for returning the z unit for the read files? |
||
| if len(h5_returned) == 3: | ||
| image, pixel_to_nanometre_scaling_factor, _ = h5_returned # type: ignore[misc] | ||
| elif len(h5_returned) == 4: | ||
| image, pixel_to_nanometre_scaling_factor, _, curve_data = h5_returned # type: ignore[misc] | ||
| self.loaded_curves = True | ||
| return image, pixel_to_nanometre_scaling_factor, curve_data | ||
| else: | ||
| logger.error(f"Loading h5-jpk file returned unexpected number of items: {len(h5_returned)}") | ||
| elif self.suffix == ".jpk-qi-data": | ||
| if "jpk_qi_loader" not in self.cached_data: | ||
| self.cached_data["jpk_qi_loader"] = jpk_qi.jpk_qi_loader( | ||
| filepath=self.filepath, channel=self.channel, **self.kwargs | ||
| ) | ||
| jpk_qi_returned = self.cached_data["jpk_qi_loader"].load(channel=self.channel, **self.kwargs) | ||
| if len(jpk_qi_returned) == 2: | ||
| image, pixel_to_nanometre_scaling_factor = jpk_qi_returned | ||
| elif len(jpk_qi_returned) == 3: | ||
| image, pixel_to_nanometre_scaling_factor, curve_data = jpk_qi_returned | ||
| self.loaded_curves = True | ||
| return image, pixel_to_nanometre_scaling_factor, curve_data | ||
| else: | ||
| logger.error(f"Loading h5-jpk file returned unexpected number of items: {len(jpk_qi_returned)}") | ||
| elif self.suffix == ".stp": | ||
| image, pixel_to_nanometre_scaling_factor = stp.load_stp(self.filepath) | ||
| elif self.suffix == ".top": | ||
|
|
@@ -82,13 +128,48 @@ def load(self) -> tuple[npt.NDArray | str, float | None]: # noqa: C901 | |
| f"'{self.channel}' not in available image keys: " | ||
| f"{[im for im in image_keys if im in topostats_keys]}" | ||
| ) from exc | ||
| elif self.suffix == ".bin": | ||
| image, pixel_to_nanometre_scaling_factor = raw_bin.load_bin(self.filepath, **self.kwargs) | ||
| else: | ||
| raise ValueError(f"File type '{self.suffix}' is not currently handled by AFMReader.") | ||
|
|
||
| return image, pixel_to_nanometre_scaling_factor | ||
|
|
||
| except ValueError as e: | ||
| logger.error(f"{e}") | ||
| return (e, None) # cheeky return of an image, px2nm-like tuple object to propagate error message to Napari | ||
| raise e | ||
|
|
||
| # scope for a "check what channels are available" function similar to above. | ||
| def get_available_channels(self): # noqa: C901 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guessing this is for the napari feature to list the channels? Well done if this is all working, that's a lot of work.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! |
||
| """ | ||
| Get the available channels for the file type. | ||
|
|
||
| Returns | ||
| ------- | ||
| list | ||
| List of available channels. | ||
| """ | ||
| if self.suffix == ".asd": | ||
| available_channels = asd.get_asd_channels(self.filepath) | ||
| elif self.suffix == ".gwy": | ||
| available_channels = gwy.get_gwy_channels(self.filepath) | ||
| elif self.suffix == ".ibw": | ||
| available_channels = ibw.get_ibw_channels(self.filepath) | ||
| elif self.suffix in [".jpk", ".jpk-qi-image"]: | ||
| available_channels = jpk.get_jpk_channels(self.filepath) | ||
| elif self.suffix == ".spm": | ||
| available_channels = spm.get_spm_channels(self.filepath) | ||
| elif self.suffix == ".h5-jpk": | ||
| available_channels = h5_jpk.get_h5jpk_channels(self.filepath) | ||
| elif self.suffix == ".jpk-qi-data": | ||
| if "jpk_qi_loader" not in self.cached_data: | ||
| self.cached_data["jpk_qi_loader"] = jpk_qi.jpk_qi_loader(filepath=self.filepath, **self.kwargs) | ||
| available_channels = self.cached_data["jpk_qi_loader"].get_available_channels() | ||
| elif self.suffix == ".topostats": | ||
| available_channels = ["image", "image_original"] | ||
| elif self.suffix == ".bin": | ||
| available_channels = raw_bin.get_bin_channels() | ||
| elif self.suffix in [".stp", ".top"]: | ||
| return [] | ||
| else: | ||
| raise ValueError(f"File type '{self.suffix}' is not currently handled by AFMReader.") | ||
| return available_channels | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have time to check this manually - does it work? There isn't a test but frankly we don't have the dev time to move slowly. If you say it works, this is fine with me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The channel fetching seems to work though I'm happy to quickly make some tests for them as that should be pretty quick.