scoring.py 17.9 KB
Newer Older
Anthony Larcher's avatar
Anthony Larcher committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding: utf-8 -*-
#
# This file is part of s4d.
#
# s4d is a python package for speaker diarization.
# Home page: http://www-lium.univ-lemans.fr/s4d/
#
# s4d is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
#
# s4d is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with s4d.  If not, see <http://www.gnu.org/licenses/>.


"""
Anthony Larcher's avatar
Anthony Larcher committed
23
Copyright 2014-2020 Sylvain Meignier
Anthony Larcher's avatar
Anthony Larcher committed
24
25
"""

Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
26
27
import copy
import numpy as np
Anthony Larcher's avatar
Anthony Larcher committed
28
29

from scipy.optimize import linear_sum_assignment
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
30

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
31

Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
32
33
34
35
36
37
38
39
40
41
class DER:
    """ Class to compute a s4d error rate.
    Error is computed at a rate of 10ms.

    :attr uem_set: a set of index to evaluate
    :attr collar: is the no-score zone around reference speaker segment
    boundaries.  (Speaker Diarization output is not evaluated within +/- collar
    seconds of a reference speaker segment boundary.)
    :attr uem_set_collar: a set of index on with the collar is applied
    :attr length: the last index in the reference
42
    :attr ref_cluster_list: the list of reference cluster_list
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
43
    :attr ref_mat: the numpy.ndarry of reference indexes, each row correspond
44
45
    to a reference cluster (same order as ref_cluster_list)
    :attr hyp_cluster_list: the list of hypothesis cluster_list
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
46
    :attr hyp_mat: the numpy.ndarry of reference indexes, each row correspond
47
    to a hypothesis cluster (same order as ref_cluster_list)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
48
49
    :attr conf: the confusion matrix
    :attr assigned: a numpy.ndarray given the association between the references and hypotheses
50
    :attr not_assigned: a list of hypothesis cluster_list not associated to a reference cluster
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
51
52

    """
53
    def __init__(self, hyp_diarization, ref_diarization, uem_diarization = None, collar=0, no_overlap=False):
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
54
55
        """

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
56
57
        :param hyp_diarization: a hypothesis Diar object
        :param ref_diarization: a reference Diar object
58
        :param uem_diarization: a uem Diar object
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
59
60
61
62
63
        :param collar: is the no-score zone around reference speaker segments
        :param no_overlap: to limit scoring to those time regions in which only
        a single speaker is speaking

        """
64
        uem = uem_diarization
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
65
        self.collar = collar
Sylvain Meignier's avatar
Sylvain Meignier committed
66
        self.match = None
67
68
        if uem_diarization is not None:
            uem = copy.deepcopy(uem_diarization)
69
70
            if len(uem.unique('cluster')) > 1:
                raise Exception('UEM contains more than one cluster')
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
71
72
73
74
            if len(uem.unique('show')) > 1:
                raise Exception('UEM contains more than one show')
            self.uem_set = set(uem.features())

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
75
        ref = copy.deepcopy(ref_diarization)
Sylvain Meignier's avatar
Sylvain Meignier committed
76
        ref.pack()
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
77
78
79
80
81
        lst = ref.unique('show')
        if len(lst) > 1:
            raise Exception('REF contains more than one show')
        else:
            self.show = lst[0]
82
        if uem_diarization is None:
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
83
            print('uem from ref')
84
            self.uem_set = set([i for i in range(min(ref.unique('start')), max(ref.unique('stop')))])
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
85
86
87
88

        if collar > 0:
            rem = list()
            for row in ref:
89
90
                rem += [i for i in range(row['start']-collar, row['start']+collar)]
                rem += [i for i in range(row['stop']-collar, row['stop']+collar)]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
91
92
93
94
95
            self.uem_set_collar = self.uem_set - set(rem)
        else:
            self.uem_set_collar = self.uem_set

        self.length = max(self.uem_set_collar)+1
96
97
98
        self.ref_cluster_list = ref.unique('cluster')
        ref_idx = ref.features_by_cluster()
        self.ref_mat = self._to_bool(ref_idx, self.ref_cluster_list, self.uem_set_collar)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
99
100
101
102
103
104
        if no_overlap:
            print('remove overlap')
            sum = np.sum(self.ref_mat, axis=0)
            idx = np.squeeze(np.asarray(np.argwhere(sum == 1))).tolist()
            self.uem_set_collar = self.uem_set & set(idx)
            self.length = max(self.uem_set_collar)+1
