From 7e1348b6ba8f2e578336ef23bb44559a822fca2b Mon Sep 17 00:00:00 2001 From: duzelis Date: Thu, 14 Mar 2024 17:20:57 +0100 Subject: [PATCH 1/4] feat: add a remove_roi_margins() method --- pandora2d/__init__.py | 2 +- pandora2d/common.py | 10 ++++++- pandora2d/img_tools.py | 60 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/pandora2d/__init__.py b/pandora2d/__init__.py index 95c03a5..2519e6f 100644 --- a/pandora2d/__init__.py +++ b/pandora2d/__init__.py @@ -116,6 +116,6 @@ def main(cfg_path: str, path_output: str, verbose: bool) -> None: # save dataset if not empty if bool(dataset_disp_maps.data_vars): - common.save_dataset(dataset_disp_maps, path_output) + common.save_dataset(dataset_disp_maps, completed_cfg, path_output) # save config save_config(path_output, completed_cfg) diff --git a/pandora2d/common.py b/pandora2d/common.py index c08e131..3a0000c 100644 --- a/pandora2d/common.py +++ b/pandora2d/common.py @@ -35,13 +35,15 @@ from xarray import Coordinate as Coordinates import os +from typing import Dict import xarray as xr import numpy as np from pandora.common import mkdir_p, write_data_array +from pandora2d.img_tools import remove_roi_margins -def save_dataset(dataset: xr.Dataset, output: str) -> None: +def save_dataset(dataset: xr.Dataset, cfg: Dict, output: str) -> None: """ Save results in the output directory @@ -50,11 +52,17 @@ def save_dataset(dataset: xr.Dataset, output: str) -> None: - lines : the disparity map for the lines 2D DataArray (row, col) - columns : the disparity map for the columns 2D DataArray (row, col) :type dataset: xr.Dataset + :param cfg: user configuration + :type cfg: Dict :param output: output directory :type output: string :return: None """ + # remove ROI margins to save only user ROI in tif files + if "ROI" in cfg: + dataset = remove_roi_margins(dataset, cfg) + # create output dir mkdir_p(output) diff --git a/pandora2d/img_tools.py b/pandora2d/img_tools.py index 2b8c369..03b7e75 100644 --- a/pandora2d/img_tools.py +++ b/pandora2d/img_tools.py @@ -28,6 +28,7 @@ from collections.abc import Sequence from typing import List, Dict, NamedTuple, Any +from math import floor import xarray as xr import numpy as np from scipy.ndimage import shift @@ -261,3 +262,62 @@ def get_roi_processing(roi: dict, col_disparity: List[int], row_disparity: List[ new_roi["margins"][3] = max(abs(row_disparity[1]), roi["margins"][3]) return new_roi + + +def remove_roi_margins(dataset: xr.Dataset, cfg: Dict): + """ + Remove ROI margins before saving output dataset + + :param dataset: dataset containing disparity row and col maps + :type dataset: xr.Dataset + :param cfg: output configuration of the pandora2d machine + :type cfg: Dict + """ + + step = cfg["pipeline"]["matching_cost"]["step"] + + row = dataset.row.data + col = dataset.col.data + + # Initialized indexes to get right rows and columns + (left, up, right, down) = (0, 0, len(col), len(row)) + + # Example with col = [8,10,12,14,16], step_col=2, row = [0,4,8,12], step_row=4 + # ROI={ + # {"col": "first": 10, "last": 14}, + # {"row": "first": 0, "last": 10} } + # {"margins": (3,3,3,3)} + + # According to ROI, we want new_col=[10,12,14]=col[1:-1] + # with 1=floor((cfg["ROI"]["col"]["first"] - col[0]) / step_col)=left + # and -1=floor((cfg["ROI"]["col"]["last"] - col[-1]) / step_col)=right + + # According to ROI, we want new_row=[0,4,8]=row[0:-1] + # with 0=initialized up + # and -1=floor((cfg["ROI"]["row"]["last"] - row[-1]) / step[0])=down + + # Get the correct indexes to get the right columns based on the user ROI + if col[0] < cfg["ROI"]["col"]["first"]: + left = floor((cfg["ROI"]["col"]["first"] - col[0]) / step[1]) + if col[-1] > cfg["ROI"]["col"]["last"]: + right = floor((cfg["ROI"]["col"]["last"] - col[-1]) / step[1]) + + # Get the correct indexes to get the right rows based on the user ROI + if row[0] < cfg["ROI"]["row"]["first"]: + up = floor((cfg["ROI"]["row"]["first"] - row[0]) / step[0]) + if row[-1] > cfg["ROI"]["row"]["last"]: + down = floor((cfg["ROI"]["row"]["last"] - row[-1]) / step[0]) + + # Create a new dataset with right rows and columns. + data_variables = { + "row_map": (("row", "col"), dataset["row_map"].data[up:down, left:right]), + "col_map": (("row", "col"), dataset["col_map"].data[up:down, left:right]), + } + + coords = {"row": row[up:down], "col": col[left:right]} + + new_dataset = xr.Dataset(data_variables, coords) + + new_dataset.attrs = dataset.attrs + + return new_dataset From 94b31fcb197fefeaf4dbe1d9b69bd95fb2a68a67 Mon Sep 17 00:00:00 2001 From: duzelis Date: Thu, 14 Mar 2024 17:26:19 +0100 Subject: [PATCH 2/4] test: add test for remove_roi_margins() and modify save_dataset test --- tests/unit_tests/test_common.py | 4 +- .../test_img_tools/test_remove_roi_margins.py | 219 ++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/test_img_tools/test_remove_roi_margins.py diff --git a/tests/unit_tests/test_common.py b/tests/unit_tests/test_common.py index de42100..79bd3d6 100644 --- a/tests/unit_tests/test_common.py +++ b/tests/unit_tests/test_common.py @@ -62,12 +62,12 @@ def create_test_dataset(self): return dataset - def test_save_dataset(self, create_test_dataset): + def test_save_dataset(self, create_test_dataset, correct_input_cfg): """ Function for testing the dataset_save function """ - common.save_dataset(create_test_dataset, "./tests/res_test/") + common.save_dataset(create_test_dataset, correct_input_cfg, "./tests/res_test/") assert os.path.exists("./tests/res_test/") assert os.path.exists("./tests/res_test/columns_disparity.tif") diff --git a/tests/unit_tests/test_img_tools/test_remove_roi_margins.py b/tests/unit_tests/test_img_tools/test_remove_roi_margins.py new file mode 100644 index 0000000..fde6f9b --- /dev/null +++ b/tests/unit_tests/test_img_tools/test_remove_roi_margins.py @@ -0,0 +1,219 @@ +# Copyright (c) 2024 Centre National d'Etudes Spatiales (CNES). +# +# This file is part of PANDORA2D +# +# https://github.com/CNES/Pandora2D +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Test remove_roi_margins. +""" + +import pytest +import numpy as np +import xarray as xr + +from pandora2d.img_tools import remove_roi_margins + + +def create_dataset(row, col): + """ + Create dataset to test remove_roi_margins method + """ + + data_variables = { + "row_map": (("row", "col"), np.full((len(row), len(col)), 1)), + "col_map": (("row", "col"), np.full((len(row), len(col)), 1)), + } + + coords = {"row": row, "col": col} + + dataset = xr.Dataset(data_variables, coords) + + return dataset + + +def make_cfg(roi, step): + """ + Create user configuration to test remove_roi_margins method + """ + return { + "ROI": roi, + "pipeline": { + "matching_cost": {"matching_cost_method": "zncc", "window_size": 7, "step": step}, + }, + } + + +class TestRemoveRoiMargins: + """ + Test remove_roi_margins function + """ + + # pylint:disable=too-few-public-methods + + @pytest.mark.parametrize( + ["roi", "step", "row", "col", "row_gt", "col_gt"], + [ + pytest.param( + { + "col": {"first": 10, "last": 100}, + "row": {"first": 10, "last": 100}, + "margins": (3, 3, 3, 3), + }, + [1, 1], + np.arange(7, 104), + np.arange(7, 104), + np.arange(10, 101), + np.arange(10, 101), + id="Centered ROI, step_row=1 and step_col=1 ", + ), + pytest.param( + { + "col": {"first": 10, "last": 100}, + "row": {"first": 0, "last": 100}, + "margins": (2, 2, 2, 2), + }, + [1, 1], + np.arange(0, 103), + np.arange(8, 103), + np.arange(0, 101), + np.arange(10, 101), + id="ROI overlap at the top, step_row=1 and step_col=1 ", + ), + pytest.param( + { + "col": {"first": 10, "last": 100}, + "row": {"first": 100, "last": 201}, + "margins": (3, 3, 3, 3), + }, + [1, 1], + np.arange(97, 200), + np.arange(7, 104), + np.arange(100, 200), + np.arange(10, 101), + id="ROI overlap at the bottom, step_row=1 and step_col=1 ", + ), + pytest.param( + { + "col": {"first": 0, "last": 100}, + "row": {"first": 10, "last": 100}, + "margins": (3, 3, 3, 3), + }, + [1, 1], + np.arange(7, 104), + np.arange(0, 104), + np.arange(10, 101), + np.arange(0, 101), + id="ROI overlap on the left, step_row=1 and step_col=1 ", + ), + pytest.param( + { + "col": {"first": 100, "last": 202}, + "row": {"first": 10, "last": 100}, + "margins": (3, 3, 3, 3), + }, + [1, 1], + np.arange(7, 104), + np.arange(97, 200), + np.arange(10, 101), + np.arange(100, 200), + id="ROI overlap on the right, step_row=1 and step_col=1 ", + ), + pytest.param( + { + "col": {"first": 110, "last": 200}, + "row": {"first": 110, "last": 200}, + "margins": (3, 3, 3, 3), + }, + [2, 3], + np.arange(108, 204, 2), # step=2 --> we start at 108 even if margin=3 + np.arange(107, 204, 3), + np.arange(110, 201, 2), + np.arange(110, 201, 3), + id="Centered ROI, step_row=2 and step_col=3 ", + ), + pytest.param( + { + "col": {"first": 0, "last": 200}, + "row": {"first": 110, "last": 200}, + "margins": (3, 3, 3, 3), + }, + [2, 3], + np.arange(108, 204, 2), # step=2 --> we start at 108 even if margin=3 + np.arange(0, 204, 3), + np.arange(110, 201, 2), + np.arange(0, 201, 3), + id="ROI overlap on the left, step_row=2 and step_col=3 ", + ), + pytest.param( + { + "col": {"first": 100, "last": 203}, + "row": {"first": 10, "last": 100}, + "margins": (3, 3, 3, 3), + }, + [3, 2], + np.arange(7, 104, 3), + np.arange(98, 200, 2), # step=2 --> we start at 98 even if margin=3 + np.arange(10, 101, 3), + np.arange(100, 200, 2), + id="ROI overlap on the right, step_row=3 and step_col=2 ", + ), + pytest.param( + { + "col": {"first": 10, "last": 100}, + "row": {"first": 0, "last": 100}, + "margins": (3, 3, 3, 3), + }, + [4, 2], + np.arange(0, 104, 4), + np.arange(8, 104, 2), # step=2 --> we start at 8 even if margin=3 + np.arange(0, 101, 4), + np.arange(10, 101, 2), + id="ROI overlap at the top, step_row=4 and step_col=2", + ), + pytest.param( + { + "col": {"first": 10, "last": 100}, + "row": {"first": 100, "last": 203}, + "margins": (3, 3, 3, 3), + }, + [5, 3], + np.arange(100, 200, 5), # step=5 --> we start at 100 even if margin=3 + np.arange(7, 104, 3), + np.arange(100, 200, 5), + np.arange(10, 101, 3), + id="ROI overlap at the bottom, step_row=5 and step_col=3", + ), + ], + ) + def test_remove_roi_margins(self, roi, step, row, col, row_gt, col_gt): + """ + Test remove_roi_margins method + """ + + # Create user configuration with given roi and step + cfg = make_cfg(roi, step) + + # Create dataset input for remove_roi_margins + # row & col are supposed to be the correct coordinates according to the roi and the step + dataset = create_dataset(row, col) + + # Create ground truth dataset + dataset_gt = create_dataset(row_gt, col_gt) + + dataset_test = remove_roi_margins(dataset, cfg) + + xr.testing.assert_identical(dataset_test, dataset_gt) From 8cd13dccd3e5170a490646dec3f4f3f5dc5eabae Mon Sep 17 00:00:00 2001 From: duzelis Date: Thu, 14 Mar 2024 17:27:34 +0100 Subject: [PATCH 3/4] doc: add a note about roi margins in disparity maps visualization --- docs/source/userguide/as_an_api.rst | 4 ++-- docs/source/userguide/faq.rst | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/userguide/as_an_api.rst b/docs/source/userguide/as_an_api.rst index b198a7d..062de75 100644 --- a/docs/source/userguide/as_an_api.rst +++ b/docs/source/userguide/as_an_api.rst @@ -72,7 +72,7 @@ Pandora2D provides a full python API which can be used to compute disparity maps ) # trigger all the steps of the machine at ones - dataset = pandora2d.run( + dataset, completed_cfg = pandora2d.run( pandora2d_machine, image_datasets.left, image_datasets.right, @@ -80,7 +80,7 @@ Pandora2D provides a full python API which can be used to compute disparity maps ) # save dataset - common.save_dataset(dataset, path_output) + common.save_dataset(dataset, completed_cfg, path_output) Pandora2D's data diff --git a/docs/source/userguide/faq.rst b/docs/source/userguide/faq.rst index 1055aa0..4f9d6f7 100644 --- a/docs/source/userguide/faq.rst +++ b/docs/source/userguide/faq.rst @@ -85,3 +85,7 @@ It is possible to work on only one section of the image with an ROI. For this, t img_left = create_dataset_from_inputs(input_config=input_config["left"], roi=roi) img_right = create_dataset_from_inputs(input_config=input_config["right"], roi=roi) +.. note:: + When the usage_step_roi_config.ipynb notebook is run, disparity maps are displayed. + Margins can be present on these disparity maps, which is why they may be larger than the ROI given by the user. + To remove these margins and display only the user ROI, you can use the `pandora2d.img_tools.remove_roi_margins()` method. From 0b6eb6a2042d45d6b5977df4a196ace11f548d94 Mon Sep 17 00:00:00 2001 From: duzelis Date: Fri, 15 Mar 2024 09:40:09 +0100 Subject: [PATCH 4/4] notebook:add a comment about roi margins in disparity maps visualization --- notebooks/usage_step_roi_config.ipynb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/notebooks/usage_step_roi_config.ipynb b/notebooks/usage_step_roi_config.ipynb index 678754c..bb645dc 100644 --- a/notebooks/usage_step_roi_config.ipynb +++ b/notebooks/usage_step_roi_config.ipynb @@ -764,6 +764,14 @@ "#### Visualize output disparity maps" ] }, + { + "cell_type": "markdown", + "id": "69e62604", + "metadata": {}, + "source": [ + "Processing margins are included in the disparity map view below. These can be removed by calling the method pandora2d.img_tools.remove_roi_margins()" + ] + }, { "cell_type": "markdown", "id": "4ea5cba4-b57b-415f-97fb-21387913d32d",