plot.py 19.7 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
4
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Mon 23 May 2011 14:36:14 CEST

André Anjos's avatar
André Anjos committed
5

6
def log_values(min_step = -4, counts_per_step = 4):
André Anjos's avatar
André Anjos committed
7
8
9
10
11
12
13
14
  """Computes log-scaled values between :math:`10^{M}` and 1

  This function computes log-scaled values between :math:`10^{M}` and 1
  (including), where :math:`M` is the ``min_ste`` argument, which needs to be a
  negative integer.  The integral ``counts_per_step`` value defines how many
  values between two adjacent powers of 10 will be created.  The total number
  of values will be ``-min_step * counts_per_step + 1``.

15

André Anjos's avatar
André Anjos committed
16
  Parameters:
17

André Anjos's avatar
André Anjos committed
18
19
20
    min_step (:py:class:`int`, optional): The power of 10 that will be the
      minimum value.  E.g., the default ``-4`` will result in the first number
      to be :math:`10^{-4}` = ``0.00001`` or ``0.01%``
21

André Anjos's avatar
André Anjos committed
22
23
24
25
    counts_per_step (:py:class:`int`, optional): The number of values that will
      be put between two adjacent powers of 10.  With the default value ``4``
      (and default values of ``min_step``), we will get ``log_list[0] ==
      1e-4``, ``log_list[4] == 1e-3``, ..., ``log_list[16] == 1``.
26
27


André Anjos's avatar
André Anjos committed
28
29
30
  Returns:

    list: A list of logarithmically scaled values between :math:`10^{M}` and 1.
31
32

  """
André Anjos's avatar
André Anjos committed
33

34
35
36
  import math
  return [math.pow(10., i * 1./counts_per_step) for i in range(min_step*counts_per_step,0)] + [1.]

André Anjos's avatar
André Anjos committed
37
38

def roc(negatives, positives, npoints=100, CAR=False, **kwargs):
39
  """Plots Receiver Operating Characteristic (ROC) curve.
André Anjos's avatar
André Anjos committed
40

41
  This method will call ``matplotlib`` to plot the ROC curve for a system which
André Anjos's avatar
André Anjos committed
42
  contains a particular set of negatives (impostors) and positives (clients)
André Anjos's avatar
André Anjos committed
43
44
45
  scores. We use the standard :py:func:`matplotlib.pyplot.plot` command. All
  parameters passed with exception of the three first parameters of this method
  will be directly passed to the plot command.
André Anjos's avatar
André Anjos committed
46

André Anjos's avatar
André Anjos committed
47
48
49
  The plot will represent the false-alarm on the horizontal axis and the
  false-rejection on the vertical axis.  The values for the axis will be
  computed using :py:func:`bob.measure.roc`.
André Anjos's avatar
André Anjos committed
50

51
  .. note::
André Anjos's avatar
André Anjos committed
52

53
54
55
    This function does not initiate and save the figure instance, it only
    issues the plotting command. You are the responsible for setting up and
    saving the figure as you see fit.
André Anjos's avatar
André Anjos committed
56
57


André Anjos's avatar
André Anjos committed
58
  Parameters:
André Anjos's avatar
André Anjos committed
59

André Anjos's avatar
André Anjos committed
60
61
62
    negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier. See
      (:py:func:`bob.measure.roc`)
André Anjos's avatar
André Anjos committed
63

André Anjos's avatar
André Anjos committed
64
65
66
    positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier. See
      (:py:func:`bob.measure.roc`)
André Anjos's avatar
André Anjos committed
67

André Anjos's avatar
André Anjos committed
68
    npoints (:py:class:`int`, optional): The number of points for the plot. See
André Anjos's avatar
André Anjos committed
69
      (:py:func:`bob.measure.roc`)
André Anjos's avatar
André Anjos committed
70

André Anjos's avatar
André Anjos committed
71
72
73
    CAR (:py:class:`bool`, optional): If set to ``True``, it will plot the CAR
      over FAR in using :py:func:`matplotlib.pyplot.semilogx`, otherwise the
      FAR over FRR linearly using :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
74

André Anjos's avatar
André Anjos committed
75
76
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
77
78
79
80


  Returns:

81
82
83
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
84
85
86

  """

