czyoung commited on
Commit
12b68bc
·
verified ·
1 Parent(s): f8088cc

Documentation provided for Sonogram.py

Browse files
Files changed (1) hide show
  1. sonogram.py +226 -21
sonogram.py CHANGED
@@ -11,29 +11,76 @@ from pyannote.pipeline.parameter import ParamDict
11
  import torch
12
 
13
  class Sonogram():
 
 
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def __init__(self,version='1.0'):
 
 
 
 
 
 
16
  self.earlyCleanup = True
17
 
18
  self.isTPU = False
19
  self.isGPU = False
 
20
  try:
 
21
  raise(RuntimeError("Not an error"))
22
  #device = xm.xla_device()
23
  print("TPU is available.")
24
  self.isTPU = True
25
  except RuntimeError as e:
26
  print(f"TPU is not available: {e}")
27
- # Fallback to CPU or other devices if needed
28
  self.isGPU = torch.cuda.is_available()
29
  if not self.isGPU:
30
  print(f"GPU is not available")
 
31
  self.device = torch.device("cuda" if self.isGPU else "cpu")
32
  print(f"Using {self.device} instead.")
33
 
34
  self.version = version
 
35
  if version == 'speaker-diarization-3.1':
36
  self.pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
 
37
  elif version == '1.0':
38
  baselinePipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
39
  newSpecs = baselinePipeline._segmentation.model.specifications
@@ -51,25 +98,60 @@ class Sonogram():
51
 
52
  baselinePipeline.segmentation = ParamDict(min_duration_off=0.0,)
53
  self.pipeline = baselinePipeline
54
-
55
-
56
- # Should manage this outside class now
57
- #self.pipeline.to(self.device)
58
 
59
- # Load SVM classifier
60
  with open('05062026_groupClassifier.pkl', 'rb') as f:
61
  self.groupClassifier = pickle.load(f)
62
 
63
  def classifyEmbedding(self,embedding):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  return int(self.groupClassifier.predict(embedding.reshape(1, -1)).item())
65
 
66
  def processFile(self,filePath):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  # Loading audio file
68
  print(f"Loading file: {filePath}")
69
  data, sample_rate = sf.read(filePath, dtype="float32", always_2d=True)
70
  waveform = torch.from_numpy(data.T) # shape: [channels, samples]
71
  # Wrapping as AudioFile
72
  audioFile = {"waveform": waveform, "sample_rate": sample_rate}
 
 
 
73
  print("Detecting Voices")
74
  segmentations = self.pipeline.get_segmentations(audioFile)
75
  print("Generating vocal embeddings")
@@ -82,19 +164,21 @@ class Sonogram():
82
  warm_up=(0.0, 0.0),)
83
  print("Classifying Embeddings")
84
  embeddingClasses = np.zeros((embeddings.shape[0],embeddings.shape[1]))
85
- # Dumb loop
86
- # Timestep
87
  tempCount = [0,0]
88
  for i,e in enumerate(embeddings):
89
  # Speaker, skip empty
90
  for j,eS in enumerate(e):
91
  if np.any(segmentations.data[i,:,j] > 0):
 
92
  groupClass = self.classifyEmbedding(eS)
93
  embeddingClasses[i][j] = groupClass
94
- # Correct silence detections, remove group for later addition
95
  if groupClass == 2:
96
  segmentations.data[i,:,j] = 0
97
  tempCount[1] += 1
 
98
  elif groupClass == 0 and np.mean(segmentations.data[i,:,j]) < 0.5:
99
  segmentations.data[i,:,j] = 0
100
  tempCount[0] += 1
@@ -114,6 +198,7 @@ class Sonogram():
114
  )
115
  # keep track of group speakers
116
  group_speakers = np.any(embeddingClasses >= 2,axis=1)
 
117
  start = None
118
  for timeStep in range(group_speakers.shape[0]):
119
  if group_speakers[timeStep] > 0:
@@ -127,10 +212,11 @@ class Sonogram():
127
  if start is not None:
128
  segment = Segment(start, embeddingClasses.shape[0]-1)
129
  diarization[segment] = 'group'
130
-
 
131
  totalTimeInSeconds = int(waveform.shape[-1]/sample_rate)
132
 
133
- # Rename labels
134
  currId = 0
135
  mapping = {}
136
  for label in diarization.labels():
@@ -146,25 +232,53 @@ class Sonogram():
146
 
147
 
