pingmapper 5.1.1__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.1 → pingmapper-5.2.0}/PKG-INFO +1 -1
  2. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/__main__.py +5 -0
  3. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/class_portstarObj.py +150 -39
  4. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/class_rectObj.py +565 -93
  5. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/class_sonObj.py +636 -28
  6. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/class_sonObj_nadirgaptest.py +61 -2
  7. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/default_params.json +4 -0
  8. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/doWork.py +17 -5
  9. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/funcs_common.py +18 -9
  10. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/funcs_rectify.py +63 -49
  11. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/gui_main.py +205 -59
  12. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/main_mapSubstrate.py +8 -1
  13. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/main_readFiles.py +155 -44
  14. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/main_rectify.py +106 -11
  15. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/test_PINGMapper.py +4 -0
  16. pingmapper-5.2.0/pingmapper/version.py +1 -0
  17. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper.egg-info/PKG-INFO +1 -1
  18. pingmapper-5.1.1/pingmapper/version.py +0 -1
  19. {pingmapper-5.1.1 → pingmapper-5.2.0}/LICENSE +0 -0
  20. {pingmapper-5.1.1 → pingmapper-5.2.0}/README.md +0 -0
  21. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/__init__.py +0 -0
  22. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/class_mapSubstrateObj.py +0 -0
  23. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/funcs_model.py +0 -0
  24. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper/test_time.py +0 -0
  25. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper.egg-info/SOURCES.txt +0 -0
  26. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper.egg-info/dependency_links.txt +0 -0
  27. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper.egg-info/requires.txt +0 -0
  28. {pingmapper-5.1.1 → pingmapper-5.2.0}/pingmapper.egg-info/top_level.txt +0 -0
  29. {pingmapper-5.1.1 → pingmapper-5.2.0}/setup.cfg +0 -0
  30. {pingmapper-5.1.1 → 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.1
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)
@@ -149,19 +149,38 @@ class portstarObj(object):
149
149
  -------
150
150
  portstarObj instance.