105
            self.ref_mat = self._to_bool(ref_idx, self.ref_cluster_list, self.uem_set_collar)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
106

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
107
        hyp = copy.deepcopy(hyp_diarization)
Sylvain Meignier's avatar
Sylvain Meignier committed
108
        hyp.pack()
109
110
111
        self.hyp_cluster_list = hyp.unique('cluster')
        hyp_idx = hyp.features_by_cluster()
        self.hyp_mat = self._to_bool(hyp_idx, self.hyp_cluster_list, self.uem_set_collar)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
112
113
114
115
116

        self.conf = None
        self.assigned = None
        self.not_assigned = None

117
118
    def _to_bool(self, map, cluster_list, uem_set):
        """Transform the dict of segments indexed by cluster into a matrix
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
119
120
121
        of booleans.

        :param map: dict of segments indexed by speaker
122
        :param cluster_list: list of cluster_list
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
123
124
125
        :param uem_set: the segments is filtered according to the uem


126
        :return: a numpy.ndarray object of shape (#cluster, #features)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
127
        """
128
        mat = np.zeros((len(cluster_list), self.length))
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
129

130
131
        for i, cluster in enumerate(cluster_list):
            mat[i, list(set(map[cluster]) & uem_set)] = True
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
132
133
134
135
136
137
138
        return mat

    def confusion(self):
        """Compute the confusion matrix between reference and hypothesis

        :return: a numpy.ndarray object of shape (#reference, #hypothesis)
        """
Sylvain Meignier's avatar
stable    
Sylvain Meignier committed
139
140
141
142
143
144
145
146
        #nh = len(self.hyp_cluster_list)
        #nr = len(self.ref_cluster_list)
        #self.conf = np.full((nr, nh), 0.0)
        #for i in range(nr):
        #    for j in range(nh):
        #        self.conf[i, j] = np.sum(np.logical_and(self.ref_mat[i], self.hyp_mat[j]))

        self.conf = np.dot(self.ref_mat, self.hyp_mat.transpose())
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
147
148
149
150
151
152
153
154
155
156
157
158

        return self.conf

    def confusion2str(self, all_value=False):
        """Generate a string version of the confusion matrix

        :param all_value: if false, only the confision score upper to 0 is
        generated

        :return: a str
        """
        mr = mh = 0
159
        for ch in self.ref_cluster_list+['reference']:
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
160
161
            if mr < len(ch):
                mr = len(ch)
162
        for ch in self.hyp_cluster_list+['hypothesis']:
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
163
164
165
166
167
168
            if mh < len(ch):
                mh = len(ch)

        f = '{:'+str(mr)+'} {:'+str(mh)+'} {:>9}\n'
        line = f.format('reference', 'hypothesis', 'conf')
        f = '{:'+str(mr)+'} {:'+str(mh)+'} {:>8.2f}s\n'
169
170
        nh = len(self.hyp_cluster_list)
        nr = len(self.ref_cluster_list)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
171
172
173
174

        for i in range(nr):
            for j in range(nh):
                if self.conf[i, j] > 0 or all_value == True:
175
176
                    r = self.ref_cluster_list[i]
                    h = self.hyp_cluster_list[j]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
177
178
179
180
181
                    s = self.conf[i, j]/100
                    line += f.format(r, h, s)
        return line

    def assignment(self):
182
        """Compute the reference to hypothese association using the hungarian
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
183
184
        algorithm

185
        :return: a dict object with key equal to (reference cluster, hypothesis cluster)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
186
        and value equal to the confusion score
187
        :return: a list object containing the hypothesis cluster_list that are not assigned
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
188
        """
189
        self.assigned = np.stack(linear_sum_assignment(-1.0 * self.conf), 1)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
190
191
        name_assigned = dict()
        self.match = 0;
192
        self.not_assigned = copy.deepcopy(self.hyp_cluster_list)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
193
194

        for i in range(self.assigned.shape[0]):
195
196
            r = self.ref_cluster_list[self.assigned[i, 0]]
            h = self.hyp_cluster_list[self.assigned[i, 1]]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
197
198
199
200
201
202
203
204
205
206
207
208
            s = self.conf[self.assigned[i, 0], self.assigned[i, 1]]
            if s > 0:
                self.not_assigned.remove(h)
                name_assigned[(r, h)] = s
                self.match += s
            else:
                self.assigned[i, 0] = -1
                self.assigned[i, 1] = -1

        return name_assigned, self.not_assigned

    def set_assignment(self, name_assigned):
