pingmapper 5.4.6__tar.gz → 5.4.9__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 (60) hide show
  1. {pingmapper-5.4.6 → pingmapper-5.4.9}/PKG-INFO +44 -9
  2. {pingmapper-5.4.6 → pingmapper-5.4.9}/README.md +43 -8
  3. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/class_mapSubstrateObj.py +7 -4
  4. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/class_portstarObj.py +48 -17
  5. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/class_rectObj.py +5 -3
  6. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/self_check.py +6 -1
  7. pingmapper-5.4.9/pingmapper/test_cli_self_check.py +118 -0
  8. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/test_dq_filter.py +199 -0
  9. pingmapper-5.4.9/pingmapper/version.py +1 -0
  10. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper.egg-info/PKG-INFO +44 -9
  11. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper.egg-info/SOURCES.txt +1 -0
  12. pingmapper-5.4.6/pingmapper/version.py +0 -1
  13. {pingmapper-5.4.6 → pingmapper-5.4.9}/LICENSE +0 -0
  14. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/__init__.py +0 -0
  15. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/__main__.py +0 -0
  16. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/class_sonObj.py +0 -0
  17. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/class_sonObj_nadirgaptest.py +0 -0
  18. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/default_params.json +0 -0
  19. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/doWork.py +0 -0
  20. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/funcs_common.py +0 -0
  21. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/funcs_model.py +0 -0
  22. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/funcs_rectify.py +0 -0
  23. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/gui_main.py +0 -0
  24. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/main_mapSubstrate.py +0 -0
  25. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/main_readFiles.py +0 -0
  26. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/main_rectify.py +0 -0
  27. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/nonGUI_batch_main.py +0 -0
  28. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/nonGui_main.py +0 -0
  29. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0926.py +0 -0
  30. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0929.py +0 -0
  31. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/scratch/funcs_pyhum_correct.py +0 -0
  32. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/scratch/main.py +0 -0
  33. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/scratch/main_batchDirectory.py +0 -0
  34. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/test_PINGMapper.py +0 -0
  35. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/test_time.py +0 -0
  36. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py +0 -0
  37. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/DRAFT_Workflows/gen_centerline.py +0 -0
  38. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/DRAFT_Workflows/gen_centerline_from_bankline.py +0 -0
  39. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/DRAFT_Workflows/gen_centerline_trkpnts_fitspline_DRAFT.py +0 -0
  40. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py +0 -0
  41. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/RawEGN_avg_predictions.py +0 -0
  42. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/00_substrate_logits_mosaic_transects.py +0 -0
  43. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/00_substrate_shps_mosaic_transects.py +0 -0
  44. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/01_gen_centerline_from_coverage.py +0 -0
  45. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/02_gen_summary_stamp_shps.py +0 -0
  46. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py +0 -0
  47. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/04_combine_summary_shp_csv.py +0 -0
  48. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/05_gen_summary_shp_plots.py +0 -0
  49. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/06_compare_raw-egn_volume.py +0 -0
  50. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/08_raw-egn_hardReacheFreq_hist.py +0 -0
  51. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/09_raw-egn_PatchSize_density.py +0 -0
  52. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/Substrate_Summaries/summarize_project_substrate.py +0 -0
  53. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/export_coverage.py +0 -0
  54. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper/utils/main_mosaic_transects.py +0 -0
  55. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper.egg-info/dependency_links.txt +0 -0
  56. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper.egg-info/requires.txt +0 -0
  57. {pingmapper-5.4.6 → pingmapper-5.4.9}/pingmapper.egg-info/top_level.txt +0 -0
  58. {pingmapper-5.4.6 → pingmapper-5.4.9}/pyproject.toml +0 -0
  59. {pingmapper-5.4.6 → pingmapper-5.4.9}/setup.cfg +0 -0
  60. {pingmapper-5.4.6 → pingmapper-5.4.9}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.4.6
3
+ Version: 5.4.9
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, Iury T Simoes-Sousa
6
6
  Author-email: bodine.cs@gmail.email
@@ -69,6 +69,15 @@ Dynamic: requires-python
69
69
 
70
70
  `PINGMapper` is an open-source Python interface for reading and processing side scan sonar datasets and reproducibly mapping benthic habitat features. `PINGMapper` transforms recreation-grade sonar systems (i.e. fishfinders) into scientific data collectors, allowing researchers and citizens alike to reproducibly map their aquatic system with minimal expertise in data processing.
71
71
 
