lucid-hf commited on
Commit
ee84d02
·
verified ·
1 Parent(s): 9a3291a

CI: deploy Docker/PDM Space

Browse files
services/app_service/app.py CHANGED
@@ -3,7 +3,6 @@ from pathlib import Path
3
 
4
  import streamlit as st
5
 
6
-
7
  def local_image_to_data_url(path: str | Path) -> str:
8
  """Convert a local image into a base64 data URL for inlined CSS backgrounds."""
9
  p = Path(path)
@@ -13,7 +12,6 @@ def local_image_to_data_url(path: str | Path) -> str:
13
  b64 = base64.b64encode(p.read_bytes()).decode()
14
  return f"data:{mime};base64,{b64}"
15
 
16
-
17
  def main() -> None:
18
  st.set_page_config(
19
  page_title="Home",
@@ -24,10 +22,11 @@ def main() -> None:
24
  with st.sidebar:
25
  st.header("Menu")
26
  st.page_link("app.py", label="Home")
27
- st.page_link("pages/bushland_beacon.py", label="Bushland beacon")
28
  st.page_link("pages/lost_at_sea.py", label="Lost at sea")
29
- st.page_link("pages/signal_watch.py", label="Signal Watch")
 
30
 
 
31
  hide_default_css = """
32
  <style>
33
  #MainMenu {visibility: hidden;}
@@ -36,9 +35,7 @@ def main() -> None:
36
  </style>
37
  """
38
  st.markdown(hide_default_css, unsafe_allow_html=True)
39
-
40
- bg_image = local_image_to_data_url("resources/images/rescue.png")
41
-
42
  st.markdown(
43
  f"""
44
  <style>
@@ -49,6 +46,18 @@ def main() -> None:
49
  --cta-alt: #f4f4f4;
50
  }}
51
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  .hero {{
53
  position: relative;
54
  min-height: 92vh;
@@ -57,12 +66,12 @@ def main() -> None:
57
  place-items: center;
58
  text-align: center;
59
  color: var(--text);
60
- background-image:
61
- linear-gradient(180deg, rgba(0,0,0,0.10) 0%, rgba(0,0,0,0.20) 35%, rgba(0,0,0,0.35) 100%),
62
- url('{bg_image}');
63
- background-size: cover;
64
- background-position: center;
65
- background-repeat: no-repeat;
66
  }}
67
 
68
  .hero .content {{
@@ -70,11 +79,11 @@ def main() -> None:
70
  padding: 2rem 1rem 5rem;
71
  }}
72
 
73
- .hero h1 {{
74
- font-size: clamp(2.6rem, 6vw, 5rem);
75
- font-weight: 700;
76
  letter-spacing: 0.3px;
77
- margin: 0 0 0.7rem 0;
78
  }}
79
 
80
  .hero .subtitle {{
@@ -89,53 +98,19 @@ def main() -> None:
89
  justify-content: center;
90
  flex-wrap: wrap;
91
  }}
92
-
93
- .hero .btn {{
94
- border: 0;
95
- border-radius: 4px;
96
- padding: 0.9rem 1.4rem;
97
- font-weight: 600;
98
- cursor: pointer;
99
- transition: transform 120ms ease, opacity 120ms ease;
100
- }}
101
-
102
- .hero .btn.primary {{ background: var(--cta); color: white; }}
103
- .hero .btn.secondary {{ background: var(--cta-alt); color: #171a20; }}
104
-
105
- .hero .btn:hover {{ transform: translateY(-1px); }}
106
- .nav {{
107
- position: fixed;
108
- top: 0; left: 0; right: 0;
109
- height: 54px;
110
- display: flex;
111
- align-items: center;
112
- justify-content: center;
113
- gap: 1.2rem;
114
- backdrop-filter: saturate(180%) blur(10px);
115
- background: rgba(255,255,255,0.2);
116
- color: white;
117
- z-index: 9999;
118
- }}
119
- .nav a {{
120
- color: white;
121
- text-decoration: none;
122
- font-weight: 600;
123
- font-size: 0.95rem;
124
- opacity: 0.95;
125
- }}
126
- .nav a:hover {{ opacity: 1; }}
127
  </style>
128
  """,
129
  unsafe_allow_html=True,
130
  )
131
-
132
  st.markdown(
133
  """
134
  <section class="hero">
135
  <div class="content">
136
- <h2>NATSAR Search & Rescue Simulation Hub</h2>
137
  <div class="subtitle">
138
- This web application helps search and rescue teams and researchers detect critical objects from images captured via drones, bodycams, or other camera systems.
139
  </div>
140
  </div>
141
  </section>
@@ -143,6 +118,5 @@ def main() -> None:
143
  unsafe_allow_html=True,
144
  )
145
 
146
-
147
  if __name__ == "__main__":
148
  main()
 
3
 
4
  import streamlit as st
5
 
 
6
  def local_image_to_data_url(path: str | Path) -> str:
7
  """Convert a local image into a base64 data URL for inlined CSS backgrounds."""
8
  p = Path(path)
 
12
  b64 = base64.b64encode(p.read_bytes()).decode()
13
  return f"data:{mime};base64,{b64}"
14
 
 
15
  def main() -> None:
16
  st.set_page_config(
17
  page_title="Home",
 
22
  with st.sidebar:
23
  st.header("Menu")
24
  st.page_link("app.py", label="Home")
 
25
  st.page_link("pages/lost_at_sea.py", label="Lost at sea")
26
+ st.page_link("pages/bushland_beacon.py", label="Bushland beacon")
27
+ st.page_link("pages/signal_watch.py", label="Signal watch")
28
 
29
+ bg_image = local_image_to_data_url("resources/images/rescue.jpg")
30
  hide_default_css = """
31
  <style>
32
  #MainMenu {visibility: hidden;}
 
35
  </style>
36
  """
37
  st.markdown(hide_default_css, unsafe_allow_html=True)
38
+
 
 
39
  st.markdown(
40
  f"""
41
  <style>
 
46
  --cta-alt: #f4f4f4;
47
  }}
