Skip to content

Commit

Permalink
mobj0: add basic intrinsic metric
Browse files Browse the repository at this point in the history
  • Loading branch information
yoann-apel authored and leavauchier committed Jan 25, 2024
1 parent abd232e commit 4c1080f
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 1 deletion.
3 changes: 2 additions & 1 deletion coclico/metrics/listing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from coclico.malt0.malt0 import MALT0
from coclico.mobj0.mobj0 import MOBJ0
from coclico.mpap0.mpap0 import MPAP0
from coclico.mpla0.mpla0 import MPLA0

METRICS = {"mpap0": MPAP0, "mpla0": MPLA0, "malt0": MALT0}
METRICS = {"mpap0": MPAP0, "mpla0": MPLA0, "malt0": MALT0, "mobj0": MOBJ0}
Empty file added coclico/mobj0/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions coclico/mobj0/mobj0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pathlib import Path
from typing import Dict, List

import pandas as pd
from gpao.job import Job

from coclico.metrics.metric import Metric


class MOBJ0(Metric):
"""Metric MOBJ0 (for "Métrique par objet 0")
TODO Description
See doc/mobj0.md
"""

# Pixel size for occupancy map
pixel_size = 0.5
metric_name = "mobj0"

def create_metric_intrinsic_one_job(self, name: str, input: Path, output: Path, is_ref: bool):
raise NotImplementedError

def create_metric_relative_to_ref_jobs(
self, name: str, out_c1: Path, out_ref: Path, output: Path, c1_jobs: List[Job], ref_jobs: List[Job]
) -> Job:
raise NotImplementedError

@staticmethod
def compute_note(metric_df: pd.DataFrame, note_config: Dict):
raise NotImplementedError
89 changes: 89 additions & 0 deletions coclico/mobj0/mobj0_intrinsic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import argparse
import logging
from pathlib import Path

import geopandas as gpd
import numpy as np
import pandas as pd
import rasterio
from osgeo import gdal
from rasterio.features import shapes as rasterio_shapes
from shapely.geometry import shape as shapely_shape

import coclico.io
from coclico.metrics.occupancy_map import create_occupancy_map_array, read_las
from coclico.mobj0.mobj0 import MOBJ0

gdal.UseExceptions()


def create_objects_array(las_file, pixel_size, class_weights):
xs, ys, classifs, crs = read_las(las_file)

binary_maps, x_min, y_max = create_occupancy_map_array(xs, ys, classifs, pixel_size, class_weights)

return binary_maps, crs, x_min, y_max


def vectorize_occupancy_map(binary_maps, crs, x_min, y_max, pixel_size):
# Create empty dataframe
gdf_list = []

for ii, map_layer in enumerate(binary_maps):
shapes_layer = rasterio_shapes(
map_layer,
connectivity=8,
transform=rasterio.transform.from_origin(
x_min - pixel_size / 2, y_max + pixel_size / 2, pixel_size, pixel_size
),
)

geometries = [shapely_shape(shapedict) for shapedict, value in shapes_layer if value != 0]
nb_geometries = len(geometries)
gdf_list.append(
gpd.GeoDataFrame(
{"layer": ii * np.ones(nb_geometries), "geometry": geometries},
geometry="geometry",
crs=crs,
)
)

gdf = pd.concat(gdf_list)

return gdf


def compute_metric_intrinsic(las_file: Path, config_file: Path, output_geojson: Path, pixel_size: float = 0.5):
config_dict = coclico.io.read_config_file(config_file)
class_weights = config_dict[MOBJ0.metric_name]["weights"]
output_geojson.parent.mkdir(parents=True, exist_ok=True)
obj_array, crs, x_min, y_max = create_objects_array(las_file, pixel_size, class_weights)
polygons_gdf = vectorize_occupancy_map(obj_array, crs, x_min, y_max, pixel_size)
polygons_gdf.to_file(output_geojson)


def parse_args():
parser = argparse.ArgumentParser("Run malt0 intrinsic metric on one tile")
parser.add_argument("-i", "--input-file", type=Path, required=True, help="Path to the LAS file")
parser.add_argument("-o", "--output-geojson", type=Path, required=True, help="Path to the output geojson")
parser.add_argument(
"-c",
"--config-file",
type=Path,
required=True,
help="Coclico configuration file",
)
parser.add_argument(
"-p", "--pixel-size", type=float, required=True, help="Pixel size of the intermediate occupancy map"
)
return parser.parse_args()


if __name__ == "__main__":
args = parse_args()
logging.basicConfig(format="%(message)s", level=logging.DEBUG)
compute_metric_intrinsic(
las_file=Path(args.input_file),
config_file=args.config_file,
output_geojson=Path(args.output_geojson),
)
74 changes: 74 additions & 0 deletions coclico/mobj0/mobj0_relative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import argparse
import logging
from pathlib import Path

import pandas as pd

from coclico.config import csv_separator
from coclico.io import read_config_file
from coclico.mobj0.mobj0 import MOBJ0