151
151
  '''
152
+ beam_names = []
152
153
  for obj in objs:
153
- 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'):
154
158
  self.port = obj
155
- elif obj.beamName == 'ss_star':
159
+ elif beam_name.startswith('ss_star'):
156
160
  self.star = obj
157
161
  else:
158
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
+ )
159
169
  return
160
170
 
161
171
  ############################################################################
162
172
  # Get Port/Star Sonar Intensity and Merge #
163
173
  ############################################################################
164
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
+
165
184
  #=======================================================================
166
185
  def _getPortStarScanChunk(self,
167
186
  i):
@@ -428,6 +447,13 @@ class portstarObj(object):
428
447
 
429
448
  chunkField = 'chunk_id'
430
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
+
431
457
  if son:
432
458
  if self.port.rect_wcp: # Moscaic wcp sonograms if previousl exported
433
459
  self.port._loadSonMeta()
@@ -436,15 +462,18 @@ class portstarObj(object):
436
462
  portPath = os.path.join(self.port.outDir, 'rect_wcp')
437
463
 
438
464
  port = []
439
- for name, group in df.groupby('transect'):
465
+ for name, group in _iter_groups(df):
440
466
  chunks = pd.unique(group[chunkField])
441
467
  port_transect = []
442
468
  for chunk in chunks:
443
469
  zero = self.port._addZero(chunk)
444
470
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
445
- img = glob(img_path)[0]
446
- port_transect.append(img)
447
- 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)
448
477
 
449
478
  self.star._loadSonMeta()
450
479
  df = self.star.sonMetaDF
@@ -452,15 +481,18 @@ class portstarObj(object):
452
481
  starPath = os.path.join(self.star.outDir, 'rect_wcp')
453
482
 
454
483
  star = []
455
- for name, group in df.groupby('transect'):
484
+ for name, group in _iter_groups(df):
456
485
  chunks = pd.unique(group[chunkField])
457
486
  star_transect = []
458
487
  for chunk in chunks:
459
488
  zero = self.port._addZero(chunk)
460
489
  img_path = os.path.join(starPath, '*_{}{}.tif'.format(zero, chunk))
461
- img = glob(img_path)[0]
462
- star_transect.append(img)
463
- 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)
464
496
 
465
497
  wcpToMosaic = [list(itertools.chain(*i)) for i in zip(port, star)]
466
498
 
@@ -472,18 +504,21 @@ class portstarObj(object):
472
504
  portPath = os.path.join(self.port.outDir, 'rect_wcr')
473
505
 
474
506
  port = []
475
- for name, group in df.groupby('transect'):
507
+ for name, group in _iter_groups(df):
476
508
  chunks = pd.unique(group[chunkField])
477
509
  port_transect = []
478
510
  for chunk in chunks:
479
511
  # try:
480
512
  zero = self.port._addZero(chunk)
481
513
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
482
- img = glob(img_path)[0]
483
- port_transect.append(img)
514
+ imgs = glob(img_path)
515
+ if len(imgs) == 0:
516
+ continue
517
+ port_transect.append(imgs[0])
484
518
  # except:
485
519
  # pass
486
- port.append(port_transect)
520
+ if len(port_transect) > 0:
521
+ port.append(port_transect)
487
522
 
488
523
  self.star._loadSonMeta()
489
524
  df = self.star.sonMetaDF
@@ -491,18 +526,21 @@ class portstarObj(object):
491
526
  starPath = os.path.join(self.star.outDir, 'rect_wcr')
492
527
 
493
528
  star = []
494
- for name, group in df.groupby('transect'):
529
+ for name, group in _iter_groups(df):
495
530
  chunks = pd.unique(group[chunkField])
496
531
  star_transect = []
497
532
  for chunk in chunks:
498
533
  # try:
499
534
  zero = self.star._addZero(chunk)
500
535
  img_path = os.path.join(starPath, '*_{}{}.tif'.format(zero, chunk))
501
- img = glob(img_path)[0]
502
- star_transect.append(img)
536
+ imgs = glob(img_path)
537
+ if len(imgs) == 0:
538
+ continue
539
+ star_transect.append(imgs[0])
503
540
  # except:
504
541
  # pass
505
- star.append(star_transect)
542
+ if len(star_transect) > 0:
543
+ star.append(star_transect)
506
544
 
507
545
  srcToMosaic = [list(itertools.chain(*i)) for i in zip(port, star)]
508
546
 
@@ -524,15 +562,18 @@ class portstarObj(object):
524
562
  portPath = os.path.join(self.port.substrateDir, 'map_substrate_raster')
525
563
 
526
564
  subToMosaic = []
527
- for name, group in df.groupby('transect'):
565
+ for name, group in _iter_groups(df):
528
566
  chunks = pd.unique(group[chunkField])
529
567
  port_transect = []
530
568
  for chunk in chunks:
531
569
  zero = self.port._addZero(chunk)
532
570
  img_path = os.path.join(portPath, '*_{}{}.tif'.format(zero, chunk))
533
- img = glob(img_path)[0]
534
- port_transect.append(img)
535
- 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)
536
577
 
537
578
  if self.port.map_predict:
538
579
  # Locate map files
@@ -552,9 +593,11 @@ class portstarObj(object):
552
593
  if mosaic == 1:
553
594
  if son:
554
595
  if self.port.rect_wcp:
555
- _ = 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))
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))
556
598
  if self.port.rect_wcr:
557
- _ = 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))
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))
558
601
  else:
559
602
  if self.port.map_sub:
560
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))
@@ -626,10 +669,27 @@ class portstarObj(object):
626
669
  # Iterate each sublist of images
627
670
  outMosaic = []
628
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
629
688
 
630
689
  # Set output file names
631
690
  fileSuffix = os.path.split(os.path.dirname(imgs[0]))[-1] + '_mosaic_'+str(i)+'.vrt'
632
691
  filePrefix = os.path.split(self.port.projDir)[-1]
692
+ sonar_token = self._get_sonar_mosaic_name_token()
633
693
  if 'substrate' in fileSuffix:
634
694
  outDir = os.path.join(self.port.substrateDir, 'map_substrate_mosaic')
635
695
  outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
@@ -641,7 +701,10 @@ class portstarObj(object):
641
701
  outVRT = os.path.join(outDir, filePrefix+'_'+'class_'+str(bands[0]-1)+'_'+fileSuffix)
642
702
  else:
643
703
  outDir = os.path.join(self.port.projDir, 'sonar_mosaic')
644
- 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)
645
708
  outTIF = outVRT.replace('.vrt', '.tif')
646
709
 
647
710
  if not os.path.isdir(outDir):
@@ -651,7 +714,7 @@ class portstarObj(object):
651
714
  pass
652
715
 
653
716
  # First built a vrt
654
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands)
717
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
655
718
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
656
719
 
657
720
  # Create GeoTiff from vrt
@@ -682,7 +745,7 @@ class portstarObj(object):
682
745
  outMosaic.append(outTIF)
683
746
 
684
747
  gc.collect()
685
- return outTIF
748
+ return outMosaic
686
749
 
687
750
  #=======================================================================
688
751
  def _mosaicVRT(self,
@@ -729,10 +792,26 @@ class portstarObj(object):
729
792
  # Iterate each sublist of images
730
793
  outMosaic = []
731
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
732
810
 
733
811
  # Set output file names
734
812
  fileSuffix = os.path.split(os.path.dirname(imgs[0]))[-1] + '_mosaic_'+str(i)+'.vrt'
735
813
  filePrefix = os.path.split(self.port.projDir)[-1]
814
+ sonar_token = self._get_sonar_mosaic_name_token()
736
815
  if 'substrate' in fileSuffix:
737
816
  outDir = os.path.join(self.port.substrateDir, 'map_substrate_mosaic')
738
817
  outVRT = os.path.join(outDir, filePrefix+'_'+fileSuffix)
@@ -744,7 +823,10 @@ class portstarObj(object):
744
823
  outVRT = os.path.join(outDir, filePrefix+'_'+'class_'+str(bands[0]-1)+'_'+fileSuffix)
745
824
  else:
746
825
  outDir = os.path.join(self.port.projDir, 'sonar_mosaic')
747
- 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)
748
830
 
749
831
  if not os.path.isdir(outDir):
750
832
  try:
@@ -758,7 +840,7 @@ class portstarObj(object):
758
840
 
759
841
  # Build VRT
760
842
  # vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands, xRes=xRes, yRes=yRes)
761
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands)
843
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
762
844
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
763
845
 
764
846
  # Generate overviews
@@ -1213,7 +1295,7 @@ class portstarObj(object):
1213
1295
  port = []
1214
1296
  for pdi, pdc in zip(portDepPix, portDepPixFinal):
1215
1297
  if np.isnan(pdc): # Final pick is nan
1216
- if np.isnan(pdc): # Initial pick is nan
1298
+ if np.isnan(pdi): # Initial pick is nan
1217
1299
  port.append(0) # set to 0
1218
1300
  else: # Initial pick not nan
1219
1301
  port.append(pdi) # Use initial pick
@@ -1234,7 +1316,7 @@ class portstarObj(object):
1234
1316
  star.append(sdi) # Use initial pick
1235
1317
 
1236
1318
  elif sdc < 0:
1237
- port.append(0)
1319
+ star.append(0)
1238
1320
 
1239
1321
  else: # Final pick ok
1240
1322
  star.append(sdc)
@@ -1485,8 +1567,25 @@ class portstarObj(object):
1485
1567
  chunks = pd.unique(portDF['chunk_id'])
1486
1568
 
1487
1569
  if detectDep == 0:
1488
- portInstDepth = portDF['inst_dep_m']
1489
- 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)
1490
1589
 
1491
1590
  if smthDep:
1492
1591
  # print("\nSmoothing depth values...")
@@ -1512,8 +1611,8 @@ class portstarObj(object):
1512
1611
  portDF['dep_m'] = portInstDepth
1513
1612
  starDF['dep_m'] = starInstDepth
1514
1613
 
1515
- portDF['dep_m_Method'] = 'Instrument Depth'
1516
- starDF['dep_m_Method'] = 'Instrument Depth'
1614
+ portDF['dep_m_Method'] = 'Instrument/Metadata Depth'
1615
+ starDF['dep_m_Method'] = 'Instrument/Metadata Depth'
1517
1616
 
1518
1617
  portDF['dep_m_smth'] = smthDep
1519
1618
  starDF['dep_m_smth'] = smthDep
@@ -1615,11 +1714,17 @@ class portstarObj(object):
1615
1714
  starDep[starDep == 0] = np.nan
1616
1715
 
1617
1716
  nans, x = np.isnan(portDep), lambda z: z.nonzero()[0]
1618
- 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
1619
1721
  portDF['dep_m'] = portDep
1620
1722
 
1621
1723
  nans, x = np.isnan(starDep), lambda z: z.nonzero()[0]
1622
- 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
1623
1728
  starDF['dep_m'] = starDep
1624
1729
 
1625
1730
  # Export to csv
@@ -1700,10 +1805,16 @@ class portstarObj(object):
1700
1805
  portDF = portDF.loc[portDF['chunk_id'] == i, ['inst_dep_m', 'dep_m', 'pixM']]
1701
1806
  starDF = starDF.loc[starDF['chunk_id'] == i, ['inst_dep_m', 'dep_m', 'pixM']]
1702
1807
 
1703
- 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)
1704
1812
  portAuto = (portDF['dep_m'] / portDF['pixM']).to_numpy(dtype=int, copy=True)
1705
1813
 
1706
- 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)
1707
1818
  starAuto = (starDF['dep_m'] / starDF['pixM']).to_numpy(dtype=int, copy=True)
1708
1819
 
1709
1820
  # Ensure port/star same length