48
 
49
+ /* Fixed full-screen background behind main content */
50
+ .stApp {{
51
+ background: url("{bg_image}") no-repeat center center fixed;
52
+ background-size: cover;
53
+ }}
54
+
55
+ /* Keep sidebar opaque (so it’s not transparent) */
56
+ [data-testid="stSidebar"] > div:first-child {{
57
+ background-color: rgba(255,255,255,0);
58
+ backdrop-filter: blur(2px);
59
+ }}
60
+
61
  .hero {{
62
  position: relative;
63
  min-height: 92vh;
 
66
  place-items: center;
67
  text-align: center;
68
  color: var(--text);
69
+ # background-image:
70
+ # linear-gradient(180deg, rgba(0,0,0,0.10) 0%, rgba(0,0,0,0.20) 35%, rgba(0,0,0,0.35) 100%),
71
+ # url('{bg_image}');
72
+ # background-size: cover;
73
+ # background-position: center;
74
+ # background-repeat: no-repeat;
75
  }}
76
 
77
  .hero .content {{
 
79
  padding: 2rem 1rem 5rem;
80
  }}
81
 
82
+ .hero h2 {{
83
+ font-size: clamp(2rem, 4vw, 5rem);
84
+ font-weight: 500;
85
  letter-spacing: 0.3px;
86
+ margin: 0 0 1rem 0;
87
  }}
88
 
89
  .hero .subtitle {{
 
98
  justify-content: center;
99
  flex-wrap: wrap;
100
  }}
101
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </style>
103
  """,
104
  unsafe_allow_html=True,
105
  )
106
+ # --- Streamlit content ---
107
  st.markdown(
108
  """
109
  <section class="hero">
110
  <div class="content">
111
+ <h2>NATSAR Detection Hub</h2>
112
  <div class="subtitle">
113
+ Search and detect people and ships within images and video.
114
  </div>
115
  </div>
116
  </section>
 
118
  unsafe_allow_html=True,
119
  )
120
 
 
121
  if __name__ == "__main__":
122
  main()
services/app_service/pages/signal_watch.py CHANGED
@@ -542,11 +542,103 @@ def extract_youtube_stream_url(url: str, cookies_path: str | None = None) -> str
542
 
543
 
544
  @st.cache_resource
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  def load_model_cached(model_key: str):
546
  """Cached model loading function."""
547
  return load_model(model_key)
548
 
549
-
550
  # ---------- Main UI logic ----------
551
 
552
  # Initialize session state for stop functionality
 
542
 
543
 
544
  @st.cache_resource