209
        """Set assignment from an external source (from a collection of cluster_list)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
210
211

        :param: name_assigned is a dict object with key equal to
212
        (reference cluster, hypothesis cluster) and value equal to the confusion
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
213
214
215
        score
        """
        i = 0;
216
        self.assigned = np.ones((len(self.ref_cluster_list), 2)) * -1
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
217
218

        for key in name_assigned:
Sylvain Meignier's avatar
Sylvain Meignier committed
219
            # print(key)
220
221
222
            if key[0] in self.ref_cluster_list and key[1] in self.hyp_cluster_list:
                r = self.ref_cluster_list.index(key[0])
                h = self.hyp_cluster_list.index(key[1])
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
223
224
225
226
227
228
229
230
231
232
233
234
235
                self.assigned[i, 0] = r
                self.assigned[i, 1] = h
                i += 1

    def assignment2str(self):
        """Generate a string version of the assignment matrix attribut

        :return: a str
        """
        if self.assigned is None:
            return "Assignment is not set"
        mr = mh = 0

236
        for ch in self.ref_cluster_list+['reference']:
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
237
238
            if mr < len(ch):
                mr = len(ch)
239
        for ch in self.hyp_cluster_list+['hypothesis']:
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
240
241
242
243
244
245
246
247
            if mh < len(ch):
                mh = len(ch)
        f = '{:'+str(mr)+'} {:'+str(mh)+'} {:>9}\n'
        line = f.format('reference', 'hypothesis', 'matching')
        f = '{:'+str(mr)+'} {:'+str(mh)+'} {:>8.2f}s {:d} {:d}\n'

        for i in range(self.assigned.shape[0]):
            if self.assigned[i, 0] >= 0:
248
249
                r = self.ref_cluster_list[self.assigned[i, 0]]
                h = self.hyp_cluster_list[self.assigned[i, 1]]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
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
                s = self.conf[self.assigned[i, 0], self.assigned[i, 1]]/100
                line += f.format(r, h, s, self.assigned[i, 0], self.assigned[i, 1])

        r = 'None'
        s = 0.0
        for h in self.not_assigned:
            line += f.format(r, h, s, -1, -1)

        return line

    def _correct(self):
        """compute the correct decision

        :return: a numpy.ndarray
        """
        c = np.zeros(self.length)
        for i in range(self.assigned.shape[0]):
            if self.assigned[i, 0] >= 0 and self.assigned[i, 1] >= 0:
                diff = np.logical_and(self.ref_mat[self.assigned[i, 0]], self.hyp_mat[self.assigned[i, 1]])
                c += diff
        return c

    def error(self):
        """compute the s4d error

        :return: a DER_result object
        """
        h_sum = np.sum(self.hyp_mat, axis=0)
        r_sum = np.sum(self.ref_mat, axis=0)
        c = self._correct()

        res = DER_result(self.show)
        res.uem_time = len(self.uem_set)
        res.uem_with_collar_time = len(self.uem_set_collar)
        res.uem_collar_time = res.uem_time - res.uem_with_collar_time

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
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
        z = np.zeros(r_sum.shape[0])

        mrh = np.sum(np.maximum(r_sum - h_sum, z))
        mhr = np.sum(np.maximum(h_sum - r_sum, z))

        res.speech_time = np.count_nonzero(r_sum)
        res.sns_miss_time = np.count_nonzero(np.logical_and(r_sum, np.logical_not(h_sum)))
        res.sns_fa_time = np.count_nonzero(np.logical_and(h_sum, np.logical_not(r_sum)))
        res.spk_time = r_sum.sum()
        res.spk_miss_time = mrh
        res.spk_fa_time = mhr
        res.spk_conf_time = np.sum(np.minimum(h_sum, r_sum) - c)

        # for i in range(c.shape[0]):
        #     ref = r_sum[i]
        #     hyp = h_sum[i]
        #     mrh = max(ref - hyp, 0)
        #     mhr = max(hyp - ref, 0)
        #     res.speech_time += bool(ref)
        #     res.sns_miss_time += bool(ref and not hyp)
        #     res.sns_fa_time += bool(hyp and not ref)
        #     if ref > 1:
        #         res.overlap_time += ref
        #         res.overlap_miss_time += mrh
        #         res.overlap_fa_time += mhr
        #     res.spk_time += ref
        #     res.spk_miss_time += mrh
        #     res.spk_fa_time += mhr
        #     res.spk_conf_time += min(hyp, ref) - c[i]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
