Mingze commited on
Commit
e00e6a7
·
verified ·
1 Parent(s): edf7ac3

Add guided NASA satellite case studies

Browse files
Files changed (3) hide show
  1. README.md +12 -0
  2. app.py +56 -0
  3. tests/test_app_contract.py +4 -0
README.md CHANGED
@@ -50,6 +50,18 @@ Every run creates visual and machine-readable outputs:
50
  - Independent Gradio API endpoints at `/classify`, `/segment`, `/detect`, and `/analyze`.
51
  - A reusable Codex skill and command-line Space client.
52
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  ## How it works
54
 
55
  ```text
 
50
  - Independent Gradio API endpoints at `/classify`, `/segment`, `/detect`, and `/analyze`.
51
  - A reusable Codex skill and command-line Space client.
52
 
53
+ ## Guided case studies
54
+
55
+ The interface includes three one-click NASA Earth Observatory cases. Each case loads the reference image and shows the acquisition context, recommended analytical question, and source attribution.
56
+
57
+ | Case | Sensor / date | Suggested use |
58
+ |---|---|---|
59
+ | [Indus River irrigated agriculture](https://earthobservatory.nasa.gov/images/52076/seasonal-changes-along-the-indus-river) | Landsat 5 TM / 2009-09-10 | Compare crop and river scene alternatives with cropland/water pixel shares |
60
+ | [Lluta River desert agriculture](https://earthobservatory.nasa.gov/images/82296/lluta-river-chile) | EO-1 ALI / 2012-07-19 | Inspect ambiguity where narrow irrigated valleys cross dominant bare land |
61
+ | [Zambezi wet-season floodplain](https://earthobservatory.nasa.gov/images/80835/wet-season-transforms-the-zambezi-river) | EO-1 ALI / 2013-03-31 | Evaluate water, vegetation, and bare-land composition |
62
+
63
+ These are method-exploration examples, not ground-truth demonstrations. The imagery remains credited to NASA Earth Observatory and the source instrument teams.
64
+
65
  ## How it works
66
 
67
  ```text
