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

André Anjos's avatar
André Anjos committed
6

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
7
def log_values(min_step=-4, counts_per_step=4):
André Anjos's avatar
André Anjos committed
8 9 10 11 12 13 14 15
  """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``.

16

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

André Anjos's avatar
André Anjos committed
19 20 21
    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%``
22

André Anjos's avatar
André Anjos committed
23 24 25 26
    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``.
27 28


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

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

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

36
  import math
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
37
  return [math.pow(10., i * 1. / counts_per_step) for i in range(min_step * counts_per_step, 0)] + [1.]
38

André Anjos's avatar
André Anjos committed
39

40 41
def _semilogx(x, y, **kwargs):
  # remove points were x is 0
42
  x, y = numpy.asarray(x), numpy.asarray(y)
43 44 45 46 47 48 49
  zero_index = x == 0
  x = x[~zero_index]
  y = y[~zero_index]
  from matplotlib import pyplot
  return pyplot.semilogx(x, y, **kwargs)


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

53
  This method will call ``matplotlib`` to plot the ROC curve for a system which
André Anjos's avatar
André Anjos committed
54
  contains a particular set of negatives (impostors) and positives (clients)
André Anjos's avatar
André Anjos committed
55 56 57
  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
58

André Anjos's avatar
André Anjos committed
59 60 61
  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
62

63
  .. note::
André Anjos's avatar
André Anjos committed
64

65 66 67
    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
68 69


André Anjos's avatar
André Anjos committed
70
  Parameters:
André Anjos's avatar
André Anjos committed
71

André Anjos's avatar
André Anjos committed
72 73 74
    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
75

André Anjos's avatar
André Anjos committed
76 77 78
    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
79

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

83 84
    CAR (:py:class:`bool`, optional): If set to ``True``, it will plot the CPR
      (CAR) over FPR in using :py:func:`matplotlib.pyplot.semilogx`, otherwise the
85
      FPR over FNR linearly using :py:func:`matplotlib.pyplot.plot`.
André Anjos's avatar
André Anjos committed
86

André Anjos's avatar
André Anjos committed
87 88
    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
89 90 91 92


  Returns:

93 94 95
    :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
96 97 98

  """

99
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
100
  from . import roc as calc
101
  out = calc(negatives, positives, npoints)
André Anjos's avatar
André Anjos committed
102
  if not CAR:
103
    return pyplot.plot(out[0, :], out[1, :], **kwargs)
André Anjos's avatar
André Anjos committed
104
  else:
105
    return _semilogx(out[0, :], (1 - out[1, :]), **kwargs)
106

André Anjos's avatar
André Anjos committed
107

108 109
def roc_for_far(negatives, positives, far_values=log_values(), CAR=True,
                **kwargs):
110
  """Plots the ROC curve for the given list of False Positive Rates (FAR).
111 112 113

  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
114 115 116
  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.
117

118 119
  The plot will represent the False Positive Rate (FPR) on the horizontal
  axis and the Correct Positive Rate (CPR) on the vertical axis.  The values
André Anjos's avatar
André Anjos committed
120
  for the axis will be computed using :py:func:`bob.measure.roc_for_far`.
121 122 123 124 125 126 127 128

  .. 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
129 130 131 132 133
  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`)
134

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

139
    far_values (:py:class:`list`, optional): The values for the FPR, where the
140
      CPR (CAR) should be plotted; each value should be in range [0,1].
141

142 143
    CAR (:py:class:`bool`, optional): If set to ``True``, it will plot the CPR
      (CAR) over FPR in using :py:func:`matplotlib.pyplot.semilogx`, otherwise the
144
      FPR over FNR linearly using :py:func:`matplotlib.pyplot.plot`.
145

André Anjos's avatar
André Anjos committed
146 147
    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
148 149 150 151


  Returns:

152 153 154
    :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`.
155 156 157 158 159 160

  """

  from matplotlib import pyplot
  from . import roc_for_far as calc
  out = calc(negatives, positives, far_values)
161 162 163 164
  if not CAR:
    return pyplot.plot(out[0, :], out[1, :], **kwargs)
  else:
    return _semilogx(out[0, :], (1 - out[1, :]), **kwargs)
165 166


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

André Anjos's avatar
André Anjos committed
170 171 172 173 174 175
  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
176

177
  .. note::
André Anjos's avatar
André Anjos committed
178

179 180 181
    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
182 183


André Anjos's avatar
André Anjos committed
184 185 186 187 188 189 190 191 192
  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
193

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

André Anjos's avatar
André Anjos committed
197 198
    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
199 200


