mohdelgaar commited on
Commit
4eafe9d
·
1 Parent(s): f9ad666

Align Space with local HyperText config and R2-only persistence.

Browse files

Switch to sectioned clinical-note labeling, drop baked-in seed DB in favor of R2 sync, and update docs for aggregated pre-annotations.

Files changed (4) hide show
  1. Dockerfile +1 -2
  2. README.md +4 -5
  3. config/label_studio.xml +87 -18
  4. scripts/entrypoint.sh +1 -16
Dockerfile CHANGED
@@ -10,11 +10,10 @@ COPY scripts/r2_storage.py /app/scripts/r2_storage.py
10
  COPY config/label_studio.xml /app/config/label_studio.xml
11
 
12
  RUN chmod +x /entrypoint.sh \
13
- && mkdir -p /label-studio/data /label-studio/logs /app/seed/label-studio /app/scripts /app/config
14
 
15
  ENV LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
16
  LABEL_STUDIO_LOG_DIR=/label-studio/logs \
17
- LABEL_STUDIO_SEED_DIR=/app/seed/label-studio \
18
  STORAGE_PERSISTENCE=1 \
19
  PYTHONPATH=/app/scripts \
20
  R2_SYNC_ON_START=1 \
 
10
  COPY config/label_studio.xml /app/config/label_studio.xml
11
 
12
  RUN chmod +x /entrypoint.sh \
13
+ && mkdir -p /label-studio/data /label-studio/logs /app/scripts /app/config
14
 
15
  ENV LABEL_STUDIO_BASE_DATA_DIR=/label-studio/data \
16
  LABEL_STUDIO_LOG_DIR=/label-studio/logs \
 
17
  STORAGE_PERSISTENCE=1 \
18
  PYTHONPATH=/app/scripts \
19
  R2_SYNC_ON_START=1 \
README.md CHANGED
@@ -13,7 +13,7 @@ app_port: 8080
13
 
14
  # AntioBioNLP — expert adjudication
15
 
16
- Label Studio Space for **human adjudication** of antibiotic decisions on the MIMIC sepsis pilot cohort, with **three overlapping LLM pre-annotation layers** (Qwen3.6-27B, Gemma-4-31B-it, Qwen3.6-35B-A3B) stored on **Cloudflare R2**.
17
 
18
  On boot the Space **pulls your Label Studio database from R2** (project, tasks, user accounts) and **pushes** changes back on shutdown / every 5 minutes.
19
 
@@ -50,16 +50,15 @@ Optional:
50
 
51
  | Prefix | Contents |
52
  |--------|----------|
53
- | `tasks/` | `tasks.json`, `notes_for_annotation.parquet` |
54
- | `llm/<model>/` | `llm_annotations.jsonl` per model |
55
  | `label-studio/` | SQLite DB, media, exports (synced from the Space) |
56
 
57
  ## Using the UI
58
 
59
  1. Log in with the **same email/password** you created locally.
60
  2. Open project **Antibiotic decision adjudication**.
61
- 3. Use **Settings Predictions** to toggle Qwen / Gemma / Qwen-35B layers.
62
- 4. Span colors = decision type; region **meta** shows LLM name and rationale categories.
63
 
64
  Public signup is disabled (`LABEL_STUDIO_DISABLE_SIGNUP_WITHOUT_LINK=true`). Invite collaborators from **Organization → Invite**.
65
 
 
13
 
14
  # AntioBioNLP — expert adjudication
15
 
16
+ Label Studio Space for **human adjudication** of antibiotic decisions on the MIMIC sepsis pilot cohort, with **aggregated LLM pre-annotations** (majority vote across three models) on **Cloudflare R2**.
17
 
18
  On boot the Space **pulls your Label Studio database from R2** (project, tasks, user accounts) and **pushes** changes back on shutdown / every 5 minutes.
19
 
 
50
 
51
  | Prefix | Contents |
52
  |--------|----------|
53
+ | `tasks/` | `tasks.json`, `aggregated_annotations.jsonl`, `notes_for_annotation.parquet` |
 
