pingmapper 5.3.5__tar.gz → 5.3.7__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.5 → pingmapper-5.3.7}/PKG-INFO +1 -1
  2. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/class_portstarObj.py +74 -29
  3. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/doWork.py +4 -2
  4. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/funcs_common.py +22 -1
  5. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/main_readFiles.py +105 -60
  6. pingmapper-5.3.7/pingmapper/version.py +1 -0
  7. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper.egg-info/PKG-INFO +1 -1
  8. pingmapper-5.3.5/pingmapper/version.py +0 -1
  9. {pingmapper-5.3.5 → pingmapper-5.3.7}/LICENSE +0 -0
  10. {pingmapper-5.3.5 → pingmapper-5.3.7}/README.md +0 -0
  11. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/__init__.py +0 -0
  12. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/__main__.py +0 -0
  13. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/class_mapSubstrateObj.py +0 -0
  14. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/class_rectObj.py +0 -0
  15. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/class_sonObj.py +0 -0
  16. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/class_sonObj_nadirgaptest.py +0 -0
  17. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/default_params.json +0 -0
  18. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/funcs_model.py +0 -0
  19. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/funcs_rectify.py +0 -0
  20. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/gui_main.py +0 -0
  21. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/main_mapSubstrate.py +0 -0
  22. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/main_rectify.py +0 -0
  23. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0926.py +0 -0
  24. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0929.py +0 -0
  25. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/scratch/funcs_pyhum_correct.py +0 -0
  26. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/scratch/main.py +0 -0
  27. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/scratch/main_batchDirectory.py +0 -0
  28. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/test_PINGMapper.py +0 -0
  29. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/test_time.py +0 -0
  30. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py +0 -0
  31. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/DRAFT_Workflows/gen_centerline.py +0 -0
  32. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/DRAFT_Workflows/gen_centerline_from_bankline.py +0 -0
  33. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/DRAFT_Workflows/gen_centerline_trkpnts_fitspline_DRAFT.py +0 -0
  34. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py +0 -0
  35. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/RawEGN_avg_predictions.py +0 -0
  36. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/00_substrate_logits_mosaic_transects.py +0 -0
  37. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/00_substrate_shps_mosaic_transects.py +0 -0
  38. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/01_gen_centerline_from_coverage.py +0 -0
  39. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/02_gen_summary_stamp_shps.py +0 -0
  40. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py +0 -0
  41. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/04_combine_summary_shp_csv.py +0 -0
  42. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/05_gen_summary_shp_plots.py +0 -0
  43. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/06_compare_raw-egn_volume.py +0 -0
  44. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/08_raw-egn_hardReacheFreq_hist.py +0 -0
  45. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/09_raw-egn_PatchSize_density.py +0 -0
  46. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/Substrate_Summaries/summarize_project_substrate.py +0 -0
  47. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/export_coverage.py +0 -0
  48. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper/utils/main_mosaic_transects.py +0 -0
  49. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper.egg-info/SOURCES.txt +0 -0
  50. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper.egg-info/dependency_links.txt +0 -0
  51. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper.egg-info/requires.txt +0 -0
  52. {pingmapper-5.3.5 → pingmapper-5.3.7}/pingmapper.egg-info/top_level.txt +0 -0
  53. {pingmapper-5.3.5 → pingmapper-5.3.7}/pyproject.toml +0 -0
  54. {pingmapper-5.3.5 → pingmapper-5.3.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.3.5
3
+ Version: 5.3.7
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
@@ -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)
@@ -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,6 +169,7 @@ def read_master_func(logfilename='',
158
169
  mosaic=False,
159
170
  map_mosaic=0,
160
171
  banklines=False,
172
+ side_scan_only=False,
161
173
  return_context=False,
162
174
  **kwargs):
163
175
 