André Anjos's avatar
André Anjos committed
201 202
  Returns:

203 204 205
    :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
206

André Anjos's avatar
André Anjos committed
207 208
  """

209
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
210 211
  from . import precision_recall_curve as calc
  out = calc(negatives, positives, npoints)
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
212
  return pyplot.plot(100.0 * out[0, :], 100.0 * out[1, :], **kwargs)
André Anjos's avatar
André Anjos committed
213 214 215


def epc(dev_negatives, dev_positives, test_negatives, test_positives,
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
216
        npoints=100, **kwargs):
André Anjos's avatar
André Anjos committed
217 218 219 220 221 222 223
  """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/

224
  This method will call ``matplotlib`` to plot the EPC curve for a system which
André Anjos's avatar
André Anjos committed
225 226
  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
227 228 229
  :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
230

André Anjos's avatar
André Anjos committed
231 232
  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
233

234
  .. note::
André Anjos's avatar
André Anjos committed
235

236 237 238
    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
239 240


André Anjos's avatar
André Anjos committed
241 242 243 244 245 246 247 248 249
  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
250

André Anjos's avatar
André Anjos committed
251 252 253
    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
254

André Anjos's avatar
André Anjos committed
255 256 257
    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
258

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

André Anjos's avatar
André Anjos committed
262 263
    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
264 265 266 267


  Returns:

268 269 270
    :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
271 272 273

  """

274
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
275 276
  from . import epc as calc

André Anjos's avatar
André Anjos committed
277
  out = calc(dev_negatives, dev_positives, test_negatives, test_positives,
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
278 279
             npoints)
  return pyplot.plot(out[0, :], 100.0 * out[1, :], **kwargs)
280

André Anjos's avatar
André Anjos committed
281

282
def det(negatives, positives, npoints=100, **kwargs):
André Anjos's avatar
André Anjos committed
283 284 285 286 287 288 289
  """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
290 291 292 293 294
  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
295

296 297
  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
298 299 300 301 302 303 304 305 306 307 308 309 310 311

  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::

312
    If you wish to reset axis zooming, you must use the Gaussian scale rather
André Anjos's avatar
André Anjos committed
313
    than the visual marks showed at the plot, which are just there for
André Anjos's avatar
André Anjos committed
314 315 316
    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
317 318 319

    .. code-block:: python

André Anjos's avatar
André Anjos committed
320
      import bob.measure
321
      from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
322
      bob.measure.plot.det(...) #call this as many times as you need
André Anjos's avatar
André Anjos committed
323
      #AFTER you plot the DET curve, just set the axis in this way:
324
      pyplot.axis([bob.measure.ppndf(k/100.0) for k in (1, 40, 1, 40)])
André Anjos's avatar
André Anjos committed
325 326

    We provide a convenient way for you to do the above in this module. So,
André Anjos's avatar
André Anjos committed
327 328
    optionally, you may use the :py:func:`bob.measure.plot.det_axis` method
    like this:
André Anjos's avatar
André Anjos committed
329 330 331

    .. code-block:: python

André Anjos's avatar
André Anjos committed
332 333
      import bob.measure
      bob.measure.plot.det(...)
André Anjos's avatar
André Anjos committed
334
      # please note we convert percentage values in det_axis()
André Anjos's avatar
André Anjos committed
335
      bob.measure.plot.det_axis([1, 40, 1, 40])
André Anjos's avatar
André Anjos committed
336 337


André Anjos's avatar
André Anjos committed
338 339 340 341 342
  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`)
343

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

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

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

André Anjos's avatar
André Anjos committed
354 355
    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
356 357 358 359


  Returns:

360 361 362
    :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`.
