diff --git a/python_sdk/docs/specifications/clearance.rst b/python_sdk/docs/specifications/clearance.rst deleted file mode 100644 index 85368a01..00000000 --- a/python_sdk/docs/specifications/clearance.rst +++ /dev/null @@ -1,38 +0,0 @@ -===================== -Clearance Calculation -===================== - -The *Clearance calculation* job allows you to compute clearance information for various structures (e.g. bridges). - - -.. contents:: Quick access - :local: - :depth: 2 - -Examples -======== - -In this example, we will create a specification for submitting a clearance computation job. - -.. literalinclude:: examples/clearance_specs.py - :language: Python - -Classes -======= - -.. currentmodule:: reality_capture.specifications.clearance - -.. autopydantic_model:: ClearanceSpecificationsCreate - -.. autoclass:: ClearanceOutputsCreate - :show-inheritance: - :members: - :undoc-members: - -.. autopydantic_model:: ClearanceSpecifications - -.. autopydantic_model:: ClearanceInputs - :model-show-json: False - -.. autopydantic_model:: ClearanceOutputs - :model-show-json: False diff --git a/python_sdk/docs/specifications/examples/cd_specs_detect_changes_meshes.py b/python_sdk/docs/specifications/examples/cd_specs_detect_changes_meshes.py index 597f778b..de4d75ab 100644 --- a/python_sdk/docs/specifications/examples/cd_specs_detect_changes_meshes.py +++ b/python_sdk/docs/specifications/examples/cd_specs_detect_changes_meshes.py @@ -1,7 +1,10 @@ import reality_capture.specifications.change_detection as change_detection -cd_inputs = change_detection.ChangeDetectionInputs(model3dA="279db84b-090f-4922-b9a5-7a4fd0a71fcd", - model3dB="40af080d-7ata-48c8-974c-610820fe90f2") -cd_outputs = [change_detection.ChangeDetectionOutputsCreate.OBJECTS3D] +cd_inputs = change_detection.ChangeDetectionInputs(model3DA="279db84b-090f-4922-b9a5-7a4fd0a71fcd", + model3DB="40af080d-7ata-48c8-974c-610820fe90f2") +cd_outputs = [ + change_detection.ChangeDetectionOutputsCreate.LOCATIONS3D_A_AS_GEOJSON, + change_detection.ChangeDetectionOutputsCreate.LOCATIONS3D_B_AS_GEOJSON, +] cd_options = change_detection.ChangeDetectionOptions() cds = change_detection.ChangeDetectionSpecificationsCreate(inputs=cd_inputs, outputs=cd_outputs, options=cd_options) diff --git a/python_sdk/docs/specifications/examples/clearance_specs.py b/python_sdk/docs/specifications/examples/clearance_specs.py deleted file mode 100644 index d12cd799..00000000 --- a/python_sdk/docs/specifications/examples/clearance_specs.py +++ /dev/null @@ -1,6 +0,0 @@ -import reality_capture.specifications.clearance as clearance - -clearance_inputs = clearance.ClearanceInputs(model3d="084985b0-71b5-4b02-a788-db261dd0730c", - clearanceFootprint="635f801b-82cc-4477-8d59-f01eb2fea1d9") -clearance_outputs = [clearance.ClearanceOutputsCreate.OVF_AREAS, clearance.ClearanceOutputsCreate.OVF_LINES] -clearance_specs = clearance.ClearanceSpecificationsCreate(inputs=clearance_inputs, outputs=clearance_outputs) \ No newline at end of file diff --git a/python_sdk/docs/specifications/examples/eval_s3d_specs.py b/python_sdk/docs/specifications/examples/eval_s3d_specs.py index 803b317d..f0742d7d 100644 --- a/python_sdk/docs/specifications/examples/eval_s3d_specs.py +++ b/python_sdk/docs/specifications/examples/eval_s3d_specs.py @@ -2,6 +2,6 @@ eval_s3d_inputs = eval_s3d.EvalS3DInputs(reference="587a14fd-305a-474c-b037-26d4ee8829d9", prediction="08342927-859c-4563-a4b8-6c6cfb7d5bb3") -eval_s3d_outputs = [eval_s3d.EvalS3DOutputsCreate.SEGMENTATION3D, eval_s3d.EvalS3DOutputsCreate.SEGMENTED_POINT_CLOUD, +eval_s3d_outputs = [eval_s3d.EvalS3DOutputsCreate.SEGMENTATION3D, eval_s3d.EvalS3DOutputsCreate.SEGMENTED_MODEL_3D, eval_s3d.EvalS3DOutputsCreate.REPORT] eval_s3ds = eval_s3d.EvalS3DSpecificationsCreate(inputs=eval_s3d_inputs, outputs=eval_s3d_outputs) diff --git a/python_sdk/docs/specifications/examples/s3d_specs_segment_meshes.py b/python_sdk/docs/specifications/examples/s3d_specs_segment_meshes.py index 8fe6ec26..43654a74 100644 --- a/python_sdk/docs/specifications/examples/s3d_specs_segment_meshes.py +++ b/python_sdk/docs/specifications/examples/s3d_specs_segment_meshes.py @@ -3,6 +3,6 @@ s3d_inputs = segmentation3d.Segmentation3DInputs(meshes="401975b7-0c0a-4498-5896-84987921f4bb", pointCloudSegmentationDetector="08342927-859c-4563-a4b8-6c6cfb7d5bb3") s3d_outputs = [segmentation3d.Segmentation3DOutputsCreate.SEGMENTATION3D, - segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_POINT_CLOUD] + segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_MODEL_3D] s3d_options = segmentation3d.Segmentation3DOptions() s3ds = segmentation3d.Segmentation3DSpecificationsCreate(inputs=s3d_inputs, outputs=s3d_outputs, options=s3d_options) diff --git a/python_sdk/docs/specifications/examples/s3d_specs_segment_pc.py b/python_sdk/docs/specifications/examples/s3d_specs_segment_pc.py index 715632a1..66b33320 100644 --- a/python_sdk/docs/specifications/examples/s3d_specs_segment_pc.py +++ b/python_sdk/docs/specifications/examples/s3d_specs_segment_pc.py @@ -3,6 +3,6 @@ s3d_inputs = segmentation3d.Segmentation3DInputs(pointClouds="401975b7-0c0a-4498-5896-84987921f4bb", pointCloudSegmentationDetector="08342927-859c-4563-a4b8-6c6cfb7d5bb3") s3d_outputs = [segmentation3d.Segmentation3DOutputsCreate.SEGMENTATION3D, - segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_POINT_CLOUD] + segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_MODEL_3D] s3d_options = segmentation3d.Segmentation3DOptions() s3ds = segmentation3d.Segmentation3DSpecificationsCreate(inputs=s3d_inputs, outputs=s3d_outputs, options=s3d_options) diff --git a/python_sdk/docs/specifications/examples/s3d_specs_segment_pc_and_infer_3d_lines.py b/python_sdk/docs/specifications/examples/s3d_specs_segment_pc_and_infer_3d_lines.py index 293db9da..e3a5d9e4 100644 --- a/python_sdk/docs/specifications/examples/s3d_specs_segment_pc_and_infer_3d_lines.py +++ b/python_sdk/docs/specifications/examples/s3d_specs_segment_pc_and_infer_3d_lines.py @@ -3,7 +3,7 @@ s3d_inputs = segmentation3d.Segmentation3DInputs(meshes="401975b7-0c0a-4498-5896-84987921f4bb", pointCloudSegmentationDetector="08342927-859c-4563-a4b8-6c6cfb7d5bb3") s3d_outputs = [segmentation3d.Segmentation3DOutputsCreate.SEGMENTATION3D, - segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_POINT_CLOUD, + segmentation3d.Segmentation3DOutputsCreate.SEGMENTED_MODEL_3D, segmentation3d.Segmentation3DOutputsCreate.LINES3D] s3d_options = segmentation3d.Segmentation3DOptions() s3ds = segmentation3d.Segmentation3DSpecificationsCreate(inputs=s3d_inputs, outputs=s3d_outputs, options=s3d_options) diff --git a/python_sdk/docs/specifications/examples/training_s3d_default.py b/python_sdk/docs/specifications/examples/training_s3d_default.py new file mode 100644 index 00000000..600e59d3 --- /dev/null +++ b/python_sdk/docs/specifications/examples/training_s3d_default.py @@ -0,0 +1,14 @@ +import reality_capture.specifications.training as training + +training_s3d_inputs = training.TrainingS3DInputs( + segmentations3D=["401975b7-0c0a-4498-5896-84987921f4bb"], + detectorName="example-detector", +) + +training_s3d_outputs = [ + training.TrainingS3DOutputsCreate.DETECTOR, +] + +training_s3ds = training.TrainingS3DSpecificationsCreate( + inputs=training_s3d_inputs, outputs=training_s3d_outputs +) diff --git a/python_sdk/docs/specifications/examples/training_s3d_options.py b/python_sdk/docs/specifications/examples/training_s3d_options.py new file mode 100644 index 00000000..3a223b4a --- /dev/null +++ b/python_sdk/docs/specifications/examples/training_s3d_options.py @@ -0,0 +1,22 @@ +import reality_capture.specifications.training as training + +training_s3d_inputs = training.TrainingS3DInputs( + segmentations3D=["401975b7-0c0a-4498-5896-84987921f4bb"], + detectorName="example-detector", +) + +training_s3d_outputs = [ + training.TrainingS3DOutputsCreate.DETECTOR, +] + +training_s3d_options = training.TrainingS3DOptions( + epochs=2, + spacing=0.2, + model=training.Segmentation3DTrainingModel.SPLATNET, + features=[training.PointCloudFeature.RGB, training.PointCloudFeature.INTENSITY], + versionNumber="1.0", +) + +training_s3ds = training.TrainingS3DSpecificationsCreate( + inputs=training_s3d_inputs, outputs=training_s3d_outputs, options=training_s3d_options +) diff --git a/python_sdk/docs/specifications/index.rst b/python_sdk/docs/specifications/index.rst index 3daad349..46547827 100644 --- a/python_sdk/docs/specifications/index.rst +++ b/python_sdk/docs/specifications/index.rst @@ -33,7 +33,7 @@ Specifications regroup all the settings used to create jobs with our APIs. eval_s2d eval_s3d eval_sortho - clearance + training_s3d Modeling @@ -59,13 +59,14 @@ Analysis * :doc:`/specifications/segmentation3d` uses a point cloud segmentation detector to classify each point of a point cloud and create 3D features. * :doc:`/specifications/change_detection` will take two point clouds or two meshes to to get 3D regions that capture the changes. * :doc:`/specifications/eval_o2d`, :doc:`/specifications/eval_o3d`, :doc:`/specifications/eval_s2d`, :doc:`/specifications/eval_s3d` and :doc:`/specifications/eval_sortho` will compare a prediction to a reference for a specific detection. -* :doc:`/specifications/clearance` uses a 3D model and a footprint to compute clearance information. +* :doc:`/specifications/training_s3d` will train a Segmentation 3D detector from ContextScenes .. Conversion .. ========== .. * :doc:`/specifications/point_cloud_conversion` will convert point clouds from one format to another. + Utilities ========= diff --git a/python_sdk/docs/specifications/objects2d.rst b/python_sdk/docs/specifications/objects2d.rst index a248b199..362b81cc 100644 --- a/python_sdk/docs/specifications/objects2d.rst +++ b/python_sdk/docs/specifications/objects2d.rst @@ -4,7 +4,7 @@ Objects 2D The *Objects 2D* job uses a photo object detector to detect 2D objects in photos. This job produces a context scene annotated with 2D objects. If the context scene input is oriented, it can turn these 2D objects into 3D objects and produce a context scene annotated with 3D objects. -Optionally, these 3D objects that can be exported in another formats, such as 3D Tiles, DGN, SHP, or GeoJSON. +Optionally, these 3D objects that can be exported in another formats, such as 3D Tiles, SHP, or GeoJSON. .. contents:: Quick access :local: @@ -36,7 +36,6 @@ This job has three different purposes : | *meshes* (optional) - | *objects2d*, | *objects3d*, - | *objects3d_as_dgn* (optional), | *objects3d_as_3d_tiles* (optional), | *objects3d_as_geojson* (optional), | *locations3d_as_shp* (optional), @@ -52,7 +51,6 @@ This job has three different purposes : | *point_clouds* (optional), | *meshes* (optional) - | *objects3d*, - | *objects3d_as_dgn* (optional), | *objects3d_as_3d_tiles* (optional), | *objects3d_as_geojson* (optional), | *locations3d_as_shp* (optional), diff --git a/python_sdk/docs/specifications/segmentation2d.rst b/python_sdk/docs/specifications/segmentation2d.rst index 2873ae19..858f3ba5 100644 --- a/python_sdk/docs/specifications/segmentation2d.rst +++ b/python_sdk/docs/specifications/segmentation2d.rst @@ -2,7 +2,7 @@ Segmentation 2D =============== -The Segmentation 2D job uses a photo segmentation detector to classify pixels in photos. If photos are oriented, it can project the segmentation on meshes or point clouds, and detect 3D lines and 3D polygons. This job produces segmented photos and a context scene pointing to these photos. Optionally, it can also produce context scenes annotated with 3D polygons and 3D lines. Those can be exported in another formats, such as 3D Tiles, DGN, or GeoJSON. +The Segmentation 2D job uses a photo segmentation detector to classify pixels in photos. If photos are oriented, it can project the segmentation on meshes or point clouds, and detect 3D lines and 3D polygons. This job produces segmented photos and a context scene pointing to these photos. Optionally, it can also produce context scenes annotated with 3D polygons and 3D lines. Those can be exported in another formats, such as 3D Tiles, or GeoJSON. .. contents:: Quick access :local: @@ -38,11 +38,9 @@ This job has three different purposes : - | *segmentation2d*, | *segmented_photos*, | *lines3d* (optional), - | *lines3d_as_dgn* (optional), | *lines3d_as_3d_tiles* (optional), | *lines3d_as_geojson* (optional), | *polygons3d* (optional), - | *polygons3d_as_dgn* (optional), | *polygons3d_as_3d_tiles* (optional), | *polygons3d_as_geojson* (optional) - | *min_photos*, @@ -58,11 +56,9 @@ This job has three different purposes : | *segmentation2d*, | *point_clouds*/*meshes* - | *lines3d* (optional), - | *lines3d_as_dgn* (optional), | *lines3d_as_3d_tiles* (optional), | *lines3d_as_geojson* (optional), | *polygons3d* (optional), - | *polygons3d_as_dgn* (optional), | *polygons3d_as_3d_tiles* (optional), | *polygons3d_as_geojson* (optional) - | *min_photos*, diff --git a/python_sdk/docs/specifications/segmentation3d.rst b/python_sdk/docs/specifications/segmentation3d.rst index 1483e2e3..255b8ef0 100644 --- a/python_sdk/docs/specifications/segmentation3d.rst +++ b/python_sdk/docs/specifications/segmentation3d.rst @@ -2,7 +2,7 @@ Segmentation 3D =============== -The Segmentation 3D job uses a point cloud segmentation detector to classify each point of a point cloud. It produces a segmented point cloud (in OPC format) and a context scene pointing to this point cloud. The segmented point cloud can be exported as LAS, LAZ, POD or PLY. Optionally, it can also produce context scenes annotated with 3D objects, 3D polygons and/or 3D lines. Those can be exported in another format, such as 3D Tiles, SHP, DGN, or GeoJSON. +The Segmentation 3D job uses a point cloud segmentation detector to classify each point of a point cloud. It produces a segmented point cloud (in OPC format) and a context scene pointing to this point cloud. Optionally, it can also produce context scenes annotated with 3D objects, 3D polygons and/or 3D lines. Those can be exported in another format, such as 3D Tiles, SHP, or GeoJSON. .. contents:: Quick access :local: @@ -27,26 +27,16 @@ This job has four different purposes : | *point_cloud_segmentation_detector*, | *clip_polygon* (optional) - | *segmentation3d*, - | *segmented_point_cloud*, - | *segmentation3d_as_las* (optional), - | *segmentation3d_as_laz* (optional), - | *segmentation3d_as_pod* (optional), - | *segmentation3d_as_ply* (optional) + | *segmented_model_3d* - | *save_confidence*, - | *crs* * - | Segment a collection | of meshes - | *meshes*, | *point_cloud_segmentation_detector*, | *clip_polygon* (optional) - | *segmentation3d*, - | *segmented_point_cloud*, - | *segmentation3d_as_las* (optional), - | *segmentation3d_as_laz* (optional), - | *segmentation3d_as_pod* (optional), - | *segmentation3d_as_ply* (optional) + | *segmented_model_3d* - | *save_confidence*, - | *crs* * - | Segment a collection | of point clouds or meshes | and infer 3D objects, @@ -55,23 +45,16 @@ This job has four different purposes : | *point_cloud_segmentation_detector*, | *clip_polygon* (optional) - | *segmentation3d*, - | *segmented_point_cloud*, - | *segmentation3d_as_las* (optional), - | *segmentation3d_as_laz* (optional), - | *segmentation3d_as_pod* (optional), - | *segmentation3d_as_ply* (optional), + | *segmented_model_3d*, | *objects3d* (optional), - | *objects3d_as_dgn* (optional), | *objects3d_as_3d_tiles* (optional), | *objects3d_as_geojson* (optional), | *locations3d_as_shp* (optional), | *locations3d_as_geojson* (optional), | *lines3d* (optional), - | *lines3d_as_dgn* (optional), | *lines3d_as_3d_tiles* (optional), | *lines3d_as_geojson* (optional), | *polygons3d* (optional), - | *polygons3d_as_dgn* (optional), | *polygons3d_as_3d_tiles* (optional), | *polygons3d_as_geojson* (optional) - | *remove_small_components*, @@ -83,22 +66,15 @@ This job has four different purposes : | lines and 3D polygons - | *segmentation3d*, | *clip_polygon* (optional) - - | *segmentation3d_as_las* (optional), - | *segmentation3d_as_laz* (optional), - | *segmentation3d_as_pod* (optional), - | *segmentation3d_as_ply* (optional), - | *objects3d* (optional), - | *objects3d_as_dgn* (optional), + - | *objects3d* (optional), | *objects3d_as_3d_tiles* (optional), | *objects3d_as_geojson* (optional), | *locations3d_as_shp* (optional), | *locations3d_as_geojson* (optional), | *lines3d* (optional), - | *lines3d_as_dgn* (optional), | *lines3d_as_3d_tiles* (optional), | *lines3d_as_geojson* (optional), | *polygons3d* (optional), - | *polygons3d_as_dgn* (optional), | *polygons3d_as_3d_tiles* (optional), | *polygons3d_as_geojson* (optional) - | *remove_small_components*, diff --git a/python_sdk/docs/specifications/segmentation_orthophoto.rst b/python_sdk/docs/specifications/segmentation_orthophoto.rst index 4d7b2f59..c39ab104 100644 --- a/python_sdk/docs/specifications/segmentation_orthophoto.rst +++ b/python_sdk/docs/specifications/segmentation_orthophoto.rst @@ -2,7 +2,7 @@ Segmentation Orthophoto ======================= -The Segmentation Orthophoto job uses an orthophoto segmentation detector to classify pixels in orthophotos. It produces segmented orthophotos and a context pointing to these orthophotos. Depending on the detector, it can detect 2D lines and 2D polygons. Optionally, these 2D lines and 2D polygons can be exported as SHP, DGN or GeoJSON files. +The Segmentation Orthophoto job uses an orthophoto segmentation detector to classify pixels in orthophotos. It produces segmented orthophotos and a context pointing to these orthophotos. Depending on the detector, it can detect 2D lines and 2D polygons. Optionally, these 2D lines and 2D polygons can be exported as SHP or GeoJSON files. .. contents:: Quick access :local: @@ -28,7 +28,6 @@ Purposes | *polygons2d_as_shp* (optional), | *polygons2d_as_geojson* (optional), | *lines2d* (optional), - | *lines2d_as_dgn* (optional), | *lines2d_as_shp* (optional), | *lines2d_as_geojson* (optional) - | diff --git a/python_sdk/docs/specifications/training_s3d.rst b/python_sdk/docs/specifications/training_s3d.rst new file mode 100644 index 00000000..810f0704 --- /dev/null +++ b/python_sdk/docs/specifications/training_s3d.rst @@ -0,0 +1,63 @@ +============ +Training S3D +============ + +The *Training S3D* job uses a ContextScene containing annotated pointclouds to train a new S3D detector. + +.. contents:: Quick access + :local: + :depth: 2 + +Purpose +======= + + This job has the following purposes: + + +.. list-table:: + :widths: auto + :header-rows: 1 + + * - Purpose + - Inputs + - Possible outputs + - Useful options + * - Train new detector on a dataset (ContextScene) + - | *segmentations3D*, + | *detectorName*, + | *preset* (optional) + - | *detector*, + - | *epochs* + | *spacing* + | *versionNumber* + +Examples +======== + +In this example, we will create a specification for submitting a Training S3D job to produce a new S3D detector from a ContextScene. + +.. literalinclude:: examples/training_s3d_default.py + :language: Python + +Classes +======= + +.. currentmodule:: reality_capture.specifications.training + +.. autopydantic_model:: TrainingS3DSpecificationsCreate + +.. autoclass:: TrainingS3DOutputsCreate + :show-inheritance: + :members: + :undoc-members: + +.. autopydantic_model:: TrainingS3DSpecifications + +.. autopydantic_model:: TrainingS3DInputs + :model-show-json: False + +.. autopydantic_model:: TrainingS3DOutputs + :model-show-json: False + +.. autopydantic_model:: TrainingS3DOptions + :model-show-json: False diff --git a/python_sdk/src/reality_capture/service/data_handler.py b/python_sdk/src/reality_capture/service/data_handler.py index 1d474bf1..a6c4a0c5 100644 --- a/python_sdk/src/reality_capture/service/data_handler.py +++ b/python_sdk/src/reality_capture/service/data_handler.py @@ -1,5 +1,5 @@ import os.path -from typing import Optional +from typing import Callable, Optional from reality_capture.service.error import DetailedErrorResponse, DetailedError, Error from reality_capture.service.response import Response from reality_capture.service.service import RealityCaptureService @@ -169,7 +169,7 @@ def __init__(self, token_factory, **kwargs) -> None: :param token_factory: An object that implements a ``get_token() -> str`` method. :type token_factory: Object - :param \**kwargs: Internal parameters used only for development purposes. + :param \\**kwargs: Internal parameters used only for development purposes. """ self._service = RealityCaptureService(token_factory, **kwargs) self._progress_hook = None @@ -252,7 +252,7 @@ def delete_data(self, reality_data_id, files_to_delete: list[str], return Response(r.status_code, r.error, None) return _DataHandler.delete_data(r.value.links.container_url.href, files_to_delete) - def set_progress_hook(self, hook: Optional) -> None: + def set_progress_hook(self, hook: Optional[Callable[[float], bool]]) -> None: """ Set the progress hook. @@ -273,7 +273,7 @@ def __init__(self, token_factory, **kwargs) -> None: :param token_factory: An object that implements a ``get_token() -> str`` method. :type token_factory: Object - :param \**kwargs: Internal parameters used only for development purposes. + :param \\**kwargs: Internal parameters used only for development purposes. """ self._service = RealityCaptureService(token_factory, **kwargs) self._progress_hook = None @@ -336,7 +336,7 @@ def delete_data(self, itwin_id, files_to_delete: list[str]) -> Response[None]: return Response(r.status_code, r.error, None) return _DataHandler.delete_data(r.value.links.container_url.href, files_to_delete) - def set_progress_hook(self, hook: Optional) -> None: + def set_progress_hook(self, hook: Optional[Callable[[float], bool]]) -> None: """ Set the progress hook. diff --git a/python_sdk/src/reality_capture/service/detectors.py b/python_sdk/src/reality_capture/service/detectors.py index 4ea29cd2..e3cbc094 100644 --- a/python_sdk/src/reality_capture/service/detectors.py +++ b/python_sdk/src/reality_capture/service/detectors.py @@ -13,6 +13,13 @@ class DetectorExport(Enum): LOCATIONS = "Locations" +class DetectorType(Enum): + PHOTO_OBJECT_DETECTOR = "PhotoObjectDetector" + PHOTO_SEGMENTATION_DETECTOR = "PhotoSegmentationDetector" + ORTHOPHOTO_SEGMENTATION_DETECTOR = "OrthophotoSegmentationDetector" + POINT_CLOUD_SEGMENTATION_DETECTOR = "PointCloudSegmentationDetector" + + class Capabilities(BaseModel): labels: list[str] = Field(description="Labels of the detector version.") exports: list[DetectorExport] = Field(description="Exports of the detector version.") @@ -49,13 +56,6 @@ class DetectorVersionWithLinks(BaseModel): alias="_links") -class DetectorType(Enum): - PHOTO_OBJECT_DETECTOR = "PhotoObjectDetector" - PHOTO_SEGMENTATION_DETECTOR = "PhotoSegmentationDetector" - ORTHOPHOTO_SEGMENTATION_DETECTOR = "OrthophotoSegmentationDetector" - POINT_CLOUD_SEGMENTATION_DETECTOR = "PointCloudSegmentationDetector" - - class DetectorUpdate(BaseModel): display_name: Optional[str] = Field(None, description="Display name of the detector.", alias="displayName") description: Optional[str] = Field(None, description="Description of the detector.") diff --git a/python_sdk/src/reality_capture/service/job.py b/python_sdk/src/reality_capture/service/job.py index 2c095918..977115cb 100644 --- a/python_sdk/src/reality_capture/service/job.py +++ b/python_sdk/src/reality_capture/service/job.py @@ -28,7 +28,7 @@ WaterConstraintsSpecificationsCreate) """from reality_capture.specifications.point_cloud_conversion import (PointCloudConversionSpecificationsCreate, PointCloudConversionSpecifications)""" - +from reality_capture.specifications.training import (TrainingS3DSpecificationsCreate, TrainingS3DSpecifications) from reality_capture.specifications.gaussian_splats import (GaussianSplatsSpecificationsCreate, GaussianSplatsSpecifications) from reality_capture.specifications.eval_o2d import (EvalO2DSpecificationsCreate, EvalO2DSpecifications) @@ -37,7 +37,6 @@ from reality_capture.specifications.eval_s3d import (EvalS3DSpecificationsCreate, EvalS3DSpecifications) from reality_capture.specifications.eval_sortho import (EvalSOrthoSpecificationsCreate, EvalSOrthoSpecifications) -from reality_capture.specifications.clearance import (ClearanceSpecificationsCreate, ClearanceSpecifications) from reality_capture.service.reality_data import URL @@ -63,7 +62,7 @@ class JobType(Enum): TOUCH_UP_IMPORT = "TouchUpImport" TOUCH_UP_EXPORT = "TouchUpExport" WATER_CONSTRAINTS = "WaterConstraints" - CLEARANCE_CALCULATION = "ClearanceCalculation" + TRAINING_S3D = "TrainingS3D" # POINT_CLOUD_CONVERSION = "PointCloudConversion" class Service(Enum): @@ -79,7 +78,7 @@ def _get_appropriate_service(jt: JobType): return Service.MODELING if jt in [JobType.OBJECTS_2D, JobType.SEGMENTATION_2D, JobType.SEGMENTATION_3D, JobType.SEGMENTATION_ORTHOPHOTO, JobType.CHANGE_DETECTION, JobType.EVAL_O2D, JobType.EVAL_O3D, JobType.EVAL_S2D, - JobType.EVAL_S3D, JobType.EVAL_SORTHO, JobType.CLEARANCE_CALCULATION]: + JobType.EVAL_S3D, JobType.EVAL_SORTHO, JobType.TRAINING_S3D]: return Service.ANALYSIS # return Service.CONVERSION raise NotImplementedError("Other services not yet implemented") @@ -100,7 +99,7 @@ class JobCreate(BaseModel): type: JobType = Field(description="Type of job.") # TODO : PointCloudConversionSpecificationsCreate, specifications: Union[CalibrationSpecificationsCreate, ChangeDetectionSpecificationsCreate, - ConstraintsSpecificationsCreate, + ConstraintsSpecificationsCreate, # PointCloudConversionSpecifications, EvalO2DSpecificationsCreate, EvalO3DSpecificationsCreate, EvalS2DSpecificationsCreate, EvalS3DSpecificationsCreate, EvalSOrthoSpecificationsCreate, FillImagePropertiesSpecificationsCreate, @@ -110,7 +109,7 @@ class JobCreate(BaseModel): Segmentation3DSpecificationsCreate, SegmentationOrthophotoSpecificationsCreate, TilingSpecificationsCreate, TouchUpExportSpecificationsCreate, TouchUpImportSpecificationsCreate, WaterConstraintsSpecificationsCreate, - ClearanceSpecificationsCreate] = ( + TrainingS3DSpecificationsCreate] = ( Field(description="Specifications aligned with the job type.")) itwin_id: str = Field(description="iTwin ID, used by the service for finding " "input reality data and uploading output data.", @@ -145,7 +144,7 @@ class Job(BaseModel): user_id: str = Field(description="Identifier of the user that created the job.", alias="userId") # TODO : add PointCloudConversionSpecifications specifications: Union[CalibrationSpecifications, ChangeDetectionSpecifications, - ConstraintsSpecifications, + ConstraintsSpecifications, # PointCloudConversionSpecifications, EvalO2DSpecifications, EvalO3DSpecifications, EvalS2DSpecifications, EvalS3DSpecifications, EvalSOrthoSpecifications, FillImagePropertiesSpecifications, @@ -155,7 +154,7 @@ class Job(BaseModel): Segmentation3DSpecifications, SegmentationOrthophotoSpecifications, TilingSpecifications, TouchUpExportSpecifications, TouchUpImportSpecifications, WaterConstraintsSpecifications, - ClearanceSpecifications] = ( + TrainingS3DSpecifications] = ( Field(description="Specifications aligned with the job type.")) @field_validator("specifications", mode="plain") @@ -207,8 +206,8 @@ def set_specification_validation_model(cls, raw_dict: dict[str, Any], validation specifications = TouchUpImportSpecifications(**raw_dict) elif job_type == JobType.WATER_CONSTRAINTS: specifications = WaterConstraintsSpecifications(**raw_dict) - elif job_type == JobType.CLEARANCE_CALCULATION: - specifications = ClearanceSpecifications(**raw_dict) + elif job_type == JobType.TRAINING_S3D: + specifications = TrainingS3DSpecifications(**raw_dict) else: raise ValueError(f"Unsupported job type: {job_type}") diff --git a/python_sdk/src/reality_capture/service/response.py b/python_sdk/src/reality_capture/service/response.py index f85ad98f..2a46eb14 100644 --- a/python_sdk/src/reality_capture/service/response.py +++ b/python_sdk/src/reality_capture/service/response.py @@ -1,13 +1,14 @@ from typing import TypeVar, Generic, Optional +from dataclasses import dataclass from reality_capture.service.error import DetailedErrorResponse T = TypeVar("T") - -class Response(tuple, Generic[T]): +@dataclass +class Response(Generic[T]): """ - A tuple containing a Service response. + A service response container. """ status_code: int @@ -17,13 +18,6 @@ class Response(tuple, Generic[T]): value: Optional[T] "Optional object if the request succeed." - def __new__(cls, status_code: int, error: Optional[DetailedErrorResponse], value: Optional[T]): - self = tuple.__new__(cls, (status_code, error, value)) - self.value = value - self.status_code = status_code - self.error = error - return self - def get_response_status_code(self) -> int: """ Return the HTTP response status_code diff --git a/python_sdk/src/reality_capture/service/service.py b/python_sdk/src/reality_capture/service/service.py index d01dc23a..72fed8d0 100644 --- a/python_sdk/src/reality_capture/service/service.py +++ b/python_sdk/src/reality_capture/service/service.py @@ -312,7 +312,7 @@ def get_detector(self, detector_name: str) -> Response[DetectorResponse]: def create_detector(self, detector_create: DetectorBase) -> Response[DetectorResponse]: """ - Create a detector. + Create a new detector. :param detector_create: DetectorBase information to create the detector. :return: A Response[DetectorResponse] containing either the created detector details or the error from the service. @@ -350,7 +350,7 @@ def update_detector(self, detector_name: str, detector_update: DetectorUpdate) - def delete_detector(self, detector_name: str) -> Response[None]: """ - Delete a detector. + Delete a detector and all of its versions. :param detector_name: Name of the detector to delete. :return: A Response[None] containing either nothing if successful or the error from the service. @@ -410,7 +410,7 @@ def delete_detector_version(self, detector_name: str, detector_version: str) -> def publish_detector_version(self, detector_name: str, version_number: str) -> Response[None]: """ - Publish the specified detector version. + Publish a specific version of a detector. :param detector_name: Name of the detector. :param version_number: Name of the version. @@ -431,7 +431,7 @@ def publish_detector_version(self, detector_name: str, version_number: str) -> R def unpublish_detector_version(self, detector_name: str, version_number: str) -> Response[None]: """ - Unpublish the specified detector version. + Unpublish a specific version of a detector. :param detector_name: Name of the detector. :param version_number: Name of the version. diff --git a/python_sdk/src/reality_capture/specifications/change_detection.py b/python_sdk/src/reality_capture/specifications/change_detection.py index eec92092..b98f3fd0 100644 --- a/python_sdk/src/reality_capture/specifications/change_detection.py +++ b/python_sdk/src/reality_capture/specifications/change_detection.py @@ -4,46 +4,60 @@ class ChangeDetectionInputs(BaseModel): - model_3d_a: str = Field(alias="model3dA", description="Reality data id of ContextScene, point cloud or mesh") - model_3d_b: str = Field(alias="model3dB", description="Reality data id of ContextScene, point cloud or mesh") + model_3d_a: str = Field(alias="model3DA", description="Reality data id of ContextScene, point cloud, Gaussian splats, or mesh") + model_3d_b: str = Field(alias="model3DB", description="Reality data id of ContextScene, point cloud, Gaussian splats, or mesh") extent: Optional[str] = Field(None, alias="extent", pattern=r"^bkt:.+", description="Path in the bucket of the clipping polygon to apply") + preset: Optional[str] = Field(None, alias="preset", pattern=r"^bkt:.+", + description="Path in the bucket of a preset file to use") class ChangeDetectionOutputs(BaseModel): - objects3d: Optional[str] = Field(None, alias="objects3D", - description="Reality data id of ContextScene, annotated with embedded 3D objects") - locations3d_as_shp: Optional[str] = Field(None, alias="locations3DAsSHP", - description="Reality data id of 3D objects locations " + segmentation_3d_a: Optional[str] = Field(None, description="Reality data id of ContextScene, pointing to the segmented 3D model A", alias="segmentation3DA") + segmented_model_3d_b: Optional[str] = Field(None, description="Reality data id of the 3D segmented model in the same format of model 3d B", alias="segmentedModel3DB") + segmentation_3d_b: Optional[str] = Field(None, description="Reality data id of ContextScene, pointing to the segmented 3D model B", alias="segmentation3DB") + segmented_model_3d_a: Optional[str] = Field(None, description="Reality data id of the 3D segmented model in the same format of model 3d A", alias="segmentedModel3DA") + locations3d_a: Optional[str] = Field(None, alias="locations3DA", + description="Reality data id of ContextScene with locations of changes in A") + locations3d_a_as_shp: Optional[str] = Field(None, alias="locations3DAAsSHP", + description="Reality data id of locations of changes in A" "as SHP format") - locations3d_as_geojson: Optional[str] = Field(None, alias="locations3DAsGeoJSON", - description="Reality data id of 3D objects locations " + locations3d_a_as_geojson: Optional[str] = Field(None, alias="locations3DAAsGeoJSON", + description="Reality data id of locations of changes in A" "as GeoJSON file") - changes_in_model_b: Optional[str] = Field(None, description="Points in B not in A as OPC", alias="changesInModelB") - changes_in_model_a: Optional[str] = Field(None, description="Points in A not in B as OPC", alias="changesInModelA") + locations3d_b: Optional[str] = Field(None, alias="locations3DB", + description="Reality data id of ContextScene with locations of changes in B") + locations3d_b_as_shp: Optional[str] = Field(None, alias="locations3DBAsSHP", + description="Reality data id of locations of changes in B as SHP format") + locations3d_b_as_geojson: Optional[str] = Field(None, alias="locations3DBAsGeoJSON", + description="Reality data id of locations of changes in B as GeoJSON file") + class ChangeDetectionOutputsCreate(Enum): - OBJECTS3D = "objects3D" - LOCATIONS3D_AS_SHP = "locations3DAsSHP" - LOCATIONS3D_AS_GEOJSON = "locations3DAsGeoJSON" - CHANGES_IN_MODEL_A = "changesInModelA" - CHANGES_IN_MODEL_B = "changesInModelB" - + SEGMENTATION3D_A = "segmentation3DA" + SEGMENTED_MODEL3D_A = "segmentedModel3DA" + SEGMENTATION3D_B = "segmentation3DB" + SEGMENTED_MODEL3D_B = "segmentedModel3DB" + LOCATIONS3D_A = "locations3DA" + LOCATIONS3D_A_AS_SHP = "locations3DAAsSHP" + LOCATIONS3D_A_AS_GEOJSON = "locations3DAAsGeoJSON" + LOCATIONS3D_B = "locations3DB" + LOCATIONS3D_B_AS_SHP = "locations3DBAsSHP" + LOCATIONS3D_B_AS_GEOJSON = "locations3DBAsGeoJSON" + class ChangeDetectionOptions(BaseModel): - output_crs: Optional[str] = Field(None, alias="outputCrs", - description="CRS used by locations3DAsSHP output") min_points_per_change: Optional[int] = Field(None, alias="minPointsPerChange", description="Minimum number of points in a region " "to be considered as a change") - mesh_sampling_resolution: Optional[float] = Field(None, alias="meshSamplingResolution", - description="Target point cloud resolution when starting " - "from meshes") - threshold: Optional[float] = Field(None, description="High threshold to detect spatial changes " + sampling_resolution: Optional[float] = Field(None, alias="samplingResolution", + description="Target point cloud resolution when starting from meshes") + grow_threshold: Optional[float] = Field(None, alias="growThreshold", + description="Low threshold to detect spatial changes " "(hysteresis detection)") filter_threshold: Optional[float] = Field(None, alias="filterThreshold", - description="Low threshold to detect spatial changes " + description="High threshold to detect spatial changes " "(hysteresis detection)") diff --git a/python_sdk/src/reality_capture/specifications/clearance.py b/python_sdk/src/reality_capture/specifications/clearance.py deleted file mode 100644 index cadde664..00000000 --- a/python_sdk/src/reality_capture/specifications/clearance.py +++ /dev/null @@ -1,30 +0,0 @@ -from pydantic import BaseModel, Field -from typing import Optional -from enum import Enum - - -class ClearanceInputs(BaseModel): - model_3d: str = Field(alias="model3d", description="Reality data id of a point cloud.") - clearance_footprint: str = Field(alias="clearanceFootprint", description="Reality data id of building footprints.") - - -class ClearanceOutputs(BaseModel): - ovf_points: Optional[str] = Field(None, alias="ovfPoints", description="Reality data id of OVF Clearance Points") - ovf_lines: Optional[str] = Field(None, alias="ovfLines", description="Reality data id of OVF Clearance Lines") - ovf_areas: Optional[str] = Field(None, alias="ovfAreas", description="Reality data id of OVF Clearance Areas") - - -class ClearanceOutputsCreate(Enum): - OVF_POINTS = "ovfPoints" - OVF_LINES = "ovfLines" - OVF_AREAS = "ovfAreas" - - -class ClearanceSpecificationsCreate(BaseModel): - inputs: ClearanceInputs = Field(description="Inputs") - outputs: list[ClearanceOutputsCreate] = Field(description="Outputs") - - -class ClearanceSpecifications(BaseModel): - inputs: ClearanceInputs = Field(description="Inputs") - outputs: ClearanceOutputs = Field(description="Outputs") diff --git a/python_sdk/src/reality_capture/specifications/eval_s3d.py b/python_sdk/src/reality_capture/specifications/eval_s3d.py index 9a5de6ed..1e28f97c 100644 --- a/python_sdk/src/reality_capture/specifications/eval_s3d.py +++ b/python_sdk/src/reality_capture/specifications/eval_s3d.py @@ -13,17 +13,16 @@ class EvalS3DInputs(BaseModel): class EvalS3DOutputs(BaseModel): report: Optional[str] = Field(None, description="Path in Bucket of json report with confusion matrix", pattern=r"^bkt:.+") - segmented_point_cloud: Optional[str] = Field(None, alias="segmentedPointCloud", - description="Reality data id of segmented point cloud, " - "annotated with confusion matrix index") + segmented_model_3d: Optional[str] = Field(None, alias="segmentedModel3D", + description="Reality data id of segmented 3D model OPC as annotated with confusion matrix index") segmentation3d: Optional[str] = Field(None, alias="segmentation3D", description="Reality data id of ContextScene, " - "pointing to segmented point cloud") + "pointing to segmented 3D model") class EvalS3DOutputsCreate(Enum): REPORT = "report" - SEGMENTED_POINT_CLOUD = "segmentedPointCloud" + SEGMENTED_MODEL_3D = "segmentedModel3D" SEGMENTATION3D = "segmentation3D" diff --git a/python_sdk/src/reality_capture/specifications/segmentation3d.py b/python_sdk/src/reality_capture/specifications/segmentation3d.py index f50fd04a..9d009de9 100644 --- a/python_sdk/src/reality_capture/specifications/segmentation3d.py +++ b/python_sdk/src/reality_capture/specifications/segmentation3d.py @@ -25,27 +25,9 @@ class Segmentation3DInputs(BaseModel): class Segmentation3DOutputs(BaseModel): segmentation3d: Optional[str] = Field(None, alias="segmentation3D", - description="Reality data id of ContextScene, " - "pointing to the segmented point cloud") - segmented_point_cloud: Optional[str] = Field(None, alias="segmentedPointCloud", - description="Reality data id of " - "the 3D segmentation as OPC file") - segmentation3d_as_pod: Optional[str] = Field(None, alias="segmentation3DAsPOD", - description="Reality data id of " - "the segmented point cloud as POD file, " - "segmentation3D output must be defined") - segmentation3d_as_las: Optional[str] = Field(None, alias="segmentation3DAsLAS", - description="Reality data id of " - "the segmented point cloud as LAS file, " - "segmentation3D output must be defined") - segmentation3d_as_laz: Optional[str] = Field(None, alias="segmentation3DAsLAZ", - description="Reality data id of " - "the segmented point cloud as LAZ file, " - "segmentation3D output must be defined") - segmentation3d_as_ply: Optional[str] = Field(None, alias="segmentation3DAsPLY", - description="Reality data id of " - "the segmented point cloud as PLY file, " - "segmentation3D output must be defined") + description="Reality data id of ContextScene, pointing to the segmented 3D model") + segmented_model_3d: Optional[str] = Field(None, alias="segmentedModel3D", + description="Reality data id of the 3D segmentation model follows the same format as the model3D file") objects3d: Optional[str] = Field(None, alias="objects3D", description="Reality data id of ContextScene, " "annotated with embedded 3D objects") @@ -83,11 +65,7 @@ class Segmentation3DOutputs(BaseModel): class Segmentation3DOutputsCreate(Enum): SEGMENTATION3D = "segmentation3D" - SEGMENTED_POINT_CLOUD = "segmentedPointCloud" - SEGMENTATION3D_AS_POD = "segmentation3DAsPOD" - SEGMENTATION3D_AS_LAS = "segmentation3DAsLAS" - SEGMENTATION3D_AS_LAZ = "segmentation3DAsLAZ" - SEGMENTATION3D_AS_PLY = "segmentation3DAsPLY" + SEGMENTED_MODEL_3D = "segmentedModel3D" OBJECTS3D = "objects3D" OBJECTS3D_AS_3DTILES = "objects3DAs3DTiles" OBJECTS3D_AS_GEOJSON = "objects3DAsGeoJSON" @@ -102,7 +80,6 @@ class Segmentation3DOutputsCreate(Enum): class Segmentation3DOptions(BaseModel): - crs: Optional[str] = Field(None, description="CRS used by POD, LAS, LAZ, PLY, DGN and SHP outputs") save_confidence: Optional[bool] = Field(None, alias="saveConfidence", description="Save confidence in 3D segmentation") compute_line_width: Optional[bool] = Field(None, alias="computeLineWidth", @@ -110,8 +87,6 @@ class Segmentation3DOptions(BaseModel): remove_small_lines: Optional[float] = Field(None, alias="removeSmallLines", description="Remove 3D lines with total length " "smaller than this value") - keep_input_resolution: Optional[bool] = Field(None, alias="keepInputResolution", - description="To make segmentation 3D output exact same point input ") class Segmentation3DSpecificationsCreate(BaseModel): diff --git a/python_sdk/src/reality_capture/specifications/training.py b/python_sdk/src/reality_capture/specifications/training.py new file mode 100644 index 00000000..2175f9c7 --- /dev/null +++ b/python_sdk/src/reality_capture/specifications/training.py @@ -0,0 +1,61 @@ +from pydantic import BaseModel, Field +from typing import Optional +from enum import Enum + + +class TrainingS3DInputs(BaseModel): + segmentations_3d: list[str] = Field( + description="List of 3D models to train on.", + alias="segmentations3D" + ) + preset: Optional[str] = Field(default=None, description="Path to a preset") + detector_name: str = Field(description="Name of the detector to train", alias="detectorName") + + +class TrainingS3DOutputs(BaseModel): + detector: str = Field(description="Full detector information (name/version)") + + +class Segmentation3DTrainingModel(Enum): + SPLATNET = "SPLATNet" + + +class PointCloudFeature(Enum): + RGB = "RGB" + NORMAL = "NORMAL" + INTENSITY = "INTENSITY" + + +class TrainingS3DOptions(BaseModel): + epochs: Optional[int] = Field( + None, description="Number of time to iterate over the entire dataset", ge=1, le=100 + ) + spacing: Optional[float] = Field( + None, + description="Spacing of the pointcloud seen by the detector (in meters).", + gt=0 + ) + model: Optional[Segmentation3DTrainingModel] = Field(None, description="Training Model architecture to use.") + features: Optional[list[PointCloudFeature]] = Field(None, description="Features to use for the training.") + version_number: Optional[str] = Field( + None, + description="String representing the version number for the newly trained detector.", + alias="versionNumber", + pattern=r"^\d+(?:\.\d+)?$" + ) + + +class TrainingS3DOutputsCreate(Enum): + DETECTOR = "detector" + + +class TrainingS3DSpecificationsCreate(BaseModel): + inputs: TrainingS3DInputs = Field(description="Inputs") + outputs: list[TrainingS3DOutputsCreate] = Field(description="Outputs") + options: Optional[TrainingS3DOptions] = Field(None, description="Options") + + +class TrainingS3DSpecifications(BaseModel): + inputs: TrainingS3DInputs = Field(description="Inputs") + outputs: TrainingS3DOutputs = Field(description="Outputs") + options: Optional[TrainingS3DOptions] = Field(None, description="Options") diff --git a/python_sdk/tests/test_job.py b/python_sdk/tests/test_job.py index d2bb96a0..3d5ab85a 100644 --- a/python_sdk/tests/test_job.py +++ b/python_sdk/tests/test_job.py @@ -31,9 +31,9 @@ def test_appropriate_service_job_create(self): assert j.get_appropriate_service() == Service.MODELING eg_specs = {"inputs": {"model3D": "pointClouds"}, "outputs": [Segmentation3DOutputsCreate.SEGMENTATION3D, - Segmentation3DOutputsCreate.SEGMENTED_POINT_CLOUD]} + Segmentation3DOutputsCreate.SEGMENTED_MODEL_3D]} j = JobCreate(**{"type": JobType.SEGMENTATION_3D, "iTwinId": "itwin", "specifications": eg_specs}) - # assert j.get_appropriate_service() == Service.ANALYSIS + assert j.get_appropriate_service() == Service.ANALYSIS # pc_conversion_specs = {"inputs": {"pointClouds": ["point_cloud"]}, "outputs": PCConversionOutputsCreate.OPC} # j = JobCreate(**{"type": JobType.POINT_CLOUD_CONVERSION, "iTwinId": "itwin", "specifications": pc_conversion_specs}) # assert j.get_appropriate_service() == Service.CONVERSION diff --git a/python_sdk/tests/test_job_validator.py b/python_sdk/tests/test_job_validator.py index 25fe1323..770d693b 100644 --- a/python_sdk/tests/test_job_validator.py +++ b/python_sdk/tests/test_job_validator.py @@ -19,7 +19,6 @@ from reality_capture.specifications.tiling import TilingSpecifications from reality_capture.specifications.touchup import TouchUpImportSpecifications, TouchUpExportSpecifications from reality_capture.specifications.water_constraints import WaterConstraintsSpecifications -from reality_capture.specifications.clearance import ClearanceSpecifications import pytest from unittest.mock import patch, MagicMock import reality_capture.service.job as job_module @@ -69,16 +68,35 @@ def test_validation_change_detection(self): j["type"] = "ChangeDetection" j["specifications"] = { "inputs": { - "model3dA": "modela", - "model3dB": "modelb" + "model3DA": "modela", + "model3DB": "modelb" }, "outputs": { - "changesInModelA": "rdid", - "objects3d": "obj" + "segmentedModel3DA": "rdid_seg_a", + "segmentedModel3DB": "rdid_seg_b", + "segmentation3DA": "rdid_segmentation_a", + "segmentation3DB": "rdid_segmentation_b", + "locations3DA": "loca_rdid", + "locations3DAAsGeoJSON": "geojsona_rdid", + "locations3DAAsSHP": "shpa_rdid", + "locations3DB": "locb_rdid", + "locations3DBAsGeoJSON": "geojsonb_rdid", + "locations3DBAsSHP": "shpb_rdid" } } job = Job(**j) assert isinstance(job.specifications, ChangeDetectionSpecifications) + outputs = job.specifications.outputs + assert outputs.segmented_model_3d_a == "rdid_seg_a" + assert outputs.segmented_model_3d_b == "rdid_seg_b" + assert outputs.segmentation_3d_a == "rdid_segmentation_a" + assert outputs.segmentation_3d_b == "rdid_segmentation_b" + assert outputs.locations3d_a == "loca_rdid" + assert outputs.locations3d_a_as_geojson == "geojsona_rdid" + assert outputs.locations3d_a_as_shp == "shpa_rdid" + assert outputs.locations3d_b == "locb_rdid" + assert outputs.locations3d_b_as_geojson == "geojsonb_rdid" + assert outputs.locations3d_b_as_shp == "shpb_rdid" def test_validation_constraints(self): j = self.j_base.copy() @@ -104,7 +122,7 @@ def test_validation_eval_o2d(self): "prediction": "predid" }, "outputs": { - "objects2d": "objid" + "objects2D": "objid" } } job = Job(**j) @@ -119,7 +137,7 @@ def test_validation_eval_o3d(self): "prediction": "predid" }, "outputs": { - "objects3d": "objid" + "objects3D": "objid" } } job = Job(**j) @@ -134,7 +152,7 @@ def test_validation_eval_s2d(self): "prediction": "predid" }, "outputs": { - "segmentation2d": "sid" + "segmentation2D": "sid" } } job = Job(**j) @@ -149,7 +167,7 @@ def test_validation_eval_s3d(self): "prediction": "predid" }, "outputs": { - "segmentation3d": "sid" + "segmentation3D": "sid" } } job = Job(**j) @@ -164,7 +182,7 @@ def test_validation_eval_sortho(self): "prediction": "predid" }, "outputs": { - "segmentation2d": "sid" + "segmentation2D": "sid" } } job = Job(**j) @@ -207,7 +225,7 @@ def test_validation_o2d(self): "photos": "mfid" }, "outputs": { - "objects2d": "sid" + "objects2D": "sid" } } job = Job(**j) @@ -250,7 +268,7 @@ def test_validation_s2d(self): "photos": "mfid", }, "outputs": { - "segmentation2d": "s2did" + "segmentation2D": "s2did" } } job = Job(**j) @@ -261,7 +279,7 @@ def test_validation_s3d(self): j["type"] = "Segmentation3D" j["specifications"] = { "inputs": { - "model3d": "mfid", + "model3D": "mfid", }, "outputs": { "segmentation3D": "s3did" @@ -279,7 +297,7 @@ def test_validation_sortho(self): "orthophotoSegmentationDetector": "detector" }, "outputs": { - "segmentation2d": "s2did" + "segmentation2D": "s2did" } } job = Job(**j) @@ -345,21 +363,6 @@ def test_validation_wc(self): job = Job(**j) assert isinstance(job.specifications, WaterConstraintsSpecifications) - def test_validation_clearance(self): - j = self.j_base.copy() - j["type"] = "ClearanceCalculation" - j["specifications"] = { - "inputs": { - "model3d": "mfid", - "clearanceFootprint": "sid" - }, - "outputs": { - "ovfPoints": "rdId" - } - } - job = Job(**j) - assert isinstance(job.specifications, ClearanceSpecifications) - def test_validation_unsupported_job_type_raises(self): j = self.j_base.copy() j["specifications"] = {"inputs": {}, "outputs": {}} diff --git a/typescript/packages/reality-capture/src/service/job.ts b/typescript/packages/reality-capture/src/service/job.ts index fe2929df..e9da7077 100644 --- a/typescript/packages/reality-capture/src/service/job.ts +++ b/typescript/packages/reality-capture/src/service/job.ts @@ -14,7 +14,7 @@ import { SegmentationOrthophotoSpecificationsCreateSchema, SegmentationOrthophot import { TilingSpecificationsCreateSchema, TilingSpecificationsSchema } from "../specifications/tiling"; import { TouchUpExportSpecificationsCreateSchema, TouchUpImportSpecificationsCreateSchema, TouchUpExportSpecificationsSchema, TouchUpImportSpecificationsSchema } from "../specifications/touchup"; import { WaterConstraintsSpecificationsCreateSchema, WaterConstraintsSpecificationsSchema } from "../specifications/water_constraints"; -import { TrainingO2DSpecificationsCreateSchema, TrainingS3DSpecificationsCreateSchema, TrainingO2DSpecificationsSchema, TrainingS3DSpecificationsSchema } from "../specifications/training"; +import { TrainingS3DSpecificationsCreateSchema, TrainingS3DSpecificationsSchema } from "../specifications/training"; //import { PointCloudConversionSpecificationsCreateSchema, PointCloudConversionSpecificationsSchema } from '../specifications/point_cloud_conversion'; import { GaussianSplatsSpecificationsCreateSchema, GaussianSplatsSpecificationsSchema } from "../specifications/gaussian_splats"; import { URLSchema } from "./bucket"; @@ -23,7 +23,6 @@ import { EvalO3DSpecificationsCreateSchema, EvalO3DSpecificationsSchema } from " import { EvalS2DSpecificationsCreateSchema, EvalS2DSpecificationsSchema } from "../specifications/eval_s2d"; import { EvalS3DSpecificationsCreateSchema, EvalS3DSpecificationsSchema } from "../specifications/eval_s3d"; import { EvalSOrthoSpecificationsCreateSchema, EvalSOrthoSpecificationsSchema } from "../specifications/eval_sortho"; -import { ClearanceSpecificationsCreateSchema, ClearanceSpecificationsSchema } from "../specifications/clearance"; export enum JobType { CALIBRATION = "Calibration", @@ -44,12 +43,10 @@ export enum JobType { SEGMENTATION_3D = "Segmentation3D", SEGMENTATION_ORTHOPHOTO = "SegmentationOrthophoto", TILING = "Tiling", - TRAINING_O2D = "TrainingO2D", TRAINING_S3D = "TrainingS3D", TOUCH_UP_IMPORT = "TouchUpImport", TOUCH_UP_EXPORT = "TouchUpExport", WATER_CONSTRAINTS = "WaterConstraints", - CLEARANCE_CALCULATION = "ClearanceCalculation", //POINT_CLOUD_CONVERSION = "PointCloudConversion", } @@ -70,10 +67,9 @@ export function getAppropriateService(jt: JobType): Service { } if ([ JobType.OBJECTS_2D, JobType.SEGMENTATION_2D, JobType.SEGMENTATION_3D, - JobType.SEGMENTATION_ORTHOPHOTO, JobType.CHANGE_DETECTION, JobType.TRAINING_O2D, + JobType.SEGMENTATION_ORTHOPHOTO, JobType.CHANGE_DETECTION, JobType.EVAL_O2D, JobType.EVAL_O3D, JobType.EVAL_S2D, JobType.EVAL_S3D, - JobType.EVAL_SORTHO, JobType.TRAINING_O2D, JobType.TRAINING_S3D, - JobType.CLEARANCE_CALCULATION + JobType.EVAL_SORTHO, JobType.TRAINING_S3D, ].includes(jt)) { return Service.ANALYSIS; } @@ -116,10 +112,8 @@ export const JobCreateSchema = z.object({ TouchUpExportSpecificationsCreateSchema, TouchUpImportSpecificationsCreateSchema, WaterConstraintsSpecificationsCreateSchema, - TrainingO2DSpecificationsCreateSchema, //PointCloudConversionSpecificationsCreateSchema, TrainingS3DSpecificationsCreateSchema, - ClearanceSpecificationsCreateSchema, ]).describe("Specifications aligned with the job type."), iTwinId: z.string().describe("iTwin ID, used by the service for finding input reality data and uploading output data."), }); @@ -235,12 +229,7 @@ export const JobSchema = z.discriminatedUnion("type", [ }), z.object({ ...CommonFields, - type: z.literal("TraningO2D"), - specifications: TrainingO2DSpecificationsSchema, - }), - z.object({ - ...CommonFields, - type: z.literal("TraningS3D"), + type: z.literal("TrainingS3D"), specifications: TrainingS3DSpecificationsSchema, }), z.object({ @@ -257,11 +246,6 @@ export const JobSchema = z.discriminatedUnion("type", [ ...CommonFields, type: z.literal("WaterConstraints"), specifications: WaterConstraintsSpecificationsSchema, - }), - z.object({ - ...CommonFields, - type: z.literal("ClearanceCalculation"), - specifications: ClearanceSpecificationsSchema, }) ]); export type Job = z.infer; diff --git a/typescript/packages/reality-capture/src/specifications/change_detection.ts b/typescript/packages/reality-capture/src/specifications/change_detection.ts index 5228578f..19e6054c 100644 --- a/typescript/packages/reality-capture/src/specifications/change_detection.ts +++ b/typescript/packages/reality-capture/src/specifications/change_detection.ts @@ -1,50 +1,131 @@ import { z } from "zod"; export enum ChangeDetectionOutputsCreate { - OBJECTS3D = "objects3D", - LOCATIONS3D_AS_SHP = "locations3DAsSHP", - LOCATIONS3D_AS_GEOJSON = "locations3DAsGeoJSON", - CHANGES_IN_MODEL_A = "changesInModelA", - CHANGES_IN_MODEL_B = "changesInModelB", + SEGMENTATION3D_A = "segmentation3DA", + SEGMENTED_MODEL3D_A = "segmentedModel3DA", + SEGMENTATION3D_B = "segmentation3DB", + SEGMENTED_MODEL3D_B = "segmentedModel3DB", + LOCATIONS3D_A = "locations3DA", + LOCATIONS3D_A_AS_SHP = "locations3DAAsSHP", + LOCATIONS3D_A_AS_GEOJSON = "locations3DAAsGeoJSON", + LOCATIONS3D_B = "locations3DB", + LOCATIONS3D_B_AS_SHP = "locations3DBAsSHP", + LOCATIONS3D_B_AS_GEOJSON = "locations3DBAsGeoJSON", } export const ChangeDetectionInputsSchema = z.object({ - model3dA: z.string().describe("Reality data id of ContextScene, point cloud or mesh"), - model3dB: z.string().describe("Reality data id of ContextScene, point cloud or mesh"), - extent: z.string().describe("Path in the bucket of the clipping polygon to apply").regex(/^bkt:.+/, { - message: "Path in the bucket to the extent file must start with 'bkt:'", - }).optional(), + model3DA: z + .string() + .describe("Reality data id of ContextScene, point cloud, GS or mesh"), + model3DB: z + .string() + .describe("Reality data id of ContextScene, point cloud, GS or mesh"), + extent: z + .string() + .describe("Path in the bucket of the clipping polygon to apply") + .regex(/^bkt:.+/, { + message: "Path in the bucket to the extent file must start with 'bkt:'", + }) + .optional(), + preset: z + .string() + .describe("Path in the bucket of a preset file to use") + .regex(/^bkt:.+/, { + message: "Path in the bucket to the preset file must start with 'bkt:'", + }) + .optional(), }); export type ChangeDetectionInputs = z.infer; export const ChangeDetectionOutputsSchema = z.object({ - objects3D: z.string().optional().describe("Reality data id of ContextScene, annotated with embedded 3D objects"), - locations3DAsSHP: z.string().optional().describe("Reality data id of 3D objects locations as SHP format"), - locations3DAsGeoJSON: z.string().optional().describe("Reality data id of 3D objects locations as GeoJSON file"), - changesInModelB: z.string().optional().describe("Points in B not in A as OPC"), - changesInModelA: z.string().optional().describe("Points in A not in B as OPC"), + segmentation3DA: z + .string() + .optional() + .describe("ContextScene CD on Model3D A"), + segmentedModel3DA: z + .string() + .optional() + .describe("Model3D A with CD segmentation"), + segmentation3DB: z + .string() + .optional() + .describe("ContextScene CD on Model3D B"), + segmentedModel3DB: z + .string() + .optional() + .describe("Model3D B with CD segmentation"), + locations3DA: z + .string() + .optional() + .describe("Reality data id of locations of changes A"), + locations3DAAsSHP: z + .string() + .optional() + .describe("Reality data id of locations of changes A as SHP format"), + locations3DAAsGeoJSON: z + .string() + .optional() + .describe("Reality data id of locations of changes A as GeoJSON file"), + locations3DB: z + .string() + .optional() + .describe("Reality data id of locations of changes B"), + locations3DBAsSHP: z + .string() + .optional() + .describe("Reality data id of locations of changes B as SHP format"), + locations3DBAsGeoJSON: z + .string() + .optional() + .describe("Reality data id of locations of changes B as GeoJSON file"), }); -export type ChangeDetectionOutputs = z.infer; +export type ChangeDetectionOutputs = z.infer< + typeof ChangeDetectionOutputsSchema +>; export const ChangeDetectionOptionsSchema = z.object({ - outputCrs: z.string().optional().describe("CRS used by locations3DAsSHP output"), - minPointsPerChange: z.number().int().optional().describe("Minimum number of points in a region to be considered as a change"), - meshSamplingResolution: z.number().optional().describe("Target point cloud resolution when starting from meshes"), - threshold: z.number().optional().describe("High threshold to detect spatial changes (hysteresis detection)"), - filterThreshold: z.number().optional().describe("Low threshold to detect spatial changes (hysteresis detection)"), + minPointsPerChange: z + .number() + .int() + .optional() + .describe( + "Minimum number of points in a region to be considered as a change", + ), + samplingResolution: z + .number() + .optional() + .describe("Target point cloud resolution when starting from meshes"), + growThreshold: z + .number() + .optional() + .describe("Low threshold to detect spatial changes (hysteresis detection)"), + filterThreshold: z + .number() + .optional() + .describe( + "High threshold to detect spatial changes (hysteresis detection)", + ), }); -export type ChangeDetectionOptions = z.infer; +export type ChangeDetectionOptions = z.infer< + typeof ChangeDetectionOptionsSchema +>; export const ChangeDetectionSpecificationsCreateSchema = z.object({ inputs: ChangeDetectionInputsSchema.describe("Inputs"), - outputs: z.array(z.nativeEnum(ChangeDetectionOutputsCreate)).describe("Outputs"), + outputs: z + .array(z.nativeEnum(ChangeDetectionOutputsCreate)) + .describe("Outputs"), options: ChangeDetectionOptionsSchema.optional().describe("Options"), }); -export type ChangeDetectionSpecificationsCreate = z.infer; +export type ChangeDetectionSpecificationsCreate = z.infer< + typeof ChangeDetectionSpecificationsCreateSchema +>; export const ChangeDetectionSpecificationsSchema = z.object({ inputs: ChangeDetectionInputsSchema.describe("Inputs"), outputs: ChangeDetectionOutputsSchema.describe("Outputs"), options: ChangeDetectionOptionsSchema.optional().describe("Options"), }); -export type ChangeDetectionSpecifications = z.infer; \ No newline at end of file +export type ChangeDetectionSpecifications = z.infer< + typeof ChangeDetectionSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/clearance.ts b/typescript/packages/reality-capture/src/specifications/clearance.ts deleted file mode 100644 index 6cbb1c80..00000000 --- a/typescript/packages/reality-capture/src/specifications/clearance.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { z } from "zod"; - -export const ClearanceInputsSchema = z.object({ - model3d: z.string().describe("Reality data id of a point cloud"), - clearanceFootprint: z.string().describe("Reality data id of Cbuilding footprints."), -}); -export type ClearanceInputs = z.infer; - -export const ClearanceOutputsSchema = z.object({ - ovfPoints: z.string() - .describe("Reality data id of OVF Clearance Points") - .optional(), - ovfLines: z.string() - .describe("Reality data id of OVF Clearance Lines") - .optional(), - ovfAreas: z.string() - .describe("Reality data id of OVF Clearance Areas") - .optional(), -}); -export type ClearanceOutputs = z.infer; - -export enum ClearanceOutputsCreate { - OVF_POINTS = "ovfPoints", - OVF_LINES = "ovfLines", - OVF_AREAS = "ovfAreas" -} - -export const ClearanceSpecificationsCreateSchema = z.object({ - inputs: ClearanceInputsSchema.describe("Inputs"), - outputs: z.array(z.nativeEnum(ClearanceOutputsCreate)).describe("Outputs"), -}); -export type ClearanceSpecificationsCreate = z.infer; - -export const ClearanceSpecificationsSchema = z.object({ - inputs: ClearanceInputsSchema.describe("Inputs"), - outputs: ClearanceOutputsSchema.describe("Outputs"), -}); -export type ClearanceSpecifications = z.infer; \ No newline at end of file diff --git a/typescript/packages/reality-capture/src/specifications/eval_s3d.ts b/typescript/packages/reality-capture/src/specifications/eval_s3d.ts index 292fef3f..ee105038 100644 --- a/typescript/packages/reality-capture/src/specifications/eval_s3d.ts +++ b/typescript/packages/reality-capture/src/specifications/eval_s3d.ts @@ -11,8 +11,8 @@ export const EvalS3DOutputsSchema = z.object({ .regex(/^bkt:.+/) .describe("Path in Bucket of json report with confusion matrix") .optional(), - segmentedPointCloud: z.string() - .describe("Reality data id of segmented point cloud, annotated with confusion matrix index") + segmentedModel3D: z.string() + .describe("Reality data id of segmented 3D model, annotated with confusion matrix index") .optional(), segmentation3D: z.string() .describe("Reality data id of ContextScene, pointing to segmented point cloud") @@ -22,7 +22,7 @@ export type EvalS3DOutputs = z.infer; export enum EvalS3DOutputsCreate { REPORT = "report", - SEGMENTED_POINT_CLOUD = "segmentedPointCloud", + SEGMENTED_MODEL_3D = "segmentedModel3D", SEGMENTATION3D = "segmentation3D", } diff --git a/typescript/packages/reality-capture/src/specifications/segmentation3d.ts b/typescript/packages/reality-capture/src/specifications/segmentation3d.ts index 16cb2efb..37049344 100644 --- a/typescript/packages/reality-capture/src/specifications/segmentation3d.ts +++ b/typescript/packages/reality-capture/src/specifications/segmentation3d.ts @@ -2,11 +2,7 @@ import { z } from "zod"; export enum Segmentation3DOutputsCreate { SEGMENTATION3D = "segmentation3D", - SEGMENTED_POINT_CLOUD = "segmentedPointCloud", - SEGMENTATION3D_AS_POD = "segmentation3DAsPOD", - SEGMENTATION3D_AS_LAS = "segmentation3DAsLAS", - SEGMENTATION3D_AS_LAZ = "segmentation3DAsLAZ", - SEGMENTATION3D_AS_PLY = "segmentation3DAsPLY", + SEGMENTED_MODEL_3D = "segmentedModel3D", OBJECTS3D = "objects3D", OBJECTS3D_AS_3DTILES = "objects3DAs3DTiles", OBJECTS3D_AS_GEOJSON = "objects3DAsGeoJSON", @@ -17,60 +13,148 @@ export enum Segmentation3DOutputsCreate { LINES3D_AS_GEOJSON = "lines3DAsGeoJSON", POLYGONS3D = "polygons3D", POLYGONS3D_AS_3DTILES = "polygons3DAs3DTiles", - POLYGONS3D_AS_GEOJSON = "polygons3DAsGeoJSON" + POLYGONS3D_AS_GEOJSON = "polygons3DAsGeoJSON", } export const Segmentation3DInputsSchema = z.object({ - model3D: z.string().optional().describe("Reality data id of ContextScene, pointing to a collection of point clouds/meshes to process, or a point cloud, or a mesh."), - pointCloudSegmentationDetector: z.string().optional().describe("Either reality data id of point cloud segmentation object detector or point cloud segmentation detector identifier from the AI Detectors library"), - segmentation3D: z.string().optional().describe("Reality data id of ContextScene, pointing to a segmented point cloud, this input replaces point_cloud_segmentation_detector, point_clouds and meshes inputs"), - extent: z.string() + model3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, pointing to a collection of point clouds/meshes to process, or a point cloud, or a mesh.", + ), + pointCloudSegmentationDetector: z + .string() + .optional() + .describe( + "Either reality data id of point cloud segmentation object detector or point cloud segmentation detector identifier from the AI Detectors library", + ), + segmentation3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, pointing to a segmented point cloud, this input replaces point_cloud_segmentation_detector, point_clouds and meshes inputs", + ), + extent: z + .string() .regex(/^bkt:.+/) .optional() - .describe("Path in the bucket of the clipping polygon to apply") + .describe("Path in the bucket of the clipping polygon to apply"), }); export type Segmentation3DInputs = z.infer; export const Segmentation3DOutputsSchema = z.object({ - segmentation3D: z.string().optional().describe("Reality data id of ContextScene, pointing to the segmented point cloud"), - segmentedPointCloud: z.string().optional().describe("Reality data id of the 3D segmentation as OPC file"), - segmentation3DAsPOD: z.string().optional().describe("Reality data id of the segmented point cloud as POD file, segmentation3D output must be defined"), - segmentation3DAsLAS: z.string().optional().describe("Reality data id of the segmented point cloud as LAS file, segmentation3D output must be defined"), - segmentation3DAsLAZ: z.string().optional().describe("Reality data id of the segmented point cloud as LAZ file, segmentation3D output must be defined"), - segmentation3DAsPLY: z.string().optional().describe("Reality data id of the segmented point cloud as PLY file, segmentation3D output must be defined"), - objects3D: z.string().optional().describe("Reality data id of ContextScene, annotated with embedded 3D objects"), - objects3DAs3DTiles: z.string().optional().describe("Reality data id of 3D objects as 3D Tiles file, objects3d output must be defined"), - objects3DAsGeoJSON: z.string().optional().describe("Reality data id of 3D objects as GeoJSON file, objects3d output must be defined"), - locations3DAsSHP: z.string().optional().describe("Reality data id of 3D objects locations as SHP file, objects3d output must be defined"), - locations3DAsGeoJSON: z.string().optional().describe("Reality data id of 3D objects locations as GeoJSON file, objects3d output must be defined"), - lines3D: z.string().optional().describe("Reality data id of ContextScene, annotated with embedded 3D lines"), - lines3DAs3DTiles: z.string().optional().describe("Reality data id of 3D lines as 3D Tiles file, lines3d output must be defined"), - lines3DAsGeoJSON: z.string().optional().describe("Reality data id of 3D lines as GeoJSON file, lines3d output must be defined"), - polygons3D: z.string().optional().describe("Reality data id of ContextScene, annotated with embedded 3D polygons"), - polygons3DAs3DTiles: z.string().optional().describe("Reality data id of 3D polygons as 3D Tiles file, polygons3d output must be defined"), - polygons3DAsGeoJSON: z.string().optional().describe("Reality data id of 3D polygons as GeoJSON file, polygons3d output must be defined") + segmentation3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, pointing to the segmented point cloud", + ), + segmentedModel3D: z + .string() + .optional() + .describe("Reality data id of the 3D segmentation as OPC file"), + objects3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, annotated with embedded 3D objects", + ), + objects3DAs3DTiles: z + .string() + .optional() + .describe( + "Reality data id of 3D objects as 3D Tiles file, objects3d output must be defined", + ), + objects3DAsGeoJSON: z + .string() + .optional() + .describe( + "Reality data id of 3D objects as GeoJSON file, objects3d output must be defined", + ), + locations3DAsSHP: z + .string() + .optional() + .describe( + "Reality data id of 3D objects locations as SHP file, objects3d output must be defined", + ), + locations3DAsGeoJSON: z + .string() + .optional() + .describe( + "Reality data id of 3D objects locations as GeoJSON file, objects3d output must be defined", + ), + lines3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, annotated with embedded 3D lines", + ), + lines3DAs3DTiles: z + .string() + .optional() + .describe( + "Reality data id of 3D lines as 3D Tiles file, lines3d output must be defined", + ), + lines3DAsGeoJSON: z + .string() + .optional() + .describe( + "Reality data id of 3D lines as GeoJSON file, lines3d output must be defined", + ), + polygons3D: z + .string() + .optional() + .describe( + "Reality data id of ContextScene, annotated with embedded 3D polygons", + ), + polygons3DAs3DTiles: z + .string() + .optional() + .describe( + "Reality data id of 3D polygons as 3D Tiles file, polygons3d output must be defined", + ), + polygons3DAsGeoJSON: z + .string() + .optional() + .describe( + "Reality data id of 3D polygons as GeoJSON file, polygons3d output must be defined", + ), }); export type Segmentation3DOutputs = z.infer; export const Segmentation3DOptionsSchema = z.object({ - crs: z.string().optional().describe("CRS used by POD, LAS, LAZ, PLY, DGN and SHP outputs"), - saveConfidence: z.boolean().optional().describe("Save confidence in 3D segmentation"), - computeLineWidth: z.boolean().optional().describe("Estimation 3D line width at each vertex"), - removeSmallLines: z.number().optional().describe("Remove 3D lines with total length smaller than this value"), - keepInputResolution: z.boolean().optional().describe("To make segmentation 3D output exact same point input ") + saveConfidence: z + .boolean() + .optional() + .describe("Save confidence in 3D segmentation"), + computeLineWidth: z + .boolean() + .optional() + .describe("Estimation 3D line width at each vertex"), + removeSmallLines: z + .number() + .optional() + .describe("Remove 3D lines with total length smaller than this value"), }); export type Segmentation3DOptions = z.infer; export const Segmentation3DSpecificationsCreateSchema = z.object({ inputs: Segmentation3DInputsSchema.describe("Inputs"), - outputs: z.array(z.nativeEnum(Segmentation3DOutputsCreate)).describe("Outputs"), - options: Segmentation3DOptionsSchema.optional().describe("Options") + outputs: z + .array(z.nativeEnum(Segmentation3DOutputsCreate)) + .describe("Outputs"), + options: Segmentation3DOptionsSchema.optional().describe("Options"), }); -export type Segmentation3DSpecificationsCreate = z.infer; +export type Segmentation3DSpecificationsCreate = z.infer< + typeof Segmentation3DSpecificationsCreateSchema +>; export const Segmentation3DSpecificationsSchema = z.object({ inputs: Segmentation3DInputsSchema.describe("Inputs"), outputs: Segmentation3DOutputsSchema.describe("Outputs"), - options: Segmentation3DOptionsSchema.optional().describe("Options") + options: Segmentation3DOptionsSchema.optional().describe("Options"), }); -export type Segmentation3DSpecifications = z.infer; \ No newline at end of file +export type Segmentation3DSpecifications = z.infer< + typeof Segmentation3DSpecificationsSchema +>; diff --git a/typescript/packages/reality-capture/src/specifications/training.ts b/typescript/packages/reality-capture/src/specifications/training.ts index cb03239b..b43588ba 100644 --- a/typescript/packages/reality-capture/src/specifications/training.ts +++ b/typescript/packages/reality-capture/src/specifications/training.ts @@ -1,87 +1,51 @@ import { z } from "zod"; -export const TrainingO2DInputsSchema = z.object({ - scene: z.string().describe( - "Reality data id of a ContextScene pointing to photos with annotations (in the contextscene file)." - ), -}); -export type TrainingO2DInputs = z.infer; - -export const TrainingO2DOutputsSchema = z.object({ - detector: z.string().describe("Reality data id of the detector."), - metrics: z - .string() - .regex(/^bkt:.+/, "Must start with 'bkt:'") - .describe("Path in the bucket of the training metrics") - .optional(), -}); -export type TrainingO2DOutputs = z.infer; - -export const TrainingO2DOptionsSchema = z.object({ - epochs: z - .number().int() - .describe("Number of time to iterate over the entire dataset") - .optional(), - maxTrainingSplit: z - .number() - .gt(0.0, { message: "Must be greater than 0.0" }) - .lte(1.0, { message: "Must be less than or equal to 1.0" }) - .describe( - "Ratio (between 0.0 excluded and 1.0 included) of training data used to train the detector, the rest will be used to evaluate the model after each epoch and compute extra evaluation metrics" - ) - .optional(), -}); -export type TrainingO2DOptions = z.infer; - -export enum TrainingO2DOutputsCreate { - DETECTOR = "detector", - METRICS = "metrics" -} - -export const TrainingO2DSpecificationsCreateSchema = z.object({ - inputs: TrainingO2DInputsSchema.describe("Inputs"), - outputs: z - .array(z.nativeEnum(TrainingO2DOutputsCreate)) - .describe("Outputs"), - options: TrainingO2DOptionsSchema.describe("Options").optional(), -}); -export type TrainingO2DSpecificationsCreate = z.infer; - -export const TrainingO2DSpecificationsSchema = z.object({ - inputs: TrainingO2DInputsSchema.describe("Inputs"), - outputs: TrainingO2DOutputsSchema.describe("Outputs"), - options: TrainingO2DOptionsSchema.describe("Options").optional(), -}); -export type TrainingO2DSpecifications = z.infer; - export const TrainingS3DInputsSchema = z.object({ - scene: z.string().describe( - "Reality data id of a ContextScene pointing to photos with annotations (in the contextscene file)." - ), + segmentations3D: z.array(z.string()).describe("List of 3D models to train on."), + preset: z.string().optional().describe("Path to a preset"), + detectorName: z.string().describe("Name of the detector to train"), }); export type TrainingS3DInputs = z.infer; export const TrainingS3DOutputsSchema = z.object({ - detector: z.string().describe("Reality data id of the detector."), + detector: z.string().describe("Full detector information (name/version)"), }); export type TrainingS3DOutputs = z.infer; +export enum Segmentation3DTrainingModel { + SPLATNET = "SPLATNet" +} + +export enum PointCloudFeature { + RGB = "RGB", + NORMAL = "NORMAL", + INTENSITY = "INTENSITY" +} + export const TrainingS3DOptionsSchema = z.object({ epochs: z - .number() + .number().int() + .gte(1, { message: "Must be greater than or equal to 1" }) + .lte(100, { message: "Must be less than or equal to 100" }) .describe("Number of time to iterate over the entire dataset") .optional(), spacing: z .number() - .describe("Spacing of the pointcloud seen by the detector") + .gt(0, { message: "Must be greater than 0" }) + .describe("Spacing of the pointcloud seen by the detector (in meters).") .optional(), - maxTrainingSplit: z - .number() - .gt(0.0, { message: "Must be greater than 0.0" }) - .lte(1.0, { message: "Must be less than or equal to 1.0" }) - .describe( - "Ratio (between 0.0 excluded and 1.0 included) of training data used to train the detector, the rest will be used to evaluate the model after each epoch and compute extra evaluation metrics" - ) + model: z + .nativeEnum(Segmentation3DTrainingModel) + .describe("Training Model architecture to use.") + .optional(), + features: z + .array(z.nativeEnum(PointCloudFeature)) + .describe("Features to use for the training.") + .optional(), + versionNumber: z + .string() + .regex(/^\d+(?:\.\d+)?$/, "Must be a version like '1' or '1.0'") + .describe("String representing the version number for the newly trained detector.") .optional(), }); export type TrainingS3DOptions = z.infer; diff --git a/typescript/packages/reality-capture/src/tests/specifications/test_change_detection.test.ts b/typescript/packages/reality-capture/src/tests/specifications/test_change_detection.test.ts index e019cbd0..dd3e30b2 100644 --- a/typescript/packages/reality-capture/src/tests/specifications/test_change_detection.test.ts +++ b/typescript/packages/reality-capture/src/tests/specifications/test_change_detection.test.ts @@ -13,19 +13,19 @@ describe("change_detection specifications", () => { describe("ChangeDetectionInputsSchema", () => { it("should validate valid inputs", () => { const valid = { - model3dA: "contextSceneId1", - model3dB: "contextSceneId2", + model3DA: "contextSceneId1", + model3DB: "contextSceneId2", }; expect(() => ChangeDetectionInputsSchema.parse(valid)).not.to.throw(); }); it("should fail when missing reference", () => { - const invalid = { model3dB: "contextSceneId2" }; + const invalid = { model3DB: "contextSceneId2" }; expect(() => ChangeDetectionInputsSchema.parse(invalid)).to.throw(z.ZodError); }); it("should fail when missing toCompare", () => { - const invalid = { model3dA: "contextSceneId1" }; + const invalid = { model3DA: "contextSceneId1" }; expect(() => ChangeDetectionInputsSchema.parse(invalid)).to.throw(z.ZodError); }); }); @@ -33,11 +33,16 @@ describe("change_detection specifications", () => { describe("ChangeDetectionOutputsSchema", () => { it("should validate with all outputs", () => { const valid = { - objects3D: "obj3dId", - locations3DAsSHP: "shpId", - locations3DAsGeoJSON: "geojsonId", - added: "addedId", - removed: "removedId", + segmentation3DA: "segmentationAId", + segmentedModel3DA: "segmentedModelAId", + segmentation3DB: "segmentationBId", + segmentedModel3DB: "segmentedModelBId", + locations3DA: "locationsAId", + locations3DB: "locationsBId", + locations3DAAsSHP: "shpAId", + locations3DAAsGeoJSON: "geojsonAId", + locations3DBAsSHP: "shpBId", + locations3DBAsGeoJSON: "geojsonBId", }; expect(() => ChangeDetectionOutputsSchema.parse(valid)).not.to.throw(); }); @@ -51,11 +56,10 @@ describe("change_detection specifications", () => { describe("ChangeDetectionOptionsSchema", () => { it("should validate all options", () => { const valid = { - outputCrs: "EPSG:4326", - minPointsPerChange: 10, - meshSamplingResolution: 0.5, - threshold: 0.2, - filterThreshold: 0.1, + minPointsPerChange: 100, + samplingResolution: 0.5, + growThreshold: 0.5, + filterThreshold: 0.2, }; expect(() => ChangeDetectionOptionsSchema.parse(valid)).not.to.throw(); }); @@ -74,8 +78,8 @@ describe("change_detection specifications", () => { describe("ChangeDetectionSpecificationsCreateSchema", () => { it("should validate valid specification", () => { const valid = { - inputs: { model3dA: "id1", model3dB: "id2" }, - outputs: [ChangeDetectionOutputsCreate.CHANGES_IN_MODEL_A, ChangeDetectionOutputsCreate.CHANGES_IN_MODEL_B], + inputs: { model3DA: "id1", model3DB: "id2" }, + outputs: [ChangeDetectionOutputsCreate.SEGMENTED_MODEL3D_A, ChangeDetectionOutputsCreate.SEGMENTED_MODEL3D_B], options: { minPointsPerChange: 10 }, }; expect(() => ChangeDetectionSpecificationsCreateSchema.parse(valid)).not.to.throw(); @@ -83,49 +87,31 @@ describe("change_detection specifications", () => { it("should fail if outputs contains invalid value", () => { const invalid = { - inputs: { model3dA: "id1", model3dB: "id2" }, + inputs: { model3DA: "id1", model3DB: "id2" }, outputs: ["notValidValue"], }; expect(() => ChangeDetectionSpecificationsCreateSchema.parse(invalid)).to.throw(z.ZodError); }); - it("should validate without options", () => { - const valid = { - inputs: { model3dA: "id1", model3dB: "id2" }, - outputs: [ChangeDetectionOutputsCreate.OBJECTS3D], - }; - expect(() => ChangeDetectionSpecificationsCreateSchema.parse(valid)).not.to.throw(); - }); }); describe("ChangeDetectionSpecificationsSchema", () => { it("should validate valid specification", () => { const valid = { - inputs: { model3dA: "id1", model3dB: "id2" }, + inputs: { model3DA: "id1", model3DB: "id2" }, outputs: { - objects3D: "objId", - locations3DAsSHP: "shpId", + locations3DAAsSHP: "shpId", }, - options: { meshSamplingResolution: 0.5 }, + options: { samplingResolution: 0.5 }, }; expect(() => ChangeDetectionSpecificationsSchema.parse(valid)).not.to.throw(); }); it("should fail if outputs is missing", () => { const invalid = { - inputs: { model3dA: "id1", model3dB: "id2" }, + inputs: { model3DA: "id1", model3DB: "id2" }, }; expect(() => ChangeDetectionSpecificationsSchema.parse(invalid)).to.throw(z.ZodError); }); - - it("should validate without options", () => { - const valid = { - inputs: { model3dA: "id1", model3dB: "id2" }, - outputs: { - objects3D: "objId" - }, - }; - expect(() => ChangeDetectionSpecificationsSchema.parse(valid)).not.to.throw(); - }); }); }); diff --git a/typescript/packages/reality-capture/src/tests/specifications/test_eval_s3d.test.ts b/typescript/packages/reality-capture/src/tests/specifications/test_eval_s3d.test.ts index a3454a4b..773e8e16 100644 --- a/typescript/packages/reality-capture/src/tests/specifications/test_eval_s3d.test.ts +++ b/typescript/packages/reality-capture/src/tests/specifications/test_eval_s3d.test.ts @@ -50,9 +50,9 @@ describe("EvalS3DOutputsSchema", () => { ).to.throw(z.ZodError); }); - it("should validate correct segmentedPointCloud output", () => { + it("should validate correct segmentedModel3D output", () => { expect(() => - EvalS3DOutputsSchema.parse({ segmentedPointCloud: "cloud-id" }) + EvalS3DOutputsSchema.parse({ segmentedModel3D: "cloud-id" }) ).to.not.throw(); }); @@ -66,7 +66,7 @@ describe("EvalS3DOutputsSchema", () => { expect(() => EvalS3DOutputsSchema.parse({ report: "bkt:my/report.json", - segmentedPointCloud: "cloud-id", + segmentedModel3D: "cloud-id", segmentation3D: "scene-id", }) ).to.not.throw(); @@ -76,7 +76,7 @@ describe("EvalS3DOutputsSchema", () => { describe("EvalS3DOutputsCreate enum", () => { it("should have the right values", () => { expect(EvalS3DOutputsCreate.REPORT).to.equal("report"); - expect(EvalS3DOutputsCreate.SEGMENTED_POINT_CLOUD).to.equal("segmentedPointCloud"); + expect(EvalS3DOutputsCreate.SEGMENTED_MODEL_3D).to.equal("segmentedModel3D"); expect(EvalS3DOutputsCreate.SEGMENTATION3D).to.equal("segmentation3D"); }); }); @@ -90,7 +90,7 @@ describe("EvalS3DSpecificationsCreateSchema", () => { }, outputs: [ EvalS3DOutputsCreate.REPORT, - EvalS3DOutputsCreate.SEGMENTED_POINT_CLOUD, + EvalS3DOutputsCreate.SEGMENTED_MODEL_3D, ], }; expect(() => EvalS3DSpecificationsCreateSchema.parse(input)).to.not.throw(); @@ -127,7 +127,7 @@ describe("EvalS3DSpecificationsSchema", () => { }, outputs: { report: "bkt:my/report.json", - segmentedPointCloud: "cloud-id", + segmentedModel3D: "cloud-id", segmentation3D: "scene-id", }, }; diff --git a/typescript/packages/reality-capture/src/tests/specifications/test_segmentation3d.test.ts b/typescript/packages/reality-capture/src/tests/specifications/test_segmentation3d.test.ts index 3c3f55e2..3442810d 100644 --- a/typescript/packages/reality-capture/src/tests/specifications/test_segmentation3d.test.ts +++ b/typescript/packages/reality-capture/src/tests/specifications/test_segmentation3d.test.ts @@ -40,11 +40,7 @@ describe("Segmentation3DOutputsSchema", () => { it("should validate all outputs as strings", () => { const data: Record = { segmentation3D: "id1", - segmentedPointCloud: "id2", - segmentation3DAsPOD: "id3", - segmentation3DAsLAS: "id4", - segmentation3DAsLAZ: "id5", - segmentation3DAsPLY: "id6", + segmentedModel3D: "id2", objects3D: "id7", objects3DAs3DTiles: "id8", objects3DAsGeoJSON: "id9", @@ -73,7 +69,6 @@ describe("Segmentation3DOptionsSchema", () => { saveConfidence: true, computeLineWidth: false, removeSmallLines: 0.2, - keepInputResolution: true, }; expect(() => Segmentation3DOptionsSchema.parse(data)).to.not.throw(); }); @@ -90,7 +85,9 @@ describe("Segmentation3DSpecificationsCreateSchema", () => { inputs: {}, outputs: [Segmentation3DOutputsCreate.SEGMENTATION3D], }; - expect(() => Segmentation3DSpecificationsCreateSchema.parse(data)).to.not.throw(); + expect(() => + Segmentation3DSpecificationsCreateSchema.parse(data), + ).to.not.throw(); }); it("should validate with options", () => { @@ -99,12 +96,16 @@ describe("Segmentation3DSpecificationsCreateSchema", () => { outputs: [Segmentation3DOutputsCreate.SEGMENTATION3D], options: { crs: "EPSG:4326" }, }; - expect(() => Segmentation3DSpecificationsCreateSchema.parse(data)).to.not.throw(); + expect(() => + Segmentation3DSpecificationsCreateSchema.parse(data), + ).to.not.throw(); }); it("should fail if outputs is missing", () => { const data = { inputs: {} }; - expect(() => Segmentation3DSpecificationsCreateSchema.parse(data)).to.throw(z.ZodError); + expect(() => Segmentation3DSpecificationsCreateSchema.parse(data)).to.throw( + z.ZodError, + ); }); }); @@ -113,26 +114,38 @@ describe("Segmentation3DSpecificationsSchema", () => { const data = { inputs: { model3D: "id", extent: "bkt:/polygon" }, outputs: { segmentation3D: "id1" }, - options: { keepInputResolution: true }, }; expect(() => Segmentation3DSpecificationsSchema.parse(data)).to.not.throw(); }); it("should fail if inputs are missing", () => { const data = { outputs: {}, options: {} }; - expect(() => Segmentation3DSpecificationsSchema.parse(data)).to.throw(z.ZodError); + expect(() => Segmentation3DSpecificationsSchema.parse(data)).to.throw( + z.ZodError, + ); }); it("should fail if outputs are missing", () => { const data = { inputs: {} }; - expect(() => Segmentation3DSpecificationsSchema.parse(data)).to.throw(z.ZodError); + expect(() => Segmentation3DSpecificationsSchema.parse(data)).to.throw( + z.ZodError, + ); }); }); describe("Segmentation3DOutputsCreate enum", () => { it("should contain expected values", () => { - expect(Segmentation3DOutputsCreate.SEGMENTATION3D).to.equal("segmentation3D"); - expect(Segmentation3DOutputsCreate.OBJECTS3D_AS_GEOJSON).to.equal("objects3DAsGeoJSON"); - expect(Segmentation3DOutputsCreate.POLYGONS3D_AS_GEOJSON).to.equal("polygons3DAsGeoJSON"); + expect(Segmentation3DOutputsCreate.SEGMENTATION3D).to.equal( + "segmentation3D", + ); + expect(Segmentation3DOutputsCreate.SEGMENTED_MODEL_3D).to.equal( + "segmentedModel3D", + ); + expect(Segmentation3DOutputsCreate.OBJECTS3D_AS_GEOJSON).to.equal( + "objects3DAsGeoJSON", + ); + expect(Segmentation3DOutputsCreate.POLYGONS3D_AS_GEOJSON).to.equal( + "polygons3DAsGeoJSON", + ); }); }); diff --git a/typescript/packages/reality-capture/src/tests/specifications/test_training.test.ts b/typescript/packages/reality-capture/src/tests/specifications/test_training.test.ts index 933a2507..06034e47 100644 --- a/typescript/packages/reality-capture/src/tests/specifications/test_training.test.ts +++ b/typescript/packages/reality-capture/src/tests/specifications/test_training.test.ts @@ -1,135 +1,34 @@ import { expect } from "chai"; import { z } from "zod"; import { - TrainingO2DInputsSchema, - TrainingO2DOutputsSchema, - TrainingO2DOptionsSchema, - TrainingO2DOutputsCreate, - TrainingO2DSpecificationsCreateSchema, - TrainingO2DSpecificationsSchema, TrainingS3DInputsSchema, TrainingS3DOutputsSchema, TrainingS3DOptionsSchema, TrainingS3DOutputsCreate, + Segmentation3DTrainingModel, + PointCloudFeature, TrainingS3DSpecificationsCreateSchema, TrainingS3DSpecificationsSchema, } from "../../specifications/training"; -describe("TrainingO2DInputsSchema", () => { +describe("TrainingS3DInputsSchema", () => { it("should validate a correct input", () => { - const data = { scene: "some-id" }; - expect(() => TrainingO2DInputsSchema.parse(data)).not.to.throw(); - }); - - it("should fail if scene is missing", () => { - const data = {}; - expect(() => TrainingO2DInputsSchema.parse(data)).to.throw(z.ZodError); - }); -}); - -describe("TrainingO2DOutputsSchema", () => { - it("should validate a correct output with metrics", () => { - const data = { detector: "det-id", metrics: "bkt:/metrics/path" }; - expect(() => TrainingO2DOutputsSchema.parse(data)).not.to.throw(); - }); - - it("should validate a correct output without metrics", () => { - const data = { detector: "det-id" }; - expect(() => TrainingO2DOutputsSchema.parse(data)).not.to.throw(); - }); - - it("should fail if detector is missing", () => { - const data = { metrics: "bkt:/metrics/path" }; - expect(() => TrainingO2DOutputsSchema.parse(data)).to.throw(z.ZodError); - }); - - it("should fail if metrics does not start with bkt:", () => { - const data = { detector: "det-id", metrics: "/metrics/path" }; - expect(() => TrainingO2DOutputsSchema.parse(data)).to.throw(z.ZodError); - }); -}); - -describe("TrainingO2DOptionsSchema", () => { - it("should validate correct options", () => { - const data = { epochs: 10, maxTrainingSplit: 0.8 }; - expect(() => TrainingO2DOptionsSchema.parse(data)).not.to.throw(); - }); - - it("should accept partial data", () => { - const data = {}; - expect(() => TrainingO2DOptionsSchema.parse(data)).not.to.throw(); - }); - - it("should fail if maxTrainingSplit is <= 0", () => { - const data = { maxTrainingSplit: 0 }; - expect(() => TrainingO2DOptionsSchema.parse(data)).to.throw(z.ZodError); - }); - - it("should fail if maxTrainingSplit is > 1", () => { - const data = { maxTrainingSplit: 1.1 }; - expect(() => TrainingO2DOptionsSchema.parse(data)).to.throw(z.ZodError); - }); - - it("should fail if epochs is not an int", () => { - const data = { epochs: 3.5 }; - expect(() => TrainingO2DOptionsSchema.parse(data)).to.throw(z.ZodError); - }); -}); - -describe("TrainingO2DSpecificationsCreateSchema", () => { - it("should validate a minimal spec", () => { - const data = { - inputs: { scene: "scene-id" }, - outputs: [TrainingO2DOutputsCreate.DETECTOR], - }; - expect(() => TrainingO2DSpecificationsCreateSchema.parse(data)).not.to.throw(); - }); - - it("should validate a full spec", () => { - const data = { - inputs: { scene: "scene-id" }, - outputs: [TrainingO2DOutputsCreate.DETECTOR, TrainingO2DOutputsCreate.METRICS], - options: { epochs: 5, maxTrainingSplit: 0.7 }, - }; - expect(() => TrainingO2DSpecificationsCreateSchema.parse(data)).not.to.throw(); - }); - - it("should fail if inputs are invalid", () => { - const data = { - inputs: { scene: 123 }, - outputs: [TrainingO2DOutputsCreate.DETECTOR], - }; - expect(() => TrainingO2DSpecificationsCreateSchema.parse(data)).to.throw(z.ZodError); - }); -}); - -describe("TrainingO2DSpecificationsSchema", () => { - it("should validate a full specification", () => { - const data = { - inputs: { scene: "scene-id" }, - outputs: { detector: "det-id", metrics: "bkt:/metrics/path" }, - options: { epochs: 10, maxTrainingSplit: 0.9 }, - }; - expect(() => TrainingO2DSpecificationsSchema.parse(data)).not.to.throw(); + const data = { segmentations3D: ["model-id"], detectorName: "my-detector" }; + expect(() => TrainingS3DInputsSchema.parse(data)).not.to.throw(); }); - it("should fail if outputs is invalid", () => { - const data = { - inputs: { scene: "scene-id" }, - outputs: { detector: "det-id", metrics: "/metrics/path" }, - }; - expect(() => TrainingO2DSpecificationsSchema.parse(data)).to.throw(z.ZodError); + it("should validate a correct input with preset", () => { + const data = { segmentations3D: ["model-id"], preset: "path/to/preset", detectorName: "my-detector" }; + expect(() => TrainingS3DInputsSchema.parse(data)).not.to.throw(); }); -}); -describe("TrainingS3DInputsSchema", () => { - it("should validate a correct input", () => { - const data = { scene: "scene-id" }; - expect(() => TrainingS3DInputsSchema.parse(data)).not.to.throw(); + it("should fail if segmentations3D is missing", () => { + const data = { detectorName: "my-detector" }; + expect(() => TrainingS3DInputsSchema.parse(data)).to.throw(z.ZodError); }); - it("should fail if scene is missing", () => { - const data = {}; + it("should fail if detectorName is missing", () => { + const data = { segmentations3D: ["model-id"] }; expect(() => TrainingS3DInputsSchema.parse(data)).to.throw(z.ZodError); }); }); @@ -148,7 +47,13 @@ describe("TrainingS3DOutputsSchema", () => { describe("TrainingS3DOptionsSchema", () => { it("should validate correct options", () => { - const data = { epochs: 10, spacing: 0.5, maxTrainingSplit: 0.7 }; + const data = { + epochs: 10, + spacing: 0.5, + model: Segmentation3DTrainingModel.SPLATNET, + features: [PointCloudFeature.RGB, PointCloudFeature.NORMAL], + versionNumber: "1.0", + }; expect(() => TrainingS3DOptionsSchema.parse(data)).not.to.throw(); }); @@ -157,13 +62,23 @@ describe("TrainingS3DOptionsSchema", () => { expect(() => TrainingS3DOptionsSchema.parse(data)).not.to.throw(); }); - it("should fail if maxTrainingSplit is <= 0", () => { - const data = { maxTrainingSplit: 0 }; + it("should fail if epochs is not an int", () => { + const data = { epochs: 3.5 }; + expect(() => TrainingS3DOptionsSchema.parse(data)).to.throw(z.ZodError); + }); + + it("should fail if epochs is < 1", () => { + const data = { epochs: 0 }; + expect(() => TrainingS3DOptionsSchema.parse(data)).to.throw(z.ZodError); + }); + + it("should fail if epochs is > 100", () => { + const data = { epochs: 101 }; expect(() => TrainingS3DOptionsSchema.parse(data)).to.throw(z.ZodError); }); - it("should fail if maxTrainingSplit is > 1", () => { - const data = { maxTrainingSplit: 1.2 }; + it("should fail if spacing is <= 0", () => { + const data = { spacing: 0 }; expect(() => TrainingS3DOptionsSchema.parse(data)).to.throw(z.ZodError); }); }); @@ -171,7 +86,7 @@ describe("TrainingS3DOptionsSchema", () => { describe("TrainingS3DSpecificationsCreateSchema", () => { it("should validate a minimal spec", () => { const data = { - inputs: { scene: "scene-id" }, + inputs: { segmentations3D: ["model-id"], detectorName: "my-detector" }, outputs: [TrainingS3DOutputsCreate.DETECTOR], }; expect(() => TrainingS3DSpecificationsCreateSchema.parse(data)).not.to.throw(); @@ -179,9 +94,15 @@ describe("TrainingS3DSpecificationsCreateSchema", () => { it("should validate a full spec", () => { const data = { - inputs: { scene: "scene-id" }, + inputs: { segmentations3D: ["model-id"], preset: "path/to/preset", detectorName: "my-detector" }, outputs: [TrainingS3DOutputsCreate.DETECTOR], - options: { epochs: 3, spacing: 2.0, maxTrainingSplit: 0.5 }, + options: { + epochs: 3, + spacing: 2.0, + model: Segmentation3DTrainingModel.SPLATNET, + features: [PointCloudFeature.INTENSITY], + versionNumber: "2.1", + }, }; expect(() => TrainingS3DSpecificationsCreateSchema.parse(data)).not.to.throw(); }); @@ -190,16 +111,16 @@ describe("TrainingS3DSpecificationsCreateSchema", () => { describe("TrainingS3DSpecificationsSchema", () => { it("should validate a full specification", () => { const data = { - inputs: { scene: "scene-id" }, + inputs: { segmentations3D: ["model-id"], detectorName: "my-detector" }, outputs: { detector: "det-id" }, - options: { epochs: 2, spacing: 0.1, maxTrainingSplit: 0.99 }, + options: { epochs: 2, spacing: 0.1 }, }; expect(() => TrainingS3DSpecificationsSchema.parse(data)).not.to.throw(); }); it("should fail if outputs is invalid", () => { const data = { - inputs: { scene: "scene-id" }, + inputs: { segmentations3D: ["model-id"], detectorName: "my-detector" }, outputs: {}, }; expect(() => TrainingS3DSpecificationsSchema.parse(data)).to.throw(z.ZodError); diff --git a/typescript/packages/reality-capture/tsconfig.json b/typescript/packages/reality-capture/tsconfig.json index 21cae272..6009cdb9 100644 --- a/typescript/packages/reality-capture/tsconfig.json +++ b/typescript/packages/reality-capture/tsconfig.json @@ -15,5 +15,9 @@ "exclude": [ "lib", "src/tests/**/*" - ] + ], + "ts-node": { + "esm": false, + "transpileOnly": true + } } \ No newline at end of file