Change detection¶
Differences between two dates of a habitat map: pixel transitions, structural change without edge noise, and the change of fragmentation and of landscape mosaics. Tutorial: change detection.
All functions take the two dates on the same grid. The change of
connectivity indices over time is in
ls.conn.eca_change.
Habitat change¶
simple_change
¶
simple_change(
t1: DataArray | ndarray | str | Path, t2: DataArray | ndarray | str | Path
) -> SimpleChangeResult
Pixel-by-pixel change of a habitat map between two dates.
Every pixel gets one of four transitions: stable background (11), gain (12), loss (21) or stable foreground (22); 255 where either date is missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t1
|
DataArray | ndarray | str | Path
|
Maps of the two dates, coded 0 missing, 1 background, 2 foreground (DataArray, array or path), on the same grid. |
required |
t2
|
DataArray | ndarray | str | Path
|
Maps of the two dates, coded 0 missing, 1 background, 2 foreground (DataArray, array or path), on the same grid. |
required |
Returns:
| Type | Description |
|---|---|
SimpleChangeResult
|
|
See Also
morphological_change : separates structural change from edge noise.
Examples:
>>> import landschaft as ls
>>> before = ls.load_raster(ls.example_data("input")) # 0 missing, 1 background, 2 forest
>>> after = before.copy()
>>> cut = after.values[600:700, 100:260]
>>> cut[cut == 2] = 1 # a clear-cut
>>> plantation = after.values[250:330, 420:520]
>>> plantation[plantation == 1] = 2 # a new plantation
>>> change = ls.simple_change(before, after)
>>> change.gain, change.loss, change.net_change
(6839, 5510, 1329)
SimpleChangeResult
dataclass
¶
SimpleChangeResult(
change_map: DataArray | ndarray,
stable_bg: int,
gain: int,
loss: int,
stable_fg: int,
missing: int,
total_valid: int,
)
Bases: Saveable
Result of simple_change().
Attributes:
| Name | Type | Description |
|---|---|---|
change_map |
DataArray or ndarray
|
Byte raster with pixel values: 11 = Stable Background, 12 = Gain, 21 = Loss, 22 = Stable Foreground, 255 = Missing. |
stable_bg |
int
|
Pixels of stable background. |
gain |
int
|
Pixels of foreground gain. |
loss |
int
|
Pixels of foreground loss. |
stable_fg |
int
|
Pixels of stable foreground. |
missing |
int
|
Pixels missing at one or both times. |
total_valid |
int
|
Total valid pixels (stable_bg + gain + loss + stable_fg). |
Examples:
>>> import landschaft as ls
>>> before = ls.load_raster(ls.example_data("input")) # 0 missing, 1 background, 2 forest
>>> after = before.copy()
>>> cut = after.values[600:700, 100:260]
>>> cut[cut == 2] = 1 # a clear-cut
>>> plantation = after.values[250:330, 420:520]
>>> plantation[plantation == 1] = 2 # a new plantation
>>> print(ls.simple_change(before, after).summary())
Simple Change Analysis Summary (A -> B)
...
Net Change [pixels] : +1,329
...
morphological_change
¶
morphological_change(
t1: DataArray | ndarray | str | Path,
t2: DataArray | ndarray | str | Path,
*,
n_threads: int | None = None,
) -> MorphologicalChangeResult
Change of a habitat map that separates real structural change from edge noise (GTB's Morphological Change Detection).
Small shifts along patch edges (e.g. from slightly different classifications of the two dates) are "spurious" (176); gains and losses that change the shape of the habitat are kept (12, 21). The method finds the cores of each date (pixels more than 2 pixels from the edge), reconstructs them by geodesic dilation and closes the result with a 3 x 3 window.
The landscape elasticity compares the change of the core interior with the total change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t1
|
DataArray | ndarray | str | Path
|
Maps of the two dates, coded 0 missing, 1 background, 2 foreground (DataArray, array or path). |
required |
t2
|
DataArray | ndarray | str | Path
|
Maps of the two dates, coded 0 missing, 1 background, 2 foreground (DataArray, array or path). |
required |
n_threads
|
int | None
|
Threads (0 or |
None
|
Returns:
| Type | Description |
|---|---|
MorphologicalChangeResult
|
|
Examples:
>>> import landschaft as ls
>>> before = ls.load_raster(ls.example_data("input")) # 0 missing, 1 background, 2 forest
>>> after = before.copy()
>>> cut = after.values[600:700, 100:260]
>>> cut[cut == 2] = 1 # a clear-cut
>>> plantation = after.values[250:330, 420:520]
>>> plantation[plantation == 1] = 2 # a new plantation
>>> change = ls.morphological_change(before, after)
>>> change.mcd_gain, change.mcd_loss, change.spurious
(7348, 5504, 395)
MorphologicalChangeResult
dataclass
¶
MorphologicalChangeResult(
change_map: DataArray | ndarray,
fg_area_t1: int,
fg_area_t2: int,
net_change: int,
stable_bg: int,
stable_fg: int,
mcd_gain: int,
mcd_loss: int,
spurious: int,
missing: int,
elasticity: float,
)
Bases: Saveable
Result of morphological_change().
Attributes:
| Name | Type | Description |
|---|---|---|
change_map |
DataArray or ndarray
|
Byte raster with pixel values: 11 = Stable BG, 12 = MCD Gain, 21 = MCD Loss, 22 = Stable FG, 176 = Spurious boundary chatter, 255 = Missing. |
fg_area_t1 |
int
|
Total foreground area at time 1. |
fg_area_t2 |
int
|
Total foreground area at time 2. |
net_change |
int
|
Net foreground change (T2 - T1). |
stable_bg |
int
|
Stable background pixels. |
stable_fg |
int
|
Stable foreground pixels. |
mcd_gain |
int
|
Structural foreground gain pixels. |
mcd_loss |
int
|
Structural foreground loss pixels. |
spurious |
int
|
Boundary chatter / spurious change pixels. |
missing |
int
|
Missing pixels at one or both times. |
elasticity |
float
|
Landscape elasticity index (ratio of core interior change to total change). |
Fragmentation change¶
fos_change
¶
fos_change(
t1: DataArray | ndarray | str | Path,
t2: DataArray | ndarray | str | Path,
*,
n_threads: int | None = None,
) -> FOSChangeResult
Change of fragmentation between two dates (GTB's FOS change).
Compares two fos() maps: for the foreground at both dates, the shift
of its fragmentation class (a 6 x 6 transition matrix from Rare to Intact),
and whether it became more or less fragmented; gains and losses of
foreground are marked separately.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t1
|
DataArray | ndarray | str | Path
|
FOS maps of the two dates ( |
required |
t2
|
DataArray | ndarray | str | Path
|
FOS maps of the two dates ( |
required |
n_threads
|
int | None
|
Threads (0 or |
None
|
Returns:
| Type | Description |
|---|---|
FOSChangeResult
|
|
Examples:
>>> import landschaft as ls
>>> before = ls.load_raster(ls.example_data("input")) # 0 missing, 1 background, 2 forest
>>> after = before.copy()
>>> cut = after.values[600:700, 100:260]
>>> cut[cut == 2] = 1 # a clear-cut
>>> plantation = after.values[250:330, 420:520]
>>> plantation[plantation == 1] = 2 # a new plantation
>>> shift = ls.fos_change(ls.fos(before, window_size=27).map, ls.fos(after, window_size=27).map)
>>> round(shift.frag_increase_pct, 2), round(shift.frag_decrease_pct, 2)
(54.71, 45.29)
>>> shift.matrix_6x6.shape
(6, 6)
FOSChangeResult
dataclass
¶
FOSChangeResult(
change_map: DataArray | ndarray,
fg_gain: int,
fg_loss: int,
stable_bg: int,
forcom: int,
diag_same_class: int,
diff_class: int,
frag_decrease_pct: float,
frag_increase_pct: float,
matrix_6x6: DataFrame,
)
Bases: Saveable
Result of fos_change().
Attributes:
| Name | Type | Description |
|---|---|---|
change_map |
DataArray or ndarray
|
Byte raster with pixel values: 0..200 = Delta FOS shifted by +100 (100 = No change), 250 = Foreground Gain, 251 = Foreground Loss, 252 = Stable BG, 253 = Water, 254 = Missing. |
fg_gain |
int
|
Pixels transitioning from BG to FG. |
fg_loss |
int
|
Pixels transitioning from FG to BG. |
stable_bg |
int
|
Pixels remaining background at both times. |
forcom |
int
|
Common foreground pixels evaluated for fragmentation shift. |
diag_same_class |
int
|
Pixels maintaining the exact same FOS density class. |
diff_class |
int
|
Pixels shifting between FOS density classes. |
frag_decrease_pct |
float
|
Percentage of shifting pixels with decreased fragmentation (increased connectivity). |
frag_increase_pct |
float
|
Percentage of shifting pixels with increased fragmentation (decreased connectivity). |
matrix_6x6 |
DataFrame
|
6x6 transition matrix across FOS classes (T1 rows -> T2 columns). |
fad_multiscale_change
¶
fad_multiscale_change(
a, b, *, window_sizes: Sequence[int] = (7, 13, 27, 81, 243), n_threads: int = 0
) -> FADMultiscaleChangeResult
Fragmentation class change between two FAD-MS analyses (GTB Change -> FAD Multiscale).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Times A and B. Each is a |
required | |
b
|
Times A and B. Each is a |
required | |
window_sizes
|
Sequence[int]
|
FAD-MS observation scales (GTB: 7, 13, 27, 81, 243). |
(7, 13, 27, 81, 243)
|
n_threads
|
int
|
Threads (0 or |
0
|
Returns:
| Type | Description |
|---|---|
FADMultiscaleChangeResult
|
One transition table per observation scale and for the multiscale map. |
Examples:
>>> import landschaft as ls
>>> before = ls.load_raster(ls.example_data("input")) # 0 missing, 1 background, 2 forest
>>> after = before.copy()
>>> cut = after.values[600:700, 100:260]
>>> cut[cut == 2] = 1 # a clear-cut
>>> plantation = after.values[250:330, 420:520]
>>> plantation[plantation == 1] = 2 # a new plantation
>>> change = ls.fad_multiscale_change(before, after)
>>> list(change.scales)
[7, 13, 27, 81, 243, 'mscale']
>>> scale = change.scales[27]
>>> scale.changed, round(scale.frag_increase_pct, 1)
(3470, 54.7)
>>> change.averages.loc[27, ["fad_av_a", "fad_av_b"]].round(2).tolist()
[73.54, 74.01]
FADMultiscaleChangeResult
dataclass
¶
FADMultiscaleChangeResult(scales: dict[int | str, FADChangeScale], averages: DataFrame)
Bases: Saveable
Result of fad_multiscale_change().
scales maps every window size and "mscale" (the multiscale map) to
a FADChangeScale. averages holds GTB's FAD_AV (mean FAD of the
foreground) and AVCON (FAD_AV x foreground / non-missing area) per scale at
times A and B, and their differences.
summary
¶
Report of one scale, in the layout of GTB's FAD_change_<scale>.txt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scale
|
int | str
|
A window size or |
'mscale'
|
Returns:
| Type | Description |
|---|---|
str
|
|
FADChangeScale
dataclass
¶
FADChangeScale(
scale: int | str,
matrix: DataFrame,
percent: DataFrame,
fg_area_a: int,
fg_area_b: int,
forcom: int,
same_class: int,
changed: int,
frag_decrease_pct: float,
frag_increase_pct: float,
)
Bases: Saveable
FAD change A -> B at one observation scale (one GTB FAD_change_<scale> table).
Attributes:
| Name | Type | Description |
|---|---|---|
matrix |
DataFrame
|
7x7 pixel counts; rows are the class at time A ( |
percent |
DataFrame
|
The 6x6 foreground part of |
same_class, changed |
Foreground pixels (at both times) on / off the diagonal. |
|
frag_decrease_pct, frag_increase_pct |
% of the changed pixels above / below the diagonal, i.e. moving to a less / more fragmented class (NaN when nothing changed). |
Landscape mosaic change¶
landscape_mosaic_change
¶
landscape_mosaic_change(
a, b, window_size: int = 27, *, is_lm103: bool = False, threads: int = 0
) -> LMHeatmapChange
Change of the Landscape Mosaic heatmap from A to B (GTB Change -> Landscape Mosaic Heatmap).
a and b are LMHeatmap objects or maps accepted by
landscape_mosaic_heatmap() (with window_size / is_lm103).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Times A and B: |
required | |
b
|
Times A and B: |
required | |
window_size
|
int
|
As in |
27
|
is_lm103
|
int
|
As in |
27
|
threads
|
int
|
Threads (0: all cores but one). |
0
|
Returns:
| Type | Description |
|---|---|
LMHeatmapChange
|
|
Examples:
Agriculture (1) around the north-west corner becomes developed (3):
>>> import landschaft as ls
>>> clc = ls.load_raster(ls.example_data("clc"))
>>> before = ls.reclassify(clc, [(1, 12, 3), (12, 23, 1), (23, 35, 2)], others=0) # developed, agriculture, natural
>>> after = before.copy()
>>> corner = after.values[:600, :600]
>>> corner[corner == 1] = 3
>>> change = ls.landscape_mosaic_change(before, after, window_size=27)
>>> change.table.columns.tolist()
['percent_a', 'percent_b', 'change', 'recode_class_19']
>>> _ = change.to_csv("delta_heatmap.csv")
LMHeatmapChange
dataclass
¶
Bases: Saveable
Result of landscape_mosaic_change(): the heatmap difference B - A (GTB's delta_heatmap.csv).
Attributes:
| Name | Type | Description |
|---|---|---|
values |
ndarray
|
Change of the share [%] of every LM class, as |
a, b |
The heatmaps of the two times. |
triangle
property
¶
The change as GTB's 12 x 21 triangle grid (NaN outside the triangle).