ashandilgith commited on
Commit
ab65fad
·
1 Parent(s): 8fad80c

pushing tested revised files for HF compatibility

Browse files
.gitignore CHANGED
@@ -1,4 +1,9 @@
1
- gcp_key.json
2
  __pycache__/
3
- *.pyctemp_audio_uploads/
 
 
4
  temp_audio_uploads/
 
 
 
 
1
+ # .gitignore
2
  __pycache__/
3
+ *.pyc
4
+ .env
5
+ gcp_key.json
6
  temp_audio_uploads/
7
+ temp_models/
8
+ *.h5
9
+ *.wav
app.py CHANGED
@@ -1,62 +1,59 @@
 
1
  import streamlit as st
 
 
 
 
 
 
 
 
2
  import os
3
  from src.processing import train_mode_cloud, predict_health_cloud
4
 
5
- #st.set_page_config(page_title="Piranaware Cloud", page_icon="☁️", layout="wide")
6
- #st.markdown("""<style>.stApp {background-color: #F0F2F6;}</style>""", unsafe_allow_html=True)
7
- # --- PIRANAWARE COASTAL THEME (CSS) ---
8
  st.markdown("""
9
  <style>
10
- /* 1. Main Background - Pure black for maximum contrast */
11
  .stApp {
12
  background-color: #000000;
13
  }
14
 
15
- /* 2. Text Color Fix - High visibility yellow */
16
- .stApp, .stMarkdown, p, label {
17
- color: #FFD700 !important; /* Bright safety yellow */
18
  }
19
 
20
- /* 3. Headers - Strong yellow, slightly warmer */
21
- h1, h2, h3, h4, h5, h6 {
22
- color: #FFEB3B !important; /* Vivid header yellow */
23
- font-family: 'Helvetica Neue', sans-serif;
24
- font-weight: 700;
25
  }
26
 
27
- /* 4. Tab Styling */
28
- button[data-baseweb="tab"] {
29
- color: #BDB76B !important; /* Muted yellow for inactive */
30
- font-weight: 600;
31
- }
32
- button[data-baseweb="tab"][aria-selected="true"] {
33
- color: #FFD700 !important;
34
- border-bottom: 4px solid #FFD700 !important;
35
- background-color: #111111 !important;
36
- }
37
-
38
- /* 5. Buttons - Black & yellow safety style */
39
  div.stButton > button {
40
  background-color: #000000;
41
  color: #FFD700;
42
- border: 3px solid #FFD700;
43
  border-radius: 8px;
44
  font-weight: bold;
45
  }
46
  div.stButton > button:hover {
47
  background-color: #FFD700;
48
  color: #000000;
49
- box-shadow: 0 4px 14px rgba(255, 215, 0, 0.6);
50
- border: 3px solid #FFD700;
51
  }
52
 
53
- /* 6. Input Labels */
54
- .stAudioInput label, .stFileUploader label, .stSelectbox label, .stTextInput label {
 
 
 
55
  color: #FFD700 !important;
56
- font-weight: 700;
57
  }
58
 
59
- /* 7. Results Box Styling */
60
  .result-box-healthy {
61
  background-color: #111111;
62
  border: 2px solid #00FF9C;
@@ -74,35 +71,39 @@ st.markdown("""
74
  </style>
75
  """, unsafe_allow_html=True)
76
 
77
-
78
- TEMP_AUDIO_DIR = "temp_audio_uploads"
79
- os.makedirs(TEMP_AUDIO_DIR, exist_ok=True)
80
 
81
  def save_audio(audio_value):
82
- if audio_value is None: return None
 
 
 
 
83
  audio_value.seek(0)
84
- save_path = os.path.join(TEMP_AUDIO_DIR, "input.wav")
85
- with open(save_path, "wb") as f:
 
86
  f.write(audio_value.read())
87
- return save_path
 
88
 
89
- # --- LOGIN ---
90
  with st.sidebar:
91
- #st.image("https://img.icons8.com/color/96/speedboat.png", width=80)
92
  st.title("User Login")
93
  st.markdown("### Ensure to use your exact boat ID")
94
 
95
- # BOAT ID INPUT
96
  boat_id = st.text_input("Enter Boat ID", value="DEMO_BOAT_01").upper().replace(" ", "_")
97
  st.caption("Training saved online on Google Cloud.")
98
  st.divider()
99
  st.info(f"Active Session:\n**{boat_id}**")
100
 
101
- # --- MAIN APP ---
102
  st.title("Piranaware Boat Engine AI")
103
 
104
  tab_train, tab_test = st.tabs(["🛠️ Train Baseline", "🩺 Diagnostics"])
105
 
 
106
  with tab_train:
107
  st.info(f"Training models for: **{boat_id}**. Ensure engine is HEALTHY.")
108
  c1, c2, c3 = st.columns(3)
@@ -110,8 +111,11 @@ with tab_train:
110
  for col, mode in [(c1, "idle"), (c2, "slow"), (c3, "fast")]:
111
  with col:
112
  st.markdown(f"### {mode.upper()}")
113
- try: audio = st.audio_input(f"Rec {mode}", key=f"rec_{mode}")
114
- except: audio = st.file_uploader(f"Up {mode}", type=['wav'], key=f"rec_{mode}")
 
 
 
115
 
116
  if st.button(f"Train {mode.upper()}", key=f"btn_{mode}"):
117
  if audio:
@@ -122,6 +126,7 @@ with tab_train:
122
  else:
123
  st.error("No Audio")
124
 
 
125
  with tab_test:
126
  st.divider()
127
  st.markdown(f"### Diagnostics for: **{boat_id}**")
@@ -129,8 +134,11 @@ with tab_test:
129
  col_in, col_out = st.columns([1, 2])
130
  with col_in:
131
  mode = st.selectbox("Select Mode", ["idle", "slow", "fast"])
132
- try: test_audio = st.audio_input("Record", key="test")
133
- except: test_audio = st.file_uploader("Upload", type=['wav'], key="test")
 
 
 
134
  btn = st.button("Run Diagnostics")
135
 
136
  with col_out:
@@ -140,8 +148,8 @@ with tab_test:
140
  report = predict_health_cloud(path, mode, boat_id)
141
 
142
  if "HEALTHY" in report:
143
- st.success(report)
144
  elif "ANOMALY" in report:
145
- st.error(report)
146
  else:
147
  st.warning(report)
 
1
+ #this is the app.py file created for Hugging Face. If this causes problems, revert to app_local.py - which works fine
2
  import streamlit as st
3
+
4
+ # --- 1. CONFIG MUST BE FIRST (CRITICAL FIX) ---
5
+ st.set_page_config(
6
+ page_title="Piranaware",
7
+ layout="wide",
8
+ page_icon="🛥️"
9
+ )
10
+
11
  import os
12
  from src.processing import train_mode_cloud, predict_health_cloud
13
 
14
+ # --- 2. CSS STYLING (THEME) ---
 
 
15
  st.markdown("""
16
  <style>
17
+ /* Main Background - Pure black */
18
  .stApp {
19
  background-color: #000000;
20
  }
21
 
22
+ /* Text Color - Safety Yellow */
23
+ .stApp, .stMarkdown, p, label, h1, h2, h3, h4, h5, h6, .stTextInput > label {
24
+ color: #FFD700 !important;
25
  }
26
 
27
+ /* Input Fields (Text Input, etc) - Dark Grey Background */
28
+ .stTextInput > div > div > input {
29
+ color: #FFD700;
30
+ background-color: #111111;
 
31
  }
32
 
33
+ /* Buttons - Black & Yellow */
 
 
 
 
 
 
 
 
 
 
 
34
  div.stButton > button {
35
  background-color: #000000;
36
  color: #FFD700;
37
+ border: 2px solid #FFD700;
38
  border-radius: 8px;
39
  font-weight: bold;
40
  }
41
  div.stButton > button:hover {
42
  background-color: #FFD700;
43
  color: #000000;
44
+ border: 2px solid #FFD700;
 
45
  }
46
 
47
+ /* Tabs */
48
+ button[data-baseweb="tab"] {
49
+ color: #BDB76B !important;
50
+ }
51
+ button[data-baseweb="tab"][aria-selected="true"] {
52
  color: #FFD700 !important;
53
+ border-bottom: 4px solid #FFD700 !important;
54
  }
55
 
56
+ /* Result Boxes */
57
  .result-box-healthy {
58
  background-color: #111111;
59
  border: 2px solid #00FF9C;
 
71
  </style>
72
  """, unsafe_allow_html=True)
73
 
74
+ # --- 3. LINUX TEMP PATH SETUP ---
75
+ TEMP_AUDIO_PATH = "/tmp/input.wav"
 
76
 
77
  def save_audio(audio_value):
78
+ """Saves audio to /tmp/input.wav for Linux/Docker compatibility"""
79
+ if audio_value is None:
80
+ return None
81
+
82
+ # Reset pointer to start of file
83
  audio_value.seek(0)
84
+
85
+ # Write to the absolute path in /tmp
86
+ with open(TEMP_AUDIO_PATH, "wb") as f:
87
  f.write(audio_value.read())
88
+
89
+ return TEMP_AUDIO_PATH
90
 
91
+ # --- 4. SIDEBAR ---
92
  with st.sidebar:
 
93
  st.title("User Login")
94
  st.markdown("### Ensure to use your exact boat ID")
95
 
 
96
  boat_id = st.text_input("Enter Boat ID", value="DEMO_BOAT_01").upper().replace(" ", "_")
97
  st.caption("Training saved online on Google Cloud.")
98
  st.divider()
99
  st.info(f"Active Session:\n**{boat_id}**")
100
 
101
+ # --- 5. MAIN APP ---
102
  st.title("Piranaware Boat Engine AI")
103
 
104
  tab_train, tab_test = st.tabs(["🛠️ Train Baseline", "🩺 Diagnostics"])
105
 
106
+ # --- TAB 1: TRAIN ---
107
  with tab_train:
108
  st.info(f"Training models for: **{boat_id}**. Ensure engine is HEALTHY.")
109
  c1, c2, c3 = st.columns(3)
 
111
  for col, mode in [(c1, "idle"), (c2, "slow"), (c3, "fast")]:
112
  with col:
113
  st.markdown(f"### {mode.upper()}")
114
+ # Try/Except handles older Streamlit versions that lack st.audio_input
115
+ try:
116
+ audio = st.audio_input(f"Rec {mode}", key=f"rec_{mode}")
117
+ except AttributeError:
118
+ audio = st.file_uploader(f"Up {mode}", type=['wav'], key=f"rec_{mode}")
119
 
120
  if st.button(f"Train {mode.upper()}", key=f"btn_{mode}"):
121
  if audio:
 
126
  else:
127
  st.error("No Audio")
128
 
129
+ # --- TAB 2: DIAGNOSTICS ---
130
  with tab_test:
131
  st.divider()
132
  st.markdown(f"### Diagnostics for: **{boat_id}**")
 
134
  col_in, col_out = st.columns([1, 2])
135
  with col_in:
136
  mode = st.selectbox("Select Mode", ["idle", "slow", "fast"])
137
+ try:
138
+ test_audio = st.audio_input("Record", key="test")
139
+ except AttributeError:
140
+ test_audio = st.file_uploader("Upload", type=['wav'], key="test")
141
+
142
  btn = st.button("Run Diagnostics")
143
 
144
  with col_out:
 
148
  report = predict_health_cloud(path, mode, boat_id)
149
 
150
  if "HEALTHY" in report:
151
+ st.markdown(f'<div class="result-box-healthy">{report}</div>', unsafe_allow_html=True)
152
  elif "ANOMALY" in report:
153
+ st.markdown(f'<div class="result-box-anomaly">{report}</div>', unsafe_allow_html=True)
154
  else:
155
  st.warning(report)
app_local.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #this is the original app.py file, and has been left out for Hugging Face. It works well for Streamlit locallly
2
+
3
+
4
+ import streamlit as st
5
+ import os
6
+ from src.processing import train_mode_cloud, predict_health_cloud
7
+
8
+ #st.set_page_config(page_title="Piranaware Cloud", page_icon="☁️", layout="wide")
9
+ #st.markdown("""<style>.stApp {background-color: #F0F2F6;}</style>""", unsafe_allow_html=True)
10
+ # --- PIRANAWARE COASTAL THEME (CSS) ---
11
+ st.markdown("""
12
+ <style>
13
+ /* 1. Main Background - Pure black for maximum contrast */
14
+ .stApp {
15
+ background-color: #000000;
16
+ }
17
+
18
+ /* 2. Text Color Fix - High visibility yellow */
19
+ .stApp, .stMarkdown, p, label {
20
+ color: #FFD700 !important; /* Bright safety yellow */
21
+ }
22
+
23
+ /* 3. Headers - Strong yellow, slightly warmer */
24
+ h1, h2, h3, h4, h5, h6 {
25
+ color: #FFEB3B !important; /* Vivid header yellow */
26
+ font-family: 'Helvetica Neue', sans-serif;
27
+ font-weight: 700;
28
+ }
29
+
30
+ /* 4. Tab Styling */
31
+ button[data-baseweb="tab"] {
32
+ color: #BDB76B !important; /* Muted yellow for inactive */
33
+ font-weight: 600;
34
+ }
35
+ button[data-baseweb="tab"][aria-selected="true"] {
36
+ color: #FFD700 !important;
37
+ border-bottom: 4px solid #FFD700 !important;
38
+ background-color: #111111 !important;
39
+ }
40
+
41
+ /* 5. Buttons - Black & yellow safety style */
42
+ div.stButton > button {
43
+ background-color: #000000;
44
+ color: #FFD700;
45
+ border: 3px solid #FFD700;
46
+ border-radius: 8px;
47
+ font-weight: bold;
48
+ }
49
+ div.stButton > button:hover {
50
+ background-color: #FFD700;
51
+ color: #000000;
52
+ box-shadow: 0 4px 14px rgba(255, 215, 0, 0.6);
53
+ border: 3px solid #FFD700;
54
+ }
55
+
56
+ /* 6. Input Labels */
57
+ .stAudioInput label, .stFileUploader label, .stSelectbox label, .stTextInput label {
58
+ color: #FFD700 !important;
59
+ font-weight: 700;
60
+ }
61
+
62
+ /* 7. Results Box Styling */
63
+ .result-box-healthy {
64
+ background-color: #111111;
65
+ border: 2px solid #00FF9C;
66
+ border-left: 6px solid #00C781;
67
+ padding: 15px; border-radius: 5px;
68
+ color: #00FF9C;
69
+ }
70
+ .result-box-anomaly {
71
+ background-color: #111111;
72
+ border: 2px solid #FF5252;
73
+ border-left: 6px solid #D32F2F;
74
+ padding: 15px; border-radius: 5px;
75
+ color: #FF5252;
76
+ }
77
+ </style>
78
+ """, unsafe_allow_html=True)
79
+
80
+
81
+ TEMP_AUDIO_DIR = "temp_audio_uploads"
82
+ os.makedirs(TEMP_AUDIO_DIR, exist_ok=True)
83
+
84
+ def save_audio(audio_value):
85
+ if audio_value is None: return None
86
+ audio_value.seek(0)
87
+ save_path = os.path.join(TEMP_AUDIO_DIR, "input.wav")
88
+ with open(save_path, "wb") as f:
89
+ f.write(audio_value.read())
90
+ return save_path
91
+
92
+ # --- LOGIN ---
93
+ with st.sidebar:
94
+ #st.image("https://img.icons8.com/color/96/speedboat.png", width=80)
95
+ st.title("User Login")
96
+ st.markdown("### Ensure to use your exact boat ID")
97
+
98
+ # BOAT ID INPUT
99
+ boat_id = st.text_input("Enter Boat ID", value="DEMO_BOAT_01").upper().replace(" ", "_")
100
+ st.caption("Training saved online on Google Cloud.")
101
+ st.divider()
102
+ st.info(f"Active Session:\n**{boat_id}**")
103
+
104
+ # --- MAIN APP ---
105
+ st.title("Piranaware Boat Engine AI")
106
+
107
+ tab_train, tab_test = st.tabs(["🛠️ Train Baseline", "🩺 Diagnostics"])
108
+
109
+ with tab_train:
110
+ st.info(f"Training models for: **{boat_id}**. Ensure engine is HEALTHY.")
111
+ c1, c2, c3 = st.columns(3)
112
+
113
+ for col, mode in [(c1, "idle"), (c2, "slow"), (c3, "fast")]:
114
+ with col:
115
+ st.markdown(f"### {mode.upper()}")
116
+ try: audio = st.audio_input(f"Rec {mode}", key=f"rec_{mode}")
117
+ except: audio = st.file_uploader(f"Up {mode}", type=['wav'], key=f"rec_{mode}")
118
+
119
+ if st.button(f"Train {mode.upper()}", key=f"btn_{mode}"):
120
+ if audio:
121
+ path = save_audio(audio)
122
+ with st.spinner("Training & Uploading to Cloud..."):
123
+ res = train_mode_cloud(path, mode, boat_id)
124
+ st.success(res)
125
+ else:
126
+ st.error("No Audio")
127
+
128
+ with tab_test:
129
+ st.divider()
130
+ st.markdown(f"### Diagnostics for: **{boat_id}**")
131
+
132
+ col_in, col_out = st.columns([1, 2])
133
+ with col_in:
134
+ mode = st.selectbox("Select Mode", ["idle", "slow", "fast"])
135
+ try: test_audio = st.audio_input("Record", key="test")
136
+ except: test_audio = st.file_uploader("Upload", type=['wav'], key="test")
137
+ btn = st.button("Run Diagnostics")
138
+
139
+ with col_out:
140
+ if btn and test_audio:
141
+ path = save_audio(test_audio)
142
+ with st.spinner("Downloading Model & Analyzing..."):
143
+ report = predict_health_cloud(path, mode, boat_id)
144
+
145
+ if "HEALTHY" in report:
146
+ st.success(report)
147
+ elif "ANOMALY" in report:
148
+ st.error(report)
149
+ else:
150
+ st.warning(report)
src/preprocess.py CHANGED
@@ -1,26 +1,57 @@
 
 
 
1
  import librosa
2
  import numpy as np
 
3
 
 
4
  SAMPLE_RATE = 22050
5
- DURATION = 1.0
6
  SAMPLES_PER_SLICE = int(SAMPLE_RATE * DURATION)
7
- N_MELS = 128
8
 
9
  def audio_to_spectrograms(file_path):
 
 
 
 
10
  try:
 
 
 
 
 
 
 
11
  y, sr = librosa.load(file_path, sr=SAMPLE_RATE)
 
 
12
  num_slices = len(y) // SAMPLES_PER_SLICE
13
- if num_slices < 1: return None
 
 
 
 
14
 
 
15
  spectrograms = []
16
  for i in range(num_slices):
 
17
  y_slice = y[i*SAMPLES_PER_SLICE : (i+1)*SAMPLES_PER_SLICE]
 
 
18
  spec = librosa.feature.melspectrogram(y=y_slice, sr=sr, n_mels=N_MELS)
 
 
19
  log_spec = librosa.power_to_db(spec, ref=np.max)
20
  norm_spec = np.clip((log_spec + 80) / 80, 0, 1)
 
 
21
  spectrograms.append(norm_spec[..., np.newaxis])
22
 
23
  return np.array(spectrograms)
 
24
  except Exception as e:
25
- print(f"Error: {e}")
26
  return None
 
1
+ #this version was created for hugging face. if issues persist revert to preprocess_local
2
+
3
+
4
  import librosa
5
  import numpy as np
6
+ import os
7
 
8
+ # --- CONSTANTS ---
9
  SAMPLE_RATE = 22050
10
+ DURATION = 1.0 # Slice length (Seconds)
11
  SAMPLES_PER_SLICE = int(SAMPLE_RATE * DURATION)
12
+ N_MELS = 128 # Frequency resolution
13
 
14
  def audio_to_spectrograms(file_path):
15
+ """
16
+ Converts audio into a batch of 1-second spectrogram slices.
17
+ Returns shape: (Num_Slices, 128, 44, 1)
18
+ """
19
  try:
20
+ # 1. Safety Check: File Existence
21
+ if not os.path.exists(file_path):
22
+ print(f"❌ Error: File not found at {file_path}")
23
+ return None
24
+
25
+ # 2. Load Audio
26
+ # We enforce sr=22050 for consistency
27
  y, sr = librosa.load(file_path, sr=SAMPLE_RATE)
28
+
29
+ # 3. Calculate Slices
30
  num_slices = len(y) // SAMPLES_PER_SLICE
31
+
32
+ # Safety: If audio is too short (< 1 second), fail gracefully
33
+ if num_slices < 1:
34
+ print("❌ Audio too short")
35
+ return None
36
 
37
+ # 4. Create Spectrograms
38
  spectrograms = []
39
  for i in range(num_slices):
40
+ # Extract 1-second chunk
41
  y_slice = y[i*SAMPLES_PER_SLICE : (i+1)*SAMPLES_PER_SLICE]
42
+
43
+ # Generate Mel Spectrogram
44
  spec = librosa.feature.melspectrogram(y=y_slice, sr=sr, n_mels=N_MELS)
45
+
46
+ # Convert to Decibels (Log Scale) and Normalize (0-1)
47
  log_spec = librosa.power_to_db(spec, ref=np.max)
48
  norm_spec = np.clip((log_spec + 80) / 80, 0, 1)
49
+
50
+ # Add Channel Dimension (Required for CNNs)
51
  spectrograms.append(norm_spec[..., np.newaxis])
52
 
53
  return np.array(spectrograms)
54
+
55
  except Exception as e:
56
+ print(f"❌ Preprocessing Error: {e}")
57
  return None
src/preprocess_local.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #this is the actual preprocess file. it is being replaced with a Huggging Face friendly one. if needed revert to this.
2
+
3
+ import librosa
4
+ import numpy as np
5
+
6
+ SAMPLE_RATE = 22050
7
+ DURATION = 1.0
8
+ SAMPLES_PER_SLICE = int(SAMPLE_RATE * DURATION)
9
+ N_MELS = 128
10
+
11
+ def audio_to_spectrograms(file_path):
12
+ try:
13
+ y, sr = librosa.load(file_path, sr=SAMPLE_RATE)
14
+ num_slices = len(y) // SAMPLES_PER_SLICE
15
+ if num_slices < 1: return None
16
+
17
+ spectrograms = []
18
+ for i in range(num_slices):
19
+ y_slice = y[i*SAMPLES_PER_SLICE : (i+1)*SAMPLES_PER_SLICE]
20
+ spec = librosa.feature.melspectrogram(y=y_slice, sr=sr, n_mels=N_MELS)
21
+ log_spec = librosa.power_to_db(spec, ref=np.max)
22
+ norm_spec = np.clip((log_spec + 80) / 80, 0, 1)
23
+ spectrograms.append(norm_spec[..., np.newaxis])
24
+
25
+ return np.array(spectrograms)
26
+ except Exception as e:
27
+ print(f"Error: {e}")
28
+ return None
src/processing.py CHANGED
@@ -1,17 +1,23 @@
1
  import numpy as np
2
  import os
 
3
  import json
4
  import tensorflow as tf
5
  from src.preprocess import audio_to_spectrograms
6
  from src.model import build_autoencoder
7
  from src.storage import upload_file, download_file
8
 
9
- TEMP_DIR = "temp_models"
 
 
 
 
10
  if not os.path.exists(TEMP_DIR):
11
  os.makedirs(TEMP_DIR)
12
 
13
  def train_mode_cloud(audio_path, mode_name, boat_id):
14
  # 1. Preprocess
 
15
  X_train = audio_to_spectrograms(audio_path)
16
  if X_train is None: return "❌ Audio too short (min 1 sec)."
17
 
@@ -19,24 +25,27 @@ def train_mode_cloud(audio_path, mode_name, boat_id):
19
  autoencoder = build_autoencoder(X_train.shape[1:])
20
  autoencoder.fit(X_train, X_train, epochs=40, batch_size=4, verbose=0)
21
 
22
- # 3. Calculate Threshold (THE FIX)
23
  reconstructions = autoencoder.predict(X_train)
24
  mse = np.mean(np.power(X_train - reconstructions, 2), axis=(1, 2, 3))
25
 
26
-
27
  threshold = float(np.mean(mse) + (2 * np.std(mse)))
28
 
29
- # 4. Save Locally
30
  model_filename = f"{mode_name}_model.h5"
31
  meta_filename = f"{mode_name}_meta.json"
 
 
32
  local_model_path = os.path.join(TEMP_DIR, model_filename)
33
  local_meta_path = os.path.join(TEMP_DIR, meta_filename)
34
 
 
35
  autoencoder.save(local_model_path, save_format='h5', include_optimizer=False)
36
  with open(local_meta_path, 'w') as f:
37
  json.dump({"threshold": threshold}, f)
38
 
39
- # 5. Upload
40
  u1 = upload_file(local_model_path, boat_id, model_filename)
41
  u2 = upload_file(local_meta_path, boat_id, meta_filename)
42
 
@@ -48,10 +57,11 @@ def train_mode_cloud(audio_path, mode_name, boat_id):
48
  def predict_health_cloud(audio_path, mode_name, boat_id):
49
  model_filename = f"{mode_name}_model.h5"
50
  meta_filename = f"{mode_name}_meta.json"
 
51
  local_model_path = os.path.join(TEMP_DIR, model_filename)
52
  local_meta_path = os.path.join(TEMP_DIR, meta_filename)
53
 
54
- # 1. Download
55
  d1 = download_file(boat_id, model_filename, local_model_path)
56
  d2 = download_file(boat_id, meta_filename, local_meta_path)
57
 
@@ -69,22 +79,20 @@ def predict_health_cloud(audio_path, mode_name, boat_id):
69
  if X_test is None: return "Error: Audio too short."
70
 
71
  reconstructions = model.predict(X_test)
72
- # Calculate error for each second of audio
73
  mse = np.mean(np.power(X_test - reconstructions, 2), axis=(1, 2, 3))
74
 
75
  # 4. Analysis
76
  anomalies = np.sum(mse > threshold)
77
  health_score = 100 * (1 - (anomalies / len(mse)))
78
 
79
- # 5. Debug Data (Shows you WHY it decided what it decided)
80
  avg_error = np.mean(mse)
81
  max_error = np.max(mse)
82
 
83
  status = "🟢 HEALTHY" if health_score > 85 else "🔴 ANOMALY DETECTED"
84
 
85
- # Return detailed report
86
  return f"""
87
- STATUS: {status}
88
  Confidence Score: {health_score:.1f}%
89
 
90
  --- TECHNICAL TELEMETRY ---
 
1
  import numpy as np
2
  import os
3
+ import shutil
4
  import json
5
  import tensorflow as tf
6
  from src.preprocess import audio_to_spectrograms
7
  from src.model import build_autoencoder
8
  from src.storage import upload_file, download_file
9
 
10
+ # --- CRITICAL FIX FOR CLOUD DEPLOYMENT ---
11
+ # We use /tmp because it is the ONLY folder guaranteed to be writable in Docker.
12
+ TEMP_DIR = "/tmp/temp_models"
13
+
14
+ # Create the directory if it doesn't exist
15
  if not os.path.exists(TEMP_DIR):
16
  os.makedirs(TEMP_DIR)
17
 
18
  def train_mode_cloud(audio_path, mode_name, boat_id):
19
  # 1. Preprocess
20
+ # audio_path is now likely "/tmp/input.wav" passed from app.py
21
  X_train = audio_to_spectrograms(audio_path)
22
  if X_train is None: return "❌ Audio too short (min 1 sec)."
23
 
 
25
  autoencoder = build_autoencoder(X_train.shape[1:])
26
  autoencoder.fit(X_train, X_train, epochs=40, batch_size=4, verbose=0)
27
 
28
+ # 3. Calculate Threshold (Dynamic Safety Margin)
29
  reconstructions = autoencoder.predict(X_train)
30
  mse = np.mean(np.power(X_train - reconstructions, 2), axis=(1, 2, 3))
31
 
32
+ # Using 2 Standard Deviations (Industry Standard for Strict Detection)
33
  threshold = float(np.mean(mse) + (2 * np.std(mse)))
34
 
35
+ # 4. Save Locally (TO /tmp)
36
  model_filename = f"{mode_name}_model.h5"
37
  meta_filename = f"{mode_name}_meta.json"
38
+
39
+ # These paths now point to /tmp/temp_models/...
40
  local_model_path = os.path.join(TEMP_DIR, model_filename)
41
  local_meta_path = os.path.join(TEMP_DIR, meta_filename)
42
 
43
+ # Save to the temporary linux folder
44
  autoencoder.save(local_model_path, save_format='h5', include_optimizer=False)
45
  with open(local_meta_path, 'w') as f:
46
  json.dump({"threshold": threshold}, f)
47
 
48
+ # 5. Upload to Google Cloud
49
  u1 = upload_file(local_model_path, boat_id, model_filename)
50
  u2 = upload_file(local_meta_path, boat_id, meta_filename)
51
 
 
57
  def predict_health_cloud(audio_path, mode_name, boat_id):
58
  model_filename = f"{mode_name}_model.h5"
59
  meta_filename = f"{mode_name}_meta.json"
60
+
61
  local_model_path = os.path.join(TEMP_DIR, model_filename)
62
  local_meta_path = os.path.join(TEMP_DIR, meta_filename)
63
 
64
+ # 1. Download from Cloud to /tmp
65
  d1 = download_file(boat_id, model_filename, local_model_path)
66
  d2 = download_file(boat_id, meta_filename, local_meta_path)
67
 
 
79
  if X_test is None: return "Error: Audio too short."
80
 
81
  reconstructions = model.predict(X_test)
 
82
  mse = np.mean(np.power(X_test - reconstructions, 2), axis=(1, 2, 3))
83
 
84
  # 4. Analysis
85
  anomalies = np.sum(mse > threshold)
86
  health_score = 100 * (1 - (anomalies / len(mse)))
87
 
88
+ # 5. Telemetry
89
  avg_error = np.mean(mse)
90
  max_error = np.max(mse)
91
 
92
  status = "🟢 HEALTHY" if health_score > 85 else "🔴 ANOMALY DETECTED"
93
 
 
94
  return f"""
95
+ {status}
96
  Confidence Score: {health_score:.1f}%
97
 
98
  --- TECHNICAL TELEMETRY ---
src/processing_local.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #this is the original processing.py file, but is being revised for Hugging Face. this works well locally with Streamlit and consider using if the other has issues.
2
+
3
+ import numpy as np
4
+ import os
5
+ import json
6
+ import tensorflow as tf
7
+ from src.preprocess import audio_to_spectrograms
8
+ from src.model import build_autoencoder
9
+ from src.storage import upload_file, download_file
10
+
11
+ TEMP_DIR = "temp_models"
12
+ if not os.path.exists(TEMP_DIR):
13
+ os.makedirs(TEMP_DIR)
14
+
15
+ def train_mode_cloud(audio_path, mode_name, boat_id):
16
+ # 1. Preprocess
17
+ X_train = audio_to_spectrograms(audio_path)
18
+ if X_train is None: return "❌ Audio too short (min 1 sec)."
19
+
20
+ # 2. Train
21
+ autoencoder = build_autoencoder(X_train.shape[1:])
22
+ autoencoder.fit(X_train, X_train, epochs=40, batch_size=4, verbose=0)
23
+
24
+ # 3. Calculate Threshold (THE FIX)
25
+ reconstructions = autoencoder.predict(X_train)
26
+ mse = np.mean(np.power(X_train - reconstructions, 2), axis=(1, 2, 3))
27
+
28
+
29
+ threshold = float(np.mean(mse) + (2 * np.std(mse)))
30
+
31
+ # 4. Save Locally
32
+ model_filename = f"{mode_name}_model.h5"
33
+ meta_filename = f"{mode_name}_meta.json"
34
+ local_model_path = os.path.join(TEMP_DIR, model_filename)
35
+ local_meta_path = os.path.join(TEMP_DIR, meta_filename)
36
+
37
+ autoencoder.save(local_model_path, save_format='h5', include_optimizer=False)
38
+ with open(local_meta_path, 'w') as f:
39
+ json.dump({"threshold": threshold}, f)
40
+
41
+ # 5. Upload
42
+ u1 = upload_file(local_model_path, boat_id, model_filename)
43
+ u2 = upload_file(local_meta_path, boat_id, meta_filename)
44
+
45
+ if u1 and u2:
46
+ return f"✅ Calibrated {mode_name.upper()} | Threshold: {threshold:.5f}"
47
+ else:
48
+ return "⚠️ Trained locally, but Cloud Upload Failed."
49
+
50
+ def predict_health_cloud(audio_path, mode_name, boat_id):
51
+ model_filename = f"{mode_name}_model.h5"
52
+ meta_filename = f"{mode_name}_meta.json"
53
+ local_model_path = os.path.join(TEMP_DIR, model_filename)
54
+ local_meta_path = os.path.join(TEMP_DIR, meta_filename)
55
+
56
+ # 1. Download
57
+ d1 = download_file(boat_id, model_filename, local_model_path)
58
+ d2 = download_file(boat_id, meta_filename, local_meta_path)
59
+
60
+ if not (d1 and d2):
61
+ return f"⚠️ No trained model found in cloud for Boat: {boat_id} (Mode: {mode_name})"
62
+
63
+ # 2. Load
64
+ with open(local_meta_path, 'r') as f:
65
+ threshold = json.load(f)["threshold"]
66
+
67
+ model = tf.keras.models.load_model(local_model_path, compile=False)
68
+
69
+ # 3. Predict
70
+ X_test = audio_to_spectrograms(audio_path)
71
+ if X_test is None: return "Error: Audio too short."
72
+
73
+ reconstructions = model.predict(X_test)
74
+ # Calculate error for each second of audio
75
+ mse = np.mean(np.power(X_test - reconstructions, 2), axis=(1, 2, 3))
76
+
77
+ # 4. Analysis
78
+ anomalies = np.sum(mse > threshold)
79
+ health_score = 100 * (1 - (anomalies / len(mse)))
80
+
81
+ # 5. Debug Data (Shows you WHY it decided what it decided)
82
+ avg_error = np.mean(mse)
83
+ max_error = np.max(mse)
84
+
85
+ status = "🟢 HEALTHY" if health_score > 85 else "🔴 ANOMALY DETECTED"
86
+
87
+ # Return detailed report
88
+ return f"""
89
+ STATUS: {status}
90
+ Confidence Score: {health_score:.1f}%
91
+
92
+ --- TECHNICAL TELEMETRY ---
93
+ Threshold Limit : {threshold:.5f}
94
+ Your Avg Error : {avg_error:.5f}
95
+ Your Max Error : {max_error:.5f}
96
+ Anomalous Secs : {anomalies} / {len(mse)}
97
+ """
src/storage.py CHANGED
@@ -1,58 +1,81 @@
 
 
1
  import os
2
  import json
3
  import streamlit as st
4
  from google.cloud import storage
5
  from google.oauth2 import service_account
6
 
7
- # ✅ UPDATED WITH YOUR BUCKET NAME
8
  BUCKET_NAME = "piranaware20251227841ph"
9
 
10
  def get_storage_client():
11
  """
12
  Authenticates with Google Cloud.
13
- CHECKS LOCAL FILE FIRST (to prevent crashes in Codespaces),
14
- then checks Secrets (for Hugging Face).
 
 
15
  """
16
- # 1. Local Dev: Check for local file FIRST
17
  if os.path.exists("gcp_key.json"):
18
  return storage.Client.from_service_account_json("gcp_key.json")
19
 
20
- # 2. Production: Check Streamlit Secrets
21
- try:
22
- if "gcp_service_account" in st.secrets:
 
 
 
 
 
 
 
 
 
 
 
 
23
  creds_dict = dict(st.secrets["gcp_service_account"])
24
  creds = service_account.Credentials.from_service_account_info(creds_dict)
25
  return storage.Client(credentials=creds)
26
- except Exception:
27
- # If secrets don't exist, we just move on
28
- pass
29
 
30
- # 3. If neither works
31
- st.error("⚠️ No Google Cloud credentials found. Cannot save models.")
32
  return None
33
 
34
  def upload_file(local_path, boat_id, filename):
35
  client = get_storage_client()
36
  if not client: return False
37
 
38
- bucket = client.bucket(BUCKET_NAME)
39
- # Creates folder structure: boat_id/filename
40
- blob_name = f"{boat_id}/{filename}"
41
- blob = bucket.blob(blob_name)
42
-
43
- blob.upload_from_filename(local_path)
44
- return True
 
 
 
 
45
 
46
  def download_file(boat_id, filename, local_dest):
47
  client = get_storage_client()
48
  if not client: return False
49
 
50
- bucket = client.bucket(BUCKET_NAME)
51
- blob_name = f"{boat_id}/{filename}"
52
- blob = bucket.blob(blob_name)
53
-
54
- if not blob.exists():
55
- return False
56
 
57
- blob.download_to_filename(local_dest)
58
- return True
 
 
 
 
 
 
 
1
+ #this was not the original storage py file, and was created for Huggging Face. If this fails consider using storage_local.py
2
+
3
  import os
4
  import json
5
  import streamlit as st
6
  from google.cloud import storage
7
  from google.oauth2 import service_account
8
 
9
+ # ✅ YOUR BUCKET NAME
10
  BUCKET_NAME = "piranaware20251227841ph"
11
 
12
  def get_storage_client():
13
  """
14
  Authenticates with Google Cloud.
15
+ Priority:
16
+ 1. Local File (Codespaces)
17
+ 2. Environment Variable (Hugging Face Docker)
18
+ 3. Streamlit Secrets (Streamlit Cloud fallback)
19
  """
20
+ # 1. Local Dev: Check for local file
21
  if os.path.exists("gcp_key.json"):
22
  return storage.Client.from_service_account_json("gcp_key.json")
23
 
24
+ # 2. Hugging Face / Docker: Check Environment Variable
25
+ # In HF, secrets are stored as Env Vars, so we parse the JSON string.
26
+ env_key = os.environ.get("gcp_service_account")
27
+ if env_key:
28
+ try:
29
+ creds_dict = json.loads(env_key)
30
+ creds = service_account.Credentials.from_service_account_info(creds_dict)
31
+ return storage.Client(credentials=creds)
32
+ except Exception as e:
33
+ st.error(f"⚠️ Credential Error: {e}")
34
+ return None
35
+
36
+ # 3. Streamlit Cloud: Check Secrets (Fallback)
37
+ if "gcp_service_account" in st.secrets:
38
+ try:
39
  creds_dict = dict(st.secrets["gcp_service_account"])
40
  creds = service_account.Credentials.from_service_account_info(creds_dict)
41
  return storage.Client(credentials=creds)
42
+ except Exception:
43
+ pass
 
44
 
45
+ # 4. If nothing works
46
+ st.error("⚠️ No Google Cloud credentials found. Cannot save/load models.")
47
  return None
48
 
49
  def upload_file(local_path, boat_id, filename):
50
  client = get_storage_client()
51
  if not client: return False
52
 
53
+ try:
54
+ bucket = client.bucket(BUCKET_NAME)
55
+ # Creates folder structure: boat_id/filename
56
+ blob_name = f"{boat_id}/{filename}"
57
+ blob = bucket.blob(blob_name)
58
+
59
+ blob.upload_from_filename(local_path)
60
+ return True
61
+ except Exception as e:
62
+ st.error(f"Upload failed: {e}")
63
+ return False
64
 
65
  def download_file(boat_id, filename, local_dest):
66
  client = get_storage_client()
67
  if not client: return False
68
 
69
+ try:
70
+ bucket = client.bucket(BUCKET_NAME)
71
+ blob_name = f"{boat_id}/{filename}"
72
+ blob = bucket.blob(blob_name)
 
 
73
 
74
+ if not blob.exists():
75
+ return False
76
+
77
+ blob.download_to_filename(local_dest)
78
+ return True
79
+ except Exception as e:
80
+ st.error(f"Download failed: {e}")
81
+ return False
src/storage_local.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #this was the original storage py file that worked well locally. revert to this if needed. the new file renamed storage py was revised for Hugging Face
2
+ import os
3
+ import json
4
+ import streamlit as st
5
+ from google.cloud import storage
6
+ from google.oauth2 import service_account
7
+
8
+ # ✅ UPDATED WITH YOUR BUCKET NAME
9
+ BUCKET_NAME = "piranaware20251227841ph"
10
+
11
+ def get_storage_client():
12
+ """
13
+ Authenticates with Google Cloud.
14
+ CHECKS LOCAL FILE FIRST (to prevent crashes in Codespaces),
15
+ then checks Secrets (for Hugging Face).
16
+ """
17
+ # 1. Local Dev: Check for local file FIRST
18
+ if os.path.exists("gcp_key.json"):
19
+ return storage.Client.from_service_account_json("gcp_key.json")
20
+
21
+ # 2. Production: Check Streamlit Secrets
22
+ try:
23
+ if "gcp_service_account" in st.secrets:
24
+ creds_dict = dict(st.secrets["gcp_service_account"])
25
+ creds = service_account.Credentials.from_service_account_info(creds_dict)
26
+ return storage.Client(credentials=creds)
27
+ except Exception:
28
+ # If secrets don't exist, we just move on
29
+ pass
30
+
31
+ # 3. If neither works
32
+ st.error("⚠️ No Google Cloud credentials found. Cannot save models.")
33
+ return None
34
+
35
+ def upload_file(local_path, boat_id, filename):
36
+ client = get_storage_client()
37
+ if not client: return False
38
+
39
+ bucket = client.bucket(BUCKET_NAME)
40
+ # Creates folder structure: boat_id/filename
41
+ blob_name = f"{boat_id}/{filename}"
42
+ blob = bucket.blob(blob_name)
43
+
44
+ blob.upload_from_filename(local_path)
45
+ return True
46
+
47
+ def download_file(boat_id, filename, local_dest):
48
+ client = get_storage_client()
49
+ if not client: return False
50
+
51
+ bucket = client.bucket(BUCKET_NAME)
52
+ blob_name = f"{boat_id}/{filename}"
53
+ blob = bucket.blob(blob_name)
54
+
55
+ if not blob.exists():
56
+ return False
57
+
58
+ blob.download_to_filename(local_dest)
59
+ return True