pingmapper 5.1.0__tar.gz → 5.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {pingmapper-5.1.0 → pingmapper-5.2.0}/PKG-INFO +1 -1
  2. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/__main__.py +5 -0
  3. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/class_portstarObj.py +171 -57
  4. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/class_rectObj.py +565 -93
  5. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/class_sonObj.py +691 -44
  6. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/class_sonObj_nadirgaptest.py +116 -18
  7. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/default_params.json +4 -0
  8. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/doWork.py +17 -5
  9. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/funcs_common.py +91 -10
  10. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/funcs_model.py +6 -5
  11. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/funcs_rectify.py +66 -52
  12. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/gui_main.py +205 -59
  13. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/main_mapSubstrate.py +12 -5
  14. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/main_readFiles.py +182 -53
  15. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/main_rectify.py +108 -13
  16. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/test_PINGMapper.py +4 -0
  17. pingmapper-5.2.0/pingmapper/version.py +1 -0
  18. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper.egg-info/PKG-INFO +1 -1
  19. pingmapper-5.1.0/pingmapper/version.py +0 -1
  20. {pingmapper-5.1.0 → pingmapper-5.2.0}/LICENSE +0 -0
  21. {pingmapper-5.1.0 → pingmapper-5.2.0}/README.md +0 -0
  22. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/__init__.py +0 -0
  23. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/class_mapSubstrateObj.py +0 -0
  24. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper/test_time.py +0 -0
  25. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper.egg-info/SOURCES.txt +0 -0
  26. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper.egg-info/dependency_links.txt +0 -0
  27. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper.egg-info/requires.txt +0 -0
  28. {pingmapper-5.1.0 → pingmapper-5.2.0}/pingmapper.egg-info/top_level.txt +0 -0
  29. {pingmapper-5.1.0 → pingmapper-5.2.0}/setup.cfg +0 -0
  30. {pingmapper-5.1.0 → pingmapper-5.2.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: Open-source interface for processing recreation-grade side scan sonar datasets and reproducibly mapping benthic habitat
5
5
  Author: Cameron Bodine, Daniel Buscombe
6
6
  Author-email: bodine.cs@gmail.email
@@ -1,6 +1,11 @@
1
1
 
2
2
  import os, sys
3
3
 
4
+ # Configure TensorFlow runtime logging/CPU backend before any TF import occurs.
5
+ # Use setdefault so user-provided environment values still take precedence.
6
+ os.environ.setdefault('TF_CPP_MIN_LOG_LEVEL', '2')
7
+ os.environ.setdefault('TF_ENABLE_ONEDNN_OPTS', '0')
8
+
4
9
  # Add 'pingmapper' to the path, may not need after pypi package...
5
10
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
6
11
  PACKAGE_DIR = os.path.dirname(SCRIPT_DIR)
@@ -59,10 +59,13 @@ import matplotlib.pyplot as plt
59
59
 
60
60
  import inspect
61
61
 
62
+ quiet_tensorflow_warnings()
63
+
62
64
  try:
63
- from doodleverse_utils.imports import *
64
- from doodleverse_utils.model_imports import *
65
- from doodleverse_utils.prediction_imports import *
65
+ with suppress_stdout_stderr():
66
+ from doodleverse_utils.imports import *
67
+ from doodleverse_utils.model_imports import *
68
+ from doodleverse_utils.prediction_imports import *
66
69
  except ImportError as e:
67
70
  import traceback
68
71
  print('\n' + '='*80)
@@ -146,19 +149,38 @@ class portstarObj(object):
146
149
  -------
147
150
  portstarObj instance.
148
151
  '''
152
+ beam_names = []
149
153
  for obj in objs:
150
- if obj.beamName == 'ss_port':
154
+ beam_name = str(obj.beamName)
155
+ beam_names.append(beam_name)
156
+
157
+ if beam_name.startswith('ss_port'):
151
158
  self.port = obj
152
- elif obj.beamName == 'ss_star':
159
+ elif beam_name.startswith('ss_star'):
153
160
  self.star = obj
154
161
  else:
155
162
  print("Object is unknown...")
163
+
164
+ if not hasattr(self, 'port') or not hasattr(self, 'star'):
165
+ raise ValueError(
166
+ "portstarObj requires both side-scan channels (port and star). "
167
+ f"Received beam names: {beam_names}"
168
+ )
156
169
  return
157
170
 
158
171
  ############################################################################
159
172
  # Get Port/Star Sonar Intensity and Merge #
160
173
  ############################################################################
161
174
 
175
+ #=======================================================================
176
+ def _get_sonar_mosaic_name_token(self):
177
+ beam_name = str(self.port.beamName)
178
+ if beam_name.startswith('ss_port_'):
179
+ return beam_name.replace('ss_port_', 'ss_', 1)
180
+ if beam_name.startswith('ss_port'):
181
+ return ''
182
+ return beam_name
183
+
162
184
  #=======================================================================
163
185
  def _getPortStarScanChunk(self,
164
186
  i):
@@ -348,35 +370,35 @@ class portstarObj(object):
348
370
  if mosaic == 1:
349
371
  if son:
350
372
  if self.port.rect_wcp:
351
- _ = Parallel(n_jobs= np.min([len(wcpToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
373
+ _ = Parallel(n_jobs=safe_n_jobs(len(wcpToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
352
374
  if self.port.rect_wcr:
353
- _ = Parallel(n_jobs= np.min([len(srcToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
375
+ _ = Parallel(n_jobs=safe_n_jobs(len(srcToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
354
376
  else:
355
377
  if self.port.map_sub:
356
- _ = Parallel(n_jobs= np.min([len(subToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([sub], overview=overview, i=i, son=son) for i, sub in enumerate(subToMosaic))
378
+ _ = Parallel(n_jobs=safe_n_jobs(len(subToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([sub], overview=overview, i=i, son=son) for i, sub in enumerate(subToMosaic))
357
379
 
358
380
  if self.port.map_predict:
359
381
  # Determine number of bands, i.e. substrate classes
360
382
  bands = self._getBandCount(predictToMosaic[0][0])
361
383
  for i, pred in enumerate(predictToMosaic):
362
- _ = Parallel(n_jobs= np.min([bands, threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
384
+ _ = Parallel(n_jobs=safe_n_jobs(bands, threadCnt), verbose=10)(delayed(self._mosaicGtiff)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
363
385
 
364
386
  # Create vrt
365
387
  elif mosaic == 2:
366
388
  if son:
367
389
  if self.port.rect_wcp:
368
- _ = Parallel(n_jobs= np.min([len(wcpToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
390
+ _ = Parallel(n_jobs=safe_n_jobs(len(wcpToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
369
391
  if self.port.rect_wcr:
370
- _ = Parallel(n_jobs= np.min([len(srcToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
392
+ _ = Parallel(n_jobs=safe_n_jobs(len(srcToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
371
393
  else:
372
394
  if self.port.map_sub:
373
- _ = Parallel(n_jobs= np.min([len(subToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([sub], overview, i, son=son) for i, sub in enumerate(subToMosaic))
395
+ _ = Parallel(n_jobs=safe_n_jobs(len(subToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([sub], overview, i, son=son) for i, sub in enumerate(subToMosaic))
374
396
 
375
397
  if self.port.map_predict:
376
398
  # Determine number of bands, i.e. substrate classes
377
399
  bands = self._getBandCount(predictToMosaic[0][0])
378
400
  for i, pred in enumerate(predictToMosaic):
379
- _ = Parallel(n_jobs= np.min([bands, threadCnt]), verbose=10)(delayed(self._mosaicVRT)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
401
+ _ = Parallel(n_jobs=safe_n_jobs(bands, threadCnt), verbose=10)(delayed(self._mosaicVRT)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
380
402
 
381
403
  return
382
404
 
@@ -425,6 +447,13 @@ class portstarObj(object):
425
447
 
426
448
  chunkField = 'chunk_id'
427
449
 
450
+ def _iter_groups(df):
451
+ if 'transect' in df.columns:
452
+ groups = list(df.groupby('transect', dropna=False))
453
+ if len(groups) > 0:
454
+ return groups
455
+ return [(0, df)]
456
+
428
457
  if son:
429
458
  if self.port.rect_wcp: # Moscaic wcp sonograms if previousl exported
430
459
  self.port._loadSonMeta()
@@ -433,15 +462,18 @@ class portstarObj(object):
433
462
  portPath = os.path.join(self.port.outDir, 'rect_wcp')
434
463
 
435
464
  port = []
436
- for name, group in df.groupby('transect'):
465
+ for name, group in _iter_groups(df):
437
466
  chunks = pd.unique(group[chunkField])
438
467
  port_transect = []
439
468
  for chunk in chunks:
440
469
  zero = self.port._addZero(chunk)
441
470
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
442
- img = glob(img_path)[0]
443
- port_transect.append(img)
444
- port.append(port_transect)
471
+ imgs = glob(img_path)
472
+ if len(imgs) == 0:
473
+ continue
474
+ port_transect.append(imgs[0])
475
+ if len(port_transect) > 0:
476
+ port.append(port_transect)
445
477
 
446
478
  self.star._loadSonMeta()
447
479
  df = self.star.sonMetaDF
@@ -449,15 +481,18 @@ class portstarObj(object):
449
481
  starPath = os.path.join(self.star.outDir, 'rect_wcp')
450
482
 
451
483
  star = []
452
- for name, group in df.groupby('transect'):
484
+ for name, group in _iter_groups(df):
453
485
  chunks = pd.unique(group[chunkField])
454
486
  star_transect = []
455
487
  for chunk in chunks:
456
488
  zero = self.port._addZero(chunk)
457
489
  img_path = os.path.join(starPath, '*_{}{}.tif'.format(zero, chunk))
458
- img = glob(img_path)[0]
459
- star_transect.append(img)
460
- star.append(star_transect)
490
+ imgs = glob(img_path)
491
+ if len(imgs) == 0:
492
+ continue
493
+ star_transect.append(imgs[0])
494
+ if len(star_transect) > 0:
495
+ star.append(star_transect)
461
496
 
462
497
  wcpToMosaic = [list(itertools.chain(*i)) for i in zip(port, star)]
463
498
 
@@ -469,18 +504,21 @@ class portstarObj(object):
469
504
  portPath = os.path.join(self.port.outDir, 'rect_wcr')
470
505
 
471
506
  port = []
472
- for name, group in df.groupby('transect'):
507
+ for name, group in _iter_groups(df):
473
508
  chunks = pd.unique(group[chunkField])
474
509
  port_transect = []
475
510
  for chunk in chunks:
476
511
  # try:
477
512
  zero = self.port._addZero(chunk)
478
513
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
479
- img = glob(img_path)[0]
480
- port_transect.append(img)
514
+ imgs = glob(img_path)
515
+ if len(imgs) == 0:
516
+ continue
517
+ port_transect.append(imgs[0])
481
518
  # except:
482
519
  # pass
483
- port.append(port_transect)
520
+ if len(port_transect) > 0:
521
+ port.append(port_transect)
484
522
 
485
523
  self.star._loadSonMeta()
486
524
  df = self.star.sonMetaDF
@@ -488,18 +526,21 @@ class portstarObj(object):
488
526
  starPath = os.path.join(self.star.outDir, 'rect_wcr')
489
527
 
490
528
  star = []
491
- for name, group in df.groupby('transect'):
529
+ for name, group in _iter_groups(df):
492
530
  chunks = pd.unique(group[chunkField])
493
531
  star_transect = []
494
532
  for chunk in chunks:
495
533
  # try:
496
534
  zero = self.star._addZero(chunk)
497
535
  img_path = os.path.join(starPath, '*_{}{}.tif'.format(zero, chunk))
498
- img = glob(img_path)[0]
499
- star_transect.append(img)
536
+ imgs = glob(img_path)
537
+ if len(imgs) == 0:
538
+ continue
539
+ star_transect.append(imgs[0])
500
540
  # except:
501
541
  # pass
502
- star.append(star_transect)
542
+ if len(star_transect) > 0:
543
+ star.append(star_transect)
503
544
 
504
545
  srcToMosaic = [list(itertools.chain(*i)) for i in zip(port, star)]
505
546
 
@@ -521,15 +562,18 @@ class portstarObj(object):
521
562
  portPath = os.path.join(self.port.substrateDir, 'map_substrate_raster')
522
563
 
523
564
  subToMosaic = []
524
- for name, group in df.groupby('transect'):
565
+ for name, group in _iter_groups(df):
525
566
  chunks = pd.unique(group[chunkField])
526
567
  port_transect = []
527
568
  for chunk in chunks:
528
569
  zero = self.port._addZero(chunk)
529
570
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
530
- img = glob(img_path)[0]
531
- port_transect.append(img)
532
- subToMosaic.append(port_transect)
571
+ imgs = glob(img_path)
572
+ if len(imgs) == 0:
573
+ continue
574
+ port_transect.append(imgs[0])
575
+ if len(port_transect) > 0:
576
+ subToMosaic.append(port_transect)
533
577
 
534
578
  if self.port.map_predict:
535
579
  # Locate map files
@@ -549,35 +593,37 @@ class portstarObj(object):
549
593
  if mosaic == 1:
550
594
  if son:
551
595
  if self.port.rect_wcp:
552
- _ = Parallel(n_jobs= np.min([len(wcpToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
596
+ if len(wcpToMosaic) > 0:
597
+ _ = Parallel(n_jobs=safe_n_jobs(len(wcpToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
553
598
  if self.port.rect_wcr:
554
- _ = Parallel(n_jobs= np.min([len(srcToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
599
+ if len(srcToMosaic) > 0:
600
+ _ = Parallel(n_jobs=safe_n_jobs(len(srcToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
555
601
  else:
556
602
  if self.port.map_sub:
557
- _ = Parallel(n_jobs= np.min([len(subToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([sub], overview=overview, i=i, son=son) for i, sub in enumerate(subToMosaic))
603
+ _ = Parallel(n_jobs=safe_n_jobs(len(subToMosaic), threadCnt), verbose=10)(delayed(self._mosaicGtiff)([sub], overview=overview, i=i, son=son) for i, sub in enumerate(subToMosaic))
558
604
 
559
605
  if self.port.map_predict:
560
606
  # Determine number of bands, i.e. substrate classes
561
607
  bands = self._getBandCount(predictToMosaic[0][0])
562
608
  for i, pred in enumerate(predictToMosaic):
563
- _ = Parallel(n_jobs= np.min([bands, threadCnt]), verbose=10)(delayed(self._mosaicGtiff)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
609
+ _ = Parallel(n_jobs=safe_n_jobs(bands, threadCnt), verbose=10)(delayed(self._mosaicGtiff)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
564
610
 
565
611
  # Create vrt
566
612
  elif mosaic == 2:
567
613
  if son:
568
614
  if self.port.rect_wcp:
569
- _ = Parallel(n_jobs= np.min([len(wcpToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
615
+ _ = Parallel(n_jobs=safe_n_jobs(len(wcpToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([wcp], overview, i, son=son) for i, wcp in enumerate(wcpToMosaic))
570
616
  if self.port.rect_wcr:
571
- _ = Parallel(n_jobs= np.min([len(srcToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
617
+ _ = Parallel(n_jobs=safe_n_jobs(len(srcToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([src], overview, i, son=son) for i, src in enumerate(srcToMosaic))
572
618
  else:
573
619
  if self.port.map_sub:
574
- _ = Parallel(n_jobs= np.min([len(subToMosaic), threadCnt]), verbose=10)(delayed(self._mosaicVRT)([sub], overview, i, son=son) for i, sub in enumerate(subToMosaic))
620
+ _ = Parallel(n_jobs=safe_n_jobs(len(subToMosaic), threadCnt), verbose=10)(delayed(self._mosaicVRT)([sub], overview, i, son=son) for i, sub in enumerate(subToMosaic))
575
621
 
576
622
  if self.port.map_predict:
577
623
  # Determine number of bands, i.e. substrate classes
578
624
  bands = self._getBandCount(predictToMosaic[0][0])
579
625
  for i, pred in enumerate(predictToMosaic):
580
- _ = Parallel(n_jobs= np.min([bands, threadCnt]), verbose=10)(delayed(self._mosaicVRT)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
626
+ _ = Parallel(n_jobs=safe_n_jobs(bands, threadCnt), verbose=10)(delayed(self._mosaicVRT)([pred], overview, i, bands=[c], son=True) for c in range(1,bands+1))
581
627
 
582
628
  return
583
629
 
@@ -623,10 +669,27 @@ class portstarObj(object):
623
669
  # Iterate each sublist of images
624
670
  outMosaic = []
625
671
  for imgs in imgsToMosaic:
672
+ if imgs is None or len(imgs) == 0:
673
+ continue
674
+
675
+ # Preserve all source bands for sonar mosaics when no explicit band
676
+ # selection is provided. This is required for colorized 16-bit
677
+ # rectified outputs (RGB) so mosaics do not collapse to band 1.
678
+ bands_to_use = bands
679
+ if son and bands == [1] and len(imgs) > 0:
680
+ try:
681
+ src_ds = gdal.Open(imgs[0])
682
+ src_band_count = int(src_ds.RasterCount) if src_ds is not None else 1
683
+ src_ds = None
684
+ if src_band_count > 1:
685
+ bands_to_use = list(range(1, src_band_count + 1))
686
+ except Exception:
687
+ pass
626
688
 
627
689
  # Set output file names
628
690
  fileSuffix = os.path.split(os.path.dirname(imgs[0]))[-1] + '_mosaic_'+str(i)+'.vrt'
629
691
  filePrefix = os.path.split(self.port.projDir)[-1]
692
+ sonar_token = self._get_sonar_mosaic_name_token()
630
693
  if 'substrate' in fileSuffix:
631
694
  outDir = os.path.join(self.port.substrateDir, 'map_substrate_mosaic')
632
695
  outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
@@ -638,7 +701,10 @@ class portstarObj(object):
638
701
  outVRT = os.path.join(outDir, filePrefix+'_'+'class_'+str(bands[0]-1)+'_'+fileSuffix)
639
702
  else:
640
703
  outDir = os.path.join(self.port.projDir, 'sonar_mosaic')
641
- outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
704
+ if sonar_token:
705
+ outVRT = os.path.join(outDir, filePrefix+'_'+sonar_token+'_'+fileSuffix)
706
+ else:
707
+ outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
642
708
  outTIF = outVRT.replace('.vrt', '.tif')
643
709
 
644
710
  if not os.path.isdir(outDir):
@@ -648,7 +714,7 @@ class portstarObj(object):
648
714
  pass
649
715
 
650
716
  # First built a vrt
651
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands)
717
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
652
718
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
653
719
 
654
720
  # Create GeoTiff from vrt
@@ -679,7 +745,7 @@ class portstarObj(object):
679
745
  outMosaic.append(outTIF)
680
746
 
681
747
  gc.collect()
682
- return outTIF
748
+ return outMosaic
683
749
 
684
750
  #=======================================================================
685
751
  def _mosaicVRT(self,
@@ -726,10 +792,26 @@ class portstarObj(object):
726
792
  # Iterate each sublist of images
727
793
  outMosaic = []
728
794
  for imgs in imgsToMosaic:
795
+ if imgs is None or len(imgs) == 0:
796
+ continue
797
+
798
+ # Preserve all source bands for sonar mosaics when no explicit band
799
+ # selection is provided.
800
+ bands_to_use = bands
801
+ if son and bands == [1] and len(imgs) > 0:
802
+ try:
803
+ src_ds = gdal.Open(imgs[0])
804
+ src_band_count = int(src_ds.RasterCount) if src_ds is not None else 1
805
+ src_ds = None
806
+ if src_band_count > 1:
807
+ bands_to_use = list(range(1, src_band_count + 1))
808
+ except Exception:
809
+ pass
729
810
 
730
811
  # Set output file names
731
812
  fileSuffix = os.path.split(os.path.dirname(imgs[0]))[-1] + '_mosaic_'+str(i)+'.vrt'
732
813
  filePrefix = os.path.split(self.port.projDir)[-1]
814
+ sonar_token = self._get_sonar_mosaic_name_token()
733
815
  if 'substrate' in fileSuffix:
734
816
  outDir = os.path.join(self.port.substrateDir, 'map_substrate_mosaic')
735
817
  outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
@@ -741,7 +823,10 @@ class portstarObj(object):
741
823
  outVRT = os.path.join(outDir, filePrefix+'_'+'class_'+str(bands[0]-1)+'_'+fileSuffix)
742
824
  else:
743
825
  outDir = os.path.join(self.port.projDir, 'sonar_mosaic')
744
- outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
826
+ if sonar_token:
827
+ outVRT = os.path.join(outDir, filePrefix+'_'+sonar_token+'_'+fileSuffix)
828
+ else:
829
+ outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
745
830
 
746
831
  if not os.path.isdir(outDir):
747
832
  try:
@@ -755,7 +840,7 @@ class portstarObj(object):
755
840
 
756
841
  # Build VRT
757
842
  # vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands, xRes=xRes, yRes=yRes)
758
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands)
843
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
759
844
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
760
845
 
761
846
  # Generate overviews
@@ -1210,7 +1295,7 @@ class portstarObj(object):
1210
1295
  port = []
1211
1296
  for pdi, pdc in zip(portDepPix, portDepPixFinal):
1212
1297
  if np.isnan(pdc): # Final pick is nan
1213
- if np.isnan(pdc): # Initial pick is nan
1298
+ if np.isnan(pdi): # Initial pick is nan
1214
1299
  port.append(0) # set to 0
1215
1300
  else: # Initial pick not nan
1216
1301
  port.append(pdi) # Use initial pick
@@ -1231,7 +1316,7 @@ class portstarObj(object):
1231
1316
  star.append(sdi) # Use initial pick
1232
1317
 
1233
1318
  elif sdc < 0:
1234
- port.append(0)
1319
+ star.append(0)
1235
1320
 
1236
1321
  else: # Final pick ok
1237
1322
  star.append(sdc)
@@ -1482,8 +1567,25 @@ class portstarObj(object):
1482
1567
  chunks = pd.unique(portDF['chunk_id'])
1483
1568
 
1484
1569
  if detectDep == 0:
1485
- portInstDepth = portDF['inst_dep_m']
1486
- starInstDepth = starDF['inst_dep_m']
1570
+ def _depth_series(df):
1571
+ # Some formats (e.g., Garmin RSD) may not include dep_m in metadata.
1572
+ if 'dep_m' in df.columns:
1573
+ return pd.to_numeric(df['dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1574
+ if 'inst_dep_m' in df.columns:
1575
+ return pd.to_numeric(df['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1576
+ return np.zeros(len(df), dtype=float)
1577
+
1578
+ portInstDepth = pd.to_numeric(portDF['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1579
+ starInstDepth = pd.to_numeric(starDF['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1580
+
1581
+ portMetaDepth = _depth_series(portDF)
1582
+ starMetaDepth = _depth_series(starDF)
1583
+
1584
+ portValid = np.isfinite(portInstDepth) & (portInstDepth > 0)
1585
+ starValid = np.isfinite(starInstDepth) & (starInstDepth > 0)
1586
+
1587
+ portInstDepth = np.where(portValid, portInstDepth, portMetaDepth)
1588
+ starInstDepth = np.where(starValid, starInstDepth, starMetaDepth)
1487
1589
 
1488
1590
  if smthDep:
1489
1591
  # print("\nSmoothing depth values...")
@@ -1509,8 +1611,8 @@ class portstarObj(object):
1509
1611
  portDF['dep_m'] = portInstDepth
1510
1612
  starDF['dep_m'] = starInstDepth
1511
1613
 
1512
- portDF['dep_m_Method'] = 'Instrument Depth'
1513
- starDF['dep_m_Method'] = 'Instrument Depth'
1614
+ portDF['dep_m_Method'] = 'Instrument/Metadata Depth'
1615
+ starDF['dep_m_Method'] = 'Instrument/Metadata Depth'
1514
1616
 
1515
1617
  portDF['dep_m_smth'] = smthDep
1516
1618
  starDF['dep_m_smth'] = smthDep
@@ -1612,11 +1714,17 @@ class portstarObj(object):
1612
1714
  starDep[starDep == 0] = np.nan
1613
1715
 
1614
1716
  nans, x = np.isnan(portDep), lambda z: z.nonzero()[0]
1615
- portDep[nans] = np.interp(x(nans), x(~nans), portDep[~nans])
1717
+ if (~nans).any():
1718
+ portDep[nans] = np.interp(x(nans), x(~nans), portDep[~nans])
1719
+ else:
1720
+ portDep[nans] = 0
1616
1721
  portDF['dep_m'] = portDep
1617
1722
 
1618
1723
  nans, x = np.isnan(starDep), lambda z: z.nonzero()[0]
1619
- starDep[nans] = np.interp(x(nans), x(~nans), starDep[~nans])
1724
+ if (~nans).any():
1725
+ starDep[nans] = np.interp(x(nans), x(~nans), starDep[~nans])
1726
+ else:
1727
+ starDep[nans] = 0
1620
1728
  starDF['dep_m'] = starDep
1621
1729
 
1622
1730
  # Export to csv
@@ -1697,10 +1805,16 @@ class portstarObj(object):
1697
1805
  portDF = portDF.loc[portDF['chunk_id'] == i, ['inst_dep_m', 'dep_m', 'pixM']]
1698
1806
  starDF = starDF.loc[starDF['chunk_id'] == i, ['inst_dep_m', 'dep_m', 'pixM']]
1699
1807
 
1700
- portInst = (portDF['inst_dep_m'] / portDF['pixM']).to_numpy(dtype=int, copy=True)
1808
+ portInstDepth = pd.to_numeric(portDF['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1809
+ portMetaDepth = pd.to_numeric(portDF['dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1810
+ portInstDepth = np.where(np.isfinite(portInstDepth) & (portInstDepth > 0), portInstDepth, portMetaDepth)
1811
+ portInst = np.nan_to_num(portInstDepth / portDF['pixM'].to_numpy(dtype=float), nan=0.0).astype(int)
1701
1812
  portAuto = (portDF['dep_m'] / portDF['pixM']).to_numpy(dtype=int, copy=True)
1702
1813
 
1703
- starInst = (starDF['inst_dep_m'] / starDF['pixM']).to_numpy(dtype=int, copy=True)
1814
+ starInstDepth = pd.to_numeric(starDF['inst_dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1815
+ starMetaDepth = pd.to_numeric(starDF['dep_m'], errors='coerce').to_numpy(dtype=float, copy=True)
1816
+ starInstDepth = np.where(np.isfinite(starInstDepth) & (starInstDepth > 0), starInstDepth, starMetaDepth)
1817
+ starInst = np.nan_to_num(starInstDepth / starDF['pixM'].to_numpy(dtype=float), nan=0.0).astype(int)
1704
1818
  starAuto = (starDF['dep_m'] / starDF['pixM']).to_numpy(dtype=int, copy=True)
1705
1819
 
1706
1820
  # Ensure port/star same length
@@ -2572,7 +2686,7 @@ class portstarObj(object):
2572
2686
  os.mkdir(outDir)
2573
2687
 
2574
2688
  print("\n\tExporting to shapefile...")
2575
- _ = Parallel(n_jobs= np.min([len(rasterFiles), threadCnt]), verbose=10)(delayed(self._createPolygon)(f, outDir) for f in rasterFiles)
2689
+ _ = Parallel(n_jobs=safe_n_jobs(len(rasterFiles), threadCnt), verbose=10)(delayed(self._createPolygon)(f, outDir) for f in rasterFiles)
2576
2690
 
2577
2691
  return
2578
2692