Skip to content

Commit f1b0098

Browse files
Analytics_in_Fused: 2026_04_02_2341_rename_workbench (#1970)
Analytics_in_Fused: 2026_04_02_2341_rename_workbench Made in [Fused Workbench](https://www.fused.io/workbench) Co-authored-by: aman@fused.io <aman@fused.io>
1 parent 1760ae7 commit f1b0098

File tree

17 files changed

+23
-27
lines changed

17 files changed

+23
-27
lines changed

public/Analytics_in_Fused/collection.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

public/Analytics_in_Fused/fetch_ndvi_monthly/fetch_ndvi_monthly.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def udf(
2727

2828
@fused.cache
2929
def search_items(bounds, date_range, max_cloud_cover):
30+
3031
items = catalog.search(
3132
collections=["sentinel-2-l2a"],
3233
bbox=bounds,

public/Analytics_in_Fused/fetch_ndvi_monthly/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Monthly cloud-free NDVI composite for a given bounds using\n Sentinel-2 L2A from the Microsoft Planetary Computer.\n\n Returns a numpy array of mean NDVI (float32, values -1 to 1).\n \"\"\"\n import numpy as np\n import odc.stac\n import planetary_computer\n import pystac_client\n\n # \u2500\u2500 STAC search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n catalog = pystac_client.Client.open(\n \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n modifier=planetary_computer.sign_inplace,\n )\n\n date_range = f\"{year}-{month:02d}-01/{year}-{month:02d}-28\"\n\n @fused.cache\n def search_items(bounds, date_range, max_cloud_cover):\n items = catalog.search(\n collections=[\"sentinel-2-l2a\"],\n bbox=bounds,\n datetime=date_range,\n query={\"eo:cloud_cover\": {\"lt\": max_cloud_cover}},\n ).item_collection()\n print(f\"Found {len(items)} scenes for {date_range} (cloud < {max_cloud_cover}%)\")\n return items\n\n items = search_items(tuple(bounds), date_range, max_cloud_cover)\n\n if len(items) == 0:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return None\n\n # \u2500\u2500 Load & compute NDVI (cached to avoid repeated Planetary Computer requests)\n @fused.cache\n def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution):\n ds = odc.stac.load(\n items,\n bands=[\"B08\", \"B04\"],\n crs=\"EPSG:4326\",\n resolution=resolution / 111_320, # degrees per pixel (approx)\n bbox=list(bounds),\n groupby=\"solar_day\",\n )\n\n nir_raw = ds[\"B08\"].astype(\"float32\")\n red_raw = ds[\"B04\"].astype(\"float32\")\n\n nir_raw = nir_raw.where(nir_raw > 0)\n red_raw = red_raw.where(red_raw > 0)\n\n nir = nir_raw / 10_000.0\n red = red_raw / 10_000.0\n\n ndvi = (nir - red) / (nir + red + 1e-6)\n ndvi_composite = ndvi.median(dim=\"time\")\n arr = ndvi_composite.values.astype(\"float32\")\n return np.clip(arr, 0, 1)\n\n arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution)\n print(f\"NDVI array shape: {arr.shape}\")\n valid = arr[~np.isnan(arr)]\n print(f\"Valid pixels: {len(valid)} / {arr.size} min={valid.min():.3f} max={valid.max():.3f} mean={valid.mean():.3f}\")\n\n return arr\n",
60+
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Monthly cloud-free NDVI composite for a given bounds using\n Sentinel-2 L2A from the Microsoft Planetary Computer.\n\n Returns a numpy array of mean NDVI (float32, values -1 to 1).\n \"\"\"\n import numpy as np\n import odc.stac\n import planetary_computer\n import pystac_client\n\n # \u2500\u2500 STAC search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n catalog = pystac_client.Client.open(\n \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n modifier=planetary_computer.sign_inplace,\n )\n\n date_range = f\"{year}-{month:02d}-01/{year}-{month:02d}-28\"\n\n @fused.cache\n def search_items(bounds, date_range, max_cloud_cover):\n \n items = catalog.search(\n collections=[\"sentinel-2-l2a\"],\n bbox=bounds,\n datetime=date_range,\n query={\"eo:cloud_cover\": {\"lt\": max_cloud_cover}},\n ).item_collection()\n print(f\"Found {len(items)} scenes for {date_range} (cloud < {max_cloud_cover}%)\")\n return items\n\n items = search_items(tuple(bounds), date_range, max_cloud_cover)\n\n if len(items) == 0:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return None\n\n # \u2500\u2500 Load & compute NDVI (cached to avoid repeated Planetary Computer requests)\n @fused.cache\n def load_ndvi_composite(bounds, date_range, max_cloud_cover, resolution):\n ds = odc.stac.load(\n items,\n bands=[\"B08\", \"B04\"],\n crs=\"EPSG:4326\",\n resolution=resolution / 111_320, # degrees per pixel (approx)\n bbox=list(bounds),\n groupby=\"solar_day\",\n )\n\n nir_raw = ds[\"B08\"].astype(\"float32\")\n red_raw = ds[\"B04\"].astype(\"float32\")\n\n nir_raw = nir_raw.where(nir_raw > 0)\n red_raw = red_raw.where(red_raw > 0)\n\n nir = nir_raw / 10_000.0\n red = red_raw / 10_000.0\n\n ndvi = (nir - red) / (nir + red + 1e-6)\n ndvi_composite = ndvi.median(dim=\"time\")\n arr = ndvi_composite.values.astype(\"float32\")\n return np.clip(arr, 0, 1)\n\n arr = load_ndvi_composite(tuple(bounds), date_range, max_cloud_cover, resolution)\n print(f\"NDVI array shape: {arr.shape}\")\n valid = arr[~np.isnan(arr)]\n print(f\"Valid pixels: {len(valid)} / {arr.size} min={valid.min():.3f} max={valid.max():.3f} mean={valid.mean():.3f}\")\n\n return arr\n",
6161
"headers": []
6262
}
6363
}

public/Analytics_in_Fused/ndvi_map_2/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500,\n):\n import numpy as np\n map_utils = fused.load(\"https://github.com/fusedio/udfs/tree/6800334/community/milind/map_utils/\")\n\n # \u2500\u2500 Fetch NDVI array from the compute UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ndvi_udf = fused.load(\"fetch_ndvi_monthly\")\n ndvi_arr = ndvi_udf(\n bounds=bounds,\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_arr is None:\n return \"<p style='color:red'>No scenes found \u2014 try raising max_cloud_cover or changing month/year.</p>\"\n\n print(f\"NDVI array shape: {ndvi_arr.shape}, min={np.nanmin(ndvi_arr):.3f}, max={np.nanmax(ndvi_arr):.3f}\")\n\n # \u2500\u2500 Colour map: NDVI -0.2 \u2192 1.0 mapped to RGBA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n # Clamp to [-0.2, 1.0] then normalise to [0, 1]\n lo, hi = -0.2, 1.0\n norm = np.clip((ndvi_arr - lo) / (hi - lo), 0, 1) # (H, W)\n\n # Green colour ramp: brown (low) \u2192 yellow \u2192 bright green (high)\n r = np.interp(norm, [0, 0.3, 0.6, 1.0], [139, 210, 120, 34]).astype(np.uint8)\n g = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 90, 180, 200, 139]).astype(np.uint8)\n b = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 43, 60, 60, 34]).astype(np.uint8)\n a = np.where(np.isnan(ndvi_arr), 0, 220).astype(np.uint8)\n\n rgba = np.stack([r, g, b, a], axis=-1) # (H, W, 4)\n print(f\"RGBA image shape: {rgba.shape}\")\n\n return map_utils.deckgl_raster(\n image_data=rgba,\n bounds=list(bounds),\n basemap=\"light\",\n config={\n \"rasterLayer\": {\"opacity\": 0.85, \"pickable\": True},\n },\n )",
60+
"code": "@fused.udf\ndef udf(\n bounds: fused.types.Bounds = [-122.5, 47.3, -121.7, 47.8], # King County, WA\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500,\n):\n import numpy as np\n map_utils = fused.load(\"https://github.com/fusedio/udfs/tree/6800334/community/milind/map_utils/\")\n\n # \u2500\u2500 Fetch NDVI array from the compute UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ndvi_udf = fused.load(\"fetch_ndvi_monthly\")\n ndvi_arr = ndvi_udf(\n bounds=bounds,\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution\n )\n\n if ndvi_arr is None:\n return \"<p style='color:red'>No scenes found \u2014 try raising max_cloud_cover or changing month/year.</p>\"\n\n print(f\"NDVI array shape: {ndvi_arr.shape}, min={np.nanmin(ndvi_arr):.3f}, max={np.nanmax(ndvi_arr):.3f}\")\n\n # \u2500\u2500 Colour map: NDVI -0.2 \u2192 1.0 mapped to RGBA \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n # Clamp to [-0.2, 1.0] then normalise to [0, 1]\n lo, hi = -0.2, 1.0\n norm = np.clip((ndvi_arr - lo) / (hi - lo), 0, 1) # (H, W)\n\n # Green colour ramp: brown (low) \u2192 yellow \u2192 bright green (high)\n r = np.interp(norm, [0, 0.3, 0.6, 1.0], [139, 210, 120, 34]).astype(np.uint8)\n g = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 90, 180, 200, 139]).astype(np.uint8)\n b = np.interp(norm, [0, 0.3, 0.6, 1.0], [ 43, 60, 60, 34]).astype(np.uint8)\n a = np.where(np.isnan(ndvi_arr), 0, 220).astype(np.uint8)\n\n rgba = np.stack([r, g, b, a], axis=-1) # (H, W, 4)\n print(f\"RGBA image shape: {rgba.shape}\")\n\n return map_utils.deckgl_raster(\n image_data=rgba,\n bounds=list(bounds),\n basemap=\"light\",\n config={\n \"rasterLayer\": {\"opacity\": 0.85, \"pickable\": True},\n },\n )\n",
6161
"headers": []
6262
}
6363
}

