duongthienz commited on
Commit
56bc4e0
·
verified ·
1 Parent(s): 759b69a

Add Population tab and convert .csv to .xml (May need more adjust)

Browse files
Files changed (1) hide show
  1. state.py +78 -80
state.py CHANGED
@@ -29,20 +29,6 @@ import utils
29
  verbosity = 4 # 0=None 1=Low 2=Medium 3=High 4=Debug
30
 
31
  def printV(message, level):
32
- '''
33
- Logging function to print based on verbosity level
34
-
35
- ...
36
-
37
- Parameters
38
- ----------
39
- message : str
40
- Message to display
41
- level : int
42
- Target verbosity level of message; 0=None 1=Low 2=Medium 3=High 4=Debug
43
- '''
44
- # Read from global value above this function
45
- global verbosity
46
  if verbosity >= level:
47
  print(message)
48
 
@@ -62,6 +48,7 @@ def init_session_state():
62
  "removeCategory": None,
63
  "resetResult": False,
64
  "unusedSpeakers": {}, # {filename: [speaker, ...]}
 
65
  "file_names": [],
66
  "valid_files": [],
67
  "file_paths": {}, # {filename: path}
@@ -104,54 +91,96 @@ def convert_df(df):
104
  return df.to_csv(index=False).encode("utf-8")
105
 
106
 
107
- def build_all_csv_zip():
108
- """Build an in-memory ZIP containing one CSV per analyzed file.
109
-
110
- Applies the same transformations as the single-file download:
111
- drop Task, rename Resource -> Speaker, sort by Start, add Role.
112
- Returns raw ZIP bytes ready for st.download_button.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  """
114
- import io
115
- import zipfile
116
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  buf = io.BytesIO()
118
  with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
119
  for fname, result in st.session_state.results.items():
120
  if len(result) != 2:
121
  continue
122
  try:
123
- annotation, _ = result
124
- currDF, _ = su.annotationToSimpleDataFrame(annotation)
125
-
126
- # Add Role column against raw SPEAKER_## labels BEFORE renames
127
- raw_to_role = {
128
- token.split(": ", 1)[1]: st.session_state.categories[i]
129
- for i, tokens in enumerate(st.session_state.categorySelect)
130
- for token in tokens
131
- if token.startswith(f"{fname}: ")
132
- }
133
- currDF = currDF.copy()
134
- currDF["Role"] = currDF["Resource"].map(raw_to_role).fillna("")
135
-
136
- # Apply speaker renames after Role is set
137
- renames = st.session_state.speakerRenames.get(fname, {})
138
- if "Resource" in currDF.columns:
139
- currDF["Resource"] = currDF["Resource"].apply(
140
- lambda s: renames.get(s, s)
141
- )
142
- currDF = currDF.drop(columns=["Task"], errors="ignore")
143
- currDF = currDF.rename(columns={"Resource": "Speaker"})
144
- if "Start" in currDF.columns:
145
- currDF = currDF.sort_values("Start").reset_index(drop=True)
146
 
 
 
 
 
 
 
 
 
 
147
  plain_name = fname.rsplit(".", 1)[0]
148
  zf.writestr(
149
  f"sonogram-analysis-{plain_name}.csv",
150
- currDF.to_csv(index=False)
151
  )
152
  except Exception as e:
153
  print(f"build_all_csv_zip: skipping {fname} — {e}")
154
-
155
  buf.seek(0)
156
  return buf.read()
157
 
@@ -161,37 +190,19 @@ def build_all_csv_zip():
161
  # ---------------------------------------------------------------------------
162
 
163
  def addCategory():
164
- '''
165
- Create new category
166
- '''
167
- # Clean up input
168
  new = st.session_state.categoryInput.strip()
169
  if not new:
170
  return
171
- # Notify user
172
  st.toast(f"Adding {new}")
173
- # Update all relevant session_state variables
174
  st.session_state.categories.append(new)
175
  st.session_state.categorySelect.append([])
