Spaces:
Runtime error
Runtime error
:broom:
Browse files- README.md +1 -1
- pages/{01_leafmap.py β 00_leafmap.py} +11 -20
- pages/{00_home.py β 01_docs.py} +11 -2
- solara-app.ipynb +19 -7
- solara-test.ipynb +0 -249
README.md
CHANGED
|
@@ -24,4 +24,4 @@ This application accesses boundary polygons from the National Parks Service and
|
|
| 24 |
|
| 25 |
### Code
|
| 26 |
|
| 27 |
-
The required code for analysis is in `fire.py`, with solara visualization in `pages/01_leafmap.py`. (For interactive )
|
|
|
|
| 24 |
|
| 25 |
### Code
|
| 26 |
|
| 27 |
+
The required code for analysis is in `fire.py`, with solara visualization in `pages/01_leafmap.py`. (For interactive use, see `solar-app.pynb`)
|
pages/{01_leafmap.py β 00_leafmap.py}
RENAMED
|
@@ -1,32 +1,23 @@
|
|
| 1 |
import leafmap
|
| 2 |
import solara
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
import
|
| 6 |
-
import geopandas as gpd
|
| 7 |
-
import dask.distributed
|
| 8 |
-
import matplotlib.pyplot as plt
|
| 9 |
-
import rioxarray
|
| 10 |
-
from osgeo import gdal
|
| 11 |
-
import matplotlib.pyplot as plt
|
| 12 |
|
| 13 |
zoom = solara.reactive(14)
|
| 14 |
center = solara.reactive((34, -116))
|
| 15 |
|
| 16 |
-
|
| 17 |
-
calfire = gpd.read_file("/vsicurl/https://huggingface.co/datasets/cboettig/biodiversity/resolve/main/data/fire22_1.gdb", layer = "firep22_1")
|
| 18 |
-
jtree = nps[nps.PARKNAME == "Joshua Tree"].to_crs(calfire.crs)
|
| 19 |
-
jtree_fires = jtree.overlay(calfire, how="intersection")
|
| 20 |
-
|
| 21 |
-
recent = jtree_fires[jtree_fires.YEAR_ > "2015"]
|
| 22 |
-
big = recent[recent.Shape_Area == recent.Shape_Area.max()].to_crs("EPSG:4326")
|
| 23 |
-
datetime = big.ALARM_DATE.item() + "/" + big.CONT_DATE.item()
|
| 24 |
-
box = big.buffer(0.01).bounds.to_numpy()[0] # Fire bbox + buffer #box = jtree.to_crs("EPSG:4326").bounds.to_numpy()[0] # Park bbox
|
| 25 |
-
|
| 26 |
before_url = "https://huggingface.co/datasets/cboettig/solara-data/resolve/main/before.tif"
|
| 27 |
after_url = "https://huggingface.co/datasets/cboettig/solara-data/resolve/main/after.tif"
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
class Map(leafmap.Map):
|
| 32 |
def __init__(self, **kwargs):
|
|
|
|
| 1 |
import leafmap
|
| 2 |
import solara
|
| 3 |
+
|
| 4 |
+
# external script defines polygons, etc
|
| 5 |
+
from fire import *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
zoom = solara.reactive(14)
|
| 8 |
center = solara.reactive((34, -116))
|
| 9 |
|
| 10 |
+
# location of cached COGs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
before_url = "https://huggingface.co/datasets/cboettig/solara-data/resolve/main/before.tif"
|
| 12 |
after_url = "https://huggingface.co/datasets/cboettig/solara-data/resolve/main/after.tif"
|
| 13 |
|
| 14 |
+
# custom polygon appearance
|
| 15 |
+
style = {
|
| 16 |
+
"stroke": False,
|
| 17 |
+
"fill": True,
|
| 18 |
+
"fillColor": "#ff6666",
|
| 19 |
+
"fillOpacity": 0.5,
|
| 20 |
+
}
|
| 21 |
|
| 22 |
class Map(leafmap.Map):
|
| 23 |
def __init__(self, **kwargs):
|
pages/{00_home.py β 01_docs.py}
RENAMED
|
@@ -12,9 +12,18 @@ def Page():
|
|
| 12 |
**A collection of [Solara](https://github.com/widgetti/solara) web apps for geospatial applications.**
|
| 13 |
|
| 14 |
- Web App: <https://cboettig-solara-test.hf.space>
|
| 15 |
-
- GitHub: <https://github.com/boettiger-lab/solara-test>
|
| 16 |
-
-
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
"""
|
| 19 |
|
| 20 |
solara.Markdown(markdown)
|
|
|
|
| 12 |
**A collection of [Solara](https://github.com/widgetti/solara) web apps for geospatial applications.**
|
| 13 |
|
| 14 |
- Web App: <https://cboettig-solara-test.hf.space>
|
| 15 |
+
- GitHub Source code: <https://github.com/boettiger-lab/solara-test>
|
| 16 |
+
- Docker container: `ghcr.io/boettiger-lab/solara-geospatial:latest`
|
| 17 |
|
| 18 |
+
This application accesses boundary polygons from the National Parks Service
|
| 19 |
+
and fire polygon data from CalFire to determine the location of all recorded
|
| 20 |
+
fires in Joshua Tree national park. We select the largest area fire since
|
| 21 |
+
2015 in the database (currently turns out to be Elk Trail Fire) and access
|
| 22 |
+
all Sentinel-2 imagery from the two weeks before and after the fire alarm date.
|
| 23 |
+
From this imagery, we compute the Normalized Burn Severity metric (NBS)
|
| 24 |
+
around the fire polygon before and after the fire (using cloud-native approach
|
| 25 |
+
of `pystac`, `odc.stac`, and dask), and plot this on a leaflet map overlay with
|
| 26 |
+
splitmap and fire polygons.
|
| 27 |
"""
|
| 28 |
|
| 29 |
solara.Markdown(markdown)
|
solara-app.ipynb
CHANGED
|
@@ -1,8 +1,17 @@
|
|
| 1 |
{
|
| 2 |
"cells": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
{
|
| 4 |
"cell_type": "code",
|
| 5 |
-
"execution_count":
|
| 6 |
"metadata": {},
|
| 7 |
"outputs": [],
|
| 8 |
"source": [
|
|
@@ -13,16 +22,18 @@
|
|
| 13 |
},
|
| 14 |
{
|
| 15 |
"cell_type": "code",
|
| 16 |
-
"execution_count":
|
| 17 |
"metadata": {},
|
| 18 |
"outputs": [],
|
| 19 |
"source": [
|
|
|
|
|
|
|
| 20 |
"run()"
|
| 21 |
]
|
| 22 |
},
|
| 23 |
{
|
| 24 |
"cell_type": "code",
|
| 25 |
-
"execution_count":
|
| 26 |
"metadata": {},
|
| 27 |
"outputs": [],
|
| 28 |
"source": [
|
|
@@ -32,6 +43,7 @@
|
|
| 32 |
"before_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/before.tif\"\n",
|
| 33 |
"after_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/after.tif\"\n",
|
| 34 |
"\n",
|
|
|
|
| 35 |
"style = {\n",
|
| 36 |
" \"stroke\": False,\n",
|
| 37 |
" \"fill\": True,\n",
|
|
@@ -42,7 +54,7 @@
|
|
| 42 |
},
|
| 43 |
{
|
| 44 |
"cell_type": "code",
|
| 45 |
-
"execution_count":
|
| 46 |
"metadata": {},
|
| 47 |
"outputs": [],
|
| 48 |
"source": [
|
|
@@ -64,7 +76,7 @@
|
|
| 64 |
},
|
| 65 |
{
|
| 66 |
"cell_type": "code",
|
| 67 |
-
"execution_count":
|
| 68 |
"metadata": {},
|
| 69 |
"outputs": [],
|
| 70 |
"source": [
|
|
@@ -91,13 +103,13 @@
|
|
| 91 |
},
|
| 92 |
{
|
| 93 |
"cell_type": "code",
|
| 94 |
-
"execution_count":
|
| 95 |
"metadata": {},
|
| 96 |
"outputs": [
|
| 97 |
{
|
| 98 |
"data": {
|
| 99 |
"application/vnd.jupyter.widget-view+json": {
|
| 100 |
-
"model_id": "
|
| 101 |
"version_major": 2,
|
| 102 |
"version_minor": 0
|
| 103 |
},
|
|
|
|
| 1 |
{
|
| 2 |
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Solara Demo Notebook\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"This notebook is identical to `fire.py` + `pages/01_leafmap.py`. Running this notebook interactively (e.g. in Jupyter or VSCode) should render the solara page produced by `pages/01_leafmap.py`, and can be used for testing/developing the solara interface."
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
{
|
| 13 |
"cell_type": "code",
|
| 14 |
+
"execution_count": 1,
|
| 15 |
"metadata": {},
|
| 16 |
"outputs": [],
|
| 17 |
"source": [
|
|
|
|
| 22 |
},
|
| 23 |
{
|
| 24 |
"cell_type": "code",
|
| 25 |
+
"execution_count": null,
|
| 26 |
"metadata": {},
|
| 27 |
"outputs": [],
|
| 28 |
"source": [
|
| 29 |
+
"# generate COGs for the selected polygon.\n",
|
| 30 |
+
"# This task is run by GitHub Actions and cached in HuggingFace Datasets, so can be skipped\n",
|
| 31 |
"run()"
|
| 32 |
]
|
| 33 |
},
|
| 34 |
{
|
| 35 |
"cell_type": "code",
|
| 36 |
+
"execution_count": 3,
|
| 37 |
"metadata": {},
|
| 38 |
"outputs": [],
|
| 39 |
"source": [
|
|
|
|
| 43 |
"before_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/before.tif\"\n",
|
| 44 |
"after_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/after.tif\"\n",
|
| 45 |
"\n",
|
| 46 |
+
"# customize the style!\n",
|
| 47 |
"style = {\n",
|
| 48 |
" \"stroke\": False,\n",
|
| 49 |
" \"fill\": True,\n",
|
|
|
|
| 54 |
},
|
| 55 |
{
|
| 56 |
"cell_type": "code",
|
| 57 |
+
"execution_count": 4,
|
| 58 |
"metadata": {},
|
| 59 |
"outputs": [],
|
| 60 |
"source": [
|
|
|
|
| 76 |
},
|
| 77 |
{
|
| 78 |
"cell_type": "code",
|
| 79 |
+
"execution_count": 5,
|
| 80 |
"metadata": {},
|
| 81 |
"outputs": [],
|
| 82 |
"source": [
|
|
|
|
| 103 |
},
|
| 104 |
{
|
| 105 |
"cell_type": "code",
|
| 106 |
+
"execution_count": 6,
|
| 107 |
"metadata": {},
|
| 108 |
"outputs": [
|
| 109 |
{
|
| 110 |
"data": {
|
| 111 |
"application/vnd.jupyter.widget-view+json": {
|
| 112 |
+
"model_id": "2c176da4d4594b4d879b29410747b282",
|
| 113 |
"version_major": 2,
|
| 114 |
"version_minor": 0
|
| 115 |
},
|
solara-test.ipynb
DELETED
|
@@ -1,249 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "code",
|
| 5 |
-
"execution_count": 1,
|
| 6 |
-
"metadata": {},
|
| 7 |
-
"outputs": [],
|
| 8 |
-
"source": [
|
| 9 |
-
"import leafmap\n",
|
| 10 |
-
"import solara\n",
|
| 11 |
-
"import pystac_client\n",
|
| 12 |
-
"import planetary_computer\n",
|
| 13 |
-
"import odc.stac\n",
|
| 14 |
-
"import geopandas as gpd\n",
|
| 15 |
-
"import dask.distributed\n",
|
| 16 |
-
"import matplotlib.pyplot as plt\n",
|
| 17 |
-
"import rioxarray\n",
|
| 18 |
-
"from osgeo import gdal\n"
|
| 19 |
-
]
|
| 20 |
-
},
|
| 21 |
-
{
|
| 22 |
-
"cell_type": "code",
|
| 23 |
-
"execution_count": 2,
|
| 24 |
-
"metadata": {},
|
| 25 |
-
"outputs": [],
|
| 26 |
-
"source": [
|
| 27 |
-
"\n",
|
| 28 |
-
"# Stashed public copies of NPS polygons and CalFire polygons\n",
|
| 29 |
-
"\n",
|
| 30 |
-
"\n",
|
| 31 |
-
"zoom = solara.reactive(14)\n",
|
| 32 |
-
"center = solara.reactive((34, -116))\n",
|
| 33 |
-
"nps = gpd.read_file(\"/vsicurl/https://huggingface.co/datasets/cboettig/biodiversity/resolve/main/data/NPS.gdb\")\n",
|
| 34 |
-
"calfire = gpd.read_file(\"/vsicurl/https://huggingface.co/datasets/cboettig/biodiversity/resolve/main/data/fire22_1.gdb\", layer = \"firep22_1\")\n"
|
| 35 |
-
]
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
"cell_type": "code",
|
| 39 |
-
"execution_count": 3,
|
| 40 |
-
"metadata": {},
|
| 41 |
-
"outputs": [],
|
| 42 |
-
"source": [
|
| 43 |
-
"\n",
|
| 44 |
-
"\n",
|
| 45 |
-
"\n",
|
| 46 |
-
"# fire = gpd.read_file(\"/vsizip/vsicurl/https://edcintl.cr.usgs.gov/downloads/sciweb1/shared/MTBS_Fire/data/composite_data/burned_area_extent_shapefile/mtbs_perimeter_data.zip\"\n",
|
| 47 |
-
"\n",
|
| 48 |
-
"# extract and reproject the Joshua Tree NP Polygon\n",
|
| 49 |
-
"jtree = nps[nps.PARKNAME == \"Joshua Tree\"].to_crs(calfire.crs)\n",
|
| 50 |
-
"# All Fires in the DB that intersect the Park\n",
|
| 51 |
-
"jtree_fires = jtree.overlay(calfire, how=\"intersection\")\n",
|
| 52 |
-
"\n",
|
| 53 |
-
"# Extract a polygon if interest. > 2015 for Sentinel, otherwise we can use LandSat\n",
|
| 54 |
-
"recent = jtree_fires[jtree_fires.YEAR_ > \"2015\"]\n",
|
| 55 |
-
"big = recent[recent.Shape_Area == recent.Shape_Area.max()].to_crs(\"EPSG:4326\")\n",
|
| 56 |
-
"box = big.buffer(0.01).bounds.to_numpy()[0] # Fire bbox + buffer\n",
|
| 57 |
-
"#box = jtree.to_crs(\"EPSG:4326\").bounds.to_numpy()[0] # Park bbox\n"
|
| 58 |
-
]
|
| 59 |
-
},
|
| 60 |
-
{
|
| 61 |
-
"cell_type": "code",
|
| 62 |
-
"execution_count": 4,
|
| 63 |
-
"metadata": {},
|
| 64 |
-
"outputs": [],
|
| 65 |
-
"source": [
|
| 66 |
-
"from datetime import datetime, timedelta\n",
|
| 67 |
-
"alarm_date = datetime.strptime(big.ALARM_DATE.item(), \"%Y-%m-%dT%H:%M:%S+00:00\") \n",
|
| 68 |
-
"before_date = alarm_date - timedelta(days=14)\n",
|
| 69 |
-
"after_date = alarm_date + timedelta(days=14)"
|
| 70 |
-
]
|
| 71 |
-
},
|
| 72 |
-
{
|
| 73 |
-
"cell_type": "code",
|
| 74 |
-
"execution_count": 5,
|
| 75 |
-
"metadata": {},
|
| 76 |
-
"outputs": [],
|
| 77 |
-
"source": [
|
| 78 |
-
"search_dates = big.ALARM_DATE.item() + \"/\" + big.CONT_DATE.item()\n",
|
| 79 |
-
"search_dates = before_date.strftime(\"%Y-%m-%d\") + \"/\" + after_date.strftime(\"%Y-%m-%d\")"
|
| 80 |
-
]
|
| 81 |
-
},
|
| 82 |
-
{
|
| 83 |
-
"cell_type": "code",
|
| 84 |
-
"execution_count": 6,
|
| 85 |
-
"metadata": {},
|
| 86 |
-
"outputs": [],
|
| 87 |
-
"source": [
|
| 88 |
-
"\n",
|
| 89 |
-
"def stac_search(box, datetime): \n",
|
| 90 |
-
" # STAC Search for this imagery in space/time window\n",
|
| 91 |
-
" items = (\n",
|
| 92 |
-
" pystac_client.Client.\n",
|
| 93 |
-
" open(\"https://planetarycomputer.microsoft.com/api/stac/v1\",\n",
|
| 94 |
-
" modifier=planetary_computer.sign_inplace).\n",
|
| 95 |
-
" search(collections=[\"sentinel-2-l2a\"],\n",
|
| 96 |
-
" bbox=box,\n",
|
| 97 |
-
" datetime=datetime,\n",
|
| 98 |
-
" query={\"eo:cloud_cover\": {\"lt\": 10}}).\n",
|
| 99 |
-
" item_collection())\n",
|
| 100 |
-
" return items\n",
|
| 101 |
-
"\n",
|
| 102 |
-
"def compute_nbs(items, box):\n",
|
| 103 |
-
" # Time to compute:\n",
|
| 104 |
-
" client = dask.distributed.Client()\n",
|
| 105 |
-
" # landsat_bands = [\"nir08\", \"swir16\"]\n",
|
| 106 |
-
" sentinel_bands = [\"B08\", \"B12\", \"SCL\"] # NIR, SWIR, and Cloud Mask\n",
|
| 107 |
-
"\n",
|
| 108 |
-
" # The magic of gdalwarper. Can also resample, reproject, and aggregate on the fly\n",
|
| 109 |
-
" data = odc.stac.load(items,\n",
|
| 110 |
-
" bands=sentinel_bands,\n",
|
| 111 |
-
" bbox=box\n",
|
| 112 |
-
" )\n",
|
| 113 |
-
" # Compute the Normalized Burn Ratio, must be float\n",
|
| 114 |
-
" swir = data[\"B12\"].astype(\"float\")\n",
|
| 115 |
-
" nir = data[\"B08\"].astype(\"float\")\n",
|
| 116 |
-
" # can resample and aggregate in xarray. compute with dask\n",
|
| 117 |
-
" nbs = (((nir - swir) / (nir + swir)).\n",
|
| 118 |
-
" # resample(time=\"MS\").\n",
|
| 119 |
-
" # median(\"time\", keep_attrs=True).\n",
|
| 120 |
-
" compute()\n",
|
| 121 |
-
" )\n",
|
| 122 |
-
" return nbs\n",
|
| 123 |
-
"\n",
|
| 124 |
-
"items = stac_search(box, search_dates)\n",
|
| 125 |
-
"nbs = compute_nbs(items, box)\n",
|
| 126 |
-
"\n"
|
| 127 |
-
]
|
| 128 |
-
},
|
| 129 |
-
{
|
| 130 |
-
"cell_type": "code",
|
| 131 |
-
"execution_count": 7,
|
| 132 |
-
"metadata": {},
|
| 133 |
-
"outputs": [],
|
| 134 |
-
"source": [
|
| 135 |
-
"\n",
|
| 136 |
-
"nbs.isel(time=0).rio.to_raster(raster_path=\"before.tif\", driver=\"COG\")\n",
|
| 137 |
-
"nbs.isel(time=(nbs.time.size-1)).rio.to_raster(raster_path=\"after.tif\", driver=\"COG\")\n"
|
| 138 |
-
]
|
| 139 |
-
},
|
| 140 |
-
{
|
| 141 |
-
"cell_type": "code",
|
| 142 |
-
"execution_count": 8,
|
| 143 |
-
"metadata": {},
|
| 144 |
-
"outputs": [],
|
| 145 |
-
"source": [
|
| 146 |
-
"\n",
|
| 147 |
-
"before_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/before.tif\"\n",
|
| 148 |
-
"after_url = \"https://huggingface.co/datasets/cboettig/solara-data/resolve/main/after.tif\"\n",
|
| 149 |
-
"\n",
|
| 150 |
-
"style = {\n",
|
| 151 |
-
" \"stroke\": False,\n",
|
| 152 |
-
" \"fill\": True,\n",
|
| 153 |
-
" \"fillColor\": \"#ff6666\",\n",
|
| 154 |
-
" \"fillOpacity\": 0.5,\n",
|
| 155 |
-
"}"
|
| 156 |
-
]
|
| 157 |
-
},
|
| 158 |
-
{
|
| 159 |
-
"cell_type": "code",
|
| 160 |
-
"execution_count": 9,
|
| 161 |
-
"metadata": {},
|
| 162 |
-
"outputs": [],
|
| 163 |
-
"source": [
|
| 164 |
-
"class Map(leafmap.Map):\n",
|
| 165 |
-
" def __init__(self, **kwargs):\n",
|
| 166 |
-
" super().__init__(**kwargs)\n",
|
| 167 |
-
" # Add what you want below\n",
|
| 168 |
-
" # self.add_gdf(jtree, layer_name = \"Joshua Tree NP\")\n",
|
| 169 |
-
" self.add_gdf(jtree_fires, layer_name = \"All Fires\", style=style)\n",
|
| 170 |
-
" self.add_gdf(big, layer_name = big.FIRE_NAME.item())\n",
|
| 171 |
-
" #self.add_raster(\"before.tif\", layer_name = \"before\", colormap=\"viridis\")\n",
|
| 172 |
-
" #self.add_raster(\"after.tif\", layer_name = \"after\", colormap=\"viridis\")\n",
|
| 173 |
-
" self.split_map(before_url, after_url, \n",
|
| 174 |
-
" left_label= \"before fire\", \n",
|
| 175 |
-
" right_label = \"after fire\")\n",
|
| 176 |
-
" #self.add_stac_gui()\n",
|
| 177 |
-
"\n",
|
| 178 |
-
"\n",
|
| 179 |
-
"@solara.component\n",
|
| 180 |
-
"def Page():\n",
|
| 181 |
-
" with solara.Column(style={\"min-width\": \"500px\"}):\n",
|
| 182 |
-
" # solara components support reactive variables\n",
|
| 183 |
-
" # solara.SliderInt(label=\"Zoom level\", value=zoom, min=1, max=20)\n",
|
| 184 |
-
" # using 3rd party widget library require wiring up the events manually\n",
|
| 185 |
-
" # using zoom.value and zoom.set\n",
|
| 186 |
-
" Map.element( # type: ignore\n",
|
| 187 |
-
" zoom=zoom.value,\n",
|
| 188 |
-
" on_zoom=zoom.set,\n",
|
| 189 |
-
" center=center.value,\n",
|
| 190 |
-
" on_center=center.set,\n",
|
| 191 |
-
" scroll_wheel_zoom=True,\n",
|
| 192 |
-
" toolbar_ctrl=False,\n",
|
| 193 |
-
" data_ctrl=False,\n",
|
| 194 |
-
" height=\"780px\",\n",
|
| 195 |
-
" )\n",
|
| 196 |
-
" solara.Text(f\"Zoom: {zoom.value}\")\n",
|
| 197 |
-
" solara.Text(f\"Center: {center.value}\")\n"
|
| 198 |
-
]
|
| 199 |
-
},
|
| 200 |
-
{
|
| 201 |
-
"cell_type": "code",
|
| 202 |
-
"execution_count": 10,
|
| 203 |
-
"metadata": {},
|
| 204 |
-
"outputs": [
|
| 205 |
-
{
|
| 206 |
-
"data": {
|
| 207 |
-
"application/vnd.jupyter.widget-view+json": {
|
| 208 |
-
"model_id": "822080e3202b494888a05630fb5bf798",
|
| 209 |
-
"version_major": 2,
|
| 210 |
-
"version_minor": 0
|
| 211 |
-
},
|
| 212 |
-
"text/html": [
|
| 213 |
-
"Cannot show widget. You probably want to rerun the code cell above (<i>Click in the code cell, and press Shift+Enter <kbd>β§</kbd>+<kbd>β©</kbd></i>)."
|
| 214 |
-
],
|
| 215 |
-
"text/plain": [
|
| 216 |
-
"Cannot show ipywidgets in text"
|
| 217 |
-
]
|
| 218 |
-
},
|
| 219 |
-
"metadata": {},
|
| 220 |
-
"output_type": "display_data"
|
| 221 |
-
}
|
| 222 |
-
],
|
| 223 |
-
"source": [
|
| 224 |
-
"Page()"
|
| 225 |
-
]
|
| 226 |
-
}
|
| 227 |
-
],
|
| 228 |
-
"metadata": {
|
| 229 |
-
"kernelspec": {
|
| 230 |
-
"display_name": ".venv",
|
| 231 |
-
"language": "python",
|
| 232 |
-
"name": "python3"
|
| 233 |
-
},
|
| 234 |
-
"language_info": {
|
| 235 |
-
"codemirror_mode": {
|
| 236 |
-
"name": "ipython",
|
| 237 |
-
"version": 3
|
| 238 |
-
},
|
| 239 |
-
"file_extension": ".py",
|
| 240 |
-
"mimetype": "text/x-python",
|
| 241 |
-
"name": "python",
|
| 242 |
-
"nbconvert_exporter": "python",
|
| 243 |
-
"pygments_lexer": "ipython3",
|
| 244 |
-
"version": "3.11.6"
|
| 245 |
-
}
|
| 246 |
-
},
|
| 247 |
-
"nbformat": 4,
|
| 248 |
-
"nbformat_minor": 2
|
| 249 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|