148
  def activeSpeaker(self,inAnnotation,step=1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  speakerAtStep = [None]
150
  stepTime = [0]
151
  speakerHierarchy = [label for label,_ in inAnnotation.chart()]
152
-
 
153
  for label in speakerHierarchy:
154
- # Move group labels to beginning of hierarchy
155
  if label == 'group' or label == 99:
156
  speakerHierarchy.remove(label)
157
  speakerHierarchy.insert(0,label)
 
158
  for segment,_,label in inAnnotation.itertracks(yield_label=True):
159
  startI = int(segment.start / step)
160
  # Lazy end assumption, always assumes one more step
161
  endI = int(segment.end / step) + 1
 
162
  while len(stepTime) < endI+1:
163
  stepTime.append(stepTime[-1]+step)
164
  speakerAtStep.append(None)
 
165
  for i in range(startI,endI+1):
 
166
  if speakerAtStep[i] == None:
167
  speakerAtStep[i] = label
 
168
  else:
169
  currHier = speakerHierarchy.index(speakerAtStep[i])
170
  newHier = speakerHierarchy.index(label)
@@ -173,14 +287,48 @@ class Sonogram():
173
  return speakerAtStep, stepTime, speakerHierarchy
174
 
175
  def annotationToNoiseList(self,inAnnotation,maxTime,stepSize=2,windowSize=90):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  sas, st, sh = self.activeSpeaker(inAnnotation,step=stepSize)
177
-
 
 
 
178
  timeStepAggregate = []
 
179
  timeStepClass = []
 
180
  timeStepMembers = []
181
  categories = ['group','individual','silence']
 
182
  for i in st:
183
  timeStepAggregate.append({'individual':0,'group':0,'silence':0})
 
184
  for i,_ in enumerate(sas):
185
  decision = None
186
  groupCount = 0
@@ -188,42 +336,60 @@ class Sonogram():
188
  silenceCount = 0
189
  end = min(i+windowSize,len(sas))
190
  memberSet = set()
 
191
  for j in range(i,end):
 
192
  if sas[j] is not None:
193
  memberSet.add(sas[i])
 
194
  if sas[j] == None:
195
  silenceCount += 1
 
196
  elif sas[j] == 'group' or sas[j] == 99:
197
  groupCount += 1
 
198
  else:
199
  individuals.add(sas[j])
200
- if silenceCount > windowSize / 2:
 
201
  decision = 'silence'
202
- elif sas[i] == 'group' or groupCount > windowSize / 2 or len(individuals) > 2:
 
203
  decision = 'group'
 
204
  else:
205
  decision = 'individual'
 
 
 
 
206
  for j in range(i,end):
207
  timeStepAggregate[j][decision] += 1
208
  # Convert to list and sort for convenience
209
  memberSet = list(memberSet)
210
  memberSet.sort()
211
  timeStepMembers.append(memberSet)
212
-
 
213
  for i,item in enumerate(timeStepAggregate):
 
214
  cat = max(item, key=item.get)
215
  timeStepClass.append(cat)
216
  # For group decisions, members include all in window
217
  if cat == 'group':
218
  timeStepMembers[i] = '+'.join(timeStepMembers[i])
 
219
  elif cat == 'individual':
220
  timeStepMembers[i] = sas[i]
 
221
  else:
222
  timeStepMembers[i] = None
223
 
224
- # For debug
225
- endTime = 0
226
  singleDimList = []
 
 
 
227
  categorySegmentList = []
228
  for c in categories:
229
  currList = []
@@ -231,15 +397,18 @@ class Sonogram():
231
  currMembers = None
232
  duration = 0
233
  tracking = False
 
234
  for stepClass,timeIncrement,members in zip(timeStepClass,st,timeStepMembers):
235
  # Check for case of exact end of audio
236
  if st == maxTime:
237
  continue
 
238
  if stepClass == c:
 
239
  if currMembers == members:
240
-
241
  duration += min(stepSize,maxTime-timeIncrement)
242
  else:
 
243
  if tracking:
244
  singleDimList.append((currMembers,Segment(start,start+duration)))
245
  currList.append((currMembers,Segment(start,start+duration)))
@@ -253,7 +422,9 @@ class Sonogram():
253
  currMembers = members
254
  duration += min(stepSize,maxTime-timeIncrement)
255
  tracking = True
 
256
  else:
 
257
  if tracking:
258
  singleDimList.append((currMembers,Segment(start,start+duration)))
259
  currList.append((currMembers,Segment(start,start+duration)))
@@ -263,13 +434,14 @@ class Sonogram():
263
  currMembers == None
264
  duration = 0
265
  tracking = False
 
266
  if tracking:
267
  singleDimList.append((currMembers,Segment(start,start+duration)))
268
  currList.append((currMembers,Segment(start,start+duration)))
269
  if start+duration > endTime:
270
  endTime = start+duration
271
  categorySegmentList.append(currList)
272
- # Check where we left off
273
  if endTime != maxTime:
274
  singleDimList.append((None,Segment(endTime,maxTime)))
275
  categorySegmentList[2].append((None,Segment(endTime,maxTime)))
@@ -280,14 +452,47 @@ class Sonogram():
280
  return categorySegmentList, st
281
 
282
  def __call__(self,audioPath):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  annotation, totalTimeInSeconds, waveform, sampleRate = self.processFile(audioPath)
284
 
285
  return annotation, totalTimeInSeconds, waveform, sampleRate
286
 
287
  def toDevice(self):
 
 
 
 
 
288
  self.pipeline.to(self.device)
289
  print(f"Sonogram moved to {self.device}")
290
 
291
  def toCPU(self):
 
 
 
 
 
292
  self.pipeline.to(torch.device('cpu'))
293
  print(f"Sonogram moved to CPU")
 
11
  import torch
12
 
13
  class Sonogram():
14
+ '''
15
+ A class to hold the Sonogram model
16
 
17
+ ...
18
+
19
+ Attributes
20
+ ----------
21
+ earlyCleanup : bool
22
+ Determines whether temporary arrays should be deleted ASAP
23
+ isTPU : bool
24
+ Determines whether TPU has been detected and utilized
25
+ isGPU : bool
26
+ Determines whether GPU has been detected and utilized
27
+ device : torch.device
28
+ Device to use for accelerated processing
29
+ version : str
30
+ Named version to determine which Sonogram model to load
31
+ pipeline : pyannote.audio.Pipeline
32
+ Representation of model as pipeline via pyannote
33
+ groupClassifier : sklearn.svm.SVC
34
+ SVM reclassifier
35
+
36
+ Methods
37
+ -------
38
+ classifyEmbedding(embedding)
39
+ Classifies 10 second feature embedding using groupClassifier
40
+ processFile(filePath)
41
+ Loads and processes file to provide diarization and analysis context
42
+ activeSpeaker(inAnnotation,step=1)
43
+ Determines the single active speaker for each timestep
44
+ annotationToNoiseList(inAnnotation,maxTime,stepSize=2,windowSize=90)
45
+ Determines which noise category applies to each timestep
46
+ toDevice()
47
+ Moves pipeline to device
48
+ toCPU()
49
+ Moves pipeline to CPU
50
+ '''
51
+
52
  def __init__(self,version='1.0'):
53
+ '''
54
+ Parameters
55
+ ----------
56
+ version : str
57
+ The named version of Sonogram to load
58
+ '''
59
  self.earlyCleanup = True
60
 
61
  self.isTPU = False
62
  self.isGPU = False
63
+ # Check if TPU or GPU are available
64
  try:
65
+ # Force expected error as TPU has not yet been validated or necessary
66
  raise(RuntimeError("Not an error"))
67
  #device = xm.xla_device()
68
  print("TPU is available.")
69
  self.isTPU = True
70
  except RuntimeError as e:
71
  print(f"TPU is not available: {e}")
 
72
  self.isGPU = torch.cuda.is_available()
73
  if not self.isGPU:
74
  print(f"GPU is not available")
75
+ # Fallback to CPU or other devices if needed
76
  self.device = torch.device("cuda" if self.isGPU else "cpu")
77
  print(f"Using {self.device} instead.")
78
 
79
  self.version = version
80
+ # pyannote pre-trained version
81
  if version == 'speaker-diarization-3.1':
82
  self.pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
83
+ # Sonogram trained version as of 20251208
84
  elif version == '1.0':
85
  baselinePipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
86
  newSpecs = baselinePipeline._segmentation.model.specifications
 
98
 
99
  baselinePipeline.segmentation = ParamDict(min_duration_off=0.0,)
100
  self.pipeline = baselinePipeline
 
 
 
 
101
 
102
+ # Load SVM reclassifier
103
  with open('05062026_groupClassifier.pkl', 'rb') as f:
104
  self.groupClassifier = pickle.load(f)
105
 
106
  def classifyEmbedding(self,embedding):
107
+ '''
108
+ Classifies 10 second feature embedding using groupClassifier
109
+
110
+ ...
111
+
112
+ Parameters
113
+ ----------
114
+ embedding : np.array(x,x)
115
+ 10 second feature embedding to classify
116
+
117
+ Returns
118
+ -------
119
+ _ : int
120
+ 0 for No voice, 1 for Individual voice, 2 for Indistinguishable Group voices
121
+ '''
122
  return int(self.groupClassifier.predict(embedding.reshape(1, -1)).item())
123
 
124
  def processFile(self,filePath):
125
+ '''
126
+ Loads and processes file to provide diarization and analysis context
127
+
128
+ ...
129
+
130
+ Parameters
131
+ ----------
132
+ filePath : str
133
+ Full path to audio file to process
134
+
135
+ Returns
136
+ -------
137
+ diarization : pyannote.core.Annotation
138
+ Diarization result from pipeline
139
+ totalTimeInSeconds : int
140
+ Approximate length of audio for use in diagrams
141
+ waveform : np.array
142
+ Audio waveform as loaded from file
143
+ sample_rate : int
144
+ Sample rate of loaded audio
145
+ '''
146
  # Loading audio file
147
  print(f"Loading file: {filePath}")
148
  data, sample_rate = sf.read(filePath, dtype="float32", always_2d=True)
149
  waveform = torch.from_numpy(data.T) # shape: [channels, samples]
150
  # Wrapping as AudioFile
151
  audioFile = {"waveform": waveform, "sample_rate": sample_rate}
152
+
153
+ # Much of following code is modified from
154
+ # pyannote.audio.pipelines.speaker_diarization.SpeakerDiarization
155
  print("Detecting Voices")
156
  segmentations = self.pipeline.get_segmentations(audioFile)
157
  print("Generating vocal embeddings")
 
164
  warm_up=(0.0, 0.0),)
