RSATSIModel 2.5.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 (33) hide show
  1. rsatsimodel-2.5.0/LICENSE +3 -0
  2. rsatsimodel-2.5.0/MANIFEST.in +3 -0
  3. rsatsimodel-2.5.0/PKG-INFO +159 -0
  4. rsatsimodel-2.5.0/README.md +143 -0
  5. rsatsimodel-2.5.0/RSATSIModel.egg-info/PKG-INFO +159 -0
  6. rsatsimodel-2.5.0/RSATSIModel.egg-info/SOURCES.txt +31 -0
  7. rsatsimodel-2.5.0/RSATSIModel.egg-info/dependency_links.txt +1 -0
  8. rsatsimodel-2.5.0/RSATSIModel.egg-info/requires.txt +5 -0
  9. rsatsimodel-2.5.0/RSATSIModel.egg-info/top_level.txt +1 -0
  10. rsatsimodel-2.5.0/atsimodel/__init__.py +5 -0
  11. rsatsimodel-2.5.0/atsimodel/atsifunction.py +20 -0
  12. rsatsimodel-2.5.0/atsimodel/chl_models.py +37 -0
  13. rsatsimodel-2.5.0/atsimodel/classification.py +17 -0
  14. rsatsimodel-2.5.0/atsimodel/core.py +14 -0
  15. rsatsimodel-2.5.0/atsimodel/io.py +30 -0
  16. rsatsimodel-2.5.0/atsimodel/metrics.py +41 -0
  17. rsatsimodel-2.5.0/atsimodel/olci_features.py +55 -0
  18. rsatsimodel-2.5.0/atsimodel/plotting.py +19 -0
  19. rsatsimodel-2.5.0/atsimodel/preprocessing.py +29 -0
  20. rsatsimodel-2.5.0/atsimodel/rs_core.py +92 -0
  21. rsatsimodel-2.5.0/atsimodel/thresholds.py +62 -0
  22. rsatsimodel-2.5.0/atsimodel/utils.py +28 -0
  23. rsatsimodel-2.5.0/atsimodel/validation.py +70 -0
  24. rsatsimodel-2.5.0/examples/sample_atsi_input.xlsx +0 -0
  25. rsatsimodel-2.5.0/examples/sample_rs_atsi_input.csv +25 -0
  26. rsatsimodel-2.5.0/examples/sample_rs_atsi_input.txt +25 -0
  27. rsatsimodel-2.5.0/examples/sample_rs_atsi_input.xlsx +0 -0
  28. rsatsimodel-2.5.0/examples/sample_rs_atsi_rhow_input.csv +25 -0
  29. rsatsimodel-2.5.0/examples/sample_rs_atsi_rhow_input.txt +25 -0
  30. rsatsimodel-2.5.0/examples/sample_rs_atsi_rhow_input.xlsx +0 -0
  31. rsatsimodel-2.5.0/pyproject.toml +21 -0
  32. rsatsimodel-2.5.0/setup.cfg +4 -0
  33. rsatsimodel-2.5.0/setup.py +2 -0