87
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
88
  from . import roc as calc
89
  out = calc(negatives, positives, npoints)
André Anjos's avatar
André Anjos committed
90
  if not CAR:
91
    return pyplot.plot(100.0*out[0,:], 100.0*out[1,:], **kwargs)
André Anjos's avatar
André Anjos committed
92
  else:
93
94
    return pyplot.semilogx(100.0*out[0,:], 100.0*(1-out[1,:]), **kwargs)

André Anjos's avatar
André Anjos committed
95

96
def roc_for_far(negatives, positives, far_values = log_values(), **kwargs):
André Anjos's avatar
André Anjos committed
97
  """Plots the ROC curve for the given list of False Acceptance Rates (FAR).
98
99
100

  This method will call ``matplotlib`` to plot the ROC curve for a system which
  contains a particular set of negatives (impostors) and positives (clients)
André Anjos's avatar
André Anjos committed
101
102
103
  scores. We use the standard :py:func:`matplotlib.pyplot.semilogx` command.
  All parameters passed with exception of the three first parameters of this
  method will be directly passed to the plot command.
104

André Anjos's avatar
André Anjos committed
105
106
107
  The plot will represent the False Acceptance Rate (FAR) on the horizontal
  axis and the Correct Acceptance Rate (CAR) on the vertical axis.  The values
  for the axis will be computed using :py:func:`bob.measure.roc_for_far`.
108
109
110
111
112
113
114
115

  .. note::

    This function does not initiate and save the figure instance, it only
    issues the plotting command. You are the responsible for setting up and
    saving the figure as you see fit.


André Anjos's avatar
André Anjos committed
116
117
118
119
120
  Parameters:

    negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier. See
      (:py:func:`bob.measure.roc`)
121

André Anjos's avatar
André Anjos committed
122
123
124
    positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier. See
      (:py:func:`bob.measure.roc`)
125

André Anjos's avatar
André Anjos committed
126
127
    far_values (:py:class:`list`, optional): The values for the FAR, where the
      CAR should be plotted; each value should be in range [0,1].
128

André Anjos's avatar
André Anjos committed
129
130
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
131
132
133
134


  Returns:

135
136
137
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.semilogx`.
138
139
140
141
142
143
144
145
146

  """

  from matplotlib import pyplot
  from . import roc_for_far as calc
  out = calc(negatives, positives, far_values)
  return pyplot.semilogx(100.0*out[0,:], 100.0*(1-out[1,:]), **kwargs)


André Anjos's avatar
André Anjos committed
147
def precision_recall_curve(negatives, positives, npoints=100, **kwargs):
André Anjos's avatar
André Anjos committed
148
  """Plots a Precision-Recall curve.
André Anjos's avatar
André Anjos committed
149

André Anjos's avatar
André Anjos committed
150
151
152
153
154
155
  This method will call ``matplotlib`` to plot the precision-recall curve for a
  system which contains a particular set of ``negatives`` (impostors) and
  ``positives`` (clients) scores. We use the standard
  :py:func:`matplotlib.pyplot.plot` command. All parameters passed with
  exception of the three first parameters of this method will be directly
  passed to the plot command.
André Anjos's avatar
André Anjos committed
156

157
  .. note::
André Anjos's avatar
André Anjos committed
158

159
160
161
    This function does not initiate and save the figure instance, it only
    issues the plotting command. You are the responsible for setting up and
    saving the figure as you see fit.
André Anjos's avatar
André Anjos committed
162
163


André Anjos's avatar
André Anjos committed
164
165
166
167
168
169
170
171
172
  Parameters:

    negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier. See
      (:py:func:`bob.measure.precision_recall_curve`)

    positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier. See
      (:py:func:`bob.measure.precision_recall_curve`)
André Anjos's avatar
André Anjos committed
173

André Anjos's avatar
André Anjos committed
174
    npoints (:py:class:`int`, optional): The number of points for the plot. See
André Anjos's avatar
André Anjos committed
175
      (:py:func:`bob.measure.precision_recall_curve`)
André Anjos's avatar
André Anjos committed
176

André Anjos's avatar
André Anjos committed
177
178
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
179
180


André Anjos's avatar
André Anjos committed
181
182
  Returns:

183
184
185
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
186

André Anjos's avatar
André Anjos committed
187
188
  """