165
  print("Classifying Embeddings")
166
  embeddingClasses = np.zeros((embeddings.shape[0],embeddings.shape[1]))
167
+ # Dumb loop to apply reclassifier
168
+ # Counter for [silence,group] modifications for debugging
169
  tempCount = [0,0]
170
  for i,e in enumerate(embeddings):
171
  # Speaker, skip empty
172
  for j,eS in enumerate(e):
173
  if np.any(segmentations.data[i,:,j] > 0):
174
+ # Classify timestep
175
  groupClass = self.classifyEmbedding(eS)
176
  embeddingClasses[i][j] = groupClass
177
+ # Remove group from segmentations for later replacement
178
  if groupClass == 2:
179
  segmentations.data[i,:,j] = 0
180
  tempCount[1] += 1
181
+ # Remove silence from segmentations if majority of timestep
182
  elif groupClass == 0 and np.mean(segmentations.data[i,:,j]) < 0.5:
183
  segmentations.data[i,:,j] = 0
184
  tempCount[0] += 1
 
198
  )
199
  # keep track of group speakers
200
  group_speakers = np.any(embeddingClasses >= 2,axis=1)
201
+ # Seperate group speakers into 'pyannote.core.Segment's and apply to diarization under name 'group'
202
  start = None
203
  for timeStep in range(group_speakers.shape[0]):
