pysmrf 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software in your research, please cite both the foundational paper and this library."
3
+ title: "PySMRF: High-Performance Parallel Simple Morphological Filter for LiDAR Point Clouds"
4
+ version: 1.0.0
5
+ date-released: 2026-09-11
6
+ license: MIT
7
+ repository-code: "https://github.com/thomaspingel/smrf"
8
+ identifiers:
9
+ - type: doi
10
+ value: 10.1016/j.isprsjprs.2012.12.002
11
+ description: "Foundational SMRF algorithm paper in ISPRS Journal of Photogrammetry and Remote Sensing"
12
+ authors:
13
+ - family-names: Pingel
14
+ given-names: Thomas J.
15
+ email: thomas.pingel@gmail.com
16
+ orcid: "https://orcid.org/0000-0002-3971-8573"
17
+ - family-names: Clarke
18
+ given-names: Keith C.
19
+ - family-names: McBride
20
+ given-names: William A.
21
+ preferred-citation:
22
+ type: article
23
+ title: "An improved simple morphological filter for producing ground surfaces from LiDAR point clouds"
24
+ authors:
25
+ - family-names: Pingel
26
+ given-names: Thomas J.
27
+ - family-names: Clarke
28
+ given-names: Keith C.
29
+ - family-names: McBride
30
+ given-names: William A.
31
+ journal: "ISPRS Journal of Photogrammetry and Remote Sensing"
32
+ volume: 77
33
+ start: 21
34
+ end: 30
35
+ year: 2013
36
+ doi: 10.1016/j.isprsjprs.2012.12.002
37
+ url: "https://doi.org/10.1016/j.isprsjprs.2012.12.002"
pysmrf-1.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2013-2021 Thomas Pingel
4
+ Copyright (c) 2026 Aman Ranjan
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CITATION.cff
4
+ include pyproject.toml
5
+ include setup.py
6
+ recursive-include src/pysmrf *.py
7
+ recursive-include tests *.py
8
+ global-exclude __pycache__
9
+ global-exclude *.py[cod]
pysmrf-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: pysmrf
3
+ Version: 1.0.0
4
+ Summary: High-performance parallel Simple Morphological Filter (SMRF) for LiDAR ground classification and DEM generation citing Pingel et al. (2013)
5
+ Home-page: https://doi.org/10.1016/j.isprsjprs.2012.12.002
6
+ Author: Aman Ranjan, Thomas Pingel (original algorithm)
7
+ Author-email: Aman Ranjan <er.amanranjan@gmail.com>
8
+ Maintainer-email: Aman Ranjan <er.amanranjan@gmail.com>
9
+ License-Expression: MIT
10
+ Project-URL: Homepage, https://github.com/thomaspingel/smrf
11
+ Project-URL: Paper, https://doi.org/10.1016/j.isprsjprs.2012.12.002
12
+ Project-URL: Repository, https://github.com/thomaspingel/smrf
13
+ Keywords: lidar,point-cloud,gis,dem,dtm,morphological-filter,ground-classification,parallel,remote-sensing
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Topic :: Scientific/Engineering :: GIS
18
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Operating System :: OS Independent
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: numpy>=1.22.0
30
+ Requires-Dist: scipy>=1.8.0
31
+ Requires-Dist: rasterio>=1.3.0
32
+ Requires-Dist: pandas>=1.4.0
33
+ Requires-Dist: click>=8.0.0
34
+ Requires-Dist: laspy[laszip,lazrs]>=2.6.1
35
+ Provides-Extra: viz
36
+ Requires-Dist: matplotlib>=3.5.0; extra == "viz"
37
+ Provides-Extra: all
38
+ Requires-Dist: lazrs>=0.5.0; extra == "all"
39
+ Requires-Dist: matplotlib>=3.5.0; extra == "all"
40
+ Requires-Dist: scikit-image>=0.19.0; extra == "all"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
43
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
44
+ Requires-Dist: black>=23.0.0; extra == "dev"
45
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
46
+ Requires-Dist: lazrs>=0.5.0; extra == "dev"
47
+ Dynamic: author
48
+ Dynamic: home-page
49
+ Dynamic: license-file
50
+ Dynamic: requires-python
51
+
52
+ # PySMRF: High-Performance Parallel Simple Morphological Filter for LiDAR
53
+
54
+ [![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org/)
55
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
56
+ [![DOI](https://img.shields.io/badge/DOI-10.1016%2Fj.isprsjprs.2012.12.002-blue.svg)](https://doi.org/10.1016/j.isprsjprs.2012.12.002)
57
+
58
+ **PySMRF** is a clean, modern, and highly optimized Python library implementing the **Simple Morphological Filter (SMRF)** algorithm for binary ground/object classification of airborne LiDAR point clouds and bare-earth Digital Elevation Model (DEM / DTM) generation.
59
+
60
+ This project is a complete rewrite of the original [`smrf`](https://github.com/thomaspingel/smrf) codebase by Thomas Pingel, re-engineered for **maximum performance**, **built-in multi-threaded/multi-process parallelism**, **native LAS/LAZ I/O**, and **production GIS pipelines**.
61
+
62
+ ---
63
+
64
+ ## 📚 Academic Citation
65
+
66
+ If you use PySMRF in your academic research, geospatial workflows, or publications, **please cite the foundational paper**:
67
+
68
+ > **Pingel, T. J., Clarke, K. C., & McBride, W. A. (2013).**
69
+ > *An improved simple morphological filter for producing ground surfaces from LiDAR point clouds.*
70
+ > **ISPRS Journal of Photogrammetry and Remote Sensing**, 77, 21–30.
71
+ > [https://doi.org/10.1016/j.isprsjprs.2012.12.002](https://doi.org/10.1016/j.isprsjprs.2012.12.002)
72
+
73
+ ### BibTeX
74
+ ```bibtex
75
+ @article{pingel2013smrf,
76
+ title = {An improved simple morphological filter for producing ground surfaces from LiDAR point clouds},
77
+ author = {Pingel, Thomas J. and Clarke, Keith C. and McBride, William A.},
78
+ journal = {ISPRS Journal of Photogrammetry and Remote Sensing},
79
+ volume = {77},
80
+ pages = {21--30},
81
+ year = {2013},
82
+ publisher = {Elsevier},
83
+ doi = {10.1016/j.isprsjprs.2012.12.002},
84
+ url = {https://doi.org/10.1016/j.isprsjprs.2012.12.002}
85
+ }
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 🚀 Key Improvements in PySMRF
91
+
92
+ | Feature | Original `smrf` | **PySMRF (Rewrite)** |
93
+ | :--- | :--- | :--- |
94
+ | **Grid DEM Creation** | Pandas `groupby.min()` (slow, heavy RAM) | **Vectorized C-level `np.minimum.at` (10–50x faster)** |
95
+ | **Parallelism** | None (single-threaded CPU only) | **Built-in multi-threading (`workers=-1`) with tiled spatial domain decomposition** |
96
+ | **Morphology Engine** | Sequential `skimage.morphology.opening` | **High-speed `scipy.ndimage.grey_opening` with cached disk footprints & parallel tiling** |
97
+ | **Point Interpolation** | `RectBivariateSpline` on full arrays | **Parallel chunked evaluation & optional bilinear `order=1`** |
98
+ | **LAS / LAZ Support** | Pure-Python custom reader (no LAZ) | **Unified `laspy` + `lazrs` integration with metadata/attribute preservation** |
99
+ | **Void Inpainting** | Uncached spring LSQR matrix | **Optimized sparse Laplacian network, FDA, and fast nearest/IDW solvers** |
100
+ | **API Design** | Monolithic script | **Modern functional API, OOP pipeline (`SMRF`), dataclass returns, & legacy compatibility** |
101
+ | **CLI Tool** | None | **Full-featured CLI: `pysmrf classify`, `pysmrf dem`, `pysmrf batch`, `pysmrf cite`** |
102
+
103
+ ---
104
+
105
+ ## 📦 Installation
106
+
107
+ ### From Source
108
+ ```bash
109
+ git clone https://github.com/thomaspingel/smrf.git
110
+ cd smrf
111
+ pip install -e .
112
+ ```
113
+
114
+ ### With Optional Dependencies
115
+ ```bash
116
+ # With LAZ compression support (lazrs)
117
+ pip install -e ".[laz]"
118
+
119
+ # With visualization support (matplotlib)
120
+ pip install -e ".[viz]"
121
+
122
+ # Complete installation with all optional features
123
+ pip install -e ".[all]"
124
+ ```
125
+
126
+ ---
127
+
128
+ ## ⚡ Quickstart
129
+
130
+ ### 1. Modern Functional API (`classify`)
131
+ ```python
132
+ import pysmrf
133
+
134
+ # Classify points directly from arrays or file path
135
+ result = pysmrf.classify(
136
+ "survey.las",
137
+ cellsize=1.0,
138
+ windows=[1, 2, 3, 5, 10],
139
+ slope_threshold=0.15,
140
+ elevation_threshold=0.5,
141
+ elevation_scaler=1.25,
142
+ workers=-1, # Use all available CPU cores!
143
+ )
144
+
145
+ print(f"Total points: {result.num_points:,}")
146
+ print(f"Ground points: {result.ground_count:,} ({result.ground_percentage:.1f}%)")
147
+ print(f"Non-ground objects: {result.object_count:,}")
148
+
149
+ # Export bare-earth DTM raster and classified LAS
150
+ result.save_dem("bare_earth_dtm.tif")
151
+ ```
152
+
153
+ ### 2. Object-Oriented Pipeline (`SMRF`)
154
+ ```python
155
+ from pysmrf import SMRF
156
+
157
+ # Configure reusable filter
158
+ filter = SMRF(
159
+ cellsize=1.0,
160
+ windows=10,
161
+ slope_threshold=0.15,
162
+ elevation_threshold=0.4,
163
+ workers=8,
164
+ )
165
+
166
+ # Process file directly, saving classified output and DTM GeoTIFF
167
+ result = filter.classify_file(
168
+ input_path="input.las",
169
+ output_path="classified.las",
170
+ save_dem_path="dtm.tif",
171
+ )
172
+ ```
173
+
174
+ ### 3. 100% Backward-Compatible Legacy Interface (`smrf.smrf`)
175
+ Existing code written for `smrf` works out-of-the-box:
176
+
177
+ ```python
178
+ import pysmrf as smrf
179
+
180
+ # Exact legacy signature and return values
181
+ dtm, transform, object_cells, is_object_point = smrf.smrf(
182
+ x, y, z,
183
+ cellsize=1.0,
184
+ windows=5,
185
+ slope_threshold=0.15,
186
+ elevation_threshold=0.5,
187
+ elevation_scaler=1.25,
188
+ )
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 💻 Command-Line Interface (CLI)
194
+
195
+ PySMRF provides an intuitive CLI for processing files directly from the terminal:
196
+
197
+ ### Classify a LAS/LAZ file
198
+ ```bash
199
+ pysmrf classify input.las \
200
+ --output classified.las \
201
+ --dem bare_earth.tif \
202
+ --cellsize 1.0 \
203
+ --windows 1,2,3,5,10,15 \
204
+ --slope 0.15 \
205
+ --workers -1
206
+ ```
207
+
208
+ ### Generate a Bare-Earth DEM
209
+ ```bash
210
+ pysmrf dem survey.laz -o dtm.tif --cellsize 1.0 --bin-type min
211
+ ```
212
+
213
+ ### Batch Process Multiple Files in Parallel
214
+ ```bash
215
+ pysmrf batch "raw_tiles/*.las" -o classified_tiles/ --workers 8 --cellsize 1.0
216
+ ```
217
+
218
+ ### Inspect LiDAR File Metadata
219
+ ```bash
220
+ pysmrf info input.las
221
+ ```
222
+
223
+ ### Print Formal Citation
224
+ ```bash
225
+ pysmrf cite
226
+ ```
227
+
228
+ ---
229
+
230
+ ## ⚙️ Parameter Guide
231
+
232
+ | Parameter | Type | Default | Description |
233
+ | :--- | :--- | :--- | :--- |
234
+ | `cellsize` | `float` | `1.0` | Spatial resolution of the provisional grid in real-world map units (m or ft). |
235
+ | `windows` | `int` or `list` | `5` | Structuring element radii sequence. E.g., `5` evaluates `[1, 2, 3, 4, 5]`. Use larger radii (`15–30`) to remove large buildings; smaller radii (`2–5`) for small trees. |
236
+ | `slope_threshold` | `float` | `0.15` | Slope tolerance ($dz/dx$). `0.15` corresponds to 15% slope. Use higher values (`0.25–0.40`) in steep mountainous terrain. |
237
+ | `elevation_threshold`| `float` | `0.5` | Elevation distance tolerance from provisional DTM. Points within this distance are classified as ground. |
238
+ | `elevation_scaler` | `float` | `1.25` | Slope scaling factor: $\text{tol} = \text{elev\_thresh} + (\text{scaler} \times \text{slope})$. Set to `0` to disable slope scaling. |
239
+ | `low_filter_slope` | `float` | `5.0` | Threshold (500%) for identifying low outlier points from inverted surface. |
240
+ | `low_outlier_fill` | `bool` | `False` | Whether to inpaint and remove low outlier grid cells prior to progressive filtering. |
241
+ | `workers` | `int` | `1` | Number of worker threads. `-1` uses all available CPU cores. |
242
+ | `inpaint_method` | `str` | `'spring'`| Void inpainting algorithm: `'spring'` (Pingel 2013), `'fda'`, or `'nearest'`. |
243
+ | `interp_method` | `str` | `'spline'`| Surface interpolation method: `'spline'` (bivariate cubic spline) or `'linear'` (fast bilinear). |
244
+
245
+ ---
246
+
247
+ ## 🧪 Testing
248
+
249
+ Run the full pytest suite:
250
+
251
+ ```bash
252
+ pytest -v tests/
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 📄 License
258
+
259
+ This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
260
+
261
+ Original Algorithm & Prototype Copyright (c) 2013–2021 Thomas Pingel.
262
+ PySMRF Modern Rewrite Copyright (c) 2026 Aman Ranjan.
263
+
264
+ ---
265
+
266
+ ## 👤 Author & Maintainer
267
+
268
+ - **Aman Ranjan** ([er.amanranjan@gmail.com](mailto:er.amanranjan@gmail.com))
269
+ - **Thomas J. Pingel** (Original Algorithm & Prototype)
pysmrf-1.0.0/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # PySMRF: High-Performance Parallel Simple Morphological Filter for LiDAR
2
+
3
+ [![Python Version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![DOI](https://img.shields.io/badge/DOI-10.1016%2Fj.isprsjprs.2012.12.002-blue.svg)](https://doi.org/10.1016/j.isprsjprs.2012.12.002)
6
+
7
+ **PySMRF** is a clean, modern, and highly optimized Python library implementing the **Simple Morphological Filter (SMRF)** algorithm for binary ground/object classification of airborne LiDAR point clouds and bare-earth Digital Elevation Model (DEM / DTM) generation.
8
+
9
+ This project is a complete rewrite of the original [`smrf`](https://github.com/thomaspingel/smrf) codebase by Thomas Pingel, re-engineered for **maximum performance**, **built-in multi-threaded/multi-process parallelism**, **native LAS/LAZ I/O**, and **production GIS pipelines**.
10
+
11
+ ---
12
+
13
+ ## 📚 Academic Citation
14
+
15
+ If you use PySMRF in your academic research, geospatial workflows, or publications, **please cite the foundational paper**:
16
+
17
+ > **Pingel, T. J., Clarke, K. C., & McBride, W. A. (2013).**
18
+ > *An improved simple morphological filter for producing ground surfaces from LiDAR point clouds.*
19
+ > **ISPRS Journal of Photogrammetry and Remote Sensing**, 77, 21–30.
20
+ > [https://doi.org/10.1016/j.isprsjprs.2012.12.002](https://doi.org/10.1016/j.isprsjprs.2012.12.002)
21
+
22
+ ### BibTeX
23
+ ```bibtex
24
+ @article{pingel2013smrf,
25
+ title = {An improved simple morphological filter for producing ground surfaces from LiDAR point clouds},
26
+ author = {Pingel, Thomas J. and Clarke, Keith C. and McBride, William A.},
27
+ journal = {ISPRS Journal of Photogrammetry and Remote Sensing},
28
+ volume = {77},
29
+ pages = {21--30},
30
+ year = {2013},
31
+ publisher = {Elsevier},
32
+ doi = {10.1016/j.isprsjprs.2012.12.002},
33
+ url = {https://doi.org/10.1016/j.isprsjprs.2012.12.002}
34
+ }
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 🚀 Key Improvements in PySMRF
40
+
41
+ | Feature | Original `smrf` | **PySMRF (Rewrite)** |
42
+ | :--- | :--- | :--- |
43
+ | **Grid DEM Creation** | Pandas `groupby.min()` (slow, heavy RAM) | **Vectorized C-level `np.minimum.at` (10–50x faster)** |
44
+ | **Parallelism** | None (single-threaded CPU only) | **Built-in multi-threading (`workers=-1`) with tiled spatial domain decomposition** |
45
+ | **Morphology Engine** | Sequential `skimage.morphology.opening` | **High-speed `scipy.ndimage.grey_opening` with cached disk footprints & parallel tiling** |
46
+ | **Point Interpolation** | `RectBivariateSpline` on full arrays | **Parallel chunked evaluation & optional bilinear `order=1`** |
47
+ | **LAS / LAZ Support** | Pure-Python custom reader (no LAZ) | **Unified `laspy` + `lazrs` integration with metadata/attribute preservation** |
48
+ | **Void Inpainting** | Uncached spring LSQR matrix | **Optimized sparse Laplacian network, FDA, and fast nearest/IDW solvers** |
49
+ | **API Design** | Monolithic script | **Modern functional API, OOP pipeline (`SMRF`), dataclass returns, & legacy compatibility** |
50
+ | **CLI Tool** | None | **Full-featured CLI: `pysmrf classify`, `pysmrf dem`, `pysmrf batch`, `pysmrf cite`** |
51
+
52
+ ---
53
+
54
+ ## 📦 Installation
55
+
56
+ ### From Source
57
+ ```bash
58
+ git clone https://github.com/thomaspingel/smrf.git
59
+ cd smrf
60
+ pip install -e .
61
+ ```
62
+
63
+ ### With Optional Dependencies
64
+ ```bash
65
+ # With LAZ compression support (lazrs)
66
+ pip install -e ".[laz]"
67
+
68
+ # With visualization support (matplotlib)
69
+ pip install -e ".[viz]"
70
+
71
+ # Complete installation with all optional features
72
+ pip install -e ".[all]"
73
+ ```
74
+
75
+ ---
76
+
77
+ ## ⚡ Quickstart
78
+
79
+ ### 1. Modern Functional API (`classify`)
80
+ ```python
81
+ import pysmrf
82
+
83
+ # Classify points directly from arrays or file path
84
+ result = pysmrf.classify(
85
+ "survey.las",
86
+ cellsize=1.0,
87
+ windows=[1, 2, 3, 5, 10],
88
+ slope_threshold=0.15,
89
+ elevation_threshold=0.5,
90
+ elevation_scaler=1.25,
91
+ workers=-1, # Use all available CPU cores!
92
+ )
93
+
94
+ print(f"Total points: {result.num_points:,}")
95
+ print(f"Ground points: {result.ground_count:,} ({result.ground_percentage:.1f}%)")
96
+ print(f"Non-ground objects: {result.object_count:,}")
97
+
98
+ # Export bare-earth DTM raster and classified LAS
99
+ result.save_dem("bare_earth_dtm.tif")
100
+ ```
101
+
102
+ ### 2. Object-Oriented Pipeline (`SMRF`)
103
+ ```python
104
+ from pysmrf import SMRF
105
+
106
+ # Configure reusable filter
107
+ filter = SMRF(
108
+ cellsize=1.0,
109
+ windows=10,
110
+ slope_threshold=0.15,
111
+ elevation_threshold=0.4,
112
+ workers=8,
113
+ )
114
+
115
+ # Process file directly, saving classified output and DTM GeoTIFF
116
+ result = filter.classify_file(
117
+ input_path="input.las",
118
+ output_path="classified.las",
119
+ save_dem_path="dtm.tif",
120
+ )
121
+ ```
122
+
123
+ ### 3. 100% Backward-Compatible Legacy Interface (`smrf.smrf`)
124
+ Existing code written for `smrf` works out-of-the-box:
125
+
126
+ ```python
127
+ import pysmrf as smrf
128
+
129
+ # Exact legacy signature and return values
130
+ dtm, transform, object_cells, is_object_point = smrf.smrf(
131
+ x, y, z,
132
+ cellsize=1.0,
133
+ windows=5,
134
+ slope_threshold=0.15,
135
+ elevation_threshold=0.5,
136
+ elevation_scaler=1.25,
137
+ )
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 💻 Command-Line Interface (CLI)
143
+
144
+ PySMRF provides an intuitive CLI for processing files directly from the terminal:
145
+
146
+ ### Classify a LAS/LAZ file
147
+ ```bash
148
+ pysmrf classify input.las \
149
+ --output classified.las \
150
+ --dem bare_earth.tif \
151
+ --cellsize 1.0 \
152
+ --windows 1,2,3,5,10,15 \
153
+ --slope 0.15 \
154
+ --workers -1
155
+ ```
156
+
157
+ ### Generate a Bare-Earth DEM
158
+ ```bash
159
+ pysmrf dem survey.laz -o dtm.tif --cellsize 1.0 --bin-type min
160
+ ```
161
+
162
+ ### Batch Process Multiple Files in Parallel
163
+ ```bash
164
+ pysmrf batch "raw_tiles/*.las" -o classified_tiles/ --workers 8 --cellsize 1.0
165
+ ```
166
+
167
+ ### Inspect LiDAR File Metadata
168
+ ```bash
169
+ pysmrf info input.las
170
+ ```
171
+
172
+ ### Print Formal Citation
173
+ ```bash
174
+ pysmrf cite
175
+ ```
176
+
177
+ ---
178
+
179
+ ## ⚙️ Parameter Guide
180
+
181
+ | Parameter | Type | Default | Description |
182
+ | :--- | :--- | :--- | :--- |
183
+ | `cellsize` | `float` | `1.0` | Spatial resolution of the provisional grid in real-world map units (m or ft). |
184
+ | `windows` | `int` or `list` | `5` | Structuring element radii sequence. E.g., `5` evaluates `[1, 2, 3, 4, 5]`. Use larger radii (`15–30`) to remove large buildings; smaller radii (`2–5`) for small trees. |
185
+ | `slope_threshold` | `float` | `0.15` | Slope tolerance ($dz/dx$). `0.15` corresponds to 15% slope. Use higher values (`0.25–0.40`) in steep mountainous terrain. |
186
+ | `elevation_threshold`| `float` | `0.5` | Elevation distance tolerance from provisional DTM. Points within this distance are classified as ground. |
187
+ | `elevation_scaler` | `float` | `1.25` | Slope scaling factor: $\text{tol} = \text{elev\_thresh} + (\text{scaler} \times \text{slope})$. Set to `0` to disable slope scaling. |
188
+ | `low_filter_slope` | `float` | `5.0` | Threshold (500%) for identifying low outlier points from inverted surface. |
189
+ | `low_outlier_fill` | `bool` | `False` | Whether to inpaint and remove low outlier grid cells prior to progressive filtering. |
190
+ | `workers` | `int` | `1` | Number of worker threads. `-1` uses all available CPU cores. |
191
+ | `inpaint_method` | `str` | `'spring'`| Void inpainting algorithm: `'spring'` (Pingel 2013), `'fda'`, or `'nearest'`. |
192
+ | `interp_method` | `str` | `'spline'`| Surface interpolation method: `'spline'` (bivariate cubic spline) or `'linear'` (fast bilinear). |
193
+
194
+ ---
195
+
196
+ ## 🧪 Testing
197
+
198
+ Run the full pytest suite:
199
+
200
+ ```bash
201
+ pytest -v tests/
202
+ ```
203
+
204
+ ---
205
+
206
+ ## 📄 License
207
+
208
+ This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
209
+
210
+ Original Algorithm & Prototype Copyright (c) 2013–2021 Thomas Pingel.
211
+ PySMRF Modern Rewrite Copyright (c) 2026 Aman Ranjan.
212
+
213
+ ---
214
+
215
+ ## 👤 Author & Maintainer
216
+
217
+ - **Aman Ranjan** ([er.amanranjan@gmail.com](mailto:er.amanranjan@gmail.com))
218
+ - **Thomas J. Pingel** (Original Algorithm & Prototype)
@@ -0,0 +1,73 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pysmrf"
7
+ version = "1.0.0"
8
+ description = "High-performance parallel Simple Morphological Filter (SMRF) for LiDAR ground classification and DEM generation citing Pingel et al. (2013)"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "Aman Ranjan", email = "er.amanranjan@gmail.com" },
13
+ ]
14
+ maintainers = [
15
+ { name = "Aman Ranjan", email = "er.amanranjan@gmail.com" }
16
+ ]
17
+ keywords = ["lidar", "point-cloud", "gis", "dem", "dtm", "morphological-filter", "ground-classification", "parallel", "remote-sensing"]
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Intended Audience :: Science/Research",
21
+ "Intended Audience :: Developers",
22
+ "Topic :: Scientific/Engineering :: GIS",
23
+ "Topic :: Scientific/Engineering :: Image Processing",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.9",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Operating System :: OS Independent"
31
+ ]
32
+ requires-python = ">=3.9"
33
+ dependencies = [
34
+ "numpy>=1.22.0",
35
+ "scipy>=1.8.0",
36
+ "rasterio>=1.3.0",
37
+ "pandas>=1.4.0",
38
+ "click>=8.0.0",
39
+ "laspy[laszip,lazrs]>=2.6.1",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ viz = [
44
+ "matplotlib>=3.5.0"
45
+ ]
46
+ all = [
47
+ "lazrs>=0.5.0",
48
+ "matplotlib>=3.5.0",
49
+ "scikit-image>=0.19.0"
50
+ ]
51
+ dev = [
52
+ "pytest>=7.0.0",
53
+ "pytest-cov>=4.0.0",
54
+ "black>=23.0.0",
55
+ "ruff>=0.1.0",
56
+ "lazrs>=0.5.0"
57
+ ]
58
+
59
+ [project.urls]
60
+ Homepage = "https://github.com/thomaspingel/smrf"
61
+ Paper = "https://doi.org/10.1016/j.isprsjprs.2012.12.002"
62
+ Repository = "https://github.com/thomaspingel/smrf"
63
+
64
+ [project.scripts]
65
+ pysmrf = "pysmrf.cli:cli"
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+ include = ["pysmrf*"]
70
+
71
+ [tool.pytest.ini_options]
72
+ pythonpath = ["src", "tests"]
73
+ testpaths = ["tests"]
pysmrf-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+