Skip to content

Commit

Permalink
Merge branch '966-classifier-carte-perf' into 'master'
Browse files Browse the repository at this point in the history
feat: add performance map classification

Closes #966

See merge request 3d/cars-park/cars!802
  • Loading branch information
dyoussef committed Jan 27, 2025
2 parents 75ea689 + 5d29251 commit 403bea3
Show file tree
Hide file tree
Showing 12 changed files with 222 additions and 41 deletions.
87 changes: 81 additions & 6 deletions cars/applications/rasterization/rasterization_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def simple_rasterization_dataset_wrapper(
msk_no_data: int = 255,
list_computed_layers: List[str] = None,
source_pc_names: List[str] = None,
performance_map_classes: List[float] = None,
) -> xr.Dataset:
"""
Wrapper of simple_rasterization
Expand Down Expand Up @@ -123,6 +124,8 @@ def simple_rasterization_dataset_wrapper(
:param list_computed_layers: list of computed output data
:param source_pc_names: list of names of point cloud before merging :
name of sensors pair or name of point cloud file
:param performance_map_classes: list for step defining border of class
:type performance_map_classes: list or None
:return: Rasterized cloud
"""

Expand Down Expand Up @@ -160,6 +163,7 @@ def simple_rasterization_dataset_wrapper(
msk_no_data=msk_no_data,
list_computed_layers=list_computed_layers,
source_pc_names=source_pc_names,
performance_map_classes=performance_map_classes,
)

