Spaces:
Sleeping
Sleeping
File size: 46,165 Bytes
373e521 8cedd51 373e521 6a8a763 de384b8 6d79beb 9154b30 b3a9316 1b3f20e dcaa601 85b7ab1 9642585 85b7ab1 3a13b86 85b7ab1 b9e0896 6a8a763 0c3f343 19b30cf ac958ee b795064 ac958ee 347c02e 6a8a763 ef17bdc 6a8a763 a0cef41 6a8a763 373e521 85b7ab1 68956f7 92e7a87 68956f7 85b7ab1 0c3f343 85b7ab1 0c3f343 68956f7 f781cbe 0c3f343 68956f7 0c3f343 68956f7 0c3f343 f77e58d dcaa601 094a4d0 bbe1239 094a4d0 dcaa601 094a4d0 8dd318d 094a4d0 8dd318d 094a4d0 ac958ee 094a4d0 14a443b 094a4d0 ac958ee 094a4d0 ac958ee 094a4d0 d1e1561 ac958ee 094a4d0 d1e1561 094a4d0 ac958ee 094a4d0 d218fde 094a4d0 3505359 094a4d0 e60ff5c 094a4d0 dcaa601 094a4d0 e60ff5c ac958ee 094a4d0 ac958ee 094a4d0 d218fde ac958ee 094a4d0 d08af54 094a4d0 a9f7003 094a4d0 ac958ee 094a4d0 bbe1239 094a4d0 ac958ee 8dd318d 094a4d0 0c3f343 094a4d0 043eae7 094a4d0 e0cfb45 dcaa601 094a4d0 dcaa601 094a4d0 dcaa601 85b7ab1 0d22830 75b5807 f71cef1 da5ffe1 75819cc da5ffe1 6a8a763 9cc4a0c e0cfb45 6a8a763 6d79beb beaf876 6d79beb 6a8a763 68956f7 ac9f0e5 094a4d0 6a8a763 2b3fa70 6a8a763 68956f7 6a8a763 6d79beb 6a8a763 6d79beb 094a4d0 611230a 094a4d0 83708c8 7b7ab75 19b30cf 094a4d0 2b3fa70 094a4d0 5f60195 094a4d0 85b7ab1 094a4d0 85b7ab1 094a4d0 14a443b 094a4d0 b823fb5 2b3fa70 b823fb5 094a4d0 2b3fa70 9cc4a0c 2b3fa70 9cc4a0c 094a4d0 14a443b 094a4d0 b823fb5 14a443b 8dd318d 14a443b 8dd318d 094a4d0 8dd318d 094a4d0 8dd318d c3f8543 2b3fa70 094a4d0 e0cfb45 cf16727 0bfd1fd cf16727 f71cef1 cf16727 8b1e6c6 56f8e5f cf16727 3330092 ac958ee cf16727 acbac84 094a4d0 d31fca1 ff96b8f ebf5b7d ff96b8f e0cfb45 ac958ee ff96b8f 14a443b ff96b8f ac958ee cd9f583 ac958ee ff96b8f 78a4b12 ff96b8f ac958ee ff96b8f 702041e ff96b8f ebf5b7d ac958ee ebf5b7d b173d18 ebf5b7d 6ab082f 3d37959 ebf5b7d ac958ee 3d37959 e0cfb45 ac958ee e0cfb45 3d37959 ac958ee 3d37959 ebf5b7d 27ee353 ebf5b7d 3d37959 3f63a03 5b7a490 347ca34 5b7a490 3f63a03 347ca34 3f63a03 3d37959 3f63a03 5b7a490 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 3d37959 b795064 3d37959 ac958ee e0cfb45 ac958ee e0cfb45 ac958ee e0cfb45 ac958ee ebf5b7d 27ee353 ebf5b7d 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 b795064 ac958ee e0cfb45 ac958ee e0cfb45 ac958ee ebf5b7d 27ee353 ebf5b7d 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 b795064 ac958ee e0cfb45 094a4d0 e0cfb45 094a4d0 e0cfb45 ac958ee e0cfb45 ff71e94 ac958ee e0cfb45 ebf5b7d 27ee353 ebf5b7d 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 b795064 ac958ee e0cfb45 702041e e0cfb45 ac958ee fc43084 3e840ca ebf5b7d 27ee353 ebf5b7d 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 b795064 ac958ee e0cfb45 ac958ee e0cfb45 ac958ee fc43084 3e840ca e0cfb45 ebf5b7d 27ee353 ebf5b7d 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 347ca34 3f63a03 347ca34 3f63a03 3d37959 3f63a03 b795064 ff96b8f ac958ee ff96b8f ac958ee e0cfb45 702041e e0cfb45 b795064 e0cfb45 b795064 e0cfb45 b795064 e0cfb45 b795064 ff96b8f 513424d 9cc4a0c 513424d 9cc4a0c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 |
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np
import torchaudio
import sonogram_utility as su
import time
import ParquetScheduler as ps
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
import copy
import datetime
import tempfile
import os
import shutil
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import torch
#import torch_xla.core.xla_model as xm
from pyannote.audio import Pipeline
from pyannote.core import Annotation, Segment, Timeline
from df.enhance import enhance, init_df
import datetime as dt
enableDenoise = False
earlyCleanup = True
# [None,Low,Medium,High,Debug]
# [0,1,2,3,4]
verbosity=4
config = {
'displayModeBar': True,
'modeBarButtonsToRemove':[],
}
def printV(message,verbosityLevel):
global verbosity
if verbosity>=verbosityLevel:
print(message)
@st.cache_data
def convert_df(df):
return df.to_csv(index=False).encode('utf-8')
def save_data(
config_dict: Dict[str,str], audio_paths: List[str], userid: str,
) -> None:
"""Save data, i.e. move audio to a new folder and send paths+config to scheduler."""
save_dir = PARQUET_DATASET_DIR / f"{userid}"
save_dir.mkdir(parents=True, exist_ok=True)
data = copy.deepcopy(config_dict)
# Add timestamp
data["timestamp"] = datetime.datetime.utcnow().isoformat()
# Copy and add audio
for i,p in enumerate(audio_paths):
name = f"{i:03d}"
dst_path = save_dir / f"{name}{Path(p).suffix}"
shutil.copyfile(p, dst_path)
data[f"audio_{name}"] = dst_path
# Send to scheduler
scheduler.append(data)
def processFile(filePath):
global attenLimDb
global gainWindow
global minimumGain
global maximumGain
print("Loading file")
waveformList, sampleRate = su.splitIntoTimeSegments(filePath,600)
print("File loaded")
enhancedWaveformList = []
if (enableDenoise):
print("Denoising")
for w in waveformList:
if (enableDenoise):
newW = enhance(dfModel,dfState,w,atten_lim_db=attenLimDB).detach().cpu()
enhancedWaveformList.append(newW)
else:
enhancedWaveformList.append(w)
if (enableDenoise):
print("Audio denoised")
waveformEnhanced = su.combineWaveforms(enhancedWaveformList)
if (earlyCleanup):
del enhancedWaveformList
print("Equalizing Audio")
waveform_gain_adjusted = su.equalizeVolume()(waveformEnhanced,sampleRate,gainWindow,minimumGain,maximumGain)
if (earlyCleanup):
del waveformEnhanced
print("Audio Equalized")
print("Detecting speakers")
annotations = pipeline({"waveform": waveform_gain_adjusted, "sample_rate": sampleRate})
print("Speakers Detected")
totalTimeInSeconds = int(waveform_gain_adjusted.shape[-1]/sampleRate)
print("Time in seconds calculated")
return annotations, totalTimeInSeconds
def addCategory():
newCategory = st.session_state.categoryInput
st.toast(f"Adding {newCategory}")
st.session_state[f'multiselect_{newCategory}'] = []
st.session_state.categories.append(newCategory)
st.session_state.categoryInput = ''
for resultGroup in st.session_state.categorySelect:
resultGroup.append([])
def removeCategory(index):
categoryName = st.session_state.categories[index]
st.toast(f"Removing {categoryName}")
del st.session_state[f'multiselect_{categoryName}']
del st.session_state[f'remove_{categoryName}']
del st.session_state.categories[index]
for resultGroup in st.session_state.categorySelect:
del resultGroup[index]
def updateCategoryOptions(resultIndex):
if st.session_state.resetResult:
#st.info(f"Skipping update of {resultIndex}")
return
#st.info(f"Updating result {resultIndex}")
#st.info(f"In update: {st.session_state.categorySelect}")
# Handle
_, currAnnotation, _ = st.session_state.results[currFileIndex]
speakerNames = currAnnotation.labels()
# Handle speaker category sidebars
unusedSpeakers = copy.deepcopy(speakerNames)
# Remove used speakers
for i, category in enumerate(st.session_state['categories']):
category_choices = copy.deepcopy(st.session_state[f'multiselect_{category}'])
st.session_state["categorySelect"][resultIndex][i] = category_choices
for sp in category_choices:
try:
unusedSpeakers.remove(sp)
except:
continue
st.session_state.unusedSpeakers[resultIndex] = unusedSpeakers
#st.info(f"After update: {st.session_state.categorySelect}")
def updateMultiSelect():
currFileIndex = file_names.index(st.session_state["select_currFile"])
st.session_state.resetResult = True
for i, category in enumerate(st.session_state['categories']):
st.session_state[f'multiselect_{category}'] = st.session_state['categorySelect'][currFileIndex][i]
def analyze(inFileName):
try:
print(f"Start analyzing {inFileName}")
st.session_state.resetResult = False
currFileIndex = file_names.index(inFileName)
print(f"Found at index {currFileIndex}")
if len(st.session_state.results) > currFileIndex and len(st.session_state.summaries) > currFileIndex and len(st.session_state.results[currFileIndex]) > 0:
printV(f'In if',4)
# Handle
currAnnotation, currTotalTime = st.session_state.results[currFileIndex]
speakerNames = currAnnotation.labels()
printV(f'Loaded results',4)
# Update other categories
unusedSpeakers = st.session_state.unusedSpeakers[currFileIndex]
categorySelections = st.session_state["categorySelect"][currFileIndex]
printV(f'Loaded speaker selections',4)
noVoice, oneVoice, multiVoice = su.calcSpeakingTypes(currAnnotation,currTotalTime)
sumNoVoice = su.sumTimes(noVoice)
sumOneVoice = su.sumTimes(oneVoice)
sumMultiVoice = su.sumTimes(multiVoice)
printV(f'Calculated speaking types',4)
df3 = pd.DataFrame(
{
"values": [sumNoVoice,
sumOneVoice,
sumMultiVoice],
"names": ["No Voice","One Voice","Multi Voice"],
}
)
df3.name = "df3"
st.session_state.summaries[currFileIndex]["df3"] = df3
printV(f'Set df3',4)
df4_dict = {}
nameList = st.session_state.categories
extraNames = []
valueList = [0 for i in range(len(nameList))]
extraValues = []
for sp in speakerNames:
foundSp = False
for i, categoryName in enumerate(nameList):
if sp in categorySelections[i]:
#st.info(categoryName)
valueList[i] += su.sumTimes(currAnnotation.subset([sp]))
foundSp = True
break
if foundSp:
continue
else:
extraNames.append(sp)
extraValues.append(su.sumTimes(currAnnotation.subset([sp])))
extraPairsSorted = sorted(zip(extraNames, extraValues), key=lambda pair: pair[0])
extraNames, extraValues = zip(*extraPairsSorted)
df4_dict = {
"values": valueList+list(extraValues),
"names": nameList+list(extraNames),
}
df4 = pd.DataFrame(data=df4_dict)
df4.name = "df4"
st.session_state.summaries[currFileIndex]["df4"] = df4
printV(f'Set df4',4)
speakerList,timeList = su.sumTimesPerSpeaker(oneVoice)
multiSpeakerList, multiTimeList = su.sumMultiTimesPerSpeaker(multiVoice)
summativeMultiSpeaker = sum(multiTimeList)
basePercentiles = [sumNoVoice/currTotalTime,
sumOneVoice/currTotalTime,
sumMultiVoice/currTotalTime
]
df5 = pd.DataFrame(
{
"ids" : ["NV","OV","MV"]+[f"OV_{i}" for i in range(len(speakerList))]
+[f"MV_{i}" for i in range(len(multiSpeakerList))],
"labels" : ["No Voice","One Voice","Multi Voice"] + speakerList + multiSpeakerList,
"parents" : ["","",""]+["OV" for i in range(len(speakerList))]
+["MV" for i in range(len(multiSpeakerList))],
"parentNames" : ["Total","Total","Total"]+["One Voice" for i in range(len(speakerList))]
+["Multi Voice" for i in range(len(multiSpeakerList))],
"values" : [sumNoVoice,
sumOneVoice,
sumMultiVoice,
] + timeList + multiTimeList,
"valueStrings" : [su.timeToString(sumNoVoice),
su.timeToString(sumOneVoice),
su.timeToString(sumMultiVoice),
] + su.timeToString(timeList) + su.timeToString(multiTimeList),
"percentiles" : [basePercentiles[0]*100,
basePercentiles[1]*100,
basePercentiles[2]*100] +
[(t*100) / sumOneVoice * basePercentiles[1] for t in timeList] +
[(t*100) / summativeMultiSpeaker * basePercentiles[2] for t in multiTimeList],
"parentPercentiles" : [basePercentiles[0]*100,
basePercentiles[1]*100,
basePercentiles[2]*100] +
[(t*100) / sumOneVoice for t in timeList] +
[(t*100) / summativeMultiSpeaker for t in multiTimeList],
}
)
df5.name = "df5"
st.session_state.summaries[currFileIndex]["df5"] = df5
printV(f'Set df5',4)
speakers_dataFrame,speakers_times = su.annotationToDataFrame(currAnnotation)
st.session_state.summaries[currFileIndex]["speakers_dataFrame"] = speakers_dataFrame
st.session_state.summaries[currFileIndex]["speakers_times"] = speakers_times
df2_dict = {
"values":[100*t/currTotalTime for t in df4_dict["values"]],
"names":df4_dict["names"]
}
df2 = pd.DataFrame(df2_dict)
st.session_state.summaries[currFileIndex]["df2"] = df2
printV(f'Set df2',4)
except ValueError as e:
print(f"Value Error: {e}")
pass
#----------------------------------------------------------------------------------------------------------------------
torch.classes.__path__ = [os.path.join(torch.__path__[0], torch.classes.__file__)]
PARQUET_DATASET_DIR = Path("parquet_dataset")
PARQUET_DATASET_DIR.mkdir(parents=True,exist_ok=True)
sample_data = [f"CHEM1402_gt/24F_CHEM1402_Night_Class_Week_{i}_gt.rttm" for i in range(1,11)]
scheduler = ps.ParquetScheduler(repo_id="Sonogram/SampleDataset")
secondDifference = 5
gainWindow = 4
minimumGain = -45
maximumGain = -5
attenLimDB = 3
isGPU = False
try:
raise(RuntimeError("Not an error"))
#device = xm.xla_device()
print("TPU is available.")
isGPU = True
except RuntimeError as e:
print(f"TPU is not available: {e}")
# Fallback to CPU or other devices if needed
isGPU = torch.cuda.is_available()
device = torch.device("cuda" if isGPU else "cpu")
print(f"Using {device} instead.")
#device = xm.xla_device()
if (enableDenoise):
# Instantiate and prepare model for training.
dfModel, dfState, _ = init_df(model_base_dir="DeepFilterNet3")
dfModel.to(device)#torch.device("cuda"))
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
pipeline.to(device)#torch.device("cuda"))
# Store results for viewing and further processing
# Long-range usage
if 'results' not in st.session_state:
st.session_state.results = []
if 'summaries' not in st.session_state:
st.session_state.summaries = []
if 'categories' not in st.session_state:
st.session_state.categories = []
st.session_state.categorySelect = []
# Single Use
if 'removeCategory' not in st.session_state:
st.session_state.removeCategory = None
if 'resetResult' not in st.session_state:
st.session_state.resetResult = False
# Specific to target file
if 'unusedSpeakers' not in st.session_state:
st.session_state.unusedSpeakers = []
if 'file_names' not in st.session_state:
st.session_state.file_names = []
if 'showSummary' not in st.session_state:
st.session_state.showSummary = 'No'
#st.set_page_config(layout="wide")
st.title("Instructor Support Tool")
if not isGPU:
st.warning("TOOL CURRENTLY USING CPU, ANALYSIS EXTREMELY SLOW")
st.write('If you would like to see a sample result generated from real classroom audio, use the sidebar on the left and press "Load Demo Example"')
st.write('Keep in mind that this is a very early draft of the tool. Please be patient with any bugs/errors, and email Connor Young at czyoung@ualr.edu if you need help using the tool!')
st.divider()
st.write("Would you like additional data, charts, or features? We would love to hear more from you [about our project!](https://forms.gle/A32CdfGYSZoMPyyX9)")
st.write("If you would like to learn more or work with us, please contact Dr. Mark Baillie at mtbaillie@ualr.edu")
uploaded_file_paths = st.file_uploader("Upload an audio of classroom activity to analyze", accept_multiple_files=True)
supported_file_types = ('.wav','.mp3','.mp4','.txt','.rttm','.csv')
viewChoices = ["Voice Categories","Custom Categories","Detailed Voice Categories","Voice Category Treemap","Speaker Timeline","Time per Speaker"]
valid_files = []
file_paths = []
currDF = None
temp_dir = tempfile.mkdtemp()
if uploaded_file_paths is not None:
print("Found file paths")
valid_files = []
file_paths = []
file_names = []
# Reset valid_files?
for uploaded_file in uploaded_file_paths:
if not uploaded_file.name.lower().endswith(supported_file_types):
st.error('File must be of type: {}'.format(supported_file_types))
uploaded_file = None
else:
print(f"Valid file: {uploaded_file.name}")
if uploaded_file not in valid_files:
path = os.path.join(temp_dir, uploaded_file.name)
with open(path, "wb") as f:
f.write(uploaded_file.getvalue())
valid_files.append(uploaded_file)
file_paths.append(path)
# Save valid file names
if len(valid_files) > 0:
file_names = [f.name for f in valid_files]
while (len(st.session_state.results) < len(valid_files)):
st.session_state.results.append([])
while (len(st.session_state.summaries) < len(valid_files)):
st.session_state.summaries.append([])
while (len(st.session_state.unusedSpeakers) < len(valid_files)):
st.session_state.unusedSpeakers.append([])
while (len(st.session_state.categorySelect) < len(valid_files)):
tempCategories = [[] for cat in st.session_state.categories]
st.session_state.categorySelect.append(tempCategories)
while (len(st.session_state.summaries) < len(valid_files)):
st.session_state.summaries.append([])
st.session_state.file_names = file_names
file_names = st.session_state.file_names
if len(file_names) == 0:
st.text("Upload file(s) to enable analysis")
else:
if st.button("Analyze All New Audio",key=f"button_all"):
if len(valid_files) == 0:
st.error('Upload file(s) first!')
else:
print("Start analyzing")
start_time = time.time()
totalFiles = len(valid_files)
for i in range(totalFiles):
if len(st.session_state.results) > i and len(st.session_state.results[i]) > 0:
continue
# Text files use sample data
if file_paths[i].lower().endswith('.txt'):
with st.spinner(text=f'Loading Demo File {i+1} of {totalFiles}'):
# RTTM load as filler
speakerList, annotations = su.loadAudioTXT(file_paths[i])
printV(annotations,4)
# Approximate total seconds
totalSeconds = 0
for segment in annotations.itersegments():
if segment.end > totalSeconds:
totalSeconds = segment.end
st.session_state.results[i] = (annotations, totalSeconds)
st.session_state.summaries[i] = {}
speakerNames = annotations.labels()
st.session_state.unusedSpeakers[i] = speakerNames
elif file_paths[i].lower().endswith('.rttm'):
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
# RTTM load as filler
speakerList, annotations = su.loadAudioRTTM(file_paths[i])
printV(annotations,4)
# Approximate total seconds
totalSeconds = 0
for segment in annotations.itersegments():
if segment.end > totalSeconds:
totalSeconds = segment.end
st.session_state.results[i] = (annotations, totalSeconds)
st.session_state.summaries[i] = {}
speakerNames = annotations.labels()
st.session_state.unusedSpeakers[i] = speakerNames
elif file_paths[i].lower().endswith('.csv'):
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
# RTTM load as filler
speakerList, annotations = su.loadAudioCSV(file_paths[i])
printV(annotations,4)
# Approximate total seconds
totalSeconds = 0
for segment in annotations.itersegments():
if segment.end > totalSeconds:
totalSeconds = segment.end
st.session_state.results[i] = (annotations, totalSeconds)
st.session_state.summaries[i] = {}
speakerNames = annotations.labels()
st.session_state.unusedSpeakers[i] = speakerNames
else:
with st.spinner(text=f'Processing File {i+1} of {totalFiles}'):
annotations, totalSeconds = processFile(file_paths[i])
print(f"Finished processing {file_paths[i]}")
st.session_state.results[i] = (annotations, totalSeconds)
print("Results saved")
st.session_state.summaries[i] = {}
print("Summaries saved")
speakerNames = annotations.labels()
st.session_state.unusedSpeakers[i] = speakerNames
print("Speakers saved")
with st.spinner(text=f'Analyzing File {i+1} of {totalFiles}'):
analyze(file_names[i])
print(f"Finished analyzing {file_paths[i]}")
print(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
st.success(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
class FakeUpload:
def __init__(self,filepath):
self.path = filepath
self.name = filepath.split('/')[-1
]
demoPath = "sample.rttm"
isDemo = False
if st.sidebar.button("Load Demo Example"):
sampleUpload = FakeUpload(demoPath)
valid_files=[sampleUpload]
file_paths=[sampleUpload.path]
file_names=[sampleUpload.name]
start_time = time.time()
st.session_state.file_names = file_names
# Save valid file names
if len(valid_files) > 0:
file_names = [f.name for f in valid_files]
while (len(st.session_state.results) < len(valid_files)):
st.session_state.results.append([])
while (len(st.session_state.summaries) < len(valid_files)):
st.session_state.summaries.append([])
while (len(st.session_state.unusedSpeakers) < len(valid_files)):
st.session_state.unusedSpeakers.append([])
while (len(st.session_state.categorySelect) < len(valid_files)):
tempCategories = [[] for cat in st.session_state.categories]
st.session_state.categorySelect.append(tempCategories)
while (len(st.session_state.summaries) < len(valid_files)):
st.session_state.summaries.append([])
with st.spinner(text=f'Loading Demo Sample'):
# RTTM load as filler
speakerList, annotations = su.loadAudioRTTM(file_paths[0])
# Approximate total seconds
totalSeconds = 0
for segment in annotations.itersegments():
if segment.end > totalSeconds:
totalSeconds = segment.end
st.session_state.results = [(annotations, totalSeconds)]
st.session_state.summaries = [{}]
speakerNames = annotations.labels()
st.session_state.unusedSpeakers = [speakerNames]
with st.spinner(text=f'Analyzing Demo Data'):
analyze(file_names[0])
st.success(f"Took {time.time() - start_time} seconds to analyze the demo file!")
st.session_state.select_currFile=file_names[0]
isDemo = True
currFile = st.sidebar.selectbox('Current File', file_names,on_change=updateMultiSelect,key="select_currFile")
if isDemo:
currFile=file_names[0]
isDemo = False
if currFile is None and len(st.session_state.results) > 0 and len(st.session_state.results[0]) > 0:
st.write("Select a file to view from the sidebar")
try:
st.session_state.resetResult = False
currFileIndex = file_names.index(currFile)
currPlainName = currFile.split('.')[0]
if len(st.session_state.results) > currFileIndex and len(st.session_state.summaries) > currFileIndex and len(st.session_state.results[currFileIndex]) > 0:
st.header(f"Analysis of file {currFile}")
graphNames = ["Data","Voice Categories","Speaker Percentage","Speakers with Categories","Treemap","Timeline","Time Spoken"]
dataTab, pie1, pie2, sunburst1, treemap1, timeline, bar1 = st.tabs(graphNames)
# Handle
currAnnotation, currTotalTime = st.session_state.results[currFileIndex]
speakerNames = currAnnotation.labels()
speakers_dataFrame = st.session_state.summaries[currFileIndex]["speakers_dataFrame"]
currDF, _ = su.annotationToSimpleDataFrame(currAnnotation)
speakers_times = st.session_state.summaries[currFileIndex]["speakers_times"]
# Update other categories
unusedSpeakers = st.session_state.unusedSpeakers[currFileIndex]
categorySelections = st.session_state["categorySelect"][currFileIndex]
for i,category in enumerate(st.session_state.categories):
speakerSet = categorySelections[i]
st.sidebar.multiselect(category,
speakerSet+unusedSpeakers,
default=speakerSet,
key=f"multiselect_{category}",
on_change=updateCategoryOptions,
args=(currFileIndex,))
st.sidebar.button(f"Remove {category}",key=f"remove_{category}",on_click=removeCategory,args=(i,))
newCategory = st.sidebar.text_input('Add category', key='categoryInput',on_change=addCategory)
catTypeColors = su.colorsCSS(3)
allColors = su.colorsCSS(len(speakerNames)+len(st.session_state.categories))
speakerColors = allColors[:len(speakerNames)]
catColors = allColors[len(speakerNames):]
df4_dict = {}
nameList = st.session_state.categories
extraNames = []
valueList = [0 for i in range(len(nameList))]
extraValues = []
for i,speakerSet in enumerate(categorySelections):
valueList[i] += su.sumTimes(currAnnotation.subset(speakerSet))
for sp in unusedSpeakers:
extraNames.append(sp)
extraValues.append(su.sumTimes(currAnnotation.subset([sp])))
df4_dict = {
"names": nameList+extraNames,
"values": valueList+extraValues,
}
df4 = pd.DataFrame(data=df4_dict)
df4.name = "df4"
st.session_state.summaries[currFileIndex]["df4"] = df4
with dataTab:
csv = convert_df(currDF)
st.download_button(
"Press to Download analysis data",
csv,
'sonogram-analysis-'+currPlainName+'.csv',
"text/csv",
key='download-csv',
on_click="ignore",
)
st.dataframe(currDF)
with pie1:
printV("In Pie1",4)
df3 = st.session_state.summaries[currFileIndex]["df3"]
fig1 = go.Figure()
fig1.update_layout(
title_text="Percentage of each Voice Category",
colorway=catTypeColors,
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
)
printV("Pie1 Pretrace",4)
fig1.add_trace(go.Pie(values=df3["values"],labels=df3["names"],sort=False))
printV("Pie1 Posttrace",4)
col1_1, col1_2 = st.columns(2)
fig1.write_image("ascn_pie1.pdf")
fig1.write_image("ascn_pie1.svg")
printV("Pie1 files written",4)
with col1_1:
printV("Pie1 in col1_1",4)
with open('ascn_pie1.pdf','rb') as f:
printV("Pie1 in file open",4)
st.download_button(
"Save As PDF",
f,
'sonogram-voice-category-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf1',
on_click="ignore",
)
printV("Pie1 after col1_1",4)
with col1_2:
with open('ascn_pie1.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-voice-category-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg1',
on_click="ignore",
)
printV("Pie1 in col1_2",4)
st.plotly_chart(fig1, use_container_width=True,config=config)
printV("Pie1 post plotly",4)
with pie2:
df4 = st.session_state.summaries[currFileIndex]["df4"]
# Some speakers may be missing, so fix colors
figColors = []
for n in df4["names"]:
if n in speakerNames:
figColors.append(speakerColors[speakerNames.index(n)])
fig2 = go.Figure()
fig2.update_layout(
title_text="Percentage of Speakers and Custom Categories",
colorway=catColors+figColors,
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
)
fig2.add_trace(go.Pie(values=df4["values"],labels=df4["names"],sort=False))
col2_1, col2_2 = st.columns(2)
fig2.write_image("ascn_pie2.pdf")
fig2.write_image("ascn_pie2.svg")
with col2_1:
with open('ascn_pie2.pdf','rb') as f:
st.download_button(
"Save As PDF",
f,
'sonogram-speaker-percent-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf2',
on_click="ignore",
)
with col2_2:
with open('ascn_pie2.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-speaker-percent-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg2',
on_click="ignore",
)
st.plotly_chart(fig2, use_container_width=True,config=config)
with sunburst1:
df5 = st.session_state.summaries[currFileIndex]["df5"]
fig3_1 = px.sunburst(df5,
branchvalues = 'total',
names = "labels",
ids = "ids",
parents = "parents",
values = "percentiles",
custom_data=['labels','valueStrings','percentiles','parentNames','parentPercentiles'],
color = 'labels',
title="Percentage of each Voice Category with Speakers",
color_discrete_sequence=catTypeColors+speakerColors,
)
fig3_1.update_traces(
hovertemplate="<br>".join([
'<b>%{customdata[0]}</b>',
'Duration: %{customdata[1]}s',
'Percentage of Total: %{customdata[2]:.2f}%',
'Parent: %{customdata[3]}',
'Percentage of Parent: %{customdata[4]:.2f}%'
])
)
fig3_1.update_layout(
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
)
col3_1, col3_2 = st.columns(2)
fig3_1.write_image("ascn_sunburst.pdf")
fig3_1.write_image("ascn_sunburst.svg")
with col3_1:
with open('ascn_sunburst.pdf','rb') as f:
st.download_button(
"Save As PDF",
f,
'sonogram-speaker-categories-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf3',
on_click="ignore",
)
with col3_2:
with open('ascn_sunburst.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-speaker-categories-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg3',
on_click="ignore",
)
st.plotly_chart(fig3_1, use_container_width=True,config=config)
with treemap1:
df5 = st.session_state.summaries[currFileIndex]["df5"]
fig3 = px.treemap(df5,
branchvalues = "total",
names = "labels",
parents = "parents",
ids="ids",
values = "percentiles",
custom_data=['labels','valueStrings','percentiles','parentNames','parentPercentiles'],
color='labels',
title="Division of Speakers in each Voice Category",
color_discrete_sequence=catTypeColors+speakerColors,
)
fig3.update_traces(
hovertemplate="<br>".join([
'<b>%{customdata[0]}</b>',
'Duration: %{customdata[1]}s',
'Percentage of Total: %{customdata[2]:.2f}%',
'Parent: %{customdata[3]}',
'Percentage of Parent: %{customdata[4]:.2f}%'
])
)
fig3.update_layout(
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
)
col4_1, col4_2 = st.columns(2)
fig3.write_image("ascn_treemap.pdf")
fig3.write_image("ascn_treemap.svg")
with col4_1:
with open('ascn_treemap.pdf','rb') as f:
st.download_button(
"Save As PDF",
f,
'sonogram-treemap-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf4',
on_click="ignore",
)
with col4_2:
with open('ascn_treemap.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-treemap-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg4',
on_click="ignore",
)
st.plotly_chart(fig3, use_container_width=True,config=config)
# generate plotting window
with timeline:
fig_la = px.timeline(speakers_dataFrame, x_start="Start", x_end="Finish", y="Resource", color="Resource",title="Timeline of Audio with Speakers",
color_discrete_sequence=speakerColors)
fig_la.update_yaxes(autorange="reversed")
hMax = int(currTotalTime//3600)
mMax = int(currTotalTime%3600//60)
sMax = int(currTotalTime%60)
msMax = int(currTotalTime*1000000%1000000)
timeMax = dt.time(hMax,mMax,sMax,msMax)
fig_la.update_layout(
xaxis_tickformatstops = [
dict(dtickrange=[None, 1000], value="%H:%M:%S.%L"),
dict(dtickrange=[1000, None], value="%H:%M:%S")
],
xaxis=dict(
range=[dt.datetime.combine(dt.date.today(), dt.time.min),dt.datetime.combine(dt.date.today(), timeMax)]
),
xaxis_title="Time",
yaxis_title="Speaker",
legend_title=None,
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
legend={'traceorder':'reversed'},
yaxis= {'showticklabels': False},
)
col5_1, col5_2 = st.columns(2)
fig_la.write_image("ascn_timeline.pdf")
fig_la.write_image("ascn_timeline.svg")
with col5_1:
with open('ascn_timeline.pdf','rb') as f:
st.download_button(
"Save As PDF",
f,
'sonogram-timeline-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf5',
on_click="ignore",
)
with col5_2:
with open('ascn_timeline.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-timeline-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg5',
on_click="ignore",
)
st.plotly_chart(fig_la, use_container_width=True,config=config)
with bar1:
df2 = st.session_state.summaries[currFileIndex]["df2"]
fig2_la = px.bar(df2, x="values", y="names", color="names", orientation='h',
custom_data=["names","values"],title="Time Spoken by each Speaker",
color_discrete_sequence=catColors+speakerColors)
fig2_la.update_xaxes(ticksuffix="%")
fig2_la.update_yaxes(autorange="reversed")
fig2_la.update_layout(
xaxis_title="Percentage Time Spoken",
yaxis_title="Speaker",
legend_title=None,
plot_bgcolor='rgba(0, 0, 0, 0)',
paper_bgcolor='rgba(0, 0, 0, 0)',
legend={'traceorder':'reversed'},
yaxis= {'showticklabels': False},
)
fig2_la.update_traces(
hovertemplate="<br>".join([
'<b>%{customdata[0]}</b>',
'Percentage of Time: %{customdata[1]:.2f}%'
])
)
col6_1, col6_2 = st.columns(2)
fig_la.write_image("ascn_bar.pdf")
fig_la.write_image("ascn_bar.svg")
with col6_1:
with open('ascn_bar.pdf','rb') as f:
st.download_button(
"Save As PDF",
f,
'sonogram-speaker-time-'+currPlainName+'.pdf',
'application/pdf',
key='download-pdf6',
on_click="ignore",
)
with col6_2:
with open('ascn_bar.svg','rb') as f:
st.download_button(
"Save As SVG",
f,
'sonogram-speaker-time-'+currPlainName+'.svg',
'image/svg+xml',
key='download-svg6',
on_click="ignore",
)
st.plotly_chart(fig2_la, use_container_width=True,config=config)
except ValueError:
pass
if len(st.session_state.results) > 0:
with st.expander("Multi-file Summary Data"):
st.header("Multi-file Summary Data")
with st.spinner(text='Processing summary results...'):
fileNames = st.session_state.file_names
results = []
indices = []
for i, resultTuple in enumerate(st.session_state.results):
if len(resultTuple) == 2:
results.append(resultTuple)
indices.append(i)
if len(indices) > 1:
df6_dict = {
"files":fileNames,
}
allCategories = copy.deepcopy(st.session_state.categories)
for i in indices:
currAnnotation, currTotalTime = st.session_state.results[i]
categorySelections = st.session_state["categorySelect"][i]
catSummary,extraCats = su.calcCategories(currAnnotation,categorySelections)
st.session_state.summaries[i]["categories"] = (catSummary,extraCats)
for extra in extraCats:
df6_dict[extra] = []
if extra not in allCategories:
allCategories.append(extra)
for category in st.session_state.categories:
df6_dict[category] = []
for i in indices:
summary, extras = st.session_state.summaries[i]["categories"]
theseCategories = st.session_state.categories + extras
for j, timeSlots in enumerate(summary):
df6_dict[theseCategories[j]].append(sum([t.duration for _,t in timeSlots])/st.session_state.results[i][1])
for category in allCategories:
if category not in theseCategories:
df6_dict[category].append(0)
df6 = pd.DataFrame(df6_dict)
summFig = px.bar(df6, x="files", y=allCategories,title="Time Spoken by Each Speaker in Each File")
st.plotly_chart(summFig, use_container_width=True,config=config)
voiceNames = ["No Voice","One Voice","Multi Voice"]
df7_dict = {
"files":fileNames,
}
for category in voiceNames:
df7_dict[category] = []
for resultID,summary in enumerate(st.session_state.summaries):
partialDf = summary["df5"]
for i in range(len(voiceNames)):
df7_dict[voiceNames[i]].append(partialDf["percentiles"][i])
df7 = pd.DataFrame(df7_dict)
sorted_df7 = df7.sort_values(by=['One Voice', 'Multi Voice'])
summFig2 = px.bar(sorted_df7, x="files", y=["One Voice","Multi Voice","No Voice",],title="Cross-file Voice Categories sorted for One Voice")
st.plotly_chart(summFig2, use_container_width=True,config=config)
sorted_df7_3 = df7.sort_values(by=['Multi Voice','One Voice'])
summFig3 = px.bar(sorted_df7_3, x="files", y=["One Voice","Multi Voice","No Voice",],title="Cross-file Voice Categories sorted for Multi Voice")
st.plotly_chart(summFig3, use_container_width=True,config=config)
sorted_df7_4 = df7.sort_values(by=['No Voice', 'Multi Voice'],ascending=False)
summFig4 = px.bar(sorted_df7_4, x="files", y=["One Voice","Multi Voice","No Voice",],title="Cross-file Voice Categories sorted for Any Voice")
st.plotly_chart(summFig4, use_container_width=True,config=config)
old = '''userid = st.text_input("user id:", "Guest")
colorPref = st.text_input("Favorite color?", "None")
radio = st.radio('Pick one:', ['Left','Right'])
selection = st.selectbox('Select', [1,2,3])
if st.button("Upload Files to Dataset"):
save_data({"color":colorPref,"direction":radio,"number":selection},
file_paths,
userid)
st.success('I think it worked!')
'''
@st.cache_data
def convert_df(df):
return df.to_csv(index=False).encode('utf-8')
with st.expander("Instructions and additional details"):
st.write("Thank you for viewing our experimental app! The overall presentations and features are expected to be improved over time, you can think of this as our first rough draft!")
st.write("To use this app:\n1. Upload an audio file for live analysis. Alternatively, you can upload an already generated [rttm file](https://stackoverflow.com/questions/30975084/rttm-file-format)")
st.write("2. Press Analyze All. Note that no data is saved on our side, so we will not have access to your recordings. Future versions of this app will support donating audio to us for aid in our research.")
st.write("3. Use the side bar on the left to select your file (may have to be expanded by clicking the > ). Our app supports uploading multiple files for more comprehensive analysis.")
st.write("4. Use the tabs provided to view different visualizations of your audio. Each example can be downloaded for personal use.")
st.write("4a. The graphs are built using [plotly](https://plotly.com/). This allows for a high degree of interaction. Feel free to experiment with the graphs, as you can always return to the original view by double-clicking on the graph. For more examples of easily supported visualizations, see [here](https://plotly.com/python/basic-charts/)")
with st.expander("(Potentially) FAQ"):
st.write(f"**1. I tried analyzing a file, but the page refreshed and nothing happened! Why?**\n\t")
st.write("You may need to select a file using the side bar on the left. This app supports multiple files, so we require that you select which file to view after analysis.")
st.write(f"**2. I don't see a sidebar! Where is it?**\n\t")
st.write("The side bar may start by being minimized. Press the '>' in the upper left to expand the side bar.")
st.write(f"**3. I still don't have a file to select in the dropdown! Why?**\n\t")
st.write("If you are sure that you have run Analyze All and after refresh no files may be selected, then your file is likely too large. We currently have a limitation of approximately 1.5 hours of audio. This is a known issue that requires additional time **or** money to solve, and is expected to be fixed by the next update of this app. Please be patient!")
st.write(f"**4. I want to be able to view my previously analyzed data! How can I do this?**\n\t")
st.write("You can download a CSV copy of the data using the first tab. From there, you can reupload the CSV copy at a later date to view the data visualizations without having to use your original audio file. Future versions of this app will support creating optional logins for long term storage and analysis.")
st.write(f"**5. The app says 'TOOL CURRENTLY USING CPU, ANALYSIS EXTREMELY SLOW' and takes forever to analyze audio! What is wrong?**\n\t")
st.write("We are currently in the process of securing funding to allow permanent public access to this tool. Until then, we can provide an interface to view already analyzed data without cost to you or us. While this mode will technically still work, it may take over a day to analyze your audio. Feel free to reach out to us to discuss temporary solutions to this until the app's funding is secured!") |