From ee17b568a809ccccb7aec59b434bd9253fbef42f Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Fri, 16 Jan 2026 16:56:34 +0100 Subject: [PATCH 01/15] add: test prepa data --- xdem/workflows/accuracy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 58eab3a2..8d45869e 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -195,6 +195,7 @@ def _prepare_datas(self) -> None: # Intersection logging.info("Computing intersection") coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) + print(coord_intersection) if sampling_source == "reference_elev": self.reference_elev = self.reference_elev.crop(coord_intersection) self.generate_plot( From c34a956dbbc13fef071613254d15744cb1088355 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Wed, 21 Jan 2026 14:11:47 +0100 Subject: [PATCH 02/15] add: possible output file for --template-config --- doc/source/cli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/cli.md b/doc/source/cli.md index fe01ac75..41c359b1 100644 --- a/doc/source/cli.md +++ b/doc/source/cli.md @@ -37,6 +37,8 @@ Also, this template can directly be saved in a YAML file like this: xdem workflow_name --template-config template_config.yaml ``` +Optionally, a output path to save the template can be set directly in the command-line using `--output`. + When edited by a user, a configuration file **must contain at minima the input parameters listed as "required"** on the documentation page of the given workflow. xDEM then automatically fills in the rest with default settings. Users are free to edit the configuration file to run only the parts they need. From bea7b8d362b96afcc28da7664999ee129f5eb21f Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Thu, 22 Jan 2026 16:40:19 +0100 Subject: [PATCH 03/15] change: --template-config template_config.yaml @cli argv --- xdem/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xdem/cli.py b/xdem/cli.py index 32c6c78b..93538938 100644 --- a/xdem/cli.py +++ b/xdem/cli.py @@ -106,8 +106,7 @@ def main() -> None: epilog="examples:\n" " xdem accuracy --config config.yaml\n" " xdem accuracy --config config.yaml --output myoutputfolder\n" - " xdem accuracy --template-config\n" - " xdem accuracy --template-config template_config.yaml", + " xdem accuracy --template-config --output template_config.yaml", add_help=False, formatter_class=argparse.RawTextHelpFormatter, ) From f7bd448f37d020d309e09b794ee8a6802767daf6 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Mon, 26 Jan 2026 11:16:31 +0100 Subject: [PATCH 04/15] fix: doc presentation + com null/None --- doc/source/cli_accuracy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli_accuracy.md b/doc/source/cli_accuracy.md index 3e260489..95192653 100644 --- a/doc/source/cli_accuracy.md +++ b/doc/source/cli_accuracy.md @@ -122,7 +122,7 @@ Elevation input information, split between reference and to-be-aligned elevation ::::{tab-item} `reference_elev` :::{table} Inputs parameters for `reference_elev` -:widths: 20, 35, 17, 18, 10 +:widths: 20, 40, 20, 10, 10 | Name | Description | Type | Default | Required | |-----------------------|------------------------------------------|------------|---------|----------| From cf25770440f57677fb1e8cb4145f10c5ac01d04f Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Tue, 27 Jan 2026 10:29:44 +0100 Subject: [PATCH 05/15] fix: review Romain --- xdem/workflows/accuracy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 8d45869e..58eab3a2 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -195,7 +195,6 @@ def _prepare_datas(self) -> None: # Intersection logging.info("Computing intersection") coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) - print(coord_intersection) if sampling_source == "reference_elev": self.reference_elev = self.reference_elev.crop(coord_intersection) self.generate_plot( From cc27d8a171335eb1850c2075b9f92a317053341c Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Fri, 30 Jan 2026 11:25:30 +0100 Subject: [PATCH 06/15] fix: scale before/after + empty fig --- xdem/workflows/accuracy.py | 48 ++++++++++++++++++++++++-------------- xdem/workflows/topo.py | 1 + 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 58eab3a2..dabd9480 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -323,25 +323,9 @@ def run(self) -> None: vmin = vmax = None - if self.compute_coreg: - diff_pairs = [("before", self.to_be_aligned_elev), ("after", aligned_elev.reproject(ref_elev))] - else: - diff_pairs = [("", self.to_be_aligned_elev)] - - for label, dem in diff_pairs: - diff = dem - ref_elev - stats_keys = ["min", "max", "nmad", "median"] - stats = diff.get_stats(stats_keys) - - if label == "before": - self.diff_before, self.stats_before = diff, stats - vmin, vmax = -(stats["median"] + 3 * stats["nmad"]), stats["median"] + 3 * stats["nmad"] - elif label == "after": - self.diff_after, self.stats_after = diff, stats - else: - self.diff = diff - vmin, vmax = -(stats["median"] + 3 * stats["nmad"]), (stats["median"] + 3 * stats["nmad"]) + stats_keys = ["min", "max", "nmad", "median"] + def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) -> None: suffix = f"_elev_{label}_coreg_map" if label else "_elev" self.generate_plot( diff, @@ -353,6 +337,34 @@ def run(self) -> None: cbar_title="Elevation differences (m)", ) + if self.compute_coreg: + + self.diff_before = self.to_be_aligned_elev - ref_elev + self.stats_before = self.diff_before.get_stats(stats_keys) + + self.diff_after = aligned_elev.reproject(ref_elev) - ref_elev + self.stats_after = self.diff_after.get_stats(stats_keys) + + vmin_bf, vmax_bf = ( + -(self.stats_before["median"] + 3 * self.stats_before["nmad"]), + self.stats_before["median"] + 3 * self.stats_before["nmad"], + ) + vmin_af, vmax_af = ( + -(self.stats_after["median"] + 3 * self.stats_after["nmad"]), + self.stats_after["median"] + 3 * self.stats_after["nmad"], + ) + vmin = vmin_af if vmin_af < vmin_bf else vmin_bf + vmax = vmax_af if vmax_af > vmax_bf else vmax_bf + generate_plot_diff("before", self.diff_before, vmin, vmax) + generate_plot_diff("after", self.diff_after, vmin, vmax) + + else: + self.diff = [self.to_be_aligned_elev - ref_elev] + self.stats = self.diff.get_stats(stats_keys) + + vmin, vmax = -(self.stats["median"] + 3 * self.stats["nmad"]), self.stats["median"] + 3 * self.stats["nmad"] + generate_plot_diff("", self.diff, vmin, vmax) + if self.compute_coreg: stat_items = [ (self.reference_elev, "reference_elev", "Reference elevation", 2), diff --git a/xdem/workflows/topo.py b/xdem/workflows/topo.py index 2722641d..9e992d50 100644 --- a/xdem/workflows/topo.py +++ b/xdem/workflows/topo.py @@ -168,6 +168,7 @@ def generate_terrain_attributes_png(self) -> None: ax.set_xticks([]) ax.set_yticks([]) + [fig.delaxes(ax) for ax in axes.flatten() if not ax.has_data()] plt.tight_layout() plt.savefig(self.outputs_folder / "plots" / "terrain_attributes_map.png", dpi=300) plt.close() From ebb1690a1df7732a94d2dceba3801354f65ad4e2 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Wed, 4 Feb 2026 18:40:52 +0100 Subject: [PATCH 07/15] fix: table stats in accuracy --- xdem/workflows/accuracy.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index dabd9480..c367ecc6 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -403,6 +403,7 @@ def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) - if len(list_df_var) > 0: df_stats = pd.concat(list_df_var) + df_stats.set_index("Data", inplace=True) else: df_stats = None self.df_stats = df_stats @@ -484,7 +485,13 @@ def format_values(val: Any) -> Any: # Statistics table: if self.df_stats is not None: html += "