@@ -354,7 +366,7 @@ def read_master_func(logfilename='',
354
366
  # Prepare Cerulean file for PINGMapper
355
367
  elif file_type == '.svlog':
356
368
  sonar_obj = cerul2pingmapper(inFile, projDir, nchunk, tempC, exportUnknown)
357
- detectDep = 1 # No depth in cerulean files, so set to Zheng et al. 2021
369
+ detectDep = 1 if DEPTH_DETECTION_AVAILABLE else 2
358
370
  instDepAvail = False
359
371
 
360
372
  # Prepare JSF file for PINGMapper
@@ -375,6 +387,7 @@ def read_master_func(logfilename='',
375
387
  sys.exit()
376
388
 
377
389
  nav_available = bool(getattr(sonar_obj, 'has_position', True))
390
+ side_scan_only = bool(side_scan_only)
378
391
 
379
392
  # Sonar-only fallback for sources without navigation fields (e.g., some Cerulean logs).
380
393
  # Disable filters that rely on geospatial motion/position so processing can continue.
@@ -547,6 +560,19 @@ def read_master_func(logfilename='',
547
560
  else:
548
561
  pass
549
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
+
550
576
  print(sonObjs)
551
577
  ####
552
578
  # OLD
@@ -949,10 +975,15 @@ def read_master_func(logfilename='',
949
975
  valid=True
950
976
  elif (attMax != 0) or ("unknown" in att) or (att =="beam"):
951
977
  valid=True
952
- elif (att == "inst_dep_m") and (attAvg == 0): # Automatically detect depth if no instrument depth
953
- valid=False
954
- invalid[son.beam+"."+att] = False
955
- 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
956
987
  else:
957
988
  valid=False
958
989
  invalid[son.beam+"."+att] = False
@@ -1097,17 +1128,23 @@ def read_master_func(logfilename='',
1097
1128
 
1098
1129
  start_time = time.time()
1099
1130
 
1100
- # Determine which sonObj is port/star
1101
- portstar = []
1131
+ # Determine which sonObj pairs should be depth processed together.
1132
+ sidescan_groups = {}
1102
1133
  for son in sonObjs:
1103
- beam = son.beamName
1104
- if _is_sidescan_beam(beam):
1105
- 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
1106
1144
 
1107
- psObj = None
1108
- chunks = np.array([], dtype=int)
1145
+ ps_depth_jobs = []
1109
1146
 
1110
- if len(portstar) == 0:
1147
+ if len(sidescan_groups) == 0:
1111
1148
  print(
1112
1149
  '\n\nNo recognized side-scan channels available for depth processing. '\
1113
1150
  'Continuing with down-looking beams only.'
@@ -1117,20 +1154,28 @@ def read_master_func(logfilename='',
1117
1154
  pltBedPick = False
1118
1155
  remShadow = 0
1119
1156
  else:
1120
- # Create portstarObj
1121
- 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']])
1122
1165
 
1123
- chunks = []
1124
- for son in portstar:
1125
- # Get chunk id's, ignoring those with nodata
1126
- c = son._getChunkID()
1166
+ chunks = []
1167
+ for son in [group['port'], group['star']]:
1168
+ c = son._getChunkID()
1169
+ chunks.extend(c)
1170
+ del c
1127
1171
 
1128
- chunks.extend(c)
1129
- del c
1172
+ chunks = np.unique(chunks).astype(int)
1173
+ if len(chunks) == 0:
1174
+ continue
1130
1175
 
1131
- chunks = np.unique(chunks).astype(int)
1176
+ ps_depth_jobs.append((group_key, psObj, chunks))
1132
1177
 
1133
- if len(chunks) == 0:
1178
+ if len(ps_depth_jobs) == 0:
1134
1179
  print(
1135
1180
  '\n\nNo valid side-scan chunks available for depth processing. '\
1136
1181
  'Continuing with down-looking beams only.'
@@ -1143,7 +1188,12 @@ def read_master_func(logfilename='',
1143
1188
  # # Automatically estimate depth
1144
1189
  if detectDep > 0:
1145
1190
  # Check if depth detection dependencies are available
1146
- 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:
1147
1197
  print('\n\nCannot estimate depth automatically:')
1148
1198
  print('TensorFlow, Transformers, and/or Doodleverse Utils are not installed.')
1149
1199
  print('These packages are required for automatic depth detection.')
@@ -1153,37 +1203,28 @@ def read_master_func(logfilename='',
1153
1203
  autoBed = False
1154
1204
  saveDepth = True
1155
1205
  else:
1156
- print('\n\nAutomatically estimating depth for', len(chunks), 'chunks:')
1157
-
1158
- #Dictionary to store chunk : np.array(depth estimate)
1159
- psObj.portDepDetect = {}
1160
- psObj.starDepDetect = {}
1161
-
1162
- # Estimate depth using:
1163
- # Zheng et al. 2021
1164
- # Load model weights and configuration file
1165
- if detectDep == 1:
1166
-
1167
- # Store configuration file and model weights
1168
- # These were downloaded at the beginning of the script
1169
- depthModelVer = 'Bedpick_Zheng2021_Segmentation_unet_v1.0'
1170
- psObj.configfile = os.path.join(modelDir, depthModelVer, 'config', depthModelVer+'.json')
1171
- psObj.weights = os.path.join(modelDir, depthModelVer, 'weights', depthModelVer+'_fullmodel.h5')
1172
- print('\n\tUsing Zheng et al. 2021 method. Loading model:', os.path.basename(psObj.weights))
1173
-
1174
- # With binary thresholding
1175
- elif detectDep == 2:
1176
- print('\n\tUsing binary thresholding...')
1177
-
1178
- # Parallel estimate depth for each chunk using appropriate method
1179
- r = Parallel(n_jobs=safe_n_jobs(len(chunks), threadCnt))(delayed(psObj._detectDepth)(detectDep, int(chunk), USE_GPU, tileFile) for chunk in tqdm(chunks))
1180
-
1181
- # store the depth predictions in the class
1182
- for ret in r:
1183
- psObj.portDepDetect[ret[2]] = ret[0]
1184
- psObj.starDepDetect[ret[2]] = ret[1]
1185
- del ret
1186
- 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
1187
1228
 
1188
1229
  # Flag indicating depth autmatically estimated
1189
1230
  autoBed = True
@@ -1201,9 +1242,10 @@ def read_master_func(logfilename='',
1201
1242
 
1202
1243
  if saveDepth:
1203
1244
 
1204
- if ss_chan_avail and psObj is not None:
1205
- # Save detected depth to csv
1206
- 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))
1207
1249
  else:
1208
1250
  depDF = []
1209
1251
 
@@ -1260,7 +1302,7 @@ def read_master_func(logfilename='',
1260
1302
  del depDF
1261
1303
 
1262
1304
  # Cleanup
1263
- if psObj is not None:
1305
+ for _, psObj, _ in ps_depth_jobs:
1264
1306
  psObj._cleanup()
1265
1307
 
1266
1308
  print("\nDone!")
@@ -1281,7 +1323,10 @@ def read_master_func(logfilename='',
1281
1323
  # Cleanup
1282
1324
  if psObj is not None:
1283
1325
  psObj._cleanup()
1284
- del psObj, portstar
1326
+ if 'psObj' in locals():
1327
+ del psObj
1328
+ if 'portstar' in locals():
1329
+ del portstar
1285
1330
 
1286
1331
  for son in sonObjs:
1287
1332
  son._cleanup()
@@ -0,0 +1 @@
1
+ __version__ = '5.3.7'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.3.5
3
+ Version: 5.3.7
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.5'
File without changes
File without changes
File without changes
File without changes