Skip to content

Commit d6b8fdf

Browse files
authored
439 add proximity to anomaly tool (#440)
1 parent ceb9752 commit d6b8fdf

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Proximity to anomaly
2+
3+
::: eis_toolkit.raster_processing.proximity_to_anomaly

eis_toolkit/cli.py

+59
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,65 @@ def distance_to_anomaly_cli(
13151315
typer.echo(f"Computing distance to anomaly completed, writing raster to {output_raster}.")
13161316

13171317

1318+
# PROXIMITY TO ANOMALY
1319+
@app.command()
1320+
def proximity_to_anomaly_cli(
1321+
input_raster: INPUT_FILE_OPTION,
1322+
output_raster: OUTPUT_FILE_OPTION,
1323+
threshold_criteria: Annotated[ThresholdCriteria, typer.Option(case_sensitive=False)],
1324+
first_threshold_criteria_value: float = typer.Option(),
1325+
second_threshold_criteria_value: float = None,
1326+
max_distance: float = typer.Option(),
1327+
max_distance_value: float = 0.0,
1328+
anomaly_value: float = 1.0,
1329+
):
1330+
"""
1331+
Calculate proximity from each raster cell to nearest anomaly cell.
1332+
1333+
Uses only the first band of the raster.
1334+
"""
1335+
# from sys import platform
1336+
1337+
from eis_toolkit.raster_processing.proximity_to_anomaly import proximity_to_anomaly
1338+
1339+
typer.echo("Progress: 10%")
1340+
1341+
if second_threshold_criteria_value is not None:
1342+
threshold_criteria_value = (first_threshold_criteria_value, second_threshold_criteria_value)
1343+
else:
1344+
threshold_criteria_value = first_threshold_criteria_value
1345+
1346+
with rasterio.open(input_raster) as raster:
1347+
typer.echo("Progress: 25%")
1348+
# Use optimized version if Windows
1349+
# if platform == "win32":
1350+
# out_image, out_meta = proximity_to_anomaly_gdal(
1351+
# anomaly_raster_profile=raster.profile,
1352+
# anomaly_raster_data=raster.read(1),
1353+
# threshold_criteria_value=threshold_criteria_value,
1354+
# threshold_criteria=get_enum_values(threshold_criteria),
1355+
# max_distance=max_distance,
1356+
# scaling_range=(anomaly_value, max_distance_value),
1357+
# )
1358+
# else:
1359+
out_image, out_meta = proximity_to_anomaly(
1360+
anomaly_raster_profile=raster.profile,
1361+
anomaly_raster_data=raster.read(1),
1362+
threshold_criteria_value=threshold_criteria_value,
1363+
threshold_criteria=get_enum_values(threshold_criteria),
1364+
max_distance=max_distance,
1365+
scaling_range=(anomaly_value, max_distance_value),
1366+
)
1367+
1368+
typer.echo("Progress: 75%")
1369+
1370+
with rasterio.open(output_raster, "w", **out_meta) as dest:
1371+
dest.write(out_image, 1)
1372+
typer.echo("Progress: 100%")
1373+
1374+
typer.echo(f"Computing proximity to anomaly completed, writing raster to {output_raster}.")
1375+
1376+
13181377
# EXTRACT VALUES FROM RASTER
13191378
@app.command()
13201379
def extract_values_from_raster_cli(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from numbers import Number
2+
3+
import numpy as np
4+
from beartype import beartype
5+
from beartype.typing import Literal, Tuple, Union
6+
from rasterio import profiles
7+
8+
from eis_toolkit.raster_processing.distance_to_anomaly import distance_to_anomaly, distance_to_anomaly_gdal
9+
from eis_toolkit.transformations.linear import _min_max_scaling
10+
11+
12+
@beartype
13+
def proximity_to_anomaly(
14+
anomaly_raster_profile: Union[profiles.Profile, dict],
15+
anomaly_raster_data: np.ndarray,
16+
threshold_criteria_value: Union[Tuple[Number, Number], Number],
17+
threshold_criteria: Literal["lower", "higher", "in_between", "outside"],
18+
max_distance: Number,
19+
scaling_range: Tuple[Number, Number] = (1, 0),
20+
) -> Tuple[np.ndarray, Union[profiles.Profile, dict]]:
21+
"""Calculate proximity from raster cell to nearest anomaly.
22+
23+
The criteria for what is anomalous can be defined as a single number and
24+
criteria text of "higher" or "lower". Alternatively, the definition can be
25+
a range where values inside (criteria text of "within") or outside are
26+
marked as anomalous (criteria text of "outside"). If anomaly_raster_profile does
27+
contain "nodata" key, np.nan is assumed to correspond to nodata values.
28+
29+
Scales proximity values linearly in the given range. The first number in scale_range
30+
denotes the value at the anomaly cells, the second at given maximum_distance.
31+
32+
Args:
33+
anomaly_raster_profile: The raster profile in which the distances
34+
to the nearest anomalous value are determined.
35+
anomaly_raster_data: The raster data in which the distances
36+
to the nearest anomalous value are determined.
37+
threshold_criteria_value: Value(s) used to define anomalous.
38+
If the threshold criteria requires a tuple of values,
39+
the first value should be the minimum and the second
40+
the maximum value.
41+
threshold_criteria: Method to define anomalous.
42+
max_distance: The maximum distance in the output array beyond which
43+
proximity is considered 0.
44+
scaling_range: Min and max values used for scaling the proximity values.
45+
Defaults to (1, 0).
46+
47+
Returns:
48+
A 2D numpy array with the distances to anomalies computed
49+
and the original anomaly raster profile.
50+
"""
51+
out_image, anomaly_raster_profile = distance_to_anomaly(
52+
anomaly_raster_profile, anomaly_raster_data, threshold_criteria_value, threshold_criteria, max_distance
53+
)
54+
out_image = _min_max_scaling(out_image, scaling_range)
55+
56+
return out_image, anomaly_raster_profile
57+
58+
59+
@beartype
60+
def proximity_to_anomaly_gdal(
61+
anomaly_raster_profile: Union[profiles.Profile, dict],
62+
anomaly_raster_data: np.ndarray,
63+
threshold_criteria_value: Union[Tuple[Number, Number], Number],
64+
threshold_criteria: Literal["lower", "higher", "in_between", "outside"],
65+
max_distance: Number,
66+
scaling_range: Tuple[Number, Number] = (1, 0),
67+
) -> Tuple[np.ndarray, Union[profiles.Profile, dict]]:
68+
"""Calculate proximity from raster cell to nearest anomaly.
69+
70+
Uses an optimized, faster version of `distance_to_anomaly` in the background.
71+
Available only on Windows.
72+
73+
The criteria for what is anomalous can be defined as a single number and
74+
criteria text of "higher" or "lower". Alternatively, the definition can be
75+
a range where values inside (criteria text of "within") or outside are
76+
marked as anomalous (criteria text of "outside"). If anomaly_raster_profile does
77+
contain "nodata" key, np.nan is assumed to correspond to nodata values.
78+
79+
Scales proximity values linearly in the given range. The first number in scale_range
80+
denotes the value at the anomaly cells, the second at given maximum_distance.
81+
82+
Args:
83+
anomaly_raster_profile: The raster profile in which the distances
84+
to the nearest anomalous value are determined.
85+
anomaly_raster_data: The raster data in which the distances
86+
to the nearest anomalous value are determined.
87+
threshold_criteria_value: Value(s) used to define anomalous.
88+
If the threshold criteria requires a tuple of values,
89+
the first value should be the minimum and the second
90+
the maximum value.
91+
threshold_criteria: Method to define anomalous.
92+
max_distance: The maximum distance in the output array beyond which
93+
proximity is considered 0.
94+
scaling_range: Min and max values used for scaling the proximity values.
95+
Defaults to (1, 0).
96+
97+
Returns:
98+
A 2D numpy array with the distances to anomalies computed
99+
and the original anomaly raster profile.
100+
"""
101+
out_image, anomaly_raster_profile = distance_to_anomaly_gdal(
102+
anomaly_raster_profile, anomaly_raster_data, threshold_criteria_value, threshold_criteria, max_distance
103+
)
104+
out_image = _min_max_scaling(out_image, scaling_range)
105+
106+
return out_image, anomaly_raster_profile

0 commit comments

Comments
 (0)