Statistics

\n" - html += self.df_stats.to_html(index=False) + html += "\n" + inter_columns = "\n" + for key, value in self.df_stats.T.iterrows(): + inter_line = "\n" + html += "
".join(map(str, self.df_stats.T.columns)) + html += f"
{key}{inter_columns}
".join(map(str, value.values)) + html += f"
{key}{inter_line}
\n" # Coregistration: Add elevation difference plot and histograms before/after if self.compute_coreg: From 07f404f4c259b851f273b16f9fcd54bb635b3d32 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Thu, 5 Feb 2026 10:55:56 +0100 Subject: [PATCH 08/15] fix: table start --- xdem/workflows/accuracy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index c367ecc6..659a0966 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -487,7 +487,7 @@ def format_values(val: Any) -> Any: html += "

Statistics

\n" html += "\n" inter_columns = "\n" + html += f"\n" for key, value in self.df_stats.T.iterrows(): inter_line = "\n" From 65f5156c89ff510b4e6c6d653e57f503e918e61e Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Fri, 6 Feb 2026 09:17:19 +0100 Subject: [PATCH 09/15] prop: cropp in html --- xdem/workflows/accuracy.py | 47 +++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 659a0966..989726d9 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -195,21 +195,30 @@ def _prepare_datas(self) -> None: # Intersection logging.info("Computing intersection") coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) + print() + self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) + self.reference_elev = self.reference_elev.crop(coord_intersection) + coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) + self.reference_elev = self.reference_elev.crop(coord_intersection) + + coord_intersection = self.to_be_aligned_elev.intersection(self.reference_elev) + if sampling_source == "reference_elev": - self.reference_elev = self.reference_elev.crop(coord_intersection) + self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) self.generate_plot( self.to_be_aligned_elev, - title="Cropped reference DEM", - filename="cropped_reference_elev_map", + title="Cropped to-be-aligned DEM", + filename="cropped_to_be_aligned_elev_map", cmap="terrain", cbar_title="Elevation (m)", ) else: - self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) + self.reference_elev = self.reference_elev.crop(coord_intersection) + self.generate_plot( - self.to_be_aligned_elev, - title="Cropped to-be-aligned DEM", - filename="cropped_to_be_aligned_elev_map", + self.reference_elev, + title="Cropped reference DEM", + filename="cropped_reference_elev_map", cmap="terrain", cbar_title="Elevation (m)", ) @@ -359,7 +368,7 @@ def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) - generate_plot_diff("after", self.diff_after, vmin, vmax) else: - self.diff = [self.to_be_aligned_elev - ref_elev] + self.diff = self.to_be_aligned_elev - ref_elev self.stats = self.diff.get_stats(stats_keys) vmin, vmax = -(self.stats["median"] + 3 * self.stats["nmad"]), self.stats["median"] + 3 * self.stats["nmad"] @@ -460,6 +469,28 @@ def create_html(self, list_dict: list[tuple[str, dict[str, Any]]]) -> None: ) html += "\n" + if self.compute_coreg: + html += "

