pingmapper 5.4.5__tar.gz → 5.4.8__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.5 → pingmapper-5.4.8}/PKG-INFO +57 -8
  2. {pingmapper-5.4.5 → pingmapper-5.4.8}/README.md +56 -7
  3. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/__main__.py +8 -1
  4. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_portstarObj.py +21 -13
  5. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_rectObj.py +6 -16
  6. pingmapper-5.4.8/pingmapper/self_check.py +67 -0
  7. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_PINGMapper.py +16 -16
  8. pingmapper-5.4.8/pingmapper/test_cli_self_check.py +118 -0
  9. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_dq_filter.py +199 -0
  10. pingmapper-5.4.8/pingmapper/version.py +1 -0
  11. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/PKG-INFO +57 -8
  12. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/SOURCES.txt +2 -0
  13. {pingmapper-5.4.5 → pingmapper-5.4.8}/pyproject.toml +2 -0
  14. pingmapper-5.4.5/pingmapper/version.py +0 -1
  15. {pingmapper-5.4.5 → pingmapper-5.4.8}/LICENSE +0 -0
  16. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/__init__.py +0 -0
  17. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_mapSubstrateObj.py +0 -0
  18. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_sonObj.py +0 -0
  19. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_sonObj_nadirgaptest.py +0 -0
  20. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/default_params.json +0 -0
  21. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/doWork.py +0 -0
  22. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_common.py +0 -0
  23. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_model.py +0 -0
  24. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_rectify.py +0 -0
  25. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/gui_main.py +0 -0
  26. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_mapSubstrate.py +0 -0
  27. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_readFiles.py +0 -0
  28. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_rectify.py +0 -0
  29. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/nonGUI_batch_main.py +0 -0
  30. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/nonGui_main.py +0 -0
  31. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0926.py +0 -0
  32. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0929.py +0 -0
  33. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/funcs_pyhum_correct.py +0 -0
  34. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/main.py +0 -0
  35. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/main_batchDirectory.py +0 -0
  36. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_time.py +0 -0
  37. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py +0 -0
  38. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline.py +0 -0
  39. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline_from_bankline.py +0 -0
  40. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline_trkpnts_fitspline_DRAFT.py +0 -0
  41. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py +0 -0
  42. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/RawEGN_avg_predictions.py +0 -0
  43. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/00_substrate_logits_mosaic_transects.py +0 -0
  44. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/00_substrate_shps_mosaic_transects.py +0 -0
  45. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/01_gen_centerline_from_coverage.py +0 -0
  46. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/02_gen_summary_stamp_shps.py +0 -0
  47. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py +0 -0
  48. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/04_combine_summary_shp_csv.py +0 -0
  49. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/05_gen_summary_shp_plots.py +0 -0
  50. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/06_compare_raw-egn_volume.py +0 -0
  51. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/08_raw-egn_hardReacheFreq_hist.py +0 -0
  52. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/09_raw-egn_PatchSize_density.py +0 -0
  53. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/summarize_project_substrate.py +0 -0
  54. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/export_coverage.py +0 -0
  55. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/main_mosaic_transects.py +0 -0
  56. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/dependency_links.txt +0 -0
  57. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/requires.txt +0 -0
  58. {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/top_level.txt +0 -0
  59. {pingmapper-5.4.5 → pingmapper-5.4.8}/setup.cfg +0 -0
  60. {pingmapper-5.4.5 → pingmapper-5.4.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.4.5
3
+ Version: 5.4.8
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:
@@ -168,6 +194,26 @@ python -m pingmapper gui
168
194
  pixi run -e full gui # GUI with ML support
169
195
  ```
170
196
 
197
+ ### Verify Installation
198
+
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:
200
+
201
+ ```bash
202
+ python -m pingmapper check
203
+ ```
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
+
211
+ Optional longer end-to-end processing validation:
212
+
213
+ ```bash
214
+ python -m pingmapper test
215
+ ```
216
+
171
217
  ## Ready to get started?
172
218
 
173
219
  Follow the installation and testing instructions to [Get Started!](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted)
@@ -176,9 +222,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
176
222
 
177
223
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
178
224
 
179
- # PING-Mapper is part of the Doodleverse!
180
- ![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
181
226
 
182
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
183
227
 
228
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
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)
184
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:
@@ -130,6 +156,26 @@ python -m pingmapper gui
130
156
  pixi run -e full gui # GUI with ML support
131
157
  ```
132
158
 
159
+ ### Verify Installation
160
+
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:
162
+
163
+ ```bash
164
+ python -m pingmapper check
165
+ ```
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
+
173
+ Optional longer end-to-end processing validation:
174
+
175
+ ```bash
176
+ python -m pingmapper test
177
+ ```
178
+
133
179
  ## Ready to get started?
134
180
 
135
181
  Follow the installation and testing instructions to [Get Started!](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted)
@@ -138,9 +184,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
138
184
 
139
185
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
140
186
 
141
- # PING-Mapper is part of the Doodleverse!
142
- ![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
143
188
 
144
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
145
189
 
190
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
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)
146
195
 
@@ -49,5 +49,12 @@ def main(process):
49
49
  from pingmapper.test_PINGMapper import test
50
50
  test(2)
51
51
 
52
+ elif process in ('check', 'test_unit'):
53
+ print('\n\nRunning PINGMapper self-check...\n\n')
54
+ from pingmapper.self_check import run_self_check
55
+ return run_self_check(verbose=True)
56
+
57
+ return 0
58
+
52
59
  if __name__ == "__main__":
53
- main(to_do)
60
+ sys.exit(main(to_do))
@@ -1981,26 +1981,34 @@ class portstarObj(object):
1981
1981
 
1982
1982
  # If sides diverge strongly, invalidate the side farther from
1983
1983
  # 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)
1984
+ # Port and star may have different ping counts; only compare the
1985
+ # overlapping rows.
1986
+ min_len = min(len(portArr), len(starArr))
1987
+ pA = portArr[:min_len]
1988
+ sA = starArr[:min_len]
1989
+ pI = portInst[:min_len]
1990
+ sI = starInst[:min_len]
1991
+
1992
+ pair_valid = np.isfinite(pA) & np.isfinite(sA) & (pA > 0) & (sA > 0)
1993
+ diverge = pair_valid & (np.abs(pA - sA) > 5.0)
1986
1994
  if diverge.any():
1987
- portErr = np.abs(portArr - portInst)
1988
- starErr = np.abs(starArr - starInst)
1995
+ portErr = np.abs(pA - pI)
1996
+ starErr = np.abs(sA - sI)
1989
1997
 
1990
- inst_pair_valid = np.isfinite(portInst) & (portInst > 0) & np.isfinite(starInst) & (starInst > 0)
1998
+ inst_pair_valid = np.isfinite(pI) & (pI > 0) & np.isfinite(sI) & (sI > 0)
1991
1999
  choose_by_inst = diverge & inst_pair_valid
1992
- portFlags |= choose_by_inst & (portErr >= starErr)
1993
- starFlags |= choose_by_inst & (starErr > portErr)
2000
+ portFlags[:min_len] |= choose_by_inst & (portErr >= starErr)
2001
+ starFlags[:min_len] |= choose_by_inst & (starErr > portErr)
1994
2002
 
1995
2003
  # Fallback for rows without valid instrument depth on one/both sides.
1996
2004
  fallback = diverge & (~inst_pair_valid)
1997
2005
  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)
2006
+ pmed = _rolling_median(pA)
2007
+ smed = _rolling_median(sA)
2008
+ presid = np.abs(pA - pmed)
2009
+ sresid = np.abs(sA - smed)
2010
+ portFlags[:min_len] |= fallback & (presid >= sresid)
2011
+ starFlags[:min_len] |= fallback & (sresid > presid)
2004
2012
 
2005
2013
  portArr[portFlags] = np.nan
2006
2014
  starArr[starFlags] = np.nan
@@ -327,10 +327,7 @@ class rectObj(sonObj):
327
327
  if self.egn_stretch > 0:
328
328
  self._egnDoStretch()
329
329
 
330
- img = self._reserve_zero_for_nodata(
331
- self.sonDat,
332
- preserve_zeros=(not bool(getattr(self, 'son8bit', False))),
333
- )
330
+ img = self._reserve_zero_for_nodata(self.sonDat, preserve_zeros=True)
334
331
  img = self._prepare_export_uint16(img)
335
332
  vals = img[img > 0]
336
333
  if vals.size == 0:
@@ -1864,10 +1861,7 @@ class rectObj(sonObj):
1864
1861
  col = df[xPix].to_numpy().astype(int)
1865
1862
  data = df[sonCol].to_numpy()
1866
1863
  if son:
1867
- data = self._reserve_zero_for_nodata(
1868
- data,
1869
- preserve_zeros=(not bool(getattr(self, 'son8bit', False))),
1870
- )
1864
+ data = self._reserve_zero_for_nodata(data, preserve_zeros=True)
1871
1865
  apply_post_rect_colormap = use_16bit and self._rect_colormap_selected(son=son)
1872
1866
  source_scale_bounds = None
1873
1867
  if apply_post_rect_colormap:
@@ -2219,6 +2213,8 @@ class rectObj(sonObj):
2219
2213
  crs='EPSG:4326',
2220
2214
  ).to_crs(self.humDat['epsg'])
