From 2dac87d91d1281ef8adc6f51bf8a26f1deeaa27b Mon Sep 17 00:00:00 2001 From: Lea Vauchier Date: Wed, 6 Mar 2024 11:40:05 +0100 Subject: [PATCH] Fix type error in DropPointsByClass --- .pre-commit-config.yaml | 10 +++--- CHANGELOG.md | 3 ++ myria3d/pctl/transforms/transforms.py | 7 +++-- package_metadata.yaml | 2 +- tests/myria3d/models/test_model.py | 28 +++++++++++++++++ .../transforms}/test_transforms.py | 31 +++++++++++++++---- 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 tests/myria3d/models/test_model.py rename tests/myria3d/{data => pctl/transforms}/test_transforms.py (55%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dad27b33..3e0b133a 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.5.0 hooks: # list of supported hooks: https://pre-commit.com/hooks.html - id: trailing-whitespace @@ -12,26 +12,26 @@ repos: # python code formatting - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 24.2.0 hooks: - id: black args: [--line-length, "99"] # python import sorting - repo: https://github.com/PyCQA/isort - rev: 5.8.0 + rev: 5.13.2 hooks: - id: isort # yaml formatting - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.3.0 + rev: v4.0.0-alpha.8 hooks: - id: prettier types: [yaml] # python code analysis - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 + rev: 7.0.0 hooks: - id: flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0506bfad..f278fb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # CHANGELOG +### 3.8.2 +- fix: type error in edge case when dropping points in transforms + ### 3.8.1 - fix: propagate input las format to output las (in particular epsg which comes either from input or config) diff --git a/myria3d/pctl/transforms/transforms.py b/myria3d/pctl/transforms/transforms.py index 22034d60..ddc362d2 100755 --- a/myria3d/pctl/transforms/transforms.py +++ b/myria3d/pctl/transforms/transforms.py @@ -36,6 +36,9 @@ def subsample_data(data, num_nodes, choice): continue elif torch.is_tensor(item) and item.size(0) == num_nodes and item.size(0) != 1: data[key] = item[choice] + elif isinstance(item, np.ndarray) and item.shape[0] == num_nodes and item.shape[0] != 1: + data[key] = item[choice] + return data @@ -234,7 +237,5 @@ def __call__(self, data): if points_to_drop.sum() > 0: points_to_keep = torch.logical_not(points_to_drop) data = subsample_data(data, num_nodes=data.num_nodes, choice=points_to_keep) - # Here we also subsample these idx since we do not need to interpolate these points back - if "idx_in_original_cloud" in data: - data.idx_in_original_cloud = data.idx_in_original_cloud[points_to_keep] + return data diff --git a/package_metadata.yaml b/package_metadata.yaml index 3e6890e2..5cf40438 100644 --- a/package_metadata.yaml +++ b/package_metadata.yaml @@ -1,4 +1,4 @@ -__version__: "3.8.1" +__version__: "3.8.2" __name__: "myria3d" __url__: "https://github.com/IGNF/myria3d" __description__: "Deep Learning for the Semantic Segmentation of Aerial Lidar Point Clouds" diff --git a/tests/myria3d/models/test_model.py b/tests/myria3d/models/test_model.py new file mode 100644 index 00000000..42f94c10 --- /dev/null +++ b/tests/myria3d/models/test_model.py @@ -0,0 +1,28 @@ +import hydra +from pytorch_lightning import LightningDataModule +from tests.conftest import make_default_hydra_cfg + +from myria3d.models.model import Model + + +def test_model_get_batch_tensor_by_enumeration(): + config = make_default_hydra_cfg( + overrides=[ + "predict.src_las=tests/data/toy_dataset_src/862000_6652000.classified_toy_dataset.100mx100m.las", + "datamodule.epsg=2154", + "work_dir=./../../..", + "datamodule.subtile_width=1", + "datamodule.hdf5_file_path=null", + ] + ) + + datamodule: LightningDataModule = hydra.utils.instantiate(config.datamodule) + datamodule._set_predict_data(config.predict.src_las) + + model = Model( + neural_net_class_name="PyGRandLANet", + neural_net_hparams=dict(num_features=2, num_classes=7), + ) + for batch in datamodule.predict_dataloader(): + # Check that no error is raised ("TypeError: object of type 'numpy.int64' has no len()") + _ = model._get_batch_tensor_by_enumeration(batch.idx_in_original_cloud) diff --git a/tests/myria3d/data/test_transforms.py b/tests/myria3d/pctl/transforms/test_transforms.py similarity index 55% rename from tests/myria3d/data/test_transforms.py rename to tests/myria3d/pctl/transforms/test_transforms.py index 988444a5..bb0b09ec 100644 --- a/tests/myria3d/data/test_transforms.py +++ b/tests/myria3d/pctl/transforms/test_transforms.py @@ -1,9 +1,9 @@ import numpy as np import pytest -import torch_geometric import torch +import torch_geometric -from myria3d.pctl.transforms.transforms import TargetTransform, DropPointsByClass +from myria3d.pctl.transforms.transforms import DropPointsByClass, TargetTransform def test_TargetTransform_with_valid_config(): @@ -12,10 +12,12 @@ def test_TargetTransform_with_valid_config(): # 1 becomes 0, and 6 becomes 1. classification_dict = {1: "unclassified", 6: "building"} tt = TargetTransform(classification_preprocessing_dict, classification_dict) - y = np.array([1, 1, 2, 2, 6, 6]) - data = torch_geometric.data.Data(x=None, y=y) - assert np.array_equal(tt(data).y, np.array([0, 0, 0, 0, 1, 1])) + idx = np.arange(6) + data = torch_geometric.data.Data(x=None, y=y, idx_in_original_cloud=idx) + out_data = tt(data) + assert np.array_equal(out_data.y, np.array([0, 0, 0, 0, 1, 1])) + assert np.array_equal(out_data.idx_in_original_cloud, idx) def test_TargetTransform_throws_type_error_if_invalid_classification_dict(): @@ -34,11 +36,16 @@ def test_DropPointsByClass(): # points with class 65 are droped. y = torch.Tensor([1, 65, 65, 2, 65]) x = torch.rand((5, 3)) - data = torch_geometric.data.Data(x=x, y=y) + idx = np.arange(5) # Not a tensor + data = torch_geometric.data.Data(x=x, y=y, idx_in_original_cloud=idx) drop_transforms = DropPointsByClass() transformed_data = drop_transforms(data) assert torch.equal(transformed_data.y, torch.Tensor([1, 2])) assert transformed_data.x.size(0) == 2 + print(type(transformed_data.idx_in_original_cloud)) + assert isinstance(transformed_data.idx_in_original_cloud, np.ndarray) + assert transformed_data.idx_in_original_cloud.size == 2 + assert np.all(transformed_data.idx_in_original_cloud == np.array([0, 3])) # No modification x = torch.rand((3, 3)) @@ -47,3 +54,15 @@ def test_DropPointsByClass(): transformed_data = drop_transforms(data) assert torch.equal(data.x, transformed_data.x) assert torch.equal(data.y, transformed_data.y) + + # Keep one point only + y = torch.Tensor([1, 65, 65, 65, 65]) + x = torch.rand((5, 3)) + idx = np.arange(5) # Not a tensor + data = torch_geometric.data.Data(x=x, y=y, idx_in_original_cloud=idx) + transformed_data = drop_transforms(data) + assert torch.equal(transformed_data.y, torch.Tensor([1])) + assert transformed_data.x.size(0) == 1 + assert isinstance(transformed_data.idx_in_original_cloud, np.ndarray) + assert transformed_data.idx_in_original_cloud.shape[0] == 1 + assert np.all(transformed_data.idx_in_original_cloud == np.array([0]))