176
  st.session_state.pop(f"multiselect_{new}", None)
177
- # Reset category input field
178
  st.session_state.categoryInput = ""
179
 
180
 
181
  def removeCategory(index):
182
- '''
183
- Remove category
184
-
185
- Parameters
186
- ----------
187
- index : int
188
- Index of category to be removed
189
- '''
190
- # Get name of category
191
  name = st.session_state.categories[index]
192
- # Notify user
193
  st.toast(f"Removing {name}")
194
- # Update all relevant session_state variables
195
  st.session_state.pop(f"multiselect_{name}", None)
196
  del st.session_state.categories[index]
197
  del st.session_state.categorySelect[index]
@@ -401,9 +412,6 @@ def on_grename_change(idx, token_display_map=None):
401
  # ---------------------------------------------------------------------------
402
 
403
  def updateMultiSelect():
404
- '''
405
- Updates multi-select field
406
- '''
407
  fileName = st.session_state["select_currFile"]
408
  st.session_state.resetResult = True
409
  result = st.session_state.results.get(fileName)
@@ -645,21 +653,11 @@ def build_table_df(displayDF):
645
  # ---------------------------------------------------------------------------
646
 
647
  def analyze(inFileName):
648
- """
649
- Compute and store all summary DataFrames for inFileName.
650
-
651
- Updates all session_state variables based on stored diarization results
652
-
653
- Parameters
654
- ----------
655
- inFileName : str
656
- Name of file to analyze
657
- """
658
  try:
659
  printV(f"Start analyzing {inFileName}", 4)
660
  st.session_state.resetResult = False
661
 