189
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
190
191
  from . import precision_recall_curve as calc
  out = calc(negatives, positives, npoints)
192
  return pyplot.plot(100.0*out[0,:], 100.0*out[1,:], **kwargs)
André Anjos's avatar
André Anjos committed
193
194
195
196
197
198
199
200
201
202
203


def epc(dev_negatives, dev_positives, test_negatives, test_positives,
    npoints=100, **kwargs):
  """Plots Expected Performance Curve (EPC) as defined in the paper:

  Bengio, S., Keller, M., Mariéthoz, J. (2004). The Expected Performance Curve.
  International Conference on Machine Learning ICML Workshop on ROC Analysis in
  Machine Learning, 136(1), 1963–1966. IDIAP RR. Available:
  http://eprints.pascal-network.org/archive/00000670/

204
  This method will call ``matplotlib`` to plot the EPC curve for a system which
André Anjos's avatar
André Anjos committed
205
206
  contains a particular set of negatives (impostors) and positives (clients)
  for both the development and test sets. We use the standard
André Anjos's avatar
André Anjos committed
207
208
209
  :py:func:`matplotlib.pyplot.plot` command. All parameters passed with
  exception of the five first parameters of this method will be directly passed
  to the plot command.
André Anjos's avatar
André Anjos committed
210

André Anjos's avatar
André Anjos committed
211
212
  The plot will represent the minimum HTER on the vertical axis and the cost on
  the horizontal axis.
André Anjos's avatar
André Anjos committed
213

214
  .. note::
André Anjos's avatar
André Anjos committed
215

216
217
218
    This function does not initiate and save the figure instance, it only
    issues the plotting commands. You are the responsible for setting up and
    saving the figure as you see fit.
André Anjos's avatar
André Anjos committed
219
220


André Anjos's avatar
André Anjos committed
221
222
223
224
225
226
227
228
229
  Parameters:

    dev_negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier, from the
      development set. See (:py:func:`bob.measure.epc`)

    dev_positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier, from the
      development set. See (:py:func:`bob.measure.epc`)
André Anjos's avatar
André Anjos committed
230

André Anjos's avatar
André Anjos committed
231
232
233
    test_negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier, from the test
      set. See (:py:func:`bob.measure.epc`)
André Anjos's avatar
André Anjos committed
234

André Anjos's avatar
André Anjos committed
235
236
237
    test_positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier, from the test set.
      See (:py:func:`bob.measure.epc`)
André Anjos's avatar
André Anjos committed
238

André Anjos's avatar
André Anjos committed
239
    npoints (:py:class:`int`, optional): The number of points for the plot. See
André Anjos's avatar
André Anjos committed
240
241
      (:py:func:`bob.measure.epc`)

André Anjos's avatar
André Anjos committed
242
243
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
244
245
246
247


  Returns:

248
249
250
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
251
252
253

  """

254
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
255
256
  from . import epc as calc

André Anjos's avatar
André Anjos committed
257
258
  out = calc(dev_negatives, dev_positives, test_negatives, test_positives,
      npoints)
259
260
  return pyplot.plot(out[0,:], 100.0*out[1,:], **kwargs)

André Anjos's avatar
André Anjos committed
261
262
263
264
265
266
267
268
269