2221
2215
 
2216
+ df[x_col] = df[x_col].astype(float)
2217
+ df[y_col] = df[y_col].astype(float)
2222
2218
  df.loc[need_xy, x_col] = geo.geometry.x.to_numpy()
2223
2219
  df.loc[need_xy, y_col] = geo.geometry.y.to_numpy()
2224
2220
  return df
@@ -2727,10 +2723,7 @@ class rectObj(sonObj):
2727
2723
 
2728
2724
  img = self.sonDat.copy()
2729
2725
  if son:
2730
- img = self._reserve_zero_for_nodata(
2731
- img,
2732
- preserve_zeros=(not bool(getattr(self, 'son8bit', False))),
2733
- )
2726
+ img = self._reserve_zero_for_nodata(img, preserve_zeros=True)
2734
2727
  apply_post_rect_colormap = use_16bit and self._rect_colormap_selected(son=son)
2735
2728
  source_scale_bounds = None
2736
2729
  if apply_post_rect_colormap:
@@ -2821,10 +2814,7 @@ class rectObj(sonObj):
2821
2814
 
2822
2815
  img = self.sonDat
2823
2816
  if son:
2824
- img = self._reserve_zero_for_nodata(
2825
- img,
2826
- preserve_zeros=(not bool(getattr(self, 'son8bit', False))),
2827
- )
2817
+ img = self._reserve_zero_for_nodata(img, preserve_zeros=True)
2828
2818
  apply_post_rect_colormap = son and use_16bit and self._rect_colormap_selected(son=son)