72
+ ## PING Ecosystem
73
+
74
+ `PINGMapper` is part of the [**PING Ecosystem**](https://github.com/PINGEcosystem): a coordinated set of tools for installation, conversion, quality checks, and mapping workflows.
75
+
76
+ - [PINGMapper](https://github.com/CameronBodine/PINGMapper): Core sonar processing and mapping package
77
+ - [PINGInstaller](https://github.com/CameronBodine/PINGInstaller): Recommended installer and environment bootstrap
78
+ - [PINGWizard](https://github.com/CameronBodine/PINGWizard): Point-and-click interface for testing, updates, and routine processing
79
+ - [PINGVerter](https://github.com/CameronBodine/PINGVerter): Sonar data conversion utilities
80
+
72
81
  ## Learn More Now!
73
82
  Check out PING-Mapper's [website](https://cameronbodine.github.io/PINGMapper/) for most up to date information:
74
83
 
@@ -128,7 +137,7 @@ A manuscript detailing the automated substrate mapping features made availabe in
128
137
 
129
138
  ### v3.0 - 4.0
130
139
 
131
- Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEcosystem.md) is made possible through mentorship, partnerships, and financial support listed below.
140
+ Continued support for PINGMapper and tools in the [PING Ecosystem](#ping-ecosystem) is made possible through mentorship, partnerships, and financial support listed below.
132
141
 
133
142
  **Primary Developer:** [Dr. Cameron S. Bodine](https://github.com/CameronBodine)
134
143
 
@@ -142,7 +151,24 @@ Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEc
142
151
 
143
152
  ## Quick Start
144
153
 
145
- ### Option A: Conda
154
+ Recommended workflow: install Miniforge, use PINGInstaller to set up the `ping` environment, and launch PINGWizard for testing, updates, and routine processing.
155
+
156
+ ### Recommended: Miniforge + PINGInstaller + PINGWizard
157
+
158
+ Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions. In short:
159
+
160
+ ```bash
161
+ pip install pinginstaller -U
162
+ python -m pinginstaller
163
+ ```
164
+
165
+ After installation completes, launch PINGWizard using the shortcut created by PINGInstaller, or run:
166
+
167
+ ```bash
168
+ conda run -n ping python -m pingwizard
169
+ ```
170
+
171
+ ### Alternative: Direct conda environment
146
172
 
147
173
  Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions, or simply run:
148
174
 
@@ -152,9 +178,9 @@ conda activate ping
152
178
  python -m pingmapper gui
153
179
  ```
154
180
 
155
- ### Option B: Pixi
181
+ ### Optional: Pixi
156
182
 
157
- [Pixi](https://pixi.sh) manages all dependencies (including GDAL) automatically from a single `pyproject.toml`.
183
+ [Pixi](https://pixi.sh) is available for users who prefer that workflow, but it is not the recommended installation path.
158
184
 
159
185
  1. [Install pixi](https://pixi.sh)
160
186
  2. Clone and run:
@@ -170,12 +196,18 @@ python -m pingmapper gui
170
196
 
171
197
  ### Verify Installation
172
198
 
173
- Run a lightweight self-check (same check used in CI):
199
+ Recommended routine verification is to run the included tests from PINGWizard. If you want a prompt-based check, run the lightweight self-check below:
174
200
 
175
201
  ```bash
176
202
  python -m pingmapper check
177
203
  ```
178
204
 
205
+ Direct unit-test invocation (same lightweight suite run by `check`):
206
+
207
+ ```bash
208
+ python -m unittest pingmapper.test_dq_filter pingmapper.test_cli_self_check -v
209
+ ```
210
+
179
211
  Optional longer end-to-end processing validation:
180
212
 
181
213
  ```bash
@@ -190,9 +222,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
190
222
 
191
223
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
192
224
 
193
- # PING-Mapper is part of the Doodleverse!
194
- ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
225
+ # PING-Mapper is part of the PING Ecosystem
226
+
195
227
 
196
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
228
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
197
229
 
230
+ PINGMapper also leverages components from the Doodleverse ecosystem for geoscientific image segmentation workflows. Learn more on [GitHub](https://github.com/Doodleverse).
231
+
232
+ ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
198
233
 
@@ -31,6 +31,15 @@
31
31
 
32
32
  `PINGMapper` is an open-source Python interface for reading and processing side scan sonar datasets and reproducibly mapping benthic habitat features. `PINGMapper` transforms recreation-grade sonar systems (i.e. fishfinders) into scientific data collectors, allowing researchers and citizens alike to reproducibly map their aquatic system with minimal expertise in data processing.
33
33
 
34
+ ## PING Ecosystem
35
+
36
+ `PINGMapper` is part of the [**PING Ecosystem**](https://github.com/PINGEcosystem): a coordinated set of tools for installation, conversion, quality checks, and mapping workflows.
37
+
38
+ - [PINGMapper](https://github.com/CameronBodine/PINGMapper): Core sonar processing and mapping package
39
+ - [PINGInstaller](https://github.com/CameronBodine/PINGInstaller): Recommended installer and environment bootstrap
40
+ - [PINGWizard](https://github.com/CameronBodine/PINGWizard): Point-and-click interface for testing, updates, and routine processing
41
+ - [PINGVerter](https://github.com/CameronBodine/PINGVerter): Sonar data conversion utilities
42
+
34
43
  ## Learn More Now!
35
44
  Check out PING-Mapper's [website](https://cameronbodine.github.io/PINGMapper/) for most up to date information:
36
45
 
@@ -90,7 +99,7 @@ A manuscript detailing the automated substrate mapping features made availabe in
90
99
 
91
100
  ### v3.0 - 4.0
92
101
 
93
- Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEcosystem.md) is made possible through mentorship, partnerships, and financial support listed below.
102
+ Continued support for PINGMapper and tools in the [PING Ecosystem](#ping-ecosystem) is made possible through mentorship, partnerships, and financial support listed below.
94
103
 
95
104
  **Primary Developer:** [Dr. Cameron S. Bodine](https://github.com/CameronBodine)
96
105
 
@@ -104,7 +113,24 @@ Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEc
104
113
 
105
114
  ## Quick Start
106
115
 
107
- ### Option A: Conda
116
+ Recommended workflow: install Miniforge, use PINGInstaller to set up the `ping` environment, and launch PINGWizard for testing, updates, and routine processing.
117
+
118
+ ### Recommended: Miniforge + PINGInstaller + PINGWizard
119
+
120
+ Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions. In short:
121
+
122
+ ```bash
123
+ pip install pinginstaller -U
124
+ python -m pinginstaller
125
+ ```
126
+
127
+ After installation completes, launch PINGWizard using the shortcut created by PINGInstaller, or run:
128
+
129
+ ```bash
130
+ conda run -n ping python -m pingwizard
131
+ ```
132
+
133
+ ### Alternative: Direct conda environment
108
134
 
109
135
  Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions, or simply run:
110
136
 
@@ -114,9 +140,9 @@ conda activate ping
114
140
  python -m pingmapper gui
115
141
  ```
116
142
 
117
- ### Option B: Pixi
143
+ ### Optional: Pixi
118
144
 
119
- [Pixi](https://pixi.sh) manages all dependencies (including GDAL) automatically from a single `pyproject.toml`.
145
+ [Pixi](https://pixi.sh) is available for users who prefer that workflow, but it is not the recommended installation path.
120
146
 
121
147
  1. [Install pixi](https://pixi.sh)
122
148
  2. Clone and run:
@@ -132,12 +158,18 @@ python -m pingmapper gui
132
158
 
133
159
  ### Verify Installation
134
160
 
135
- Run a lightweight self-check (same check used in CI):
161
+ Recommended routine verification is to run the included tests from PINGWizard. If you want a prompt-based check, run the lightweight self-check below:
136
162
 
137
163
  ```bash
138
164
  python -m pingmapper check
139
165
  ```
140
166
 
167
+ Direct unit-test invocation (same lightweight suite run by `check`):
168
+
169
+ ```bash
170
+ python -m unittest pingmapper.test_dq_filter pingmapper.test_cli_self_check -v
171
+ ```
172
+
141
173
  Optional longer end-to-end processing validation:
142
174
 
143
175
  ```bash
@@ -152,9 +184,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
152
184
 
153
185
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
154
186
 
155
- # PING-Mapper is part of the Doodleverse!
156
- ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
187
+ # PING-Mapper is part of the PING Ecosystem
188
+
157
189
 
158
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
190
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
159
191
 
192
+ PINGMapper also leverages components from the Doodleverse ecosystem for geoscientific image segmentation workflows. Learn more on [GitHub](https://github.com/Doodleverse).
193
+
194
+ ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
160
195
 
@@ -29,7 +29,7 @@
29
29
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
30
  # SOFTWARE.
31
31
 
32
- import os, sys
32
+ import os, sys, re
33
33
 
34
34
  # Add 'pingmapper' to the path, may not need after pypi package...
35
35
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -1066,9 +1066,12 @@ class mapSubObj(rectObj):
1066
1066
 
1067
1067
  # Extract chunkID from filename and store in dict
1068
1068
  for n in npzs:
1069
- c = os.path.basename(n)
1070
- c = c.split('.')[0]
1071
- c = int(c.split('_')[-1])
1069
+ c = os.path.splitext(os.path.basename(n))[0]
1070
+ match = re.search(r'_(\d+)$', c)
1071
+ if match is None:
1072
+ continue
1073
+
1074
+ c = int(match.group(1))
1072
1075
  toMap[c] = n
1073
1076
 
1074
1077
  del npzDir, npzs
@@ -714,8 +714,18 @@ class portstarObj(object):
714
714
  except:
715
715
  pass
716
716
 
717
- # First built a vrt
718
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
717
+ # For substrate/prediction mosaics, enforce configured map resolution.
718
+ force_map_res = (not son) and (self.port.pix_res_map != 0)
719
+ if force_map_res:
720
+ vrt_options = gdal.BuildVRTOptions(
721
+ resampleAlg=resampleAlg,
722
+ bandList=bands_to_use,
723
+ xRes=self.port.pix_res_map,
724
+ yRes=self.port.pix_res_map,
725
+ targetAlignedPixels=True,
726
+ )
727
+ else:
728
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
719
729
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
720
730
 
721
731
  # Create GeoTiff from vrt
@@ -733,7 +743,10 @@ class portstarObj(object):
733
743
  # gdal.Translate(outTIF, ds, xRes=xRes, yRes=yRes, **kwargs)
734
744
 
735
745
  # Create geotiff
736
- gdal.Translate(outTIF, ds, **kwargs)
746
+ if force_map_res:
747
+ gdal.Translate(outTIF, ds, xRes=self.port.pix_res_map, yRes=self.port.pix_res_map, **kwargs)
748
+ else:
749
+ gdal.Translate(outTIF, ds, **kwargs)
737
750
 
738
751
  # Generate overviews
739
752
  if overview:
@@ -841,7 +854,17 @@ class portstarObj(object):
841
854
 
842
855
  # Build VRT
843
856
  # vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands, xRes=xRes, yRes=yRes)
844
- vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
857
+ force_map_res = (not son) and (self.port.pix_res_map != 0)
858
+ if force_map_res:
859
+ vrt_options = gdal.BuildVRTOptions(
860
+ resampleAlg=resampleAlg,
861
+ bandList=bands_to_use,
862
+ xRes=self.port.pix_res_map,
863
+ yRes=self.port.pix_res_map,
864
+ targetAlignedPixels=True,
865
+ )
866
+ else:
867
+ vrt_options = gdal.BuildVRTOptions(resampleAlg=resampleAlg, bandList = bands_to_use)
845
868
  gdal.BuildVRT(outVRT, imgs, options=vrt_options)
846
869
 
847
870
  # Generate overviews
@@ -1981,26 +2004,34 @@ class portstarObj(object):
1981
2004
 
1982
2005
  # If sides diverge strongly, invalidate the side farther from
1983
2006
  # instrument depth so interpolation can recover continuity.
1984
- pair_valid = np.isfinite(portArr) & np.isfinite(starArr) & (portArr > 0) & (starArr > 0)
1985
- diverge = pair_valid & (np.abs(portArr - starArr) > 5.0)
2007
+ # Port and star may have different ping counts; only compare the
2008
+ # overlapping rows.
2009
+ min_len = min(len(portArr), len(starArr))
2010
+ pA = portArr[:min_len]
2011
+ sA = starArr[:min_len]
2012
+ pI = portInst[:min_len]
2013
+ sI = starInst[:min_len]
2014
+
2015
+ pair_valid = np.isfinite(pA) & np.isfinite(sA) & (pA > 0) & (sA > 0)
2016
+ diverge = pair_valid & (np.abs(pA - sA) > 5.0)
1986
2017
  if diverge.any():
1987
- portErr = np.abs(portArr - portInst)
1988
- starErr = np.abs(starArr - starInst)
2018
+ portErr = np.abs(pA - pI)
2019
+ starErr = np.abs(sA - sI)
1989
2020
 
1990
- inst_pair_valid = np.isfinite(portInst) & (portInst > 0) & np.isfinite(starInst) & (starInst > 0)
2021
+ inst_pair_valid = np.isfinite(pI) & (pI > 0) & np.isfinite(sI) & (sI > 0)
1991
2022
  choose_by_inst = diverge & inst_pair_valid
1992
- portFlags |= choose_by_inst & (portErr >= starErr)
1993
- starFlags |= choose_by_inst & (starErr > portErr)
2023
+ portFlags[:min_len] |= choose_by_inst & (portErr >= starErr)
2024
+ starFlags[:min_len] |= choose_by_inst & (starErr > portErr)
1994
2025
 
1995
2026
  # Fallback for rows without valid instrument depth on one/both sides.
1996
2027
  fallback = diverge & (~inst_pair_valid)
1997
2028
  if fallback.any():
1998
- pmed = _rolling_median(portArr)
1999
- smed = _rolling_median(starArr)
2000
- presid = np.abs(portArr - pmed)
2001
- sresid = np.abs(starArr - smed)
2002
- portFlags |= fallback & (presid >= sresid)
2003
- starFlags |= fallback & (sresid > presid)
2029
+ pmed = _rolling_median(pA)
2030
+ smed = _rolling_median(sA)
2031
+ presid = np.abs(pA - pmed)
2032
+ sresid = np.abs(sA - smed)
2033
+ portFlags[:min_len] |= fallback & (presid >= sresid)
2034
+ starFlags[:min_len] |= fallback & (sresid > presid)
2004
2035
 
2005
2036
  portArr[portFlags] = np.nan
2006
2037
  starArr[starFlags] = np.nan
@@ -2213,6 +2213,8 @@ class rectObj(sonObj):
2213
2213
  crs='EPSG:4326',
2214
2214
  ).to_crs(self.humDat['epsg'])
2215
2215
 
2216
+ df[x_col] = df[x_col].astype(float)
2217
+ df[y_col] = df[y_col].astype(float)
2216
2218
  df.loc[need_xy, x_col] = geo.geometry.x.to_numpy()
2217
2219
  df.loc[need_xy, y_col] = geo.geometry.y.to_numpy()
2218
2220
  return df
@@ -2407,7 +2409,7 @@ class rectObj(sonObj):
2407
2409
  NA
2408
2410
  '''
2409
2411
  filterIntensity = False
2410
- pix_res = self.pix_res_son
2412
+ pix_res = self.pix_res_son if son else self.pix_res_map
2411
2413
  do_resize = True
2412
2414
 
2413
2415
  # # Set pixel resolution
@@ -2784,7 +2786,7 @@ class rectObj(sonObj):
2784
2786
  del out, img
2785
2787
 
2786
2788
  if do_resize:
2787
- self._pixresResize(gtiff)
2789
+ self._pixresResize(gtiff, son=son)
2788
2790
 
2789
2791
  if self.rect_wcr:
2790
2792
  if son:
@@ -2930,7 +2932,7 @@ class rectObj(sonObj):
2930
2932
  del out
2931
2933
 
2932
2934
  if do_resize:
2933
- self._pixresResize(gtiff)
2935
+ self._pixresResize(gtiff, son=son)
2934
2936
 
2935
2937
  gc.collect()
2936
2938
 
@@ -20,6 +20,11 @@ REQUIRED_MODULES = [
20
20
  "pingverter",
21
21
  ]
22
22
 
23
+ UNIT_TEST_MODULES = [
24
+ "pingmapper.test_dq_filter",
25
+ "pingmapper.test_cli_self_check",
26
+ ]
27
+
23
28
 
24
29
  def _missing_required_modules() -> list[str]:
25
30
  """Return required modules that fail to import."""
@@ -46,7 +51,7 @@ def run_self_check(verbose: bool = True) -> int:
46
51
 
47
52
  print("Dependency import checks passed.")
48
53
 
49
- suite = unittest.defaultTestLoader.loadTestsFromName("pingmapper.test_dq_filter")
54
+ suite = unittest.defaultTestLoader.loadTestsFromNames(UNIT_TEST_MODULES)
50
55
  runner = unittest.TextTestRunner(verbosity=2 if verbose else 1)
51
56
  result = runner.run(suite)
52
57
 
@@ -0,0 +1,118 @@
1
+ """Unit tests for CLI dispatch and self-check behavior."""
2
+
3
+ import importlib
4
+ import sys
5
+ import types
6
+ import unittest
7
+ from unittest import mock
8
+
9
+
10
+ class TestCliDispatch(unittest.TestCase):
11
+ """Validate command dispatch logic in pingmapper.__main__."""
12
+
13
+ def test_main_check_dispatches_to_self_check(self):
14
+ cli = importlib.import_module('pingmapper.__main__')
15
+
16
+ fake_mod = types.ModuleType('pingmapper.self_check')
17
+ fake_run = mock.Mock(return_value=7)
18
+ fake_mod.run_self_check = fake_run
19
+
20
+ with mock.patch.dict(sys.modules, {'pingmapper.self_check': fake_mod}):
21
+ result = cli.main('check')
22
+
23
+ self.assertEqual(result, 7)
24
+ fake_run.assert_called_once_with(verbose=True)
25
+
26
+ def test_main_test_unit_dispatches_to_self_check(self):
27
+ cli = importlib.import_module('pingmapper.__main__')
28
+
29
+ fake_mod = types.ModuleType('pingmapper.self_check')
30
+ fake_run = mock.Mock(return_value=3)
31
+ fake_mod.run_self_check = fake_run
32
+
33
+ with mock.patch.dict(sys.modules, {'pingmapper.self_check': fake_mod}):
34
+ result = cli.main('test_unit')
35
+
36
+ self.assertEqual(result, 3)
37
+ fake_run.assert_called_once_with(verbose=True)
38
+
39
+ def test_main_unknown_process_returns_zero(self):
40
+ cli = importlib.import_module('pingmapper.__main__')
41
+ self.assertEqual(cli.main('not-a-command'), 0)
42
+
43
+ def test_module_sets_default_process_to_gui_when_no_args(self):
44
+ cli = importlib.import_module('pingmapper.__main__')
45
+
46
+ with mock.patch.object(sys, 'argv', ['python']):
47
+ cli = importlib.reload(cli)
48
+ self.assertEqual(cli.to_do, 'gui')
49
+
50
+ def test_module_uses_first_arg_as_process(self):
51
+ cli = importlib.import_module('pingmapper.__main__')
52
+
53
+ with mock.patch.object(sys, 'argv', ['python', 'check']):
54
+ cli = importlib.reload(cli)
55
+ self.assertEqual(cli.to_do, 'check')
56
+
57
+
58
+ class TestSelfCheck(unittest.TestCase):
59
+ """Validate dependency-check and test-run exit code behavior."""
60
+
61
+ def test_missing_required_modules_collects_only_failures(self):
62
+ self_check = importlib.import_module('pingmapper.self_check')
63
+
64
+ def _fake_import(name):
65
+ if name in {'numpy', 'pingwizard'}:
66
+ raise ImportError('missing')
67
+ return object()
68
+
69
+ with mock.patch.object(self_check.importlib, 'import_module', side_effect=_fake_import):
70
+ missing = self_check._missing_required_modules()
71
+
72
+ self.assertEqual(missing, ['numpy', 'pingwizard'])
73
+
74
+ def test_run_self_check_returns_1_when_dependencies_missing(self):
75
+ self_check = importlib.import_module('pingmapper.self_check')
76
+
77
+ with mock.patch.object(self_check, '_missing_required_modules', return_value=['numpy']):
78
+ code = self_check.run_self_check(verbose=False)
79
+
80
+ self.assertEqual(code, 1)
81
+
82
+ def test_run_self_check_returns_1_when_tests_fail(self):
83
+ self_check = importlib.import_module('pingmapper.self_check')
84
+
85
+ fake_suite = object()
86
+ fake_result = types.SimpleNamespace(wasSuccessful=lambda: False)
87
+ fake_runner = mock.Mock()
88
+ fake_runner.run.return_value = fake_result
89
+
90
+ with mock.patch.object(self_check, '_missing_required_modules', return_value=[]), \
91
+ mock.patch.object(self_check.unittest.defaultTestLoader, 'loadTestsFromNames', return_value=fake_suite) as load_mock, \
92
+ mock.patch.object(self_check.unittest, 'TextTestRunner', return_value=fake_runner):
93
+ code = self_check.run_self_check(verbose=False)
94
+
95
+ self.assertEqual(code, 1)
96
+ load_mock.assert_called_once_with(self_check.UNIT_TEST_MODULES)
97
+ fake_runner.run.assert_called_once_with(fake_suite)
98
+
99
+ def test_run_self_check_returns_0_when_tests_pass(self):
100
+ self_check = importlib.import_module('pingmapper.self_check')
101
+
102
+ fake_suite = object()
103
+ fake_result = types.SimpleNamespace(wasSuccessful=lambda: True)
104
+ fake_runner = mock.Mock()
105
+ fake_runner.run.return_value = fake_result
106
+
107
+ with mock.patch.object(self_check, '_missing_required_modules', return_value=[]), \
108
+ mock.patch.object(self_check.unittest.defaultTestLoader, 'loadTestsFromNames', return_value=fake_suite) as load_mock, \
109
+ mock.patch.object(self_check.unittest, 'TextTestRunner', return_value=fake_runner):
110
+ code = self_check.run_self_check(verbose=False)
111
+
112
+ self.assertEqual(code, 0)
113
+ load_mock.assert_called_once_with(self_check.UNIT_TEST_MODULES)
114
+ fake_runner.run.assert_called_once_with(fake_suite)
115
+
116
+
117
+ if __name__ == '__main__':
118
+ unittest.main()
@@ -199,5 +199,204 @@ class TestDQFilterEventStateBlocks(unittest.TestCase):
199
199
  self.assertEqual(kept, [2, 3, 6])
200
200
 
201
201
 
202
+ class TestDQFilterValidation(unittest.TestCase):
203
+ """Input validation and parsing error cases for DQ filtering."""
204
+
205
+ def test_missing_dq_time_field_raises(self):
206
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}])
207
+ path = _dq_csv([{'ts': 1.0, 'flag': 'Use'}])
208
+
209
+ with self.assertRaisesRegex(ValueError, 'dq_time_field is required'):
210
+ stub._filterDQ(
211
+ stub.sonMetaDF,
212
+ dq_table=path,
213
+ dq_time_field='',
214
+ dq_flag_field='flag',
215
+ dq_keep_values=['Use'],
216
+ dq_src_utc_offset=0.0,
217
+ dq_target_utc_offset=0.0,
218
+ dq_time_offset=0.0,
219
+ )
220
+
221
+ def test_missing_dq_flag_field_raises(self):
222
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}])
223
+ path = _dq_csv([{'ts': 1.0, 'flag': 'Use'}])
224
+
225
+ with self.assertRaisesRegex(ValueError, 'dq_flag_field is required'):
226
+ stub._filterDQ(
227
+ stub.sonMetaDF,
228
+ dq_table=path,
229
+ dq_time_field='ts',
230
+ dq_flag_field='',
231
+ dq_keep_values=['Use'],
232
+ dq_src_utc_offset=0.0,
233
+ dq_target_utc_offset=0.0,
234
+ dq_time_offset=0.0,
235
+ )
236
+
237
+ def test_missing_required_csv_column_raises(self):
238
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}])
239
+ path = _dq_csv([{'ts': 1.0, 'not_flag': 'Use'}])
240
+
241
+ with self.assertRaisesRegex(ValueError, 'dqLog missing required column'):
242
+ stub._filterDQ(
243
+ stub.sonMetaDF,
244
+ dq_table=path,
245
+ dq_time_field='ts',
246
+ dq_flag_field='flag',
247
+ dq_keep_values=['Use'],
248
+ dq_src_utc_offset=0.0,
249
+ dq_target_utc_offset=0.0,
250
+ dq_time_offset=0.0,
251
+ )
252
+
253
+ def test_timestamp_type_mismatch_raises(self):
254
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}, {'time_s': 3.0}])
255
+ path = _dq_csv([{'ts': '2024-01-01T00:00:00Z', 'flag': 'Use'}])
256
+
257
+ with self.assertRaisesRegex(ValueError, 'timestamp type'):
258
+ stub._filterDQ(
259
+ stub.sonMetaDF,
260
+ dq_table=path,
261
+ dq_time_field='ts',
262
+ dq_flag_field='flag',
263
+ dq_keep_values=['Use'],
264
+ dq_src_utc_offset=0.0,
265
+ dq_target_utc_offset=0.0,
266
+ dq_time_offset=0.0,
267
+ )
268
+
269
+ def test_empty_keep_values_raise(self):
270
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}])
271
+ path = _dq_csv([{'ts': 1.0, 'flag': 'Use'}])
272
+
273
+ with self.assertRaisesRegex(ValueError, 'dq_keep_values must contain at least one value'):
274
+ stub._filterDQ(
275
+ stub.sonMetaDF,
276
+ dq_table=path,
277
+ dq_time_field='ts',
278
+ dq_flag_field='flag',
279
+ dq_keep_values=[],
280
+ dq_src_utc_offset=0.0,
281
+ dq_target_utc_offset=0.0,
282
+ dq_time_offset=0.0,
283
+ )
284
+
285
+
286
+ class TestDQKeepValueNormalization(unittest.TestCase):
287
+ """Normalization of dq_keep_values and DQ flag values."""
288
+
289
+ def test_keep_values_normalize_case_and_whitespace(self):
290
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}, {'time_s': 3.0}])
291
+ path = _dq_csv([
292
+ {'ts': 1.5, 'flag': ' usE '},
293
+ ])
294
+
295
+ result = stub._filterDQ(
296
+ stub.sonMetaDF,
297
+ dq_table=path,
298
+ dq_time_field='ts',
299
+ dq_flag_field='flag',
300
+ dq_keep_values=' use , keep ',
301
+ dq_src_utc_offset=0.0,
302
+ dq_target_utc_offset=0.0,
303
+ dq_time_offset=0.0,
304
+ )
305
+
306
+ kept = list(result[result['filter_dq'] == True].index)
307
+ self.assertEqual(kept, [1, 2])
308
+
309
+
310
+ class TestDQUtcOffsetValidation(unittest.TestCase):
311
+ """Validation around UTC offset parameters for datetime DQ logs."""
312
+
313
+ def test_setting_only_one_utc_offset_raises(self):
314
+ stub = _make_stub([
315
+ {'date': '2024-01-01', 'time': '00:00:01', 'time_s': 1.0},
316
+ {'date': '2024-01-01', 'time': '00:00:02', 'time_s': 2.0},
317
+ ])
318
+ path = _dq_csv([
319
+ {'ts': '2024-01-01 00:00:00', 'flag': 'Use'},
320
+ ])
321
+
322
+ with self.assertRaisesRegex(ValueError, 'must both be provided'):
323
+ stub._filterDQ(
324
+ stub.sonMetaDF,
325
+ dq_table=path,
326
+ dq_time_field='ts',
327
+ dq_flag_field='flag',
328
+ dq_keep_values=['Use'],
329
+ dq_src_utc_offset=0.0,
330
+ dq_target_utc_offset=None,
331
+ dq_time_offset=0.0,
332
+ )
333
+
334
+ def test_non_numeric_utc_offset_raises(self):
335
+ stub = _make_stub([
336
+ {'date': '2024-01-01', 'time': '00:00:01', 'time_s': 1.0},
337
+ {'date': '2024-01-01', 'time': '00:00:02', 'time_s': 2.0},
338
+ ])
339
+ path = _dq_csv([
340
+ {'ts': '2024-01-01 00:00:00', 'flag': 'Use'},
341
+ ])
342
+
343
+ with self.assertRaisesRegex(ValueError, 'must be a numeric UTC offset'):
344
+ stub._filterDQ(
345
+ stub.sonMetaDF,
346
+ dq_table=path,
347
+ dq_time_field='ts',
348
+ dq_flag_field='flag',
349
+ dq_keep_values=['Use'],
350
+ dq_src_utc_offset='east',
351
+ dq_target_utc_offset=0.0,
352
+ dq_time_offset=0.0,
353
+ )
354
+
355
+
356
+ class TestDQEventBoundaries(unittest.TestCase):
357
+ """Boundary behavior around event timestamps and duplicate event rows."""
358
+
359
+ def test_ping_exactly_at_event_time_kept(self):
360
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}, {'time_s': 3.0}])
361
+ path = _dq_csv([
362
+ {'ts': 2.0, 'flag': 'Use'},
363
+ ])
364
+
365
+ result = stub._filterDQ(
366
+ stub.sonMetaDF,
367
+ dq_table=path,
368
+ dq_time_field='ts',
369
+ dq_flag_field='flag',
370
+ dq_keep_values=['Use'],
371
+ dq_src_utc_offset=0.0,
372
+ dq_target_utc_offset=0.0,
373
+ dq_time_offset=0.0,
374
+ )
375
+
376
+ kept = list(result[result['filter_dq'] == True].index)
377
+ self.assertEqual(kept, [1, 2])
378
+
379
+ def test_duplicate_event_timestamp_last_row_wins(self):
380
+ stub = _make_stub([{'time_s': 1.0}, {'time_s': 2.0}, {'time_s': 3.0}])
381
+ path = _dq_csv([
382
+ {'ts': 2.0, 'flag': 'Use'},
383
+ {'ts': 2.0, 'flag': 'NoUse'},
384
+ ])
385
+
386
+ result = stub._filterDQ(
387
+ stub.sonMetaDF,
388
+ dq_table=path,
389
+ dq_time_field='ts',
390
+ dq_flag_field='flag',
391
+ dq_keep_values=['Use'],
392
+ dq_src_utc_offset=0.0,
393
+ dq_target_utc_offset=0.0,
394
+ dq_time_offset=0.0,
395
+ )
396
+
397
+ kept = list(result[result['filter_dq'] == True].index)
398
+ self.assertEqual(kept, [])
399
+
400
+
202
401
  if __name__ == '__main__':
203
402
  unittest.main()
@@ -0,0 +1 @@
1
+ __version__ = '5.4.9'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.4.6
3
+ Version: 5.4.9
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, Iury T Simoes-Sousa
6
6
  Author-email: bodine.cs@gmail.email
@@ -69,6 +69,15 @@ Dynamic: requires-python
69
69
 
70
70
  `PINGMapper` is an open-source Python interface for reading and processing side scan sonar datasets and reproducibly mapping benthic habitat features. `PINGMapper` transforms recreation-grade sonar systems (i.e. fishfinders) into scientific data collectors, allowing researchers and citizens alike to reproducibly map their aquatic system with minimal expertise in data processing.
71
71
 
72
+ ## PING Ecosystem
73
+
74
+ `PINGMapper` is part of the [**PING Ecosystem**](https://github.com/PINGEcosystem): a coordinated set of tools for installation, conversion, quality checks, and mapping workflows.
75
+
76
+ - [PINGMapper](https://github.com/CameronBodine/PINGMapper): Core sonar processing and mapping package
77
+ - [PINGInstaller](https://github.com/CameronBodine/PINGInstaller): Recommended installer and environment bootstrap
78
+ - [PINGWizard](https://github.com/CameronBodine/PINGWizard): Point-and-click interface for testing, updates, and routine processing
79
+ - [PINGVerter](https://github.com/CameronBodine/PINGVerter): Sonar data conversion utilities
80
+
72
81
  ## Learn More Now!
73
82
  Check out PING-Mapper's [website](https://cameronbodine.github.io/PINGMapper/) for most up to date information:
74
83
 
@@ -128,7 +137,7 @@ A manuscript detailing the automated substrate mapping features made availabe in
128
137
 
129
138
  ### v3.0 - 4.0
130
139
 
131
- Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEcosystem.md) is made possible through mentorship, partnerships, and financial support listed below.
140
+ Continued support for PINGMapper and tools in the [PING Ecosystem](#ping-ecosystem) is made possible through mentorship, partnerships, and financial support listed below.
132
141
 
133
142
  **Primary Developer:** [Dr. Cameron S. Bodine](https://github.com/CameronBodine)
134
143
 
@@ -142,7 +151,24 @@ Continued support for PINGMapper and tools in the [PING Ecosystem](./docs/PINGEc
142
151
 
143
152
  ## Quick Start
144
153
 
145
- ### Option A: Conda
154
+ Recommended workflow: install Miniforge, use PINGInstaller to set up the `ping` environment, and launch PINGWizard for testing, updates, and routine processing.
155
+
156
+ ### Recommended: Miniforge + PINGInstaller + PINGWizard
157
+
158
+ Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions. In short:
159
+
160
+ ```bash
161
+ pip install pinginstaller -U
162
+ python -m pinginstaller
163
+ ```
164
+
165
+ After installation completes, launch PINGWizard using the shortcut created by PINGInstaller, or run:
166
+
167
+ ```bash
168
+ conda run -n ping python -m pingwizard
169
+ ```
170
+
171
+ ### Alternative: Direct conda environment
146
172
 
147
173
  Please see [Getting Started](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted) for full instructions, or simply run:
148
174
 
@@ -152,9 +178,9 @@ conda activate ping
152
178
  python -m pingmapper gui
153
179
  ```
154
180
 
155
- ### Option B: Pixi
181
+ ### Optional: Pixi
156
182
 
157
- [Pixi](https://pixi.sh) manages all dependencies (including GDAL) automatically from a single `pyproject.toml`.
183
+ [Pixi](https://pixi.sh) is available for users who prefer that workflow, but it is not the recommended installation path.
158
184
 
159
185
  1. [Install pixi](https://pixi.sh)
160
186
  2. Clone and run:
@@ -170,12 +196,18 @@ python -m pingmapper gui
170
196
 
171
197
  ### Verify Installation
172
198
 
173
- Run a lightweight self-check (same check used in CI):
199
+ Recommended routine verification is to run the included tests from PINGWizard. If you want a prompt-based check, run the lightweight self-check below:
174
200
 
175
201
  ```bash
176
202
  python -m pingmapper check
177
203
  ```
178
204
 
205
+ Direct unit-test invocation (same lightweight suite run by `check`):
206
+
207
+ ```bash
208
+ python -m unittest pingmapper.test_dq_filter pingmapper.test_cli_self_check -v
209
+ ```
210
+
179
211
  Optional longer end-to-end processing validation:
180
212
 
181
213
  ```bash
@@ -190,9 +222,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
190
222
 
191
223
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
192
224
 
193
- # PING-Mapper is part of the Doodleverse!
194
- ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
225
+ # PING-Mapper is part of the PING Ecosystem
226
+
195
227
 
196
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
228
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
197
229
 
230
+ PINGMapper also leverages components from the Doodleverse ecosystem for geoscientific image segmentation workflows. Learn more on [GitHub](https://github.com/Doodleverse).
231
+
232
+ ![153729377-e16d0679-ca0d-4d0d-a9f9-90306ba2f871](https://github.com/CameronBodine/PINGMapper/assets/54146655/54df6fdd-26a6-4c26-9cab-9fc834e60ed1)
198
233
 
@@ -22,6 +22,7 @@ pingmapper/nonGUI_batch_main.py
22
22
  pingmapper/nonGui_main.py
23
23
  pingmapper/self_check.py
24
24
  pingmapper/test_PINGMapper.py
25
+ pingmapper/test_cli_self_check.py
25
26
  pingmapper/test_dq_filter.py
26
27
  pingmapper/test_time.py
27
28
  pingmapper/version.py
@@ -1 +0,0 @@
1
- __version__ = '5.4.6'
File without changes
File without changes
File without changes
File without changes