def det(negatives, positives, npoints=100, axisfontsize='x-small', **kwargs):
  """Plots Detection Error Trade-off (DET) curve as defined in the paper:

  Martin, A., Doddington, G., Kamm, T., Ordowski, M., & Przybocki, M. (1997).
  The DET curve in assessment of detection task performance. Fifth European
  Conference on Speech Communication and Technology (pp. 1895-1898). Available:
  http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.117.4489&rep=rep1&type=pdf

André Anjos's avatar
André Anjos committed
270
271
272
273
274
  This method will call ``matplotlib`` to plot the DET curve(s) for a system
  which contains a particular set of negatives (impostors) and positives
  (clients) scores. We use the standard :py:func:`matplotlib.pyplot.plot`
  command. All parameters passed with exception of the three first parameters
  of this method will be directly passed to the plot command.
André Anjos's avatar
André Anjos committed
275

276
277
  The plot will represent the false-alarm on the horizontal axis and the
  false-rejection on the vertical axis.
André Anjos's avatar
André Anjos committed
278
279
280
281
282
283
284
285
286
287
288
289
290
291

  This method is strongly inspired by the NIST implementation for Matlab,
  called DETware, version 2.1 and available for download at the NIST website:

  http://www.itl.nist.gov/iad/mig/tools/

  .. note::

    This function does not initiate and save the figure instance, it only
    issues the plotting commands. You are the responsible for setting up and
    saving the figure as you see fit.

  .. note::

292
    If you wish to reset axis zooming, you must use the Gaussian scale rather
André Anjos's avatar
André Anjos committed
293
    than the visual marks showed at the plot, which are just there for
André Anjos's avatar
André Anjos committed
294
295
296
    displaying purposes. The real axis scale is based on
    :py:func:`bob.measure.ppndf`.  For example, if you wish to set the x and y
    axis to display data between 1% and 40% here is the recipe:
André Anjos's avatar
André Anjos committed
297
298
299

    .. code-block:: python

André Anjos's avatar
André Anjos committed
300
      import bob.measure
301
      from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
302
      bob.measure.plot.det(...) #call this as many times as you need
André Anjos's avatar
André Anjos committed
303
      #AFTER you plot the DET curve, just set the axis in this way:
304
      pyplot.axis([bob.measure.ppndf(k/100.0) for k in (1, 40, 1, 40)])
André Anjos's avatar
André Anjos committed
305
306

    We provide a convenient way for you to do the above in this module. So,
André Anjos's avatar
André Anjos committed
307
308
    optionally, you may use the :py:func:`bob.measure.plot.det_axis` method
    like this:
André Anjos's avatar
André Anjos committed
309
310
311

    .. code-block:: python

André Anjos's avatar
André Anjos committed
312
313
      import bob.measure
      bob.measure.plot.det(...)
André Anjos's avatar
André Anjos committed
314
      # please note we convert percentage values in det_axis()
André Anjos's avatar
André Anjos committed
315
      bob.measure.plot.det_axis([1, 40, 1, 40])
André Anjos's avatar
André Anjos committed
316
317


André Anjos's avatar
André Anjos committed
318
319
320
321
322
  Parameters:

    negatives (array): 1D float array that contains the scores of the
      "negative" (noise, non-class) samples of your classifier. See
      (:py:func:`bob.measure.det`)
323

André Anjos's avatar
André Anjos committed
324
325
326
    positives (array): 1D float array that contains the scores of the
      "positive" (signal, class) samples of your classifier. See
      (:py:func:`bob.measure.det`)
327

André Anjos's avatar
André Anjos committed
328
    npoints (:py:class:`int`, optional): The number of points for the plot. See
André Anjos's avatar
André Anjos committed
329
      (:py:func:`bob.measure.det`)
330

André Anjos's avatar
André Anjos committed
331
332
    axisfontsize (:py:class:`str`, optional): The size to be used by
      x/y-tick-labels to set the font size on the axis
333

André Anjos's avatar
André Anjos committed
334
335
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
336
337
338
339


  Returns:

340
341
342
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.plot`.
343
344

  """
André Anjos's avatar
André Anjos committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

  # these are some constants required in this method
  desiredTicks = [
      "0.00001", "0.00002", "0.00005",
      "0.0001", "0.0002", "0.0005",
      "0.001", "0.002", "0.005",
      "0.01", "0.02", "0.05",
      "0.1", "0.2", "0.4", "0.6", "0.8", "0.9",
      "0.95", "0.98", "0.99",
      "0.995", "0.998", "0.999",
      "0.9995", "0.9998", "0.9999",
      "0.99995", "0.99998", "0.99999"
      ]

  desiredLabels = [
      "0.001", "0.002", "0.005",
      "0.01", "0.02", "0.05",
      "0.1", "0.2", "0.5",
      "1", "2", "5",
      "10", "20", "40", "60", "80", "90",
      "95", "98", "99",
      "99.5", "99.8", "99.9",
      "99.95", "99.98", "99.99",
      "99.995", "99.998", "99.999"
      ]

  # this will actually do the plotting
372
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
373
374
375
  from . import det as calc
  from . import ppndf

376
  out = calc(negatives, positives, npoints)
