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.
- {pingmapper-5.4.5 → pingmapper-5.4.8}/PKG-INFO +57 -8
- {pingmapper-5.4.5 → pingmapper-5.4.8}/README.md +56 -7
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/__main__.py +8 -1
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_portstarObj.py +21 -13
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_rectObj.py +6 -16
- pingmapper-5.4.8/pingmapper/self_check.py +67 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_PINGMapper.py +16 -16
- pingmapper-5.4.8/pingmapper/test_cli_self_check.py +118 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_dq_filter.py +199 -0
- pingmapper-5.4.8/pingmapper/version.py +1 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/PKG-INFO +57 -8
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/SOURCES.txt +2 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pyproject.toml +2 -0
- pingmapper-5.4.5/pingmapper/version.py +0 -1
- {pingmapper-5.4.5 → pingmapper-5.4.8}/LICENSE +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/__init__.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_mapSubstrateObj.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_sonObj.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/class_sonObj_nadirgaptest.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/default_params.json +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/doWork.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_common.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_model.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/funcs_rectify.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/gui_main.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_mapSubstrate.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_readFiles.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/main_rectify.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/nonGUI_batch_main.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/nonGui_main.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0926.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/processing_scripts/main_batchDirectory_2024-01-18_0929.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/funcs_pyhum_correct.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/main.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/scratch/main_batchDirectory.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/test_time.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline_from_bankline.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/gen_centerline_trkpnts_fitspline_DRAFT.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/RawEGN_avg_predictions.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/00_substrate_logits_mosaic_transects.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/00_substrate_shps_mosaic_transects.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/01_gen_centerline_from_coverage.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/02_gen_summary_stamp_shps.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/04_combine_summary_shp_csv.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/05_gen_summary_shp_plots.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/06_compare_raw-egn_volume.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/08_raw-egn_hardReacheFreq_hist.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/09_raw-egn_PatchSize_density.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/summarize_project_substrate.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/export_coverage.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/main_mosaic_transects.py +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/dependency_links.txt +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/requires.txt +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper.egg-info/top_level.txt +0 -0
- {pingmapper-5.4.5 → pingmapper-5.4.8}/setup.cfg +0 -0
- {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.
|
|
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](
|
|
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
|
-
|
|
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
|
-
###
|
|
181
|
+
### Optional: Pixi
|
|
156
182
|
|
|
157
|
-
[Pixi](https://pixi.sh)
|
|
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
|
|
180
|
-

|
|
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
|
+

|
|
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](
|
|
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
|
-
|
|
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
|
-
###
|
|
143
|
+
### Optional: Pixi
|
|
118
144
|
|
|
119
|
-
[Pixi](https://pixi.sh)
|
|
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
|
|
142
|
-

|
|
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
|
+

|
|
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
|
-
|
|
1985
|
-
|
|
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(
|
|
1988
|
-
starErr = np.abs(
|
|
1995
|
+
portErr = np.abs(pA - pI)
|
|
1996
|
+
starErr = np.abs(sA - sI)
|
|
1989
1997
|
|
|
1990
|
-
inst_pair_valid = np.isfinite(
|
|
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(
|
|
1999
|
-
smed = _rolling_median(
|
|
2000
|
-
presid = np.abs(
|
|
2001
|
-
sresid = np.abs(
|
|
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
|
|
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
|
|
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.
|
|
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](
|
|
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
|
-
|
|
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
|
-
###
|
|
181
|
+
### Optional: Pixi
|
|
156
182
|
|
|
157
|
-
[Pixi](https://pixi.sh)
|
|
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
|
|
180
|
-

|
|
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
|
+

|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/avg_predictions_Mussel_WBL.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/DRAFT_Workflows/testEXAMPLE_mosaic_logit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pingmapper-5.4.5 → pingmapper-5.4.8}/pingmapper/utils/Substrate_Summaries/03_gen_summary_shp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|