return raster
Expand Down Expand Up @@ -477,6 +481,8 @@ def create_raster_dataset(
filling: np.ndarray = None,
band_filling: List[str] = None,
performance_map: np.ndarray = None,
performance_map_classified: np.ndarray = None,
performance_map_classified_index: list = None,
) -> xr.Dataset:
"""
Create final raster xarray dataset
Expand Down Expand Up @@ -505,7 +511,11 @@ def create_raster_dataset(
:param source_pc: binary raster with source point cloud information
:param source_pc_names: list of names of point cloud before merging :
name of sensors pair or name of point cloud file
:param performance_map: raster containing the performance map
:param performance_map: raster containing the raw performance map
:param performance_map_classified: raster containing the classified
performance map
:param performance_map_classified_index: indexes of
performance_map_classified
:return: the raster xarray dataset
"""
raster_dims = (cst.Y, cst.X)
Expand Down Expand Up @@ -666,9 +676,16 @@ def create_raster_dataset(

if performance_map is not None:
performance_map = np.nan_to_num(performance_map, nan=msk_no_data)
raster_out[cst.RASTER_PERFORMANCE_MAP] = xr.DataArray(
raster_out[cst.RASTER_PERFORMANCE_MAP_RAW] = xr.DataArray(
performance_map, dims=raster_dims
)
if performance_map_classified is not None:
raster_out[cst.RASTER_PERFORMANCE_MAP] = xr.DataArray(
performance_map_classified, dims=raster_dims
)
raster_out.attrs[cst.RIO_TAG_PERFORMANCE_MAP_CLASSES] = (
performance_map_classified_index
)

return raster_out

Expand All @@ -688,6 +705,7 @@ def rasterize(
msk_no_data: int = 255,
list_computed_layers: List[str] = None,
source_pc_names: List[str] = None,
performance_map_classes: List[float] = None,
) -> Union[xr.Dataset, None]:
"""
Rasterize a point cloud with its color bands to a Dataset
Expand All @@ -708,6 +726,9 @@ def rasterize(
:param color_no_data: no data value to use for color
:param msk_no_data: no data value to use in the final mask image
:param list_computed_layers: list of computed output data
:param source_pc_names: list of source pc names
:param performance_map_classes: list for step defining border of class
:type performance_map_classes: list or None
:return: Rasterized cloud color and statistics.
"""

Expand Down Expand Up @@ -742,7 +763,7 @@ def rasterize(
source_pc,
filling,
filling_indexes,
performance_map,
performance_map_raw,
) = compute_vector_raster_and_stats(
cloud,
x_start,
Expand Down Expand Up @@ -793,8 +814,16 @@ def rasterize(
filling = filling.reshape(shape_out + (-1,))
filling = np.moveaxis(filling, 2, 0)

if performance_map is not None:
performance_map = performance_map.reshape(shape_out)
performance_map_classified = None
performance_map_classified_indexes = None
if performance_map_raw is not None:
performance_map_raw = performance_map_raw.reshape(shape_out)
if performance_map_classes is not None:
(performance_map_classified, performance_map_classified_indexes) = (
classify_performance_map(
performance_map_raw, performance_map_classes, msk_no_data
)
)

# build output dataset
raster_out = create_raster_dataset(
Expand Down Expand Up @@ -824,12 +853,58 @@ def rasterize(
source_pc_names,
filling,
filling_indexes,
performance_map,
performance_map_raw,
performance_map_classified,
performance_map_classified_indexes,
)

return raster_out


def classify_performance_map(
performance_map_raw, performance_map_classes, msk_no_data
):
"""
Classify performance map with given classes
"""
if performance_map_classes[0] != 0:
performance_map_classes = [0] + performance_map_classes
if performance_map_classes[-1] != np.inf:
performance_map_classes.append(np.inf)

performance_map_classified_infos = {}

performance_map_classified = msk_no_data * np.ones(
performance_map_raw.shape, dtype=np.int8
)

index_start, index_end = 0, 1
value = 0
while index_end < len(performance_map_classes):
current_class = (
performance_map_classes[index_start],
performance_map_classes[index_end],
)

# update information
performance_map_classified_infos[value] = current_class

# create classified performance map
performance_map_classified[
np.logical_and(
performance_map_raw >= current_class[0],
performance_map_raw < current_class[1],
)
] = value

# next class
index_start += 1
index_end += 1
value += 1

return performance_map_classified, performance_map_classified_infos


def update_weights(old_weights, weights):
"""
Update weights
Expand Down
59 changes: 39 additions & 20 deletions cars/applications/rasterization/simple_gaussian.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def run( # noqa: C901 function is too complex
filling_file_name=None,
color_dtype=None,
dump_dir=None,
performance_map_classes=None,
phasing=None,
):
"""
Expand Down Expand Up @@ -272,6 +273,8 @@ def run( # noqa: C901 function is too complex
:type color_dtype: str (numpy type)
:param dump_dir: directory used for outputs with no associated filename
:type dump_dir: str
:param performance_map_classes: list for step defining border of class
:type performance_map_classes: list or None
:param phasing: if activated, we phase the dsm on this point
:type phasing: dict
Expand Down Expand Up @@ -388,24 +391,6 @@ def run( # noqa: C901 function is too complex
source_pc_names = point_clouds.attributes["source_pc_names"]

# Save objects
# Initialize files names
# TODO get from config ?
out_dsm_file_name = None
out_dsm_inf_file_name = None
out_dsm_sup_file_name = None
out_weights_file_name = None
out_clr_file_name = None
out_msk_file_name = None
out_confidence = None
out_performance_map = None
out_dsm_mean_file_name = None
out_dsm_std_file_name = None
out_dsm_n_pts_file_name = None
out_dsm_points_in_cell_file_name = None
out_dsm_inf_mean_file_name = None
out_dsm_inf_std_file_name = None
out_dsm_sup_mean_file_name = None
out_dsm_sup_std_file_name = None

if dsm_file_name is not None:
safe_makedirs(os.path.dirname(dsm_file_name))
Expand Down Expand Up @@ -523,6 +508,10 @@ def run( # noqa: C901 function is too complex
)

out_performance_map = performance_map_file_name

# save raw as performance map if performance_map_classes is None
# save classified performance map if performance_map_classes is not None
out_performance_map_raw = None
if out_performance_map is not None:
# add contributing pair filename to index
self.orchestrator.update_index(
Expand All @@ -534,18 +523,41 @@ def run( # noqa: C901 function is too complex
}
}
)
if performance_map_classes is None:
# No classes, we return raw data
out_performance_map_raw = out_performance_map
out_performance_map = None
elif save_intermediate_data:
# File is not part of the official product, write it in dump_dir
out_performance_map = os.path.join(
out_dump_dir, "performance_map.tif"
)
if out_performance_map:
out_performance_map_raw = os.path.join(
out_dump_dir, "performance_map_raw.tif"
)

if out_performance_map_raw is not None:
list_computed_layers += ["performance_map_raw"]
self.orchestrator.add_to_save_lists(
out_performance_map_raw,
cst.RASTER_PERFORMANCE_MAP_RAW,
terrain_raster,
dtype=np.float32,
nodata=self.msk_no_data,
cars_ds_name="performance_map_raw",
optional_data=True,
)
if (
out_performance_map is not None
and performance_map_classes is not None
):
# classified performance map exists
list_computed_layers += ["performance_map"]
self.orchestrator.add_to_save_lists(
out_performance_map,
cst.RASTER_PERFORMANCE_MAP,
terrain_raster,
dtype=np.float32,
dtype=np.uint8,
nodata=self.msk_no_data,
cars_ds_name="performance_map",
optional_data=True,
Expand Down Expand Up @@ -833,6 +845,7 @@ def run( # noqa: C901 function is too complex
color_dtype=color_dtype,
msk_no_data=self.msk_no_data,
source_pc_names=source_pc_names,
performance_map_classes=performance_map_classes,
)
else:

Expand Down Expand Up @@ -870,6 +883,7 @@ def run( # noqa: C901 function is too complex
color_dtype=color_dtype,
msk_no_data=self.msk_no_data,
source_pc_names=source_pc_names,
performance_map_classes=performance_map_classes,
)
ind_tile += 1

Expand All @@ -895,6 +909,7 @@ def rasterization_wrapper(
color_dtype: str = "float32",
msk_no_data: int = 255,
source_pc_names=None,
performance_map_classes=None,
):
"""
Wrapper for rasterization step :
Expand Down Expand Up @@ -928,6 +943,9 @@ def rasterization_wrapper(
:param msk_no_data: no data value to use in the final mask image
:param source_pc_names: list of names of point cloud before merging :
name of sensors pair or name of point cloud file
:param performance_map_classes: list for step defining border of class
:type performance_map_classes: list or None
:return: digital surface model + projected colors
:rtype: xr.Dataset
"""
Expand Down Expand Up @@ -1049,6 +1067,7 @@ def rasterization_wrapper(
msk_no_data=msk_no_data,
list_computed_layers=list_computed_layers,
source_pc_names=source_pc_names,
performance_map_classes=performance_map_classes,
)

# Fill raster
Expand Down
2 changes: 2 additions & 0 deletions cars/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
BAND_PERFORMANCE_MAP = "band_performance_map"
BAND_FILLING = "filling_type"
BAND_SOURCE_PC = "source_point_cloud"
RIO_TAG_PERFORMANCE_MAP_CLASSES = "rio_tag_performance_map_classes"
X = "x"
Y = "y"
Z = "z"
Expand Down Expand Up @@ -105,6 +106,7 @@
RASTER_BAND_STD_DEV = "band_stdev"
RASTER_CONFIDENCE = "confidence"
RASTER_PERFORMANCE_MAP = "performance_map"
RASTER_PERFORMANCE_MAP_RAW = "performance_map_raw"
RASTER_SOURCE_PC = "source_pc"
RASTER_FILLING = "filling"
# Geometry constants
Expand Down
10 changes: 10 additions & 0 deletions cars/core/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,18 @@ def rasterio_write_georaster(
window: rio.windows.Window = None,
descriptor=None,
bands_description=None,
classes_info_tag=None,
):
"""
Write a raster file from array
:param raster_file: Image file
:param data: image data
:param profile: rasterio profile
:param window: window to write in
:param descriptor: rasterio descriptor
:param bands_description: description of bands
:param classes_info_tag: tag to add to descriptor
"""

def write_data(data, window=None, descriptor=None):
Expand All @@ -141,6 +146,11 @@ def write_data(data, window=None, descriptor=None):
descriptor.write(data, window=window)

if descriptor is not None:
# write tag
if classes_info_tag is not None:
descriptor.update_tags(CLASSES=classes_info_tag)

# write data
if bands_description is not None:
for idx, description in enumerate(bands_description):
# Band indexing starts at 1
Expand Down
7 changes: 7 additions & 0 deletions cars/data_structures/cars_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1137,13 +1137,20 @@ def save_dataset(
if tag in (cst.EPI_FILLING, cst.RASTER_FILLING):
bands_description = dataset.coords[cst.BAND_FILLING].values

classes_info_tag = None
if tag == cst.RASTER_PERFORMANCE_MAP:
classes_info_tag = dataset.attrs.get(
cst.RIO_TAG_PERFORMANCE_MAP_CLASSES, None
)

outputs.rasterio_write_georaster(
file_name,
data,
new_profile,
window=rio_window,
descriptor=descriptor,
bands_description=bands_description,
classes_info_tag=classes_info_tag,
)


Expand Down
3 changes: 3 additions & 0 deletions cars/pipelines/default/default_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,9 @@ def rasterize_point_cloud(self):
filling_file_name=filling_file_name,
color_dtype=self.color_type,
dump_dir=rasterization_dump_dir,
performance_map_classes=self.used_conf[ADVANCED][
adv_cst.PERFORMANCE_MAP_CLASSES
],
phasing=self.phasing,
)

Expand Down
Loading

0 comments on commit 403bea3

Please sign in to comment.