czyoung commited on
Commit
7f8749e
·
verified ·
1 Parent(s): 51100a7

Sonogram class overhaul

Browse files

Updated Sonogram class to use trained model with reclassifier SVM and provide categorical classification

Files changed (1) hide show
  1. sonogram.py +160 -88
sonogram.py CHANGED
@@ -2,22 +2,13 @@ import sonogram_utility as su
2
  from pyannote.audio import Pipeline
3
  import pickle
4
  import torch
 
 
 
5
 
6
  class Sonogram():
7
 
8
- def __init__(self,enableDenoise=False):
9
- '''
10
- Initialize Sonogram Class
11
-
12
- enableDenoise : False|True
13
- Legacy code to support denoise, which has currently been removed. Consider removing if denoise will not be reimplemented in the future.
14
- '''
15
- #TODO: Should these be adjustable via initialization, or constants?
16
- self.secondDifference = 5
17
- self.gainWindow = 4
18
- self.minimumGain = -45
19
- self.maximumGain = -5
20
- self.attenLimDB = 3
21
  self.earlyCleanup = True
22
 
23
  self.isTPU = False
@@ -40,86 +31,167 @@ class Sonogram():
40
  self.pipeline.to(self.device)
41
 
42
  # Load SVM classifier
43
- with open('groupClassifier.pkl', 'rb') as f:
44
  self.groupClassifier = pickle.load(f)
 
 
 
45
 
46
  def processFile(self,filePath):
47
- '''
48
- Processes audio file to generate diarization output
49
-
50
- filePath : string
51
- Path to the audio file
52
-
53
- Returns
54
- --------
55
- diarizationOutput : DiarizeOutput
56
- found here https://github.com/pyannote/pyannote-audio/blob/main/src/pyannote/audio/pipelines/speaker_diarization.py#L64
57
-
58
- totalTimeInSeconds : int
59
- Approximate total seconds of audio file
60
-
61
- waveformGainAdjusted : np.array
62
- The waveform of the audio file after equalization
63
-
64
- sampleRate : int
65
- The sample rate of the audio file
66
- '''
67
- print(f"Loading file : {filePath}")
68
- waveformList, sampleRate = su.splitIntoTimeSegments(filePath,600)
69
- print("File loaded")
70
- waveformEnhanced = su.combineWaveforms(waveformList)
71
- if (self.earlyCleanup):
72
- del waveformList
73
- print("Equalizing Audio")
74
- waveform_gain_adjusted = su.equalizeVolume()(waveformEnhanced,sampleRate,self.gainWindow,self.minimumGain,self.maximumGain)
75
- if (self.earlyCleanup):
76
- del waveformEnhanced
77
- print("Audio Equalized")
78
- print("Detecting speakers")
79
- diarizationOutput, embeddings = self.pipeline({"waveform": waveform_gain_adjusted, "sample_rate": sampleRate}, return_embeddings=True)
80
- annotations = diarizationOutput.speaker_diarization
81
- embeddings = diarizationOutput.speaker_embeddings
82
- print("Speakers Detected")
83
- totalTimeInSeconds = int(waveform_gain_adjusted.shape[-1]/sampleRate)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  print("Time in seconds calculated")
85
- return diarizationOutput, totalTimeInSeconds, waveformGainAdjusted, sampleRate
86
-
87
- def __call__(self,audioPath):
88
- '''
89
- Processes audio file to generate results necessary for app
90
 
91
- filePath : string
92
- Path to the audio file
93
 
94
- Returns
95
- --------
96
- annotation : pyannote.core.annotation
97
- found here https://pyannote.github.io/pyannote-core/_modules/pyannote/core/annotation.html
98
-
99
- totalTimeInSeconds : int
100
- Approximate total seconds of audio file
101
-
102
- waveformGainAdjusted : np.array
103
- The waveform of the audio file after equalization
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
- sampleRate : int
106
- The sample rate of the audio file
107
- '''
108
- diarizationOutput, totalTimeInSeconds, waveformGainAdjusted, sampleRate = self.processFile(audioPath)
109
- annotation = diarizationOutput.speaker_diarization
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- # Relabel any existing silence and group speakers
112
- labelMapping = {}
113
- for s, speaker in enumerate(output.speaker_diarization.labels()):
114
- diarizationOutput.speaker_embeddings[s]
115
- prediction = self.groupClassifier.predict(diarizationOutput.speaker_embeddings[s].reshape(1,-1))
116
- if prediction == 0:
117
- labelMapping[speaker] = "silence"
118
- elif prediction == 2:
119
- labelMapping[speaker] = "group"
120
- else:
121
- # May not be necessary, consider using to reformat default names away from SPEAKER_XX
122
- labelMapping[speaker] = speaker
123
- # Rename in place
124
- annotation.rename_labels(labelMapping)
125
- return annotation, totalTimeInSeconds, waveformGainAdjusted, sampleRate
 
