Skip to content

Commit dca3132

Browse files
committed
Merge branch 'Polymersome' of https://github.com/gipplab/Electronic-Laboratory-Notebook into Polymersome
2 parents 9b8cad4 + 9333e28 commit dca3132

3 files changed

Lines changed: 59 additions & 45 deletions

File tree

Analysis/templates/Analysis.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ <h4 style="font-size: 1.1em; margin-bottom: 15px;">🖥️ Cluster Live Monitor<
119119
terminal.innerText = data.log;
120120
// Scrollt automatisch ans Ende des Textes
121121
terminal.scrollTop = terminal.scrollHeight;
122+
123+
// Automatischer Stopp, wenn die Analyse fertig/gescheitert ist
124+
if (data.log.includes("✅ Analyse erfolgreich abgeschlossen") || data.log.includes("❌ Systemfehler")) {
125+
clearInterval(logInterval);
126+
terminal.innerText += "\n\n> [Live Monitor automatisch gestoppt]";
127+
terminal.scrollTop = terminal.scrollHeight;
128+
}
122129
}
123130
})
124131
.catch(error => {

Analysis/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ def cleanup_processes_on_exit():
159159
print(f"Fehler beim Aufräumen: {e}")
160160

161161
# Registriert die Aufräum-Funktion beim Start von Django
162-
atexit.register(cleanup_processes_on_exit)
162+
if 'runserver' in sys.argv:
163+
atexit.register(cleanup_processes_on_exit)
163164

164165
# --- Die angepasste Index-View ---
165166

Lab_Dash/dash_apps/MFP_Dashboard.py

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -237,48 +237,53 @@ def get_data(entry_id):
237237
# =========================================================
238238

239239
@app.callback(
240-
[Output('ai-analysis-status', 'children'), Output('log-interval', 'disabled'), Output('ai-log-output-wrapper', 'style')],
241-
[Input('run-ai-btn', 'n_clicks')],
240+
[Output('ai-analysis-status', 'children'),
241+
Output('log-interval', 'disabled'),
242+
Output('ai-log-output-wrapper', 'style'),
243+
Output('ai-log-output', 'children')],
244+
[Input('run-ai-btn', 'n_clicks'), Input('log-interval', 'n_intervals')],
242245
[State('entry-id', 'data')]
243246
)
244-
def start_ai_analysis(n_clicks, entry_id):
245-
if n_clicks == 0 or not entry_id:
246-
return dash.no_update, dash.no_update, dash.no_update
247+
def handle_ai_analysis(n_clicks, n_intervals, entry_id):
248+
ctx = dash.callback_context
249+
if not ctx.triggered:
250+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
247251

248-
try:
249-
analysis = MFPAnalysis.objects.get(Entry_id=entry_id)
250-
dia = getattr(analysis, 'Particle_Diameter', 51)
251-
minmass = getattr(analysis, 'Threshold', 0.05)
252-
253-
threading.Thread(target=run_cellpose_cement_analysis, args=(entry_id, dia, minmass, None, None, 'all', None)).start()
254-
255-
style = {'margin': '10px', 'padding': '10px', 'backgroundColor': '#eef2f5', 'borderRadius': '5px', 'maxHeight': '150px', 'overflowY': 'auto', 'fontFamily': 'monospace', 'fontSize': '12px', 'display': 'block'}
256-
257-
return html.Div("🚀 KI-Analyse läuft im Hintergrund...", style={'color': 'blue', 'fontWeight': 'bold'}), False, style
258-
except Exception as e:
259-
return html.Div(f"❌ Fehler: {str(e)}", style={'color': 'red', 'fontWeight': 'bold'}), dash.no_update, dash.no_update
260-
261-
@app.callback(
262-
Output('ai-log-output', 'children'),
263-
[Input('log-interval', 'n_intervals')],
264-
[State('entry-id', 'data')]
265-
)
266-
def update_log(n, entry_id):
267-
if not entry_id: return dash.no_update
268-
log_file = f"/tmp/cellpose_log_{entry_id}.txt"
269-
if os.path.exists(log_file):
252+
trigger_id = ctx.triggered[0]['prop_id'].split('.')[0]
253+
254+
# FALL 1: Der Start-Button wurde gedrückt
255+
if trigger_id == 'run-ai-btn':
256+
if n_clicks == 0 or not entry_id:
257+
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
270258
try:
271-
with open(log_file, "r") as f:
272-
lines = f.readlines()
273-
return html.Div([
274-
html.Div(line, style={
275-
'color': 'green' if '✅' in line else ('red' if '❌' in line else ('orange' if '⚠️' in line else 'black')),
276-
'marginBottom': '2px'
277-
}) for line in lines if line.strip()
278-
])
279-
except:
280-
return html.Div("Lese Logdatei...")
281-
return html.Div("Warte auf Logdatei...")
259+
analysis = MFPAnalysis.objects.get(Entry_id=entry_id)
260+
dia = getattr(analysis, 'Particle_Diameter', 51)
261+
minmass = getattr(analysis, 'Threshold', 0.05)
262+
263+
threading.Thread(target=run_cellpose_cement_analysis, args=(entry_id, dia, minmass, None, None, 'all', None)).start()
264+
265+
style = {'margin': '10px', 'padding': '10px', 'backgroundColor': '#eef2f5', 'borderRadius': '5px', 'maxHeight': '150px', 'overflowY': 'auto', 'fontFamily': 'monospace', 'fontSize': '12px', 'display': 'block'}
266+
return html.Div("🚀 KI-Analyse läuft im Hintergrund...", style={'color': 'blue', 'fontWeight': 'bold'}), False, style, "Warte auf Logdatei..."
267+
except Exception as e:
268+
return html.Div(f"❌ Fehler: {str(e)}", style={'color': 'red', 'fontWeight': 'bold'}), dash.no_update, dash.no_update, dash.no_update
269+
270+
# FALL 2: Das Intervall holt sich neue Log-Updates
271+
elif trigger_id == 'log-interval':
272+
if not entry_id: return dash.no_update, dash.no_update, dash.no_update, dash.no_update
273+
log_file = f"/tmp/cellpose_log_{entry_id}.txt"
274+
if os.path.exists(log_file):
275+
try:
276+
with open(log_file, "r") as f:
277+
lines = f.readlines()
278+
log_divs = [html.Div(line, style={'color': 'green' if '✅' in line else ('red' if '❌' in line else ('orange' if '⚠️' in line else 'black')), 'marginBottom': '2px'}) for line in lines if line.strip()]
279+
text_content = "".join(lines)
280+
if "✅ Analyse erfolgreich abgeschlossen" in text_content or "❌ Systemfehler" in text_content:
281+
status = html.Div("✅ Analyse fertig! Lade die Seite neu (🔄 Reload).", style={'color': 'green', 'fontWeight': 'bold'})
282+
return status, True, dash.no_update, log_divs
283+
else:
284+
return dash.no_update, dash.no_update, dash.no_update, log_divs
285+
except: return dash.no_update, dash.no_update, dash.no_update, html.Div("Lese Logdatei...")
286+
return dash.no_update, dash.no_update, dash.no_update, html.Div("Warte auf Logdatei...")
282287

283288
@app.callback(
284289
[Output('entry-id', 'data'), Output('loading-status', 'children'),
@@ -509,8 +514,9 @@ def update_glob(sel, metric, tab, eid):
509514
if col not in tracks.columns and col == 'radius_cellpose' and 'radius' in tracks.columns:
510515
col = 'radius'
511516

512-
df = tracks[tracks['particle'].isin(sel)] if sel else tracks
513-
_, t_unit = General.get_smart_time(df['time'])
517+
df = tracks[tracks['particle'].isin(sel)].copy() if sel else tracks.copy()
518+
scaled_time, t_unit = General.get_smart_time(df['time'])
519+
df['scaled_time'] = scaled_time
514520

515521
# --- SPAGHETTI PLOT ---
516522
fig_s = go.Figure()
@@ -519,7 +525,7 @@ def update_glob(sel, metric, tab, eid):
519525

520526
for p in uids[:limit]:
521527
d = df[df['particle'] == p].sort_values('time')
522-
t_vals, _ = General.get_smart_time(d['time'])
528+
t_vals = d['scaled_time']
523529

524530
y_vals = d[col].fillna(0) if col in d.columns else [0] * len(d)
525531
fig_s.add_trace(go.Scatter(
@@ -533,11 +539,11 @@ def update_glob(sel, metric, tab, eid):
533539

534540
# Durchschnittslinie (AVG)
535541
if col in df.columns:
536-
avg = df.groupby('frame').agg({'time': 'first', col: 'mean'})
542+
avg = df.groupby('frame').agg({'scaled_time': 'first', col: 'mean'})
537543
else:
538-
avg = df.groupby('frame').agg({'time': 'first'})
544+
avg = df.groupby('frame').agg({'scaled_time': 'first'})
539545
avg[col] = 0
540-
avg_t, _ = General.get_smart_time(avg['time'])
546+
avg_t = avg['scaled_time']
541547

542548
fig_s.add_trace(go.Scatter(
543549
x=avg_t, y=avg[col],

0 commit comments

Comments
 (0)