pingmapper 5.3.4__tar.gz → 5.3.6__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.
Files changed (54) hide show
  1. {pingmapper-5.3.4 → pingmapper-5.3.6}/PKG-INFO +1 -1
  2. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/class_portstarObj.py +75 -30
  3. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/class_sonObj.py +2 -2
  4. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/class_sonObj_nadirgaptest.py +2 -2
  5. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/doWork.py +4 -2
  6. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/funcs_common.py +24 -3
  7. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/gui_main.py +2 -5
  8. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/main_mapSubstrate.py +2 -1
  9. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/main_readFiles.py +103 -60
  10. pingmapper-5.3.6/pingmapper/version.py +1 -0
  11. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper.egg-info/PKG-INFO +1 -1
  12. pingmapper-5.3.4/pingmapper/version.py +0 -1
  13. {pingmapper-5.3.4 → pingmapper-5.3.6}/LICENSE +0 -0
  14. {pingmapper-5.3.4 → pingmapper-5.3.6}/README.md +0 -0
  15. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/__init__.py +0 -0
  16. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/__main__.py +0 -0
  17. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/class_mapSubstrateObj.py +0 -0
  18. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/class_rectObj.py +0 -0
  19. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/default_params.json +0 -0
  20. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/funcs_model.py +0 -0
  21. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/funcs_rectify.py +0 -0
  22. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/main_rectify.py +0 -0
  23. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0926.py +0 -0
  24. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0929.py +0 -0
  25. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/scratch/funcs_pyhum_correct.py +0 -0
  26. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/scratch/main.py +0 -0
  27. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/scratch/main_batchDirectory.py +0 -0
  28. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/test_PINGMapper.py +0 -0
  29. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/test_time.py +0 -0
  30. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py +0 -0
  31. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/DRAFT_Workflows/gen_centerline.py +0 -0
  32. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/DRAFT_Workflows/gen_centerline_from_bankline.py +0 -0
  33. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/DRAFT_Workflows/gen_centerline_trkpnts_fitspline_DRAFT.py +0 -0
  34. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py +0 -0
  35. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/RawEGN_avg_predictions.py +0 -0
  36. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/00_substrate_logits_mosaic_transects.py +0 -0
  37. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/00_substrate_shps_mosaic_transects.py +0 -0
  38. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/01_gen_centerline_from_coverage.py +0 -0
  39. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/02_gen_summary_stamp_shps.py +0 -0
  40. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py +0 -0
  41. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/04_combine_summary_shp_csv.py +0 -0
  42. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/05_gen_summary_shp_plots.py +0 -0
  43. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/06_compare_raw-egn_volume.py +0 -0
  44. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/08_raw-egn_hardReacheFreq_hist.py +0 -0
  45. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/09_raw-egn_PatchSize_density.py +0 -0
  46. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/Substrate_Summaries/summarize_project_substrate.py +0 -0
  47. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/export_coverage.py +0 -0
  48. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper/utils/main_mosaic_transects.py +0 -0
  49. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper.egg-info/SOURCES.txt +0 -0
  50. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper.egg-info/dependency_links.txt +0 -0
  51. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper.egg-info/requires.txt +0 -0
  52. {pingmapper-5.3.4 → pingmapper-5.3.6}/pingmapper.egg-info/top_level.txt +0 -0
  53. {pingmapper-5.3.4 → pingmapper-5.3.6}/pyproject.toml +0 -0
  54. {pingmapper-5.3.4 → pingmapper-5.3.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.3.4
3
+ Version: 5.3.6
4
4
  Summary: Open-source interface for processing recreation-grade side scan sonar datasets and reproducibly mapping benthic habitat
5
5
  Author: Daniel Buscombe
6
6
  Author-email: Cameron Bodine <bodine.cs@gmail.email>
@@ -979,20 +979,20 @@ class portstarObj(object):
979
979
  self._depthZheng() or self._depthThreshold()
980
980
  '''
981
981
 
982
- # Check if depth detection dependencies are available
983
- if not DEPTH_DETECTION_AVAILABLE:
984
- raise ImportError(
985
- "TensorFlow, Transformers, and/or Doodleverse Utils are not installed. "
986
- "These packages are required for automatic depth detection. "
987
- "Please install them using: pip install tensorflow transformers doodleverse-utils"
988
- )
989
-
990
- # Open model configuration file
991
- with open(self.configfile) as f:
992
- config = json.load(f)
993
- globals().update(config)
994
-
995
982
  if method == 1:
983
+ # Check if depth detection dependencies are available
984
+ if not DEPTH_DETECTION_AVAILABLE:
985
+ raise ImportError(
986
+ "TensorFlow, Transformers, and/or Doodleverse Utils are not installed. "
987
+ "These packages are required for ML depth detection. "
988
+ "Use method=2 for binary-threshold depth picking instead."
989
+ )
990
+
991
+ # Open model configuration file
992
+ with open(self.configfile) as f:
993
+ config = json.load(f)
994
+ globals().update(config)
995
+
996
996
  if not hasattr(self, 'bedpickModel'):
997
997
  # model = self._initModel(USE_GPU)
998
998
  model = initModel(self.weights, self.configfile, USE_GPU)
@@ -1003,7 +1003,7 @@ class portstarObj(object):
1003
1003
  elif method == 2:
1004
1004
  self.port._loadSonMeta()
1005
1005
  self.star._loadSonMeta()
1006
- portDepPixCrop, starDepPixCrop, i = self._depthThreshold(i, tileFile)
1006
+ portDepPixCrop, starDepPixCrop, i = self._depthThreshold(i)
1007
1007
 
1008
1008
  gc.collect()
1009
1009
  return portDepPixCrop, starDepPixCrop, i
@@ -1391,25 +1391,31 @@ class portstarObj(object):
1391
1391
  for son in portstar:
1392
1392
  # Load sonar intensity, standardize & rescale
1393
1393
  son._getScanChunkSingle(chunk)
1394
- img = son.sonDat
1395
- img = standardize(img, 0, 1, True)[:,:,-1].squeeze()
1394
+ img = standardize(np.asarray(son.sonDat)).squeeze()
1395
+ if img.ndim == 3:
1396
+ img = img[:, :, -1]
1396
1397
  W, H = img.shape[1], img.shape[0]
1397
1398
 
1398
1399
  # Get chunks sonar metadata and instrument depth
1399
- isChunk = son.sonMetaDF['chunk_id']==1
1400
+ isChunk = son.sonMetaDF['chunk_id'] == chunk
1400
1401
  sonMeta = son.sonMetaDF[isChunk].reset_index()
1401
- # acousticBed = round(sonMeta['inst_dep_m'] / sonMeta['pix_m'], 0).astype(int)
1402
- acousticBed = round(sonMeta['inst_dep_m'] / sonMeta['pixM'], 0).astype(int)
1402
+ acoustic_depth = pd.to_numeric(sonMeta['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1403
+ acousticBed = np.round(acoustic_depth / sonMeta['pixM'].to_numpy(dtype=float, copy=True), 0)
1404
+ acousticBed = acousticBed[np.isfinite(acousticBed) & (acousticBed > 0)]
1403
1405
 
1404
1406
  ##################################
1405
1407
  # Step 1 : Acoustic Bedpick Filter
1406
1408
  # Use acoustic bed pick to crop image
1407
- bedMin = max(min(acousticBed)-50, 0)
1408
- bedMax = max(acousticBed)+pix_buf
1409
+ if acousticBed.size > 0:
1410
+ bedMin = max(int(np.nanmin(acousticBed)) - 50, 0)
1411
+ bedMax = int(np.nanmax(acousticBed)) + pix_buf
1412
+ else:
1413
+ bedMin = 0
1414
+ bedMax = H
1409
1415
 
1410
1416
  cropMask = np.ones((H, W)).astype(int)
1411
1417
  cropMask[:bedMin,:] = 0
1412
- cropMask[bedMax:,:] = 0
1418
+ cropMask[min(bedMax, H):,:] = 0
1413
1419
 
1414
1420
  # Mask the image with bed_mask
1415
1421
  imgMasked = img*cropMask
@@ -1464,7 +1470,7 @@ class portstarObj(object):
1464
1470
  imgBinaryMask[imgBinaryMask>0] = 1 # Now set all val's greater than 0 to 1 to create the mask
1465
1471
 
1466
1472
  # Now fill in above last row filled to make sure no gaps in bed pixels
1467
- lastRow = bedMax
1473
+ lastRow = min(bedMax, H - 1)
1468
1474
  imgBinaryMask[lastRow] = True
1469
1475
  for i in range(W):
1470
1476
  if imgBinaryMask[lastRow-1,i] == 0:
@@ -1509,10 +1515,10 @@ class portstarObj(object):
1509
1515
  bed[nans] = np.interp(x(nans), x(~nans), bed[~nans])
1510
1516
  bed = bed.astype(int)
1511
1517
 
1512
- if son.beamName == 'ss_port':
1518
+ if str(son.beamName).startswith('ss_port'):
1513
1519
  # self.portDepDetect[chunk] = bed
1514
1520
  portDepPixCrop = bed
1515
- elif son.beamName == 'ss_star':
1521
+ elif str(son.beamName).startswith('ss_star'):
1516
1522
  # self.starDepDetect[chunk] = bed
1517
1523
  starDepPixCrop = bed
1518
1524
 
@@ -1557,6 +1563,43 @@ class portstarObj(object):
1557
1563
  Integer < 0 = decrease depth estimate by x pixels.
1558
1564
  0 = use depth estimate with no adjustment.
1559
1565
  '''
1566
+ def _format_depth_adjustment(pix_m_series):
1567
+ pix_m = pd.to_numeric(pix_m_series, errors='coerce')
1568
+ valid_pix = pix_m[np.isfinite(pix_m) & (pix_m > 0)]
1569
+ if len(valid_pix) == 0:
1570
+ return '0 pixels'
1571
+ return str(float(adjDep) / float(valid_pix.iloc[0])) + ' pixels'
1572
+
1573
+ def _sync_trackline_depth(beam_obj, beam_df):
1574
+ trk_file = os.path.join(beam_obj.metaDir, 'Trackline_Smth_' + beam_obj.beamName + '.csv')
1575
+ if not os.path.exists(trk_file):
1576
+ return
1577
+
1578
+ trk_df = pd.read_csv(trk_file)
1579
+ if 'record_num' not in trk_df.columns or 'record_num' not in beam_df.columns:
1580
+ return
1581
+
1582
+ depth_cols = ['record_num', 'dep_m']
1583
+ if 'dep_m_Method' in beam_df.columns:
1584
+ depth_cols.append('dep_m_Method')
1585
+ if 'dep_m_smth' in beam_df.columns:
1586
+ depth_cols.append('dep_m_smth')
1587
+ if 'dep_m_adjBy' in beam_df.columns:
1588
+ depth_cols.append('dep_m_adjBy')
1589
+
1590
+ depth_df = beam_df[depth_cols].drop_duplicates(subset=['record_num'], keep='last').set_index('record_num')
1591
+ trk_df = trk_df.set_index('record_num')
1592
+
1593
+ trk_df['dep_m'] = depth_df['dep_m']
1594
+ if 'dep_m_Method' in depth_df.columns:
1595
+ trk_df['dep_m_Method'] = depth_df['dep_m_Method']
1596
+ if 'dep_m_smth' in depth_df.columns:
1597
+ trk_df['dep_m_smth'] = depth_df['dep_m_smth']
1598
+ if 'dep_m_adjBy' in depth_df.columns:
1599
+ trk_df['dep_m_adjBy'] = depth_df['dep_m_adjBy']
1600
+
1601
+ trk_df.reset_index().to_csv(trk_file, index=False, float_format='%.14f')
1602
+
1560
1603
  # Load sonar metadata file
1561
1604
  self.port._loadSonMeta()
1562
1605
  portDF = self.port.sonMetaDF
@@ -1617,8 +1660,8 @@ class portstarObj(object):
1617
1660
  portDF['dep_m_smth'] = smthDep
1618
1661
  starDF['dep_m_smth'] = smthDep
1619
1662
 
1620
- portDF['dep_m_adjBy'] = str(adjDep / portDF['pixM']) + ' pixels'
1621
- starDF['dep_m_adjBy'] = str(adjDep / starDF['pixM']) + ' pixels'
1663
+ portDF['dep_m_adjBy'] = _format_depth_adjustment(portDF['pixM'])
1664
+ starDF['dep_m_adjBy'] = _format_depth_adjustment(starDF['pixM'])
1622
1665
 
1623
1666
  elif detectDep > 0:
1624
1667
  # Prepare depth detection dictionaries
@@ -1703,8 +1746,8 @@ class portstarObj(object):
1703
1746
  portDF['dep_m_smth'] = smthDep
1704
1747
  starDF['dep_m_smth'] = smthDep
1705
1748
 
1706
- portDF['dep_m_adjBy'] = str(adjDep / portDF['pixM']) + ' pixels'
1707
- starDF['dep_m_adjBy'] = str(adjDep / starDF['pixM']) + ' pixels'
1749
+ portDF['dep_m_adjBy'] = _format_depth_adjustment(portDF['pixM'])
1750
+ starDF['dep_m_adjBy'] = _format_depth_adjustment(starDF['pixM'])
1708
1751
 
1709
1752
  # Interpolate over nan's (and set zeros to nan)
1710
1753
  portDep = portDF['dep_m'].to_numpy(copy=True)
@@ -1730,6 +1773,8 @@ class portstarObj(object):
1730
1773
  # Export to csv
1731
1774
  portDF.to_csv(self.port.sonMetaFile, index=False, float_format='%.14f')
1732
1775
  starDF.to_csv(self.star.sonMetaFile, index=False, float_format='%.14f')
1776
+ _sync_trackline_depth(self.port, portDF)
1777
+ _sync_trackline_depth(self.star, starDF)
1733
1778
 
1734
1779
  try:
1735
1780
  # Take average of both estimates to store with downlooking sonar csv
@@ -2305,7 +2350,7 @@ class portstarObj(object):
2305
2350
  dd = float(depth * depth)
2306
2351
  row_arr = np.arange(depth, H_pred)
2307
2352
  src_idx = np.round(
2308
- np.sqrt(row_arr.astype(np.float64) ** 2 - dd)
2353
+ np.sqrt(row_arr.astype(np.float32) ** 2 - dd)
2309
2354
  ).astype(int)
2310
2355
  valid = src_idx < H_pred
2311
2356
  rows_v = row_arr[valid]
@@ -1328,7 +1328,7 @@ class sonObj(object):
1328
1328
  row_arr = np.arange(depth, H)
1329
1329
  # Horizontal range index via Pythagorean theorem (vectorised)
1330
1330
  src_idx = np.round(
1331
- np.sqrt(row_arr.astype(np.float64) ** 2 - dd)
1331
+ np.sqrt(row_arr.astype(np.float32) ** 2 - dd)
1332
1332
  ).astype(int)
1333
1333
 
1334
1334
  # Discard out-of-bounds indices
@@ -2919,7 +2919,7 @@ class sonObj(object):
2919
2919
  '''
2920
2920
 
2921
2921
  # Get sonDat
2922
- sonDat = self.sonDat.astype('float64')
2922
+ sonDat = self.sonDat.astype('float32')
2923
2923
 
2924
2924
  # Create mask from zero values
2925
2925
  mask = np.where(sonDat == 0, 0, 1)
@@ -957,7 +957,7 @@ class sonObj(object):
957
957
  row_arr = np.arange(depth, H)
958
958
  # Horizontal range via Pythagorean theorem, plus nadir gap offset
959
959
  src_idx = np.round(
960
- np.sqrt(row_arr.astype(np.float64) ** 2 - dd)
960
+ np.sqrt(row_arr.astype(np.float32) ** 2 - dd)
961
961
  ).astype(int) + nadirGap
962
962
 
963
963
  # Discard out-of-bounds indices
@@ -2356,7 +2356,7 @@ class sonObj(object):
2356
2356
  '''
2357
2357
 
2358
2358
  # Get sonDat
2359
- sonDat = self.sonDat.astype('float64')
2359
+ sonDat = self.sonDat.astype('float32')
2360
2360
 
2361
2361
  # Create mask from zero values
2362
2362
  mask = np.where(sonDat == 0, 0, 1)
@@ -293,6 +293,7 @@ def doWork(
293
293
  if ss_chan_avail:
294
294
  rect_wcp = run_params.get('rect_wcp', False)
295
295
  rect_wcr = run_params.get('rect_wcr', False)
296
+ force_rectify = run_params.get('force_rectify', False)
296
297
  banklines = run_params.get('banklines', False)
297
298
  coverage = run_params.get('coverage', False)
298
299
  pred_sub = run_params.get('pred_sub', False)
@@ -301,11 +302,12 @@ def doWork(
301
302
  plt_subclass = run_params.get('pltSubClass', False)
302
303
 
303
304
  if not nav_available:
304
- if rect_wcp or rect_wcr or banklines or coverage or pred_sub or map_sub or export_poly or plt_subclass:
305
+ if rect_wcp or rect_wcr or force_rectify or banklines or coverage or pred_sub or map_sub or export_poly or plt_subclass:
305
306
  print('\nWARNING: Navigation info is unavailable for this recording.')
306
307
  print('Skipping rectification and substrate mapping workflows (non-georeferenced sonogram-only processing).')
307
308
  rect_wcp = False
308
309
  rect_wcr = False
310
+ force_rectify = False
309
311
  banklines = False
310
312
  coverage = False
311
313
  pred_sub = False
@@ -313,7 +315,7 @@ def doWork(
313
315
  export_poly = False
314
316
  plt_subclass = False
315
317
 
316
- if rect_wcp or rect_wcr or banklines or coverage or pred_sub or map_sub or export_poly:
318
+ if rect_wcp or rect_wcr or force_rectify or banklines or coverage or pred_sub or map_sub or export_poly:
317
319
  print('\n===========================================')
318
320
  print('===========================================')
319
321
  print('***** RECTIFYING *****')
@@ -31,6 +31,12 @@
31
31
 
32
32
  import os, sys, struct, gc, io, contextlib
33
33
 
34
+ # Configure TensorFlow runtime defaults as early as possible so informational
35
+ # startup messages do not leak to stderr before logging is initialized.
36
+ os.environ.setdefault('TF_CPP_MIN_LOG_LEVEL', '3')
37
+ os.environ.setdefault('TF_ENABLE_ONEDNN_OPTS', '0')
38
+ os.environ.setdefault('AUTOGRAPH_VERBOSITY', '0')
39
+
34
40
  # Add 'pingmapper' to the path, may not need after pypi package...
35
41
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
36
42
  PACKAGE_DIR = os.path.dirname(SCRIPT_DIR)
@@ -97,13 +103,27 @@ import json
97
103
 
98
104
  import logging
99
105
 
100
- from tqdm import tqdm
106
+ from tqdm import tqdm as _tqdm
101
107
 
102
108
  import subprocess
103
109
 
104
110
  # from funcs_pyhum_correct import doPyhumCorrections
105
111
 
106
112
 
113
+ # =========================================================
114
+ def tqdm(*args, **kwargs):
115
+ '''
116
+ Shared tqdm wrapper so CLI progress bars stay readable in the GUI console.
117
+ Use PINGMAPPER_TQDM_NCOLS to override the default width when needed.
118
+ '''
119
+ if 'ncols' not in kwargs:
120
+ try:
121
+ kwargs['ncols'] = int(os.environ.get('PINGMAPPER_TQDM_NCOLS', '80'))
122
+ except (TypeError, ValueError):
123
+ kwargs['ncols'] = 80
124
+ return _tqdm(*args, **kwargs)
125
+
126
+
107
127
  # =========================================================
108
128
  def safe_n_jobs(task_count, thread_count=0):
109
129
  '''
@@ -139,6 +159,7 @@ def quiet_tensorflow_warnings():
139
159
  Safe to call even if TensorFlow is not installed.
140
160
  '''
141
161
  os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
162
+ os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
142
163
  os.environ['AUTOGRAPH_VERBOSITY'] = '0'
143
164
 
144
165
  logging.getLogger('tensorflow').setLevel(logging.ERROR)
@@ -443,7 +464,7 @@ def saveDefaultParams(values, user_params=None):
443
464
  return {k: _sanitize(val) for k, val in v.items()}
444
465
  if isinstance(v, (np.integer, np.int_, np.int64)):
445
466
  return int(v)
446
- if isinstance(v, (np.floating, np.float64, np.float64)):
467
+ if isinstance(v, (np.floating, np.float32, np.float32)):
447
468
  return float(v)
448
469
  if isinstance(v, (np.ndarray, list, tuple)):
449
470
  return [_sanitize(x) for x in v]
@@ -516,7 +537,7 @@ class FastPiecewiseAffineTransform(PiecewiseAffineTransform):
516
537
  [self.affines[i].params for i in range(len(self._tesselation.simplices))]
517
538
  )
518
539
 
519
- result = np.empty((n, 3), dtype=np.float64)
540
+ result = np.empty((n, 3), dtype=np.float32)
520
541
 
521
542
  # Process in batches to avoid large allocations in find_simplex and the
522
543
  # affine einsum, both of which scale with n_coords and can exceed several GiB.
@@ -2,11 +2,8 @@
2
2
  import sys, os
3
3
  import webbrowser
4
4
 
5
- PySimpleGUI_License = 'e3yAJ9MVaOWANplCbmndNNl2VwHvlCwpZTSjIl6DIjkiRGpYc53aRty8aBWpJF1qdwGLlzv9bUiHILs3Inkyxpp5Yq2OVku8cg2ZVrJ7RNCQI66bMcTLcnyKMbTRMK57OCTPMGxGNtS8whirTBGTlLjxZEWg5DzWZdUXRUlLcDGfxnv7eiWB1jlOb6nqR8WTZ2XsJVzbabW19ouWI6j0oXiKN0Si4AwtI7iFw8iGTBmtFftjZEUxZMpYcLncNk0rIJj4oyisQq2uFCtqZnXWJvvqbEiCICsSIbkC5jhKbvWTVqM2YtX6Ni0XIJjloji1QEmU9Ak5ayWp5nlnIwi3wiiOQK279ytqcKGwFGuvepS6IH6iIOiYIGs7I4kYNQ13cY33RkvIbqWkVyypSKUOQoiZO2ijIFzaMNTEAp0bNxyWI1sLIwkRRjhZdNGBVoJkcZ3MNN1yZMWTQtihOiieIYyXMDDIIF0ILaTyAt36LKTREj5JI1iYwcixRgGuFk0BZGU5VZ4dciGUl3ykZ3XtMbilOMiBIhy1M5DtId1mL6T1A935LYTLEN5iI3iJwoirR8Wa12h5a0WtxkBNZdGiRJyYZXX9N5zZI2jSoZizYpmp9YkHaIWz5YluLTmcNXzNQmGZd0twYGW6l3sALZmTNWvubcSEItsPITk6lFQgQUWZRrkfcEmAVxz0c9y7IG6sILjZEYyzO8Cf4c0WLDj3QCwSLwjPEt2BMMi0J69p854e39898f71ea82d3a530f7a6ed8a02a4eea9ffd2c7b1279074b491c71b411f392e6d726a2d2f9dbf63388356cf4e083e358fe428852d676073e128607b9ad194c15e34a4feb463a749fd3295606caa293b823d102e854cd845b79b5ec5eaec0b2ef7f9cf0c87b2dfcad3f14cd0d66a2da97e6b38a535eb8707b4486c9802a4bfeb09703382e157449096f0e3551af9f444197cacb3f3d42187cea97ab61978985ddeecd086b9cb86c4ec1c08082d47b3ed0ae9c044d9aa65e5c9bf6e00238f78ed858cfdaf0021fb95d636e0cce84d84d2c2da7ac57f2e54fe793fce44a8b8abf96ce7c381f4b7eeb55dc4b68768e8172a4dffc1b683e62a108b2dfc2ef340dab058e6ee5c1f525f93e89d39258862f099987a8ec7022db5aecb5a58e81d02370d5717d18498ae58749aa5e463cf757ab7fa84efe49c1b770da397eef22423696ad433e7232646e279906bef084b21714ac5fc2af564a03ebc789123aed44531765b3e72c6165131feab68e35e0276a64760ee9abf043bece1e3cd148bcec97ab835395391387ff9d2b74a835a15ea5bac9c7e1218c217481a3999a91e037a138aaf5dddadb2247141242140b130e273aab5e1e6855fae8b7ee80d64be2d09a46f3d49555f53a7a849138fc3b9d2323658ea7e86a0039c40f3c15fd3647f99ec98232d9734a5933177c48c6575a1415e2808640cfb27773e728fe128b99757'
6
- try:
7
- import FreeSimpleGUI as sg
8
- except ImportError:
9
- import PySimpleGUI as sg
5
+
6
+ import FreeSimpleGUI as sg
10
7
  import matplotlib.pyplot as plt
11
8
 
12
9
  # Add 'pingmapper' to the path, may not need after pypi package...
@@ -110,7 +110,8 @@ def map_master_func(logfilename='',
110
110
  banklines=False,
111
111
  export_16bit=False,
112
112
  export_16bit_colormap=False,
113
- export_colormap_uint8=True):
113
+ export_colormap_uint8=True,
114
+ **kwargs):
114
115
 
115
116
  '''
116
117
  Main script to map substrates from side scan sonar imagery.
@@ -94,6 +94,17 @@ def _is_sidescan_beam(beam_name):
94
94
  return beam_name.startswith('ss_port') or beam_name.startswith('ss_star')
95
95
 
96
96
 
97
+ def _sidescan_group_key(beam_name):
98
+ beam_name = str(beam_name)
99
+ if beam_name.startswith('ss_port_'):
100
+ return beam_name[len('ss_port_'):]
101
+ if beam_name.startswith('ss_star_'):
102
+ return beam_name[len('ss_star_'):]
103
+ if beam_name in {'ss_port', 'ss_star'}:
104
+ return 'default'
105
+ return beam_name
106
+
107
+
97
108
  #===========================================
98
109
  def read_master_func(logfilename='',
99
110
  project_mode=0,
@@ -158,7 +169,9 @@ def read_master_func(logfilename='',
158
169
  mosaic=False,
159
170
  map_mosaic=0,
160
171
  banklines=False,
161
- return_context=False):
172
+ side_scan_only=False,
173
+ return_context=False,
174
+ **kwargs):
162
175
 
163
176
  '''
164
177
  Main script to read data from Humminbird sonar recordings. Scripts have been
@@ -353,7 +366,7 @@ def read_master_func(logfilename='',
353
366
  # Prepare Cerulean file for PINGMapper
354
367
  elif file_type == '.svlog':
355
368
  sonar_obj = cerul2pingmapper(inFile, projDir, nchunk, tempC, exportUnknown)
356
- detectDep = 1 # No depth in cerulean files, so set to Zheng et al. 2021
369
+ detectDep = 1 if DEPTH_DETECTION_AVAILABLE else 2
357
370
  instDepAvail = False
358
371
 
359
372
  # Prepare JSF file for PINGMapper
@@ -374,6 +387,7 @@ def read_master_func(logfilename='',
374
387
  sys.exit()
375
388
 
376
389
  nav_available = bool(getattr(sonar_obj, 'has_position', True))
390
+ side_scan_only = bool(side_scan_only)
377
391
 
378
392
  # Sonar-only fallback for sources without navigation fields (e.g., some Cerulean logs).
379
393
  # Disable filters that rely on geospatial motion/position so processing can continue.
@@ -546,6 +560,19 @@ def read_master_func(logfilename='',
546
560
  else:
547
561
  pass
548
562
 
563
+ if side_scan_only:
564
+ if len(ss_chan_avail) == 0:
565
+ print('\n\nSide-scan only mode enabled, but no side-scan channels were recognized. Skipping processing.')
566
+ if return_context:
567
+ return {
568
+ 'has_sidescan': False,
569
+ 'has_nav': nav_available,
570
+ }
571
+ return False
572
+
573
+ print('\n\nSide-scan only mode enabled. Skipping non-side-scan channels.')
574
+ sonObjs = [son for son in sonObjs if _is_sidescan_beam(getattr(son, 'beamName', ''))]
575
+
549
576
  print(sonObjs)
550
577
  ####
551
578
  # OLD
@@ -948,10 +975,15 @@ def read_master_func(logfilename='',
948
975
  valid=True
949
976
  elif (attMax != 0) or ("unknown" in att) or (att =="beam"):
950
977
  valid=True
951
- elif (att == "inst_dep_m") and (attAvg == 0): # Automatically detect depth if no instrument depth
952
- valid=False
953
- invalid[son.beam+"."+att] = False
954
- detectDep=1
978
+ elif att == "inst_dep_m":
979
+ depth_values = pd.to_numeric(df[att], errors='coerce').to_numpy(dtype=float, copy=False)
980
+ has_valid_instrument_depth = np.isfinite(depth_values).any() and np.nanmax(depth_values) > 0
981
+ if not has_valid_instrument_depth: # Automatically detect depth if instrument depth is missing/empty
982
+ valid=False
983
+ invalid[son.beam+"."+att] = False
984
+ detectDep = 1 if DEPTH_DETECTION_AVAILABLE else 2
985
+ else:
986
+ valid=True
955
987
  else:
956
988
  valid=False
957
989
  invalid[son.beam+"."+att] = False
@@ -1096,17 +1128,23 @@ def read_master_func(logfilename='',
1096
1128
 
1097
1129
  start_time = time.time()
1098
1130
 
1099
- # Determine which sonObj is port/star
1100
- portstar = []
1131
+ # Determine which sonObj pairs should be depth processed together.
1132
+ sidescan_groups = {}
1101
1133
  for son in sonObjs:
1102
- beam = son.beamName
1103
- if _is_sidescan_beam(beam):
1104
- portstar.append(son)
1134
+ beam = str(son.beamName)
1135
+ if not _is_sidescan_beam(beam):
1136
+ continue
1137
+
1138
+ group_key = _sidescan_group_key(beam)
1139
+ group = sidescan_groups.setdefault(group_key, {})
1140
+ if beam.startswith('ss_port'):
1141
+ group['port'] = son
1142
+ elif beam.startswith('ss_star'):
1143
+ group['star'] = son
1105
1144
 
1106
- psObj = None
1107
- chunks = np.array([], dtype=int)
1145
+ ps_depth_jobs = []
1108
1146
 
1109
- if len(portstar) == 0:
1147
+ if len(sidescan_groups) == 0:
1110
1148
  print(
1111
1149
  '\n\nNo recognized side-scan channels available for depth processing. '\
1112
1150
  'Continuing with down-looking beams only.'
@@ -1116,20 +1154,28 @@ def read_master_func(logfilename='',
1116
1154
  pltBedPick = False
1117
1155
  remShadow = 0
1118
1156
  else:
1119
- # Create portstarObj
1120
- psObj = portstarObj(portstar)
1157
+ for group_key, group in sorted(sidescan_groups.items()):
1158
+ if 'port' not in group or 'star' not in group:
1159
+ print(
1160
+ '\nSkipping side-scan depth group {} because a matching port/star pair was not found.'.format(group_key)
1161
+ )
1162
+ continue
1163
+
1164
+ psObj = portstarObj([group['port'], group['star']])
1121
1165
 
1122
- chunks = []
1123
- for son in portstar:
1124
- # Get chunk id's, ignoring those with nodata
1125
- c = son._getChunkID()
1166
+ chunks = []
1167
+ for son in [group['port'], group['star']]:
1168
+ c = son._getChunkID()
1169
+ chunks.extend(c)
1170
+ del c
1126
1171
 
1127
- chunks.extend(c)
1128
- del c
1172
+ chunks = np.unique(chunks).astype(int)
1173
+ if len(chunks) == 0:
1174
+ continue
1129
1175
 
1130
- chunks = np.unique(chunks).astype(int)
1176
+ ps_depth_jobs.append((group_key, psObj, chunks))
1131
1177
 
1132
- if len(chunks) == 0:
1178
+ if len(ps_depth_jobs) == 0:
1133
1179
  print(
1134
1180
  '\n\nNo valid side-scan chunks available for depth processing. '\
1135
1181
  'Continuing with down-looking beams only.'
@@ -1142,7 +1188,12 @@ def read_master_func(logfilename='',
1142
1188
  # # Automatically estimate depth
1143
1189
  if detectDep > 0:
1144
1190
  # Check if depth detection dependencies are available
1145
- if not DEPTH_DETECTION_AVAILABLE:
1191
+ if detectDep == 1 and not DEPTH_DETECTION_AVAILABLE:
1192
+ print('\n\nML depth detection dependencies are unavailable.')
1193
+ print('Falling back to binary-threshold depth detection...\n')
1194
+ detectDep = 2
1195
+
1196
+ if detectDep == 1 and not DEPTH_DETECTION_AVAILABLE:
1146
1197
  print('\n\nCannot estimate depth automatically:')
1147
1198
  print('TensorFlow, Transformers, and/or Doodleverse Utils are not installed.')
1148
1199
  print('These packages are required for automatic depth detection.')
@@ -1152,37 +1203,28 @@ def read_master_func(logfilename='',
1152
1203
  autoBed = False
1153
1204
  saveDepth = True
1154
1205
  else:
1155
- print('\n\nAutomatically estimating depth for', len(chunks), 'chunks:')
1156
-
1157
- #Dictionary to store chunk : np.array(depth estimate)
1158
- psObj.portDepDetect = {}
1159
- psObj.starDepDetect = {}
1160
-
1161
- # Estimate depth using:
1162
- # Zheng et al. 2021
1163
- # Load model weights and configuration file
1164
- if detectDep == 1:
1165
-
1166
- # Store configuration file and model weights
1167
- # These were downloaded at the beginning of the script
1168
- depthModelVer = 'Bedpick_Zheng2021_Segmentation_unet_v1.0'
1169
- psObj.configfile = os.path.join(modelDir, depthModelVer, 'config', depthModelVer+'.json')
1170
- psObj.weights = os.path.join(modelDir, depthModelVer, 'weights', depthModelVer+'_fullmodel.h5')
1171
- print('\n\tUsing Zheng et al. 2021 method. Loading model:', os.path.basename(psObj.weights))
1172
-
1173
- # With binary thresholding
1174
- elif detectDep == 2:
1175
- print('\n\tUsing binary thresholding...')
1176
-
1177
- # Parallel estimate depth for each chunk using appropriate method
1178
- r = Parallel(n_jobs=safe_n_jobs(len(chunks), threadCnt))(delayed(psObj._detectDepth)(detectDep, int(chunk), USE_GPU, tileFile) for chunk in tqdm(chunks))
1179
-
1180
- # store the depth predictions in the class
1181
- for ret in r:
1182
- psObj.portDepDetect[ret[2]] = ret[0]
1183
- psObj.starDepDetect[ret[2]] = ret[1]
1184
- del ret
1185
- del r
1206
+ total_chunks = sum(len(chunks) for _, _, chunks in ps_depth_jobs)
1207
+ print('\n\nAutomatically estimating depth for', total_chunks, 'chunks across', len(ps_depth_jobs), 'side-scan group(s):')
1208
+
1209
+ for group_key, psObj, chunks in ps_depth_jobs:
1210
+ psObj.portDepDetect = {}
1211
+ psObj.starDepDetect = {}
1212
+
1213
+ if detectDep == 1:
1214
+ depthModelVer = 'Bedpick_Zheng2021_Segmentation_unet_v1.0'
1215
+ psObj.configfile = os.path.join(modelDir, depthModelVer, 'config', depthModelVer+'.json')
1216
+ psObj.weights = os.path.join(modelDir, depthModelVer, 'weights', depthModelVer+'_fullmodel.h5')
1217
+ print('\n\tGroup {}: Using Zheng et al. 2021 method. Loading model: {}'.format(group_key, os.path.basename(psObj.weights)))
1218
+ elif detectDep == 2:
1219
+ print('\n\tGroup {}: Using binary thresholding...'.format(group_key))
1220
+
1221
+ r = Parallel(n_jobs=safe_n_jobs(len(chunks), threadCnt))(delayed(psObj._detectDepth)(detectDep, int(chunk), USE_GPU, tileFile) for chunk in tqdm(chunks))
1222
+
1223
+ for ret in r:
1224
+ psObj.portDepDetect[ret[2]] = ret[0]
1225
+ psObj.starDepDetect[ret[2]] = ret[1]
1226
+ del ret
1227
+ del r
1186
1228
 
1187
1229
  # Flag indicating depth autmatically estimated
1188
1230
  autoBed = True
@@ -1200,9 +1242,10 @@ def read_master_func(logfilename='',
1200
1242
 
1201
1243
  if saveDepth:
1202
1244
 
1203
- if ss_chan_avail and psObj is not None:
1204
- # Save detected depth to csv
1205
- depDF = psObj._saveDepth(chunks, detectDep, smthDep, adjDep, instDepAvail)
1245
+ if ss_chan_avail and len(ps_depth_jobs) > 0:
1246
+ depDF = []
1247
+ for _, psObj, chunks in ps_depth_jobs:
1248
+ depDF.append(psObj._saveDepth(chunks, detectDep, smthDep, adjDep, instDepAvail))
1206
1249
  else:
1207
1250
  depDF = []
1208
1251
 
@@ -1259,7 +1302,7 @@ def read_master_func(logfilename='',
1259
1302
  del depDF
1260
1303
 
1261
1304
  # Cleanup
1262
- if psObj is not None:
1305
+ for _, psObj, _ in ps_depth_jobs:
1263
1306
  psObj._cleanup()
1264
1307
 
1265
1308
  print("\nDone!")
@@ -0,0 +1 @@
1
+ __version__ = '5.3.6'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.3.4
3
+ Version: 5.3.6
4
4
  Summary: Open-source interface for processing recreation-grade side scan sonar datasets and reproducibly mapping benthic habitat
5
5
  Author: Daniel Buscombe
6
6
  Author-email: Cameron Bodine <bodine.cs@gmail.email>
@@ -1 +0,0 @@
1
- __version__ = '5.3.4'
File without changes
File without changes
File without changes
File without changes