377
  retval = pyplot.plot(out[0,:], out[1,:], **kwargs)
André Anjos's avatar
André Anjos committed
378
379
380

  # now the trick: we must plot the tick marks by hand using the PPNDF method
  pticks = [ppndf(float(v)) for v in desiredTicks]
381
  ax = pyplot.gca() #and finally we set our own tick marks
André Anjos's avatar
André Anjos committed
382
383
384
385
386
387
388
  ax.set_xticks(pticks)
  ax.set_xticklabels(desiredLabels, size=axisfontsize)
  ax.set_yticks(pticks)
  ax.set_yticklabels(desiredLabels, size=axisfontsize)

  return retval

389

André Anjos's avatar
André Anjos committed
390
391
392
def det_axis(v, **kwargs):
  """Sets the axis in a DET plot.

393
  This method wraps the :py:func:`matplotlib.pyplot.axis` by calling
André Anjos's avatar
André Anjos committed
394
395
396
397
398
  :py:func:`bob.measure.ppndf` on the values passed by the user so they are
  meaningful in a DET plot as performed by :py:func:`bob.measure.plot.det`.


  Parameters:
399

André Anjos's avatar
André Anjos committed
400
401
402
403
404
    v (:py:class:`list`, :py:class:`tuple`): A sequence contaiing the X and Y
      limits in the order ``(xmin, xmax, ymin, ymax)``. Expected values should
      be in percentage (between 0 and 100%).  If ``v`` is not a list or tuple
      that contains 4 numbers it is passed without further inspection to
      :py:func:`matplotlib.pyplot.axis`.
André Anjos's avatar
André Anjos committed
405

André Anjos's avatar
André Anjos committed
406
407
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.axis`.
André Anjos's avatar
André Anjos committed
408
409


André Anjos's avatar
André Anjos committed
410
411
412
  Returns:

    object: Whatever is returned by :py:func:`matplotlib.pyplot.axis`.
André Anjos's avatar
André Anjos committed
413
414
415
416

  """

  import logging
417
  logger = logging.getLogger("bob.measure")
André Anjos's avatar
André Anjos committed
418

419
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
420
421
422
423
424
425
426
  from . import ppndf

  # treat input
  try:
    tv = list(v) #normal input
    if len(tv) != 4: raise IndexError
    tv = [ppndf(float(k)/100) for k in tv]
427
    cur = pyplot.axis()
André Anjos's avatar
André Anjos committed
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445

    # limits must be within bounds
    if tv[0] < cur[0]:
      logger.warn("Readjusting xmin: the provided value is out of bounds")
      tv[0] = cur[0]
    if tv[1] > cur[1]:
      logger.warn("Readjusting xmax: the provided value is out of bounds")
      tv[1] = cur[1]
    if tv[2] < cur[2]:
      logger.warn("Readjusting ymin: the provided value is out of bounds")
      tv[2] = cur[2]
    if tv[3] > cur[3]:
      logger.warn("Readjusting ymax: the provided value is out of bounds")
      tv[3] = cur[3]

  except:
    tv = v

446
447
  return pyplot.axis(tv, **kwargs)

André Anjos's avatar
André Anjos committed
448
449

def cmc(cmc_scores, logx = True, **kwargs):
André Anjos's avatar
André Anjos committed
450
451
452
453
454
455
456
457
458
459
  """Plots the (cumulative) match characteristics and returns the maximum rank.

  This function plots a CMC curve using the given CMC scores, which can be read
  from the our score files using the
  :py:func:`bob.measure.load.cmc_four_column` or
  :py:func:`bob.measure.load.cmc_five_column` methods.  The structure of the
  ``cmc_scores`` parameter is relatively complex.  It contains a list of pairs
  of lists.  For each probe object, a pair of list negative and positive scores
  is required.

460

André Anjos's avatar
André Anjos committed
461
  Parameters:
462

André Anjos's avatar
André Anjos committed
463
464
    cmc_scores (array): 1D float array containing the CMC values (See
      :py:func:`bob.measure.cmc`)
465

André Anjos's avatar
André Anjos committed
466
467
468
    logx (:py:class:`bool`, optional): If set (the default), plots the rank
      axis in logarithmic scale using :py:func:`matplotlib.pyplot.semilogx` or
      in linear scale using :py:func:`matplotlib.pyplot.plot`
469

André Anjos's avatar
André Anjos committed
470
471
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
472

473

André Anjos's avatar
André Anjos committed
474
475
476
  Returns:

    int: The number of classes (clients) in the given scores.
André Anjos's avatar
André Anjos committed
477

478
  """
