SmokeScan / ui /samples.py
KinetoLabs's picture
Fix SessionState field name in samples.py
6fc2368
"""Sample room data for testing the FDAM AI Pipeline.
Provides 4 pre-configured sample scenarios with complete room data,
images, and qualitative observations.
MVP Simplification: Single room, no project-level data.
"""
import uuid
import io
from pathlib import Path
from dataclasses import dataclass, field
from PIL import Image
from ui.state import (
SessionState,
RoomFormData,
ImageFormData,
ObservationsFormData,
)
from ui.components import image_store
# Path to sample images directory
SAMPLE_IMAGES_DIR = Path(__file__).parent.parent / "sample_images"
@dataclass
class SampleScenario:
"""Definition of a sample fire damage scenario."""
id: str
name: str
description: str
room_data: dict
observations_data: dict
image_files: list[str] = field(default_factory=list)
# --- Sample Scenario Definitions ---
SAMPLE_SCENARIOS = [
# 1. Bar & Dining Area
SampleScenario(
id="bar_dining",
name="Bar & Dining Area",
description="3 images",
room_data={
"name": "Bar & Dining Area",
"length_ft": 40.0,
"width_ft": 30.0,
"ceiling_height_ft": 12.0,
"facility_classification": "non-operational",
"construction_era": "pre-1980",
},
observations_data={
"smoke_fire_odor": True,
"odor_intensity": "strong",
"visible_soot_deposits": True,
"soot_pattern_description": "Heavy soot deposits on corrugated metal ceiling, moderate wall discoloration",
"large_char_particles": True,
"char_density_estimate": "moderate",
"ash_like_residue": True,
"ash_color_texture": "Ash deposits on horizontal surfaces and upholstered furniture",
"surface_discoloration": True,
"discoloration_description": "Tan/brown soot staining on walls, yellowing on decorative elements",
"dust_loading_interference": False,
"dust_notes": "",
"wildfire_indicators": False,
"wildfire_notes": "",
"additional_notes": "",
},
image_files=[
"Bar and dining area1.jpg",
"Bar and dining area2.jpg",
"Bar and dining area3.jpg",
],
),
# 2. Bar Area
SampleScenario(
id="bar_area",
name="Bar Area",
description="3 images",
room_data={
"name": "Bar Area",
"length_ft": 25.0,
"width_ft": 20.0,
"ceiling_height_ft": 14.0,
"facility_classification": "non-operational",
"construction_era": "pre-1980",
},
observations_data={
"smoke_fire_odor": True,
"odor_intensity": "strong",
"visible_soot_deposits": True,
"soot_pattern_description": "Dense black coating on ceiling/ductwork, severe overhead damage",
"large_char_particles": True,
"char_density_estimate": "dense",
"ash_like_residue": True,
"ash_color_texture": "Heavy ash on shelving and bottled goods",
"surface_discoloration": True,
"discoloration_description": "Metal oxidation, melted plastic signage, deformed ductwork",
"dust_loading_interference": False,
"dust_notes": "",
"wildfire_indicators": False,
"wildfire_notes": "",
"additional_notes": "",
},
image_files=[
"Bar area1.jpg",
"Bar area2.jpg",
"Bar area3.jpg",
],
),
# 3. Kitchen
SampleScenario(
id="kitchen",
name="Kitchen",
description="6 images",
room_data={
"name": "Commercial Kitchen",
"length_ft": 30.0,
"width_ft": 25.0,
"ceiling_height_ft": 10.0,
"facility_classification": "non-operational",
"construction_era": "1980-2000",
},
observations_data={
"smoke_fire_odor": True,
"odor_intensity": "strong",
"visible_soot_deposits": True,
"soot_pattern_description": "Heavy soot on all surfaces, ceiling collapse debris",
"large_char_particles": True,
"char_density_estimate": "dense",
"ash_like_residue": True,
"ash_color_texture": "Thick ash deposits on work surfaces, equipment heavily coated",
"surface_discoloration": True,
"discoloration_description": "Charred drywall, oxidized metal equipment, concrete staining",
"dust_loading_interference": False,
"dust_notes": "",
"wildfire_indicators": False,
"wildfire_notes": "",
"additional_notes": "",
},
image_files=[
"Kitchen 1.jpg",
"Kitchen 2.jpg",
"Kitchen 3.jpg",
"Kitchen 4.jpg",
"Kitchen 5.jpg",
"Kitchen 6.jpg",
],
),
# 4. Factory Area
SampleScenario(
id="factory",
name="Factory Area",
description="1 image",
room_data={
"name": "Factory Production Area",
"length_ft": 80.0,
"width_ft": 60.0,
"ceiling_height_ft": 25.0,
"facility_classification": "operational",
"construction_era": "pre-1980",
},
observations_data={
"smoke_fire_odor": True,
"odor_intensity": "strong",
"visible_soot_deposits": True,
"soot_pattern_description": "Complete structural compromise, deep char on all surfaces",
"large_char_particles": True,
"char_density_estimate": "dense",
"ash_like_residue": True,
"ash_color_texture": "Heavy ash coating throughout, debris accumulation",
"surface_discoloration": True,
"discoloration_description": "Extreme oxidation on metal framing, thermal spalling on concrete",
"dust_loading_interference": False,
"dust_notes": "",
"wildfire_indicators": False,
"wildfire_notes": "",
"additional_notes": "",
},
image_files=[
"factory_area.jpg",
],
),
]
# Create lookup dict for fast access
SAMPLE_SCENARIOS_BY_ID = {s.id: s for s in SAMPLE_SCENARIOS}
def get_sample_choices() -> list[tuple[str, str]]:
"""Get dropdown choices for sample selector.
Returns:
List of (label, value) tuples for Gradio dropdown.
"""
choices = [("Select a sample scenario...", "")]
for scenario in SAMPLE_SCENARIOS:
label = f"{scenario.name} ({scenario.description})"
choices.append((label, scenario.id))
return choices
def load_sample_images(scenario: SampleScenario, room_id: str) -> list[ImageFormData]:
"""Load sample images from disk into image_store.
Args:
scenario: The sample scenario to load images for.
room_id: The room ID to associate images with.
Returns:
List of ImageFormData objects for the loaded images.
"""
image_metas = []
for filename in scenario.image_files:
filepath = SAMPLE_IMAGES_DIR / filename
if filepath.exists():
try:
# Read and convert image to PNG bytes
img = Image.open(filepath)
img_bytes = io.BytesIO()
img.save(img_bytes, format="PNG")
# Generate unique image ID
image_id = f"sample-{uuid.uuid4().hex[:8]}"
# Store in image_store
image_store.store(image_id, img_bytes.getvalue())
# Create metadata
image_metas.append(
ImageFormData(
id=image_id,
filename=filename,
room_id=room_id,
description=f"Sample image: {filename}",
)
)
except Exception:
# Skip files that can't be opened as images
continue
return image_metas
def load_sample(scenario_id: str) -> SessionState | None:
"""Load a sample scenario into a new SessionState.
Args:
scenario_id: The ID of the scenario to load.
Returns:
A new SessionState populated with the scenario data, or None if not found.
"""
scenario = SAMPLE_SCENARIOS_BY_ID.get(scenario_id)
if not scenario:
return None
# Create room with unique ID and all fields from room_data
room_id = f"room-{uuid.uuid4().hex[:8]}"
room = RoomFormData(
id=room_id,
name=scenario.room_data["name"],
length_ft=scenario.room_data["length_ft"],
width_ft=scenario.room_data["width_ft"],
ceiling_height_ft=scenario.room_data["ceiling_height_ft"],
facility_classification=scenario.room_data.get("facility_classification", "non-operational"),
construction_era=scenario.room_data.get("construction_era", "post-2000"),
)
# Load images
images = load_sample_images(scenario, room_id)
# Create session with single room
session = SessionState(
room=room,
images=images,
observations=ObservationsFormData(**scenario.observations_data),
name=scenario.room_data["name"],
)
# Mark input as complete since sample has all required data
session.input_complete = True
return session
def get_scenario_by_id(scenario_id: str) -> SampleScenario | None:
"""Get a sample scenario by its ID.
Args:
scenario_id: The scenario ID.
Returns:
The SampleScenario object or None if not found.
"""
return SAMPLE_SCENARIOS_BY_ID.get(scenario_id)