ctdcast 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. ctdcast-0.1.0/LICENSE +21 -0
  2. ctdcast-0.1.0/PKG-INFO +192 -0
  3. ctdcast-0.1.0/README.md +148 -0
  4. ctdcast-0.1.0/ctdcast/__init__.py +25 -0
  5. ctdcast-0.1.0/ctdcast/__main__.py +6 -0
  6. ctdcast-0.1.0/ctdcast/_version.py +9 -0
  7. ctdcast-0.1.0/ctdcast/analysis/__init__.py +21 -0
  8. ctdcast-0.1.0/ctdcast/analysis/bathymetry.py +200 -0
  9. ctdcast-0.1.0/ctdcast/analysis/derive.py +407 -0
  10. ctdcast-0.1.0/ctdcast/analysis/geometry.py +68 -0
  11. ctdcast-0.1.0/ctdcast/cli/__init__.py +62 -0
  12. ctdcast-0.1.0/ctdcast/cli/convert.py +229 -0
  13. ctdcast-0.1.0/ctdcast/cli/draft.py +278 -0
  14. ctdcast-0.1.0/ctdcast/cli/init.py +1234 -0
  15. ctdcast-0.1.0/ctdcast/cli/process.py +392 -0
  16. ctdcast-0.1.0/ctdcast/cli/report.py +293 -0
  17. ctdcast-0.1.0/ctdcast/cli/run.py +164 -0
  18. ctdcast-0.1.0/ctdcast/cli/validate.py +244 -0
  19. ctdcast-0.1.0/ctdcast/config/__init__.py +1 -0
  20. ctdcast-0.1.0/ctdcast/config/loader.py +67 -0
  21. ctdcast-0.1.0/ctdcast/config/parameters.py +504 -0
  22. ctdcast-0.1.0/ctdcast/converters.py +27 -0
  23. ctdcast-0.1.0/ctdcast/ctdcast.mplstyle +24 -0
  24. ctdcast-0.1.0/ctdcast/identity.py +132 -0
  25. ctdcast-0.1.0/ctdcast/plotters/__init__.py +1 -0
  26. ctdcast-0.1.0/ctdcast/plotters/plots.py +1815 -0
  27. ctdcast-0.1.0/ctdcast/processors/__init__.py +129 -0
  28. ctdcast-0.1.0/ctdcast/processors/profiles.py +380 -0
  29. ctdcast-0.1.0/ctdcast/processors/qc.py +113 -0
  30. ctdcast-0.1.0/ctdcast/processors/stage1.py +213 -0
  31. ctdcast-0.1.0/ctdcast/processors/stage2.py +346 -0
  32. ctdcast-0.1.0/ctdcast/processors/stage3.py +138 -0
  33. ctdcast-0.1.0/ctdcast/readers/__init__.py +1 -0
  34. ctdcast-0.1.0/ctdcast/readers/cnv.py +50 -0
  35. ctdcast-0.1.0/ctdcast/readers/ladcp.py +55 -0
  36. ctdcast-0.1.0/ctdcast/readers/metadata.py +101 -0
  37. ctdcast-0.1.0/ctdcast/reports/__init__.py +5 -0
  38. ctdcast-0.1.0/ctdcast/reports/_cast.py +278 -0
  39. ctdcast-0.1.0/ctdcast/reports/_chrome.py +28 -0
  40. ctdcast-0.1.0/ctdcast/reports/_css.py +120 -0
  41. ctdcast-0.1.0/ctdcast/reports/_env.py +45 -0
  42. ctdcast-0.1.0/ctdcast/reports/_format.py +34 -0
  43. ctdcast-0.1.0/ctdcast/reports/_index.py +1192 -0
  44. ctdcast-0.1.0/ctdcast/reports/_leaflet.py +544 -0
  45. ctdcast-0.1.0/ctdcast/reports/_plots.py +655 -0
  46. ctdcast-0.1.0/ctdcast/reports/_section.py +394 -0
  47. ctdcast-0.1.0/ctdcast/reports/_timeseries.py +335 -0
  48. ctdcast-0.1.0/ctdcast/reports/leaflet/leaflet.min.css +8 -0
  49. ctdcast-0.1.0/ctdcast/reports/leaflet/leaflet.min.js +12 -0
  50. ctdcast-0.1.0/ctdcast/reports/templates/base.html +38 -0
  51. ctdcast-0.1.0/ctdcast/reports/templates/cast.html +203 -0
  52. ctdcast-0.1.0/ctdcast/reports/templates/casts.html +100 -0
  53. ctdcast-0.1.0/ctdcast/reports/templates/index.html +73 -0
  54. ctdcast-0.1.0/ctdcast/reports/templates/leaflet.html +165 -0
  55. ctdcast-0.1.0/ctdcast/reports/templates/section.html +147 -0
  56. ctdcast-0.1.0/ctdcast/reports/templates/sections.html +76 -0
  57. ctdcast-0.1.0/ctdcast/reports/templates/timeseries.html +80 -0
  58. ctdcast-0.1.0/ctdcast/reports/templates/timeseries_page.html +136 -0
  59. ctdcast-0.1.0/ctdcast/writers/__init__.py +1 -0
  60. ctdcast-0.1.0/ctdcast/writers/netcdf.py +89 -0
  61. ctdcast-0.1.0/ctdcast.egg-info/PKG-INFO +192 -0
  62. ctdcast-0.1.0/ctdcast.egg-info/SOURCES.txt +80 -0
  63. ctdcast-0.1.0/ctdcast.egg-info/dependency_links.txt +1 -0
  64. ctdcast-0.1.0/ctdcast.egg-info/entry_points.txt +2 -0
  65. ctdcast-0.1.0/ctdcast.egg-info/requires.txt +18 -0
  66. ctdcast-0.1.0/ctdcast.egg-info/top_level.txt +1 -0
  67. ctdcast-0.1.0/pyproject.toml +75 -0
  68. ctdcast-0.1.0/setup.cfg +4 -0
  69. ctdcast-0.1.0/tests/test_analysis.py +245 -0
  70. ctdcast-0.1.0/tests/test_converters.py +98 -0
  71. ctdcast-0.1.0/tests/test_cruise_profiles.py +152 -0
  72. ctdcast-0.1.0/tests/test_derive_salinity.py +62 -0
  73. ctdcast-0.1.0/tests/test_identity.py +107 -0
  74. ctdcast-0.1.0/tests/test_map_leaflet.py +47 -0
  75. ctdcast-0.1.0/tests/test_plots.py +120 -0
  76. ctdcast-0.1.0/tests/test_processors.py +71 -0
  77. ctdcast-0.1.0/tests/test_qc.py +108 -0
  78. ctdcast-0.1.0/tests/test_readers_cnv.py +109 -0
  79. ctdcast-0.1.0/tests/test_stage1.py +114 -0
  80. ctdcast-0.1.0/tests/test_stage2.py +124 -0
  81. ctdcast-0.1.0/tests/test_station.py +144 -0
  82. ctdcast-0.1.0/tests/test_writers_netcdf.py +89 -0
