Skip to content

Commit b6677d8

Browse files
committed
small notebook fixes
1 parent 91d6568 commit b6677d8

7 files changed

Lines changed: 1393 additions & 154 deletions

examples/user_guide/12_Flood_Analysis.ipynb

Lines changed: 395 additions & 33 deletions
Large diffs are not rendered by default.

examples/user_guide/13_Fire_Analysis.ipynb

Lines changed: 508 additions & 38 deletions
Large diffs are not rendered by default.

examples/user_guide/16_Diffusion.ipynb

Lines changed: 79 additions & 12 deletions
Large diffs are not rendered by default.

examples/user_guide/17_Morphological_Operators.ipynb

Lines changed: 97 additions & 13 deletions
Large diffs are not rendered by default.

examples/user_guide/18_Landform_Classification.ipynb

Lines changed: 83 additions & 13 deletions
Large diffs are not rendered by default.

examples/user_guide/25_GLCM_Texture.ipynb

Lines changed: 182 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,105 +3,252 @@
33
{
44
"cell_type": "markdown",
55
"id": "zvy4yyjkifj",
6-
"source": "# GLCM Texture Metrics\n\nGray-Level Co-occurrence Matrix (GLCM) texture features capture spatial patterns in raster data that spectral bands alone cannot distinguish. They were introduced by Haralick et al. (1973) and remain a standard tool in remote sensing classification, geological mapping, and medical imaging.\n\n`xrspatial.glcm_texture` computes six Haralick features over a sliding window:\n\n| Metric | What it measures |\n|---|---|\n| **contrast** | Intensity difference between neighboring pixels |\n| **dissimilarity** | Mean absolute gray-level difference |\n| **homogeneity** | Inverse difference moment (smooth vs. rough) |\n| **energy** | Sum of squared GLCM entries (uniformity) |\n| **correlation** | Linear dependency of gray levels |\n| **entropy** | Randomness of the co-occurrence distribution |",
7-
"metadata": {}
6+
"metadata": {},
7+
"source": [
8+
"# GLCM Texture Metrics\n",
9+
"\n",
10+
"Gray-Level Co-occurrence Matrix (GLCM) texture features capture spatial patterns in raster data that spectral bands alone cannot distinguish. They were introduced by Haralick et al. (1973) and remain a standard tool in remote sensing classification, geological mapping, and medical imaging.\n",
11+
"\n",
12+
"`xrspatial.glcm_texture` computes six Haralick features over a sliding window:\n",
13+
"\n",
14+
"| Metric | What it measures |\n",
15+
"|---|---|\n",
16+
"| **contrast** | Intensity difference between neighboring pixels |\n",
17+
"| **dissimilarity** | Mean absolute gray-level difference |\n",
18+
"| **homogeneity** | Inverse difference moment (smooth vs. rough) |\n",
19+
"| **energy** | Sum of squared GLCM entries (uniformity) |\n",
20+
"| **correlation** | Linear dependency of gray levels |\n",
21+
"| **entropy** | Randomness of the co-occurrence distribution |"
22+
]
823
},
924
{
1025
"cell_type": "code",
26+
"execution_count": null,
1127
"id": "3a06tk9sxuh",
12-
"source": "import numpy as np\nimport xarray as xr\nimport matplotlib.pyplot as plt\nfrom xrspatial import glcm_texture",
1328
"metadata": {},
14-
"execution_count": null,
15-
"outputs": []
29+
"outputs": [],
30+
"source": [
31+
"import numpy as np\n",
32+
"import xarray as xr\n",
33+
"import matplotlib.pyplot as plt\n",
34+
"from xrspatial import glcm_texture"
35+
]
1636
},
1737
{
1838
"cell_type": "markdown",
1939
"id": "cs9lld5daa",
20-
"source": "## Synthetic test raster\n\nWe'll build a 100x100 raster with four quadrants that have distinct textures: smooth, noisy, striped, and checkerboard. GLCM metrics should clearly separate these regions.",
21-
"metadata": {}
40+
"metadata": {},
41+
"source": [
42+
"## Synthetic test raster\n",
43+
"\n",
44+
"We'll build a 100x100 raster with four quadrants that have distinct textures: smooth, noisy, striped, and checkerboard. GLCM metrics should clearly separate these regions."
45+
]
2246
},
2347
{
2448
"cell_type": "code",
49+
"execution_count": null,
2550
"id": "mqtgqmsnvlp",
26-
"source": "# Build a 100x100 raster with four texture quadrants\nrng = np.random.default_rng(42)\ndata = np.zeros((100, 100), dtype=np.float64)\n\n# Top-left: smooth gradient\ndata[:50, :50] = np.linspace(0, 1, 50)[np.newaxis, :]\n\n# Top-right: random noise\ndata[:50, 50:] = rng.random((50, 50))\n\n# Bottom-left: vertical stripes\ndata[50:, :50] = np.tile([0.0, 1.0], 25)[np.newaxis, :]\n\n# Bottom-right: checkerboard\ncb = np.zeros((50, 50))\ncb[::2, 1::2] = 1.0\ncb[1::2, ::2] = 1.0\ndata[50:, 50:] = cb\n\nagg = xr.DataArray(data, dims=['y', 'x'])\n\nfig, ax = plt.subplots(figsize=(5, 5))\nax.imshow(data, cmap='gray')\nax.set_title('Input raster (4 texture regions)')\nplt.tight_layout()\nplt.show()",
2751
"metadata": {},
28-
"execution_count": null,
29-
"outputs": []
52+
"outputs": [],
53+
"source": [
54+
"# Build a 100x100 raster with four texture quadrants\n",
55+
"rng = np.random.default_rng(42)\n",
56+
"data = np.zeros((100, 100), dtype=np.float64)\n",
57+
"\n",
58+
"# Top-left: smooth gradient\n",
59+
"data[:50, :50] = np.linspace(0, 1, 50)[np.newaxis, :]\n",
60+
"\n",
61+
"# Top-right: random noise\n",
62+
"data[:50, 50:] = rng.random((50, 50))\n",
63+
"\n",
64+
"# Bottom-left: vertical stripes\n",
65+
"data[50:, :50] = np.tile([0.0, 1.0], 25)[np.newaxis, :]\n",
66+
"\n",
67+
"# Bottom-right: checkerboard\n",
68+
"cb = np.zeros((50, 50))\n",
69+
"cb[::2, 1::2] = 1.0\n",
70+
"cb[1::2, ::2] = 1.0\n",
71+
"data[50:, 50:] = cb\n",
72+
"\n",
73+
"agg = xr.DataArray(data, dims=['y', 'x'])\n",
74+
"\n",
75+
"fig, ax = plt.subplots(figsize=(5, 5))\n",
76+
"ax.imshow(data, cmap='gray')\n",
77+
"ax.set_title('Input raster (4 texture regions)')\n",
78+
"plt.tight_layout()\n",
79+
"plt.show()"
80+
]
3081
},
3182
{
3283
"cell_type": "markdown",
3384
"id": "5dh3jr9qbba",
34-
"source": "## Computing a single metric\n\nThe simplest usage: pass a single metric name and get a 2-D result back.",
35-
"metadata": {}
85+
"metadata": {},
86+
"source": [
87+
"## Computing a single metric\n",
88+
"\n",
89+
"The simplest usage: pass a single metric name and get a 2-D result back."
90+
]
3691
},
3792
{
3893
"cell_type": "code",
94+
"execution_count": null,
3995
"id": "2fgm4xz3uwj",
40-
"source": "contrast = glcm_texture(agg, metric='contrast', window_size=7, levels=64)\n\nfig, axes = plt.subplots(1, 2, figsize=(10, 4))\naxes[0].imshow(data, cmap='gray')\naxes[0].set_title('Input')\nim = axes[1].imshow(contrast.values, cmap='inferno')\naxes[1].set_title('GLCM Contrast')\nplt.colorbar(im, ax=axes[1], shrink=0.8)\nplt.tight_layout()\nplt.show()",
4196
"metadata": {},
42-
"execution_count": null,
43-
"outputs": []
97+
"outputs": [],
98+
"source": [
99+
"contrast = glcm_texture(agg, metric='contrast', window_size=7, levels=64)\n",
100+
"\n",
101+
"fig, axes = plt.subplots(1, 2, figsize=(10, 4))\n",
102+
"axes[0].imshow(data, cmap='gray')\n",
103+
"axes[0].set_title('Input')\n",
104+
"im = axes[1].imshow(contrast.values, cmap='inferno')\n",
105+
"axes[1].set_title('GLCM Contrast')\n",
106+
"plt.colorbar(im, ax=axes[1], shrink=0.8)\n",
107+
"plt.tight_layout()\n",
108+
"plt.show()"
109+
]
44110
},
45111
{
46112
"cell_type": "markdown",
47113
"id": "d6oq5gt2i6",
48-
"source": "## Computing multiple metrics at once\n\nPass a list of metric names to get a 3-D result with a leading `metric` dimension. This is more efficient than calling `glcm_texture` separately for each metric when using the numpy backend, since the GLCM is built once per window position.",
49-
"metadata": {}
114+
"metadata": {},
115+
"source": [
116+
"## Computing multiple metrics at once\n",
117+
"\n",
118+
"Pass a list of metric names to get a 3-D result with a leading `metric` dimension. This is more efficient than calling `glcm_texture` separately for each metric when using the numpy backend, since the GLCM is built once per window position."
119+
]
50120
},
51121
{
52122
"cell_type": "code",
123+
"execution_count": null,
53124
"id": "7ah4bm6v5ut",
54-
"source": "metrics = ['contrast', 'dissimilarity', 'homogeneity', 'energy', 'correlation', 'entropy']\ntextures = glcm_texture(agg, metric=metrics, window_size=7, levels=64)\n\nfig, axes = plt.subplots(2, 3, figsize=(14, 9))\nfor ax, name in zip(axes.flat, metrics):\n vals = textures.sel(metric=name).values\n im = ax.imshow(vals, cmap='viridis')\n ax.set_title(name.capitalize())\n plt.colorbar(im, ax=ax, shrink=0.7)\nplt.suptitle('All six GLCM texture metrics', fontsize=14, y=1.01)\nplt.tight_layout()\nplt.show()",
55125
"metadata": {},
56-
"execution_count": null,
57-
"outputs": []
126+
"outputs": [],
127+
"source": [
128+
"metrics = ['contrast', 'dissimilarity', 'homogeneity', 'energy', 'correlation', 'entropy']\n",
129+
"textures = glcm_texture(agg, metric=metrics, window_size=7, levels=64)\n",
130+
"\n",
131+
"fig, axes = plt.subplots(2, 3, figsize=(14, 9))\n",
132+
"for ax, name in zip(axes.flat, metrics):\n",
133+
" vals = textures.sel(metric=name).values\n",
134+
" im = ax.imshow(vals, cmap='viridis')\n",
135+
" ax.set_title(name.capitalize())\n",
136+
" plt.colorbar(im, ax=ax, shrink=0.7)\n",
137+
"plt.suptitle('All six GLCM texture metrics', fontsize=14, y=1.01)\n",
138+
"plt.tight_layout()\n",
139+
"plt.show()"
140+
]
58141
},
59142
{
60143
"cell_type": "markdown",
61144
"id": "hats5p5wfk5",
62-
"source": "## Effect of angle\n\nThe `angle` parameter controls the direction of pixel pairing:\n- `0` -- horizontal (right)\n- `45` -- upper-right diagonal\n- `90` -- vertical (up)\n- `135` -- upper-left diagonal\n- `None` (default) -- average over all four\n\nDirectional textures like stripes respond differently depending on the angle.",
63-
"metadata": {}
145+
"metadata": {},
146+
"source": [
147+
"## Effect of angle\n",
148+
"\n",
149+
"The `angle` parameter controls the direction of pixel pairing:\n",
150+
"- `0` -- horizontal (right)\n",
151+
"- `45` -- upper-right diagonal\n",
152+
"- `90` -- vertical (up)\n",
153+
"- `135` -- upper-left diagonal\n",
154+
"- `None` (default) -- average over all four\n",
155+
"\n",
156+
"Directional textures like stripes respond differently depending on the angle."
157+
]
64158
},
65159
{
66160
"cell_type": "code",
161+
"execution_count": null,
67162
"id": "nkzw9wmyxio",
68-
"source": "fig, axes = plt.subplots(1, 4, figsize=(16, 4))\nfor ax, ang in zip(axes, [0, 45, 90, 135]):\n result = glcm_texture(agg, metric='contrast', window_size=7,\n levels=64, angle=ang)\n im = ax.imshow(result.values, cmap='inferno')\n ax.set_title(f'Contrast (angle={ang})')\n plt.colorbar(im, ax=ax, shrink=0.7)\nplt.tight_layout()\nplt.show()",
69163
"metadata": {},
70-
"execution_count": null,
71-
"outputs": []
164+
"outputs": [],
165+
"source": [
166+
"fig, axes = plt.subplots(1, 4, figsize=(16, 4))\n",
167+
"for ax, ang in zip(axes, [0, 45, 90, 135]):\n",
168+
" result = glcm_texture(agg, metric='contrast', window_size=7,\n",
169+
" levels=64, angle=ang)\n",
170+
" im = ax.imshow(result.values, cmap='inferno')\n",
171+
" ax.set_title(f'Contrast (angle={ang})')\n",
172+
" plt.colorbar(im, ax=ax, shrink=0.7)\n",
173+
"plt.tight_layout()\n",
174+
"plt.show()"
175+
]
72176
},
73177
{
74178
"cell_type": "markdown",
75179
"id": "fbwobep1n9f",
76-
"source": "## Dask support\n\n`glcm_texture` works on chunked Dask arrays out of the box. The input is quantized globally (so gray-level mapping stays consistent across chunks), then each chunk computes GLCM features independently via `map_overlap`.",
77-
"metadata": {}
180+
"metadata": {},
181+
"source": [
182+
"## Dask support\n",
183+
"\n",
184+
"`glcm_texture` works on chunked Dask arrays out of the box. The input is quantized globally (so gray-level mapping stays consistent across chunks), then each chunk computes GLCM features independently via `map_overlap`."
185+
]
78186
},
79187
{
80188
"cell_type": "code",
189+
"execution_count": null,
81190
"id": "keurc3dmugs",
82-
"source": "import dask.array as da\n\ndask_agg = xr.DataArray(\n da.from_array(data, chunks=(50, 50)),\n dims=['y', 'x'],\n)\n\ndask_contrast = glcm_texture(dask_agg, metric='contrast',\n window_size=7, levels=64)\nprint(f'Result type: {type(dask_contrast.data)}')\nprint(f'Chunks: {dask_contrast.data.chunks}')\n\n# Verify it matches the numpy result\nnp.testing.assert_allclose(\n contrast.values, dask_contrast.values,\n rtol=1e-10, equal_nan=True,\n)\nprint('Dask result matches numpy result.')",
83191
"metadata": {},
84-
"execution_count": null,
85-
"outputs": []
192+
"outputs": [],
193+
"source": [
194+
"import dask.array as da\n",
195+
"\n",
196+
"dask_agg = xr.DataArray(\n",
197+
" da.from_array(data, chunks=(50, 50)),\n",
198+
" dims=['y', 'x'],\n",
199+
")\n",
200+
"\n",
201+
"dask_contrast = glcm_texture(dask_agg, metric='contrast',\n",
202+
" window_size=7, levels=64)\n",
203+
"print(f'Result type: {type(dask_contrast.data)}')\n",
204+
"print(f'Chunks: {dask_contrast.data.chunks}')\n",
205+
"\n",
206+
"# Verify it matches the numpy result\n",
207+
"np.testing.assert_allclose(\n",
208+
" contrast.values, dask_contrast.values,\n",
209+
" rtol=1e-10, equal_nan=True,\n",
210+
")\n",
211+
"print('Dask result matches numpy result.')"
212+
]
86213
},
87214
{
88215
"cell_type": "markdown",
89216
"id": "vda99az5wo",
90-
"source": "## Parameters reference\n\n| Parameter | Default | Description |\n|---|---|---|\n| `metric` | `'contrast'` | One metric name (str) or a list of names |\n| `window_size` | `7` | Side length of the sliding window (must be odd, >= 3) |\n| `levels` | `64` | Number of gray levels for quantization (2-256) |\n| `distance` | `1` | Pixel pair distance |\n| `angle` | `None` | 0, 45, 90, 135, or None (average all four) |\n\nLower `levels` values run faster but lose gray-level resolution. For most remote sensing work, 32-64 levels is a good balance.",
91-
"metadata": {}
217+
"metadata": {},
218+
"source": [
219+
"## Parameters reference\n",
220+
"\n",
221+
"| Parameter | Default | Description |\n",
222+
"|---|---|---|\n",
223+
"| `metric` | `'contrast'` | One metric name (str) or a list of names |\n",
224+
"| `window_size` | `7` | Side length of the sliding window (must be odd, >= 3) |\n",
225+
"| `levels` | `64` | Number of gray levels for quantization (2-256) |\n",
226+
"| `distance` | `1` | Pixel pair distance |\n",
227+
"| `angle` | `None` | 0, 45, 90, 135, or None (average all four) |\n",
228+
"\n",
229+
"Lower `levels` values run faster but lose gray-level resolution. For most remote sensing work, 32-64 levels is a good balance."
230+
]
92231
}
93232
],
94233
"metadata": {
95234
"kernelspec": {
96-
"display_name": "Python 3",
235+
"display_name": "Python 3 (ipykernel)",
97236
"language": "python",
98237
"name": "python3"
99238
},
100239
"language_info": {
240+
"codemirror_mode": {
241+
"name": "ipython",
242+
"version": 3
243+
},
244+
"file_extension": ".py",
245+
"mimetype": "text/x-python",
101246
"name": "python",
102-
"version": "3.10.0"
247+
"nbconvert_exporter": "python",
248+
"pygments_lexer": "ipython3",
249+
"version": "3.14.2"
103250
}
104251
},
105252
"nbformat": 4,
106253
"nbformat_minor": 5
107-
}
254+
}

examples/user_guide/28_Rasterize.ipynb

Lines changed: 49 additions & 10 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)