315
316
317
318
        res.compute_rate()
        return res

class DER_coll(DER):
319
320
321
322
323
324
    def __init__(self, ref_cluster_list, hyp_cluster_list):
        self.ref_cluster_list = ref_cluster_list
        self.hyp_cluster_list = hyp_cluster_list
        hyp_nb_clusters = len(hyp_cluster_list)
        ref_nb_clusters = len(ref_cluster_list)
        self.conf = np.full((ref_nb_clusters, hyp_nb_clusters), 0.0)
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
325
326

    def merge_confusion(self, der):
327
328
        for i in range(len(der.ref_cluster_list)):
            for j in range(len(der.hyp_cluster_list)):
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
329
330
                v = der.conf[i, j]
                if v > 0:
331
332
                    ig = self.ref_cluster_list.index(der.ref_cluster_list[i])
                    jg = self.hyp_cluster_list.index(der.hyp_cluster_list[j])
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
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
                    self.conf[ig, jg] += v

class DER_result:
    """
    Class to store the s4d error results
    """
    def __init__(self, show):
        """

        :param show: a string
        """
        self.show = show

        self.uem_time = 0
        self.uem_with_collar_time = 0
        self.uem_collar_time = 0
        self.spk_time = 0
        self.speech_time = 0
        self.sns_time = 0
        self.overlap_time = 0
        self.overlap_miss_time = 0
        self.overlap_fa_time = 0
        self.spk_conf_time = 0
        self.sns_fa_time = 0
        self.sns_miss_time = 0
        self.overlap_miss = 0
359
        self.overlap_fa = 0
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
360
361
362
363
364
365
366
        self.spk_miss_time = 0
        self.spk_fa_time = 0
        self.spk_miss = 0
        self.spk_fa = 0
        self.spk_conf = 0
        self.speech_fa = 0
        self.speech_miss = 0
Sylvain Meignier's avatar
Sylvain Meignier committed
367
        self.len_line = 0
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387

    def accumulate(self, result):
        """
        Accumulate the scores
        :param result: a DER_result object to accumulate
        :return:
        """
        self.uem_time += result.uem_time
        self.uem_with_collar_time += result.uem_with_collar_time
        self.uem_collar_time += result.uem_collar_time
        self.spk_time += result.spk_time
        self.speech_time += result.speech_time
        self.sns_time += result.sns_time
        self.overlap_time += result.overlap_time
        self.overlap_miss_time += result.overlap_miss_time
        self.overlap_fa_time += result.overlap_fa_time
        self.spk_conf_time += result.spk_conf_time
        self.sns_fa_time += result.sns_fa_time
        self.sns_miss_time += result.sns_miss_time
        self.overlap_miss += result.overlap_miss
388
        self.overlap_fa += result.overlap_fa
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
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
        self.spk_miss_time += result.spk_miss_time
        self.spk_fa_time += result.spk_fa_time
        self.spk_miss += result.spk_miss
        self.spk_fa += result.spk_fa
        self.spk_conf += result.spk_conf
        self.speech_fa += result.speech_fa
        self.speech_miss += result.speech_miss

    def compute_rate(self):
        """
        Compute the various rates
        """
        self.sns_fa = self.sns_fa_time / self.uem_time * 100
        self.sns_miss = self.sns_miss_time / self.uem_time * 100

        if self.overlap_time > 0:
            self.overlap_fa = self.overlap_fa_time / self.overlap_time * 100
            self.overlap_miss = self.overlap_miss_time / self.overlap_time * 100
        else:
            self.overlap_fa = 0
            self.overlap_miss = 0

        self.spk_fa = self.spk_fa_time / self.spk_time * 100
        self.spk_miss = self.spk_miss_time / self.spk_time * 100
        self.spk_conf = self.spk_conf_time / self.spk_time * 100

Sylvain Meignier's avatar
Sylvain Meignier committed
415
416
    @classmethod
    def header(cls, speech=True, overlap=False, speaker=True):
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
417
418
419
420
421
422
423
        """
        Make the header
        :param speech: add speech/non-speech results
        :param overlap: add overlap result
        :param speaker: add speaker result (DER)
        :return: a str
        """
Sylvain Meignier's avatar
Sylvain Meignier committed
424
        line = ['show', 'type']
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
425
        if speech:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
426
            line += ['fa', 'miss', 'sns']
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
427
        if overlap:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
428
            line += ['fa', 'miss', 'overlap']
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
429
        if speaker:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