Processed Dataset

\n" + html += "
\n" + sampling_source = self.config["inputs"]["sampling_grid"] + + if sampling_source == "reference_elev": + reference_elev_intersection = "plots/reference_elev_map.png" + to_be_aligned_elev_intersection = "plots/cropped_to_be_aligned_elev_map.png" + elif sampling_source == "to_be_aligned_elev": + reference_elev_intersection = "plots/cropped_reference_elev_map.png" + to_be_aligned_elev_intersection = "plots/to_be_aligned_elev_map.png" + + html += ( + " Image PNG\n" + ) + html += ( + " Image PNG\n" + ) + html += "
\n" + def format_values(val: Any) -> Any: """Format values for the dictionary.""" if isinstance(val, float): From 196f5ea376f2d14401bb1d28f7f78116e0c3ded1 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Mon, 9 Feb 2026 09:45:11 +0100 Subject: [PATCH 10/15] add: same scale intermediate plots --- xdem/workflows/accuracy.py | 49 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 989726d9..8d5d396c 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -205,23 +205,30 @@ def _prepare_datas(self) -> None: if sampling_source == "reference_elev": self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) - self.generate_plot( - self.to_be_aligned_elev, - title="Cropped to-be-aligned DEM", - filename="cropped_to_be_aligned_elev_map", - cmap="terrain", - cbar_title="Elevation (m)", - ) else: self.reference_elev = self.reference_elev.crop(coord_intersection) - self.generate_plot( - self.reference_elev, - title="Cropped reference DEM", - filename="cropped_reference_elev_map", - cmap="terrain", - cbar_title="Elevation (m)", - ) + vmin = min(self.reference_elev.get_stats("min"), self.to_be_aligned_elev.get_stats("min")) + vmax = max(self.reference_elev.get_stats("max"), self.to_be_aligned_elev.get_stats("max")) + self.generate_plot( + self.to_be_aligned_elev, + title="Cropped to-be-aligned DEM", + filename="cropped_to_be_aligned_elev_map", + cmap="terrain", + vmin=vmin, + vmax=vmax, + cbar_title="Elevation (m)", + ) + + self.generate_plot( + self.reference_elev, + title="Cropped reference DEM", + filename="cropped_reference_elev_map", + cmap="terrain", + vmin=vmin, + vmax=vmax, + cbar_title="Elevation (m)", + ) if self.level > 1: self.reference_elev.to_file(self.outputs_folder / "rasters" / "reference_elev_reprojected.tif") @@ -338,7 +345,7 @@ def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) - suffix = f"_elev_{label}_coreg_map" if label else "_elev" self.generate_plot( diff, - title=f"Difference\n{label} coregistration", + title=f"Difference {label} coregistration", filename=f"diff{suffix}", vmin=vmin, vmax=vmax, @@ -472,21 +479,13 @@ def create_html(self, list_dict: list[tuple[str, dict[str, Any]]]) -> None: if self.compute_coreg: html += "

Processed Dataset

\n" html += "
\n" - sampling_source = self.config["inputs"]["sampling_grid"] - - if sampling_source == "reference_elev": - reference_elev_intersection = "plots/reference_elev_map.png" - to_be_aligned_elev_intersection = "plots/cropped_to_be_aligned_elev_map.png" - elif sampling_source == "to_be_aligned_elev": - reference_elev_intersection = "plots/cropped_reference_elev_map.png" - to_be_aligned_elev_intersection = "plots/to_be_aligned_elev_map.png" html += ( - " Image PNG\n" ) html += ( - " Image PNG\n" ) html += "
\n" From dc8410f21c6f89b07d15fc6e7eabb79c959efa5b Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Mon, 9 Feb 2026 10:01:47 +0100 Subject: [PATCH 11/15] fix: simplication vmin/vmax --- xdem/workflows/accuracy.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 8d5d396c..ff603812 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -354,23 +354,21 @@ def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) - ) if self.compute_coreg: - self.diff_before = self.to_be_aligned_elev - ref_elev self.stats_before = self.diff_before.get_stats(stats_keys) self.diff_after = aligned_elev.reproject(ref_elev) - ref_elev self.stats_after = self.diff_after.get_stats(stats_keys) - vmin_bf, vmax_bf = ( + vmin = min( -(self.stats_before["median"] + 3 * self.stats_before["nmad"]), - self.stats_before["median"] + 3 * self.stats_before["nmad"], - ) - vmin_af, vmax_af = ( -(self.stats_after["median"] + 3 * self.stats_after["nmad"]), + ) + vmax = max( + self.stats_before["median"] + 3 * self.stats_before["nmad"], self.stats_after["median"] + 3 * self.stats_after["nmad"], ) - vmin = vmin_af if vmin_af < vmin_bf else vmin_bf - vmax = vmax_af if vmax_af > vmax_bf else vmax_bf + generate_plot_diff("before", self.diff_before, vmin, vmax) generate_plot_diff("after", self.diff_after, vmin, vmax) From 18092c721534e029a988eddfcb1062db42c65246 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Mon, 9 Feb 2026 11:01:30 +0100 Subject: [PATCH 12/15] fix: some opti --- xdem/workflows/accuracy.py | 62 +++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index ff603812..84f14bd3 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -195,7 +195,6 @@ def _prepare_datas(self) -> None: # Intersection logging.info("Computing intersection") coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) - print() self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) self.reference_elev = self.reference_elev.crop(coord_intersection) coord_intersection = self.reference_elev.intersection(self.to_be_aligned_elev) @@ -474,20 +473,6 @@ def create_html(self, list_dict: list[tuple[str, dict[str, Any]]]) -> None: ) html += "\n" - if self.compute_coreg: - html += "

Processed Dataset

\n" - html += "
\n" - - html += ( - " Image PNG\n" - ) - html += ( - " Image PNG\n" - ) - html += "
\n" - def format_values(val: Any) -> Any: """Format values for the dictionary.""" if isinstance(val, float): @@ -497,26 +482,55 @@ def format_values(val: Any) -> Any: else: return str(val) - # Metadata: Inputs, coregistration - for title, dictionary in list_dict: # type: ignore - html += "
\n" - html += f"

{title}

\n" - html += "
".join(map(str, self.df_stats.T.columns)) - html += f"
{key}{inter_columns}
Data{inter_columns}
".join(map(str, value.values)) html += f"
{key}{inter_line}
\n" - html += "\n" + def print_dict(title: str, dictionary: dict[str, Any]) -> str: + div_html = "
\n" + div_html += f"

{title}

\n" + div_html += "
InformationValue
\n" + div_html += "\n" for key, value in dictionary.items(): if isinstance(value, dict): value = {k: format_values(v) for k, v in value.items()} - html += f"\n" - html += "
InformationValue
{key}{value}
\n" + div_html += f"{key}{value}\n" + div_html += "\n" + div_html += "\n" + return div_html + + # Metadata: Inputs + inputs_information = list_dict[0] + html += print_dict(inputs_information[0], inputs_information[1]) + + # Plot processed data if did + if "sampling_grid" in self.config["inputs"] and self.config["inputs"]["sampling_grid"] is not None: + html += "

Processed Dataset

\n" + html += "
\n" + + html += ( + " Image PNG\n" + ) + html += ( + " Image PNG\n" + ) html += "
\n" + # Metadata: Inputs + for title, dictionary in list_dict[1:]: # type: ignore + html += print_dict(title, dictionary) + # Statistics table: if self.df_stats is not None: html += "

Statistics

\n" html += "\n" + + # Plot one stat by row inter_columns = "\n" - for key, value in self.df_stats.T.iterrows(): + + # Rounded to three decimal numbers + rounded_stats = self.df_stats.astype(float).round(3) + + for key, value in rounded_stats.T.iterrows(): inter_line = "\n" html += "
".join(map(str, self.df_stats.T.columns)) html += f"
Data{inter_columns}
".join(map(str, value.values)) html += f"
{key}{inter_line}
\n" From a7084e17d6ac6120e4e035c713df9fd3d0a6fa3a Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Mon, 9 Feb 2026 13:45:56 +0100 Subject: [PATCH 13/15] fix: preprocessed data --- xdem/workflows/accuracy.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 84f14bd3..2582d90c 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -211,8 +211,8 @@ def _prepare_datas(self) -> None: vmax = max(self.reference_elev.get_stats("max"), self.to_be_aligned_elev.get_stats("max")) self.generate_plot( self.to_be_aligned_elev, - title="Cropped to-be-aligned DEM", - filename="cropped_to_be_aligned_elev_map", + title="Preprocessed to-be-aligned DEM", + filename="preprocessed_to_be_aligned_elev_map", cmap="terrain", vmin=vmin, vmax=vmax, @@ -221,8 +221,8 @@ def _prepare_datas(self) -> None: self.generate_plot( self.reference_elev, - title="Cropped reference DEM", - filename="cropped_reference_elev_map", + title="Preprocessed reference DEM", + filename="preprocessed_reference_elev_map", cmap="terrain", vmin=vmin, vmax=vmax, @@ -499,17 +499,17 @@ def print_dict(title: str, dictionary: dict[str, Any]) -> str: inputs_information = list_dict[0] html += print_dict(inputs_information[0], inputs_information[1]) - # Plot processed data if did + # Plot preprocessed data if did if "sampling_grid" in self.config["inputs"] and self.config["inputs"]["sampling_grid"] is not None: - html += "

Processed Dataset

\n" + html += "

Preprocessed Dataset

\n" html += "
\n" html += ( - " Image PNG\n" ) html += ( - " Image PNG\n" ) html += "
\n" @@ -524,15 +524,15 @@ def print_dict(title: str, dictionary: dict[str, Any]) -> str: html += "\n" # Plot one stat by row - inter_columns = "\n" + df_cols = "".join([f'' for col in self.df_stats.T.columns]) + html += f'{df_cols}\n' # Rounded to three decimal numbers rounded_stats = self.df_stats.astype(float).round(3) for key, value in rounded_stats.T.iterrows(): - inter_line = "\n" + df_values = "".join([f"" for val in value.values]) + html += f"{df_values}\n" html += "
".join(map(str, self.df_stats.T.columns)) - html += f"
Data{inter_columns}
{col}
Data
".join(map(str, value.values)) - html += f"
{key}{inter_line}
{str(val)}
{key}
\n" # Coregistration: Add elevation difference plot and histograms before/after From 05b53f8ac87c84a59e23830701bfcab4733df0a1 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Thu, 12 Feb 2026 14:13:41 +0100 Subject: [PATCH 14/15] fix: same scale plot everywhere --- doc/source/_workflows/accuracy_config.yaml | 4 +- xdem/workflows/accuracy.py | 99 ++++++++++++---------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/doc/source/_workflows/accuracy_config.yaml b/doc/source/_workflows/accuracy_config.yaml index 39114b7f..7fe837f9 100644 --- a/doc/source/_workflows/accuracy_config.yaml +++ b/doc/source/_workflows/accuracy_config.yaml @@ -8,8 +8,8 @@ outputs: path: "outputs_accuracy" coregistration: step_one: - method: "LZD" - extra_information: {"subsample": 10000} + method: NuthKaab + extra_information: null statistics: - median - nmad diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 2582d90c..651980f7 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -74,17 +74,26 @@ def __init__(self, config_dem: str | Dict[str, Any], output: str | None = None) self.config = self.remove_none(self.config) # type: ignore - def _load_data(self) -> None: - """Load data.""" + def _load_data(self) -> tuple[float, float]: + """ + Load data + :return vmin, vmax: to plot elevation data with the same scale + """ self.to_be_aligned_elev, tba_mask, tba_path_mask = self.load_dem(self.config["inputs"]["to_be_aligned_elev"]) self.reference_elev, ref_mask, ref_mask_path = self.load_dem(self.config["inputs"].get("reference_elev", None)) if self.reference_elev is None: self.reference_elev = self._get_reference_elevation() + + vmin = min(self.reference_elev.get_stats("min"), self.to_be_aligned_elev.get_stats("min")) + vmax = max(self.reference_elev.get_stats("max"), self.to_be_aligned_elev.get_stats("max")) + self.generate_plot( self.reference_elev, title="Reference DEM", filename="reference_elev_map", + vmin=vmin, + vmax=vmax, cmap="terrain", cbar_title="Elevation (m)", ) @@ -92,6 +101,8 @@ def _load_data(self) -> None: self.to_be_aligned_elev, title="To-be-aligned DEM", filename="to_be_aligned_elev_map", + vmin=vmin, + vmax=vmax, cmap="terrain", cbar_title="Elevation (m)", ) @@ -109,11 +120,15 @@ def _load_data(self) -> None: self.to_be_aligned_elev, title="Masked (inlier) terrain", filename="masked_elev_map", + vmin=vmin, + vmax=vmax, mask_path=path_mask, cmap="terrain", cbar_title="Elevation (m)", ) + return vmin, vmax + def _get_reference_elevation(self) -> float: """ Get reference elevation. @@ -169,9 +184,12 @@ def _compute_coregistration(self) -> RasterType: return aligned_elev - def _prepare_datas(self) -> None: + def _prepare_datas(self, vmin: float, vmax: float) -> None: """ Compute reprojection. + + :param vmin: to plot elevation data with the same scale + :param vmax: to plot elevation data with the same scale """ sampling_source = self.config["inputs"]["sampling_grid"] @@ -204,30 +222,26 @@ def _prepare_datas(self) -> None: if sampling_source == "reference_elev": self.to_be_aligned_elev = self.to_be_aligned_elev.crop(coord_intersection) + self.generate_plot( + self.to_be_aligned_elev, + title="Preprocessed to-be-aligned DEM", + filename="preprocessed_to_be_aligned_elev_map", + vmin=vmin, + vmax=vmax, + cmap="terrain", + cbar_title="Elevation (m)", + ) else: self.reference_elev = self.reference_elev.crop(coord_intersection) - - vmin = min(self.reference_elev.get_stats("min"), self.to_be_aligned_elev.get_stats("min")) - vmax = max(self.reference_elev.get_stats("max"), self.to_be_aligned_elev.get_stats("max")) - self.generate_plot( - self.to_be_aligned_elev, - title="Preprocessed to-be-aligned DEM", - filename="preprocessed_to_be_aligned_elev_map", - cmap="terrain", - vmin=vmin, - vmax=vmax, - cbar_title="Elevation (m)", - ) - - self.generate_plot( - self.reference_elev, - title="Preprocessed reference DEM", - filename="preprocessed_reference_elev_map", - cmap="terrain", - vmin=vmin, - vmax=vmax, - cbar_title="Elevation (m)", - ) + self.generate_plot( + self.reference_elev, + title="Preprocessed reference DEM", + filename="preprocessed_reference_elev_map", + vmin=vmin, + vmax=vmax, + cmap="terrain", + cbar_title="Elevation (m)", + ) if self.level > 1: self.reference_elev.to_file(self.outputs_folder / "rasters" / "reference_elev_reprojected.tif") @@ -320,11 +334,11 @@ def run(self) -> None: t0 = time.time() - self._load_data() + vmin, vmax = self._load_data() # Reprojection step if "sampling_grid" in self.config["inputs"]: - self._prepare_datas() + self._prepare_datas(vmin, vmax) if self.compute_coreg: # Coregistration step @@ -336,8 +350,6 @@ def run(self) -> None: output_grid = self.config["outputs"]["output_grid"] ref_elev = self.reference_elev if output_grid == "reference_elev" else self.to_be_aligned_elev - vmin = vmax = None - stats_keys = ["min", "max", "nmad", "median"] def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) -> None: @@ -353,28 +365,28 @@ def generate_plot_diff(label: str, diff: RasterType, vmin: float, vmax: float) - ) if self.compute_coreg: + self.diff_before = self.to_be_aligned_elev - ref_elev self.stats_before = self.diff_before.get_stats(stats_keys) self.diff_after = aligned_elev.reproject(ref_elev) - ref_elev self.stats_after = self.diff_after.get_stats(stats_keys) - vmin = min( + vmin_diff = min( -(self.stats_before["median"] + 3 * self.stats_before["nmad"]), -(self.stats_after["median"] + 3 * self.stats_after["nmad"]), ) - vmax = max( + vmax_diff = max( self.stats_before["median"] + 3 * self.stats_before["nmad"], self.stats_after["median"] + 3 * self.stats_after["nmad"], ) - generate_plot_diff("before", self.diff_before, vmin, vmax) - generate_plot_diff("after", self.diff_after, vmin, vmax) + generate_plot_diff("before", self.diff_before, vmin_diff, vmax_diff) + generate_plot_diff("after", self.diff_after, vmin_diff, vmax_diff) else: self.diff = self.to_be_aligned_elev - ref_elev self.stats = self.diff.get_stats(stats_keys) - vmin, vmax = -(self.stats["median"] + 3 * self.stats["nmad"]), self.stats["median"] + 3 * self.stats["nmad"] generate_plot_diff("", self.diff, vmin, vmax) @@ -501,15 +513,15 @@ def print_dict(title: str, dictionary: dict[str, Any]) -> str: # Plot preprocessed data if did if "sampling_grid" in self.config["inputs"] and self.config["inputs"]["sampling_grid"] is not None: + if self.config["inputs"]["sampling_grid"] == "reference_elev": + preprocessed_data = "plots/preprocessed_to_be_aligned_elev_map.png" + else: + preprocessed_data = "plots/preprocessed_reference_elev_map.png" + html += "