363 364

  """
André Anjos's avatar
André Anjos committed
365 366 367

  # these are some constants required in this method
  desiredTicks = [
368
      "0.000001", "0.000002", "0.000005",
André Anjos's avatar
André Anjos committed
369 370 371 372 373 374 375 376 377
      "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"
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
378
  ]
André Anjos's avatar
André Anjos committed
379 380

  desiredLabels = [
381
      "0.0001", "0.0002", "0.0005",
André Anjos's avatar
André Anjos committed
382 383 384 385 386 387 388 389 390
      "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"
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
391
  ]
André Anjos's avatar
André Anjos committed
392 393

  # this will actually do the plotting
394
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
395 396 397
  from . import det as calc
  from . import ppndf

398
  out = calc(negatives, positives, npoints)
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
399
  retval = pyplot.plot(out[0, :], out[1, :], **kwargs)
André Anjos's avatar
André Anjos committed
400 401 402

  # now the trick: we must plot the tick marks by hand using the PPNDF method
  pticks = [ppndf(float(v)) for v in desiredTicks]
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
403
  ax = pyplot.gca()  # and finally we set our own tick marks
André Anjos's avatar
André Anjos committed
404
  ax.set_xticks(pticks)
405
  ax.set_xticklabels(desiredLabels)
André Anjos's avatar
André Anjos committed
406
  ax.set_yticks(pticks)
407
  ax.set_yticklabels(desiredLabels)
André Anjos's avatar
André Anjos committed
408 409 410

  return retval

411

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

415
  This method wraps the :py:func:`matplotlib.pyplot.axis` by calling
André Anjos's avatar
André Anjos committed
416 417 418 419 420
  :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:
421

André Anjos's avatar
André Anjos committed
422 423 424 425 426
    v (``sequence``): A sequence (list, tuple, array or the like) containing
      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
427

André Anjos's avatar
André Anjos committed
428 429
    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
430 431


André Anjos's avatar
André Anjos committed
432 433 434
  Returns:

    object: Whatever is returned by :py:func:`matplotlib.pyplot.axis`.
André Anjos's avatar
André Anjos committed
435 436 437 438

  """

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

441
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
442 443 444 445
  from . import ppndf

  # treat input
  try:
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
446 447 448 449
    tv = list(v)  # normal input
    if len(tv) != 4:
      raise IndexError
    tv = [ppndf(float(k) / 100) for k in tv]
450
    cur = pyplot.axis()
André Anjos's avatar
André Anjos committed
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468

    # 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

469 470
  return pyplot.axis(tv, **kwargs)

André Anjos's avatar
André Anjos committed
471

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
472
def cmc(cmc_scores, logx=True, **kwargs):
André Anjos's avatar
André Anjos committed
473 474
  """Plots the (cumulative) match characteristics and returns the maximum rank.

475
  This function plots a CMC curve using the given CMC scores (:py:class:`list`:
476 477
      A list of tuples, where each tuple contains the
      ``negative`` and ``positive`` scores for one probe of the database).
André Anjos's avatar
André Anjos committed
478

479

André Anjos's avatar
André Anjos committed
480
  Parameters:
481

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

André Anjos's avatar
André Anjos committed
485 486 487
    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`
488

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

492

André Anjos's avatar
André Anjos committed
493 494 495
  Returns:

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

497
  """
André Anjos's avatar
André Anjos committed
498

499
  from matplotlib import pyplot
André Anjos's avatar
André Anjos committed
500
  from . import cmc as calc
501

André Anjos's avatar
André Anjos committed
502 503 504
  out = calc(cmc_scores)

  if logx:
505
    _semilogx(range(1, len(out) + 1), out, **kwargs)
André Anjos's avatar
André Anjos committed
506
  else:
507
    pyplot.plot(range(1, len(out) + 1), out, **kwargs)
André Anjos's avatar
André Anjos committed
508 509

  return len(out)
510 511


Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
512
def detection_identification_curve(cmc_scores, far_values=log_values(), rank=1, logx=True, **kwargs):
513
  """Plots the Detection & Identification curve over the FPR
514

André Anjos's avatar
André Anjos committed
515 516 517 518
  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``.
519

520
  The detection and identification curve first computes FPR thresholds based on
André Anjos's avatar
André Anjos committed
521 522 523 524
  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.
525

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


André Anjos's avatar
André Anjos committed
529
  Parameters:
530

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

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

537 538
    far_values (:py:class:`list`, optional): The values for the FPR (FAR), where the
      CPR (CAR) should be plotted; each value should be in range [0,1].
539

André Anjos's avatar
André Anjos committed
540 541 542
    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
543

André Anjos's avatar
André Anjos committed
544 545
    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
546 547 548 549


  Returns:

550 551 552
    :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`.
553 554 555

  """

556
  import numpy
557
  import math
558
  from matplotlib import pyplot
559
  from . import far_threshold, detection_identification_rate
560

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
561 562 563 564
  # 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)
565
  if not negatives:
Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
566 567
    raise ValueError(
        "There need to be at least one pair with only negative scores")
568 569 570 571

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

Amir MOHAMMADI's avatar
lint  
Amir MOHAMMADI committed
572 573
  # compute detection and identification rate based on the thresholds for
  # the given rank
574 575
  rates = [detection_identification_rate(cmc_scores, t, rank)
             if not math.isnan(t) else numpy.nan for t in thresholds]
576 577 578

  # plot curve
  if logx:
579
    return _semilogx(far_values, rates, **kwargs)
580 581
  else:
    return pyplot.plot(far_values, rates, **kwargs)