app.py CHANGED
@@ -65,6 +65,29 @@ OPEN_EARTH_MAP_LABELS = {
65
  7: "cropland",
66
  8: "building",
67
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
 
70
  def _device() -> torch.device:
@@ -109,6 +132,19 @@ def _require_image(image: Image.Image | None) -> Image.Image:
109
  return resize_for_inference(image)
110
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def _classify_impl(prepared: Image.Image, top_k: int, output_dir: Path) -> dict[str, object]:
113
  processor, model, id2label, device = load_classifier()
114
  inputs = {name: tensor.to(device) for name, tensor in processor(images=prepared, return_tensors="pt").items()}
@@ -353,6 +389,7 @@ CSS = """
353
  .prob-row {display:grid;grid-template-columns:155px 1fr 62px;gap:10px;align-items:center;margin:8px 0;font-size:.86rem;}
354
  .prob-row b {text-align:right}.prob-track {height:9px;background:#e5edf1;border-radius:20px;overflow:hidden}.prob-track i {display:block;height:100%;background:linear-gradient(90deg,#169c7d,#36b7c5);border-radius:20px;}
355
  .section-note {padding:12px 14px;border-left:4px solid #15947a;background:#eef9f6;border-radius:8px;color:#315c62;}
 
356
  @media(max-width:850px){.pipeline,.summary-grid{grid-template-columns:1fr}.prob-row{grid-template-columns:115px 1fr 56px}}
357
  """
358
 
@@ -371,6 +408,12 @@ with gr.Blocks(title="Satellite Vision Toolkit Pro", css=CSS, theme=gr.themes.So
371
  <div><b>03 · Object detection</b><span>Bounding boxes and inventory-style summaries for 10 VHR object types.</span></div>
372
  </div>
373
  """)
 
 
 
 
 
 
374
  with gr.Row(equal_height=True):
375
  image_input = gr.Image(type="pil", label="Satellite / aerial RGB image", height=430)
376
  with gr.Column():
@@ -453,6 +496,19 @@ with gr.Blocks(title="Satellite Vision Toolkit Pro", css=CSS, theme=gr.themes.So
453
  **Interpretation guardrails:** EuroSAT is a European Sentinel-2 scene dataset; classification may shift on other sensors, regions, resolutions, or crops. Pixel shares are not automatically physical ground-area shares. Pixel-coordinate GeoJSON is not georeferenced. Models can miss small or obscured objects. Do not use outputs alone for legal, surveillance, emergency, navigation, or safety-critical decisions.
454
  """)
455
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  classify_button.click(
457
  classify_lulc,
458
  inputs=[image_input, top_k],
 
65
  7: "cropland",
66
  8: "building",
67
  }
68
+ CASE_STUDIES = {
69
+ "indus": {
70
+ "title": "Indus River · irrigated agriculture",
71
+ "image": "https://eoimages.gsfc.nasa.gov/images/imagerecords/52000/52076/indus_tm5_20090910_lrg.jpg",
72
+ "source": "https://earthobservatory.nasa.gov/images/52076/seasonal-changes-along-the-indus-river",
73
+ "sensor": "Landsat 5 TM · natural color · 2009-09-10",
74
+ "focus": "Compare scene-level crop/river alternatives with pixel-level cropland and water shares.",
75
+ },
76
+ "lluta": {
77
+ "title": "Lluta River · desert agriculture",
78
+ "image": "https://eoimages.gsfc.nasa.gov/images/imagerecords/82000/82296/Lluta_ali_2012201_lrg.jpg",
79
+ "source": "https://earthobservatory.nasa.gov/images/82296/lluta-river-chile",
80
+ "sensor": "EO-1 ALI · natural color · 2012-07-19",
81
+ "focus": "Inspect classification ambiguity where narrow irrigated valleys cross dominant bare land.",
82
+ },
83
+ "zambezi": {
84
+ "title": "Zambezi River · wet-season floodplain",
85
+ "image": "https://eoimages.gsfc.nasa.gov/images/imagerecords/80000/80835/zambezi_ali_2013090_lrg.jpg",
86
+ "source": "https://earthobservatory.nasa.gov/images/80835/wet-season-transforms-the-zambezi-river",
87
+ "sensor": "EO-1 ALI · 2013-03-31",
88
+ "focus": "Evaluate water, vegetation, and bare-land composition across a seasonal floodplain.",
89
+ },
90
+ }
91
 
92
 
93
  def _device() -> torch.device:
 
132
  return resize_for_inference(image)
133
 
134
 
135
+ def load_case_study(case_key: str):
136
+ """Load a documented NASA case into the shared image input."""
137
+ case = CASE_STUDIES[case_key]
138
+ note = (
139
+ f"### {case['title']}\n"
140
+ f"**Acquisition:** {case['sensor']} \n"
141
+ f"**Suggested analysis:** {case['focus']} \n"
142
+ f"[Open NASA Earth Observatory source]({case['source']}) · "
143
+ "Reference imagery is provided for method exploration; model outputs are not ground truth."
144
+ )
145
+ return case["image"], note
146
+
147
+
148
  def _classify_impl(prepared: Image.Image, top_k: int, output_dir: Path) -> dict[str, object]:
149
  processor, model, id2label, device = load_classifier()
150
  inputs = {name: tensor.to(device) for name, tensor in processor(images=prepared, return_tensors="pt").items()}
 
389
  .prob-row {display:grid;grid-template-columns:155px 1fr 62px;gap:10px;align-items:center;margin:8px 0;font-size:.86rem;}
390
  .prob-row b {text-align:right}.prob-track {height:9px;background:#e5edf1;border-radius:20px;overflow:hidden}.prob-track i {display:block;height:100%;background:linear-gradient(90deg,#169c7d,#36b7c5);border-radius:20px;}
391
  .section-note {padding:12px 14px;border-left:4px solid #15947a;background:#eef9f6;border-radius:8px;color:#315c62;}
392
+ .case-panel {padding:16px 18px;border:1px solid #dce6ed;border-radius:16px;background:#fff;margin:8px 0 14px}.case-panel h3{margin:0 0 4px;color:#123c49}.case-panel p{margin:0;color:#647984}
393
  @media(max-width:850px){.pipeline,.summary-grid{grid-template-columns:1fr}.prob-row{grid-template-columns:115px 1fr 56px}}
394
  """
395
 
 
408
  <div><b>03 · Object detection</b><span>Bounding boxes and inventory-style summaries for 10 VHR object types.</span></div>
409
  </div>
410
  """)
411
+ gr.HTML("<div class='case-panel'><h3>Guided case studies</h3><p>Load a documented NASA scene, review the analytical question, then run the complete assessment or an individual model.</p></div>")
412
+ with gr.Row():
413
+ indus_case = gr.Button("🌾 Indus agriculture")
414
+ lluta_case = gr.Button("🏜️ Lluta desert valley")
415
+ zambezi_case = gr.Button("🌊 Zambezi floodplain")
416
+ case_note = gr.Markdown("Select a case study to load its image and methodological prompt.")
417
  with gr.Row(equal_height=True):
418
  image_input = gr.Image(type="pil", label="Satellite / aerial RGB image", height=430)
419
  with gr.Column():
 
496
  **Interpretation guardrails:** EuroSAT is a European Sentinel-2 scene dataset; classification may shift on other sensors, regions, resolutions, or crops. Pixel shares are not automatically physical ground-area shares. Pixel-coordinate GeoJSON is not georeferenced. Models can miss small or obscured objects. Do not use outputs alone for legal, surveillance, emergency, navigation, or safety-critical decisions.
497
  """)
498
 
499
+ indus_case.click(
500
+ lambda: load_case_study("indus"),
501
+ outputs=[image_input, case_note],
502
+ )
503
+ lluta_case.click(
504
+ lambda: load_case_study("lluta"),
505
+ outputs=[image_input, case_note],
506
+ )
507
+ zambezi_case.click(
508
+ lambda: load_case_study("zambezi"),
509
+ outputs=[image_input, case_note],
510
+ )
511
+
512
  classify_button.click(
513
  classify_lulc,
514
  inputs=[image_input, top_k],
tests/test_app_contract.py CHANGED
@@ -18,3 +18,7 @@ def test_app_exposes_all_api_endpoints_and_models():
18
  assert "mfaytin/mask2former-satellite" in constants
19
  assert "bluelabel/satellite-equipment-detection-yolov8n-vhr10" in constants
20
  assert "cropland" in constants
 
 
 
 
 
18
  assert "mfaytin/mask2former-satellite" in constants
19
  assert "bluelabel/satellite-equipment-detection-yolov8n-vhr10" in constants
20
  assert "cropland" in constants
21
+ assert "indus" in constants
22
+ assert "lluta" in constants
23
+ assert "zambezi" in constants
24
+ assert "Guided case studies" in source