ctdcast-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eleanor Frajka-Williams
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ctdcast-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctdcast
3
+ Version: 0.1.0
4
+ Summary: Self-contained HTML report generator for shipboard CTD and LADCP data
5
+ Author: Angel Ruiz-Angulo
6
+ Author-email: Eleanor Frajka-Williams <eleanorfrajka@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/ocean-uhh/ctdcast
9
+ Project-URL: Source, https://github.com/ocean-uhh/ctdcast
10
+ Project-URL: Documentation, https://ocean-uhh.github.io/ctdcast
11
+ Project-URL: Bug Tracker, https://github.com/ocean-uhh/ctdcast/issues
12
+ Keywords: oceanography,CTD,LADCP,shipboard,seawater,report
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Visualization
24
+ Requires-Python: <3.14,>=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: gsw>=3.6
28
+ Requires-Dist: matplotlib>=3.7
29
+ Requires-Dist: numpy>=1.24
30
+ Requires-Dist: xarray>=2023.1
31
+ Requires-Dist: netcdf4>=1.6
32
+ Requires-Dist: jinja2>=3.1
33
+ Requires-Dist: pyyaml>=6.0
34
+ Requires-Dist: ruamel.yaml>=0.18
35
+ Requires-Dist: scipy>=1.9
36
+ Provides-Extra: docs
37
+ Requires-Dist: sphinx>=7.2; extra == "docs"
38
+ Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
39
+ Requires-Dist: myst-parser>=2.0; extra == "docs"
40
+ Provides-Extra: test
41
+ Requires-Dist: pytest>=8; extra == "test"
42
+ Requires-Dist: pytest-cov>=5; extra == "test"
43
+ Dynamic: license-file
44
+
45
+ # ctdcast
46
+
47
+ [![Tests](https://github.com/ocean-uhh/ctdcast/actions/workflows/tests.yml/badge.svg)](https://github.com/ocean-uhh/ctdcast/actions/workflows/tests.yml)
48
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20–%203.13-blue?logo=python&logoColor=white)](https://www.python.org)
49
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://opensource.org/licenses/MIT)
50
+ [![Docs](https://img.shields.io/badge/docs-gh--pages-blue)](https://ocean-uhh.github.io/ctdcast/)
51
+
52
+ Self-contained HTML report generator for shipboard CTD and LADCP data.
53
+ Produces portable HTML files — all figures embedded as base64 PNGs, no external requests —
54
+ for three report types: per-cast station pages, transect section pages, and a cruise-wide
55
+ time series page.
56
+
57
+ Designed for use at sea where internet connectivity is limited or absent.
58
+ All output files are fully self-contained and work offline.
59
+
60
+ ---
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ pip install ctdcast
66
+ ```
67
+
68
+ For development (editable install from source):
69
+
70
+ ```bash
71
+ git clone https://github.com/ocean-uhh/ctdcast
72
+ cd ctdcast
73
+ python -m venv venv
74
+ source venv/bin/activate # macOS / Linux
75
+ # venv\Scripts\activate # Windows
76
+ pip install -e .
77
+ ```
78
+
79
+ Dependencies: `gsw`, `matplotlib`, `numpy`, `xarray`, `netcdf4`, `jinja2`, `pyyaml`, `scipy`
80
+
81
+ CTD conversion: `seasenselib` converts raw CNV files to the netCDF format expected by ctdcast (`ctdcast draft` or `ctdcast run --ctd`). Install with `pip install seasenselib`. Pre-converted netCDF files from other tools must match ctdcast's variable naming convention (see docs).
82
+
83
+ To verify the installation, run the bundled demo against the committed fixture casts:
84
+
85
+ ```bash
86
+ ctdcast run config_demo.yaml # writes demo_report/index.html
87
+ ```
88
+
89
+ For a full walkthrough see the [Quickstart guide](https://ocean-uhh.github.io/ctdcast/quickstart.html).
90
+
91
+ ---
92
+
93
+ ## Quick start
94
+
95
+ ### Quick look (no config file needed)
96
+
97
+ Raw CNV files fresh off the instrument? One command gives you station pages + index + map:
98
+
99
+ ```bash
100
+ ctdcast draft /path/to/cnv/ # generates ./ctd_draft/index.html
101
+ ctdcast draft /path/to/cnv/ out/ --cruise odb2026 # with cruise ID
102
+ ctdcast draft /path/to/cnv/ --dry-run # preview what would happen
103
+ ```
104
+
105
+ Requires `seasenselib` for CNV conversion (`pip install seasenselib`).
106
+
107
+ ### Full workflow (with config)
108
+
109
+ For sections, time series, and LADCP panels you need a `config.yaml`:
110
+
111
+ #### 1. Write a config
112
+
113
+ ```bash
114
+ ctdcast init # writes a template config.yaml
115
+ ctdcast init --interactive # guided setup: prompts for paths and
116
+ # auto-detects sections/timeseries from profiles.nc
117
+ ctdcast validate config.yaml # check paths before the first run
118
+ ```
119
+
120
+ #### 2. Generate
121
+
122
+ ```bash
123
+ ctdcast run config.yaml # smart update — skips up-to-date pages
124
+ ctdcast run config.yaml --force # rebuild everything
125
+ ctdcast run config.yaml --cast 42 # rebuild one cast page
126
+ ```
127
+
128
+ Open `<output.dir>/index.html` in any browser.
129
+
130
+ ---
131
+
132
+ ## Input data
133
+
134
+ | File | Description |
135
+ |---|---|
136
+ | `cnv_nc/*.nc` | Per-cast netCDF files, one per CTD cast |
137
+ | `profiles.nc` | Compiled profiles on a 1 dbar grid |
138
+ | `ctd_sections.yaml` | Section definitions — which casts belong to each transect |
139
+
140
+ ### ctd_sections.yaml format
141
+
142
+ ```yaml
143
+ sections:
144
+ KTout:
145
+ description: "Kögur Transect outflow"
146
+ color: "#e41a1c"
147
+ cast_numbers: [[1, 12], 15] # ranges and/or individual cast numbers
148
+ FARDWO:
149
+ description: "FARDWO mooring array"
150
+ color: "#377eb8"
151
+ cast_numbers: [[20, 35], "22b"] # add "NNNb" to include a lettered sibling cast
152
+ ```
153
+
154
+ Cast numbers are kept in the order written. An integer or range selects the
155
+ plain casts; a lettered sibling event (from a ``NNNb`` / ``NNN_b`` file) is a
156
+ distinct cast and must be named explicitly as a quoted ``"NNNb"`` string.
157
+
158
+ ---
159
+
160
+ ## Output structure
161
+
162
+ ```
163
+ <output.dir>/
164
+ index.html front page — map + stats + navigation
165
+ casts.html table of all casts (latest first)
166
+ sections.html section cards with links
167
+ timeseries.html T, S, O₂ vs time × pressure
168
+ casts/
169
+ cast_001.html
170
+ cast_002.html
171
+ ...
172
+ sections/
173
+ section_KTout.html
174
+ ...
175
+ ```
176
+
177
+ Each station page shows: CT profile, T/S/σ₀ triple-axis profile, T-S diagram coloured by
178
+ O₂ saturation, auxiliary profiles (O₂, fluorescence, turbidity), N²/Turner-angle stability
179
+ panels, and a cruise-track map with the cast highlighted.
180
+
181
+ ---
182
+
183
+ ## GEBCO bathymetry
184
+
185
+ Maps show GEBCO 2025 bathymetry when `gebco_nc` is set in `config.yaml`.
186
+ The file (~8 GB) is not bundled. Maps render without bathymetry if the path is missing — not an error.
187
+
188
+ ---
189
+
190
+ ## Documentation
191
+
192
+ Full documentation: [ocean-uhh.github.io/ctdcast](https://ocean-uhh.github.io/ctdcast/)
@@ -0,0 +1,148 @@
1
+ # ctdcast
2
+
3
+ [![Tests](https://github.com/ocean-uhh/ctdcast/actions/workflows/tests.yml/badge.svg)](https://github.com/ocean-uhh/ctdcast/actions/workflows/tests.yml)
4
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20–%203.13-blue?logo=python&logoColor=white)](https://www.python.org)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://opensource.org/licenses/MIT)
6
+ [![Docs](https://img.shields.io/badge/docs-gh--pages-blue)](https://ocean-uhh.github.io/ctdcast/)
7
+
8
+ Self-contained HTML report generator for shipboard CTD and LADCP data.
9
+ Produces portable HTML files — all figures embedded as base64 PNGs, no external requests —
10
+ for three report types: per-cast station pages, transect section pages, and a cruise-wide
11
+ time series page.
12
+
13
+ Designed for use at sea where internet connectivity is limited or absent.
14
+ All output files are fully self-contained and work offline.
15
+
16
+ ---
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ pip install ctdcast
22
+ ```
23
+
24
+ For development (editable install from source):
25
+
26
+ ```bash
27
+ git clone https://github.com/ocean-uhh/ctdcast
28
+ cd ctdcast
29
+ python -m venv venv
30
+ source venv/bin/activate # macOS / Linux
31
+ # venv\Scripts\activate # Windows
32
+ pip install -e .
33
+ ```
34
+
35
+ Dependencies: `gsw`, `matplotlib`, `numpy`, `xarray`, `netcdf4`, `jinja2`, `pyyaml`, `scipy`
36
+
37
+ CTD conversion: `seasenselib` converts raw CNV files to the netCDF format expected by ctdcast (`ctdcast draft` or `ctdcast run --ctd`). Install with `pip install seasenselib`. Pre-converted netCDF files from other tools must match ctdcast's variable naming convention (see docs).
38
+
39
+ To verify the installation, run the bundled demo against the committed fixture casts:
40
+
41
+ ```bash
42
+ ctdcast run config_demo.yaml # writes demo_report/index.html
43
+ ```
44
+
45
+ For a full walkthrough see the [Quickstart guide](https://ocean-uhh.github.io/ctdcast/quickstart.html).
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ ### Quick look (no config file needed)
52
+
53
+ Raw CNV files fresh off the instrument? One command gives you station pages + index + map:
54
+
55
+ ```bash
56
+ ctdcast draft /path/to/cnv/ # generates ./ctd_draft/index.html
57
+ ctdcast draft /path/to/cnv/ out/ --cruise odb2026 # with cruise ID
58
+ ctdcast draft /path/to/cnv/ --dry-run # preview what would happen
59
+ ```
60
+
61
+ Requires `seasenselib` for CNV conversion (`pip install seasenselib`).
62
+
63
+ ### Full workflow (with config)
64
+
65
+ For sections, time series, and LADCP panels you need a `config.yaml`:
66
+
67
+ #### 1. Write a config
68
+
69
+ ```bash
70
+ ctdcast init # writes a template config.yaml
71
+ ctdcast init --interactive # guided setup: prompts for paths and
72
+ # auto-detects sections/timeseries from profiles.nc
73
+ ctdcast validate config.yaml # check paths before the first run
74
+ ```
75
+
76
+ #### 2. Generate
77
+
78
+ ```bash
79
+ ctdcast run config.yaml # smart update — skips up-to-date pages
80
+ ctdcast run config.yaml --force # rebuild everything
81
+ ctdcast run config.yaml --cast 42 # rebuild one cast page
82
+ ```
83
+
84
+ Open `<output.dir>/index.html` in any browser.
85
+
86
+ ---
87
+
88
+ ## Input data
89
+
90
+ | File | Description |
91
+ |---|---|
92
+ | `cnv_nc/*.nc` | Per-cast netCDF files, one per CTD cast |
93
+ | `profiles.nc` | Compiled profiles on a 1 dbar grid |
94
+ | `ctd_sections.yaml` | Section definitions — which casts belong to each transect |
95
+
96
+ ### ctd_sections.yaml format
97
+
98
+ ```yaml
99
+ sections:
100
+ KTout:
101
+ description: "Kögur Transect outflow"
102
+ color: "#e41a1c"
103
+ cast_numbers: [[1, 12], 15] # ranges and/or individual cast numbers
104
+ FARDWO:
105
+ description: "FARDWO mooring array"
106
+ color: "#377eb8"
107
+ cast_numbers: [[20, 35], "22b"] # add "NNNb" to include a lettered sibling cast
108
+ ```
109
+
110
+ Cast numbers are kept in the order written. An integer or range selects the
111
+ plain casts; a lettered sibling event (from a ``NNNb`` / ``NNN_b`` file) is a
112
+ distinct cast and must be named explicitly as a quoted ``"NNNb"`` string.
113
+
114
+ ---
115
+
116
+ ## Output structure
117
+
118
+ ```
119
+ <output.dir>/
120
+ index.html front page — map + stats + navigation
121
+ casts.html table of all casts (latest first)
122
+ sections.html section cards with links
123
+ timeseries.html T, S, O₂ vs time × pressure
124
+ casts/
125
+ cast_001.html
126
+ cast_002.html
127
+ ...
128
+ sections/
129
+ section_KTout.html
130
+ ...
131
+ ```
132
+
133
+ Each station page shows: CT profile, T/S/σ₀ triple-axis profile, T-S diagram coloured by
134
+ O₂ saturation, auxiliary profiles (O₂, fluorescence, turbidity), N²/Turner-angle stability
135
+ panels, and a cruise-track map with the cast highlighted.
136
+
137
+ ---
138
+
139
+ ## GEBCO bathymetry
140
+
141
+ Maps show GEBCO 2025 bathymetry when `gebco_nc` is set in `config.yaml`.
142
+ The file (~8 GB) is not bundled. Maps render without bathymetry if the path is missing — not an error.
143
+
144
+ ---
145
+
146
+ ## Documentation
147
+
148
+ Full documentation: [ocean-uhh.github.io/ctdcast](https://ocean-uhh.github.io/ctdcast/)
@@ -0,0 +1,25 @@
1
+ """ctdcast — CTD data processing and reporting for shipboard oceanography.
2
+
3
+ Subpackages
4
+ -----------
5
+ analysis: TEOS-10, cast geometry, GEBCO bathymetry (pure computation).
6
+ processors: per-cast and cruise-level processing pipeline (all stages).
7
+ readers: format readers (LADCP ``.mat``, CNV shim, sensor metadata).
8
+ reports: HTML page builders and the public ``report()`` entry point.
9
+ writers: output writers (CF-compliant netCDF).
10
+
11
+ Public API
12
+ ----------
13
+ report(nc_dir, profiles_path, section_yaml, out_dir, force=False)
14
+ stage1(cnv_dir, nc_dir, ...)
15
+ profiles(nc_dir, profiles_path, ...)
16
+ process(stage=None, *, proc_dir, force=False, **kw)
17
+ """
18
+
19
+ from ctdcast._version import __version__
20
+ from ctdcast.processors import process
21
+ from ctdcast.processors.profiles import build_profiles as profiles
22
+ from ctdcast.processors.stage1 import stage1
23
+ from ctdcast.reports._index import report
24
+
25
+ __all__ = ["__version__", "process", "profiles", "report", "stage1"]
@@ -0,0 +1,6 @@
1
+ """Entry point for ``python -m ctdcast``."""
2
+
3
+ from ctdcast.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,9 @@
1
+ """Package version, resolved from installed metadata."""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _pkg_version
5
+
6
+ try:
7
+ __version__: str = _pkg_version("ctdcast")
8
+ except PackageNotFoundError:
9
+ __version__ = "0.1.0.dev0"
@@ -0,0 +1,21 @@
1
+ """Shared scientific analysis: TEOS-10 derived variables, cast geometry, GEBCO bathymetry."""
2
+
3
+ from ctdcast.analysis.derive import (
4
+ derive_AOU,
5
+ derive_CT,
6
+ derive_SA,
7
+ derive_salinity,
8
+ derive_sigma0,
9
+ derive_teos10,
10
+ derive_teos10_profiles,
11
+ )
12
+
13
+ __all__ = [
14
+ "derive_AOU",
15
+ "derive_CT",
16
+ "derive_SA",
17
+ "derive_salinity",
18
+ "derive_sigma0",
19
+ "derive_teos10",
20
+ "derive_teos10_profiles",
21
+ ]
@@ -0,0 +1,200 @@
1
+ """GEBCO bathymetry loading and interpolation.
2
+
3
+ Pure computation — no matplotlib, no HTML. GEBCO stores elevation as negative
4
+ below sea level; this module returns depth as positive below sea level
5
+ (``depth = -elevation``).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ import xarray as xr
14
+
15
+ # Module-level numpy cache: populated by preload_gebco() at report start.
16
+ # Maps str(path) → (all_lons, all_lats, all_depth) numpy arrays for the cruise area.
17
+ # Once populated, load_gebco subsets with numpy (zero disk I/O).
18
+ _GEBCO_CACHE: dict[str, tuple[np.ndarray, np.ndarray, np.ndarray]] = {}
19
+
20
+
21
+ def preload_gebco(
22
+ path: Path,
23
+ lat_lo: float,
24
+ lat_hi: float,
25
+ lon_lo: float,
26
+ lon_hi: float,
27
+ margin: float = 1.0,
28
+ ) -> bool:
29
+ """Load a GEBCO region into memory once for the cruise area.
30
+
31
+ Call this at report-generation start with the full lat/lon extent of all
32
+ casts. Subsequent ``load_gebco`` calls then subset from numpy arrays
33
+ (no disk I/O) instead of reopening the file for every map figure.
34
+
35
+ Parameters
36
+ ----------
37
+ path:
38
+ Path to GEBCO netCDF file.
39
+ lat_lo, lat_hi, lon_lo, lon_hi:
40
+ Bounding box of the cruise area.
41
+ margin:
42
+ Extra degrees around the bounding box. Default 1.0 deg.
43
+
44
+ Returns
45
+ -------
46
+ bool
47
+ True if the file was found and cached successfully.
48
+ """
49
+ if not Path(path).exists():
50
+ return False
51
+ try:
52
+ bathy = xr.open_dataset(path, engine="netcdf4")
53
+ lon_dim = "lon" if "lon" in bathy.coords else "longitude"
54
+ lat_dim = "lat" if "lat" in bathy.coords else "latitude"
55
+ sub = bathy.sel(
56
+ {
57
+ lon_dim: slice(lon_lo - margin, lon_hi + margin),
58
+ lat_dim: slice(lat_lo - margin, lat_hi + margin),
59
+ }
60
+ )
61
+ lons = sub[lon_dim].values.copy()
62
+ lats = sub[lat_dim].values.copy()
63
+ depth = -sub["elevation"].values.copy() # GEBCO: negative = below sea level
64
+ bathy.close()
65
+ _GEBCO_CACHE[str(path)] = (lons, lats, depth)
66
+ return True
67
+ except Exception: # noqa: BLE001
68
+ return False
69
+
70
+
71
+ def load_gebco(
72
+ lat_lo: float,
73
+ lat_hi: float,
74
+ lon_lo: float,
75
+ lon_hi: float,
76
+ margin: float = 0.05,
77
+ path: Path | None = None,
78
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray] | None:
79
+ """Return a GEBCO subset as (lons, lats, depth_m) or None if unavailable.
80
+
81
+ If ``preload_gebco`` has been called for *path*, subsets from the in-memory
82
+ numpy cache (fast). Otherwise opens the file from disk (slow).
83
+
84
+ Parameters
85
+ ----------
86
+ path:
87
+ Path to GEBCO_2025.nc. Pass ``plots.GEBCO_PATH`` from the caller.
88
+ Returns None if not provided or file not found.
89
+ """
90
+ if path is None or not Path(path).exists():
91
+ return None
92
+ path_str = str(path)
93
+ try:
94
+ if path_str in _GEBCO_CACHE:
95
+ all_lons, all_lats, all_depth = _GEBCO_CACHE[path_str]
96
+ lon_mask = (all_lons >= lon_lo - margin) & (all_lons <= lon_hi + margin)
97
+ lat_mask = (all_lats >= lat_lo - margin) & (all_lats <= lat_hi + margin)
98
+ lons = all_lons[lon_mask]
99
+ lats = all_lats[lat_mask]
100
+ if lons.size == 0 or lats.size == 0:
101
+ return None
102
+ depth = all_depth[lat_mask][:, lon_mask]
103
+ return lons, lats, depth
104
+ # Fallback when preload_gebco was not called: open file directly.
105
+ bathy = xr.open_dataset(path, engine="netcdf4")
106
+ lon_dim = "lon" if "lon" in bathy.coords else "longitude"
107
+ lat_dim = "lat" if "lat" in bathy.coords else "latitude"
108
+ sub = bathy.sel(
109
+ {
110
+ lon_dim: slice(lon_lo - margin, lon_hi + margin),
111
+ lat_dim: slice(lat_lo - margin, lat_hi + margin),
112
+ }
113
+ )
114
+ lons = sub[lon_dim].values
115
+ lats = sub[lat_dim].values
116
+ depth = -sub["elevation"].values # GEBCO: negative = below sea level
117
+ bathy.close()
118
+ return lons, lats, depth
119
+ except Exception: # noqa: BLE001
120
+ return None
121
+
122
+
123
+ def interpolate_bathy_at_casts(
124
+ lats: list[float],
125
+ lons: list[float],
126
+ path: Path | None = None,
127
+ ) -> np.ndarray | None:
128
+ """Return GEBCO water depth (m, positive below sea level) at each cast position.
129
+
130
+ Uses bilinear interpolation via xarray. Returns ``None`` if GEBCO is not
131
+ available or on any error. Land points (elevation > 0) are clamped to 0.
132
+ """
133
+ if path is None or not Path(path).exists():
134
+ return None
135
+ try:
136
+ lats_arr = np.asarray(lats, dtype=float)
137
+ lons_arr = np.asarray(lons, dtype=float)
138
+ finite = np.isfinite(lats_arr) & np.isfinite(lons_arr)
139
+ if not finite.any():
140
+ return None
141
+ lat_lo = float(lats_arr[finite].min())
142
+ lat_hi = float(lats_arr[finite].max())
143
+ lon_lo = float(lons_arr[finite].min())
144
+ lon_hi = float(lons_arr[finite].max())
145
+ gebco = load_gebco(lat_lo, lat_hi, lon_lo, lon_hi, margin=0.1, path=path)
146
+ if gebco is None:
147
+ return None
148
+ lons_g, lats_g, depth_g = gebco
149
+ # depth_g shape: (N_lat, N_lon) — positive = below sea level
150
+ da = xr.DataArray(
151
+ depth_g,
152
+ dims=["lat", "lon"],
153
+ coords={"lat": lats_g, "lon": lons_g},
154
+ )
155
+ da = da.sortby("lat").sortby("lon")
156
+ result = da.interp(
157
+ lat=xr.DataArray(lats_arr, dims="n"),
158
+ lon=xr.DataArray(lons_arr, dims="n"),
159
+ method="linear",
160
+ )
161
+ return np.maximum(result.values.astype(float), 0.0)
162
+ except Exception: # noqa: BLE001
163
+ return None
164
+
165
+
166
+ def dense_bathy_along_track(
167
+ lats: list[float],
168
+ lons: list[float],
169
+ x_vals: np.ndarray,
170
+ path: Path | None = None,
171
+ n_per_segment: int = 20,
172
+ ) -> tuple[np.ndarray | None, np.ndarray | None]:
173
+ """Return ``(dense_x, dense_depths)`` interpolated between cast positions.
174
+
175
+ Generates *n_per_segment* equally-spaced points along each segment between
176
+ consecutive casts, giving a smooth GEBCO bathymetry fill rather than the
177
+ stepped appearance produced by one sample per cast. Returns ``(None, None)``
178
+ when GEBCO is unavailable or fewer than two cast positions are supplied.
179
+ """
180
+ if path is None or not Path(path).exists() or len(lats) < 2:
181
+ return None, None
182
+ try:
183
+ dense_lats: list[float] = []
184
+ dense_lons: list[float] = []
185
+ dense_x: list[float] = []
186
+ for i in range(len(lats) - 1):
187
+ t = np.linspace(0.0, 1.0, n_per_segment, endpoint=False)
188
+ dense_lats.extend((lats[i] + t * (lats[i + 1] - lats[i])).tolist())
189
+ dense_lons.extend((lons[i] + t * (lons[i + 1] - lons[i])).tolist())
190
+ dense_x.extend((x_vals[i] + t * (x_vals[i + 1] - x_vals[i])).tolist())
191
+ dense_lats.append(lats[-1])
192
+ dense_lons.append(lons[-1])
193
+ dense_x.append(float(x_vals[-1]))
194
+
195
+ depths = interpolate_bathy_at_casts(dense_lats, dense_lons, path=path)
196
+ if depths is None:
197
+ return None, None
198
+ return np.array(dense_x), depths
199
+ except Exception: # noqa: BLE001
200
+ return None, None