pylineament 0.1.0__tar.gz → 0.1.2__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 epokus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylineament
3
+ Version: 0.1.2
4
+ Summary: Python Geological Lineament extraction package
5
+ Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/epokus/pylineament
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: affine==2.4.0
11
+ Requires-Dist: attrs==25.4.0
12
+ Requires-Dist: certifi==2025.10.5
13
+ Requires-Dist: click==8.3.0
14
+ Requires-Dist: click-plugins==1.1.1.2
15
+ Requires-Dist: cligj==0.7.2
16
+ Requires-Dist: colorama==0.4.6
17
+ Requires-Dist: fiona==1.10.1
18
+ Requires-Dist: imageio==2.37.0
19
+ Requires-Dist: joblib==1.5.2
20
+ Requires-Dist: lazy_loader==0.4
21
+ Requires-Dist: networkx==3.4.2
22
+ Requires-Dist: numpy==2.2.6
23
+ Requires-Dist: packaging==25.0
24
+ Requires-Dist: pandas==2.3.3
25
+ Requires-Dist: pillow==12.0.0
26
+ Requires-Dist: pyparsing==3.2.5
27
+ Requires-Dist: PyQt5==5.15.11
28
+ Requires-Dist: PyQt5-Qt5==5.15.2
29
+ Requires-Dist: PyQt5_sip==12.17.1
30
+ Requires-Dist: pyqtgraph==0.13.7
31
+ Requires-Dist: python-dateutil==2.9.0.post0
32
+ Requires-Dist: pytz==2025.2
33
+ Requires-Dist: rasterio==1.4.3
34
+ Requires-Dist: scikit-image==0.25.2
35
+ Requires-Dist: scikit-learn==1.7.2
36
+ Requires-Dist: scipy==1.15.3
37
+ Requires-Dist: six==1.17.0
38
+ Requires-Dist: threadpoolctl==3.6.0
39
+ Requires-Dist: tifffile==2025.5.10
40
+ Requires-Dist: tzdata==2025.2
41
+ Dynamic: license-file
42
+
43
+ # PyLineament
44
+
45
+ **PyLineament** is a Python-based, open-source toolkit for **automatic and regional-scale lineament extraction** from Digital Elevation Models (DEMs) and remote sensing imagery.
46
+
47
+ It is designed for geological and geomorphological analysis — providing a **fully automated, reproducible**, and **scalable** workflow for extracting, reducing, and mapping lineaments across local to regional scales.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install pylineament
55
+ ```
56
+ Or from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/epokus/pylineament.git
60
+ cd pylineament
61
+ pip install -e .
62
+ ```
63
+
64
+ ## Quick Start (Command Line)
65
+
66
+ Once installed, simply open your terminal or command prompt and run:
67
+
68
+ ```bash
69
+ pylineament
70
+ ```
71
+
72
+ ## Key Features
73
+ - Interactive UI — Run `pylineament` in the terminal to open the GUI.
74
+ - Automated Workflow — Full end-to-end lineament extraction.
75
+ - Customizable Parameters — Control edge detection thresholds, segment length, and merging distance.
76
+ - Multi-Resolution Support — Works with various DEM/image resolutions.
77
+ - Scalable — Efficient for large-area or regional-scale mapping.
78
+ - Reproducible — Transparent parameters and open-source implementation.
79
+
80
+ ## Core Functions
81
+ | Function | Description |
82
+ | ---------------------------- | ------------------------------------------------------------ |
83
+ | `read_raster()` | Reads and preprocesses a raster (DEM or image). |
84
+ | `extract_lineament_points()` | Detects lineament-like edge points using gradient filters. |
85
+ | `convert_points_to_line()` | Converts clustered edge points into connected line segments. |
86
+ | `reduce_lines()` | Simplifies and merges overlapping or redundant lineaments. |
87
+ | `hillshade()` | Generates a hillshade image for visualization. |
88
+ | `dem_to_line()` | Extracts lineaments directly from a DEM file. |
89
+ | `dem_to_shp()` | Full workflow: extract + merge + export to shapefile. |
90
+
91
+
92
+ ## Example Workflow
93
+ - Input your DEM or satellite image.
94
+ - Optionally apply hillshade() or downscale for efficiency.
95
+ - Extract edge points using extract_lineament_points().
96
+ - Convert points to lines and merge them with reduce_lines().
97
+ - Save as shapefile using dem_to_shp() or merge_lines_csv_to_shp().
98
+
99
+ ## example how to use this Library with CLI or python
100
+
101
+ ```python
102
+ from pylineament import dem_to_shp
103
+ dem_to_shp("data/srtm_sample.tif", shp_name= "lineamentsExtract")
104
+ ```
105
+ shp file will be saved in "lineamentsExtract" folder.
106
+ see more examples in example folder
107
+ ![PyLineament UI Preview](examples/lienament_extracted.png)
108
+
109
+
110
+ ## Parameters Overview
111
+ | Parameter | Description | Typical Range |
112
+ | ------------ | ------------------------------------- | ----------------- |
113
+ | `eps` | Edge detection sensitivity | 0.8 – 2.0 |
114
+ | `thresh` | Edge detection threshold | 20 – 80 |
115
+ | `z_multip` | Vertical exaggeration factor | 0.5 – 2.0 |
116
+ | `min_dist` | Minimum distance between merged lines | 5 – 20 pixels |
117
+ | `seg_len` | Minimum segment length | 5 – 20 pixels |
118
+ | `split_size` | DEM/image tile size for processing | 250 – 1000 pixels |
119
+
120
+
121
+
122
+ ## Why PyLineament?
123
+ Typical “automatic” lineament extraction tools are limited by:
124
+ - Fixed image resolution and poor scalability,
125
+ - Heavy preprocessing requirements,
126
+ - Loss of geological meaning across scales,
127
+ - Slow performance in large regions.
128
+
129
+ ## PyLineament addresses these by providing:
130
+ - Automated but parameter-controllable extraction,
131
+ - Multi-resolution and downscaling support,
132
+ - Robust reduction and merging algorithms,
133
+ - Compatibility with both small-area (detailed) and large-area datasets (regional mapping).
134
+
135
+ ## Citation
136
+ If you use PyLineament in your research, please cite:
137
+ Prasetya Kusumah, E. (2025). PyLineament: A Python Toolkit for Regional-Scale Lineament Extraction.
138
+ Version 1.0. https://github.com/epokus/pylineament
139
+
140
+ ## License
141
+ This project is licensed under the MIT License — see the LICENSE
142
+ file for details.
143
+
@@ -0,0 +1,101 @@
1
+ # PyLineament
2
+
3
+ **PyLineament** is a Python-based, open-source toolkit for **automatic and regional-scale lineament extraction** from Digital Elevation Models (DEMs) and remote sensing imagery.
4
+
5
+ It is designed for geological and geomorphological analysis — providing a **fully automated, reproducible**, and **scalable** workflow for extracting, reducing, and mapping lineaments across local to regional scales.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install pylineament
13
+ ```
14
+ Or from source:
15
+
16
+ ```bash
17
+ git clone https://github.com/epokus/pylineament.git
18
+ cd pylineament
19
+ pip install -e .
20
+ ```
21
+
22
+ ## Quick Start (Command Line)
23
+
24
+ Once installed, simply open your terminal or command prompt and run:
25
+
26
+ ```bash
27
+ pylineament
28
+ ```
29
+
30
+ ## Key Features
31
+ - Interactive UI — Run `pylineament` in the terminal to open the GUI.
32
+ - Automated Workflow — Full end-to-end lineament extraction.
33
+ - Customizable Parameters — Control edge detection thresholds, segment length, and merging distance.
34
+ - Multi-Resolution Support — Works with various DEM/image resolutions.
35
+ - Scalable — Efficient for large-area or regional-scale mapping.
36
+ - Reproducible — Transparent parameters and open-source implementation.
37
+
38
+ ## Core Functions
39
+ | Function | Description |
40
+ | ---------------------------- | ------------------------------------------------------------ |
41
+ | `read_raster()` | Reads and preprocesses a raster (DEM or image). |
42
+ | `extract_lineament_points()` | Detects lineament-like edge points using gradient filters. |
43
+ | `convert_points_to_line()` | Converts clustered edge points into connected line segments. |
44
+ | `reduce_lines()` | Simplifies and merges overlapping or redundant lineaments. |
45
+ | `hillshade()` | Generates a hillshade image for visualization. |
46
+ | `dem_to_line()` | Extracts lineaments directly from a DEM file. |
47
+ | `dem_to_shp()` | Full workflow: extract + merge + export to shapefile. |
48
+
49
+
50
+ ## Example Workflow
51
+ - Input your DEM or satellite image.
52
+ - Optionally apply hillshade() or downscale for efficiency.
53
+ - Extract edge points using extract_lineament_points().
54
+ - Convert points to lines and merge them with reduce_lines().
55
+ - Save as shapefile using dem_to_shp() or merge_lines_csv_to_shp().
56
+
57
+ ## example how to use this Library with CLI or python
58
+
59
+ ```python
60
+ from pylineament import dem_to_shp
61
+ dem_to_shp("data/srtm_sample.tif", shp_name= "lineamentsExtract")
62
+ ```
63
+ shp file will be saved in "lineamentsExtract" folder.
64
+ see more examples in example folder
65
+ ![PyLineament UI Preview](examples/lienament_extracted.png)
66
+
67
+
68
+ ## Parameters Overview
69
+ | Parameter | Description | Typical Range |
70
+ | ------------ | ------------------------------------- | ----------------- |
71
+ | `eps` | Edge detection sensitivity | 0.8 – 2.0 |
72
+ | `thresh` | Edge detection threshold | 20 – 80 |
73
+ | `z_multip` | Vertical exaggeration factor | 0.5 – 2.0 |
74
+ | `min_dist` | Minimum distance between merged lines | 5 – 20 pixels |
75
+ | `seg_len` | Minimum segment length | 5 – 20 pixels |
76
+ | `split_size` | DEM/image tile size for processing | 250 – 1000 pixels |
77
+
78
+
79
+
80
+ ## Why PyLineament?
81
+ Typical “automatic” lineament extraction tools are limited by:
82
+ - Fixed image resolution and poor scalability,
83
+ - Heavy preprocessing requirements,
84
+ - Loss of geological meaning across scales,
85
+ - Slow performance in large regions.
86
+
87
+ ## PyLineament addresses these by providing:
88
+ - Automated but parameter-controllable extraction,
89
+ - Multi-resolution and downscaling support,
90
+ - Robust reduction and merging algorithms,
91
+ - Compatibility with both small-area (detailed) and large-area datasets (regional mapping).
92
+
93
+ ## Citation
94
+ If you use PyLineament in your research, please cite:
95
+ Prasetya Kusumah, E. (2025). PyLineament: A Python Toolkit for Regional-Scale Lineament Extraction.
96
+ Version 1.0. https://github.com/epokus/pylineament
97
+
98
+ ## License
99
+ This project is licensed under the MIT License — see the LICENSE
100
+ file for details.
101
+
@@ -0,0 +1,30 @@
1
+ from .pylineament import (
2
+ read_raster,
3
+ reduce_lines,
4
+ extract_lineament_points,
5
+ convert_points_to_line,
6
+ hillshade,
7
+ image_splitting,
8
+ merge_lines_csv_to_shp,
9
+ merge_single_csv_to_shp,
10
+ raster_resize,
11
+ dem_to_line,
12
+ dem_to_shp,
13
+ dem_to_shp_small
14
+
15
+ )
16
+
17
+ __all__ = [
18
+ "read_raster",
19
+ "reduce_lines",
20
+ "extract_lineament_points",
21
+ "convert_points_to_line",
22
+ "hillshade",
23
+ "image_splitting",
24
+ "merge_lines_csv_to_shp",
25
+ "merge_single_csv_to_shp",
26
+ "raster_resize",
27
+ "dem_to_line",
28
+ "dem_to_shp",
29
+ "dem_to_shp_small"
30
+ ]
@@ -9,7 +9,27 @@ import rasterio
9
9
 
10
10
 
11
11
  def read_raster(im_path, split_size=500):
12
-
12
+ """
13
+ Read and process a raster image (e.g., DEM).
14
+
15
+ Parameters
16
+ ----------
17
+ im_path : str
18
+ Path to the input raster file.
19
+ split_size : int, optional
20
+ Block size for splitting large rasters during processing (default is 500).
21
+
22
+ Returns
23
+ -------
24
+ imageSplitting : pandas.DataFrame
25
+ image subset coordinate and array slicing.
26
+ dem : numpy.ndarray
27
+ Digital Elevation Model array.
28
+ extent : tuple
29
+ Spatial extent of the raster (left, bottom, right, yop).
30
+ crs_espg : int
31
+ EPSG code of the raster’s coordinate reference system.
32
+ """
13
33
  img_format = os.path.split(im_path)[-1].split('.')[-1]
14
34
 
15
35
  im_path = os.path.abspath(im_path)
@@ -64,21 +84,48 @@ def read_raster(im_path, split_size=500):
64
84
  resYs = [resY]*len(grids)
65
85
  szs = [sz]*len(grids)
66
86
 
67
- df = pd.DataFrame(zip(ns, lefts, tops, resXs, resYs, szs), columns=['ns', 'lefts', 'tops', 'resXs', 'resYs', 'szs'])
87
+ imageSplitting = pd.DataFrame(zip(ns, lefts, tops, resXs, resYs, szs), columns=['ns', 'lefts', 'tops', 'resXs', 'resYs', 'szs'])
68
88
 
69
- df[['L', 'R', 'B', 'T']] = grids
89
+ imageSplitting[['L', 'R', 'B', 'T']] = grids
70
90
 
71
- df['left_bound'] = df['lefts'] + df['L']*df['resXs']
72
- df['bottom_bound'] = df['tops'] - df['T']*df['resYs']
73
- df['right_bound'] = df['left_bound'] + abs(df['L'] - df['R'])*df['resXs']
74
- df['top_bound'] = df['bottom_bound'] + abs(df['T'] - df['B'])*df['resYs']
91
+ imageSplitting['left_bound'] = imageSplitting['lefts'] + imageSplitting['L']*imageSplitting['resXs']
92
+ imageSplitting['bottom_bound'] = imageSplitting['tops'] - imageSplitting['T']*imageSplitting['resYs']
93
+ imageSplitting['right_bound'] = imageSplitting['left_bound'] + abs(imageSplitting['L'] - imageSplitting['R'])*imageSplitting['resXs']
94
+ imageSplitting['top_bound'] = imageSplitting['bottom_bound'] + abs(imageSplitting['T'] - imageSplitting['B'])*imageSplitting['resYs']
75
95
 
76
96
  dem = im
77
97
 
78
- return df, dem, extent,crs_espg
98
+ return imageSplitting, dem, extent,crs_espg
79
99
 
80
100
 
81
101
  def reduce_lines(lines, extent, dem_shape, min_dist=10, seg_len=10):
102
+
103
+ """
104
+ Simplify and merge extracted line segments to reduce redundancy.
105
+ Parameters
106
+ ----------
107
+ lines : dataframes
108
+ Extracted line segments.
109
+ extent : tuple
110
+ Raster extent used for spatial reference L B R T.
111
+ dem_shape : tuple
112
+ Shape of the DEM (rows, columns).
113
+ min_dist : float, optional
114
+ Minimum allowed distance between lines before merging (default is 10).
115
+ seg_len : float, optional
116
+ Minimum segment length to retain (default is 10).
117
+
118
+ Returns
119
+ -------
120
+ initial_lines : list
121
+ Original extracted lines.
122
+ broken_lines_ : list
123
+ Split or fragmented lines before reduction.
124
+ reduced_lines : list
125
+ Final reduced set of merged lineaments.
126
+ """
127
+
128
+
82
129
  # frag lines
83
130
 
84
131
  d = np.sqrt((lines['max_x'] - lines['min_x'])**2 + (lines['max_y'] - lines['min_y'])**2)
@@ -112,21 +159,21 @@ def reduce_lines(lines, extent, dem_shape, min_dist=10, seg_len=10):
112
159
  df_[['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'dr_dx', 'coef', 'intercept']] = azi, clust, xmin, xmax, ymin, ymax, dr_dx, coef, intercept
113
160
  container.append(df_)
114
161
 
115
- broken_lines = pd.concat(container)
162
+ initial_lines = pd.concat(container)
116
163
 
117
- if len(broken_lines)>10:
164
+ if len(initial_lines)>10:
118
165
 
119
166
  from sklearn.neighbors import KDTree
120
167
 
121
- tree= KDTree(broken_lines[['xs_a', 'xs_b', 'ys_a', 'ys_b']])
122
- dist, idxs = tree.query(broken_lines[['xs_a', 'xs_b', 'ys_a', 'ys_b']], k=10, return_distance=True)
168
+ tree= KDTree(initial_lines[['xs_a', 'xs_b', 'ys_a', 'ys_b']])
169
+ dist, idxs = tree.query(initial_lines[['xs_a', 'xs_b', 'ys_a', 'ys_b']], k=10, return_distance=True)
123
170
  idxs = np.where(dist>min_dist,dist.shape[0]+1,idxs)
124
171
 
125
172
  keepit = np.unique(np.sort(idxs, axis=1)[:,0])
126
- broken_lines_ = broken_lines.iloc[keepit]
173
+ broken_lines_ = initial_lines.iloc[keepit]
127
174
  # broken_lines_ = broken_lines.iloc[keepit].drop_duplicates(['azi', 'clust'])
128
175
  else:
129
- broken_lines_ = broken_lines
176
+ broken_lines_ = initial_lines
130
177
 
131
178
 
132
179
  container = []
@@ -145,28 +192,52 @@ def reduce_lines(lines, extent, dem_shape, min_dist=10, seg_len=10):
145
192
  deg = -np.degrees(np.arctan((max_y_ - min_y_) / (max_x_ - min_x_)))+90
146
193
  container.append([quad, group, min_x_, max_x_, min_y_, max_y_, L, deg])
147
194
 
148
- broken_lines__ = pd.DataFrame(container, columns=['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg'])
195
+ reduced_lines = pd.DataFrame(container, columns=['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg'])
149
196
 
150
197
  left, right, top, bot = extent
151
198
 
152
- broken_lines__['min_x_'] = broken_lines__['min_x']
153
- broken_lines__['max_x_'] = broken_lines__['max_x']
199
+ reduced_lines['min_x_'] = reduced_lines['min_x']
200
+ reduced_lines['max_x_'] = reduced_lines['max_x']
154
201
 
155
- broken_lines__['min_y_'] = broken_lines__['min_y']
156
- broken_lines__['max_y_'] = broken_lines__['max_y']
202
+ reduced_lines['min_y_'] = reduced_lines['min_y']
203
+ reduced_lines['max_y_'] = reduced_lines['max_y']
157
204
 
158
205
 
159
- broken_lines__['min_x'] = (broken_lines__['min_x']/dem_shape[1])*(right - left) + left
160
- broken_lines__['max_x'] = (broken_lines__['max_x']/dem_shape[1])*(right - left) + left
206
+ reduced_lines['min_x'] = (reduced_lines['min_x']/dem_shape[1])*(right - left) + left
207
+ reduced_lines['max_x'] = (reduced_lines['max_x']/dem_shape[1])*(right - left) + left
161
208
 
162
- broken_lines__['min_y'] = -(broken_lines__['min_y']/dem_shape[0])*(bot - top) + bot
163
- broken_lines__['max_y'] = -(broken_lines__['max_y']/dem_shape[0])*(bot - top) + bot
164
- broken_lines__['length'] = (broken_lines__['max_x'] - broken_lines__['min_x'])**2 + (broken_lines__['max_y'] - broken_lines__['min_y'])**2
209
+ reduced_lines['min_y'] = -(reduced_lines['min_y']/dem_shape[0])*(bot - top) + bot
210
+ reduced_lines['max_y'] = -(reduced_lines['max_y']/dem_shape[0])*(bot - top) + bot
211
+ reduced_lines['length'] = np.sqrt((reduced_lines['max_x'] - reduced_lines['min_x'])**2 + (reduced_lines['max_y'] - reduced_lines['min_y'])**2)
165
212
 
166
- return broken_lines, broken_lines_, broken_lines__
213
+ return initial_lines, broken_lines_, reduced_lines
167
214
 
168
215
 
169
216
  def extract_lineament_points(dem, eps=1.2, thresh=40,z_multip=1):
217
+ """
218
+ Detect edge points representing potential lineaments from a DEM.
219
+
220
+ Parameters
221
+ ----------
222
+ dem : numpy.ndarray
223
+ Input Digital Elevation Model.
224
+ eps : float, optional
225
+ Edge detection sensitivity parameter (default is 1.2).
226
+ thresh : float, optional
227
+ Threshold for edge classification (default is 40).
228
+ z_multip : float, optional
229
+ Vertical exaggeration factor for slope computation (default is 1).
230
+
231
+ Returns
232
+ -------
233
+ container : list
234
+ Container of extracted lineament points.
235
+ im_prewitt : numpy.ndarray
236
+ Image after applying Prewitt edge filter.
237
+ im_prewitt_clip : numpy.ndarray
238
+ Thresholded edge image highlighting lineament points.
239
+ """
240
+
170
241
  from sklearn.cluster import DBSCAN
171
242
  # import rasterio
172
243
  from skimage.filters import prewitt
@@ -227,6 +298,21 @@ def extract_lineament_points(dem, eps=1.2, thresh=40,z_multip=1):
227
298
 
228
299
 
229
300
  def convert_points_to_line(container):
301
+
302
+ """
303
+ Convert sets of edge points into connected line segments.
304
+
305
+ Parameters
306
+ ----------
307
+ container : list
308
+ Extracted lineament points grouped by proximity.
309
+
310
+ Returns
311
+ -------
312
+ lines : dataFrame
313
+ Line geometries derived from point clusters.
314
+ """
315
+
230
316
  from sklearn.linear_model import LinearRegression
231
317
 
232
318
  if len(container)>0:
@@ -264,6 +350,26 @@ def convert_points_to_line(container):
264
350
 
265
351
 
266
352
  def hillshade(array, azimuth=315, angle_altitude=45, z_multip=1):
353
+ """
354
+ Generate a hillshade image from a DEM for enhanced visualization.
355
+
356
+ Parameters
357
+ ----------
358
+ array : numpy.ndarray
359
+ DEM array.
360
+ azimuth : float, optional
361
+ Illumination azimuth in degrees (default is 315).
362
+ angle_altitude : float, optional
363
+ Illumination altitude angle in degrees (default is 45).
364
+ z_multip : float, optional
365
+ Vertical exaggeration multiplier (default is 1).
366
+
367
+ Returns
368
+ -------
369
+ hs : numpy.ndarray
370
+ Hillshaded representation of the DEM.
371
+ """
372
+
267
373
  array = array*z_multip
268
374
  azimuth = 360.0 - azimuth # Convert azimuth to geographic convention
269
375
  x, y = np.gradient(array)
@@ -273,11 +379,33 @@ def hillshade(array, azimuth=315, angle_altitude=45, z_multip=1):
273
379
  alt_rad = angle_altitude * np.pi / 180. # Convert altitude to radians
274
380
 
275
381
  shaded = np.sin(alt_rad) * np.sin(slope) + np.cos(alt_rad) * np.cos(slope) * np.cos((azm_rad - np.pi / 2.) - aspect)
382
+ hs = 255 * (shaded + 1) / 2 # Scale to 0-255
383
+
384
+
276
385
 
277
- return 255 * (shaded + 1) / 2 # Scale to 0-255
386
+ return hs
278
387
 
279
388
 
280
389
  def image_splitting(im_path='srtm/srtm_java.tif', split_size=500, tempfolder='temp'):
390
+
391
+ """
392
+ Split a large image or DEM into smaller tiles for efficient processing.
393
+
394
+ Parameters
395
+ ----------
396
+ im_path : str, optional
397
+ Path to the input raster file (default is 'srtm/srtm_java.tif').
398
+ split_size : int, optional
399
+ Tile size in pixels (default is 500).
400
+ tempfolder : str, optional
401
+ Temporary folder for saving image tiles (default is 'temp').
402
+
403
+ Returns
404
+ -------
405
+ imageBoundaries : dataFrame
406
+ List of boundaries or filenames for generated image tiles.
407
+ """
408
+
281
409
  import pandas as pd
282
410
  import numpy as np
283
411
  import rasterio
@@ -332,14 +460,14 @@ def image_splitting(im_path='srtm/srtm_java.tif', split_size=500, tempfolder='te
332
460
  resYs = [resY]*len(grids)
333
461
  szs = [sz]*len(grids)
334
462
 
335
- df = pd.DataFrame(zip(ns, target_folders, lefts, tops, resXs, resYs, szs), columns=['ns', 'target_folders', 'lefts', 'tops', 'resXs', 'resYs', 'szs'])
463
+ imageBoundaries = pd.DataFrame(zip(ns, target_folders, lefts, tops, resXs, resYs, szs), columns=['ns', 'target_folders', 'lefts', 'tops', 'resXs', 'resYs', 'szs'])
336
464
 
337
- df[['L', 'R', 'B', 'T']] = grids
465
+ imageBoundaries[['L', 'R', 'B', 'T']] = grids
338
466
 
339
- for n, tempfolder, left, top, resX, resY, sz, L, R, B, T in df.values:
467
+ for n, tempfolder, left, top, resX, resY, sz, L, R, B, T in imageBoundaries.values:
340
468
  l = left + L*resX
341
469
  # b = top - T*resY - sz*resY
342
- b = -top - T*resY
470
+ b = top - T*resY
343
471
 
344
472
  transform = rasterio.Affine.translation(l - resX / 2, b - resY / 2) * rasterio.Affine.scale(resX, resY)
345
473
 
@@ -360,26 +488,43 @@ def image_splitting(im_path='srtm/srtm_java.tif', split_size=500, tempfolder='te
360
488
  new_dataset.write(Z, 1)
361
489
  new_dataset.close()
362
490
 
363
- df['left_bound'] = df['lefts'] + df['L']*df['resXs']
364
- df['bottom_bound'] = df['tops'] - df['T']*df['resYs']
365
- df['right_bound'] = df['left_bound'] + abs(df['L'] - df['R'])*df['resXs']
366
- df['top_bound'] = df['bottom_bound'] + abs(df['T'] - df['B'])*df['resYs']
491
+ imageBoundaries['left_bound'] = imageBoundaries['lefts'] + imageBoundaries['L']*imageBoundaries['resXs']
492
+ imageBoundaries['bottom_bound'] = imageBoundaries['tops'] - imageBoundaries['T']*imageBoundaries['resYs']
493
+ imageBoundaries['right_bound'] = imageBoundaries['left_bound'] + abs(imageBoundaries['L'] - imageBoundaries['R'])*imageBoundaries['resXs']
494
+ imageBoundaries['top_bound'] = imageBoundaries['bottom_bound'] + abs(imageBoundaries['T'] - imageBoundaries['B'])*imageBoundaries['resYs']
367
495
 
368
- return df
496
+ return imageBoundaries
369
497
 
370
498
 
371
499
  def merge_lines_csv_to_shp(tempfolder, shppath, save_to_file):
372
-
500
+ """
501
+ Merge multiple lineament CSV outputs into a single shapefile.
502
+
503
+ Parameters
504
+ ----------
505
+ tempfolder : str
506
+ Directory containing intermediate CSV line data.
507
+ shppath : str
508
+ Path to the base shapefile used for spatial reference.
509
+ save_to_file : str
510
+ Output shapefile path.
511
+
512
+ Returns
513
+ -------
514
+ lines : Dataframe
515
+ Combined lineament data as a Dataframe.
516
+ """
517
+
373
518
  flist = glob.glob(f'{tempfolder}/*.csv')
374
519
 
375
- dfs = [pd.read_csv(f) for f in flist]
520
+ all_lines = [pd.read_csv(f) for f in flist]
376
521
 
377
- df = pd.concat(dfs).reset_index(drop=True)
378
- df = df[['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg', 'length', 'crs']]
522
+ lines = pd.concat(all_lines).reset_index(drop=True)
523
+ lines = lines[['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg', 'length', 'crs']]
379
524
 
380
525
  if save_to_file==True:
381
526
 
382
- crs_espg = df['crs'].values[0]
527
+ crs_espg = lines['crs'].values[0]
383
528
 
384
529
  if type(crs_espg) != int:
385
530
  crs_espg = 4326
@@ -410,7 +555,7 @@ def merge_lines_csv_to_shp(tempfolder, shppath, save_to_file):
410
555
  lineShp = fiona.open(shppath, mode='w', driver='ESRI Shapefile',
411
556
  schema = schema, crs = crs_espg)
412
557
 
413
- for quad, group,min_x, max_x, min_y, max_y, L_px, deg, L_crs, crs in df.values:
558
+ for quad, group,min_x, max_x, min_y, max_y, L_px, deg, L_crs, crs in lines.values:
414
559
  g = ([(min_x, min_y), (max_x, max_y)])
415
560
  rowDict = {
416
561
  'geometry' : {'type':'LineString',
@@ -427,15 +572,34 @@ def merge_lines_csv_to_shp(tempfolder, shppath, save_to_file):
427
572
 
428
573
  lineShp.close()
429
574
 
430
- return df
575
+ return lines
431
576
 
432
577
 
433
578
  def merge_single_csv_to_shp(fname, shppath, save_to_file):
434
- df = pd.read_csv(fname)
579
+ """
580
+ Merge a single CSV lineament file into a shapefile.
581
+
582
+ Parameters
583
+ ----------
584
+ fname : str
585
+ Path to the CSV file containing lineament data.
586
+ shppath : str
587
+ Path to reference shapefile for coordinate system.
588
+ save_to_file : str
589
+ Output shapefile path.
435
590
 
436
- df = df[['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg', 'length', 'crs']]
591
+ Returns
592
+ -------
593
+ lines : dataFrame
594
+ Converted lineament data as shapefile.
595
+ """
437
596
 
438
- crs_espg = df['crs'].values[0]
597
+
598
+ lines = pd.read_csv(fname)
599
+
600
+ lines = lines[['quad', 'group', 'min_x', 'max_x', 'min_y', 'max_y', 'L', 'deg', 'length', 'crs']]
601
+
602
+ crs_espg = lines['crs'].values[0]
439
603
 
440
604
  if save_to_file == True:
441
605
 
@@ -454,7 +618,7 @@ def merge_single_csv_to_shp(fname, shppath, save_to_file):
454
618
  lineShp = fiona.open(shppath, mode='w', driver='ESRI Shapefile',
455
619
  schema = schema, crs = f"EPSG:{crs_espg}")
456
620
 
457
- for quad, group,min_x, max_x, min_y, max_y, L_px, deg, L_crs, crs in df.values:
621
+ for quad, group,min_x, max_x, min_y, max_y, L_px, deg, L_crs, crs in lines.values:
458
622
  g = ([(min_x, min_y), (max_x, max_y)])
459
623
  rowDict = {
460
624
  'geometry' : {'type':'LineString',
@@ -472,10 +636,25 @@ def merge_single_csv_to_shp(fname, shppath, save_to_file):
472
636
 
473
637
  lineShp.close()
474
638
 
475
- return df
639
+ return lines
476
640
 
477
641
 
478
642
  def raster_resize (dem, factor=1):
643
+ """
644
+ Resize or downscale a DEM array.
645
+
646
+ Parameters
647
+ ----------
648
+ dem : numpy.ndarray
649
+ Input DEM array.
650
+ factor : float, optional
651
+ Rescaling factor (e.g., 0.5 reduces size by half, default is 1).
652
+
653
+ Returns
654
+ -------
655
+ dem : numpy.ndarray
656
+ Rescaled DEM array.
657
+ """
479
658
 
480
659
  from skimage.transform import rescale
481
660
 
@@ -504,7 +683,48 @@ def dem_to_line(path,
504
683
  min_dist=10,
505
684
  seg_len=10,
506
685
  z_multip=1.0,
507
- downscale = 1.0):
686
+ downscale = 1.0,
687
+ save_csv = False):
688
+ """
689
+ Extract lineaments directly from a DEM and return line features.
690
+
691
+ Parameters
692
+ ----------
693
+ path : str
694
+ Path to input DEM file.
695
+ tempfolder : str, optional
696
+ Folder for temporary outputs (default is 'temp').
697
+ eps : float, optional
698
+ Edge detection sensitivity (default is 1.2).
699
+ thresh : float, optional
700
+ Edge threshold for filtering (default is 40).
701
+ min_dist : float, optional
702
+ Minimum distance between line segments for merging (default is 10).
703
+ seg_len : float, optional
704
+ Minimum segment length (default is 10).
705
+ z_multip : float, optional
706
+ Vertical exaggeration multiplier (default is 1.0).
707
+ downscale : float, optional
708
+ DEM downscaling factor (default is 1.0).
709
+ save_csv : bool, optional
710
+ Save intermediate line data to CSV (default is False).
711
+
712
+ Returns
713
+ -------
714
+ dem : numpy.ndarray
715
+ Processed DEM array.
716
+ extent : tuple
717
+ Raster extent (Left, Bottom, Right, Top).
718
+ lines : dataFrame
719
+ Extracted lineament features.
720
+ im_prewitt : numpy.ndarray
721
+ Prewitt-filtered edge image.
722
+ im_prewitt_clip : numpy.ndarray
723
+ Thresholded edge map.
724
+ container : list
725
+ Intermediate lineament point container.
726
+ """
727
+
508
728
 
509
729
  path = os.path.normpath(path)
510
730
 
@@ -522,10 +742,10 @@ def dem_to_line(path,
522
742
  if len(lines) > 0:
523
743
  _,_,lines = reduce_lines(lines, extent=extent, dem_shape=dem.shape, min_dist=min_dist, seg_len=seg_len)
524
744
  fname = path.split('\\')[-1].split('.')[0]
525
-
526
745
  # fname = path.replace('.tiff', '.csv')
527
746
  lines['crs'] = crs_espg
528
- lines.to_csv(f'{tempfolder}\\{fname}.csv', index=False)
747
+ if save_csv:
748
+ lines.to_csv(f'{tempfolder}\\{fname}.csv', index=False)
529
749
  return dem, extent, lines, im_prewitt, im_prewitt_clip, container
530
750
 
531
751
  else:
@@ -533,18 +753,55 @@ def dem_to_line(path,
533
753
  return dem, extent, lines, im_prewitt, im_prewitt_clip, container
534
754
 
535
755
 
536
- def dem_to_line_runner(im_path,
537
- tempfolder='temp',
538
- shpname='test',
539
- eps=1.2,
540
- thresh=40,
541
- min_dist=10,
542
- seg_len=10,
543
- split_size=500,
544
- z_multip=1,
545
- downscale = 1,
546
- save_to_file=True,
547
- keep_intermediate_file = True):
756
+ def dem_to_shp(im_path,
757
+ tempfolder='temp',
758
+ eps=1.2,
759
+ thresh=40,
760
+ min_dist=10,
761
+ seg_len=10,
762
+ split_size=500,
763
+ z_multip=1,
764
+ downscale = 1,
765
+ shp_name = None,
766
+ save_to_file=True,
767
+
768
+ keep_intermediate_file = False):
769
+ """
770
+ Extract lineaments from a (large) DEM and export results as a shapefile.
771
+ This will involve image splitting, and the use of parallel computing to achieve calculation efficiency.
772
+
773
+ Parameters
774
+ ----------
775
+ im_path : str
776
+ Path to input DEM.
777
+ tempfolder : str, optional
778
+ Folder for temporary files (default is 'temp').
779
+ eps : float, optional
780
+ Edge detection sensitivity (default is 1.2).
781
+ thresh : float, optional
782
+ Edge threshold value (default is 40).
783
+ min_dist : float, optional
784
+ Minimum distance between lines (default is 10).
785
+ seg_len : float, optional
786
+ Minimum line segment length (default is 10).
787
+ split_size : int, optional
788
+ DEM tile size for splitting (default is 500).
789
+ z_multip : float, optional
790
+ Vertical exaggeration factor (default is 1).
791
+ downscale : float, optional
792
+ Downscaling factor for DEM (default is 1).
793
+ shp_name : str, optional
794
+ Output shapefile name.
795
+ save_to_file : bool, optional
796
+ Whether to save shapefile to disk (default is True).
797
+ keep_intermediate_file : bool, optional
798
+ Retain temporary files (default is False).
799
+
800
+ Returns
801
+ -------
802
+ lines : dataFrame
803
+ Extracted lineament shapefile.
804
+ """
548
805
 
549
806
  im_path = os.path.normpath(im_path)
550
807
 
@@ -568,13 +825,20 @@ def dem_to_line_runner(im_path,
568
825
  [min_dist]*len(flist),
569
826
  [seg_len]*len(flist),
570
827
  [z_multip]*len(flist),
571
- [downscale]*len(flist))).values
828
+ [downscale]*len(flist),
829
+ [True]*len(flist))).values
572
830
 
573
831
  from joblib import Parallel, delayed
574
832
  Parallel(n_jobs=-1)(delayed(dem_to_line)(*c) for c in cases)
575
833
 
834
+ fname = im_path.split('\\')[-1].split('.')[0]
835
+ csv_name = f'{tempfolder}\\{fname}.csv'
836
+
837
+ if shp_name == None:
838
+ shp_name = f'{fname}'
839
+
576
840
  df = merge_lines_csv_to_shp(tempfolder=tempfolder,
577
- shppath=shpname,
841
+ shppath=shp_name,
578
842
  save_to_file=save_to_file)
579
843
 
580
844
 
@@ -590,17 +854,51 @@ def dem_to_line_runner(im_path,
590
854
  return df
591
855
 
592
856
 
593
- def dem_to_line_runner_small(im_path,
594
- tempfolder='temp',
595
- eps=1.2,
596
- thresh=40,
597
- min_dist=10,
598
- seg_len=10,
599
- z_multip=1,
600
- downscale=1,
601
- shp_name=None,
602
- save_to_file=True):
857
+ def dem_to_shp_small(im_path,
858
+ tempfolder='temp',
859
+ eps=1.2,
860
+ thresh=40,
861
+ min_dist=10,
862
+ seg_len=10,
863
+ z_multip=1,
864
+ downscale=1,
865
+ shp_name=None,
866
+ save_to_file=True):
603
867
 
868
+ """
869
+ Extract lineaments from a (small) DEM and export results as a shapefile.
870
+ The image will not splited, and be run as single chunk. This process only use single core,
871
+ therefore calculation for large dataset will took very long time.
872
+
873
+ Parameters
874
+ ----------
875
+ im_path : str
876
+ Path to the input DEM file.
877
+ tempfolder : str, optional
878
+ Folder for temporary outputs (default is 'temp').
879
+ eps : float, optional
880
+ Edge detection sensitivity (default is 1.2).
881
+ thresh : float, optional
882
+ Edge threshold value (default is 40).
883
+ min_dist : float, optional
884
+ Minimum distance between line segments (default is 10).
885
+ seg_len : float, optional
886
+ Minimum segment length (default is 10).
887
+ z_multip : float, optional
888
+ Vertical exaggeration multiplier (default is 1).
889
+ downscale : float, optional
890
+ DEM downscaling factor (default is 1).
891
+ shp_name : str, optional
892
+ Output shapefile name.
893
+ save_to_file : bool, optional
894
+ Whether to save shapefile to disk (default is True).
895
+
896
+ Returns
897
+ -------
898
+ lines : dataFrame
899
+ Extracted lineament features.
900
+ """
901
+
604
902
  im_path = os.path.normpath(im_path)
605
903
  if os.path.isdir(tempfolder):
606
904
  'delete content'
@@ -618,7 +916,8 @@ def dem_to_line_runner_small(im_path,
618
916
  min_dist=min_dist,
619
917
  seg_len=seg_len,
620
918
  z_multip=z_multip,
621
- downscale=downscale)
919
+ downscale=downscale,
920
+ save_csv=True)
622
921
 
623
922
  fname = im_path.split('\\')[-1].split('.')[0]
624
923
  csv_name = f'{tempfolder}\\{fname}.csv'
@@ -637,5 +936,5 @@ def dem_to_line_runner_small(im_path,
637
936
  os.removedirs(tempfolder)
638
937
 
639
938
 
640
- return dem, extent, lines, im_prewitt, im_prewitt_clip, container
939
+ return lines
641
940
 
@@ -1,34 +1,39 @@
1
1
  import sys
2
2
  import numpy as np
3
3
  import pyqtgraph as pg
4
- from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
5
- QPushButton, QFileDialog,
6
- QTabWidget, QTextEdit,
7
- QHBoxLayout, QSlider, QLabel,
8
- QSpacerItem, QSizePolicy, QComboBox,
9
- QLineEdit, QCheckBox, QGridLayout, QProgressBar, QSplitter )
10
-
11
- from PyQt5.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject
4
+ from PyQt5.QtWidgets import (QApplication,
5
+ QMainWindow,
6
+ QWidget,
7
+ QVBoxLayout,
8
+ QPushButton,
9
+ QFileDialog,
10
+ QTabWidget,
11
+ QHBoxLayout,
12
+ QSlider,
13
+ QLabel,
14
+ QComboBox,
15
+ QCheckBox,
16
+ QSplitter )
17
+
18
+ from PyQt5.QtCore import QThreadPool, QRunnable
12
19
 
13
20
 
14
21
  from skimage.transform import rescale
22
+
15
23
  from PyQt5.QtCore import Qt
24
+
16
25
  import numpy as np
17
26
  import pandas as pd
18
- from pylineament import (dem_to_line, read_raster,
27
+ from pylineament import (dem_to_line,
28
+ read_raster,
19
29
  extract_lineament_points,
20
30
  convert_points_to_line,
21
- dem_to_line_runner,
22
- dem_to_line_runner_small,
23
- reduce_lines,
24
- image_splitting,
31
+ reduce_lines,
25
32
  merge_lines_csv_to_shp,
26
- merge_single_csv_to_shp,
27
33
  hillshade)
28
34
  import os
29
35
 
30
36
 
31
-
32
37
  class ImageSplitterParallel(QRunnable):
33
38
  def __init__(self, i, crs, im, transform ,temp_folder):
34
39
  super().__init__()
@@ -64,10 +69,10 @@ class ImageSplitterParallel(QRunnable):
64
69
 
65
70
  new_dataset.write(Z, 1)
66
71
  new_dataset.close()
67
-
68
72
 
69
73
 
70
74
  class MainWindow(QMainWindow):
75
+
71
76
  def __init__(self):
72
77
  super().__init__()
73
78
  self.threadpool = QThreadPool.globalInstance()
@@ -266,8 +271,19 @@ class MainWindow(QMainWindow):
266
271
  def loadImageAndPreview(self):
267
272
  sz = self.subsetSideSlider.value() * self.subsetSideSlider.step
268
273
 
274
+
269
275
  self.regions, self.dem, self.extent, self.crs_espg = read_raster(self.file_name, split_size=sz)
270
276
 
277
+ imgFormat = self.file_name.split('.')[-1]
278
+ if (imgFormat == 'jpg') or (imgFormat == 'jpeg'):
279
+ from PIL import Image
280
+ i = Image.open( r"C:\Users\user\Downloads\WhatsApp Image 2025-09-23 at 16.34.01.jpeg" )
281
+ self.orgImg = np.transpose(np.array(i),( 1,0,2))[:,::-1]
282
+
283
+ else:
284
+
285
+ self.orgImg = self.dem[::-1].T
286
+
271
287
  L,R,B,T = self.extent
272
288
 
273
289
  if (L == 0) & (T==0):
@@ -275,7 +291,9 @@ class MainWindow(QMainWindow):
275
291
 
276
292
 
277
293
  W, H = abs(L-R), abs(B-T)
278
- img = pg.ImageItem(self.dem[::-1].T)
294
+ # img = pg.ImageItem(self.dem[::-1].T)
295
+ img = pg.ImageItem(self.orgImg)
296
+
279
297
  img.setRect(L,B,W,H)
280
298
 
281
299
  self.previewBigImg.clear()
@@ -757,10 +775,14 @@ class MainWindow(QMainWindow):
757
775
  os.removedirs(tempfolder)
758
776
 
759
777
 
760
-
761
- if __name__ == "__main__":
778
+ def main():
762
779
  app = QApplication(sys.argv)
763
780
  window = MainWindow()
764
781
  window.show()
765
782
  sys.exit(app.exec_())
766
783
 
784
+
785
+ if __name__ == "__main__":
786
+ main()
787
+
788
+
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylineament
3
+ Version: 0.1.2
4
+ Summary: Python Geological Lineament extraction package
5
+ Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/epokus/pylineament
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: affine==2.4.0
11
+ Requires-Dist: attrs==25.4.0
12
+ Requires-Dist: certifi==2025.10.5
13
+ Requires-Dist: click==8.3.0
14
+ Requires-Dist: click-plugins==1.1.1.2
15
+ Requires-Dist: cligj==0.7.2
16
+ Requires-Dist: colorama==0.4.6
17
+ Requires-Dist: fiona==1.10.1
18
+ Requires-Dist: imageio==2.37.0
19
+ Requires-Dist: joblib==1.5.2
20
+ Requires-Dist: lazy_loader==0.4
21
+ Requires-Dist: networkx==3.4.2
22
+ Requires-Dist: numpy==2.2.6
23
+ Requires-Dist: packaging==25.0
24
+ Requires-Dist: pandas==2.3.3
25
+ Requires-Dist: pillow==12.0.0
26
+ Requires-Dist: pyparsing==3.2.5
27
+ Requires-Dist: PyQt5==5.15.11
28
+ Requires-Dist: PyQt5-Qt5==5.15.2
29
+ Requires-Dist: PyQt5_sip==12.17.1
30
+ Requires-Dist: pyqtgraph==0.13.7
31
+ Requires-Dist: python-dateutil==2.9.0.post0
32
+ Requires-Dist: pytz==2025.2
33
+ Requires-Dist: rasterio==1.4.3
34
+ Requires-Dist: scikit-image==0.25.2
35
+ Requires-Dist: scikit-learn==1.7.2
36
+ Requires-Dist: scipy==1.15.3
37
+ Requires-Dist: six==1.17.0
38
+ Requires-Dist: threadpoolctl==3.6.0
39
+ Requires-Dist: tifffile==2025.5.10
40
+ Requires-Dist: tzdata==2025.2
41
+ Dynamic: license-file
42
+
43
+ # PyLineament
44
+
45
+ **PyLineament** is a Python-based, open-source toolkit for **automatic and regional-scale lineament extraction** from Digital Elevation Models (DEMs) and remote sensing imagery.
46
+
47
+ It is designed for geological and geomorphological analysis — providing a **fully automated, reproducible**, and **scalable** workflow for extracting, reducing, and mapping lineaments across local to regional scales.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install pylineament
55
+ ```
56
+ Or from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/epokus/pylineament.git
60
+ cd pylineament
61
+ pip install -e .
62
+ ```
63
+
64
+ ## Quick Start (Command Line)
65
+
66
+ Once installed, simply open your terminal or command prompt and run:
67
+
68
+ ```bash
69
+ pylineament
70
+ ```
71
+
72
+ ## Key Features
73
+ - Interactive UI — Run `pylineament` in the terminal to open the GUI.
74
+ - Automated Workflow — Full end-to-end lineament extraction.
75
+ - Customizable Parameters — Control edge detection thresholds, segment length, and merging distance.
76
+ - Multi-Resolution Support — Works with various DEM/image resolutions.
77
+ - Scalable — Efficient for large-area or regional-scale mapping.
78
+ - Reproducible — Transparent parameters and open-source implementation.
79
+
80
+ ## Core Functions
81
+ | Function | Description |
82
+ | ---------------------------- | ------------------------------------------------------------ |
83
+ | `read_raster()` | Reads and preprocesses a raster (DEM or image). |
84
+ | `extract_lineament_points()` | Detects lineament-like edge points using gradient filters. |
85
+ | `convert_points_to_line()` | Converts clustered edge points into connected line segments. |
86
+ | `reduce_lines()` | Simplifies and merges overlapping or redundant lineaments. |
87
+ | `hillshade()` | Generates a hillshade image for visualization. |
88
+ | `dem_to_line()` | Extracts lineaments directly from a DEM file. |
89
+ | `dem_to_shp()` | Full workflow: extract + merge + export to shapefile. |
90
+
91
+
92
+ ## Example Workflow
93
+ - Input your DEM or satellite image.
94
+ - Optionally apply hillshade() or downscale for efficiency.
95
+ - Extract edge points using extract_lineament_points().
96
+ - Convert points to lines and merge them with reduce_lines().
97
+ - Save as shapefile using dem_to_shp() or merge_lines_csv_to_shp().
98
+
99
+ ## example how to use this Library with CLI or python
100
+
101
+ ```python
102
+ from pylineament import dem_to_shp
103
+ dem_to_shp("data/srtm_sample.tif", shp_name= "lineamentsExtract")
104
+ ```
105
+ shp file will be saved in "lineamentsExtract" folder.
106
+ see more examples in example folder
107
+ ![PyLineament UI Preview](examples/lienament_extracted.png)
108
+
109
+
110
+ ## Parameters Overview
111
+ | Parameter | Description | Typical Range |
112
+ | ------------ | ------------------------------------- | ----------------- |
113
+ | `eps` | Edge detection sensitivity | 0.8 – 2.0 |
114
+ | `thresh` | Edge detection threshold | 20 – 80 |
115
+ | `z_multip` | Vertical exaggeration factor | 0.5 – 2.0 |
116
+ | `min_dist` | Minimum distance between merged lines | 5 – 20 pixels |
117
+ | `seg_len` | Minimum segment length | 5 – 20 pixels |
118
+ | `split_size` | DEM/image tile size for processing | 250 – 1000 pixels |
119
+
120
+
121
+
122
+ ## Why PyLineament?
123
+ Typical “automatic” lineament extraction tools are limited by:
124
+ - Fixed image resolution and poor scalability,
125
+ - Heavy preprocessing requirements,
126
+ - Loss of geological meaning across scales,
127
+ - Slow performance in large regions.
128
+
129
+ ## PyLineament addresses these by providing:
130
+ - Automated but parameter-controllable extraction,
131
+ - Multi-resolution and downscaling support,
132
+ - Robust reduction and merging algorithms,
133
+ - Compatibility with both small-area (detailed) and large-area datasets (regional mapping).
134
+
135
+ ## Citation
136
+ If you use PyLineament in your research, please cite:
137
+ Prasetya Kusumah, E. (2025). PyLineament: A Python Toolkit for Regional-Scale Lineament Extraction.
138
+ Version 1.0. https://github.com/epokus/pylineament
139
+
140
+ ## License
141
+ This project is licensed under the MIT License — see the LICENSE
142
+ file for details.
143
+
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  pylineament/__init__.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pylineament = pylineament.pylineament_ui:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pylineament"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Python Geological Lineament extraction package"
9
9
  readme = "README.md"
10
10
  authors = [{ name="Epo Kusumah", email="epo.pk@universiaspertamina.ac.id" }]
@@ -47,6 +47,7 @@ dependencies = [
47
47
  Homepage = "https://github.com/epokus/pylineament"
48
48
 
49
49
  [project.scripts]
50
- lineUI = "lineui.pylineament_ui:MainWindow"
50
+ pylineament = "pylineament.pylineament_ui:main"
51
+
51
52
 
52
53
 
@@ -1,39 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pylineament
3
- Version: 0.1.0
4
- Summary: Python Geological Lineament extraction package
5
- Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/epokus/pylineament
8
- Description-Content-Type: text/markdown
9
- Requires-Dist: affine==2.4.0
10
- Requires-Dist: attrs==25.4.0
11
- Requires-Dist: certifi==2025.10.5
12
- Requires-Dist: click==8.3.0
13
- Requires-Dist: click-plugins==1.1.1.2
14
- Requires-Dist: cligj==0.7.2
15
- Requires-Dist: colorama==0.4.6
16
- Requires-Dist: fiona==1.10.1
17
- Requires-Dist: imageio==2.37.0
18
- Requires-Dist: joblib==1.5.2
19
- Requires-Dist: lazy_loader==0.4
20
- Requires-Dist: networkx==3.4.2
21
- Requires-Dist: numpy==2.2.6
22
- Requires-Dist: packaging==25.0
23
- Requires-Dist: pandas==2.3.3
24
- Requires-Dist: pillow==12.0.0
25
- Requires-Dist: pyparsing==3.2.5
26
- Requires-Dist: PyQt5==5.15.11
27
- Requires-Dist: PyQt5-Qt5==5.15.2
28
- Requires-Dist: PyQt5_sip==12.17.1
29
- Requires-Dist: pyqtgraph==0.13.7
30
- Requires-Dist: python-dateutil==2.9.0.post0
31
- Requires-Dist: pytz==2025.2
32
- Requires-Dist: rasterio==1.4.3
33
- Requires-Dist: scikit-image==0.25.2
34
- Requires-Dist: scikit-learn==1.7.2
35
- Requires-Dist: scipy==1.15.3
36
- Requires-Dist: six==1.17.0
37
- Requires-Dist: threadpoolctl==3.6.0
38
- Requires-Dist: tifffile==2025.5.10
39
- Requires-Dist: tzdata==2025.2
File without changes
File without changes
@@ -1,39 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pylineament
3
- Version: 0.1.0
4
- Summary: Python Geological Lineament extraction package
5
- Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/epokus/pylineament
8
- Description-Content-Type: text/markdown
9
- Requires-Dist: affine==2.4.0
10
- Requires-Dist: attrs==25.4.0
11
- Requires-Dist: certifi==2025.10.5
12
- Requires-Dist: click==8.3.0
13
- Requires-Dist: click-plugins==1.1.1.2
14
- Requires-Dist: cligj==0.7.2
15
- Requires-Dist: colorama==0.4.6
16
- Requires-Dist: fiona==1.10.1
17
- Requires-Dist: imageio==2.37.0
18
- Requires-Dist: joblib==1.5.2
19
- Requires-Dist: lazy_loader==0.4
20
- Requires-Dist: networkx==3.4.2
21
- Requires-Dist: numpy==2.2.6
22
- Requires-Dist: packaging==25.0
23
- Requires-Dist: pandas==2.3.3
24
- Requires-Dist: pillow==12.0.0
25
- Requires-Dist: pyparsing==3.2.5
26
- Requires-Dist: PyQt5==5.15.11
27
- Requires-Dist: PyQt5-Qt5==5.15.2
28
- Requires-Dist: PyQt5_sip==12.17.1
29
- Requires-Dist: pyqtgraph==0.13.7
30
- Requires-Dist: python-dateutil==2.9.0.post0
31
- Requires-Dist: pytz==2025.2
32
- Requires-Dist: rasterio==1.4.3
33
- Requires-Dist: scikit-image==0.25.2
34
- Requires-Dist: scikit-learn==1.7.2
35
- Requires-Dist: scipy==1.15.3
36
- Requires-Dist: six==1.17.0
37
- Requires-Dist: threadpoolctl==3.6.0
38
- Requires-Dist: tifffile==2025.5.10
39
- Requires-Dist: tzdata==2025.2
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- lineUI = lineui.pylineament_ui:MainWindow
File without changes