2
  from pyannote.audio import Pipeline
3
  import pickle
4
  import torch
5
+ import soundfile as sf
6
+ import numpy as np
7
+ from pyannote.core import Segment
8
 
9
  class Sonogram():
10
 
11
+ def __init__(self):
 
 
 
 
 
 
 
 
 
 
 
 
12
  self.earlyCleanup = True
13
 
14
  self.isTPU = False
 
31
  self.pipeline.to(self.device)
32
 
33
  # Load SVM classifier
34
+ with open('05062026_groupClassifier.pkl', 'rb') as f:
35
  self.groupClassifier = pickle.load(f)
36
+
37
+ def classifyEmbedding(self,embedding):
38
+ return int(self.groupClassifier.predict(embedding.reshape(1, -1)))
39
 
40
  def processFile(self,filePath):
41
+ # Loading audio file
42
+ print(f"Loading file: {filePath}")
43
+ data, sample_rate = sf.read(filePath, dtype="float32", always_2d=True)
44
+ waveform = torch.from_numpy(data.T) # shape: [channels, samples]
45
+ # Wrapping as AudioFile
46
+ audioFile = {"waveform": waveform, "sample_rate": sample_rate}
47
+ print("Detecting Voices")
48
+ segmentations = self.pipeline.get_segmentations(audioFile)
49
+ print("Generating vocal embeddings")
50
+ embeddings = self.pipeline.get_embeddings(audioFile,segmentations,exclude_overlap=False)
51
+ print("Clustering Speakers")
52
+ hardC, softC, centroids = self.pipeline.clustering(embeddings = embeddings,segmentations = segmentations)
53
+ count = self.pipeline.speaker_count(
54
+ segmentations,
55
+ self.pipeline._segmentation.model.receptive_field,
56
+ warm_up=(0.0, 0.0),)
57
+ print("Classifying Embeddings")
58
+ embeddingClasses = np.zeros((embeddings.shape[0],embeddings.shape[1]))
59
+ # Dumb loop
60
+ # Timestep
61
+ tempCount = [0,0]
62
+ for i,e in enumerate(embeddings):
63
+ # Speaker, skip empty
64
+ for j,eS in enumerate(e):
65
+ if np.any(segmentations.data[i,:,j] > 0):
66
+ groupClass = self.classifyEmbedding(eS)
67
+ embeddingClasses[i][j] = groupClass
68
+ # Correct silence detections, remove group for later addition
69
+ if groupClass == 2:
70
+ segmentations.data[i,:,j] = 0
71
+ tempCount[1] += 1
72
+ elif groupClass == 0 and np.mean(segmentations.data[i,:,j]) < 0.5:
73
+ segmentations.data[i,:,j] = 0
74
+ tempCount[0] += 1
75
+ print("Generating Annotation")
76
+ # shape: (num_chunks, num_speakers)
77
+ # keep track of inactive speakers
78
+ inactive_speakers = np.sum(segmentations.data, axis=1) == 0
79
+ hardC[inactive_speakers] = -2
80
+ discrete_diarization = self.pipeline.reconstruct(
81
+ segmentations,
82
+ hardC,
83
+ count,)
84
+ diarization = self.pipeline.to_annotation(
85
+ discrete_diarization,
86
+ min_duration_on=0.0,
87
+ min_duration_off=self.pipeline.segmentation.min_duration_off,
88
+ )
89
+ # keep track of group speakers
90
+ group_speakers = np.any(embeddingClasses >= 2,axis=1
91
+ start = None
92
+ for timeStep in range(group_speakers.shape[0]):
93
+ if group_speakers[timeStep] > 0:
94
+ if start is None:
95
+ start = timeStep
96
+ elif start is not None:
97
+ segment = Segment(start, timeStep)
98
+ diarization[segment] = 'group'
99
+ start = None
100
+ # Catch end case
101
+ if start is not None:
102
+ segment = Segment(start, embeddingClasses.shape[0]-1)
103
+ diarization[segment] = 'group'
104
+
105
+ totalTimeInSeconds = int(waveform.shape[-1]/sample_rate)
106
  print("Time in seconds calculated")
107
+ return diarization, totalTimeInSeconds, waveform, sample_rate
 
 
 
 
108
 
 
 
109
 
110
+ def activeSpeaker(self,inAnnotation,step=1):
111
+ speakerAtStep = [None]
112
+ stepTime = [0]
113
+ speakerHierarchy = [label for label,_ in inAnnotation.chart()]
114
+
115
+ for label in speakerHierarchy:
116
+ # Move group labels to beginning of hierarchy
117
+ if label == 'group' or label == 99:
118
+ speakerHierarchy.remove(label)
119
+ speakerHierarchy.insert(0,label)
120
+ for segment,_,label in inAnnotation.itertracks(yield_label=True):
121
+ startI = int(segment.start / step)
122
+ # Lazy end assumption, always assumes one more step
123
+ endI = int(segment.end / step) + 1
124
+ while len(stepTime) < endI+1:
125
+ stepTime.append(stepTime[-1]+step)
126
+ speakerAtStep.append(None)
127
+ for i in range(startI,endI+1):
128
+ if speakerAtStep[i] == None:
129
+ speakerAtStep[i] = label
130
+ else:
131
+ currHier = speakerHierarchy.index(speakerAtStep[i])
132
+ newHier = speakerHierarchy.index(label)
133
+ if newHier < currHier:
134
+ speakerAtStep[i] = label
135
+ return speakerAtStep, stepTime, speakerHierarchy
136
+
137
+ def annotationToNoiseList(self,inAnnotation,stepSize=2,windowSize=90):
138
+ sas, st, sh = self.activeSpeaker(inAnnotation,step=stepSize)
139
+ timeStepAggregate = []
140
+ timeStepClass = []
141
+ categories = ['group','individual','silence']
142
+ for i in st:
143
+ timeStepAggregate.append({'individual':0,'group':0,'silence':0})
144
+ for i,_ in enumerate(sas):
145
+ decision = None
146
+ groupCount = 0
147
+ individuals = set()
148
+ silenceCount = 0
149
+ end = min(i+windowSize,len(sas))
150
+ for j in range(i,end):
151
+ if sas[j] == None:
152
+ silenceCount += 1
153
+ elif sas[j] == 'group' or sas[j] == 99:
154
+ groupCount += 1
155
+ else:
156
+ individuals.add(sas[j])
157
+ if silenceCount > windowSize / 2:
158
+ decision = 'silence'
159
+ elif groupCount > windowSize / 2 or len(individuals) > 3:
160
+ decision = 'group'
161
+ else:
162
+ decision = 'individual'
163
+ for j in range(i,end):
164
+ timeStepAggregate[j][decision] += 1
165
+ for item in timeStepAggregate:
166
+ timeStepClass.append(max(item, key=item.get))
167
 
168
+
169
+ categorySegmentList = []
170
+ for c in categories:
171
+ currList = []
172
+ start = None
173
+ duration = 0
174
+ tracking = False
175
+ for stepClass,timeIncrement in zip(timeStepClass,st):
176
+ if stepClass == c:
177
+ if start == None:
178
+ start = timeIncrement
179
+ duration += stepSize
180
+ tracking = True
181
+ else:
182
+ duration += stepSize
183
+ else:
184
+ if tracking:
185
+ currList.append((start,duration))
186
+ start = None
187
+ duration = 0
188
+ tracking = False
189
+ if tracking:
190
+ currList.append((start,duration))
191
+ categorySegmentList.append(currList)
192
+ return categorySegmentList, st
193
+
194
+ def __call__(self,audioPath):
195
+ annotation, totalTimeInSeconds, waveform, sampleRate = self.processFile(audioPath)
196
 
197
+ return annotation, totalTimeInSeconds, waveform, sampleRate