Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • roboct-public/roboct-schemas
1 result
Show changes
Commits on Source (53)
Showing
with 786 additions and 0 deletions
__pycache__
.ruff_cache
.hypothesis
.venv
dist
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
3.10
# Json Datadefinition
![](./examples/logo.svg)
This repository contains data structure definitions for the RoboCT group at Deggendorf. It includes custom data structure schemas, example data files, and validation scripts.
- Example Files: Stored in the `example` folder, these files demonstrate data structure usage.
- Validation Scripts: Located in the `scripts` folder, these scripts validate `thd_json` data structure definitions against their schemas.
## THD JSON
This data structure use always a human-readable `.json` for storing header information.
## CLI
CLI tools for the validation of folder are automatically created.
To install the current tools just use:
```ps1
uv tool install --from git+https://mygit.th-deg.de/roboct/definitions/json_schemas thd-json
```
After the installation the validators are available as CLI:
```ps1
projection_validator(.exe) --help
```
### Projection
This schema defines the properties for projection data in THD JSON format.
| Property | Type | Description | Constraints | Required |
|-------------------------------|----------|---------------------------------------------------------|---------------------------|----------|
| `header` | header | Header to identify File. | format: Header | Yes |
| `focal_spot_position_mm` | array | Position of the focal spot in millimeters | 3 numbers | Yes |
| `focal_spot_orientation_quat` | array | Quaternion representing the focal spots orientation | 4 numbers | No |
| `detector_center_position_mm` | array | Center position of the detector in millimeters | 3 numbers | Yes |
| `detector_horizontal_vector` | array | Horizontal orientation vector of the detector | 3 numbers | No |
| `detector_vertical_vector` | array | Vertical orientation vector of the detector | 3 numbers | No |
| `detector_center_orientation_quat` | array | Quaternion representing the detector's center orientation | 4 numbers | Yes |
| `pixel_pitch_width_mm` | number | Pixel pitch in millimeters (width) | Minimum 0.001 | Yes |
| `pixel_pitch_height_mm` | number | Pixel pitch in millimeters (height) | Minimum 0.001 | Yes |
| `image_width_px` | integer | Width of the image in pixels | Minimum 1 | Yes |
| `image_height_px` | integer | Height of the image in pixels | Minimum 1 | Yes |
| `projection_matrix_cera` | array | Projection matrix for CERA, representing transformations | 3 arrays of 4 numbers | No |
### Header
| Property | Type | Description | Constraints | Required |
|------------|--------|--------------------------------------------------|--------------------------|----------|
| `timestamp`| string | Timestamp, e.g., 2018-11-13T20:20:39+00:00 | format: date-time | Yes |
| `uuid` | string | Unique identifier | format: uuid | Yes |
### Examples
Python example to validate a projection file:
```python:../scripts/validate_file.py
from thd_json.projection import get_projection_validator
from pathlib import Path
FILE = Path("./examples/generated/example_projection.json")
def main():
projection_validator = get_projection_validator()
projection_validator.file(FILE)
if __name__ == "__main__":
main()
```
Python example to validate a folder of projections
```python:../scripts/validate_folder.py:o:main
def main():
projection_validator = get_projection_validator()
projection_validator.folder(FOLDER)
```
## Contribute
- For changes please make a pull request.
- Update the version and date number of the schema.
- Add a new datatyp:
- add a moulde to `src`\ `thd_json`\ `new_data_type`
- add a validation function to the `__init__.py` of the `new_data_typ`. Example of [`projection`.\__init\__.py"](./src/thd_json/projection/__init__.py):
```python:../src/thd_json/projection/__init__.py
from thd_json import Validator
from thd_json.header import JsonHeader, generate_header
from thd_json.projection_geometry import get_projection_geometry_dict
from thd_json.detector import get_detector_dict
from pathlib import Path
import argparse
import numpy as np
def get_projection_validator(json_suffix: str = "*.json") -> Validator:
return Validator(Path(__file__).parent / Path("projection.json"), json_suffix)
def projection_cli():
parser = argparse.ArgumentParser(
description="JSON THD Projection Geometry Validator CLI with uv."
)
parser.add_argument("folder", help="Folder to check.", type=str)
parser.add_argument(
"suffix", help="Projection suffix.", default="*.json", type=Path, nargs="?"
)
args = parser.parse_args()
suffix = str(args.suffix)
if not suffix.startswith("*"):
raise ValueError(f'The suffix must always start with: "*". \nIt is: {suffix}')
validator = get_projection_validator(suffix)
folder = Path(args.folder)
if not folder.exists():
raise FileNotFoundError(f"Folder: {folder} does not exist")
validator.folder(Path(args.folder))
def get_projection_dict(
image_path: Path,
focal_spot_position_mm: np.ndarray,
detector_center_position_mm: np.ndarray,
detector_center_orientation_quat: np.ndarray,
image_dimensions_px: np.ndarray,
pixel_pitch_mm: np.ndarray,
header: JsonHeader | None = None,
):
if header is None:
header = generate_header()
projection = dict()
projection["projection_geometry"] = get_projection_geometry_dict(
focal_spot_position_mm,
detector_center_position_mm,
detector_center_orientation_quat,
header,
)
projection["detector"] = get_detector_dict(image_dimensions_px, pixel_pitch_mm)
projection["image"] = {"image_path", str(image_path)}
return projection
if __name__ == "__main__":
projection_cli()
```
- add the CLI script to the `pyproject.toml`
```toml
[project.scripts]
projection_validator = "thd_json.projection:projection_cli"
...
new_data_typ_validator = "thd_json.new_data_typ:new_data_typ_cli"
```
{
"projection_geometry": {
"focal_spot_position_mm": {
"x": 0.0,
"y": 0.0,
"z": 100.0
},
"detector_center_position_mm": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"detector_center_orientation_quat": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 1.0
},
"focal_spot_orientation_quat": {
"x": 0.0,
"y": 0.0,
"z": 0.0,
"w": 1.0
},
"header": {
"uuid": "1c32ae9b-ad9a-11ef-af9a-f46d3febaf33"
}
},
"detector": {
"pixel_pitch_mm": {
"u": 0.10000000149011612,
"v": 0.10000000149011612
},
"image_dimensions_px": {
"u": 1000,
"v": 1000
}
},
"image": {
"image_path": "1c32ae9b-ad9a-11ef-af9a-f46d3febaf33_projection.tif"
}
}
\ No newline at end of file
File added
{
"image_dimensions_px": {
"u": 1,
"v": 1
},
"pixel_pitch_mm": {
"u": 0.001,
"v": 0.001
}
}
\ No newline at end of file
{
"uuid": ""
}
\ No newline at end of file
{
"detector": {
"image_dimensions_px": {
"u": 1,
"v": 1
},
"pixel_pitch_mm": {
"u": 0.001,
"v": 0.001
}
},
"image": {
"image_path": ".tif"
},
"projection_geometry": {
"detector_center_orientation_quat": {
"w": 0.0,
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"detector_center_position_mm": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"focal_spot_position_mm": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"header": {
"uuid": ""
}
}
}
\ No newline at end of file
{
"header": {
"uuid": ""
},
"roi_center_position_mm": [
0.0,
0.0,
0.0
],
"roi_orientation_quat": [
0.0,
0.0,
0.0,
0.0
],
"roi_resolution_mm": [
0.0,
0.0,
0.0
],
"roi_voxels": [
1,
1,
1
]
}
\ No newline at end of file
{
"current_ma": 0.001,
"voltage_kv": 0.001
}
\ No newline at end of file
examples/logo.png

41.5 KiB

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
sodipodi:docname="logo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.1180205"
inkscape:cx="299.63672"
inkscape:cy="419.04419"
inkscape:window-width="3200"
inkscape:window-height="1711"
inkscape:window-x="3191"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<linearGradient
id="linearGradient7"
inkscape:collect="always">
<stop
style="stop-color:#f2351f;stop-opacity:1;"
offset="0"
id="stop8" />
<stop
style="stop-color:#e88800;stop-opacity:0.046875;"
offset="0.89603281"
id="stop9" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient7"
id="radialGradient9"
cx="2.8596487"
cy="96.505142"
fx="2.8596487"
fy="96.505142"
r="50"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-4.9612089,1.5676235,-1.4017317,-4.4361952,234.55247,439.48857)" />
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Colorize"
id="filter15"
x="0"
y="0"
width="1"
height="1">
<feComposite
in2="SourceGraphic"
operator="arithmetic"
k1="1.15854"
k2="0"
result="composite1"
id="feComposite14" />
<feColorMatrix
in="composite1"
values="1"
type="saturate"
result="colormatrix1"
id="feColorMatrix14" />
<feFlood
flood-opacity="0.568627"
flood-color="rgb(240,249,98)"
result="flood1"
id="feFlood14" />
<feBlend
in="flood1"
in2="colormatrix1"
mode="multiply"
result="blend1"
id="feBlend14" />
<feBlend
in2="blend1"
mode="normal"
result="blend2"
id="feBlend15" />
<feColorMatrix
in="blend2"
values="1"
type="saturate"
result="colormatrix2"
id="feColorMatrix15" />
<feComposite
in="colormatrix2"
in2="SourceGraphic"
operator="in"
k2="1"
result="fbSourceGraphic"
id="feComposite15" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix16" />
<feComposite
id="feComposite16"
in2="fbSourceGraphic"
operator="arithmetic"
k1="1.15854"
k2="0"
result="composite1"
in="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix17"
in="composite1"
values="1"
type="saturate"
result="colormatrix1" />
<feFlood
id="feFlood17"
flood-opacity="0.568627"
flood-color="rgb(240,249,98)"
result="flood1" />
<feBlend
id="feBlend17"
in="flood1"
in2="colormatrix1"
mode="multiply"
result="blend1" />
<feBlend
id="feBlend18"
in2="blend1"
mode="normal"
result="blend2" />
<feColorMatrix
id="feColorMatrix18"
in="blend2"
values="1"
type="saturate"
result="colormatrix2" />
<feComposite
id="feComposite18"
in="colormatrix2"
in2="fbSourceGraphic"
operator="in"
k2="1"
result="composite2" />
</filter>
</defs>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:0.264583;fill-opacity:0"
id="rect1"
width="100"
height="100"
x="0"
y="0"
inkscape:label="rect1" />
<rect
style="fill:url(#radialGradient9);fill-opacity:1;stroke-width:0.264583;filter:url(#filter15)"
id="rect2"
width="100"
height="100"
x="0"
y="0" />
<text
xml:space="preserve"
style="font-size:25.4px;letter-spacing:2.64583px;fill:#1eb7be;fill-opacity:1;stroke-width:0.264583"
x="47.415554"
y="21.421324"
id="text9"><tspan
sodipodi:role="line"
id="tspan9"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25.4px;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';letter-spacing:2.64583px;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
x="47.415554"
y="21.421324">THD</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05556px;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';letter-spacing:2.38125px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
x="2.3253708"
y="94.57032"
id="text10"><tspan
sodipodi:role="line"
id="tspan10"
style="font-size:7.05556px;letter-spacing:2.38125px;word-spacing:0px;stroke-width:0.264583"
x="2.3253708"
y="94.57032"
dx="0">TWIN ROBOTIC CT</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.7px;font-family:'Cascadia Code';-inkscape-font-specification:'Cascadia Code';letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
x="50.820835"
y="49.816677"
id="text10-8"><tspan
sodipodi:role="line"
style="font-size:12.7px;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;stroke-width:0.264583"
x="50.820835"
y="49.816677"
id="tspan1">JSON<tspan
style="text-align:center;letter-spacing:1.49225px;text-anchor:middle"
id="tspan3" /></tspan><tspan
sodipodi:role="line"
style="font-size:12.7px;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;stroke-width:0.264583"
x="50.820835"
y="65.691673"
id="tspan2">SCHEMAS</tspan></text>
</g>
</svg>
{
"header":
{
"uuid": "b83d81a1-333f-448e-af75-7c7db317fd96",
"timestamp": "2018-11-13T20:20:39+00:00"
},
"image":
{
"uuid": "b83d81a1-333f-448e-af75-7c7db317fd96",
"timestamp": "2018-11-13T20:20:39+00:00",
"image_path": "test_thd.tif"
},
"projection_geometry": {
"focal_spot_position_mm": [
2724.71682,
1690.5,
193.29345
],
"detector_center_position_mm": [
1902.85619,
1690.5,
121.38996
],
"detector_horizontal_vector": [
-0.08715574274765814,
0.0,
0.9961946980917458
],
"detector_vertical_vector": [
-0.0,
-1.0000000000000002,
0.0
],
"detector_center_orientation_quat": [
0.6755902076156602,
-0.0,
0.737277336810124,
0.0
]
},
"detector": {
"pixel_pitch_width_mm": 0.07479989711934157,
"pixel_pitch_height_mm": 0.07479989711934157,
"image_width_px": 1944,
"image_height_px": 1536
}
}
\ No newline at end of file
File added
[project]
name = "thd-json"
version = "0.1.9"
description = "THD JSON schema and validation tools."
authors = [
{ name = "swittl", email = "simon.wittl@th-deg.de" }
]
requires-python = ">=3.10"
dependencies = [
"hypothesis>=6.119.0",
"hypothesis-jsonschema>=0.23.1",
"json-schema>=0.3",
"jsonschema>=4.23.0",
"numpy>=2.1.3",
"pillow>=11.0.0",
"hatchling>=1.26.3",
]
[project.scripts]
projection_validator = "thd_json.projection:projection_cli"
header_validator = "thd_json.header:header_cli"
generate_header = "thd_json.header:generate_header_cli"
image_validator = "thd_json.image:image_cli"
detector_validator = "thd_json.detector:detector_cli"
source_validator = "thd_json.source:source_cli"
projection_geometry_validator = "thd_json.projection_geometry:projection_geometry_cli"
[project.optional-dependencies]
load = [
"numpy>=2.1.3",
"pillow>=11.0.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
from hypothesis_jsonschema import from_schema
from thd_json.projection import get_projection_validator
from thd_json.header import get_header_validator
from thd_json.source import get_source_validator
from thd_json.detector import get_detector_validator
from thd_json.roi import get_roi_validator
from hypothesis import given, settings
import json
from pathlib import Path
SAVE_FOLDER = Path("./examples/generated")
def main():
for name, get_validator in [
("projection", get_projection_validator),
("header", get_header_validator),
("source", get_source_validator),
("detector", get_detector_validator),
("roi", get_roi_validator),
]:
validator = get_validator()
schema = validator.get_schema()
strategy = from_schema(schema)
@settings(max_examples=1)
@given(strategy)
def schema(data):
with open(SAVE_FOLDER / f"example_{name}.json", "w") as f:
json.dump(data, f, indent=4)
return
schema()
if __name__ == "__main__":
main()
from thd_json.projection_geometry import get_projection_geometry_dict
import numpy as np
def main():
focal_spot_position_mm = np.array([100.0, 50.0, 75.0]) # Example 3D position
detector_center_position_mm = np.array([200.0, 150.0, 250.0]) # Example 3D position
detector_center_orientation_quat = np.array(
[0.0, 0.0, 0.0, 1.0]
) # Quaternion (w, x, y, z)
result = get_projection_geometry_dict(
focal_spot_position_mm=focal_spot_position_mm,
detector_center_position_mm=detector_center_position_mm,
detector_center_orientation_quat=detector_center_orientation_quat,
header=None,
)
# Print the result
print(result)
if __name__ == "__main__":
main()
from thd_json.load_projection import load_thd_projection
from pathlib import Path
LOAD_PATH = Path(
"./examples/artist/856cbbd8-ad99-11ef-8841-f46d3febaf33_projection.json"
)
def main():
meta_data, projection = load_thd_projection(LOAD_PATH)
print(meta_data)
print(f"Projection Shape: {projection.shape}")
if __name__ == "__main__":
main()
import json
from pathlib import Path
from importlib.metadata import version
from datetime import datetime
"""
This script updates the version and date metadata in all JSON files
found within the './src/thd_json' directory and its subdirectories.
It performs the following:
1. Reads each JSON file.
2. Updates the "version" field with the version of the installed 'thd_json' package.
3. Updates the "date" field with the current date formatted as DD.MM.YYYY.
4. Writes the updated JSON data back to the file.
Usage:
- Ensure the 'thd_json' package is installed in your environment.
- Run the script, and it will process all JSON files within the specified directory.
Dependencies:
- json: To handle JSON data.
- pathlib.Path: For traversing the file system.
- importlib.metadata.version: To retrieve the version of the 'thd_json' package.
- datetime: To get and format the current date.
"""
def change_version(file_path: Path):
with open(file_path, "r") as f:
schema_dict = json.load(f)
current_datetime = datetime.now()
formatted_date = current_datetime.strftime("%d.%m.%Y")
schema_dict["version"] = version("thd_json")
schema_dict["date"] = formatted_date
with open(file_path, "w") as f:
json.dump(schema_dict, f, indent=2)
def main():
for file in Path("./src/thd_json").rglob("*.json"):
change_version(file)
print(version("thd_json"))
if __name__ == "__main__":
main()