diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..ce572b41ddd1758dd9b9ef852ab8dfd6e9c4572a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,53 @@ +image: ubuntu:latest + +stages: + - code_embed + - ruff + +variables: + GIT_STRATEGY: clone + +code_embedder: + stage: code_embed + script: + - cd code-embedder/ + - poetry install --only main + - README_PATHS=$(find .. -maxdepth 1 -name "*.md" -print) + - poetry run python ./src/main.py --readme-paths $README_PATHS + - cd .. + - README_PATHS=$(find . -maxdepth 1 -name "*.md" -print) + - git add $README_PATHS + - git diff --staged + - export BRANCH_NAME="${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-$CI_COMMIT_REF_NAME}" + - echo "branch name $BRANCH_NAME" + - git remote set-url origin "https://oauth2:${PERSONAL_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" + - echo "https://oauth2:${PERSONAL_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" + - if ! git diff --cached --exit-code; then + git commit -m "Apply Code Embedder"; + git push origin HEAD:$BRANCH_NAME; + else + echo "No changes to commit"; + fi + + before_script: + - apt-get update && apt-get install -y curl git python3 python3-pip + - curl -sSL https://install.python-poetry.org | python3 - + - export PATH="$HOME/.local/bin:$PATH" + - git clone https://github.com/kvankova/code-embedder.git --branch "v0.5.2" + - git config --global user.email $GITLAB_USER_EMAIL + - git config --global user.name $GITLAB_USER_NAME + only: + - merge_requests + + +ruff_linter: + stage: ruff + script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - source $HOME/.local/bin/env + - uv tool run ruff check + before_script: + - apt-get update && apt-get install -y curl + only: + - merge_requests + diff --git a/README.md b/README.md index 8fd4e0c2241bd668110e537d52f9462a7f68b0d8..ab4ddea5a110668e1599b8da12b1b555b7cbba8c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,128 @@ # Json Datadefinition  -This repository contains `.h5` I/O utilities for the RoboCT group at Deggendorf. The data structure is auto generated from [THD JSON](https://mygit.th-deg.de/roboct/definitions/json_schemas). -For information of the data structure please look at `THD JSON`. +This repository contains `.h5` I/O utilities for the RoboCT group at Deggendorf. The data structure is auto generated from [THD JSON](https://mygit.th-deg.de/roboct-public/roboct-schemas). +For information of the data structure / group names please look at `THD JSON`. +## Examples +The main groups for a RoboCT project can be seen here [here](./examples/README.md). +You can explore small dataset with the HDF5 Viewer. + + + + +## Usage + +### Initialize a File + +```python:../scripts/generate_example_projection.py +from h5py import File +import numpy as np +from h5schemas.projection.add_projection import ( + init_projection_dataset, + add_projection_sample, +) + + +def main(): + # Generate a file in write mode. + test_file = File("./examples/projection.h5", "w") + + # Initialize the .H5 structure + init_projection_dataset(test_file, False, (128, 128), (0.139, 0.139)) + + # Add dummy projections + add_projection_sample( + test_file, + 1e5 * np.random.random((128, 128)), + [0.0, 1.0, 2.0], + [3.0, 4.0, 5.0, 6.0], + [7.0, 8.0, 9.0], + [10.0, 11.0, 12.0, 13], + ) + add_projection_sample( + test_file, + 1e5 * np.random.random((128, 128)), + np.array([0.0, 1.0, 2.0]), + np.array([3.0, 4.0, 5.0, 6.0]), + np.array([7.0, 8.0, 9.0]), + np.array([10.0, 11.0, 12.0, 13]), + voltage_kv=100, + ) + + # Access data + print(test_file["/image/image_array"][...]) + + +if __name__ == "__main__": + main() + +``` + +### Append new Group + + +```python:../scripts/add_joint_states_to_projection.py +from h5py import File +import numpy as np +from h5schemas.projection.add_projection import ( + add_projection_sample, +) +from h5schemas.thd_joint_states.add_thd_joint_states import ( + init_thd_joint_states, + add_thd_joint_states_sample, +) + + +def main(): + # Generate a file in write mode. + test_file = File("./examples/projection.h5", "a") + + # Add a new projection sample + add_projection_sample( + test_file, + 1e5 * np.random.random((128, 128)), + [0.0, 1.0, 2.0], + [3.0, 4.0, 5.0, 6.0], + [7.0, 8.0, 9.0], + [10.0, 11.0, 12.0, 13], + ) + + # Add a new group + init_thd_joint_states(test_file) + add_thd_joint_states_sample(test_file, *np.arange(15)) + + +if __name__ == "__main__": + main() + +``` + +### Slice / Load a Dataset + +```python:../scripts/load_slice.py +from h5py import File +from h5schemas.load_utilities.projection import extract_projection, GeomOptions + + +def main(): + # Load the second projection from the dataset + test_file = File("./examples/projection.h5", "r") + projection = extract_projection(test_file, 1, GeomOptions.PROJECTION_GEOMETRY) + print(projection.image_array.shape) + + # Load the first to projections an slice the projection data [20:50, 50:100:2] + projection = extract_projection( + test_file, + slice(0, 2), + GeomOptions.PROJECTION_GEOMETRY, + crop=(slice(20, 50), slice(50, 100, 2)), + ) + print(projection.image_array.shape) + + +if __name__ == "__main__": + main() + +``` \ No newline at end of file diff --git a/dump.txt b/dump.txt new file mode 100644 index 0000000000000000000000000000000000000000..a0ceb046408602ca75221595cf71ebe0e692c5b3 Binary files /dev/null and b/dump.txt differ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0d198b88387dae6b58d232c1177f30dbf732af6a --- /dev/null +++ b/examples/README.md @@ -0,0 +1,80 @@ +```bash +.\examples\projection.h5 (5 objects) +├── detector (2 objects) +│ ├── image_dimensions_px (2 objects) +│ │ ├── u (1,), int32 +│ │ └── v (1,), int32 +│ └── pixel_pitch_mm (2 objects) +│ ├── u (1,), float64 +│ └── v (1,), float64 +├── image (2 objects) +│ ├── header (2 objects) +│ │ ├── timestamp (2,), float64 +│ │ └── uuid (2,), |S100 +│ └── image_array (2, 128, 128), uint16 +├── projection_geometry (6 objects) +│ ├── detector_center_orientation_quat (4 objects) +│ │ ├── w (2,), float64 +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── detector_center_position_mm (3 objects) +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── detector_center_rotation_matrix (2 objects) +│ │ ├── u (3 objects) +│ │ │ ├── x (0,), float64 +│ │ │ ├── y (0,), float64 +│ │ │ └── z (0,), float64 +│ │ └── v (3 objects) +│ │ ├── x (0,), float64 +│ │ ├── y (0,), float64 +│ │ └── z (0,), float64 +│ ├── focal_spot_orientation_quat (4 objects) +│ │ ├── w (2,), float64 +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── focal_spot_position_mm (3 objects) +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ └── header (2 objects) +│ ├── timestamp (2,), float64 +│ └── uuid (2,), |S100 +├── projection_geometry_nominal (6 objects) +│ ├── detector_center_orientation_quat (4 objects) +│ │ ├── w (2,), float64 +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── detector_center_position_mm (3 objects) +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── detector_center_rotation_matrix (2 objects) +│ │ ├── u (3 objects) +│ │ │ ├── x (0,), float64 +│ │ │ ├── y (0,), float64 +│ │ │ └── z (0,), float64 +│ │ └── v (3 objects) +│ │ ├── x (0,), float64 +│ │ ├── y (0,), float64 +│ │ └── z (0,), float64 +│ ├── focal_spot_orientation_quat (4 objects) +│ │ ├── w (2,), float64 +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ ├── focal_spot_position_mm (3 objects) +│ │ ├── x (2,), float64 +│ │ ├── y (2,), float64 +│ │ └── z (2,), float64 +│ └── header (2 objects) +│ ├── timestamp (2,), float64 +│ └── uuid (2,), |S100 +└── source (2 objects) + ├── current_ma (256,), float64 + └── voltage_kv (256,), float64 +``` \ No newline at end of file diff --git a/examples/h5_viewer.png b/examples/h5_viewer.png new file mode 100644 index 0000000000000000000000000000000000000000..95a56cbabf6766b6ce9e846c3aeac938cbbe282a Binary files /dev/null and b/examples/h5_viewer.png differ diff --git a/examples/projection.h5 b/examples/projection.h5 index f1ad6b84c210a3aa483789e5e64199d863689462..034f5085310773948e4d7abde21b05f05529cbb7 100644 Binary files a/examples/projection.h5 and b/examples/projection.h5 differ diff --git a/pyproject.toml b/pyproject.toml index a6b85451af5f87b673311c03700d55fc0f3554a6..a213c230b6d653dabb1528b8c4dda2880a110a27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ authors = [ requires-python = ">=3.10" dependencies = [ "h5py>=3.13.0", + "h5tree>=1.0", "jsonref>=1.1.0", "numpy==2.0.2", "pandas>=2.2.3", diff --git a/scripts/add_joint_states_to_projection.py b/scripts/add_joint_states_to_projection.py index bf82dac35060b4541c318c84b0d40f28cae299f0..c21a05ddf1c7726a19063045abfa3a195dbaac79 100644 --- a/scripts/add_joint_states_to_projection.py +++ b/scripts/add_joint_states_to_projection.py @@ -1,7 +1,6 @@ from h5py import File import numpy as np from h5schemas.projection.add_projection import ( - init_projection_dataset, add_projection_sample, ) from h5schemas.thd_joint_states.add_thd_joint_states import ( @@ -14,16 +13,7 @@ def main(): # Generate a file in write mode. test_file = File("./examples/projection.h5", "a") - # Initialize the .H5 structure - init_projection_dataset(test_file, False, (128, 128), (0.139, 0.139)) - - # # Add joint states group - # joint_states_group = test_file.require_group("/joint_states/") - - # Init joint states group - init_thd_joint_states(test_file) - - # Add dummy projections 1 + # Add a new projection sample add_projection_sample( test_file, 1e5 * np.random.random((128, 128)), @@ -32,22 +22,10 @@ def main(): [7.0, 8.0, 9.0], [10.0, 11.0, 12.0, 13], ) - add_thd_joint_states_sample(test_file, *np.arange(15)) - - # # Add dummy projections 2 - # add_projection_sample( - # test_file, - # 1e5 * np.random.random((128, 128)), - # np.array([0.0, 1.0, 2.0]), - # np.array([3.0, 4.0, 5.0, 6.0]), - # np.array([7.0, 8.0, 9.0]), - # np.array([10.0, 11.0, 12.0, 13]), - # voltage_kv=100, - # ) - # add_thd_joint_states_sample(joint_states_group, *np.arange(15, 30)) - # # Access data - # print(test_file["/image/image_array"][...]) + # Add a new group + init_thd_joint_states(test_file) + add_thd_joint_states_sample(test_file, *np.arange(15)) if __name__ == "__main__": diff --git a/scripts/load_slice.py b/scripts/load_slice.py new file mode 100644 index 0000000000000000000000000000000000000000..68c73df20a808c2d5f2fdfc743d3bae64e6e2c1e --- /dev/null +++ b/scripts/load_slice.py @@ -0,0 +1,22 @@ +from h5py import File +from h5schemas.load_utilities.projection import extract_projection, GeomOptions + + +def main(): + # Load the second projection from the dataset + test_file = File("./examples/projection.h5", "r") + projection = extract_projection(test_file, 1, GeomOptions.PROJECTION_GEOMETRY) + print(projection.image_array.shape) + + # Load the first to projections an slice the projection data [20:50, 50:100:2] + projection = extract_projection( + test_file, + slice(0, 2), + GeomOptions.PROJECTION_GEOMETRY, + crop=(slice(20, 50), slice(50, 100, 2)), + ) + print(projection.image_array.shape) + + +if __name__ == "__main__": + main() diff --git a/src/h5schemas/load_utilities/projection.py b/src/h5schemas/load_utilities/projection.py index d644d101f1f6af6d48a36fa1e8f6d1d181d6ebc3..d6f2914f1317c6161d93109c67fc36b4315c2888 100644 --- a/src/h5schemas/load_utilities/projection.py +++ b/src/h5schemas/load_utilities/projection.py @@ -1,4 +1,7 @@ -from h5schemas.load_utilities.projection_geometry import extract_projection_geometry, ProjectionGeometry +from h5schemas.load_utilities.projection_geometry import ( + extract_projection_geometry, + ProjectionGeometry, +) from h5py import Group import numpy as np from typing import NamedTuple, ClassVar, Literal @@ -24,13 +27,20 @@ class Projection(NamedTuple): timestamp: np.ndarray -def extract_projection(h5_group: Group, indices: slice | int, projection_geometry_group: str = GeomOptions.PROJECTION_GEOMETRY, crop: list[slice] | None = None) -> Projection: +def extract_projection( + h5_group: Group, + indices: slice | int, + projection_geometry_group: str = GeomOptions.PROJECTION_GEOMETRY, + crop: list[slice] | None = None, +) -> Projection: if crop is None: crop = (slice(0, None, 1), slice(0, None, 1)) if len(crop) != 3 and isinstance(indices, slice): crop = (slice(0, None), crop[0], crop[1]) - projection_geometry = extract_projection_geometry(h5_group[projection_geometry_group], indices) + projection_geometry = extract_projection_geometry( + h5_group[projection_geometry_group], indices + ) uuid = h5_group["image"]["header"]["uuid"][indices] timestamp = h5_group["image"]["header"]["timestamp"][indices] image = h5_group["image"]["image_array"][indices][crop] @@ -51,6 +61,11 @@ if __name__ == "__main__": print(projection) print(projection.image_array.shape) - projection = extract_projection(test_file, slice(0, 2), GeomOptions.PROJECTION_GEOMETRY, crop=(slice(20, 50), slice(50, 100))) + projection = extract_projection( + test_file, + slice(0, 2), + GeomOptions.PROJECTION_GEOMETRY, + crop=(slice(20, 50), slice(50, 100)), + ) print(projection) print(projection.image_array.shape) diff --git a/src/h5schemas/load_utilities/projection_geometry.py b/src/h5schemas/load_utilities/projection_geometry.py index 9fc22e490ffd3531b5cd0445a732115e30f91d1a..0df49d27d3fc5cf21d27af40bc8e247f96419818 100644 --- a/src/h5schemas/load_utilities/projection_geometry.py +++ b/src/h5schemas/load_utilities/projection_geometry.py @@ -14,7 +14,9 @@ class ProjectionGeometry(NamedTuple): timestamp: np.ndarray -def extract_projection_geometry(h5_group: Group, indices: slice | int) -> ProjectionGeometry: +def extract_projection_geometry( + h5_group: Group, indices: slice | int +) -> ProjectionGeometry: detector_center_position_mm = extract_vector( h5_group["detector_center_position_mm"], indices, CommonVectors.XYZ ) diff --git a/uv.lock b/uv.lock index 45678c87793b5e9721dccfc37a748a090db5fcd8..32df3014d7558e6da43e4e837b5989fd17f12e6f 100644 --- a/uv.lock +++ b/uv.lock @@ -97,6 +97,7 @@ version = "0.1.22" source = { editable = "." } dependencies = [ { name = "h5py" }, + { name = "h5tree" }, { name = "jsonref" }, { name = "numpy" }, { name = "pandas" }, @@ -107,6 +108,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "h5py", specifier = ">=3.13.0" }, + { name = "h5tree", specifier = ">=1.0" }, { name = "jsonref", specifier = ">=1.1.0" }, { name = "numpy", specifier = "==2.0.2" }, { name = "pandas", specifier = ">=2.2.3" }, @@ -114,6 +116,19 @@ requires-dist = [ { name = "thd-json", git = "https://mygit.th-deg.de/roboct-public/roboct-schemas" }, ] +[[package]] +name = "h5tree" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/9b/ea46f715d5c5059de88ca2f87a63e7cb9a54f2a6e6cf56e88234730b179a/h5tree-1.0.tar.gz", hash = "sha256:d3c6ac2c647ebf4ba37ee91bdbd30ba147e68b0449c05ce1fe2ff9e7e61d8e12", size = 3418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/f6/949bb1616da797ffe2badb6d09ee9664387c27105c28bac59917467c6254/h5tree-1.0-py3-none-any.whl", hash = "sha256:e7b83c6ea25fbd40dc05e8cf182aa43bde9bae19d88f7bed0308ff621d5c337e", size = 3466 }, +] + [[package]] name = "hatchling" version = "1.27.0" @@ -697,6 +712,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/fd/62f31643596f6ab71fc6d2a87acdee0bc01a03fbe1a7f3f6dc0c91e2546d/tables-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:757c6ea257c174af8036cf8f273ede756bbcd6db5ac7e2a4d64e788b0f371152", size = 7527953 }, ] +[[package]] +name = "termcolor" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/72/88311445fd44c455c7d553e61f95412cf89054308a1aa2434ab835075fc5/termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f", size = 13057 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/be/df630c387a0a054815d60be6a97eb4e8f17385d5d6fe660e1c02750062b4/termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8", size = 7755 }, +] + [[package]] name = "thd-json" version = "0.1.14"