diff --git a/colab/GettingStarted.ipynb b/colab/GettingStarted.ipynb index 2e2aa81..a1ed735 100644 --- a/colab/GettingStarted.ipynb +++ b/colab/GettingStarted.ipynb @@ -41,7 +41,9 @@ "cell_type": "code", "execution_count": null, "id": "b377bfbb-d1a7-4df8-bcc7-ce13725229f8", - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ "!pip install open-iris" @@ -221,9 +223,9 @@ "id": "3b2896e5-5069-48db-b855-cd80ea04cd6e", "metadata": {}, "source": [ - "The `iris_template` value contains generated by the `IRISPipeline` iris code for an iris texture visible in the input image. The `output[\"iris_template\"]` value is a `dict` containing two keys: `[\"iris_codes\", \"mask_codes\"]`. \n", + "The `iris_template` value contains generated by the `IRISPipeline` iris code for an iris texture visible in the input image. The `output[\"iris_template\"]` value is a `IrisTemplate` object containing two fields: `[\"iris_codes: List[np.ndarray]\", \"mask_codes: List[np.ndarray]\"]`. \n", "\n", - "Each code available in `output[\"iris_template\"]` dictionary is a `numpy.ndarray` of shape `(16, 256, 2, 2)`. The output shape of iris code is determined by `IRISPipeline` filter bank parameters. The iris/mask code shape's dimmensions correspond to the following `(iris_code_height, iris_code_width, num_filters, 2)`. Values `iris_code_height` and `iris_code_width` are determined by `ProbeSchema`s defined for `ConvFilterBank` object and `num_filters` is determined by number of filters specified for `ConvFilterBank` object. The last `2` value of the iris/mask code dimmension corresponds to real and complex parts of each complex filter response.\n", + "Each code available in `output[\"iris_template\"]` object is a `numpy.ndarray` of shape `(16, 256, 2)`. The length of arrays containing iris codes and mask codes is determined by `IRISPipeline` filter bank parameters. The iris/mask code shape's dimmensions correspond to the following `(iris_code_height, iris_code_width, 2)`. Values `iris_code_height` and `iris_code_width` are determined by `ProbeSchema`'s definition for `ConvFilterBank` object and `num_filters` is determined by number of filters specified for `ConvFilterBank` object. The last `2` value of the iris/mask code dimmension corresponds to the real and imaginary parts of each complex filter response.\n", "\n", "_NOTE_: More about how to specify those parameters and configuring custom `IRISPipeline` can be found in the _Configuring custom pipeline_ tutorial." ] @@ -235,27 +237,20 @@ "metadata": {}, "outputs": [], "source": [ - "\"\"\"Available keys in `output[\"iris_template\"]` are: \"\"\" + str(output[\"iris_template\"].keys())" + "\"\"\"Available fields in `output[\"iris_template\"]` are: \"\"\" + str(output[\"iris_template\"].__fields__)" ] }, { "cell_type": "code", "execution_count": null, - "id": "1bbfbc26-6479-43f2-b548-a57070d66092", + "id": "302b0b34-04d9-46dd-8f4c-038955731b66", "metadata": {}, "outputs": [], "source": [ - "\"\"\"`output[\"iris_template\"]` value types are: \"\"\" + type(output[\"iris_template\"][\"iris_codes\"]).__name__ + \", \" + type(output[\"iris_template\"][\"mask_codes\"]).__name__" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c87e03af-db75-4086-91a9-177251560c83", - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"`output[\"iris_template\"]` value shapes are: \"\"\" + str(output[\"iris_template\"][\"iris_codes\"].shape) + \", \" + str(output[\"iris_template\"][\"mask_codes\"].shape)" + "num_codes = len(output[\"iris_template\"].iris_codes)\n", + "code_shape = output[\"iris_template\"].iris_codes[0].shape\n", + "\n", + "f\"\"\"Number of returned iris codes is equal to {num_codes} and each code shape is {code_shape}\"\"\"" ] }, { diff --git a/docs/source/examples/getting_started.rst b/docs/source/examples/getting_started.rst index 9764097..640e116 100644 --- a/docs/source/examples/getting_started.rst +++ b/docs/source/examples/getting_started.rst @@ -52,9 +52,9 @@ If ``output["error"]`` value is ``None``, ``IRISPipeline`` finished inference ca 'traceback': 'Very long exception traceback' } -The ``iris_template`` value contains generated by the ``IRISPipeline`` iris code for an iris texture visible in the input image. The ``output["iris_template"]`` value is a ``dict`` containing two keys: ``["iris_codes", "mask_codes"]``. +The ``iris_template`` value contains generated by the ``IRISPipeline`` iris code for an iris texture visible in the input image. The ``output["iris_template"]`` value is a ``IrisTemplate`` object containing two fields: ``["iris_codes: List[np.ndarray]", "mask_codes: List[np.ndarray]"]``. -Each code available in ``output["iris_template"]`` dictionary is a ``numpy.ndarray`` of shape ``(16, 256, 2, 2)``. The output shape of iris code is determined by ``IRISPipeline`` filter bank parameters. The iris/mask code shape's dimensions correspond to the following ``(iris_code_height, iris_code_width, num_filters, 2)``. Values ``iris_code_height`` and ``iris_code_width`` are determined by ``ProbeSchema``s defined for ``ConvFilterBank`` object and ``num_filters`` is determined by number of filters specified for ``ConvFilterBank`` object. The last ``2`` value of the iris/mask code dimension corresponds to real and complex parts of each complex filter response. +Each code available in ``output["iris_template"]`` dictionary is a ``numpy.ndarray`` of shape ``(16, 256, 2)``. The length of arrays containing iris codes and mask codes is determined by ``IRISPipeline`` filter bank parameters. The iris/mask code shape's dimensions correspond to the following ``(iris_code_height, iris_code_width, 2)``. Values ``iris_code_height`` and ``iris_code_width`` are determined by ``ProbeSchema``'s definition for ``ConvFilterBank`` object and ``num_filters`` is determined by number of filters specified for ``ConvFilterBank`` object. The last ``2`` value of the iris/mask code dimension corresponds to the real and imaginary parts of each complex filter response. *NOTE*: More about how to specify those parameters and configuring custom ``IRISPipeline`` can be found in the *Configuring custom pipeline* tutorial. diff --git a/pyproject.toml b/pyproject.toml index ff8ae90..493cccb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ exclude_lines = ["if __name__ == .__main__.:"] [tool.ruff] exclude = ["__init__.py"] -select = ["E", "F", "PLC", "PLE", "PLR", "PLW"] -ignore = ["E501", "F722", "F821", "PLR2004", "PLR0915", "PLR0913", "PLC0414", "PLR0402", "PLR5501", "PLR0911", "PLR0912", "PLW0603", "PLW2901"] +lint.select = ["E", "F", "PLC", "PLE", "PLR", "PLW"] +lint.ignore = ["E501", "F722", "F821", "PLR2004", "PLR0915", "PLR0913", "PLC0414", "PLR0402", "PLR5501", "PLR0911", "PLR0912", "PLW0603", "PLW2901"] [tool.isort] profile = "black" diff --git a/src/iris/nodes/iris_response/image_filters/gabor_filters.py b/src/iris/nodes/iris_response/image_filters/gabor_filters.py index ed817cc..0afaacb 100644 --- a/src/iris/nodes/iris_response/image_filters/gabor_filters.py +++ b/src/iris/nodes/iris_response/image_filters/gabor_filters.py @@ -318,9 +318,7 @@ def compute_kernel_values(self) -> np.ndarray: # calculate envelope and orientation envelope = np.exp( - -0.5 - * np.log2(radius * self.params.lambda_rho / self.params.kernel_size[1]) ** 2 - / self.params.sigma_rho**2 + -0.5 * np.log2(radius * self.params.lambda_rho / self.params.kernel_size[1]) ** 2 / self.params.sigma_rho**2 ) envelope[ksize_rho_half][ksize_phi_half] = 0 orientation = np.exp(-0.5 * dtheta**2 / self.params.sigma_phi**2) diff --git a/src/iris/nodes/normalization/nonlinear_normalization.py b/src/iris/nodes/normalization/nonlinear_normalization.py index 46f514f..688d519 100644 --- a/src/iris/nodes/normalization/nonlinear_normalization.py +++ b/src/iris/nodes/normalization/nonlinear_normalization.py @@ -38,7 +38,7 @@ def __init__(self, res_in_r: int = 128, oversat_threshold: int = 254) -> None: Args: res_in_r (int): Normalized image r resolution. Defaults to 128. - oversat_threshold (int, optional): threshold for masking over-satuated pixels. Defaults to 254. + oversat_threshold (int, optional): threshold for masking over-satuated pixels. Defaults to 254. """ intermediate_radiuses = np.array([getgrids(max(0, res_in_r), p2i_ratio) for p2i_ratio in range(100)]) super().__init__( diff --git a/src/iris/orchestration/output_builders.py b/src/iris/orchestration/output_builders.py index d74bfac..e451b5c 100644 --- a/src/iris/orchestration/output_builders.py +++ b/src/iris/orchestration/output_builders.py @@ -6,7 +6,7 @@ from iris.io.dataclasses import ImmutableModel -def build_orb_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: +def build_simple_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: """Build the output for the Orb. Args: @@ -14,26 +14,14 @@ def build_orb_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: Returns: Dict[str, Any]: { - "iris_template": (Optional[Dict]) the iris template dict if the pipeline succeeded, + "iris_template": (Optional[IrisTemplate]) the iris template object if the pipeline succeeded, "error": (Optional[Dict]) the error dict if the pipeline returned an error, "metadata": (Dict) the metadata dict, }. """ - iris_template = __safe_serialize(call_trace["encoder"]) metadata = __get_metadata(call_trace=call_trace) error = __get_error(call_trace=call_trace) - - exception = call_trace.get_error() - if exception is None: - iris_template = __safe_serialize(call_trace["encoder"]) - error = None - elif isinstance(exception, Exception): - iris_template = None - error = { - "error_type": type(exception).__name__, - "message": str(exception), - "traceback": "".join(traceback.format_tb(exception.__traceback__)), - } + iris_template = call_trace["encoder"] output = { "error": error, @@ -44,6 +32,25 @@ def build_orb_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: return output +def build_orb_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: + """Build the output for the Orb. + + Args: + call_trace (PipelineCallTraceStorage): Pipeline call results storage. + + Returns: + Dict[str, Any]: { + "iris_template": (Optional[Dict]) the iris template dict if the pipeline succeeded, + "error": (Optional[Dict]) the error dict if the pipeline returned an error, + "metadata": (Dict) the metadata dict, + }. + """ + output = build_simple_output(call_trace) + output["iris_template"] = __safe_serialize(output["iris_template"]) + + return output + + def build_debugging_output(call_trace: PipelineCallTraceStorage) -> Dict[str, Any]: """Build the output for debugging purposes. diff --git a/src/iris/pipelines/iris_pipeline.py b/src/iris/pipelines/iris_pipeline.py index 6ddd5d7..fa9a58a 100644 --- a/src/iris/pipelines/iris_pipeline.py +++ b/src/iris/pipelines/iris_pipeline.py @@ -18,7 +18,7 @@ from iris.io.errors import IRISPipelineError from iris.orchestration.environment import Environment from iris.orchestration.error_managers import store_error_manager -from iris.orchestration.output_builders import build_debugging_output, build_orb_output +from iris.orchestration.output_builders import build_debugging_output, build_orb_output, build_simple_output from iris.orchestration.pipeline_dataclasses import PipelineClass, PipelineMetadata, PipelineNode from iris.orchestration.validators import pipeline_config_duplicate_node_name_check @@ -41,6 +41,12 @@ class IRISPipeline(Algorithm): call_trace_initialiser=PipelineCallTraceStorage.initialise, ) + ORB_ENVIRONMENT = Environment( + pipeline_output_builder=build_orb_output, + error_manager=store_error_manager, + call_trace_initialiser=PipelineCallTraceStorage.initialise, + ) + class Parameters(Algorithm.Parameters): """IRISPipeline parameters, all derived from the input `config`.""" @@ -57,7 +63,7 @@ def __init__( self, config: Union[Dict[str, Any], Optional[str]] = None, env: Environment = Environment( - pipeline_output_builder=build_orb_output, + pipeline_output_builder=build_simple_output, error_manager=store_error_manager, call_trace_initialiser=PipelineCallTraceStorage.initialise, ), @@ -66,7 +72,7 @@ def __init__( Args: config (Union[Dict[str, Any], Optional[str]]): Input configuration, as a YAML-formatted string or dictionary specifying all nodes configuration. Defaults to None, which loads the default config. - env (Environment, optional): Environment properties. Defaults to Environment(output_builder=build_orb_output, error_manager=store_error_manager, call_trace_initialiser=PipelineCallTraceStorage). + env (Environment, optional): Environment properties. Defaults to Environment(output_builder=build_simple_output, error_manager=store_error_manager, call_trace_initialiser=PipelineCallTraceStorage). """ deserialized_config = self.load_config(config) if isinstance(config, str) or config is None else config super().__init__(**deserialized_config) diff --git a/tests/e2e_tests/pipelines/test_e2e_iris_pipeline.py b/tests/e2e_tests/pipelines/test_e2e_iris_pipeline.py index 6f6d716..16033ac 100644 --- a/tests/e2e_tests/pipelines/test_e2e_iris_pipeline.py +++ b/tests/e2e_tests/pipelines/test_e2e_iris_pipeline.py @@ -35,7 +35,7 @@ def expected_debug_pipeline_output() -> Dict[str, Any]: def test_e2e_iris_pipeline(ir_image: np.ndarray, expected_iris_pipeline_output: Dict[str, Any]) -> None: """End-to-end test of the IRISPipeline in the Orb setup""" - iris_pipeline = IRISPipeline() + iris_pipeline = IRISPipeline(env=IRISPipeline.ORB_ENVIRONMENT) computed_pipeline_output = iris_pipeline(img_data=ir_image, eye_side="right") compare_iris_pipeline_outputs(computed_pipeline_output, expected_iris_pipeline_output)