2020
2121_DEFAULT_RTOL = 1e-6
2222_DEFAULT_ATOL = 1e-6
23+ _DEFAULT_PIXELTOL = 0.0
2324
2425# https://paulbourke.net/dataformats/asciiart
2526DEFAULT_GRAYSCALE_70_CHARACTERS = r"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. " [::- 1 ]
2627DEFAULT_GRAYSCALE_10_CHARACTERS = " .:-=+*#%@"
2728
29+
2830def _load_xarray_netcdf (path : Union [str , Path ], ** kwargs ) -> xarray .Dataset :
2931 """
3032 Load a netCDF file as Xarray Dataset
@@ -194,6 +196,7 @@ def _compare_xarray_dataarray(
194196 * ,
195197 rtol : float = _DEFAULT_RTOL ,
196198 atol : float = _DEFAULT_ATOL ,
199+ pixel_tolerance : float = _DEFAULT_PIXELTOL ,
197200 name : str = None ,
198201) -> List [str ]:
199202 """
@@ -230,9 +233,19 @@ def _compare_xarray_dataarray(
230233 issues .append (f"Shape mismatch: { actual .shape } != { expected .shape } " )
231234 compatible = len (issues ) == 0
232235 try :
233- xarray .testing .assert_allclose (a = actual , b = expected , rtol = rtol , atol = atol )
236+ if pixel_tolerance and compatible :
237+ threshold = abs (expected * rtol ) + atol
238+ bad_pixels = abs (actual * 1.0 - expected * 1.0 ) > threshold
239+ percentage_bad_pixels = bad_pixels .mean ().item () * 100
240+ assert (
241+ percentage_bad_pixels <= pixel_tolerance
242+ ), f"Fraction significantly differing pixels: { percentage_bad_pixels } % > { pixel_tolerance } %"
243+ xarray .testing .assert_allclose (
244+ a = actual .where (~ bad_pixels ), b = expected .where (~ bad_pixels ), rtol = rtol , atol = atol
245+ )
246+ else :
247+ xarray .testing .assert_allclose (a = actual , b = expected , rtol = rtol , atol = atol )
234248 except AssertionError as e :
235- # TODO: message of `assert_allclose` is typically multiline, split it again or make it one line?
236249 issues .append (str (e ).strip ())
237250 if compatible and {"x" , "y" } <= set (expected .dims ):
238251 issues .extend (
@@ -266,12 +279,14 @@ def assert_xarray_dataarray_allclose(
266279 if issues :
267280 raise AssertionError ("\n " .join (issues ))
268281
282+
269283def _compare_xarray_datasets (
270284 actual : Union [xarray .Dataset , str , Path ],
271285 expected : Union [xarray .Dataset , str , Path ],
272286 * ,
273287 rtol : float = _DEFAULT_RTOL ,
274288 atol : float = _DEFAULT_ATOL ,
289+ pixel_tolerance : float = _DEFAULT_PIXELTOL ,
275290) -> List [str ]:
276291 """
277292 Compare two xarray ``DataSet``s with tolerance and report mismatch issues (as strings)
@@ -290,7 +305,9 @@ def _compare_xarray_datasets(
290305 all_issues .append (f"Xarray DataSet variables mismatch: { actual_vars } != { expected_vars } " )
291306 for var in expected_vars .intersection (actual_vars ):
292307 _log .debug (f"_compare_xarray_datasets: comparing variable { var !r} " )
293- issues = _compare_xarray_dataarray (actual [var ], expected [var ], rtol = rtol , atol = atol , name = var )
308+ issues = _compare_xarray_dataarray (
309+ actual [var ], expected [var ], rtol = rtol , atol = atol , pixel_tolerance = pixel_tolerance , name = var
310+ )
294311 if issues :
295312 all_issues .append (f"Issues for variable { var !r} :" )
296313 all_issues .extend (issues )
@@ -387,6 +404,7 @@ def _compare_job_results(
387404 * ,
388405 rtol : float = _DEFAULT_RTOL ,
389406 atol : float = _DEFAULT_ATOL ,
407+ pixel_tolerance : float = _DEFAULT_PIXELTOL ,
390408 tmp_path : Optional [Path ] = None ,
391409) -> List [str ]:
392410 """
@@ -415,12 +433,16 @@ def _compare_job_results(
415433 all_issues .append (f"Issues for metadata file { filename !r} :" )
416434 all_issues .extend (issues )
417435 elif expected_path .suffix .lower () in {".nc" , ".netcdf" }:
418- issues = _compare_xarray_datasets (actual = actual_path , expected = expected_path , rtol = rtol , atol = atol )
436+ issues = _compare_xarray_datasets (
437+ actual = actual_path , expected = expected_path , rtol = rtol , atol = atol , pixel_tolerance = pixel_tolerance
438+ )
419439 if issues :
420440 all_issues .append (f"Issues for file { filename !r} :" )
421441 all_issues .extend (issues )
422442 elif expected_path .suffix .lower () in {".tif" , ".tiff" , ".gtiff" , ".geotiff" }:
423- issues = _compare_xarray_dataarray (actual = actual_path , expected = expected_path , rtol = rtol , atol = atol )
443+ issues = _compare_xarray_dataarray (
444+ actual = actual_path , expected = expected_path , rtol = rtol , atol = atol , pixel_tolerance = pixel_tolerance
445+ )
424446 if issues :
425447 all_issues .append (f"Issues for file { filename !r} :" )
426448 all_issues .extend (issues )
@@ -463,6 +485,7 @@ def assert_job_results_allclose(
463485 * ,
464486 rtol : float = _DEFAULT_RTOL ,
465487 atol : float = _DEFAULT_ATOL ,
488+ pixel_tolerance : float = _DEFAULT_PIXELTOL ,
466489 tmp_path : Optional [Path ] = None ,
467490):
468491 """
@@ -474,6 +497,8 @@ def assert_job_results_allclose(
474497 :py:meth:`~openeo.rest.job.JobResults` object or path to directory with downloaded assets.
475498 :param rtol: relative tolerance
476499 :param atol: absolute tolerance
500+ :param pixel_tolerance: maximum fraction of pixels (in percent)
501+ that is allowed to be significantly different (considering ``atol`` and ``rtol``)
477502 :param tmp_path: root temp path to download results if needed.
478503 It's recommended to pass pytest's `tmp_path` fixture here
479504 :raises AssertionError: if not equal within the given tolerance
@@ -483,6 +508,8 @@ def assert_job_results_allclose(
483508 .. warning::
484509 This function is experimental and subject to change.
485510 """
486- issues = _compare_job_results (actual , expected , rtol = rtol , atol = atol , tmp_path = tmp_path )
511+ issues = _compare_job_results (
512+ actual , expected , rtol = rtol , atol = atol , pixel_tolerance = pixel_tolerance , tmp_path = tmp_path
513+ )
487514 if issues :
488515 raise AssertionError ("\n " .join (issues ))
0 commit comments