pylineament 0.1.0__tar.gz → 0.1.1__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.

Potentially problematic release.


This version of pylineament might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylineament
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python Geological Lineament extraction package
5
5
  Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
6
  License-Expression: MIT
@@ -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
+ keep_intermediate_file = False,
768
+ n_job = -1):
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
- Parallel(n_jobs=-1)(delayed(dem_to_line)(*c) for c in cases)
832
+ Parallel(n_jobs=n_job)(delayed(dem_to_line)(*c) for c in cases)
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}'
575
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,40 @@
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
+ QMessageBox )
18
+
19
+ from PyQt5.QtCore import QThreadPool, QRunnable
12
20
 
13
21
 
14
22
  from skimage.transform import rescale
23
+
15
24
  from PyQt5.QtCore import Qt
25
+
16
26
  import numpy as np
17
27
  import pandas as pd
18
- from pylineament import (dem_to_line, read_raster,
28
+ from pylineament import (dem_to_line,
29
+ read_raster,
19
30
  extract_lineament_points,
20
31
  convert_points_to_line,
21
- dem_to_line_runner,
22
- dem_to_line_runner_small,
23
- reduce_lines,
24
- image_splitting,
32
+ reduce_lines,
25
33
  merge_lines_csv_to_shp,
26
- merge_single_csv_to_shp,
27
34
  hillshade)
28
35
  import os
29
36
 
30
37
 
31
-
32
38
  class ImageSplitterParallel(QRunnable):
33
39
  def __init__(self, i, crs, im, transform ,temp_folder):
34
40
  super().__init__()
@@ -64,10 +70,10 @@ class ImageSplitterParallel(QRunnable):
64
70
 
65
71
  new_dataset.write(Z, 1)
66
72
  new_dataset.close()
67
-
68
73
 
69
74
 
70
75
  class MainWindow(QMainWindow):
76
+
71
77
  def __init__(self):
72
78
  super().__init__()
73
79
  self.threadpool = QThreadPool.globalInstance()
@@ -94,6 +100,8 @@ class MainWindow(QMainWindow):
94
100
  self.previewRegen.clicked.connect(self.previewRegenActionAndSmallCalc)
95
101
  self.extractButon.clicked.connect(self.extractLineamentAction)
96
102
 
103
+ self.aboutButton.clicked.connect(self.aboutAction)
104
+
97
105
  self.previewImageOverlaySelector.currentIndexChanged.connect(self.previewRegenAction)
98
106
  self.previewRegionSelector.currentIndexChanged.connect(self.previewRegenAction)
99
107
  self.hillshadeAngleSelector.currentIndexChanged.connect(self.previewRegenAction)
@@ -143,6 +151,9 @@ class MainWindow(QMainWindow):
143
151
 
144
152
  self.resetButton = QPushButton('Reset to Default')
145
153
  self.previewRegen = QPushButton('Regenerate Preview')
154
+ self.aboutButton = QPushButton('?')
155
+ self.aboutButton.setMaximumWidth(20)
156
+
146
157
  self.extractButon = QPushButton('Extract Lineament')
147
158
 
148
159
  self.subsetSideSlider, subsetSideLabel, subsetSideLayout = self.add_slider("Image Subset size", 500, 100, 2000, 50)
@@ -250,6 +261,7 @@ class MainWindow(QMainWindow):
250
261
  leftLayout.addWidget(self.resetButton)
251
262
  # leftLayout.addWidget(self.previewRegen)
252
263
  leftLayout.addStretch()
264
+ leftLayout.addWidget(self.aboutButton)
253
265
  leftLayout.addWidget(self.extractButon)
254
266
 
255
267
 
@@ -266,8 +278,19 @@ class MainWindow(QMainWindow):
266
278
  def loadImageAndPreview(self):
267
279
  sz = self.subsetSideSlider.value() * self.subsetSideSlider.step
268
280
 
281
+
269
282
  self.regions, self.dem, self.extent, self.crs_espg = read_raster(self.file_name, split_size=sz)
270
283
 
284
+ imgFormat = self.file_name.split('.')[-1]
285
+ if (imgFormat == 'jpg') or (imgFormat == 'jpeg'):
286
+ from PIL import Image
287
+ i = Image.open( r"C:\Users\user\Downloads\WhatsApp Image 2025-09-23 at 16.34.01.jpeg" )
288
+ self.orgImg = np.transpose(np.array(i),( 1,0,2))[:,::-1]
289
+
290
+ else:
291
+
292
+ self.orgImg = self.dem[::-1].T
293
+
271
294
  L,R,B,T = self.extent
272
295
 
273
296
  if (L == 0) & (T==0):
@@ -275,7 +298,9 @@ class MainWindow(QMainWindow):
275
298
 
276
299
 
277
300
  W, H = abs(L-R), abs(B-T)
278
- img = pg.ImageItem(self.dem[::-1].T)
301
+ # img = pg.ImageItem(self.dem[::-1].T)
302
+ img = pg.ImageItem(self.orgImg)
303
+
279
304
  img.setRect(L,B,W,H)
280
305
 
281
306
  self.previewBigImg.clear()
@@ -425,7 +450,6 @@ class MainWindow(QMainWindow):
425
450
  hs = hs.replace(' ', '').split('-')
426
451
  avgAng = (float(hs[0]) + float(hs[1])) /2
427
452
 
428
-
429
453
  if overlay == 'original image':
430
454
  img = self.demSubset
431
455
 
@@ -757,10 +781,35 @@ class MainWindow(QMainWindow):
757
781
  os.removedirs(tempfolder)
758
782
 
759
783
 
760
-
761
- if __name__ == "__main__":
784
+ def aboutAction(self):
785
+ msg = QMessageBox(self)
786
+ msg.setWindowTitle("About PyLineament")
787
+ msg.setTextFormat(Qt.RichText) # enable HTML formatting
788
+ msg.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse)
789
+ msg.setStandardButtons(QMessageBox.Ok)
790
+ msg.setText(
791
+ """<b>PyLineament</b><br><br>
792
+ PyLineament is an open-source Python toolkit for
793
+ <b>automatic and regional-scale lineament extraction</b>
794
+ from DEMs and remote sensing imagery.<br><br>
795
+ Version: 1.0.0<br>
796
+ Author: Epo Prasetya Kusumah - Universitas Pertamina<br>
797
+ License: MIT<br>
798
+ GitHub: <a href='https://github.com/epokus/pylineament'>github.com/epokus/pylineament</a><br><br>
799
+ <b>How to cite:</b><br>
800
+ Kusumah, E. P., (2025). <i>PyLineament: A Python Toolkit for Regional-Scale Lineament Extraction</i>.
801
+ Version 1.0. <a href='https://github.com/epokus/pylineament'>https://github.com/epokus/pylineament</a><br><br>
802
+ © 2025 PyLineament Developers
803
+ """
804
+ )
805
+ msg.exec_()
806
+
807
+ def main():
762
808
  app = QApplication(sys.argv)
763
809
  window = MainWindow()
764
810
  window.show()
765
811
  sys.exit(app.exec_())
766
812
 
813
+
814
+ if __name__ == "__main__":
815
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylineament
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python Geological Lineament extraction package
5
5
  Author-email: Epo Kusumah <epo.pk@universiaspertamina.ac.id>
6
6
  License-Expression: MIT
@@ -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.1"
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,6 @@ 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
 
File without changes
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- lineUI = lineui.pylineament_ui:MainWindow
File without changes
File without changes