54
  | `label-studio/` | SQLite DB, media, exports (synced from the Space) |
55
 
56
  ## Using the UI
57
 
58
  1. Log in with the **same email/password** you created locally.
59
  2. Open project **Antibiotic decision adjudication**.
60
+ 3. Enable **aggregated (majority vote)** under **Settings Predictions**.
61
+ 4. Span colors = decision type; region **meta** shows per-model votes and rationale categories.
62
 
63
  Public signup is disabled (`LABEL_STUDIO_DISABLE_SIGNUP_WITHOUT_LINK=true`). Invite collaborators from **Organization → Invite**.
64
 
config/label_studio.xml CHANGED
@@ -1,21 +1,90 @@
1
  <View>
2
- <Header value="Antibiotic decision adjudication (expert)"/>
3
- <View style="display:flex; gap:12px; margin-bottom:8px; flex-wrap:wrap;">
4
- <Text name="note_id" value="Note ID: $note_id"/>
5
- <Text name="subject" value="Subject: $subject_id | HADM: $hadm_id"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  </View>
7
- <Header value="LLM pre-annotations" size="4"/>
8
- <Text name="legend" value="Toggle prediction layers (Settings → Predictions) to compare models. Colors encode decision type; meta shows rationale categories per span."/>
9
- <Text name="text" value="$text"/>
10
- <Labels name="decision" toName="text" showInline="true">
11
- <Label value="initiation" background="#2563eb"/>
12
- <Label value="escalation" background="#dc2626"/>
13
- <Label value="de-escalation" background="#059669"/>
14
- <Label value="discontinuation" background="#7c3aed"/>
15
- <Label value="within_class_switching" background="#d97706"/>
16
- <Label value="other" background="#64748b"/>
17
- <Label value="none" background="#94a3b8"/>
18
- </Labels>
19
- <Header value="Expert notes" size="4"/>
20
- <TextArea name="expert_notes" toName="text" rows="3" placeholder="Quality flags, adjudication notes…" maxSubmissions="1"/>
21
  </View>
 
1
  <View>
2
+ <Style>
3
+ .labeling-clinical-layout {
4
+ display: flex;
5
+ gap: 1em;
6
+ align-items: stretch;
7
+ max-height: calc(100vh - 190px);
8
+ min-height: 560px;
9
+ font-family: Arial, sans-serif;
10
+ }
11
+
12
+ .labeling-label-panel {
13
+ width: 220px;
14
+ min-width: 220px;
15
+ flex-shrink: 0;
16
+ padding: 1em;
17
+ background: #f8fafc;
18
+ border: 1px solid #d6dde8;
19
+ border-radius: 4px;
20
+ position: sticky;
21
+ top: 0;
22
+ align-self: flex-start;
23
+ max-height: calc(100vh - 190px);
24
+ overflow-y: auto;
25
+ }
26
+
27
+ .labeling-note-panel {
28
+ flex: 1;
29
+ min-width: 0;
30
+ overflow-y: auto;
31
+ overflow-x: hidden;
32
+ padding: 0.75em 1em;
33
+ border: 1px solid #d6dde8;
34
+ border-radius: 4px;
35
+ background: #fff;
36
+ }
37
+
38
+ .clinical-note {
39
+ max-width: 1120px;
40
+ margin: 0 auto;
41
+ color: #111827;
42
+ background: #fff;
43
+ font-family: Arial, sans-serif;
44
+ }
45
+
46
+ .note-document {
47
+ min-width: 0;
48
+ }
49
+
50
+ .note-section {
51
+ margin: 0 0 22px;
52
+ }
53
+
54
+ .note-section-title {
55
+ font-size: 13px;
56
+ font-weight: 700;
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.02em;
59
+ color: #1f2937;
60
+ padding-bottom: 5px;
61
+ margin-bottom: 8px;
62
+ border-bottom: 1px solid #d9dee8;
63
+ }
64
+
65
+ .note-body {
66
+ white-space: pre-wrap;
67
+ font-size: 14px;
68
+ line-height: 1.42;
69
+ }
70
+ </Style>
71
+
72
+ <View className="labeling-clinical-layout">
73
+ <View className="labeling-label-panel">
74
+ <Header value="Decision type" size="4"/>
75
+ <HyperTextLabels name="decision" toName="note" showInline="false">
76
+ <Label value="initiation" background="#2563eb"/>
77
+ <Label value="escalation" background="#f97316"/>
78
+ <Label value="de-escalation" background="#16a34a"/>
79
+ <Label value="discontinuation" background="#7c3aed"/>
80
+ <Label value="within_class_switching" background="#0891b2"/>
81
+ <Label value="other" background="#64748b"/>
82
+ <Label value="undetermined" background="#dc2626"/>
83
+ </HyperTextLabels>
84
+ </View>
85
+
86
+ <View className="labeling-note-panel">
87
+ <HyperText name="note" value="$note_html" inline="true" clickableLinks="false" saveTextResult="yes" granularity="word"/>
88
+ </View>
89
  </View>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  </View>