204
  if group_speakers[timeStep] > 0:
 
212
  if start is not None:
213
  segment = Segment(start, embeddingClasses.shape[0]-1)
214
  diarization[segment] = 'group'
215
+
216
+ # Estimate length of audio for charting
217
  totalTimeInSeconds = int(waveform.shape[-1]/sample_rate)
218
 
219
+ # Rename labels to standard format
220
  currId = 0
221
  mapping = {}
222
  for label in diarization.labels():
 
232
 
233
 
234
  def activeSpeaker(self,inAnnotation,step=1):
235
+ '''
236
+ Determines the single active speaker for each timestep
237
+
238
+ This estimates the primary speaker for each timestep for later use in identifying group discussion
239
+ and when presenters/instructors change
240
+
241
+ Parameters
242
+ ----------
243
+ inAnnotation : pyannote.core.Annotation
244
+ Annotation object (diarization) to analyze
245
+ step : float or int
246
+ Time in seconds to use for determining current active speaker
247
+
248
+ Returns
249
+ -------
250
+ speakerAtStep : list
251
+ List of active speaker at each timestep (length matches stepTime)
252
+ stepTime : list
253
+ List of start time for each timestep (length matches speakerAtStep)
254
+ speakerHierarchy : list
255
+ List of speakers in order of priority. From most speech to least speech with group speech in front
256
+ '''
257
  speakerAtStep = [None]
258
  stepTime = [0]
