diff --git a/README.md b/README.md index e0d3ab963c5c78c76822abfa2eb80b0996b10ca4..a13e1f787ff44f440c855bc0bcea281f298b4576 100644 --- a/README.md +++ b/README.md @@ -55,15 +55,25 @@ Python example to validate a projection file: ```python:../scripts/validate_file.py 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 pathlib import Path -FILE = Path("./examples/generated/example_projection.json") +FILE_H = Path("./examples/generated/example_header.json") +FILE_P = Path("./examples/generated/example_projection.json") +FILE_S = Path("./examples/generated/example_source.json") def main(): + source_validator = get_source_validator() + source_validator.file(FILE_S) + projection_validator = get_projection_validator() - projection_validator.file(FILE) + projection_validator.file(FILE_P) + + header_validator = get_header_validator() + header_validator.file(FILE_H) if __name__ == "__main__": diff --git a/examples/generated/example_header.json b/examples/generated/example_header.json index ea5c26e7d61a140a111d0b00efd5a2dd10b876ea..0013cf5cdc39ff3d80de18864b429be1c4bbc013 100644 --- a/examples/generated/example_header.json +++ b/examples/generated/example_header.json @@ -1,3 +1,3 @@ { - "uuid": "" + "uuid": "0060361f-056d-11f0-b60b-f46d3febaf36" } \ No newline at end of file diff --git a/examples/generated/example_projection.json b/examples/generated/example_projection.json index aea1778928b08c5b3f4b18cb19ae41eda2a8724e..4f6758240b259f5a6a958369ca469019e46cebe7 100644 --- a/examples/generated/example_projection.json +++ b/examples/generated/example_projection.json @@ -24,13 +24,19 @@ "y": 0.0, "z": 0.0 }, + "focal_spot_orientation_quat": { + "w": 0.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, "focal_spot_position_mm": { "x": 0.0, "y": 0.0, "z": 0.0 }, "header": { - "uuid": "" + "uuid": "005f11bd-056d-11f0-b60b-f46d3febaf36" } } } \ No newline at end of file diff --git a/examples/generated/example_roi.json b/examples/generated/example_roi.json index 8a3fe382145456228eae386d479b968784c57d29..9472b2c7c1dd9c594641a913665f73fc9263322a 100644 --- a/examples/generated/example_roi.json +++ b/examples/generated/example_roi.json @@ -1,6 +1,6 @@ { "header": { - "uuid": "" + "uuid": "00670fe0-056d-11f0-b60b-f46d3febaf36" }, "roi_center_position_mm": [ 0.0, diff --git a/pyproject.toml b/pyproject.toml index 2b411108e81c247176850e3cab06ebac355fcd1d..fe481c203f9643437bcc1f240116f00bc43704a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "thd-json" -version = "0.1.10" +version = "0.1.11" description = "THD JSON schema and validation tools." authors = [ { name = "swittl", email = "simon.wittl@th-deg.de" } @@ -14,6 +14,7 @@ dependencies = [ "numpy>=2.1.3", "pillow>=11.0.0", "hatchling>=1.26.3", + "jsonref>=1.1.0", ] [project.scripts] diff --git a/scripts/generate_example_data.py b/scripts/generate_example_data.py index 6627843c3d1acf4d6a18ad1cd8408af745fe26cf..b9a62e6304d4c491e9564fe1e6e0ec7d3de3093a 100644 --- a/scripts/generate_example_data.py +++ b/scripts/generate_example_data.py @@ -1,6 +1,6 @@ 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.header import get_header_validator, generate_header from thd_json.source import get_source_validator from thd_json.detector import get_detector_validator from thd_json.roi import get_roi_validator @@ -12,6 +12,16 @@ from pathlib import Path SAVE_FOLDER = Path("./examples/generated") +def set_uuid(src: dict): + for k, v in src.items(): + if isinstance(v, dict): + set_uuid(v) + + if k == "uuid": + header = generate_header() + src[k] = header.uuid + + def main(): for name, get_validator in [ ("projection", get_projection_validator), @@ -28,6 +38,7 @@ def main(): @given(strategy) def schema(data): with open(SAVE_FOLDER / f"example_{name}.json", "w") as f: + set_uuid(data) json.dump(data, f, indent=4) return diff --git a/scripts/validate_file.py b/scripts/validate_file.py index cb1c10005f73ae37b541f7704f40e6d6fe635662..2a494644d9debfa19e9a2936cf2e313296e703dd 100644 --- a/scripts/validate_file.py +++ b/scripts/validate_file.py @@ -1,13 +1,23 @@ 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 pathlib import Path -FILE = Path("./examples/generated/example_projection.json") +FILE_H = Path("./examples/generated/example_header.json") +FILE_P = Path("./examples/generated/example_projection.json") +FILE_S = Path("./examples/generated/example_source.json") def main(): + source_validator = get_source_validator() + source_validator.file(FILE_S) + projection_validator = get_projection_validator() - projection_validator.file(FILE) + projection_validator.file(FILE_P) + + header_validator = get_header_validator() + header_validator.file(FILE_H) if __name__ == "__main__": diff --git a/src/thd_json/detector/detector.json b/src/thd_json/detector/detector.json index 06f084a25419133fee44da422a59cf03150ed7a4..2eb3f6df16a4f9f3e15c2a2c6572545f07f645ee 100644 --- a/src/thd_json/detector/detector.json +++ b/src/thd_json/detector/detector.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Detector File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "pixel_pitch_mm": { "type": "object", diff --git a/src/thd_json/header/header.json b/src/thd_json/header/header.json index 69b72f29b959f3d31792d8bae13a40f4933333ba..38f1ba0c688ccfb2870d86396d1f5413229e7dbc 100644 --- a/src/thd_json/header/header.json +++ b/src/thd_json/header/header.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Data Metadata Header File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "timestamp": { "type": "string", diff --git a/src/thd_json/image/image.json b/src/thd_json/image/image.json index a353d7c299f8e157e4d98d4bc5d0ef3a3aaedc3e..00462f8ddfcb4341e7c8a550724218f424a4ac41 100644 --- a/src/thd_json/image/image.json +++ b/src/thd_json/image/image.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Projection Image File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "header": { "$ref": "../header/header.json" diff --git a/src/thd_json/projection/projection.json b/src/thd_json/projection/projection.json index 90a404f8b6786cbba5801cc92dc8ecdc9edada15..338f5b191f46a15f31c8d0a730abef4a06752efb 100644 --- a/src/thd_json/projection/projection.json +++ b/src/thd_json/projection/projection.json @@ -2,24 +2,23 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Projection File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "image": { - "$ref": "./image/image.json" + "$ref": "../image/image.json" }, "projection_geometry": { - "$ref": "./projection_geometry/projection_geometry.json" + "$ref": "../projection_geometry/projection_geometry.json" }, - "projection_geometry_nominal": - { - "$ref": "./projection_geometry/projection_geometry.json" + "projection_geometry_nominal": { + "$ref": "../projection_geometry/projection_geometry.json" }, "detector": { - "$ref": "./detector/detector.json" + "$ref": "../detector/detector.json" }, "source": { - "$ref": "./source/source.json" + "$ref": "../source/source.json" } }, "required": [ diff --git a/src/thd_json/projection_geometry/projection_geometry.json b/src/thd_json/projection_geometry/projection_geometry.json index 87870b18d84fe24ff76d89872d34d7acc8ddb34d..6db5d1a987ea9938d632049e0e0503cd3a4eabf1 100644 --- a/src/thd_json/projection_geometry/projection_geometry.json +++ b/src/thd_json/projection_geometry/projection_geometry.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Projection Geometry File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "header": { "$ref": "../header/header.json" diff --git a/src/thd_json/roi/roi.json b/src/thd_json/roi/roi.json index b4722741643d8cac6fc15a24cc1dc2ade2a24082..93ef935367733a9496ec936614986e964af9d59b 100644 --- a/src/thd_json/roi/roi.json +++ b/src/thd_json/roi/roi.json @@ -2,14 +2,14 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD ROI File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "header": { - "$ref": "./header/header.json" + "$ref": "../header/header.json" }, "volume": { - "$ref": "./volume/volume.json" + "$ref": "../volume/volume.json" }, "roi_center_position_mm": { "type": "array", diff --git a/src/thd_json/source/source.json b/src/thd_json/source/source.json index 459422bb90533ef1aecd255c4f6dd5c064016ed3..09200901060503d671dee3e4fdda09a9dad54ef3 100644 --- a/src/thd_json/source/source.json +++ b/src/thd_json/source/source.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD X-Ray Source File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "voltage_kv": { "type": "number", @@ -12,7 +12,7 @@ }, "current_ma": { "type": "number", - "description": "Source current in µA.", + "description": "Source current in \u00c2\u00b5A.", "minimum": 0.001 } }, diff --git a/src/thd_json/validation.py b/src/thd_json/validation.py index 50859cc094f4c00096adccdef5d26b1882d036ec..9239c286a13020cac5de596584234400006cbbe8 100644 --- a/src/thd_json/validation.py +++ b/src/thd_json/validation.py @@ -1,7 +1,8 @@ import json from pathlib import Path -from jsonschema import ValidationError, RefResolver +from jsonschema import ValidationError from jsonschema.validators import Draft202012Validator +import jsonref class Validator: @@ -11,20 +12,14 @@ class Validator: print(f"Validator for schema: {json_schema} \nSuffix: {json_suffix}\n") def file(self, file_path: Path) -> bool: - with ( - open(str(self.json_schema)) as schema_file, - open(str(file_path)) as data_file, - ): - schema = json.load(schema_file) + with open(str(file_path)) as data_file: data = json.load(data_file) - schema_path = self.json_schema.parent.absolute().as_uri() - resolver = RefResolver(base_uri=schema_path, referrer=schema) - + self.json_schema.parent.absolute().as_uri() + "/" + schema = self.get_schema() try: Draft202012Validator( schema=schema, format_checker=Draft202012Validator.FORMAT_CHECKER, - resolver=resolver, ).validate(data) print(f"Validate file: {file_path.parent} / {file_path.name}\n") @@ -40,29 +35,7 @@ class Validator: return True def get_schema(self): - schema_path = self.json_schema.parent.absolute().as_uri() + schema_path = self.json_schema.parent.absolute().as_uri() + "/" with open(str(self.json_schema)) as schema_file: - schema = json.load(schema_file) - - resolver = RefResolver(base_uri=schema_path, referrer=schema) - - return self.resolve_ref(resolver, schema) - - def resolve_ref(self, resolver, schema_part): - if isinstance(schema_part, dict) and "$ref" in schema_part: - # Resolve the reference - ref = schema_part["$ref"] - with resolver.resolving(ref) as resolved: - return self.resolve_ref( - resolver, resolved - ) # Recursively resolve nested references - elif isinstance(schema_part, dict): - # Resolve each value in the dictionary - return { - key: self.resolve_ref(resolver, value) - for key, value in schema_part.items() - } - elif isinstance(schema_part, list): - # Resolve each item in the list - return [self.resolve_ref(resolver, item) for item in schema_part] - return schema_part + schema = jsonref.load(schema_file, schema_path) + return schema diff --git a/src/thd_json/volume/volume.json b/src/thd_json/volume/volume.json index 4c68e433213f954a03b19bf519a7b75eaf17cd0e..135ff14ac2d3765e68122a45c1eeadeb0010f2df 100644 --- a/src/thd_json/volume/volume.json +++ b/src/thd_json/volume/volume.json @@ -2,8 +2,8 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "THD Projection Volume File Schema", "type": "object", - "version": "0.1.9", - "date": "28.11.2024", + "version": "0.1.11", + "date": "20.03.2025", "properties": { "header": { "$ref": "../header/header.json" diff --git a/uv.lock b/uv.lock index 87eac4baa8ee4124a5ba9e7b96ead4cf7f8b756d..5cb9495986414bf53084afd1f3b17341e0368223 100644 --- a/uv.lock +++ b/uv.lock @@ -68,6 +68,15 @@ version = "0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2a/04/855eecfdd379ff774ce86770808c6663ad4fdf14c43ed4e6fd7156aa5cf9/json_schema-0.3.tar.gz", hash = "sha256:a164efbb405f535615e58aff191b55fbfdad61d2ff0e7bfce6acf086358ca4b3", size = 5425 } +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425 }, +] + [[package]] name = "jsonschema" version = "4.23.0" @@ -347,13 +356,14 @@ wheels = [ [[package]] name = "thd-json" -version = "0.1.9" +version = "0.1.11" source = { editable = "." } dependencies = [ { name = "hatchling" }, { name = "hypothesis" }, { name = "hypothesis-jsonschema" }, { name = "json-schema" }, + { name = "jsonref" }, { name = "jsonschema" }, { name = "numpy" }, { name = "pillow" }, @@ -371,6 +381,7 @@ requires-dist = [ { name = "hypothesis", specifier = ">=6.119.0" }, { name = "hypothesis-jsonschema", specifier = ">=0.23.1" }, { name = "json-schema", specifier = ">=0.3" }, + { name = "jsonref", specifier = ">=1.1.0" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "numpy", specifier = ">=2.1.3" }, { name = "numpy", marker = "extra == 'load'", specifier = ">=2.1.3" },