430
            line += ['fa', 'miss', 'conf', 'speaker']
Sylvain Meignier's avatar
Sylvain Meignier committed
431

Sylvain Meignier's avatar
Sylvain Meignier committed
432
        return [line]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
433

Sylvain Meignier's avatar
Sylvain Meignier committed
434
    def rate(self, name=None, speech=True, overlap=False, speaker=True):
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
435
436
437
438
439
440
441
        """
        Make the rate results
        :param speech: add speech/non-speech results
        :param overlap: add overlap result
        :param speaker: add speaker result (DER)
        :return: a str
        """
Sylvain Meignier's avatar
Sylvain Meignier committed
442
443
444
        if name is None:
            name = ''
        line = [name, 'rate']
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
445
        if speech:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
446
            line += [self.sns_fa, self.sns_miss, self.sns_fa+self.sns_miss]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
447
        if overlap:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
448
            line += [self.overlap_fa, self.overlap_miss, self.overlap_fa+self.overlap_miss]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
449
        if speaker:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
450
            line += [self.spk_fa, self.spk_miss, self.spk_conf, self.get_der()]
Sylvain Meignier's avatar
Sylvain Meignier committed
451
        return [line]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
452

Sylvain Meignier's avatar
Sylvain Meignier committed
453
    def time(self, name=None, speech=True, overlap=False, speaker=True):
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
454
455
456
457
458
459
460
        """
        Make the time results
        :param speech: add speech/non-speech results
        :param overlap: add overlap result
        :param speaker: add speaker result (DER)
        :return: a str
        """
Sylvain Meignier's avatar
Sylvain Meignier committed
461
462
463
        if name is None:
            name = ''
        line = [name, 'time']
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
464
        if speech:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
465
            line += [self.sns_fa_time/100, self.sns_miss_time/100, self.uem_with_collar_time/100]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
466
        if overlap:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
467
            line += [self.overlap_fa_time/100, self.overlap_miss_time/100, self.overlap_time/100]
Sylvain Meignier's avatar
Origin  
Sylvain Meignier committed
468
        if speaker:
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
469
            line += [self.spk_fa_time/100, self.spk_miss_time/100, self.spk_conf_time/100, self.spk_time/100]
Sylvain Meignier's avatar
Sylvain Meignier committed
470
471
472
473
474
        return [line]

    def get_table(self, name=None, time=True, rate=True, overlap=False):
        lst = []
        add_name = False
Sylvain Meignier's avatar
Sylvain Meignier committed
475
476
        if rate:
            lst += self.rate(name=name, overlap=overlap)
Sylvain Meignier's avatar
Sylvain Meignier committed
477
478
            add_name = True

Sylvain Meignier's avatar
Sylvain Meignier committed
479
480
481
482
483
        if time:
            if not add_name:
                lst += self.time(name=name, overlap=overlap)
            else:
                lst += self.time(name=None)
Sylvain Meignier's avatar
Sylvain Meignier committed
484

Sylvain Meignier's avatar
Sylvain Meignier committed
485
        #if add_name == False and overlap:
Sylvain Meignier's avatar
Sylvain Meignier committed
486
        #    lst += self.overlap(speaker=speaker)
Sylvain Meignier's avatar
Sylvain Meignier committed
487
        #    add_name = True
Sylvain Meignier's avatar
??    
Sylvain Meignier committed
488
        #else:
Sylvain Meignier's avatar
Sylvain Meignier committed
489
            #lst += self.overlap(speaker=None)
Sylvain Meignier's avatar
Sylvain Meignier committed
490
        return lst
Sylvain Meignier's avatar
Sylvain Meignier committed
491

Sylvain Meignier's avatar
Sylvain Meignier committed
492
493
    def get_der(self):
        return self.spk_fa+self.spk_miss+self.spk_conf
Sylvain Meignier's avatar
Sylvain Meignier committed
494

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
495

Sylvain Meignier's avatar
Sylvain Meignier committed
496
497
def get_header():
    return DER_result.header()
Sylvain Meignier's avatar
Sylvain Meignier committed
498

Sylvain Meignier's avatar
new    
Sylvain Meignier committed
499

Sylvain Meignier's avatar
Sylvain Meignier committed
500
501
502
503
504
def compute_der(hyp, ref, uem=None, collar=0, no_overlap=True):
    cder = DER(hyp, ref, uem, collar=collar, no_overlap=no_overlap)
    cder.confusion()
    cder.assignment()
    return cder.error()