public/Analytics_in_Fused/ndvi_map_2/ndvi_map_2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def udf(
1616
year=year,
1717
month=month,
1818
max_cloud_cover=max_cloud_cover,
19-
resolution=resolution,
19+
resolution=resolution
2020
)
2121

2222
if ndvi_arr is None:
@@ -45,4 +45,4 @@ def udf(
4545
config={
4646
"rasterLayer": {"opacity": 0.85, "pickable": True},
4747
},
48-
)
48+
)

public/Analytics_in_Fused/ndvi_monthly_mean/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"fused:udfType": "auto"
5959
},
60-
"code": "@fused.udf\ndef udf(\n state_fips: str = \"53\", # San Juan County, WA\n county_fips: str = \"055\",\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Input: US county (state_fips + county_fips) + month/year\n Output: single-row DataFrame with mean NDVI clipped to the exact county geometry\n\n Steps:\n 1. Load county geometry from us_counties_from_api UDF\n 2. Fetch Sentinel-2 NDVI composite for the county bbox\n 3. Clip raster to exact county polygon mask\n 4. Return mean NDVI across valid pixels inside the county\n \"\"\"\n import numpy as np\n import geopandas as gpd\n import pandas as pd\n from rasterio.transform import from_bounds\n from rasterio.features import geometry_mask\n\n # \u2500\u2500 1. Load county geometry from us_counties_from_api \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n counties_udf = fused.load(\"us_counties_from_api\")\n county = counties_udf(state_fips=state_fips, county_fips=county_fips)\n\n geom = county.geometry.iloc[0]\n bounds = county.total_bounds # [minx, miny, maxx, maxy]\n print(f\"County bounds: {bounds}\")\n print(f\"County geometry type: {geom.geom_type}\")\n\n # \u2500\u2500 2. Fetch NDVI composite via fetch_ndvi_monthly UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n fetch_ndvi = fused.load(\"fetch_ndvi_monthly\")\n ndvi_composite = fetch_ndvi(\n bounds=list(bounds),\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_composite is None:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return pd.DataFrame({\"state_fips\": [state_fips], \"county_fips\": [county_fips],\n \"year\": [year], \"month\": [month],\n \"mean_ndvi\": [None], \"min_ndvi\": [None], \"max_ndvi\": [None]})\n\n print(f\"NDVI composite shape: {ndvi_composite.shape}\")\n\n # \u2500\u2500 3. Clip to exact county polygon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n h, w = ndvi_composite.shape\n transform = from_bounds(*bounds, width=w, height=h)\n\n # geometry_mask returns True OUTSIDE the geometry \u2014 invert it\n poly_mask = geometry_mask(\n [geom.__geo_interface__],\n transform=transform,\n invert=True, # True = inside county\n out_shape=(h, w),\n )\n\n clipped = ndvi_composite.copy()\n clipped[~poly_mask] = np.nan # zero out pixels outside county\n\n # \u2500\u2500 4. Return mean NDVI inside county \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n valid = clipped[~np.isnan(clipped)]\n mean_ndvi = float(np.mean(valid)) if len(valid) > 0 else None\n min_ndvi = float(np.min(valid)) if len(valid) > 0 else None\n max_ndvi = float(np.max(valid)) if len(valid) > 0 else None\n pct_valid = len(valid) / clipped.size * 100\n\n print(f\"Pixels inside county: {len(valid)} / {clipped.size} ({pct_valid:.1f}%)\")\n print(f\"Mean NDVI: {mean_ndvi:.4f}, Min: {min_ndvi:.4f}, Max: {max_ndvi:.4f}\")\n\n return pd.DataFrame({\n \"state_fips\": [state_fips],\n \"county_fips\": [county_fips],\n \"year\": [year],\n \"month\": [month],\n \"mean_ndvi\": [round(mean_ndvi, 4)],\n \"min_ndvi\": [round(min_ndvi, 4)],\n \"max_ndvi\": [round(max_ndvi, 4)],\n \"valid_pixels\":[len(valid)],\n \"total_pixels\":[clipped.size],\n })\n",
60+
"code": "@fused.udf\ndef udf(\n state_fips: str = \"53\", # San Juan County, WA\n county_fips: str = \"055\",\n year: int = 2024,\n month: int = 6,\n max_cloud_cover: int = 20,\n resolution: int = 500, # metres\n):\n \"\"\"\n Input: US county (state_fips + county_fips) + month/year\n Output: single-row DataFrame with mean NDVI clipped to the exact county geometry\n\n Steps:\n 1. Load county geometry from us_counties_from_api UDF\n 2. Fetch Sentinel-2 NDVI composite for the county bbox\n 3. Clip raster to exact county polygon mask\n 4. Return mean NDVI across valid pixels inside the county\n \"\"\"\n import numpy as np\n import geopandas as gpd\n import pandas as pd\n from rasterio.transform import from_bounds\n from rasterio.features import geometry_mask\n\n # \u2500\u2500 1. Load county geometry from us_counties_from_api \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n counties_udf = fused.load(\"us_counties_from_api\")\n county = counties_udf(state_fips=state_fips, county_fips=county_fips)\n\n geom = county.geometry.iloc[0]\n bounds = county.total_bounds # [minx, miny, maxx, maxy]\n print(f\"County bounds: {bounds}\")\n print(f\"County geometry type: {geom.geom_type}\")\n\n # \u2500\u2500 2. Fetch NDVI composite via fetch_ndvi_monthly UDF \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n fetch_ndvi = fused.load(\"fetch_ndvi_monthly\")\n ndvi_composite = fetch_ndvi(\n bounds=list(bounds),\n year=year,\n month=month,\n max_cloud_cover=max_cloud_cover,\n resolution=resolution,\n )\n\n if ndvi_composite is None:\n print(\"No scenes found \u2014 try raising max_cloud_cover or changing month/year.\")\n return pd.DataFrame({\"state_fips\": [state_fips], \"county_fips\": [county_fips],\n \"year\": [year], \"month\": [month],\n \"mean_ndvi\": [None], \"min_ndvi\": [None], \"max_ndvi\": [None]})\n\n print(f\"NDVI composite shape: {ndvi_composite.shape}\")\n\n # \u2500\u2500 3. Clip to exact county polygon \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n h, w = ndvi_composite.shape\n transform = from_bounds(*bounds, width=w, height=h)\n\n # geometry_mask returns True OUTSIDE the geometry \u2014 invert it\n poly_mask = geometry_mask(\n [geom.__geo_interface__],\n transform=transform,\n invert=True, # True = inside county\n out_shape=(h, w),\n )\n\n clipped = ndvi_composite.copy()\n clipped[~poly_mask] = np.nan # mask out pixels outside county\n\n # \u2500\u2500 4. Return mean NDVI inside county \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n valid = clipped[~np.isnan(clipped)]\n mean_ndvi = float(np.mean(valid)) if len(valid) > 0 else None\n min_ndvi = float(np.min(valid)) if len(valid) > 0 else None\n max_ndvi = float(np.max(valid)) if len(valid) > 0 else None\n pct_valid = len(valid) / clipped.size * 100\n\n print(f\"Pixels inside county: {len(valid)} / {clipped.size} ({pct_valid:.1f}%)\")\n print(f\"Mean NDVI: {mean_ndvi:.4f}, Min: {min_ndvi:.4f}, Max: {max_ndvi:.4f}\")\n\n return pd.DataFrame({\n \"state_fips\": [state_fips],\n \"county_fips\": [county_fips],\n \"year\": [year],\n \"month\": [month],\n \"mean_ndvi\": [round(mean_ndvi, 4)],\n \"min_ndvi\": [round(min_ndvi, 4)],\n \"max_ndvi\": [round(max_ndvi, 4)],\n \"valid_pixels\":[len(valid)],\n \"total_pixels\":[clipped.size],\n })\n",
6161
"headers": []
6262
}
6363
}

public/Analytics_in_Fused/ndvi_monthly_mean/ndvi_monthly_mean.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def udf(
6363
)
6464

6565
clipped = ndvi_composite.copy()
66-
clipped[~poly_mask] = np.nan # zero out pixels outside county
66+
clipped[~poly_mask] = np.nan # mask out pixels outside county
6767

6868
# ── 4. Return mean NDVI inside county ─────────────────────────────
6969
valid = clipped[~np.isnan(clipped)]

0 commit comments

Comments
 (0)