André Anjos's avatar
André Anjos committed
479

480
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
481
  from . import cmc as calc
482

André Anjos's avatar
André Anjos committed
483
484
485
  out = calc(cmc_scores)

  if logx:
486
    pyplot.semilogx(range(1, len(out)+1), out * 100, **kwargs)
André Anjos's avatar
André Anjos committed
487
  else:
488
    pyplot.plot(range(1, len(out)+1), out * 100, **kwargs)
André Anjos's avatar
André Anjos committed
489
490

  return len(out)
491
492


André Anjos's avatar
André Anjos committed
493
494
495
def detection_identification_curve(cmc_scores, far_values = log_values(), rank
    = 1, logx = True, **kwargs):
  """Plots the Detection & Identification curve over the FAR
496

André Anjos's avatar
André Anjos committed
497
498
499
500
  This curve is designed to be used in an open set identification protocol, and
  defined in Chapter 14.1 of [LiJain2005]_.  It requires to have at least one
  open set probe item, i.e., with no corresponding gallery, such that the
  positives for that pair are ``None``.
501

André Anjos's avatar
André Anjos committed
502
503
504
505
506
  The detection and identification curve first computes FAR thresholds based on
  the out-of-set probe scores (negative scores).  For each probe item, the
  **maximum** negative score is used.  Then, it plots the detection and
  identification rates for those thresholds, which are based on the in-set
  probe scores only. See [LiJain2005]_ for more details.
507

André Anjos's avatar
André Anjos committed
508
  .. [LiJain2005] **Stan Li and Anil K. Jain**, *Handbook of Face Recognition*, Springer, 2005
509
510


André Anjos's avatar
André Anjos committed
511
  Parameters:
512

André Anjos's avatar
André Anjos committed
513
514
    cmc_scores (array): 1D float array containing the CMC values (See
      :py:func:`bob.measure.cmc`)
515

André Anjos's avatar
André Anjos committed
516
517
    rank (:py:class:`int`, optional): The rank for which the curve should be
      plotted
518

André Anjos's avatar
André Anjos committed
519
520
    far_values (:py:class:`list`, optional): The values for the FAR, where the
      CAR should be plotted; each value should be in range [0,1].
521

André Anjos's avatar
André Anjos committed
522
523
524
    logx (:py:class:`bool`, optional): If set (the default), plots the rank
      axis in logarithmic scale using :py:func:`matplotlib.pyplot.semilogx` or
      in linear scale using :py:func:`matplotlib.pyplot.plot`
André Anjos's avatar
André Anjos committed
525

André Anjos's avatar
André Anjos committed
526
527
    kwargs (:py:class:`dict`, optional): Extra plotting parameters, which are
      passed directly to :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
528
529
530
531


  Returns:

532
533
534
    :py:class:`list` of :py:class:`matplotlib.lines.Line2D`: The lines that
    were added as defined by the return value of
    :py:func:`matplotlib.pyplot.plot`.
535
536
537

  """

538
  import numpy
539
  from matplotlib import pyplot
540
  from . import far_threshold, detection_identification_rate
541

542
543
544
545
  # for each probe, for which no positives exists, get the highest negative score; and sort them to compute the FAR thresholds
  negatives = sorted(max(neg) for neg,pos in cmc_scores if (pos is None or not numpy.array(pos).size) and neg is not None)
  if not negatives:
    raise ValueError("There need to be at least one pair with only negative scores")
546
547
548
549

  # compute thresholds based on FAR values
  thresholds = [far_threshold(negatives, [], v, True) for v in far_values]

550
551
  # compute detection and identification rate based on the thresholds for the given rank
  rates = [100.*detection_identification_rate(cmc_scores, t, rank) for t in thresholds]
552
553
554
555
556
557

  # plot curve
  if logx:
    return pyplot.semilogx(far_values, rates, **kwargs)
  else:
    return pyplot.plot(far_values, rates, **kwargs)