@@ -0,0 +1,3 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dr Md Galal Uddin
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include examples *
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: RSATSIModel
3
+ Version: 2.5.0
4
+ Summary: Python package for computing ATSI and RS-ATSI using CHL, SAL, and Sentinel-3 OLCI-style data.
5
+ Author-email: Dr Md Galal Uddin <jalaluddinbd1987@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: pandas>=1.5.0
11
+ Requires-Dist: numpy>=1.23.0
12
+ Requires-Dist: matplotlib>=3.6.0
13
+ Requires-Dist: openpyxl>=3.1.0
14
+ Requires-Dist: scikit-learn>=1.2.0
15
+ Dynamic: license-file
16
+
17
+ # REMOTE SENSING (RS)-DRIVEN ATSIModel
18
+
19
+ ## Assessment of Trophic Status Index (ATSI) and RS-ATSI Python Package
20
+
21
+ **ATSIModel** is an open-source Python package for computing:
22
+ 1. **ATSI** from in-situ **chlorophyll-a (CHL)** and **salinity (SAL)** data.
23
+ 2. **RS-ATSI** from **Sentinel-3 OLCI style remote-sensing inputs**, with CHL prediction, ATSI derivation, and a structured validation framework.
24
+
25
+ The package is designed for **transitional, estuarine, and coastal waters**, and supports both standard ATSI computation and remote-sensing based ATSI workflows.
26
+
27
+ ## Author
28
+
29
+ **Dr Md Galal Uddin**
30
+ School of Engineering, University of Galway, Ireland
31
+ Email: **jalaluddinbd1987@gmail.com**
32
+
33
+ ### Research Profiles
34
+ - **Google Scholar**: https://scholar.google.com/citations?user=g6xaAOkAAAAJ&hl=en
35
+
36
+ ## Scientific Background
37
+
38
+ ATSI is implemented as a **CHL-driven, salinity-conditioned trophic scoring framework**.
39
+ This package extends that framework into a **remote-sensing compatible RS-ATSI workflow**.
40
+
41
+ ## Standard ATSI workflow
42
+
43
+ Required columns:
44
+
45
+ | Column | Description | Unit |
46
+ |---|---|---|
47
+ | CHL | Chlorophyll-a concentration | µg/L |
48
+ | SAL | Salinity | PSU |
49
+
50
+ Example:
51
+
52
+ ```python
53
+ from atsimodel.core import run_atsi
54
+ df = run_atsi("examples/sample_atsi_input.xlsx")
55
+ print(df.head())
56
+ ```
57
+
58
+ Export:
59
+
60
+ ```python
61
+ from atsimodel.utils import export_single
62
+ export_single(df, out_base="outputs/ATSI_results", formats=("xlsx", "csv", "txt", "json"))
63
+ ```
64
+
65
+ ## RS-ATSI workflow
66
+
67
+ Recommended input columns:
68
+ - `CHL`
69
+ - `SAL`
70
+ - `Rhow_1`, `Rhow_2`, `Rhow_3`, `Rhow_4`, `Rhow_5`, `Rhow_6`, `Rhow_7`, `Rhow_8`, `Rhow_9`, `Rhow_10`, `Rhow_11`
71
+
72
+ Recommended optional columns:
73
+ - `SITE`
74
+ - `SEASON`
75
+ - `ZONE`
76
+ - `SPLIT`
77
+
78
+ Example RS table:
79
+
80
+ | SITE | SEASON | ZONE | SPLIT | CHL | SAL | Rhow_1 | Rhow_2 | Rhow_3 | Rhow_4 | Rhow_5 | Rhow_6 | Rhow_7 | Rhow_8 | Rhow_9 | Rhow_10 | Rhow_11 |
81
+ |---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
82
+ | S1 | Winter | Upper | TRAIN | 8.0 | 31.0 | 0.011 | 0.012 | 0.013 | 0.014 | 0.015 | 0.016 | 0.017 | 0.018 | 0.019 | 0.020 | 0.021 |
83
+
84
+ Example:
85
+
86
+ ```python
87
+ from atsimodel.rs_core import run_rs_atsi_pipeline
88
+
89
+ results = run_rs_atsi_pipeline(
90
+ input_file="examples/sample_rs_atsi_input.xlsx",
91
+ output_dir="outputs/RS_ATSI_demo"
92
+ )
93
+ print(results["chl_validation_overall"])
94
+ print(results["atsi_validation_overall"])
95
+ ```
96
+
97
+ ## Validation design
98
+
99
+ ### 1. CHL validation
100
+ - R²
101
+ - RMSE
102
+ - MAE
103
+ - Bias
104
+ - train / test / independent validation
105
+ - site-wise validation
106
+
107
+ ### 2. ATSI validation
108
+ - RS-ATSI vs in-situ ATSI
109
+ - confusion matrix by trophic class
110
+ - agreement by station and season
111
+
112
+ ### 3. Spatial validation
113
+ - hotspot consistency
114
+ - estuarine gradient realism
115
+ - upper / middle / lower estuary comparison
116
+
117
+ ## Exported outputs
118
+
119
+ The package exports:
120
+ - Excel workbook
121
+ - CSV files
122
+ - TXT files
123
+ - JSON files
124
+
125
+ Typical output tables include:
126
+ - `predictions_all`
127
+ - `chl_validation_overall`
128
+ - `chl_validation_by_site_test`
129
+ - `atsi_validation_overall`
130
+ - `atsi_validation_by_site_test`
131
+ - `atsi_confusion_matrix_test`
132
+ - `station_season_agreement_test`
133
+ - `spatial_zone_summary_test`
134
+
135
+ ## Scientific notes
136
+
137
+ - ATSI remains **CHL-driven**
138
+ - SAL is used to determine **threshold context**
139
+ - ATSI values are clipped to **0–100**
140
+ - Current class translation is operational:
141
+ - Unpolluted
142
+ - Moderate
143
+ - Eutrophic
144
+ - Hypertrophic
145
+
146
+ ## Limitations
147
+
148
+ This release does not yet include:
149
+ - uncertainty quantification
150
+ - prediction intervals
151
+ - raster ingestion
152
+ - geospatial map production
153
+ - SHAP explainability
154
+
155
+ ## Contact
156
+
157
+ **Dr Md Galal Uddin**
158
+ School of Engineering, University of Galway, Ireland
159
+ Email: **jalaluddinbd1987@gmail.com**
@@ -0,0 +1,143 @@
1
+ # REMOTE SENSING (RS)-DRIVEN ATSIModel
2
+
3
+ ## Assessment of Trophic Status Index (ATSI) and RS-ATSI Python Package
4
+
5
+ **ATSIModel** is an open-source Python package for computing:
6
+ 1. **ATSI** from in-situ **chlorophyll-a (CHL)** and **salinity (SAL)** data.
7
+ 2. **RS-ATSI** from **Sentinel-3 OLCI style remote-sensing inputs**, with CHL prediction, ATSI derivation, and a structured validation framework.
8
+
9
+ The package is designed for **transitional, estuarine, and coastal waters**, and supports both standard ATSI computation and remote-sensing based ATSI workflows.
10
+
11
+ ## Author
12
+
13
+ **Dr Md Galal Uddin**
14
+ School of Engineering, University of Galway, Ireland
15
+ Email: **jalaluddinbd1987@gmail.com**
16
+
17
+ ### Research Profiles
18
+ - **Google Scholar**: https://scholar.google.com/citations?user=g6xaAOkAAAAJ&hl=en
19
+
20
+ ## Scientific Background
21
+
22
+ ATSI is implemented as a **CHL-driven, salinity-conditioned trophic scoring framework**.
23
+ This package extends that framework into a **remote-sensing compatible RS-ATSI workflow**.
24
+
25
+ ## Standard ATSI workflow
26
+
27
+ Required columns:
28
+
29
+ | Column | Description | Unit |
30
+ |---|---|---|
31
+ | CHL | Chlorophyll-a concentration | µg/L |
32
+ | SAL | Salinity | PSU |
33
+
34
+ Example:
35
+
36
+ ```python
37
+ from atsimodel.core import run_atsi
38
+ df = run_atsi("examples/sample_atsi_input.xlsx")
39
+ print(df.head())
40
+ ```
41
+
42
+ Export:
43
+
44
+ ```python
45
+ from atsimodel.utils import export_single
46
+ export_single(df, out_base="outputs/ATSI_results", formats=("xlsx", "csv", "txt", "json"))
47
+ ```
48
+
49
+ ## RS-ATSI workflow
50
+
51
+ Recommended input columns:
52
+ - `CHL`
53
+ - `SAL`
54
+ - `Rhow_1`, `Rhow_2`, `Rhow_3`, `Rhow_4`, `Rhow_5`, `Rhow_6`, `Rhow_7`, `Rhow_8`, `Rhow_9`, `Rhow_10`, `Rhow_11`
55
+
56
+ Recommended optional columns:
57
+ - `SITE`
58
+ - `SEASON`
59
+ - `ZONE`
60
+ - `SPLIT`
61
+
62
+ Example RS table:
63
+
64
+ | SITE | SEASON | ZONE | SPLIT | CHL | SAL | Rhow_1 | Rhow_2 | Rhow_3 | Rhow_4 | Rhow_5 | Rhow_6 | Rhow_7 | Rhow_8 | Rhow_9 | Rhow_10 | Rhow_11 |
65
+ |---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
66
+ | S1 | Winter | Upper | TRAIN | 8.0 | 31.0 | 0.011 | 0.012 | 0.013 | 0.014 | 0.015 | 0.016 | 0.017 | 0.018 | 0.019 | 0.020 | 0.021 |
67
+
68
+ Example:
69
+
70
+ ```python
71
+ from atsimodel.rs_core import run_rs_atsi_pipeline
72
+
73
+ results = run_rs_atsi_pipeline(
74
+ input_file="examples/sample_rs_atsi_input.xlsx",
75
+ output_dir="outputs/RS_ATSI_demo"
76
+ )
77
+ print(results["chl_validation_overall"])
78
+ print(results["atsi_validation_overall"])
79
+ ```
80
+
81
+ ## Validation design
82
+
83
+ ### 1. CHL validation
84
+ - R²
85
+ - RMSE
86
+ - MAE
87
+ - Bias
88
+ - train / test / independent validation
89
+ - site-wise validation
90
+
91
+ ### 2. ATSI validation
92
+ - RS-ATSI vs in-situ ATSI
93
+ - confusion matrix by trophic class
94
+ - agreement by station and season
95
+
96
+ ### 3. Spatial validation
97
+ - hotspot consistency
98
+ - estuarine gradient realism
99
+ - upper / middle / lower estuary comparison
100
+
101
+ ## Exported outputs
102
+
103
+ The package exports:
104
+ - Excel workbook
105
+ - CSV files
106
+ - TXT files
107
+ - JSON files
108
+
109
+ Typical output tables include:
110
+ - `predictions_all`
111
+ - `chl_validation_overall`
112
+ - `chl_validation_by_site_test`
113
+ - `atsi_validation_overall`
114
+ - `atsi_validation_by_site_test`
115
+ - `atsi_confusion_matrix_test`
116
+ - `station_season_agreement_test`
117
+ - `spatial_zone_summary_test`
118
+
119
+ ## Scientific notes
120
+
121
+ - ATSI remains **CHL-driven**
122
+ - SAL is used to determine **threshold context**
123
+ - ATSI values are clipped to **0–100**
124
+ - Current class translation is operational:
125
+ - Unpolluted
126
+ - Moderate
127
+ - Eutrophic
128
+ - Hypertrophic
129
+
130
+ ## Limitations
131
+
132
+ This release does not yet include:
133
+ - uncertainty quantification
134
+ - prediction intervals
135
+ - raster ingestion
136
+ - geospatial map production
137
+ - SHAP explainability
138
+
139
+ ## Contact
140
+
141
+ **Dr Md Galal Uddin**
142
+ School of Engineering, University of Galway, Ireland
143
+ Email: **jalaluddinbd1987@gmail.com**
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: RSATSIModel
3
+ Version: 2.5.0
4
+ Summary: Python package for computing ATSI and RS-ATSI using CHL, SAL, and Sentinel-3 OLCI-style data.
5
+ Author-email: Dr Md Galal Uddin <jalaluddinbd1987@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: pandas>=1.5.0
11
+ Requires-Dist: numpy>=1.23.0
12
+ Requires-Dist: matplotlib>=3.6.0
13
+ Requires-Dist: openpyxl>=3.1.0
14
+ Requires-Dist: scikit-learn>=1.2.0
15
+ Dynamic: license-file
16
+
17
+ # REMOTE SENSING (RS)-DRIVEN ATSIModel
18
+
19
+ ## Assessment of Trophic Status Index (ATSI) and RS-ATSI Python Package
20
+
21
+ **ATSIModel** is an open-source Python package for computing:
22
+ 1. **ATSI** from in-situ **chlorophyll-a (CHL)** and **salinity (SAL)** data.
23
+ 2. **RS-ATSI** from **Sentinel-3 OLCI style remote-sensing inputs**, with CHL prediction, ATSI derivation, and a structured validation framework.
24
+
25
+ The package is designed for **transitional, estuarine, and coastal waters**, and supports both standard ATSI computation and remote-sensing based ATSI workflows.
26
+
27
+ ## Author
28
+
29
+ **Dr Md Galal Uddin**
30
+ School of Engineering, University of Galway, Ireland
31
+ Email: **jalaluddinbd1987@gmail.com**
32
+
33
+ ### Research Profiles
34
+ - **Google Scholar**: https://scholar.google.com/citations?user=g6xaAOkAAAAJ&hl=en
35
+
36
+ ## Scientific Background
37
+
38
+ ATSI is implemented as a **CHL-driven, salinity-conditioned trophic scoring framework**.
39
+ This package extends that framework into a **remote-sensing compatible RS-ATSI workflow**.
40
+
41
+ ## Standard ATSI workflow
42
+
43
+ Required columns:
44
+
45
+ | Column | Description | Unit |
46
+ |---|---|---|
47
+ | CHL | Chlorophyll-a concentration | µg/L |
48
+ | SAL | Salinity | PSU |
49
+
50
+ Example:
51
+
52
+ ```python
53
+ from atsimodel.core import run_atsi
54
+ df = run_atsi("examples/sample_atsi_input.xlsx")
55
+ print(df.head())
56
+ ```
57
+
58
+ Export:
59
+
60
+ ```python
61
+ from atsimodel.utils import export_single
62
+ export_single(df, out_base="outputs/ATSI_results", formats=("xlsx", "csv", "txt", "json"))
63
+ ```
64
+
65
+ ## RS-ATSI workflow
66
+
67
+ Recommended input columns:
68
+ - `CHL`
69
+ - `SAL`
70
+ - `Rhow_1`, `Rhow_2`, `Rhow_3`, `Rhow_4`, `Rhow_5`, `Rhow_6`, `Rhow_7`, `Rhow_8`, `Rhow_9`, `Rhow_10`, `Rhow_11`
71
+
72
+ Recommended optional columns:
73
+ - `SITE`
74
+ - `SEASON`
75
+ - `ZONE`
76
+ - `SPLIT`
77
+
78
+ Example RS table:
79
+
80
+ | SITE | SEASON | ZONE | SPLIT | CHL | SAL | Rhow_1 | Rhow_2 | Rhow_3 | Rhow_4 | Rhow_5 | Rhow_6 | Rhow_7 | Rhow_8 | Rhow_9 | Rhow_10 | Rhow_11 |
81
+ |---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
82
+ | S1 | Winter | Upper | TRAIN | 8.0 | 31.0 | 0.011 | 0.012 | 0.013 | 0.014 | 0.015 | 0.016 | 0.017 | 0.018 | 0.019 | 0.020 | 0.021 |
83
+
84
+ Example:
85
+
86
+ ```python
87
+ from atsimodel.rs_core import run_rs_atsi_pipeline
88
+
89
+ results = run_rs_atsi_pipeline(
90
+ input_file="examples/sample_rs_atsi_input.xlsx",
91
+ output_dir="outputs/RS_ATSI_demo"
92
+ )
93
+ print(results["chl_validation_overall"])
94
+ print(results["atsi_validation_overall"])
95
+ ```
96
+
97
+ ## Validation design
98
+
99
+ ### 1. CHL validation
100
+ - R²
101
+ - RMSE
102
+ - MAE
103
+ - Bias
104
+ - train / test / independent validation
105
+ - site-wise validation
106
+
107
+ ### 2. ATSI validation
108
+ - RS-ATSI vs in-situ ATSI
109
+ - confusion matrix by trophic class
110
+ - agreement by station and season
111
+
112
+ ### 3. Spatial validation
113
+ - hotspot consistency
114
+ - estuarine gradient realism
115
+ - upper / middle / lower estuary comparison
116
+
117
+ ## Exported outputs
118
+
119
+ The package exports:
120
+ - Excel workbook
121
+ - CSV files
122
+ - TXT files
123
+ - JSON files
124
+
125
+ Typical output tables include:
126
+ - `predictions_all`
127
+ - `chl_validation_overall`
128
+ - `chl_validation_by_site_test`
129
+ - `atsi_validation_overall`
130
+ - `atsi_validation_by_site_test`
131
+ - `atsi_confusion_matrix_test`
132
+ - `station_season_agreement_test`
133
+ - `spatial_zone_summary_test`
134
+
135
+ ## Scientific notes
136
+
137
+ - ATSI remains **CHL-driven**
138
+ - SAL is used to determine **threshold context**
139
+ - ATSI values are clipped to **0–100**
140
+ - Current class translation is operational:
141
+ - Unpolluted
142
+ - Moderate
143
+ - Eutrophic
144
+ - Hypertrophic
145
+
146
+ ## Limitations
147
+
148
+ This release does not yet include:
149
+ - uncertainty quantification
150
+ - prediction intervals
151
+ - raster ingestion
152
+ - geospatial map production
153
+ - SHAP explainability
154
+
155
+ ## Contact
156
+
157
+ **Dr Md Galal Uddin**
158
+ School of Engineering, University of Galway, Ireland
159
+ Email: **jalaluddinbd1987@gmail.com**
@@ -0,0 +1,31 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ RSATSIModel.egg-info/PKG-INFO
7
+ RSATSIModel.egg-info/SOURCES.txt
8
+ RSATSIModel.egg-info/dependency_links.txt
9
+ RSATSIModel.egg-info/requires.txt
10
+ RSATSIModel.egg-info/top_level.txt
11
+ atsimodel/__init__.py
12
+ atsimodel/atsifunction.py
13
+ atsimodel/chl_models.py
14
+ atsimodel/classification.py
15
+ atsimodel/core.py
16
+ atsimodel/io.py
17
+ atsimodel/metrics.py
18
+ atsimodel/olci_features.py
19
+ atsimodel/plotting.py
20
+ atsimodel/preprocessing.py
21
+ atsimodel/rs_core.py
22
+ atsimodel/thresholds.py
23
+ atsimodel/utils.py
24
+ atsimodel/validation.py
25
+ examples/sample_atsi_input.xlsx
26
+ examples/sample_rs_atsi_input.csv
27
+ examples/sample_rs_atsi_input.txt
28
+ examples/sample_rs_atsi_input.xlsx
29
+ examples/sample_rs_atsi_rhow_input.csv
30
+ examples/sample_rs_atsi_rhow_input.txt
31
+ examples/sample_rs_atsi_rhow_input.xlsx
@@ -0,0 +1,5 @@
1
+ pandas>=1.5.0
2
+ numpy>=1.23.0
3
+ matplotlib>=3.6.0
4
+ openpyxl>=3.1.0
5
+ scikit-learn>=1.2.0
@@ -0,0 +1 @@
1
+ atsimodel
@@ -0,0 +1,5 @@
1
+ from .core import run_atsi
2
+ from .rs_core import run_rs_atsi_pipeline
3
+
4
+ __version__ = "2.5.0"
5
+ __author__ = "Dr Md Galal Uddin"
@@ -0,0 +1,20 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def clip_0_100(x):
5
+ if pd.isna(x):
6
+ return np.nan
7
+ return float(np.clip(x, 0.0, 100.0))
8
+
9
+ def compute_atsi_score(chl_value, chl_threshold_low, chl_threshold_upper):
10
+ NFh = 100.0
11
+ NFl = 0.0
12
+ if pd.isna(chl_value) or pd.isna(chl_threshold_low) or pd.isna(chl_threshold_upper):
13
+ return np.nan
14
+ if chl_threshold_upper <= 0:
15
+ return np.nan
16
+ atsi = (NFh - NFl) - (((chl_value - chl_threshold_low) / chl_threshold_upper) * NFh)
17
+ return clip_0_100(atsi)
18
+
19
+ def compute_atsi_series(df, chl_col="CHL", low_col="CHL_TL", upper_col="CHL_TU"):
20
+ return df.apply(lambda row: compute_atsi_score(row[chl_col], row[low_col], row[upper_col]), axis=1)
@@ -0,0 +1,37 @@
1
+ from dataclasses import dataclass
2
+ import numpy as np
3
+ from sklearn.ensemble import RandomForestRegressor
4
+ from sklearn.impute import SimpleImputer
5
+ from sklearn.pipeline import Pipeline
6
+ from .metrics import regression_metrics
7
+
8
+ @dataclass
9
+ class CHLModelBundle:
10
+ model: object
11
+ feature_cols: list
12
+ train_metrics: dict
13
+ test_metrics: dict
14
+
15
+ def fit_rf_chl_model(train_df, test_df, feature_cols, target_col="CHL", random_state=42):
16
+ X_train = train_df[feature_cols].copy()
17
+ y_train = train_df[target_col].copy()
18
+ X_test = test_df[feature_cols].copy()
19
+ y_test = test_df[target_col].copy()
20
+ model = Pipeline(steps=[
21
+ ("imputer", SimpleImputer(strategy="median")),
22
+ ("rf", RandomForestRegressor(n_estimators=500, random_state=random_state, n_jobs=-1)),
23
+ ])
24
+ model.fit(X_train, y_train)
25
+ pred_train = np.clip(model.predict(X_train), 0, None)
26
+ pred_test = np.clip(model.predict(X_test), 0, None)
27
+ train_metrics = regression_metrics(y_train, pred_train, label="Train")
28
+ test_metrics = regression_metrics(y_test, pred_test, label="Test")
29
+ bundle = CHLModelBundle(model=model, feature_cols=feature_cols, train_metrics=train_metrics, test_metrics=test_metrics)
30
+ return bundle, pred_train, pred_test
31
+
32
+ def predict_chl(df, bundle, output_col="CHL_RS"):
33
+ X = df[bundle.feature_cols].copy()
34
+ pred = np.clip(bundle.model.predict(X), 0, None)
35
+ out = df.copy()
36
+ out[output_col] = pred
37
+ return out
@@ -0,0 +1,17 @@
1
+ import pandas as pd
2
+
3
+ def classify_atsi(score):
4
+ if pd.isna(score):
5
+ return None
6
+ score = max(0.0, min(100.0, float(score)))
7
+ if score >= 75:
8
+ return "Unpolluted"
9
+ elif score >= 50:
10
+ return "Moderate"
11
+ elif score >= 25:
12
+ return "Eutrophic"
13
+ else:
14
+ return "Hypertrophic"
15
+
16
+ def classify_series(series):
17
+ return series.apply(classify_atsi)
@@ -0,0 +1,14 @@
1
+ from .io import load_data
2
+ from .preprocessing import validate_atsi_input
3
+ from .thresholds import assign_thresholds
4
+ from .atsifunction import compute_atsi_series
5
+ from .classification import classify_series
6
+
7
+ def run_atsi(file_path):
8
+ df = load_data(file_path)
9
+ df = validate_atsi_input(df)
10
+ df = assign_thresholds(df, sal_col="SAL")
11
+ df["ATSI"] = compute_atsi_series(df)
12
+ df["ATSI"] = df["ATSI"].clip(lower=0, upper=100)
13
+ df["ATSI_Class"] = classify_series(df["ATSI"])
14
+ return df
@@ -0,0 +1,30 @@
1
+ from pathlib import Path
2
+ import pandas as pd
3
+
4
+ def load_data(file_path):
5
+ file_path = Path(file_path)
6
+ if not file_path.exists():
7
+ raise FileNotFoundError(f"File not found: {file_path}")
8
+ suffix = file_path.suffix.lower()
9
+ if suffix in [".xlsx", ".xls"]:
10
+ return pd.read_excel(file_path)
11
+ if suffix == ".csv":
12
+ return pd.read_csv(file_path)
13
+ if suffix in [".txt", ".tsv"]:
14
+ return pd.read_csv(file_path, sep=None, engine="python")
15
+ raise ValueError(f"Unsupported file format: {suffix}. Use xlsx/xls/csv/txt/tsv.")
16
+
17
+ def save_table(df, file_path):
18
+ file_path = Path(file_path)
19
+ file_path.parent.mkdir(parents=True, exist_ok=True)
20
+ suffix = file_path.suffix.lower()
21
+ if suffix in [".xlsx", ".xls"]:
22
+ df.to_excel(file_path, index=False)
23
+ elif suffix == ".csv":
24
+ df.to_csv(file_path, index=False)
25
+ elif suffix in [".txt", ".tsv"]:
26
+ df.to_csv(file_path, index=False, sep="\\t")
27
+ elif suffix == ".json":
28
+ df.to_json(file_path, orient="records", indent=4)
29
+ else:
30
+ raise ValueError(f"Unsupported output format: {suffix}")
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error, confusion_matrix
4
+
5
+ def rmse(y_true, y_pred):
6
+ return float(np.sqrt(mean_squared_error(y_true, y_pred)))
7
+
8
+ def mbe(y_true, y_pred):
9
+ y_true = np.asarray(y_true)
10
+ y_pred = np.asarray(y_pred)
11
+ return float(np.mean(y_pred - y_true))
12
+
13
+ def agreement_rate(y_true_labels, y_pred_labels):
14
+ y_true_labels = pd.Series(y_true_labels).astype(str)
15
+ y_pred_labels = pd.Series(y_pred_labels).astype(str)
16
+ return float((y_true_labels == y_pred_labels).mean())
17
+
18
+ def regression_metrics(y_true, y_pred, label="All"):
19
+ y_true = pd.Series(y_true).astype(float)
20
+ y_pred = pd.Series(y_pred).astype(float)
21
+ mask = y_true.notna() & y_pred.notna()
22
+ y_true = y_true[mask]
23
+ y_pred = y_pred[mask]
24
+ if len(y_true) == 0:
25
+ return {"Group": label, "N": 0, "R2": np.nan, "RMSE": np.nan, "MAE": np.nan, "Bias": np.nan}
26
+ return {
27
+ "Group": label,
28
+ "N": int(len(y_true)),
29
+ "R2": float(r2_score(y_true, y_pred)) if len(y_true) >= 2 else np.nan,
30
+ "RMSE": rmse(y_true, y_pred),
31
+ "MAE": float(mean_absolute_error(y_true, y_pred)),
32
+ "Bias": mbe(y_true, y_pred),
33
+ }
34
+
35
+ def confusion_matrix_table(y_true_labels, y_pred_labels, labels=None):
36
+ y_true_labels = pd.Series(y_true_labels).astype(str)
37
+ y_pred_labels = pd.Series(y_pred_labels).astype(str)
38
+ if labels is None:
39
+ labels = sorted(set(y_true_labels.unique()).union(set(y_pred_labels.unique())))
40
+ cm = confusion_matrix(y_true_labels, y_pred_labels, labels=labels)
41
+ return pd.DataFrame(cm, index=[f"True_{x}" for x in labels], columns=[f"Pred_{x}" for x in labels])
@@ -0,0 +1,55 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ # Sentinel-3 OLCI reflectance style inputs expected from tabular exports
5
+ DEFAULT_OLCI_FEATURES = [f"RHOW_{i}" for i in range(1, 12)]
6
+
7
+
8
+ def add_olci_features(df):
9
+ """
10
+ Add engineered features from Sentinel-3 OLCI-style reflectance inputs.
11
+
12
+ Required raw inputs:
13
+ RHOW_1 ... RHOW_11
14
+
15
+ These are intended to match user-exported tabular reflectance columns such as:
16
+ Rhow_1, Rhow_2, ..., Rhow_11
17
+ """
18
+ df = df.copy()
19
+ eps = 1e-9
20
+
21
+ for c in DEFAULT_OLCI_FEATURES:
22
+ df[c] = pd.to_numeric(df[c], errors="coerce")
23
+
24
+ # Core ratios
25
+ df["R_6_1"] = df["RHOW_6"] / (df["RHOW_1"] + eps)
26
+ df["R_6_2"] = df["RHOW_6"] / (df["RHOW_2"] + eps)
27
+ df["R_7_3"] = df["RHOW_7"] / (df["RHOW_3"] + eps)
28
+ df["R_8_4"] = df["RHOW_8"] / (df["RHOW_4"] + eps)
29
+ df["R_9_5"] = df["RHOW_9"] / (df["RHOW_5"] + eps)
30
+ df["R_10_6"] = df["RHOW_10"] / (df["RHOW_6"] + eps)
31
+ df["R_11_7"] = df["RHOW_11"] / (df["RHOW_7"] + eps)
32
+
33
+ # Normalized differences
34
+ pairs = [(6,1),(6,2),(7,3),(8,4),(9,5),(10,6),(11,7)]
35
+ for a,b in pairs:
36
+ df[f"ND_{a}_{b}"] = (df[f"RHOW_{a}"] - df[f"RHOW_{b}"]) / (df[f"RHOW_{a}"] + df[f"RHOW_{b}"] + eps)
37
+
38
+ # Adjacent band slopes / differences
39
+ for i in range(1, 11):
40
+ df[f"DIFF_{i}_{i+1}"] = df[f"RHOW_{i+1}"] - df[f"RHOW_{i}"]
41
+
42
+ # Logs
43
+ for c in DEFAULT_OLCI_FEATURES:
44
+ df[f"LOG_{c}"] = np.log1p(np.clip(df[c], a_min=0, a_max=None))
45
+
46
+ return df
47
+
48
+
49
+ def get_default_model_features():
50
+ feats = DEFAULT_OLCI_FEATURES.copy()
51
+ feats += ["R_6_1", "R_6_2", "R_7_3", "R_8_4", "R_9_5", "R_10_6", "R_11_7"]
52
+ feats += [f"ND_{a}_{b}" for a,b in [(6,1),(6,2),(7,3),(8,4),(9,5),(10,6),(11,7)]]
53
+ feats += [f"DIFF_{i}_{i+1}" for i in range(1, 11)]
54
+ feats += [f"LOG_RHOW_{i}" for i in range(1, 12)]
55
+ return feats
@@ -0,0 +1,19 @@
1
+ import matplotlib.pyplot as plt
2
+
3
+ def plot_atsi_distribution(df, score_col="ATSI"):
4
+ plt.figure(figsize=(8, 5))
5
+ plt.hist(df[score_col].dropna(), bins=20, edgecolor="black")
6
+ plt.xlabel("ATSI Score")
7
+ plt.ylabel("Frequency")
8
+ plt.title("Distribution of ATSI Scores")
9
+ plt.tight_layout()
10
+ plt.show()
11
+
12
+ def plot_chl_vs_atsi(df, chl_col="CHL", score_col="ATSI"):
13
+ plt.figure(figsize=(8, 5))
14
+ plt.scatter(df[chl_col], df[score_col])
15
+ plt.xlabel("CHL (µg/L)")
16
+ plt.ylabel("ATSI Score")
17
+ plt.title("CHL vs ATSI")
18
+ plt.tight_layout()
19
+ plt.show()
@@ -0,0 +1,29 @@
1
+ import pandas as pd
2
+
3
+ def standardize_columns(df):
4
+ df = df.copy()
5
+ df.columns = [str(c).strip().upper() for c in df.columns]
6
+ return df
7
+
8
+ def validate_atsi_input(df):
9
+ df = standardize_columns(df)
10
+ required = ["CHL", "SAL"]
11
+ missing = [c for c in required if c not in df.columns]
12
+ if missing:
13
+ raise ValueError(f"Missing required columns: {missing}. ATSI requires CHL and SAL.")
14
+ df["CHL"] = pd.to_numeric(df["CHL"], errors="coerce")
15
+ df["SAL"] = pd.to_numeric(df["SAL"], errors="coerce")
16
+ return df
17
+
18
+ def validate_rs_input(df, feature_cols, sal_col="SAL", target_col=None):
19
+ df = standardize_columns(df)
20
+ feature_cols = [c.upper() for c in feature_cols]
21
+ sal_col = sal_col.upper()
22
+ target_col = target_col.upper() if target_col else None
23
+ required = feature_cols + [sal_col] + ([target_col] if target_col else [])
24
+ missing = [c for c in required if c not in df.columns]
25
+ if missing:
26
+ raise ValueError(f"Missing RS input columns: {missing}")
27
+ for c in feature_cols + [sal_col] + ([target_col] if target_col else []):
28
+ df[c] = pd.to_numeric(df[c], errors="coerce")
29
+ return df
@@ -0,0 +1,92 @@
1
+ import pandas as pd
2
+ from sklearn.model_selection import train_test_split
3
+ from .io import load_data
4
+ from .preprocessing import validate_rs_input, standardize_columns
5
+ from .olci_features import add_olci_features, get_default_model_features
6
+ from .chl_models import fit_rf_chl_model, predict_chl
7
+ from .thresholds import assign_thresholds
8
+ from .atsifunction import compute_atsi_series
9
+ from .classification import classify_series
10
+ from .validation import validate_chl_overall, validate_chl_by_group, validate_atsi_regression, validate_atsi_by_group, validate_atsi_classification, validate_station_season_agreement, spatial_zone_summary
11
+ from .utils import export_results_dict
12
+
13
+ def _split_train_test_independent(df, split_col="SPLIT"):
14
+ df = df.copy()
15
+ if split_col in df.columns:
16
+ train_df = df[df[split_col].astype(str).str.upper() == "TRAIN"].copy()
17
+ test_df = df[df[split_col].astype(str).str.upper() == "TEST"].copy()
18
+ ind_df = df[df[split_col].astype(str).str.upper().isin(["INDEPENDENT", "VALIDATION"])].copy()
19
+ if len(train_df) == 0 or len(test_df) == 0:
20
+ raise ValueError("When SPLIT exists, it must contain at least TRAIN and TEST rows.")
21
+ return train_df, test_df, ind_df
22
+ train_df, test_df = train_test_split(df, test_size=0.25, random_state=42)
23
+ return train_df.copy(), test_df.copy(), pd.DataFrame(columns=df.columns)
24
+
25
+ def _compute_atsi_from_chl_sal(df, chl_col, sal_col="SAL", out_score_col="ATSI_RS", out_class_col="ATSI_RS_CLASS"):
26
+ out = df.copy()
27
+ out = out.rename(columns={sal_col: "SAL"})
28
+ out = assign_thresholds(out, sal_col="SAL")
29
+ out[out_score_col] = compute_atsi_series(out, chl_col=chl_col, low_col="CHL_TL", upper_col="CHL_TU").clip(lower=0, upper=100)
30
+ out[out_class_col] = classify_series(out[out_score_col])
31
+ return out
32
+
33
+ def run_rs_atsi_pipeline(input_file, output_dir="outputs_rs_atsi", target_col="CHL", sal_col="SAL", feature_cols=None, site_col="SITE", season_col="SEASON", zone_col="ZONE"):
34
+ df = load_data(input_file)
35
+ df = standardize_columns(df)
36
+ df = add_olci_features(df)
37
+ if feature_cols is None:
38
+ feature_cols = get_default_model_features()
39
+ feature_cols = [c.upper() for c in feature_cols]
40
+ df = validate_rs_input(df, feature_cols=feature_cols, sal_col=sal_col, target_col=target_col)
41
+ train_df, test_df, ind_df = _split_train_test_independent(df, split_col="SPLIT")
42
+ bundle, pred_train, pred_test = fit_rf_chl_model(train_df, test_df, feature_cols=feature_cols, target_col=target_col.upper())
43
+ train_df["CHL_RS"] = pred_train
44
+ test_df["CHL_RS"] = pred_test
45
+ if len(ind_df) > 0:
46
+ ind_df = predict_chl(ind_df, bundle, output_col="CHL_RS")
47
+ train_df = _compute_atsi_from_chl_sal(train_df, chl_col=target_col.upper(), sal_col=sal_col.upper(), out_score_col="ATSI_INSITU", out_class_col="ATSI_INSITU_CLASS")
48
+ train_df = _compute_atsi_from_chl_sal(train_df, chl_col="CHL_RS", sal_col=sal_col.upper(), out_score_col="ATSI_RS", out_class_col="ATSI_RS_CLASS")
49
+ test_df = _compute_atsi_from_chl_sal(test_df, chl_col=target_col.upper(), sal_col=sal_col.upper(), out_score_col="ATSI_INSITU", out_class_col="ATSI_INSITU_CLASS")
50
+ test_df = _compute_atsi_from_chl_sal(test_df, chl_col="CHL_RS", sal_col=sal_col.upper(), out_score_col="ATSI_RS", out_class_col="ATSI_RS_CLASS")
51
+ if len(ind_df) > 0:
52
+ ind_df = _compute_atsi_from_chl_sal(ind_df, chl_col=target_col.upper(), sal_col=sal_col.upper(), out_score_col="ATSI_INSITU", out_class_col="ATSI_INSITU_CLASS")
53
+ ind_df = _compute_atsi_from_chl_sal(ind_df, chl_col="CHL_RS", sal_col=sal_col.upper(), out_score_col="ATSI_RS", out_class_col="ATSI_RS_CLASS")
54
+ validation_tables = {}
55
+ validation_tables["chl_validation_overall"] = pd.concat([
56
+ validate_chl_overall(train_df, actual_col=target_col.upper(), pred_col="CHL_RS", group_label="Train"),
57
+ validate_chl_overall(test_df, actual_col=target_col.upper(), pred_col="CHL_RS", group_label="Test"),
58
+ validate_chl_overall(ind_df, actual_col=target_col.upper(), pred_col="CHL_RS", group_label="Independent") if len(ind_df) > 0 else pd.DataFrame(),
59
+ ], ignore_index=True)
60
+ validation_tables["chl_validation_by_site_test"] = validate_chl_by_group(test_df, actual_col=target_col.upper(), pred_col="CHL_RS", group_col=site_col.upper())
61
+ if len(ind_df) > 0:
62
+ validation_tables["chl_validation_by_site_independent"] = validate_chl_by_group(ind_df, actual_col=target_col.upper(), pred_col="CHL_RS", group_col=site_col.upper())
63
+ validation_tables["atsi_validation_overall"] = pd.concat([
64
+ validate_atsi_regression(train_df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_label="Train"),
65
+ validate_atsi_regression(test_df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_label="Test"),
66
+ validate_atsi_regression(ind_df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_label="Independent") if len(ind_df) > 0 else pd.DataFrame(),
67
+ ], ignore_index=True)
68
+ validation_tables["atsi_validation_by_site_test"] = validate_atsi_by_group(test_df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_col=site_col.upper())
69
+ if len(ind_df) > 0:
70
+ validation_tables["atsi_validation_by_site_independent"] = validate_atsi_by_group(ind_df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_col=site_col.upper())
71
+ class_test = validate_atsi_classification(test_df, actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
72
+ validation_tables["atsi_class_summary_test"] = class_test["summary"]
73
+ validation_tables["atsi_confusion_matrix_test"] = class_test["confusion_matrix"]
74
+ if len(ind_df) > 0:
75
+ class_ind = validate_atsi_classification(ind_df, actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
76
+ validation_tables["atsi_class_summary_independent"] = class_ind["summary"]
77
+ validation_tables["atsi_confusion_matrix_independent"] = class_ind["confusion_matrix"]
78
+ validation_tables["station_season_agreement_test"] = validate_station_season_agreement(test_df, site_col=site_col.upper(), season_col=season_col.upper(), actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
79
+ if len(ind_df) > 0:
80
+ validation_tables["station_season_agreement_independent"] = validate_station_season_agreement(ind_df, site_col=site_col.upper(), season_col=season_col.upper(), actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
81
+ validation_tables["spatial_zone_summary_test"] = spatial_zone_summary(test_df, zone_col=zone_col.upper(), actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
82
+ if len(ind_df) > 0:
83
+ validation_tables["spatial_zone_summary_independent"] = spatial_zone_summary(ind_df, zone_col=zone_col.upper(), actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS")
84
+ validation_tables["chl_model_metrics_summary"] = pd.DataFrame([bundle.train_metrics, bundle.test_metrics])
85
+ predictions_all = pd.concat([
86
+ train_df.assign(DATASET="Train"),
87
+ test_df.assign(DATASET="Test"),
88
+ ind_df.assign(DATASET="Independent") if len(ind_df) > 0 else pd.DataFrame()
89
+ ], ignore_index=True)
90
+ results = {"predictions_all": predictions_all, **validation_tables}
91
+ export_results_dict(results, output_dir=output_dir)
92
+ return results
@@ -0,0 +1,62 @@
1
+ import pandas as pd
2
+
3
+ SAL_THRESHOLD_TABLE = {
4
+ 0: {"CHL_TL": 15.0, "CHL_TU": 30.0},
5
+ 1: {"CHL_TL": 15.0, "CHL_TU": 30.0},
6
+ 2: {"CHL_TL": 15.0, "CHL_TU": 30.0},
7
+ 3: {"CHL_TL": 15.0, "CHL_TU": 30.0},
8
+ 4: {"CHL_TL": 15.0, "CHL_TU": 30.0},
9
+ 5: {"CHL_TL": 15.0, "CHL_TU": 30.0},
10
+ 6: {"CHL_TL": 15.0, "CHL_TU": 30.0},
11
+ 7: {"CHL_TL": 15.0, "CHL_TU": 30.0},
12
+ 8: {"CHL_TL": 15.0, "CHL_TU": 30.0},
13
+ 9: {"CHL_TL": 15.0, "CHL_TU": 30.0},
14
+ 10: {"CHL_TL": 15.0, "CHL_TU": 30.0},
15
+ 11: {"CHL_TL": 15.0, "CHL_TU": 30.0},
16
+ 12: {"CHL_TL": 15.0, "CHL_TU": 30.0},
17
+ 13: {"CHL_TL": 15.0, "CHL_TU": 30.0},
18
+ 14: {"CHL_TL": 15.0, "CHL_TU": 30.0},
19
+ 15: {"CHL_TL": 15.0, "CHL_TU": 30.0},
20
+ 16: {"CHL_TL": 15.0, "CHL_TU": 30.0},
21
+ 17: {"CHL_TL": 15.0, "CHL_TU": 30.0},
22
+ 18: {"CHL_TL": 14.7, "CHL_TU": 29.4},
23
+ 19: {"CHL_TL": 14.4, "CHL_TU": 28.9},
24
+ 20: {"CHL_TL": 14.2, "CHL_TU": 28.3},
25
+ 21: {"CHL_TL": 13.9, "CHL_TU": 27.8},
26
+ 22: {"CHL_TL": 13.6, "CHL_TU": 27.2},
27
+ 23: {"CHL_TL": 13.3, "CHL_TU": 26.7},
28
+ 24: {"CHL_TL": 13.1, "CHL_TU": 26.1},
29
+ 25: {"CHL_TL": 12.8, "CHL_TU": 25.6},
30
+ 26: {"CHL_TL": 12.5, "CHL_TU": 25.0},
31
+ 27: {"CHL_TL": 12.2, "CHL_TU": 24.4},
32
+ 28: {"CHL_TL": 11.9, "CHL_TU": 23.9},
33
+ 29: {"CHL_TL": 11.7, "CHL_TU": 23.3},
34
+ 30: {"CHL_TL": 11.4, "CHL_TU": 22.8},
35
+ 31: {"CHL_TL": 11.1, "CHL_TU": 22.2},
36
+ 32: {"CHL_TL": 10.8, "CHL_TU": 21.7},
37
+ 33: {"CHL_TL": 10.6, "CHL_TU": 21.1},
38
+ 34: {"CHL_TL": 10.3, "CHL_TU": 20.6},
39
+ 35: {"CHL_TL": 10.0, "CHL_TU": 20.0}
40
+ }
41
+
42
+ def round_salinity_to_median_bin(sal):
43
+ if pd.isna(sal):
44
+ return None
45
+ sal_bin = int(round(float(sal)))
46
+ return max(0, min(35, sal_bin))
47
+
48
+ def get_chl_thresholds_from_salinity(sal):
49
+ sal_bin = round_salinity_to_median_bin(sal)
50
+ if sal_bin is None:
51
+ return {"SAL_BIN": None, "CHL_TL": None, "CHL_TU": None}
52
+ vals = SAL_THRESHOLD_TABLE[sal_bin]
53
+ return {"SAL_BIN": sal_bin, "CHL_TL": vals["CHL_TL"], "CHL_TU": vals["CHL_TU"]}
54
+
55
+ def assign_thresholds(df, sal_col="SAL"):
56
+ df = df.copy()
57
+ sal_col = sal_col.upper()
58
+ out = df[sal_col].apply(get_chl_thresholds_from_salinity).apply(pd.Series)
59
+ df["SAL_BIN"] = out["SAL_BIN"]
60
+ df["CHL_TL"] = out["CHL_TL"]
61
+ df["CHL_TU"] = out["CHL_TU"]
62
+ return df
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+ import pandas as pd
3
+
4
+ def export_results_dict(results_dict, output_dir):
5
+ output_dir = Path(output_dir)
6
+ output_dir.mkdir(parents=True, exist_ok=True)
7
+ excel_path = output_dir / "RS_ATSI_results_bundle.xlsx"
8
+ with pd.ExcelWriter(excel_path, engine="openpyxl") as writer:
9
+ for key, value in results_dict.items():
10
+ if isinstance(value, pd.DataFrame):
11
+ value.to_excel(writer, sheet_name=key[:31], index=False)
12
+ for key, value in results_dict.items():
13
+ if isinstance(value, pd.DataFrame):
14
+ value.to_csv(output_dir / f"{key}.csv", index=False)
15
+ value.to_csv(output_dir / f"{key}.txt", index=False, sep="\t")
16
+ value.to_json(output_dir / f"{key}.json", orient="records", indent=4)
17
+
18
+ def export_single(df, out_base="ATSI_results", formats=("xlsx", "csv", "txt", "json")):
19
+ out_base = Path(out_base)
20
+ out_base.parent.mkdir(parents=True, exist_ok=True)
21
+ if "xlsx" in formats:
22
+ df.to_excel(f"{out_base}.xlsx", index=False)
23
+ if "csv" in formats:
24
+ df.to_csv(f"{out_base}.csv", index=False)
25
+ if "txt" in formats:
26
+ df.to_csv(f"{out_base}.txt", index=False, sep="\t")
27
+ if "json" in formats:
28
+ df.to_json(f"{out_base}.json", orient="records", indent=4)
@@ -0,0 +1,70 @@
1
+ import pandas as pd
2
+ from .metrics import regression_metrics, agreement_rate, confusion_matrix_table
3
+ from .classification import classify_series
4
+
5
+ def validate_chl_overall(df, actual_col="CHL", pred_col="CHL_RS", group_label="Overall"):
6
+ return pd.DataFrame([regression_metrics(df[actual_col], df[pred_col], label=group_label)])
7
+
8
+ def validate_chl_by_group(df, actual_col="CHL", pred_col="CHL_RS", group_col="SITE"):
9
+ rows = []
10
+ if group_col not in df.columns:
11
+ return pd.DataFrame()
12
+ for g, sub in df.groupby(group_col):
13
+ rows.append(regression_metrics(sub[actual_col], sub[pred_col], label=str(g)))
14
+ return pd.DataFrame(rows)
15
+
16
+ def validate_atsi_regression(df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_label="Overall"):
17
+ return pd.DataFrame([regression_metrics(df[actual_col], df[pred_col], label=group_label)])
18
+
19
+ def validate_atsi_by_group(df, actual_col="ATSI_INSITU", pred_col="ATSI_RS", group_col="SITE"):
20
+ rows = []
21
+ if group_col not in df.columns:
22
+ return pd.DataFrame()
23
+ for g, sub in df.groupby(group_col):
24
+ rows.append(regression_metrics(sub[actual_col], sub[pred_col], label=str(g)))
25
+ return pd.DataFrame(rows)
26
+
27
+ def validate_atsi_classification(df, actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS",
28
+ actual_class_col=None, pred_class_col=None):
29
+ out = {}
30
+ df = df.copy()
31
+ if actual_class_col is None:
32
+ actual_class_col = "__ATSI_CLASS_TRUE__"
33
+ df[actual_class_col] = classify_series(df[actual_score_col])
34
+ if pred_class_col is None:
35
+ pred_class_col = "__ATSI_CLASS_PRED__"
36
+ df[pred_class_col] = classify_series(df[pred_score_col])
37
+ out["summary"] = pd.DataFrame([{
38
+ "Agreement_Rate": agreement_rate(df[actual_class_col], df[pred_class_col]),
39
+ "N": int(len(df))
40
+ }])
41
+ out["confusion_matrix"] = confusion_matrix_table(df[actual_class_col], df[pred_class_col])
42
+ return out
43
+
44
+ def validate_station_season_agreement(df, site_col="SITE", season_col="SEASON",
45
+ actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS"):
46
+ rows = []
47
+ df = df.copy()
48
+ df["ATSI_CLASS_TRUE"] = classify_series(df[actual_score_col])
49
+ df["ATSI_CLASS_PRED"] = classify_series(df[pred_score_col])
50
+ group_cols = []
51
+ if site_col in df.columns: group_cols.append(site_col)
52
+ if season_col in df.columns: group_cols.append(season_col)
53
+ if not group_cols: return pd.DataFrame()
54
+ for g, sub in df.groupby(group_cols):
55
+ if not isinstance(g, tuple): g = (g,)
56
+ row = {col: val for col, val in zip(group_cols, g)}
57
+ row["Agreement_Rate"] = agreement_rate(sub["ATSI_CLASS_TRUE"], sub["ATSI_CLASS_PRED"])
58
+ row["N"] = int(len(sub))
59
+ rows.append(row)
60
+ return pd.DataFrame(rows)
61
+
62
+ def spatial_zone_summary(df, zone_col="ZONE", actual_score_col="ATSI_INSITU", pred_score_col="ATSI_RS"):
63
+ if zone_col not in df.columns:
64
+ return pd.DataFrame()
65
+ rows = []
66
+ for g, sub in df.groupby(zone_col):
67
+ row = regression_metrics(sub[actual_score_col], sub[pred_score_col], label=str(g))
68
+ row[zone_col] = g
69
+ rows.append(row)
70
+ return pd.DataFrame(rows)
@@ -0,0 +1,25 @@
1
+ SITE,SEASON,ZONE,SPLIT,CHL,SAL,Rhow_1,Rhow_2,Rhow_3,Rhow_4,Rhow_5,Rhow_6,Rhow_7,Rhow_8,Rhow_9,Rhow_10,Rhow_11
2
+ S1,Winter,Upper,TRAIN,17.694,20.95,0.0154,0.01285,0.00539,0.02886,0.0062,0.00795,0.02391,0.00521,0.02326,0.00531,0.02649
3
+ S2,Spring,Middle,TRAIN,3.238,18.24,0.01458,0.01779,0.02632,0.01676,0.02376,0.01314,0.02592,0.02152,0.02945,0.00215,0.02053
4
+ S3,Summer,Lower,TRAIN,7.068,25.54,0.0047,0.00304,0.00619,0.0207,0.02697,0.00515,0.01458,0.01374,0.00182,0.01121,0.01388
5
+ S4,Autumn,Upper,TRAIN,6.241,29.65,0.00847,0.01516,0.00188,0.02096,0.01436,0.01993,0.00716,0.02757,0.00408,0.02278,0.02706
6
+ S5,Winter,Middle,TRAIN,6.046,32.7,0.00109,0.01869,0.01722,0.00542,0.00212,0.01428,0.0029,0.01287,0.00545,0.02678,0.01874
7
+ S6,Spring,Lower,TRAIN,20.678,28.01,0.01205,0.02257,0.01344,0.00521,0.01014,0.01242,0.00331,0.0018,0.01785,0.00777,0.02077
8
+ S1,Summer,Upper,TRAIN,23.237,32.67,0.0177,0.01347,0.0259,0.0144,0.00144,0.02434,0.00497,0.02206,0.01349,0.02838,0.02167
9
+ S2,Autumn,Middle,TRAIN,8.361,31.84,0.01339,0.00978,0.00566,0.01614,0.01113,0.01431,0.00881,0.02959,0.00286,0.0033,0.01374
10
+ S3,Winter,Lower,TRAIN,20.854,21.49,0.02522,0.00117,0.01145,0.00889,0.02012,0.02106,0.02354,0.01452,0.0091,0.00327,0.0095
11
+ S4,Spring,Upper,TRAIN,22.468,31.86,0.01888,0.02294,0.01957,0.00622,0.00869,0.02368,0.00766,0.02863,0.01233,0.02146,0.02672
12
+ S5,Summer,Middle,TRAIN,13.798,29.69,0.00872,0.00325,0.028,0.01561,0.01991,0.01883,0.01323,0.00572,0.01567,0.00297,0.0117
13
+ S6,Autumn,Lower,TRAIN,7.634,22.45,0.02452,0.01521,0.00859,0.02013,0.0279,0.02888,0.01429,0.00717,0.02292,0.02915,0.01367
14
+ S1,Winter,Upper,TRAIN,20.958,30.75,0.01549,0.00983,0.02095,0.00763,0.01452,0.01698,0.00998,0.0109,0.01518,0.00895,0.00901
15
+ S2,Spring,Middle,TRAIN,6.917,31.84,0.02301,0.02538,0.00532,0.00733,0.00701,0.00735,0.00613,0.01277,0.02495,0.00473,0.01706
16
+ S3,Summer,Lower,TEST,19.054,22.79,0.01742,0.02856,0.02246,0.02869,0.02776,0.01671,0.00877,0.02708,0.01667,0.02329,0.01661
17
+ S4,Autumn,Upper,TEST,16.489,26.43,0.01369,0.01025,0.0095,0.02202,0.02843,0.00809,0.02572,0.0292,0.01136,0.01325,0.01352
18
+ S5,Winter,Middle,TEST,23.33,19.14,0.01249,0.02704,0.00807,0.002,0.00491,0.00342,0.02983,0.0158,0.02437,0.0271,0.01125
19
+ S6,Spring,Lower,TEST,7.334,27.33,0.00164,0.01079,0.00766,0.02947,0.02709,0.02293,0.01175,0.02121,0.0064,0.01528,0.01671
20
+ S1,Summer,Upper,TEST,20.38,21.81,0.01461,0.02455,0.01175,0.00125,0.00947,0.00867,0.01439,0.0192,0.02679,0.00123,0.01208
21
+ S2,Autumn,Middle,TEST,13.918,30.24,0.01908,0.02417,0.01053,0.00868,0.00472,0.0101,0.01071,0.02596,0.02053,0.00574,0.02359
22
+ S3,Winter,Lower,INDEPENDENT,7.326,20.78,0.02844,0.02,0.01877,0.0276,0.00442,0.02966,0.0116,0.00413,0.02865,0.00388,0.00545
23
+ S4,Spring,Upper,INDEPENDENT,5.816,23.0,0.01362,0.00763,0.0168,0.00333,0.02302,0.01262,0.02093,0.01729,0.00709,0.00626,0.02189
24
+ S5,Summer,Middle,INDEPENDENT,13.449,18.23,0.01508,0.00499,0.02274,0.02579,0.00356,0.0138,0.02232,0.02444,0.00775,0.00885,0.00705
25
+ S6,Autumn,Lower,INDEPENDENT,15.403,18.52,0.01605,0.01331,0.00955,0.0052,0.02313,0.0277,0.01019,0.02345,0.01424,0.00319,0.02803
@@ -0,0 +1,25 @@
1
+ SITE SEASON ZONE SPLIT CHL SAL Rhow_1 Rhow_2 Rhow_3 Rhow_4 Rhow_5 Rhow_6 Rhow_7 Rhow_8 Rhow_9 Rhow_10 Rhow_11
2
+ S1 Winter Upper TRAIN 17.694 20.95 0.0154 0.01285 0.00539 0.02886 0.0062 0.00795 0.02391 0.00521 0.02326 0.00531 0.02649
3
+ S2 Spring Middle TRAIN 3.238 18.24 0.01458 0.01779 0.02632 0.01676 0.02376 0.01314 0.02592 0.02152 0.02945 0.00215 0.02053
4
+ S3 Summer Lower TRAIN 7.068 25.54 0.0047 0.00304 0.00619 0.0207 0.02697 0.00515 0.01458 0.01374 0.00182 0.01121 0.01388
5
+ S4 Autumn Upper TRAIN 6.241 29.65 0.00847 0.01516 0.00188 0.02096 0.01436 0.01993 0.00716 0.02757 0.00408 0.02278 0.02706
6
+ S5 Winter Middle TRAIN 6.046 32.7 0.00109 0.01869 0.01722 0.00542 0.00212 0.01428 0.0029 0.01287 0.00545 0.02678 0.01874
7
+ S6 Spring Lower TRAIN 20.678 28.01 0.01205 0.02257 0.01344 0.00521 0.01014 0.01242 0.00331 0.0018 0.01785 0.00777 0.02077
8
+ S1 Summer Upper TRAIN 23.237 32.67 0.0177 0.01347 0.0259 0.0144 0.00144 0.02434 0.00497 0.02206 0.01349 0.02838 0.02167
9
+ S2 Autumn Middle TRAIN 8.361 31.84 0.01339 0.00978 0.00566 0.01614 0.01113 0.01431 0.00881 0.02959 0.00286 0.0033 0.01374
10
+ S3 Winter Lower TRAIN 20.854 21.49 0.02522 0.00117 0.01145 0.00889 0.02012 0.02106 0.02354 0.01452 0.0091 0.00327 0.0095
11
+ S4 Spring Upper TRAIN 22.468 31.86 0.01888 0.02294 0.01957 0.00622 0.00869 0.02368 0.00766 0.02863 0.01233 0.02146 0.02672
12
+ S5 Summer Middle TRAIN 13.798 29.69 0.00872 0.00325 0.028 0.01561 0.01991 0.01883 0.01323 0.00572 0.01567 0.00297 0.0117
13
+ S6 Autumn Lower TRAIN 7.634 22.45 0.02452 0.01521 0.00859 0.02013 0.0279 0.02888 0.01429 0.00717 0.02292 0.02915 0.01367
14
+ S1 Winter Upper TRAIN 20.958 30.75 0.01549 0.00983 0.02095 0.00763 0.01452 0.01698 0.00998 0.0109 0.01518 0.00895 0.00901
15
+ S2 Spring Middle TRAIN 6.917 31.84 0.02301 0.02538 0.00532 0.00733 0.00701 0.00735 0.00613 0.01277 0.02495 0.00473 0.01706
16
+ S3 Summer Lower TEST 19.054 22.79 0.01742 0.02856 0.02246 0.02869 0.02776 0.01671 0.00877 0.02708 0.01667 0.02329 0.01661
17
+ S4 Autumn Upper TEST 16.489 26.43 0.01369 0.01025 0.0095 0.02202 0.02843 0.00809 0.02572 0.0292 0.01136 0.01325 0.01352
18
+ S5 Winter Middle TEST 23.33 19.14 0.01249 0.02704 0.00807 0.002 0.00491 0.00342 0.02983 0.0158 0.02437 0.0271 0.01125
19
+ S6 Spring Lower TEST 7.334 27.33 0.00164 0.01079 0.00766 0.02947 0.02709 0.02293 0.01175 0.02121 0.0064 0.01528 0.01671
20
+ S1 Summer Upper TEST 20.38 21.81 0.01461 0.02455 0.01175 0.00125 0.00947 0.00867 0.01439 0.0192 0.02679 0.00123 0.01208
21
+ S2 Autumn Middle TEST 13.918 30.24 0.01908 0.02417 0.01053 0.00868 0.00472 0.0101 0.01071 0.02596 0.02053 0.00574 0.02359
22
+ S3 Winter Lower INDEPENDENT 7.326 20.78 0.02844 0.02 0.01877 0.0276 0.00442 0.02966 0.0116 0.00413 0.02865 0.00388 0.00545
23
+ S4 Spring Upper INDEPENDENT 5.816 23.0 0.01362 0.00763 0.0168 0.00333 0.02302 0.01262 0.02093 0.01729 0.00709 0.00626 0.02189
24
+ S5 Summer Middle INDEPENDENT 13.449 18.23 0.01508 0.00499 0.02274 0.02579 0.00356 0.0138 0.02232 0.02444 0.00775 0.00885 0.00705
25
+ S6 Autumn Lower INDEPENDENT 15.403 18.52 0.01605 0.01331 0.00955 0.0052 0.02313 0.0277 0.01019 0.02345 0.01424 0.00319 0.02803
@@ -0,0 +1,25 @@
1
+ SITE,SEASON,ZONE,SPLIT,CHL,SAL,Rhow_1,Rhow_2,Rhow_3,Rhow_4,Rhow_5,Rhow_6,Rhow_7,Rhow_8,Rhow_9,Rhow_10,Rhow_11
2
+ S1,Winter,Upper,TRAIN,17.694,20.95,0.0154,0.01285,0.00539,0.02886,0.0062,0.00795,0.02391,0.00521,0.02326,0.00531,0.02649
3
+ S2,Spring,Middle,TRAIN,3.238,18.24,0.01458,0.01779,0.02632,0.01676,0.02376,0.01314,0.02592,0.02152,0.02945,0.00215,0.02053
4
+ S3,Summer,Lower,TRAIN,7.068,25.54,0.0047,0.00304,0.00619,0.0207,0.02697,0.00515,0.01458,0.01374,0.00182,0.01121,0.01388
5
+ S4,Autumn,Upper,TRAIN,6.241,29.65,0.00847,0.01516,0.00188,0.02096,0.01436,0.01993,0.00716,0.02757,0.00408,0.02278,0.02706
6
+ S5,Winter,Middle,TRAIN,6.046,32.7,0.00109,0.01869,0.01722,0.00542,0.00212,0.01428,0.0029,0.01287,0.00545,0.02678,0.01874
7
+ S6,Spring,Lower,TRAIN,20.678,28.01,0.01205,0.02257,0.01344,0.00521,0.01014,0.01242,0.00331,0.0018,0.01785,0.00777,0.02077
8
+ S1,Summer,Upper,TRAIN,23.237,32.67,0.0177,0.01347,0.0259,0.0144,0.00144,0.02434,0.00497,0.02206,0.01349,0.02838,0.02167
9
+ S2,Autumn,Middle,TRAIN,8.361,31.84,0.01339,0.00978,0.00566,0.01614,0.01113,0.01431,0.00881,0.02959,0.00286,0.0033,0.01374
10
+ S3,Winter,Lower,TRAIN,20.854,21.49,0.02522,0.00117,0.01145,0.00889,0.02012,0.02106,0.02354,0.01452,0.0091,0.00327,0.0095
11
+ S4,Spring,Upper,TRAIN,22.468,31.86,0.01888,0.02294,0.01957,0.00622,0.00869,0.02368,0.00766,0.02863,0.01233,0.02146,0.02672
12
+ S5,Summer,Middle,TRAIN,13.798,29.69,0.00872,0.00325,0.028,0.01561,0.01991,0.01883,0.01323,0.00572,0.01567,0.00297,0.0117
13
+ S6,Autumn,Lower,TRAIN,7.634,22.45,0.02452,0.01521,0.00859,0.02013,0.0279,0.02888,0.01429,0.00717,0.02292,0.02915,0.01367
14
+ S1,Winter,Upper,TRAIN,20.958,30.75,0.01549,0.00983,0.02095,0.00763,0.01452,0.01698,0.00998,0.0109,0.01518,0.00895,0.00901
15
+ S2,Spring,Middle,TRAIN,6.917,31.84,0.02301,0.02538,0.00532,0.00733,0.00701,0.00735,0.00613,0.01277,0.02495,0.00473,0.01706
16
+ S3,Summer,Lower,TEST,19.054,22.79,0.01742,0.02856,0.02246,0.02869,0.02776,0.01671,0.00877,0.02708,0.01667,0.02329,0.01661
17
+ S4,Autumn,Upper,TEST,16.489,26.43,0.01369,0.01025,0.0095,0.02202,0.02843,0.00809,0.02572,0.0292,0.01136,0.01325,0.01352
18
+ S5,Winter,Middle,TEST,23.33,19.14,0.01249,0.02704,0.00807,0.002,0.00491,0.00342,0.02983,0.0158,0.02437,0.0271,0.01125
19
+ S6,Spring,Lower,TEST,7.334,27.33,0.00164,0.01079,0.00766,0.02947,0.02709,0.02293,0.01175,0.02121,0.0064,0.01528,0.01671
20
+ S1,Summer,Upper,TEST,20.38,21.81,0.01461,0.02455,0.01175,0.00125,0.00947,0.00867,0.01439,0.0192,0.02679,0.00123,0.01208
21
+ S2,Autumn,Middle,TEST,13.918,30.24,0.01908,0.02417,0.01053,0.00868,0.00472,0.0101,0.01071,0.02596,0.02053,0.00574,0.02359
22
+ S3,Winter,Lower,INDEPENDENT,7.326,20.78,0.02844,0.02,0.01877,0.0276,0.00442,0.02966,0.0116,0.00413,0.02865,0.00388,0.00545
23
+ S4,Spring,Upper,INDEPENDENT,5.816,23.0,0.01362,0.00763,0.0168,0.00333,0.02302,0.01262,0.02093,0.01729,0.00709,0.00626,0.02189
24
+ S5,Summer,Middle,INDEPENDENT,13.449,18.23,0.01508,0.00499,0.02274,0.02579,0.00356,0.0138,0.02232,0.02444,0.00775,0.00885,0.00705
25
+ S6,Autumn,Lower,INDEPENDENT,15.403,18.52,0.01605,0.01331,0.00955,0.0052,0.02313,0.0277,0.01019,0.02345,0.01424,0.00319,0.02803
@@ -0,0 +1,25 @@
1
+ SITE SEASON ZONE SPLIT CHL SAL Rhow_1 Rhow_2 Rhow_3 Rhow_4 Rhow_5 Rhow_6 Rhow_7 Rhow_8 Rhow_9 Rhow_10 Rhow_11
2
+ S1 Winter Upper TRAIN 17.694 20.95 0.0154 0.01285 0.00539 0.02886 0.0062 0.00795 0.02391 0.00521 0.02326 0.00531 0.02649
3
+ S2 Spring Middle TRAIN 3.238 18.24 0.01458 0.01779 0.02632 0.01676 0.02376 0.01314 0.02592 0.02152 0.02945 0.00215 0.02053
4
+ S3 Summer Lower TRAIN 7.068 25.54 0.0047 0.00304 0.00619 0.0207 0.02697 0.00515 0.01458 0.01374 0.00182 0.01121 0.01388
5
+ S4 Autumn Upper TRAIN 6.241 29.65 0.00847 0.01516 0.00188 0.02096 0.01436 0.01993 0.00716 0.02757 0.00408 0.02278 0.02706
6
+ S5 Winter Middle TRAIN 6.046 32.7 0.00109 0.01869 0.01722 0.00542 0.00212 0.01428 0.0029 0.01287 0.00545 0.02678 0.01874
7
+ S6 Spring Lower TRAIN 20.678 28.01 0.01205 0.02257 0.01344 0.00521 0.01014 0.01242 0.00331 0.0018 0.01785 0.00777 0.02077
8
+ S1 Summer Upper TRAIN 23.237 32.67 0.0177 0.01347 0.0259 0.0144 0.00144 0.02434 0.00497 0.02206 0.01349 0.02838 0.02167
9
+ S2 Autumn Middle TRAIN 8.361 31.84 0.01339 0.00978 0.00566 0.01614 0.01113 0.01431 0.00881 0.02959 0.00286 0.0033 0.01374
10
+ S3 Winter Lower TRAIN 20.854 21.49 0.02522 0.00117 0.01145 0.00889 0.02012 0.02106 0.02354 0.01452 0.0091 0.00327 0.0095
11
+ S4 Spring Upper TRAIN 22.468 31.86 0.01888 0.02294 0.01957 0.00622 0.00869 0.02368 0.00766 0.02863 0.01233 0.02146 0.02672
12
+ S5 Summer Middle TRAIN 13.798 29.69 0.00872 0.00325 0.028 0.01561 0.01991 0.01883 0.01323 0.00572 0.01567 0.00297 0.0117
13
+ S6 Autumn Lower TRAIN 7.634 22.45 0.02452 0.01521 0.00859 0.02013 0.0279 0.02888 0.01429 0.00717 0.02292 0.02915 0.01367
14
+ S1 Winter Upper TRAIN 20.958 30.75 0.01549 0.00983 0.02095 0.00763 0.01452 0.01698 0.00998 0.0109 0.01518 0.00895 0.00901
15
+ S2 Spring Middle TRAIN 6.917 31.84 0.02301 0.02538 0.00532 0.00733 0.00701 0.00735 0.00613 0.01277 0.02495 0.00473 0.01706
16
+ S3 Summer Lower TEST 19.054 22.79 0.01742 0.02856 0.02246 0.02869 0.02776 0.01671 0.00877 0.02708 0.01667 0.02329 0.01661
17
+ S4 Autumn Upper TEST 16.489 26.43 0.01369 0.01025 0.0095 0.02202 0.02843 0.00809 0.02572 0.0292 0.01136 0.01325 0.01352
18
+ S5 Winter Middle TEST 23.33 19.14 0.01249 0.02704 0.00807 0.002 0.00491 0.00342 0.02983 0.0158 0.02437 0.0271 0.01125
19
+ S6 Spring Lower TEST 7.334 27.33 0.00164 0.01079 0.00766 0.02947 0.02709 0.02293 0.01175 0.02121 0.0064 0.01528 0.01671
20
+ S1 Summer Upper TEST 20.38 21.81 0.01461 0.02455 0.01175 0.00125 0.00947 0.00867 0.01439 0.0192 0.02679 0.00123 0.01208
21
+ S2 Autumn Middle TEST 13.918 30.24 0.01908 0.02417 0.01053 0.00868 0.00472 0.0101 0.01071 0.02596 0.02053 0.00574 0.02359
22
+ S3 Winter Lower INDEPENDENT 7.326 20.78 0.02844 0.02 0.01877 0.0276 0.00442 0.02966 0.0116 0.00413 0.02865 0.00388 0.00545
23
+ S4 Spring Upper INDEPENDENT 5.816 23.0 0.01362 0.00763 0.0168 0.00333 0.02302 0.01262 0.02093 0.01729 0.00709 0.00626 0.02189
24
+ S5 Summer Middle INDEPENDENT 13.449 18.23 0.01508 0.00499 0.02274 0.02579 0.00356 0.0138 0.02232 0.02444 0.00775 0.00885 0.00705
25
+ S6 Autumn Lower INDEPENDENT 15.403 18.52 0.01605 0.01331 0.00955 0.0052 0.02313 0.0277 0.01019 0.02345 0.01424 0.00319 0.02803
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "RSATSIModel"
7
+ version = "2.5.0"
8
+ description = "Python package for computing ATSI and RS-ATSI using CHL, SAL, and Sentinel-3 OLCI-style data."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Dr Md Galal Uddin", email = "jalaluddinbd1987@gmail.com"}
14
+ ]
15
+ dependencies = [
16
+ "pandas>=1.5.0",
17
+ "numpy>=1.23.0",
18
+ "matplotlib>=3.6.0",
19
+ "openpyxl>=3.1.0",
20
+ "scikit-learn>=1.2.0"
21
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from setuptools import setup, find_packages
2
+ setup(packages=find_packages())