545
+ <<<<<<< HEAD
546
+ def load_model(model_key: str, device: str = "cpu"):
547
+ if model_key == "deim":
548
+ return DeimHgnetV2MDrone(device=device)
549
+ if not model_key.startswith("yolo:"):
550
+ raise ValueError(f"Unknown model key: {model_key}")
551
+ if not YOLO_AVAILABLE:
552
+ raise RuntimeError(
553
+ "Ultralytics YOLO weights requested but the package is not installed."
554
+ )
555
+
556
+ weight_path = Path(model_key.split(":", 1)[1])
557
+ if not weight_path.exists():
558
+ raise FileNotFoundError(
559
+ f"YOLO weights not found at {weight_path}. Place the .pt file there or "
560
+ "choose the DEIM model."
561
+ )
562
+ return YOLO(str(weight_path))
563
+
564
+
565
+ def draw_yolo_dets(frame_bgr, result, show_score=True):
566
+ out = frame_bgr.copy()
567
+ boxes = getattr(result, "boxes", None)
568
+ if boxes is None:
569
+ return out
570
+ names = result.names
571
+ cls_ids = boxes.cls.cpu().numpy().astype(int)
572
+ confs = boxes.conf.cpu().numpy()
573
+ xyxy = boxes.xyxy.cpu().numpy()
574
+ for (x1, y1, x2, y2), cls, score in zip(xyxy, cls_ids, confs):
575
+ x1, y1, x2, y2 = map(int, (x1, y1, x2, y2))
576
+ label = names.get(int(cls), str(int(cls)))
577
+ if show_score:
578
+ label = f"{label} {score:.2f}"
579
+ cv2.rectangle(out, (x1, y1), (x2, y2), (0, 255, 0), 2)
580
+ (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
581
+ cv2.rectangle(out, (x1, y1 - th - 8), (x1 + tw + 6, y1), (0, 255, 0), -1)
582
+ cv2.putText(
583
+ out,
584
+ label,
585
+ (x1 + 3, max(0, y1 - 6)),
586
+ cv2.FONT_HERSHEY_SIMPLEX,
587
+ 0.6,
588
+ (0, 0, 0),
589
+ 2,
590
+ cv2.LINE_AA,
591
+ )
592
+ return out
593
+
594
+
595
+ # ---------- Sidebar: Video feed dropdown ----------
596
+ st.sidebar.header("Streaming Video")
597
+
598
+ VIDEO_DIR = Path(__file__).resolve().parents[1] / "resources" / "videos" / "raw"
599
+
600
+ video_map = {}
601
+ if VIDEO_DIR.exists():
602
+ for p in sorted(VIDEO_DIR.glob("*")):
603
+ if p.suffix.lower() in {".mp4", ".mov", ".avi", ".mkv"}:
604
+ video_map[p.name] = str(p)
605
+
606
+ source_options = ["Webcam (0)"] + list(video_map.keys()) + ["Youtube URL…"]
607
+ src_choice = st.sidebar.selectbox("Source", source_options, index=0)
608
+
609
+ custom_url = None
610
+ if src_choice == "Youtube URL…":
611
+ custom_url = st.sidebar.text_input(
612
+ "RTSP/HTTP URL", placeholder="rtsp://... or https://..."
613
+ )
614
+
615
+ st.sidebar.header("Parameters")
616
+ label_to_key: dict[str, str] = {label: key for label, key in MODEL_ENTRIES}
617
+ available_model_labels = []
618
+ for label, key in MODEL_ENTRIES:
619
+ if key == "deim":
620
+ available_model_labels.append(label)
621
+ continue
622
+ if not key.startswith("yolo:"):
623
+ continue
624
+ weight_path = Path(key.split(":", 1)[1])
625
+ if YOLO_AVAILABLE and weight_path.exists():
626
+ available_model_labels.append(label)
627
+
628
+ if not available_model_labels:
629
+ available_model_labels = ["DEIM (NATSAR bespoke)"]
630
+
631
+ model_label = st.sidebar.selectbox("Model", available_model_labels, index=0)
632
+ model_key = label_to_key.get(model_label, "deim")
633
+ confidence_threshold = st.sidebar.slider("Minimum Confidence", 0.0, 1.0, 0.5, 0.01)
634
+ frame_stride = st.sidebar.slider("Process every Nth frame", 1, 5, 2, 1)
635
+ max_seconds = st.sidebar.slider("Max seconds (webcam/live)", 3, 60, 12, 1)
636
+ run_detection = st.sidebar.button("🎥 Run Detection", use_container_width=True)
637
+
638
  def load_model_cached(model_key: str):
639
  """Cached model loading function."""
640
  return load_model(model_key)
641
 
 
642
  # ---------- Main UI logic ----------
643
 
644
  # Initialize session state for stop functionality
services/app_service/resources/images/desktop.ini ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [LocalizedFileNames]
2
services/app_service/resources/images/rescue.jpg ADDED

Git LFS Details

  • SHA256: da5f56f5075d6e4e30dd30bf8ed7aca6651e051b5fc619f966c6ebc0cfcebb83
  • Pointer size: 132 Bytes
  • Size of remote file: 2.56 MB