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="
".join([ '%{customdata[0]}', '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="
".join([ '%{customdata[0]}', '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="
".join([ '%{customdata[0]}', '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!")