662
- # If file has not been processed by Sonogram, then end
663
  if not (
664
  inFileName in st.session_state.results
665
  and inFileName in st.session_state.summaries
 
29
  verbosity = 4 # 0=None 1=Low 2=Medium 3=High 4=Debug
30
 
31
  def printV(message, level):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  if verbosity >= level:
33
  print(message)
34
 
 
48
  "removeCategory": None,
49
  "resetResult": False,
50
  "unusedSpeakers": {}, # {filename: [speaker, ...]}
51
+ "studentPopulations": {}, # {filename: int or None}
52
  "file_names": [],
53
  "valid_files": [],
54
  "file_paths": {}, # {filename: path}
 
91
  return df.to_csv(index=False).encode("utf-8")
92
 
93
 
94
+ def _build_analysis_df(fname):
95
+ """Build the cleaned analysis DataFrame for a single file (shared logic)."""
96
+ annotation, _ = st.session_state.results[fname]
97
+ currDF, _ = su.annotationToSimpleDataFrame(annotation)
98
+ raw_to_role = {
99
+ token.split(": ", 1)[1]: st.session_state.categories[i]
100
+ for i, tokens in enumerate(st.session_state.categorySelect)
101
+ for token in tokens
102
+ if token.startswith(f"{fname}: ")
103
+ }
104
+ currDF = currDF.copy()
105
+ currDF["Role"] = currDF["Resource"].map(raw_to_role).fillna("")
106
+ renames = st.session_state.speakerRenames.get(fname, {})
107
+ if "Resource" in currDF.columns:
108
+ currDF["Resource"] = currDF["Resource"].apply(lambda s: renames.get(s, s))
109
+ currDF = currDF.drop(columns=["Task"], errors="ignore")
110
+ currDF = currDF.rename(columns={"Resource": "Speaker"})
111
+ if "Start" in currDF.columns:
112
+ currDF = currDF.sort_values("Start").reset_index(drop=True)
113
+ return currDF
114
+
115
+
116
+ def build_xml_download(fname):
117
+ """Build XML bytes for a single analyzed file.
118
+
119
+ Structure:
120
+ <recording filename="..." student_population="N">
121
+ <segment speaker="..." role="..." start="..." end="..."/>
122
+ ...
123
+ </recording>
124
  """
125
+ import xml.etree.ElementTree as ET
126
+ df = _build_analysis_df(fname)
127
+ population = st.session_state.studentPopulations.get(fname)
128
+ plain_name = fname.rsplit(".", 1)[0]
129
+
130
+ root = ET.Element("recording")
131
+ root.set("filename", plain_name)
132
+ root.set("student_population",
133
+ str(population) if population is not None else "")
134
+
135
+ for _, row in df.iterrows():
136
+ seg = ET.SubElement(root, "segment")
137
+ seg.set("speaker", str(row.get("Speaker", "")))
138
+ seg.set("role", str(row.get("Role", "")))
139
+ seg.set("start", str(row.get("Start", "")))
140
+ seg.set("end", str(row.get("Finish", row.get("End", ""))))
141
+
142
+ tree = ET.ElementTree(root)
143
+ buf = io.BytesIO()
144
+ ET.indent(tree, space=" ")
145
+ tree.write(buf, encoding="unicode", xml_declaration=True)
146
+ return buf.getvalue().encode("utf-8")
147
+
148
+
149
+ def build_all_xml_zip():
150
+ """Build an in-memory ZIP containing one XML per analyzed file."""
151
  buf = io.BytesIO()
152
  with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
153
  for fname, result in st.session_state.results.items():
154
  if len(result) != 2:
155
  continue
156
  try:
157
+ plain_name = fname.rsplit(".", 1)[0]
158
+ zf.writestr(
159
+ f"sonogram-analysis-{plain_name}.xml",
160
+ build_xml_download(fname),
161
+ )
162
+ except Exception as e:
163
+ print(f"build_all_xml_zip: skipping {fname} {e}")
164
+ buf.seek(0)
165
+ return buf.read()
166
+
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
+ def build_all_csv_zip():
169
+ """Build an in-memory ZIP containing one CSV per analyzed file."""
170
+ buf = io.BytesIO()
171
+ with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
172
+ for fname, result in st.session_state.results.items():
173
+ if len(result) != 2:
174
+ continue
175
+ try:
176
+ df = _build_analysis_df(fname)
177
  plain_name = fname.rsplit(".", 1)[0]
178
  zf.writestr(
179
  f"sonogram-analysis-{plain_name}.csv",
180
+ df.to_csv(index=False)
181
  )
182
  except Exception as e:
183
  print(f"build_all_csv_zip: skipping {fname} — {e}")
 
184
  buf.seek(0)
185
  return buf.read()
186
 
 
190
  # ---------------------------------------------------------------------------
191
 
192
  def addCategory():
 
 
 
 
193
  new = st.session_state.categoryInput.strip()
194
  if not new:
195
  return
 
196
  st.toast(f"Adding {new}")
 
197
  st.session_state.categories.append(new)
198
  st.session_state.categorySelect.append([])
199
  st.session_state.pop(f"multiselect_{new}", None)
 
200
  st.session_state.categoryInput = ""
201
 
202
 
203
  def removeCategory(index):
 
 
 
 
 
 
 
 
 
204
  name = st.session_state.categories[index]
 
205
  st.toast(f"Removing {name}")
 
206
  st.session_state.pop(f"multiselect_{name}", None)
207
  del st.session_state.categories[index]
208
  del st.session_state.categorySelect[index]
 
412
  # ---------------------------------------------------------------------------
413
 
414
  def updateMultiSelect():
 
 
 
415
  fileName = st.session_state["select_currFile"]
416
  st.session_state.resetResult = True
417
  result = st.session_state.results.get(fileName)
 
653
  # ---------------------------------------------------------------------------
654
 
655
  def analyze(inFileName):
656
+ """Compute and store all summary DataFrames for inFileName."""
 
 
 
 
 
 
 
 
 
657
  try:
658
  printV(f"Start analyzing {inFileName}", 4)
659
  st.session_state.resetResult = False
660
 
 
661
  if not (
662
  inFileName in st.session_state.results
663
  and inFileName in st.session_state.summaries