Skip to content

Commit

Permalink
chore: Abandon of option to get circular patches since it was never u…
Browse files Browse the repository at this point in the history
…sed.
  • Loading branch information
CharlesGaydon committed Oct 4, 2023
1 parent 8103d80 commit 04d78f9
Show file tree
Hide file tree
Showing 11 changed files with 54 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
myria3d
python run.py
--config-path /inputs/
--config-name proto151_V2.0_epoch_100_Myria3DV3.1.0_predict_config_V3.4.0
--config-name proto151_V2.0_epoch_100_Myria3DV3.1.0_predict_config_V3.5.0
predict.ckpt_path=/inputs/proto151_V2.0_epoch_100_Myria3DV3.1.0.ckpt
predict.src_las=/inputs/792000_6272000_subset_buildings.las
predict.output_dir=/outputs/
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# main

### 3.5.0
- Abandon of option to get circular patches since it was never used.

### 3.4.9
- Support edge-case where source LAZ has no valid subtile (i.e. pre_filter=False for all candidate subtiles) during hdf5 creation

Expand Down
1 change: 0 additions & 1 deletion configs/datamodule/hdf5_datamodule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pre_filter:

tile_width: 1000
subtile_width: 50
subtile_shape: "square" # "square" or "disk"
subtile_overlap_train: 0
subtile_overlap_predict: "${predict.subtile_overlap}"