Preprocessed Dataset

\n" html += "
\n" - html += ( - " Image PNG\n" - ) - html += ( - " Image PNG\n" ) html += "
\n" @@ -527,12 +539,9 @@ def print_dict(title: str, dictionary: dict[str, Any]) -> str: df_cols = "".join([f'{col}' for col in self.df_stats.T.columns]) html += f'Data{df_cols}\n' - # Rounded to three decimal numbers - rounded_stats = self.df_stats.astype(float).round(3) - - for key, value in rounded_stats.T.iterrows(): + for key, value in self.df_stats.T.iterrows(): df_values = "".join([f"{str(val)}" for val in value.values]) - html += f"{key}{df_values}\n" + html += f'{key}{df_values}\n' html += "\n" # Coregistration: Add elevation difference plot and histograms before/after From 9e3d9cc616744e1c494eacb22693e595f6d72614 Mon Sep 17 00:00:00 2001 From: "marine.bouchet" Date: Thu, 12 Feb 2026 14:50:10 +0100 Subject: [PATCH 15/15] fix: css style --- xdem/workflows/accuracy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xdem/workflows/accuracy.py b/xdem/workflows/accuracy.py index 651980f7..325a3ba1 100644 --- a/xdem/workflows/accuracy.py +++ b/xdem/workflows/accuracy.py @@ -474,14 +474,14 @@ def create_html(self, list_dict: list[tuple[str, dict[str, Any]]]) -> None: # Plot input elevation data html += "

Elevation datasets

\n" - html += "
\n" + html += "
\n" html += ( - " Image PNG\n" + "Image PNG" ) html += ( - " Image PNG\n" + "Image PNG" ) html += "
\n" @@ -518,8 +518,8 @@ def print_dict(title: str, dictionary: dict[str, Any]) -> str: else: preprocessed_data = "plots/preprocessed_reference_elev_map.png" - html += "

Preprocessed Dataset

\n" - html += "
\n" + html += "

Preprocessed DEM

\n" + html += "
\n" html += ( " Image PNG\n"