def compute_metric_relative(
c1_dir: Path, ref_dir: Path, occupancy_dir: Path, config_file: str, output_csv: Path, output_csv_tile: Path
):
"""Compute metrics that describe the difference between c1 and ref height maps.
The occupancy map is used to mask the pixels for which the difference is computed
The metrics are:
- mean_diff: the average difference in z between the height maps
- max_diff: the maximum difference in z between the height maps
- std_diff: the standard deviation of the difference in z betweeen the height maps
These metrics are stored tile by tile and class by class in the output_csv_tile file
These metrics are stored class by class for the whole data in the output_csv file
Args:
c1_dir (Path): path to the c1 classification directory,
where there are json files with the result of mpap0 intrinsic metric
ref_dir (Path): path to the reference classification directory,
where there are json files with the result of mpap0 intrinsic metric
class_weights (Dict): class weights dict
output_csv (Path): path to output csv file
output_csv_tile (Path): path to output csv file, result by tile
"""
config_dict = read_config_file(config_file)
class_weights = config_dict[MOBJ0.metric_name]["weights"]
classes = sorted(class_weights.keys())
classes
csv_data = []

df = pd.DataFrame(csv_data)
df.to_csv(output_csv, index=False, sep=csv_separator)

logging.debug(df.to_markdown())
raise NotImplementedError


def parse_args():
parser = argparse.ArgumentParser("Run malt0 metric on one tile")
parser.add_argument(
"-i",
"--input-dir",
required=True,
type=Path,
help="Path to the classification directory, \
where there are tif files with the result of malt0 intrinsic metric (MNx for each class)",
)
raise NotImplementedError
return parser.parse_args()


if __name__ == "__main__":
args = parse_args()
logging.basicConfig(format="%(message)s", level=logging.DEBUG)
compute_metric_relative(
c1_dir=Path(args.input_dir),
ref_dir=Path(args.ref_dir),
occupancy_dir=Path(args.occupancy_dir),
config_file=args.config_file,
output_csv=Path(args.output_csv),
output_csv_tile=Path(args.output_csv_tile),
)
raise NotImplementedError
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies:
- laspy
- rasterio
- pandas
- geopandas
- tabulate
- requests
- pytest
Expand Down
6 changes: 6 additions & 0 deletions test/configs/config_test_metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,9 @@ malt0:
max_point:
metric: 0.5
note: 0

mobj0:
weights:
"6": 2
"1": 1
notes: {}
28 changes: 28 additions & 0 deletions test/mobj0/test_mobj0.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import shutil
from pathlib import Path

import pytest

import coclico.io as io
from coclico.mobj0.mobj0 import MOBJ0

pytestmark = pytest.mark.docker

TMP_PATH = Path("./tmp/mobj0")
CONFIG_FILE_METRICS = Path("./test/configs/config_test_metrics.yaml")


def setup_module(module):
if TMP_PATH.is_dir():
shutil.rmtree(TMP_PATH)


def generate_metric_dataframes():
raise NotImplementedError


def test_compute_note():
input_df, expected_out = generate_metric_dataframes()
notes_config = io.read_config_file(CONFIG_FILE_METRICS)["mobj0"]["notes"]
out_df = MOBJ0.compute_note(input_df, notes_config)
assert out_df.equals(expected_out)
54 changes: 54 additions & 0 deletions test/mobj0/test_mobj0_intrinsic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import logging
import shutil
import subprocess as sp
from pathlib import Path

import geopandas as gpd
import pytest

import coclico.io
from coclico.mobj0 import mobj0_intrinsic

pytestmark = pytest.mark.docker

TMP_PATH = Path("./tmp/mobj0_intrinsic")
CONFIG_FILE_METRICS = Path("./test/configs/config_test_metrics.yaml")


def setup_module(module):
if TMP_PATH.is_dir():
shutil.rmtree(TMP_PATH)


def test_compute_metric_intrinsic(ensure_test1_data):
las_file = Path("./data/test1/niv1/tile_splitted_2818_32247.laz")
pixel_size = 0.5
output_geojson = TMP_PATH / "intrinsic" / "unit_test_mpla0_intrinsic.geojson"
config = coclico.io.read_config_file(CONFIG_FILE_METRICS)
nb_layers = len(config["mobj0"]["weights"])

mobj0_intrinsic.compute_metric_intrinsic(las_file, CONFIG_FILE_METRICS, output_geojson, pixel_size=pixel_size)

assert output_geojson.exists()
gdf = gpd.read_file(output_geojson)
logging.debug(gdf.to_markdown())
assert len(gdf.index) > 0
assert len(set(gdf["layer"])) == nb_layers


def test_run_main(ensure_test1_data):
pixel_size = 0.5
input_file = Path("./data/test1/niv1/tile_splitted_2818_32247.laz")
output_geojson = TMP_PATH / "intrinsic" / "tile_splitted_2818_32247.geojson"

cmd = f"""python -m coclico.mobj0.mobj0_intrinsic \
--input-file {input_file} \
--output-geojson {output_geojson} \
--config-file {CONFIG_FILE_METRICS} \
--pixel-size {pixel_size}
"""
sp.run(cmd, shell=True, check=True)

logging.info(cmd)

assert output_geojson.exists()
22 changes: 22 additions & 0 deletions test/mobj0/test_mobj0_relative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import shutil
from pathlib import Path

import pytest

pytestmark = pytest.mark.docker

TMP_PATH = Path("./tmp/mobj0_relative")
CONFIG_FILE_METRICS = Path("./test/configs/config_test_metrics.yaml")


def setup_module(module):
if TMP_PATH.is_dir():
shutil.rmtree(TMP_PATH)


def test_compute_metric_relative(ensure_mobj0_data):
raise NotImplementedError


def test_run_main(ensure_mobj0_data):
raise NotImplementedError

0 comments on commit 4c1080f

Please sign in to comment.