You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
"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": {}
8
+
},
9
+
{
10
+
"cell_type": "code",
11
+
"id": "3a06tk9sxuh",
12
+
"source": "import numpy as np\nimport xarray as xr\nimport matplotlib.pyplot as plt\nfrom xrspatial import glcm_texture",
13
+
"metadata": {},
14
+
"execution_count": null,
15
+
"outputs": []
16
+
},
17
+
{
18
+
"cell_type": "markdown",
19
+
"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.",
"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.",
"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": {}
64
+
},
65
+
{
66
+
"cell_type": "code",
67
+
"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()",
69
+
"metadata": {},
70
+
"execution_count": null,
71
+
"outputs": []
72
+
},
73
+
{
74
+
"cell_type": "markdown",
75
+
"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": {}
78
+
},
79
+
{
80
+
"cell_type": "code",
81
+
"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.')",
83
+
"metadata": {},
84
+
"execution_count": null,
85
+
"outputs": []
86
+
},
87
+
{
88
+
"cell_type": "markdown",
89
+
"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.",
0 commit comments