Spaces:
Sleeping
Sleeping
Add Population tab and convert .csv to .xml (May need more adjust)
Browse files
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
|
| 108 |
-
"""Build
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
"""
|
| 114 |
-
import
|
| 115 |
-
|
| 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 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 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 |
-
|
| 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
|