-
Notifications
You must be signed in to change notification settings - Fork 0
Add .edf and .gb (General Binary) adapters for ALS BL7.3.3
#4
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
Open
Wiebke
wants to merge
24
commits into
main
Choose a base branch
from
bl733-adapters
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
7505223
Add `edf` and `gb` adapters for `tiled<0.1.0-b16`
Wiebke d772743
Add placeholders for adapter tests
Wiebke 45a12dc
Elevate edf function adapter to class
Wiebke 7dfbfc3
Elevate gb function adapter to class
Wiebke 20f1e92
Move txt parsing to separate `metadata` module
Wiebke 6c41eef
Add unit tests and example config for edf and gp adapters
Wiebke 3b643a1
Guard against spec duplication on adapter init
Wiebke 78d965a
Add `__init__.py` to `bl733` and bl733/adapters`
Wiebke b333712
Update base image to `ghcr.io/bluesky/tiled:0.2.8` and make `tiled[cl…
Wiebke 94bbd01
Add `mypy` pre-commit and update others
Wiebke 24e032b
Refactor tests to use fixtures and remove backend fixture
Wiebke 260ce9d
Add `__init__.py` files to tests and tests/bl733 directories
Wiebke a790c9c
Add mypy configuration `.mypy.ini` and update type hints in code
Wiebke a8b4424
Add `als_tiled[bl733]` to test dependencies,
Wiebke b44c4f9
Update mypy configuration for tests to not ignore errors
Wiebke 9a6dd60
Tests need Tiled server dependencies
Wiebke 7531ae7
Expands mypy type checks to include tests
Wiebke 32598f8
Use already defined detector size constants for tests
Wiebke 26de2ff
Only check header instead of full file for gb adapter
Wiebke deb0f68
Normalize date format across edf and gb adapters
Wiebke 96894d6
Install optional bl733 dependencies in docker image
Wiebke de65382
Debug logs for loading files
Wiebke 1036bae
No need to guard against date not being present in header
Wiebke 23723e3
Delete `test_main.py` placeholder
Wiebke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,7 @@ jobs: | |
|
|
||
| - name: Type checking with mypy | ||
| run: | | ||
| mypy src | ||
| mypy src tests | ||
|
|
||
| test: | ||
| runs-on: ubuntu-latest | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| [mypy] | ||
| python_version = 3.11 | ||
| mypy_path = src | ||
| ignore_errors = True | ||
| ignore_missing_imports = True | ||
| disallow_untyped_defs = False | ||
|
|
||
| [mypy-als_tiled.bl733.adapters.*] | ||
| ignore_errors = False | ||
| ignore_missing_imports = False | ||
| check_untyped_defs = True | ||
| disallow_untyped_defs = True | ||
| disallow_incomplete_defs = True | ||
| disallow_untyped_calls = True | ||
|
|
||
| [mypy-tests.*] | ||
| ignore_errors = False | ||
| disallow_untyped_defs = False | ||
|
|
||
| [mypy-fabio] | ||
| ignore_missing_imports = True | ||
|
|
||
| [mypy-tiled.*] | ||
| ignore_missing_imports = True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| file_extensions: | ||
| edf: application/x-edf | ||
| gb: application/x-gb | ||
|
|
||
| # Placeholder for future exporters that would allow download of arrays as edf or gb | ||
| # To be determined if this should include download of metadata as txt files | ||
| #media_types: | ||
| # array: | ||
| # application/x-edf: als_tiled.bl733.adapters.edf:export_edf | ||
| # application/x-gb: als_tiled.bl733.adapters.gb:export_gb | ||
|
|
||
| trees: | ||
| - path: / | ||
| tree: tiled.catalog:from_uri | ||
| args: | ||
| uri: ./data/catalog.db | ||
| readable_storage: [./data] | ||
| init_if_not_exists: true | ||
| adapters_by_mimetype: | ||
| application/x-edf: als_tiled.bl733.adapters.edf:EDFAdapter | ||
| application/x-gb: als_tiled.bl733.adapters.gb:GeneralBinaryPilatus2MAdapter |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import logging | ||
| from datetime import datetime | ||
| from typing import Any, Optional | ||
|
|
||
| import fabio | ||
| from tiled.adapters.array import ArrayAdapter | ||
| from tiled.adapters.utils import init_adapter_from_catalog | ||
| from tiled.catalog.orm import Node | ||
| from tiled.structures.array import ArrayStructure | ||
| from tiled.structures.core import Spec, StructureFamily | ||
| from tiled.structures.data_source import DataSource | ||
| from tiled.type_aliases import JSON | ||
| from tiled.utils import path_from_uri | ||
|
|
||
| from als_tiled.bl733.adapters.metadata import parse_txt_accompanying_edf | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class EDFAdapter(ArrayAdapter): | ||
| structure_family = StructureFamily.array | ||
|
|
||
| def __init__( | ||
| self, | ||
| data_uri: str, | ||
| structure: Optional[ArrayStructure] = None, | ||
| metadata: Optional[JSON] = None, | ||
| specs: Optional[list[Spec]] = None, | ||
| **kwargs: Optional[Any], | ||
| ) -> None: | ||
| """Adapter for `.edf` files (e.g. PILATUS3 2M) at ALS beamline 7.3.3.""" | ||
| filepath = path_from_uri(data_uri) | ||
| logger.debug("Loading EDF file produced by ALS beamline 7.3.3: %s", filepath) | ||
|
|
||
| with fabio.open(filepath) as edf_file: | ||
| array = edf_file.data | ||
| metadata_edf = edf_file.header | ||
|
|
||
| date = datetime.strptime(metadata_edf["Date"], "%a %b %d %H:%M:%S %Y") | ||
| metadata_edf["Date"] = date.isoformat() | ||
|
|
||
| metadata = { | ||
| **(metadata or {}), | ||
| **metadata_edf, | ||
| **parse_txt_accompanying_edf(filepath), | ||
| } | ||
|
|
||
| edf_spec = Spec("als-bl733-edf", version="1.0") | ||
| specs = list(specs or []) | ||
| if edf_spec not in specs: | ||
| specs.append(edf_spec) | ||
| super().__init__( | ||
| array=array, | ||
| structure=structure or ArrayStructure.from_array(array), | ||
| metadata=metadata, | ||
| specs=specs, | ||
| **kwargs, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def from_catalog( | ||
| cls, | ||
| data_source: DataSource, | ||
| node: Node, | ||
| /, | ||
| **kwargs: Optional[Any], | ||
| ) -> "EDFAdapter": | ||
| return init_adapter_from_catalog(cls, data_source, node, **kwargs) | ||
|
|
||
| @classmethod | ||
| def from_uris( | ||
dylanmcreynolds marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cls, | ||
| data_uri: str, | ||
| **kwargs: Optional[Any], | ||
| ) -> "EDFAdapter": | ||
| return cls(data_uri, **kwargs) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import logging | ||
| import pathlib | ||
| from datetime import datetime | ||
| from typing import Any, Optional | ||
|
|
||
| import fabio | ||
| import numpy as np | ||
| from tiled.adapters.array import ArrayAdapter | ||
| from tiled.adapters.utils import init_adapter_from_catalog | ||
| from tiled.catalog.orm import Node | ||
| from tiled.structures.array import ArrayStructure | ||
| from tiled.structures.core import Spec, StructureFamily | ||
| from tiled.structures.data_source import DataSource | ||
| from tiled.type_aliases import JSON | ||
| from tiled.utils import path_from_uri | ||
|
|
||
| from als_tiled.bl733.adapters.metadata import parse_txt_accompanying_edf | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Pixel dimensions for the PILATUS 2M detector at ALS beamline 7.3.3 | ||
| PILATUS_2M_PIXELS_X = 1475 | ||
| PILATUS_2M_PIXELS_Y = 1679 | ||
|
|
||
|
|
||
| class GeneralBinaryPilatus2MAdapter(ArrayAdapter): | ||
| structure_family = StructureFamily.array | ||
|
|
||
| def __init__( | ||
| self, | ||
| data_uri: str, | ||
| structure: Optional[ArrayStructure] = None, | ||
| metadata: Optional[JSON] = None, | ||
| specs: Optional[list[Spec]] = None, | ||
| **kwargs: Optional[Any], | ||
| ) -> None: | ||
| """Adapter for a stitched detector image .gb produced at ALS beamline 7.3.3.""" | ||
| filepath_gb = path_from_uri(data_uri) | ||
|
Contributor
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. I think I'd like to used the unused logger here to printout what file is being processed.
Contributor
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. Added a |
||
| logger.debug("Loading GB file produced by ALS beamline 7.3.3: %s", filepath_gb) | ||
| data = np.fromfile(filepath_gb, dtype="<f4") | ||
| expected_size = PILATUS_2M_PIXELS_X * PILATUS_2M_PIXELS_Y | ||
| if data.size != expected_size: | ||
| raise ValueError( | ||
| f"Data size ({data.size}) does not match expected size " | ||
| f"({expected_size})." | ||
| ) | ||
| array = data.reshape((PILATUS_2M_PIXELS_Y, PILATUS_2M_PIXELS_X)) | ||
|
|
||
| metadata = { | ||
| **(metadata or {}), | ||
| **GeneralBinaryPilatus2MAdapter._parse_accompanying_metadata(filepath_gb), | ||
| } | ||
|
|
||
| gb_spec = Spec("als-bl733-gb", version="1.0") | ||
| specs = list(specs or []) | ||
| if gb_spec not in specs: | ||
| specs.append(gb_spec) | ||
| super().__init__( | ||
| array=array, | ||
| structure=structure or ArrayStructure.from_array(array), | ||
| metadata=metadata, | ||
| specs=specs, | ||
| **kwargs, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def from_catalog( | ||
| cls, | ||
| data_source: DataSource, | ||
| node: Node, | ||
| /, | ||
| **kwargs: Optional[Any], | ||
| ) -> "GeneralBinaryPilatus2MAdapter": | ||
| return init_adapter_from_catalog(cls, data_source, node, **kwargs) | ||
|
|
||
| @classmethod | ||
| def from_uris( | ||
| cls, | ||
| data_uri: str, | ||
| **kwargs: Optional[Any], | ||
| ) -> "GeneralBinaryPilatus2MAdapter": | ||
| return cls(data_uri, **kwargs) | ||
|
|
||
| @staticmethod | ||
| def _read_edf(filepath_edf: pathlib.Path) -> tuple[dict[str, Any], datetime | None]: | ||
| """Read one EDF file and its companion .txt, returning (metadata, date).""" | ||
| metadata_txt = parse_txt_accompanying_edf(filepath_edf) | ||
| if not filepath_edf.is_file(): | ||
| logger.warning( | ||
| f"GeneralBinary file is missing accompanying EDF file {filepath_edf}." | ||
| ) | ||
| return metadata_txt, None | ||
| header = fabio.openheader(filepath_edf).header | ||
| date = datetime.strptime(header["Date"], "%a %b %d %H:%M:%S %Y") | ||
| return {**metadata_txt, **header}, date | ||
|
|
||
| @staticmethod | ||
| def _parse_accompanying_metadata(filepath_gb: pathlib.Path) -> dict[str, Any]: | ||
| """Read the hi and lo EDF companions for a .gb file and merge their metadata.""" | ||
| filepath_edf_hi = pathlib.Path( | ||
| str(filepath_gb.with_suffix(".edf")).replace("sfloat", "hi") | ||
| ) | ||
| filepath_edf_lo = pathlib.Path( | ||
| str(filepath_gb.with_suffix(".edf")).replace("sfloat", "lo") | ||
| ) | ||
|
|
||
| metadata_hi, date_hi = GeneralBinaryPilatus2MAdapter._read_edf(filepath_edf_hi) | ||
| metadata_lo, date_lo = GeneralBinaryPilatus2MAdapter._read_edf(filepath_edf_lo) | ||
|
|
||
| combined_metadata = GeneralBinaryPilatus2MAdapter._combine_metadata( | ||
| metadata_hi, metadata_lo | ||
| ) | ||
|
|
||
| date = None | ||
| if date_hi is not None and date_lo is not None: | ||
| date = date_hi if date_hi > date_lo else date_lo | ||
| elif date_hi is not None: | ||
| date = date_hi | ||
| elif date_lo is not None: | ||
| date = date_lo | ||
| if date is not None: | ||
| combined_metadata["Date"] = date.isoformat() | ||
|
|
||
| return combined_metadata | ||
|
|
||
| @staticmethod | ||
| def _combine_metadata( | ||
| metadata_hi: dict[str, Any], metadata_lo: dict[str, Any] | ||
| ) -> dict[str, Any]: | ||
| """Combine metadata from hi and lo EDF files. | ||
|
|
||
| Keys with identical values are kept once. Keys with different values are | ||
| suffixed with _hi and _lo. | ||
| """ | ||
| combined_metadata = {} | ||
| for key in set(metadata_hi) | set(metadata_lo): | ||
| value_hi = metadata_hi.get(key) | ||
| value_lo = metadata_lo.get(key) | ||
| if value_hi == value_lo: | ||
| combined_metadata[key] = value_hi | ||
| else: | ||
| if value_hi is not None: | ||
| combined_metadata[f"{key}_hi"] = value_hi | ||
| if value_lo is not None: | ||
| combined_metadata[f"{key}_lo"] = value_lo | ||
| return combined_metadata | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 think I'd like to used the unused logger here to printout what file is being processed.
Uh oh!
There was an error while loading. Please reload this page.
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.
Added a
logger.debugcall.