259
  speakerHierarchy = [label for label,_ in inAnnotation.chart()]
260
+
261
+ # Identify hierarchy of speakers based on time present, with group in front
262
  for label in speakerHierarchy:
263
+ # Move group labels to beginning of hierarchy (99 is training group code)
264
  if label == 'group' or label == 99:
265
  speakerHierarchy.remove(label)
266
  speakerHierarchy.insert(0,label)
267
+ # Iterate over segments
268
  for segment,_,label in inAnnotation.itertracks(yield_label=True):
269
  startI = int(segment.start / step)
270
  # Lazy end assumption, always assumes one more step
271
  endI = int(segment.end / step) + 1
272
+ # If stepTime and speakerAtStep not long enough for segment, then expand them
273
  while len(stepTime) < endI+1:
274
  stepTime.append(stepTime[-1]+step)
275
  speakerAtStep.append(None)
276
+ # For each timestep in current segment, check current speaker against previous speaker
277
  for i in range(startI,endI+1):
278
+ # No active speaker yet, so apply self
279
  if speakerAtStep[i] == None:
280
  speakerAtStep[i] = label
281
+ # If active speaker exists, check against hierarchy
282
  else:
283
  currHier = speakerHierarchy.index(speakerAtStep[i])
284
  newHier = speakerHierarchy.index(label)
 
287
  return speakerAtStep, stepTime, speakerHierarchy
288
 
289
  def annotationToNoiseList(self,inAnnotation,maxTime,stepSize=2,windowSize=90):
290
+ '''
291
+ Determines which noise category applies to each timestep
292
+
293
+ ...
294
+
295
+ Parameters
296
+ ----------
297
+ inAnnotation : pyannote.core.Annotation
298
+ Annotation object (diarization) to analyze
299
+ maxTime : float
300
+ Time at end of audio
301
+ stepSize : float or int
302
+ Time in seconds to use for determining current active speaker
303
+ windowSize : float or int
304
+ Time in seconds to use as window for determining noise categories
305
+
306
+ Returns
307
+ -------
308
+ categorySegmentList : List[3,:]
309
+ List of 3 Lists representing categories: group, individual, silence. Each sublist contains tuples
310
+ of (members,pyannote.core.Segment) where members is a string representing speaker names of all
311
+ relevant to the given Segment. Groups can contain '+' as a delimeter between speakers, e.g.,
312
+ speaker1+speaker2+speaker3. Members are always in alphanumerical order.
313
+ st : list
314
+ List of start time for each timestep
315
+ '''
316
+ # Determine the active speaker for each timestep
317
  sas, st, sh = self.activeSpeaker(inAnnotation,step=stepSize)
318
+
319
+ # Number of steps in window
320
+ windowStepCount = windowSize / stepSize
321
+ # Aggregate of scores for a given window
322
  timeStepAggregate = []
323
+ # Class for a given window
324
  timeStepClass = []
325
+ # Members for a given window
326
  timeStepMembers = []
327
  categories = ['group','individual','silence']
328
+ # Initialize scores
329
  for i in st:
330
  timeStepAggregate.append({'individual':0,'group':0,'silence':0})
331
+ # For each timestep
332
  for i,_ in enumerate(sas):
333
  decision = None
334
  groupCount = 0
 
336
  silenceCount = 0
337
  end = min(i+windowSize,len(sas))
338
  memberSet = set()
339
+ # Iterate over timestep
340
  for j in range(i,end):
341
+ # Add current speaker to member set
342
  if sas[j] is not None:
343
  memberSet.add(sas[i])
344
+ # Increase silence score if no speaker
345
  if sas[j] == None:
346
  silenceCount += 1
347
+ # Increase group score for known group IDs
348
  elif sas[j] == 'group' or sas[j] == 99:
349
  groupCount += 1
350
+ # TODO: Could probably replace individuals with memberSet, but leaving this code in for now
351
  else:
352
  individuals.add(sas[j])
353
+ # If majority of window is silence, then classify as silence
354
+ if silenceCount > windowStepCount / 2:
355
  decision = 'silence'
356
+ # If majority of window is known groups OR total individual speakers above threshold, then group
357
+ elif sas[i] == 'group' or groupCount > windowStepCount / 2 or len(individuals) > 2:
358
  decision = 'group'
359
+ # Classify as individual if not silence or group
360
  else:
361
  decision = 'individual'
362
+ # If treated as individual, group should NOT be included!
363
+ if 'group' in memberSet:
364
+ memberSet.remove('group')
365
+ # Apply decision as score to all in window
366
  for j in range(i,end):
