diff --git a/opengeodeweb_viewer_schemas.json b/opengeodeweb_viewer_schemas.json index ffa7eb20..d8cd64a5 100644 --- a/opengeodeweb_viewer_schemas.json +++ b/opengeodeweb_viewer_schemas.json @@ -2544,6 +2544,59 @@ "camera_options" ] }, + "threshold": { + "$id": "opengeodeweb_viewer.viewer.threshold", + "rpc": "threshold", + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 32, + "maxLength": 32 + } + }, + "attribute": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "location": { + "type": "string", + "enum": [ + "point", + "cell" + ] + }, + "item": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "required": [ + "name", + "location", + "item", + "minimum", + "maximum" + ], + "additionalProperties": false + } + }, + "required": [ + "ids" + ], + "additionalProperties": false + }, "take_screenshot": { "$id": "opengeodeweb_viewer.viewer.take_screenshot", "rpc": "take_screenshot", diff --git a/requirements.txt b/requirements.txt index 0085a2fa..fce3de17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,4 +63,3 @@ wslink==1.12.4 yarl==1.* # via aiohttp -opengeodeweb-microservice==1.*,>=1.2.2 diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py b/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py index 9571601a..30414916 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/__init__.py @@ -1,5 +1,6 @@ from .update_data import * from .update_camera import * +from .threshold import * from .take_screenshot import * from .shrink import * from .set_z_scaling import * diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.json b/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.json new file mode 100644 index 00000000..eb90f3c6 --- /dev/null +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.json @@ -0,0 +1,52 @@ +{ + "rpc": "threshold", + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 32, + "maxLength": 32 + } + }, + "attribute": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "location": { + "type": "string", + "enum": [ + "point", + "cell" + ] + }, + "item": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "required": [ + "name", + "location", + "item", + "minimum", + "maximum" + ], + "additionalProperties": false + } + }, + "required": [ + "ids" + ], + "additionalProperties": false +} diff --git a/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.py b/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.py new file mode 100644 index 00000000..3bd0638b --- /dev/null +++ b/src/opengeodeweb_viewer/rpc/viewer/schemas/threshold.py @@ -0,0 +1,30 @@ +from dataclasses_json import DataClassJsonMixin +from enum import Enum +from dataclasses import dataclass +from typing import List, Optional + + +class Location(Enum): + CELL = "cell" + POINT = "point" + + +@dataclass +class Attribute(DataClassJsonMixin): + def __post_init__(self) -> None: + print(self, flush=True) + + item: int + location: Location + maximum: float + minimum: float + name: str + + +@dataclass +class Threshold(DataClassJsonMixin): + def __post_init__(self) -> None: + print(self, flush=True) + + ids: List[str] + attribute: Optional[Attribute] = None diff --git a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py index a143ac30..9519d64a 100644 --- a/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py +++ b/src/opengeodeweb_viewer/rpc/viewer/viewer_protocols.py @@ -385,6 +385,14 @@ def setShrink(self, rpc_params: RpcParams) -> None: params = schemas.Shrink.from_dict(rpc_params) self.set_shrink(params.ids, params.shrink_factor) + @exportRpc(viewer_prefix + viewer_schemas_dict["threshold"]["rpc"]) + def setThreshold(self, rpc_params: RpcParams) -> None: + validate_schema( + rpc_params, self.viewer_schemas_dict["threshold"], self.viewer_prefix + ) + params = schemas.Threshold.from_dict(rpc_params) + self.set_threshold(params.ids, params.attribute) + @exportRpc(viewer_prefix + viewer_schemas_dict["set_z_scaling"]["rpc"]) def setZScaling(self, rpc_params: RpcParams) -> None: validate_schema( diff --git a/src/opengeodeweb_viewer/vtk_pipeline.py b/src/opengeodeweb_viewer/vtk_pipeline.py index b04bd665..93533a8f 100644 --- a/src/opengeodeweb_viewer/vtk_pipeline.py +++ b/src/opengeodeweb_viewer/vtk_pipeline.py @@ -35,6 +35,7 @@ vtkExtractGeometry, vtkExtractSelection, ) +from vtkmodules.vtkFiltersCore import vtkThreshold from vtkmodules.vtkFiltersGeneral import vtkShrinkFilter from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter from vtkmodules.vtkIOXML import vtkXMLReader @@ -211,6 +212,7 @@ class VtkPipeline: filter: vtkGeometryFilter = field(default_factory=vtkGeometryFilter) actor: vtkActor = field(default_factory=vtkActor) clipping_filter: vtkExtractGeometry | None = None + threshold_filter: vtkThreshold | None = None shrink_filter: vtkShrinkFilter | None = None highlight: HighlightPipeline = field(default_factory=HighlightPipeline) blockDataSets: list[vtkDataObject | None] = field(default_factory=list) diff --git a/src/opengeodeweb_viewer/vtk_protocol.py b/src/opengeodeweb_viewer/vtk_protocol.py index af4e142a..ba022648 100644 --- a/src/opengeodeweb_viewer/vtk_protocol.py +++ b/src/opengeodeweb_viewer/vtk_protocol.py @@ -23,6 +23,7 @@ vtkPlane, vtkSelectionNode, ) +from vtkmodules.vtkFiltersCore import vtkThreshold from vtkmodules.vtkFiltersExtraction import vtkExtractGeometry from vtkmodules.vtkFiltersGeneral import vtkShrinkFilter from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter @@ -37,6 +38,7 @@ from opengeodeweb_microservice.database.connection import get_session from opengeodeweb_microservice.database.data import Data from opengeodeweb_viewer.rpc.viewer.schemas.clipping_planes import Plane +from opengeodeweb_viewer.rpc.viewer.schemas.threshold import Attribute, Location from opengeodeweb_viewer.vtk_pipeline import ( RulerPipeline, ViewerData, @@ -179,7 +181,11 @@ def update_pipeline_filter(self, pipeline: VtkPipeline) -> None: current_input_port = pipeline.reader.GetOutputPort() active_filters = [ filter_obj - for filter_obj in (pipeline.clipping_filter, pipeline.shrink_filter) + for filter_obj in ( + pipeline.clipping_filter, + pipeline.threshold_filter, + pipeline.shrink_filter, + ) if filter_obj is not None ] for filter_obj in active_filters: @@ -219,6 +225,30 @@ def set_clipping_planes( pipeline.clipping_filter = None self.update_pipeline_filter(pipeline) + def set_threshold(self, data_ids: list[str], attribute: Attribute | None) -> None: + for data_id in data_ids: + pipeline = self.get_vtk_pipeline(data_id) + if attribute: + threshold_filter = vtkThreshold() + threshold_filter.SetInputArrayToProcess( + 0, + 0, + 0, + ( + vtkDataObject.FIELD_ASSOCIATION_POINTS + if attribute.location == Location.POINT + else vtkDataObject.FIELD_ASSOCIATION_CELLS + ), + attribute.name, + ) + threshold_filter.SetSelectedComponent(attribute.item) + threshold_filter.SetLowerThreshold(attribute.minimum) + threshold_filter.SetUpperThreshold(attribute.maximum) + pipeline.threshold_filter = threshold_filter + else: + pipeline.threshold_filter = None + self.update_pipeline_filter(pipeline) + def set_shrink(self, data_ids: list[str], shrink_factor: float) -> None: for data_id in data_ids: pipeline = self.get_vtk_pipeline(data_id) diff --git a/tests/data/images/mesh/cells/threshold.jpeg b/tests/data/images/mesh/cells/threshold.jpeg new file mode 100644 index 00000000..c2e92f6a Binary files /dev/null and b/tests/data/images/mesh/cells/threshold.jpeg differ diff --git a/tests/data/images/model/blocks/threshold.jpeg b/tests/data/images/model/blocks/threshold.jpeg new file mode 100644 index 00000000..a6822efb Binary files /dev/null and b/tests/data/images/model/blocks/threshold.jpeg differ diff --git a/tests/mesh/cells/test_mesh_cells_protocols.py b/tests/mesh/cells/test_mesh_cells_protocols.py index dad7fde5..6e925ea7 100644 --- a/tests/mesh/cells/test_mesh_cells_protocols.py +++ b/tests/mesh/cells/test_mesh_cells_protocols.py @@ -96,3 +96,35 @@ def test_cells_shrink( ], ) assert server.compare_image("mesh/cells/shrink.jpeg") == True + + +def test_cells_threshold( + server: ServerMonitor, dataset_factory: Callable[..., str] +) -> None: + + test_register(server, dataset_factory) + + server.call( + VtkViewerView.viewer_prefix + + VtkViewerView.viewer_schemas_dict["threshold"]["rpc"], + [ + { + "ids": [mesh_id], + "attribute": { + "name": "RGB_data", + "location": "cell", + "item": 0, + "minimum": 0.0, + "maximum": 128.0, + }, + } + ], + ) + assert server.compare_image("mesh/cells/threshold.jpeg") == True + + server.call( + VtkViewerView.viewer_prefix + + VtkViewerView.viewer_schemas_dict["threshold"]["rpc"], + [{"ids": [mesh_id]}], + ) + assert server.compare_image("mesh/cells/register.jpeg") == True diff --git a/tests/model/blocks/test_model_blocks_protocols.py b/tests/model/blocks/test_model_blocks_protocols.py index 6b9b8658..c37e6dba 100644 --- a/tests/model/blocks/test_model_blocks_protocols.py +++ b/tests/model/blocks/test_model_blocks_protocols.py @@ -112,3 +112,28 @@ def test_blocks_shrink( ], ) assert server.compare_image("model/blocks/shrink.jpeg") == True + + +def test_blocks_threshold( + server: ServerMonitor, dataset_factory: Callable[..., str] +) -> None: + + test_register_model_cube(server, dataset_factory) + + server.call( + VtkViewerView.viewer_prefix + + VtkViewerView.viewer_schemas_dict["threshold"]["rpc"], + [ + { + "ids": [model_id], + "attribute": { + "name": "tetrahedron_vertices", + "location": "cell", + "item": 0, + "minimum": 0.0, + "maximum": 300.0, + }, + } + ], + ) + assert server.compare_image("model/blocks/threshold.jpeg") == True