2829
2819
  source_scale_bounds = None
2830
2820
  if apply_post_rect_colormap:
@@ -0,0 +1,67 @@
1
+ """Lightweight install/runtime checks for CI and end-user validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import sys
7
+ import unittest
8
+
9
+
10
+ REQUIRED_MODULES = [
11
+ "osgeo",
12
+ "numpy",
13
+ "pandas",
14
+ "geopandas",
15
+ "rasterio",
16
+ "pyproj",
17
+ "cv2",
18
+ "pinginstaller",
19
+ "pingwizard",
20
+ "pingverter",
21
+ ]
22
+
23
+ UNIT_TEST_MODULES = [
24
+ "pingmapper.test_dq_filter",
25
+ "pingmapper.test_cli_self_check",
26
+ ]
27
+
28
+
29
+ def _missing_required_modules() -> list[str]:
30
+ """Return required modules that fail to import."""
31
+ missing = []
32
+ for mod_name in REQUIRED_MODULES:
33
+ try:
34
+ importlib.import_module(mod_name)
35
+ except Exception:
36
+ missing.append(mod_name)
37
+ return missing
38
+
39
+
40
+ def run_self_check(verbose: bool = True) -> int:
41
+ """Run dependency checks and unit tests. Returns process exit code."""
42
+ print("\nPINGMapper self-check\n")
43
+
44
+ missing = _missing_required_modules()
45
+ if missing:
46
+ print("Missing required modules:")
47
+ for name in missing:
48
+ print(f" - {name}")
49
+ print("\nSelf-check failed: install appears incomplete.")
50
+ return 1
51
+
52
+ print("Dependency import checks passed.")
53
+
54
+ suite = unittest.defaultTestLoader.loadTestsFromNames(UNIT_TEST_MODULES)
55
+ runner = unittest.TextTestRunner(verbosity=2 if verbose else 1)
56
+ result = runner.run(suite)
57
+
58
+ if not result.wasSuccessful():
59
+ print("\nSelf-check failed: unit tests did not pass.")
60
+ return 1
61
+
62
+ print("\nSelf-check passed.")
63
+ return 0
64
+
65
+
66
+ if __name__ == "__main__":
67
+ sys.exit(run_self_check())
@@ -72,7 +72,7 @@ def test(ds):
72
72
  # ds = int(sys.argv[1])
73
73
  # else:
74
74
  # print("\n\nERROR: \nPlease enter argument to choose which test file to process:")
75
- # print("1 = Short recording \n2 = Long recording \n\nSYNTAX: python test_PINGMapper.py <1 or 2>\n\n")
75
+ # print("1 = Short recording \n2 = Long recording \n\nSYNTAX: python -m pingmapper test <1 or 2>\n\n")
76
76
  # sys.exit()
77
77
 
78
78
  #################
@@ -87,15 +87,15 @@ def test(ds):
87
87
  download_name = 'sample_recording'
88
88
  else:
89
89
  print("\n\nERROR: \nPlease enter argument to choose which test file to process:")
90
- print("1 = Short recording \n2 = Long recording \n\nSYNTAX: python test_PINGMapper.py <1 or 2>\n\n")
90
+ print("1 = Short recording \n2 = Long recording \n\nSYNTAX: python -m pingmapper test <1 or 2>\n\n")
91
91
  sys.exit()
92
92
 
93
- repo_root = PACKAGE_DIR
94
- d = os.environ.get(
95
- 'PINGMAPPER_DATA_DIR',
96
- os.path.join(repo_root, '.data'),
97
- )
98
- exampleDir = os.path.join(d, 'example_data')
93
+ repo_root = PACKAGE_DIR
94
+ d = os.environ.get(
95
+ 'PINGMAPPER_DATA_DIR',
96
+ os.path.join(repo_root, '.data'),
97
+ )
98
+ exampleDir = os.path.join(d, 'example_data')
99
99
  ds_path = os.path.join(exampleDir, ds_name)
100
100
  ds_path = os.path.normpath(ds_path)
101
101
 
@@ -122,12 +122,12 @@ def test(ds):
122
122
  # Path to data/output
123
123
  inFile = ds_path+'.DAT'
124
124
  sonPath = ds_path
125
- proj_base_dir = os.environ.get(
126
- 'PINGMAPPER_TEST_OUTPUT_DIR',
127
- os.path.join(d, 'test_runs'),
128
- )
129
-
130
- projDir = os.path.join(proj_base_dir, 'PINGMapper-'+ds_name)
125
+ proj_base_dir = os.environ.get(
126
+ 'PINGMAPPER_TEST_OUTPUT_DIR',
127
+ os.path.join(d, 'test_runs'),
128
+ )
129
+
130
+ projDir = os.path.join(proj_base_dir, 'PINGMapper-'+ds_name)
131
131
 
132
132
  inFile = os.path.abspath(inFile)
133
133
  sonPath = os.path.abspath(sonPath)
@@ -246,7 +246,7 @@ def test(ds):
246
246
  if project_mode == 0:
247
247
  # Create new project
248
248
  if not os.path.exists(projDir):
249
- os.makedirs(projDir, exist_ok=False)
249
+ os.makedirs(projDir, exist_ok=False)
250
250
  else:
251
251
  projectMode_1_inval()
252
252
 
@@ -255,7 +255,7 @@ def test(ds):
255
255
  if os.path.exists(projDir):
256
256
  shutil.rmtree(projDir)
257
257
 
258
- os.makedirs(projDir, exist_ok=False)
258
+ os.makedirs(projDir, exist_ok=False)
259
259
 
260
260
  elif project_mode == 2:
261
261
  # Update project
@@ -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.8'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingmapper
3
- Version: 5.4.5
3
+ Version: 5.4.8
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:
@@ -168,6 +194,26 @@ python -m pingmapper gui
168
194
  pixi run -e full gui # GUI with ML support
169
195
  ```
170
196
 
197
+ ### Verify Installation
198
+
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:
200
+
201
+ ```bash
202
+ python -m pingmapper check
203
+ ```
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
+
211
+ Optional longer end-to-end processing validation:
212
+
213
+ ```bash
214
+ python -m pingmapper test
215
+ ```
216
+
171
217
  ## Ready to get started?
172
218
 
173
219
  Follow the installation and testing instructions to [Get Started!](https://cameronbodine.github.io/PINGMapper/docs/gettingstarted)
@@ -176,9 +222,12 @@ Follow the installation and testing instructions to [Get Started!](https://camer
176
222
 
177
223
  If you are interested in partnering on future developments, please reach out to [Cameron Bodine](https://cameronbodine.github.io/).
178
224
 
179
- # PING-Mapper is part of the Doodleverse!
180
- ![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
181
226
 
182
- The Doodleverse is an opinionated collection of Python packages designed for geoscientific image segmentation. Find out more on [GitHub](https://github.com/Doodleverse).
183
227
 
228
+ PINGMapper is developed and distributed alongside companion PING tools (PINGInstaller, PINGWizard, and PINGVerter) to provide a complete end-to-end workflow.
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)
184
233
 
@@ -20,7 +20,9 @@ pingmapper/main_readFiles.py
20
20
  pingmapper/main_rectify.py
21
21
  pingmapper/nonGUI_batch_main.py
22
22
  pingmapper/nonGui_main.py
23
+ pingmapper/self_check.py
23
24
  pingmapper/test_PINGMapper.py
25
+ pingmapper/test_cli_self_check.py
24
26
  pingmapper/test_dq_filter.py
25
27
  pingmapper/test_time.py
26
28
  pingmapper/version.py
@@ -105,5 +105,7 @@ full = { features = ["ml"], solve-group = "default" }
105
105
  pingmapper = "python -m pingwizard"
106
106
  gui = "python -m pingmapper gui"
107
107
  gui-batch = "python -m pingmapper gui_batch"
108
+ check = "python -m pingmapper check"
109
+ test-unit = "python -m pingmapper test_unit"
108
110
  test = "python -m pingmapper test"
109
111
  test-large = "python -m pingmapper test_large"
@@ -1 +0,0 @@
1
- __version__ = '5.4.5'
File without changes
File without changes
File without changes