367
  timeStepAggregate[j][decision] += 1
368
  # Convert to list and sort for convenience
369
  memberSet = list(memberSet)
370
  memberSet.sort()
371
  timeStepMembers.append(memberSet)
372
+
373
+ # Iterate over aggregate scores for each window
374
  for i,item in enumerate(timeStepAggregate):
375
+ # Final classification is highest aggregate score
376
  cat = max(item, key=item.get)
377
  timeStepClass.append(cat)
378
  # For group decisions, members include all in window
379
  if cat == 'group':
380
  timeStepMembers[i] = '+'.join(timeStepMembers[i])
381
+ # Assume current speaker is the "individual" voice during window
382
  elif cat == 'individual':
383
  timeStepMembers[i] = sas[i]
384
+ # Remove all members if silence
385
  else:
386
  timeStepMembers[i] = None
387
 
388
+ # For debug purposes
 
389
  singleDimList = []
390
+
391
+ endTime = 0
392
+ # [group list, individual list, silence list]
393
  categorySegmentList = []
394
  for c in categories:
395
  currList = []
 
397
  currMembers = None
398
  duration = 0
399
  tracking = False
400
+ # Iterate over timestep to generate pyannote.core.Segment foreach classification and member(s)
401
  for stepClass,timeIncrement,members in zip(timeStepClass,st,timeStepMembers):
402
  # Check for case of exact end of audio
403
  if st == maxTime:
404
  continue
405
+ # If current timestep classifies for current categorySegment list
406
  if stepClass == c:
407
+ # If we see the exact same member(s), then increment time
408
  if currMembers == members:
 
409
  duration += min(stepSize,maxTime-timeIncrement)
410
  else:
411
+ # If already tracking, then generate Segment and restart tracking
412
  if tracking:
413
  singleDimList.append((currMembers,Segment(start,start+duration)))
414
  currList.append((currMembers,Segment(start,start+duration)))
 
422
  currMembers = members
423
  duration += min(stepSize,maxTime-timeIncrement)
424
  tracking = True
425
+ # Timestep does NOT belong to current categorySegment list
426
  else:
427
+ # If tracking, then generate Segment and stop tracking
428
  if tracking:
429
  singleDimList.append((currMembers,Segment(start,start+duration)))
430
  currList.append((currMembers,Segment(start,start+duration)))
 
434
  currMembers == None
435
  duration = 0
436
  tracking = False
437
+ # Exit case, if still tracking then generate final Segment
438
  if tracking:
439
  singleDimList.append((currMembers,Segment(start,start+duration)))
440
  currList.append((currMembers,Segment(start,start+duration)))
441
  if start+duration > endTime:
442
  endTime = start+duration
443
  categorySegmentList.append(currList)
444
+ # If we didn't end exactly on time, then fill in remaining time with Silence
445
  if endTime != maxTime:
446
  singleDimList.append((None,Segment(endTime,maxTime)))
447
  categorySegmentList[2].append((None,Segment(endTime,maxTime)))
 
452
  return categorySegmentList, st
453
 
454
  def __call__(self,audioPath):
455
+ '''
456
+ Apply Sonogram to a given audio file
457
+
458
+ ...
459
+
460
+ Parameters
461
+ ----------
462
+ audioPath : str
463
+ Full path to audio file to process
464
+
465
+ Returns
466
+ -------
467
+ Returns
468
+ -------
469
+ annotation : pyannote.core.Annotation
470
+ Diarization result from pipeline
471
+ totalTimeInSeconds : int
472
+ Approximate length of audio for use in diagrams
473
+ waveform : np.array
474
+ Audio waveform as loaded from file
475
+ sampleRate : int
476
+ Sample rate of loaded audio
477
+ '''
478
  annotation, totalTimeInSeconds, waveform, sampleRate = self.processFile(audioPath)
479
 
480
  return annotation, totalTimeInSeconds, waveform, sampleRate
481
 
482
  def toDevice(self):
483
+ '''
484
+ Move Sonogram pipeline to device for accelerated processing
485
+
486
+ ...
487
+ '''
488
  self.pipeline.to(self.device)
489
  print(f"Sonogram moved to {self.device}")
490
 
491
  def toCPU(self):
492
+ '''
493
+ Move Sonogram pipeline to CPU to free up device space
494
+
495
+ ...
496
+ '''
497
  self.pipeline.to(torch.device('cpu'))
498
  print(f"Sonogram moved to CPU")