scripts/entrypoint.sh CHANGED
@@ -3,30 +3,15 @@ set -euo pipefail
3
 
4
  DATA_DIR="${LABEL_STUDIO_BASE_DATA_DIR:-/label-studio/data}"
5
  LOG_DIR="${LABEL_STUDIO_LOG_DIR:-/label-studio/logs}"
6
- SEED_DIR="${LABEL_STUDIO_SEED_DIR:-/app/seed/label-studio}"
7
  mkdir -p "$DATA_DIR" "$LOG_DIR"
8
 
9
  export PYTHONPATH="/app/scripts:${PYTHONPATH:-}"
10
 
11
- # 1) Restore from R2 (primary persistence)
12
  if [[ "${R2_SYNC_ON_START:-1}" == "1" ]] && [[ -n "${R2_ACCESS_KEY_ID:-}" ]]; then
13
  python3 /app/scripts/sync_label_studio_r2.py pull --data-dir "$DATA_DIR" || true
14
  fi
15
 
16
- # 2) First boot: copy baked-in seed (project, tasks, user accounts)
17
- if [[ ! -f "$DATA_DIR/label_studio.sqlite3" ]] && [[ -d "$SEED_DIR" ]]; then
18
- echo "Seeding Label Studio data from $SEED_DIR"
19
- shopt -s dotglob nullglob
20
- for item in "$SEED_DIR"/*; do
21
- base="$(basename "$item")"
22
- [[ "$base" == ".env" ]] && continue
23
- cp -a "$item" "$DATA_DIR/"
24
- done
25
- shopt -u dotglob nullglob
26
- fi
27
-
28
- # SECRET_KEY: prefer Space secret over seed .env
29
- if [[ -n "${SECRET_KEY:-}" ]] && [[ -d "$DATA_DIR" ]]; then
30
  printf 'SECRET_KEY=%s\n' "$SECRET_KEY" > "$DATA_DIR/.env"
31
  fi
32
 
 
3
 
4
  DATA_DIR="${LABEL_STUDIO_BASE_DATA_DIR:-/label-studio/data}"
5
  LOG_DIR="${LABEL_STUDIO_LOG_DIR:-/label-studio/logs}"
 
6
  mkdir -p "$DATA_DIR" "$LOG_DIR"
7
 
8
  export PYTHONPATH="/app/scripts:${PYTHONPATH:-}"
9
 
 
10
  if [[ "${R2_SYNC_ON_START:-1}" == "1" ]] && [[ -n "${R2_ACCESS_KEY_ID:-}" ]]; then
11
  python3 /app/scripts/sync_label_studio_r2.py pull --data-dir "$DATA_DIR" || true
12
  fi
13
 
14
+ if [[ -n "${SECRET_KEY:-}" ]]; then
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  printf 'SECRET_KEY=%s\n' "$SECRET_KEY" > "$DATA_DIR/.env"
16
  fi
17