core-lens 0.1.dev101__tar.gz → 0.1.dev106__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.
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/PKG-INFO +1 -1
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/intro.md +1 -1
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/plots.md +5 -3
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/queries.md +1 -1
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/quickstart.md +6 -0
- core_lens-0.1.dev106/plot_wb.py +29 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/result.py +11 -4
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/entities/__init__.py +2 -1
- core_lens-0.1.dev106/src/core_lens/entities/waterbody.py +28 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_result.py +20 -6
- core_lens-0.1.dev106/wb_annual.txt +11 -0
- core_lens-0.1.dev106/wb_static.txt +11 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.gitignore +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.gitmessage +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/.python-version +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/LICENSE +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/README.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/SKILLS.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/Makefile +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/make.bat +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/docs/source/stats.md +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/examples/demo_mws.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/examples/demo_tehsil.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/pyproject.toml +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/aoi.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/entity.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/namespaces/plot.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/namespaces/stats.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/base/view.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/entities/mws.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/entities/tehsil.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/export/formats.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/schema/detection.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/schema/profile.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/utils/polars_utils.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/utils/season.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/src/core_lens/utils/spatial.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/conftest.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_aoi.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_export.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_plot.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_schema_detection.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_schema_profile.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_spatial.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_stats.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/tests/unit/test_view.py +0 -0
- {core_lens-0.1.dev101 → core_lens-0.1.dev106}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-lens
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev106
|
|
4
4
|
Summary: Query, analyse, and visualise CoreStack's microwatershed and Earth science data through a clean, composable Python API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ApoorvaKashyap/core-lens
|
|
6
6
|
Project-URL: Issues, https://github.com/ApoorvaKashyap/core-lens/issues
|
|
@@ -12,4 +12,4 @@ CoreLens provides a unified interface over microwatersheds, administrative bound
|
|
|
12
12
|
- **Pluggable Entities**: Built-in support for standard units (MWS, Tehsil) with a simple plugin architecture for adding new domain entities.
|
|
13
13
|
- **Temporal & Seasonal Awareness**: Native support for agronomic seasons (Kharif, Rabi, Zaid) and time-range filtering.
|
|
14
14
|
- **Spatial Statistics & Analysis**: Built-in methods for anomaly detection, spatial similarity, temporal correlation, and hypothesis testing.
|
|
15
|
-
- **Interactive Visualisation**: Generate interactive maps using Lonboard and timeseries/scatter plots using
|
|
15
|
+
- **Interactive Visualisation**: Generate interactive maps using Lonboard and timeseries/scatter plots using Bokeh directly from results.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Visualisation and Plots
|
|
2
2
|
|
|
3
|
-
The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps, while charts and timeseries use **
|
|
3
|
+
The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps, while charts and timeseries use **Bokeh**.
|
|
4
4
|
|
|
5
|
-
Plot methods return plot objects (like a
|
|
5
|
+
Plot methods return plot objects (like a Bokeh Figure or Lonboard Map) which can be displayed using `bokeh.io.show` or exported via `bokeh.io.save`.
|
|
6
6
|
|
|
7
7
|
## Choropleth Maps
|
|
8
8
|
|
|
@@ -18,9 +18,11 @@ map_view = res.plot.choropleth(column="ndvi")
|
|
|
18
18
|
Plot timeseries data. You can aggregate the data or plot the top N entities individually.
|
|
19
19
|
|
|
20
20
|
```python
|
|
21
|
+
from bokeh.io import show
|
|
22
|
+
|
|
21
23
|
# Aggregate timeseries
|
|
22
24
|
fig_agg = res.plot.timeseries(x="year", y="ndvi", aggregate=True)
|
|
23
|
-
|
|
25
|
+
show(fig_agg)
|
|
24
26
|
|
|
25
27
|
# Timeseries for top entities
|
|
26
28
|
fig_top = res.plot.timeseries(x="year", y=["ndvi", "rainfall"], top_n=5)
|
|
@@ -31,7 +31,7 @@ derived_res = res_annual.derive(
|
|
|
31
31
|
pl.when(pl.col("rainfall") < 500).then(1).otherwise(0)
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
# 2. Aggregate (temporal grouping requires fortnightly
|
|
34
|
+
# 2. Aggregate (temporal grouping like "month" requires fortnightly, but "year" works on annual too)
|
|
35
35
|
res_fortnightly = aoi.mws.fortnightly
|
|
36
36
|
monthly_avg = res_fortnightly.aggregate(pl.mean("ndvi"), by="month")
|
|
37
37
|
seasonal_avg = res_fortnightly.aggregate(pl.mean("ndvi"), by="season_year")
|
|
@@ -34,6 +34,12 @@ aoi = AoI(
|
|
|
34
34
|
tehsil="Pangi"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
# Or scope it directly to a specific list of entity IDs
|
|
38
|
+
aoi_specific = AoI(
|
|
39
|
+
data_root="/path/to/data",
|
|
40
|
+
mws_id=["13_001", "13_002"]
|
|
41
|
+
)
|
|
42
|
+
|
|
37
43
|
# You can now access entities scoped to this AoI:
|
|
38
44
|
mws_view = aoi.mws
|
|
39
45
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
from core_lens import AoI
|
|
3
|
+
from core_lens.base.entity import BaseEntity
|
|
4
|
+
|
|
5
|
+
from core_lens.entities.waterbody import WaterbodyEntity
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
# Register the entity
|
|
9
|
+
AoI.register(WaterbodyEntity)
|
|
10
|
+
|
|
11
|
+
# Init AoI with a bounding box for India (or you can adjust bbox)
|
|
12
|
+
DATA_ROOT = "data/"
|
|
13
|
+
aoi = AoI(DATA_ROOT, bbox=(68.0, 6.0, 98.0, 38.0))
|
|
14
|
+
|
|
15
|
+
# Materialise the static waterbodies data
|
|
16
|
+
print("Loading waterbodies...")
|
|
17
|
+
wb_result = aoi.waterbody.static
|
|
18
|
+
|
|
19
|
+
# Plot using the area column for choropleth colors
|
|
20
|
+
print("Plotting waterbodies...")
|
|
21
|
+
choropleth_map = wb_result.plot.choropleth("area_ha")
|
|
22
|
+
|
|
23
|
+
# Save the map to HTML
|
|
24
|
+
output_file = "wb_map.html"
|
|
25
|
+
choropleth_map.to_html(output_file)
|
|
26
|
+
print(f"Saved waterbodies map to {output_file}")
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, '
|
|
21
|
+
__version__ = version = '0.1.dev106'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev106')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -15,8 +15,9 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from core_lens.base.namespaces.stats import StatsNamespace
|
|
16
16
|
from core_lens.base.namespaces.plot import PlotNamespace
|
|
17
17
|
|
|
18
|
-
_FORTNIGHTLY_ONLY_BY = {"
|
|
19
|
-
|
|
18
|
+
_FORTNIGHTLY_ONLY_BY = {"month", "year_month", "season", "season_year"}
|
|
19
|
+
_ANNUAL_OR_FORTNIGHTLY_BY = {"year"}
|
|
20
|
+
_VALID_BY = {None} | _FORTNIGHTLY_ONLY_BY | _ANNUAL_OR_FORTNIGHTLY_BY
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class Result:
|
|
@@ -194,7 +195,11 @@ class Result:
|
|
|
194
195
|
- ❌
|
|
195
196
|
- ✅
|
|
196
197
|
- ✅
|
|
197
|
-
* - ``"year"``
|
|
198
|
+
* - ``"year"``
|
|
199
|
+
- ❌
|
|
200
|
+
- ✅
|
|
201
|
+
- ✅
|
|
202
|
+
* - ``"month"`` / ``"year_month"`` / ``"season"`` / ``"season_year"``
|
|
198
203
|
- ❌
|
|
199
204
|
- ❌
|
|
200
205
|
- ✅
|
|
@@ -203,7 +208,9 @@ class Result:
|
|
|
203
208
|
*exprs: One or more Polars aggregation expressions
|
|
204
209
|
(e.g. ``pl.mean("ndvi")``, ``pl.max("rainfall")``).
|
|
205
210
|
by: Grouping dimension. ``None`` collapses all rows to one.
|
|
206
|
-
|
|
211
|
+
``"year"`` groups by entity + year and is valid for both
|
|
212
|
+
annual and fortnightly resolution.
|
|
213
|
+
Other temporal groupings (``"month"``, ``"year_month"``,
|
|
207
214
|
``"season"``, ``"season_year"``) require
|
|
208
215
|
``resolution="fortnightly"``.
|
|
209
216
|
|
|
@@ -2,5 +2,6 @@ __module__: str = "core_lens.entities"
|
|
|
2
2
|
|
|
3
3
|
from core_lens.entities.tehsil import TehsilEntity
|
|
4
4
|
from core_lens.entities.mws import MWSEntity
|
|
5
|
+
from core_lens.entities.waterbody import WaterbodyEntity
|
|
5
6
|
|
|
6
|
-
__all__ = ["TehsilEntity", "MWSEntity"]
|
|
7
|
+
__all__ = ["TehsilEntity", "MWSEntity", "WaterbodyEntity"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from core_lens.base.entity import BaseEntity
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class WaterbodyEntity(BaseEntity):
|
|
5
|
+
"""Waterbody entity.
|
|
6
|
+
|
|
7
|
+
Backed by:
|
|
8
|
+
- Static: waterbodies/static
|
|
9
|
+
- Annual: waterbodies/annual (partitioned by basin)
|
|
10
|
+
|
|
11
|
+
All paths are relative to the AoI ``data_root``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def key_cols(self) -> list[str]:
|
|
16
|
+
return ["wb_id"]
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def geometry_col(self) -> str:
|
|
20
|
+
return "geometry"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def static_path(self) -> str:
|
|
24
|
+
return "waterbodies/static"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def annual_path(self) -> str | None:
|
|
28
|
+
return "waterbodies/annual"
|
|
@@ -220,11 +220,27 @@ class TestResultAggregate:
|
|
|
220
220
|
with pytest.raises(ValueError, match="not supported on static"):
|
|
221
221
|
result.aggregate(pl.mean("ndvi_mean"))
|
|
222
222
|
|
|
223
|
-
def
|
|
223
|
+
def test_aggregate_by_year_on_annual_succeeds(self, entity_cls: Any) -> None:
|
|
224
|
+
df = pl.DataFrame(
|
|
225
|
+
{
|
|
226
|
+
"mws_id": ["1", "1"],
|
|
227
|
+
"year": [2021, 2022],
|
|
228
|
+
"ndvi_mean": [0.4, 0.5],
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
result = _make_result(entity_cls(), resolution=Resolution.ANNUAL, data=df)
|
|
232
|
+
agg = result.aggregate(pl.mean("ndvi_mean"), by="year")
|
|
233
|
+
|
|
234
|
+
assert "year" in agg.df().columns
|
|
235
|
+
assert "mws_id" in agg.df().columns
|
|
236
|
+
|
|
237
|
+
def test_aggregate_fortnightly_only_by_on_annual_raises(
|
|
238
|
+
self, entity_cls: Any
|
|
239
|
+
) -> None:
|
|
224
240
|
result = _make_result(entity_cls(), resolution=Resolution.ANNUAL)
|
|
225
241
|
|
|
226
242
|
with pytest.raises(ValueError, match="requires data at fortnightly resolution"):
|
|
227
|
-
result.aggregate(pl.mean("ndvi_mean"), by="
|
|
243
|
+
result.aggregate(pl.mean("ndvi_mean"), by="month")
|
|
228
244
|
|
|
229
245
|
def test_aggregate_unknown_by_raises(self, entity_cls: Any) -> None:
|
|
230
246
|
result = _make_result(entity_cls())
|
|
@@ -232,10 +248,8 @@ class TestResultAggregate:
|
|
|
232
248
|
with pytest.raises(ValueError, match="Unknown grouping"):
|
|
233
249
|
result.aggregate(pl.mean("ndvi_mean"), by="quarter")
|
|
234
250
|
|
|
235
|
-
@pytest.mark.parametrize(
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
def test_all_fortnightly_by_keys_accepted(self, entity_cls: Any, by: Any) -> None:
|
|
251
|
+
@pytest.mark.parametrize("by", ["month", "year_month", "season", "season_year"])
|
|
252
|
+
def test_fortnightly_only_by_keys_accepted(self, entity_cls: Any, by: Any) -> None:
|
|
239
253
|
df = pl.DataFrame(
|
|
240
254
|
{
|
|
241
255
|
"mws_id": ["1"],
|
|
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
|
|
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
|
|
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
|