Expand Down
1 change: 0 additions & 1 deletion docs/source/apidoc/default_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ datamodule:
min_num_nodes: 50
tile_width: 1000
subtile_width: 50
subtile_shape: square
subtile_overlap_train: 0
subtile_overlap_predict: ${predict.subtile_overlap}
batch_size: 2
Expand Down
40 changes: 27 additions & 13 deletions myria3d/pctl/datamodule/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from myria3d.pctl.dataset.hdf5 import HDF5Dataset
from myria3d.pctl.dataset.iterable import InferenceDataset
from myria3d.pctl.dataset.utils import (
SHAPE_TYPE,
get_las_paths_by_split_dict,
pre_filter_below_n_points,
)
Expand All @@ -34,13 +33,13 @@ def __init__(
pre_filter: Optional[Callable[[Data], bool]] = pre_filter_below_n_points,
tile_width: Number = 1000,
subtile_width: Number = 50,
subtile_shape: SHAPE_TYPE = "square",
subtile_overlap_train: Number = 0,
subtile_overlap_predict: Number = 0,
batch_size: int = 12,
num_workers: int = 1,
prefetch_factor: int = 2,
transforms: Optional[Dict[str, TRANSFORMS_LIST]] = None,
**kwargs
):
self.split_csv_path = split_csv_path
self.data_dir = data_dir
Expand All @@ -53,7 +52,6 @@ def __init__(

self.tile_width = tile_width
self.subtile_width = subtile_width
self.subtile_shape = subtile_shape
self.subtile_overlap_train = subtile_overlap_train
self.subtile_overlap_predict = subtile_overlap_predict

Expand All @@ -62,32 +60,50 @@ def __init__(
self.prefetch_factor = prefetch_factor

t = transforms
self.preparation_train_transform: TRANSFORMS_LIST = t.get("preparations_train_list", [])
self.preparation_eval_transform: TRANSFORMS_LIST = t.get("preparations_eval_list", [])
self.preparation_predict_transform: TRANSFORMS_LIST = t.get("preparations_predict_list", [])
self.preparation_train_transform: TRANSFORMS_LIST = t.get(
"preparations_train_list", []
)
self.preparation_eval_transform: TRANSFORMS_LIST = t.get(
"preparations_eval_list", []
)
self.preparation_predict_transform: TRANSFORMS_LIST = t.get(
"preparations_predict_list", []
)
self.augmentation_transform: TRANSFORMS_LIST = t.get("augmentations_list", [])
self.normalization_transform: TRANSFORMS_LIST = t.get("normalizations_list", [])

@property
def train_transform(self) -> CustomCompose:
return CustomCompose(self.preparation_train_transform + self.normalization_transform + self.augmentation_transform)
return CustomCompose(
self.preparation_train_transform
+ self.normalization_transform
+ self.augmentation_transform
)

@property
def eval_transform(self) -> CustomCompose:
return CustomCompose(self.preparation_eval_transform + self.normalization_transform)
return CustomCompose(
self.preparation_eval_transform + self.normalization_transform
)

@property
def predict_transform(self) -> CustomCompose:
return CustomCompose(self.preparation_predict_transform + self.normalization_transform)
return CustomCompose(
self.preparation_predict_transform + self.normalization_transform
)

def prepare_data(self, stage: Optional[str] = None):
"""Prepare dataset containing train, val, test data."""

if stage in ["fit", "test"] or stage is None:
if self.split_csv_path and self.data_dir:
las_paths_by_split_dict = get_las_paths_by_split_dict(self.data_dir, self.split_csv_path)
las_paths_by_split_dict = get_las_paths_by_split_dict(
self.data_dir, self.split_csv_path
)
else:
log.warning("cfg.data_dir and cfg.split_csv_path are both null. Precomputed HDF5 dataset is used.")
log.warning(
"cfg.data_dir and cfg.split_csv_path are both null. Precomputed HDF5 dataset is used."
)
las_paths_by_split_dict = None
# Create the dataset in prepare_data, so that it is done one a single GPU.
self.las_paths_by_split_dict = las_paths_by_split_dict
Expand Down Expand Up @@ -124,7 +140,6 @@ def dataset(self) -> HDF5Dataset:
tile_width=self.tile_width,
subtile_width=self.subtile_width,
subtile_overlap_train=self.subtile_overlap_train,
subtile_shape=self.subtile_shape,
pre_filter=self.pre_filter,
train_transform=self.train_transform,
eval_transform=self.eval_transform,
Expand Down Expand Up @@ -164,7 +179,6 @@ def _set_predict_data(self, las_file_to_predict):
transform=self.predict_transform,
tile_width=self.tile_width,
subtile_width=self.subtile_width,
subtile_shape=self.subtile_shape,
subtile_overlap=self.subtile_overlap_predict,
)

Expand Down
7 changes: 0 additions & 7 deletions myria3d/pctl/dataset/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def __init__(
tile_width: Number = 1000,
subtile_width: Number = 50,
subtile_overlap_train: Number = 0,
subtile_shape: SHAPE_TYPE = "square",
pre_filter=pre_filter_below_n_points,
train_transform: List[Callable] = None,
eval_transform: List[Callable] = None,
Expand All @@ -48,7 +47,6 @@ def __init__(
points_pre_transform (Callable): Function to turn pdal points into a pyg Data object.
tile_width (Number, optional): width of a LAS tile. Defaults to 1000.
subtile_width (Number, optional): effective width of a subtile (i.e. receptive field). Defaults to 50.
subtile_shape (SHAPE_TYPE, optional): Shape of subtile could be either "square" or "disk". Defaults to "square".
subtile_overlap_train (Number, optional): Overlap for data augmentation of train set. Defaults to 0.
pre_filter (_type_, optional): Function to filter out specific subtiles. Defaults to None.
train_transform (List[Callable], optional): Transforms to apply to a sample for training. Defaults to None.
Expand All @@ -64,7 +62,6 @@ def __init__(
self.tile_width = tile_width
self.subtile_width = subtile_width
self.subtile_overlap_train = subtile_overlap_train
self.subtile_shape = subtile_shape

self.hdf5_file_path = hdf5_file_path

Expand All @@ -83,7 +80,6 @@ def __init__(
hdf5_file_path,
tile_width,
subtile_width,
subtile_shape,
pre_filter,
subtile_overlap_train,
points_pre_transform,
Expand Down Expand Up @@ -198,7 +194,6 @@ def create_hdf5(
hdf5_file_path: str,
tile_width: Number = 1000,
subtile_width: Number = 50,
subtile_shape: SHAPE_TYPE = "square",
pre_filter: Optional[Callable[[Data], bool]] = pre_filter_below_n_points,
subtile_overlap_train: Number = 0,
points_pre_transform: Callable = lidar_hd_pre_transform,
Expand All @@ -215,7 +210,6 @@ def create_hdf5(
hdf5_file_path (str): path to HDF5 dataset,
tile_width (Number, optional): width of a LAS tile. 1000 by default,
subtile_width: (Number, optional): effective width of a subtile (i.e. receptive field). 50 by default,
subtile_shape (SHAPE_TYPE, optional): Shape of subtile could be either "square" or "disk". "square" by default ,
pre_filter: Function to filter out specific subtiles. "pre_filter_below_n_points" by default,
subtile_overlap_train (Number, optional): Overlap for data augmentation of train set. 0 by default,
points_pre_transform (Callable): Function to turn pdal points into a pyg Data object.
Expand Down Expand Up @@ -246,7 +240,6 @@ def create_hdf5(
las_path,
tile_width,
subtile_width,
subtile_shape,
subtile_overlap,
)
):
Expand Down
4 changes: 0 additions & 4 deletions myria3d/pctl/dataset/iterable.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from torch_geometric.data import Data

from myria3d.pctl.dataset.utils import (
SHAPE_TYPE,
pre_filter_below_n_points,
split_cloud_into_samples,
)
Expand All @@ -26,7 +25,6 @@ def __init__(
tile_width: Number = 1000,
subtile_width: Number = 50,
subtile_overlap: Number = 0,
subtile_shape: SHAPE_TYPE = "square",
):
self.las_file = las_file

Expand All @@ -36,7 +34,6 @@ def __init__(

self.tile_width = tile_width
self.subtile_width = subtile_width
self.subtile_shape = subtile_shape
self.subtile_overlap = subtile_overlap

def __iter__(self):
Expand All @@ -48,7 +45,6 @@ def get_iterator(self):
self.las_file,
self.tile_width,
self.subtile_width,
self.subtile_shape,
self.subtile_overlap,
):
sample_data = self.points_pre_transform(sample_points)
Expand Down
30 changes: 16 additions & 14 deletions myria3d/pctl/dataset/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from shapely.geometry import Point

SPLIT_TYPE = Union[Literal["train"], Literal["val"], Literal["test"]]
SHAPE_TYPE = Union[Literal["disk"], Literal["square"]]
LAS_PATHS_BY_SPLIT_DICT_TYPE = Dict[SPLIT_TYPE, List[str]]

# commons
Expand All @@ -31,7 +30,9 @@ def find_file_in_dir(data_dir: str, basename: str) -> str:
return files[0]


def get_mosaic_of_centers(tile_width: Number, subtile_width: Number, subtile_overlap: Number = 0):
def get_mosaic_of_centers(
tile_width: Number, subtile_width: Number, subtile_overlap: Number = 0
):
if subtile_overlap < 0:
raise ValueError("datamodule.subtile_overlap must be positive.")

Expand Down Expand Up @@ -61,7 +62,9 @@ def pdal_read_las_array(las_path: str):
def pdal_read_las_array_as_float32(las_path: str):
"""Read LAS as a a named array, casted to floats."""
arr = pdal_read_las_array(las_path)
all_floats = np.dtype({"names": arr.dtype.names, "formats": ["f4"] * len(arr.dtype.names)})
all_floats = np.dtype(
{"names": arr.dtype.names, "formats": ["f4"] * len(arr.dtype.names)}
)
return arr.astype(all_floats)


Expand Down Expand Up @@ -105,34 +108,31 @@ def split_cloud_into_samples(
las_path: str,
tile_width: Number,
subtile_width: Number,
shape: SHAPE_TYPE,
subtile_overlap: Number = 0,
):
"""Split LAS point cloud into samples.
Args:
las_path (str): path to raw LAS file
tile_width (Number): width of input LAS file
subtile_width (Number): width of receptive field ; may be increased for coverage in case of disk shape.
shape: "disk" or "square"
subtile_width (Number): width of receptive field.
subtile_overlap (Number, optional): overlap between adjacent tiles. Defaults to 0.
Yields:
_type_: idx_in_original_cloud, and points of sample in pdal input format casted as floats.
"""
points = pdal_read_las_array_as_float32(las_path)
pos = np.asarray([points["X"], points["Y"], points["Z"]], dtype=np.float32).transpose()
pos = np.asarray(
[points["X"], points["Y"], points["Z"]], dtype=np.float32
).transpose()
kd_tree = cKDTree(pos[:, :2] - pos[:, :2].min(axis=0))
XYs = get_mosaic_of_centers(tile_width, subtile_width, subtile_overlap=subtile_overlap)
XYs = get_mosaic_of_centers(
tile_width, subtile_width, subtile_overlap=subtile_overlap
)
for center in XYs:
radius = subtile_width // 2 # Square receptive field.
minkowski_p = np.inf
if shape == "disk":
# Disk receptive field.
# Adapt radius to have complete coverage of the data, with a slight overlap between samples.
minkowski_p = 2
radius = radius * math.sqrt(2)
sample_idx = np.array(kd_tree.query_ball_point(center, r=radius, p=minkowski_p))
if not len(sample_idx):
# no points in this receptive fields
Expand Down Expand Up @@ -170,7 +170,9 @@ def get_las_paths_by_split_dict(
for phase in ["train", "val", "test"]:
basenames = split_df[split_df.split == phase].basename.tolist()
# Reminder: an explicit data structure with ./val, ./train, ./test subfolder is required.
las_paths_by_split_dict[phase] = [str(Path(data_dir) / phase / b) for b in basenames]
las_paths_by_split_dict[phase] = [
str(Path(data_dir) / phase / b) for b in basenames
]

if not las_paths_by_split_dict:
raise FileNotFoundError(
Expand Down
2 changes: 1 addition & 1 deletion package_metadata.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__: "3.4.9"
__version__: "3.5.0"
__name__: "myria3d"
__url__: "https://github.com/IGNF/myria3d"
__description__: "Deep Learning for the Semantic Segmentation of Aerial Lidar Point Clouds"
Expand Down
9 changes: 6 additions & 3 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

TASK_NAME_DETECTION_STRING = "task.task_name="
DEFAULT_DIRECTORY = "trained_model_assets/"
DEFAULT_CONFIG_FILE = "proto151_V2.0_epoch_100_Myria3DV3.1.0_predict_config_V3.4.0.yaml"
DEFAULT_CONFIG_FILE = "proto151_V2.0_epoch_100_Myria3DV3.1.0_predict_config_V3.5.0.yaml"
DEFAULT_CHECKPOINT = "proto151_V2.0_epoch_100_Myria3DV3.1.0.ckpt"
DEFAULT_ENV = "placeholder.env"

Expand Down Expand Up @@ -96,7 +96,6 @@ def launch_hdf5(config: DictConfig):
hdf5_file_path=config.datamodule.get("hdf5_file_path"),
tile_width=config.datamodule.get("tile_width"),
subtile_width=config.datamodule.get("subtile_width"),
subtile_shape=config.datamodule.get("subtile_shape"),
pre_filter=hydra.utils.instantiate(config.datamodule.get("pre_filter")),
subtile_overlap_train=config.datamodule.get("subtile_overlap_train"),
points_pre_transform=hydra.utils.instantiate(
Expand All @@ -114,7 +113,11 @@ def launch_hdf5(config: DictConfig):

log.info(f"Task: {task_name}")

if task_name in [TASK_NAMES.FIT.value, TASK_NAMES.TEST.value, TASK_NAMES.FINETUNE.value]:
if task_name in [
TASK_NAMES.FIT.value,
TASK_NAMES.TEST.value,
TASK_NAMES.FINETUNE.value,
]:
# load environment variables from `.env` file if it exists
# recursively searches for `.env` in all folders starting from work dir
dotenv.load_dotenv(override=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ datamodule:
min_num_nodes: 1
tile_width: 1000
subtile_width: 50
subtile_shape: square
subtile_overlap_train: 0
subtile_overlap_predict: ${predict.subtile_overlap}
batch_size: 10
Expand Down

0 comments on commit 04d78f9

Please sign in to comment.