From 255c4b4d667f3198dea0f8d6f2bb85eb89d57a60 Mon Sep 17 00:00:00 2001
From: Theophile GENTILHOMME <tgentilhomme@jurasix08.idiap.ch>
Date: Thu, 21 Jun 2018 14:59:06 +0200
Subject: [PATCH] [script][figure] Fix bug with histo legends and add
 comments/doc in the code

---
 bob/measure/script/figure.py | 40 ++++++++++++++++++++++++++++--------
 1 file changed, 31 insertions(+), 9 deletions(-)

diff --git a/bob/measure/script/figure.py b/bob/measure/script/figure.py
index b063ed2..b0f9ba8 100644
--- a/bob/measure/script/figure.py
+++ b/bob/measure/script/figure.py
@@ -571,14 +571,18 @@ class Hist(PlotBase):
         self._thres = check_list_value(
             self._thres, self.n_systems, 'thresholds')
         self._criterion = ctx.meta.get('criterion')
+        # no vertical (threshold) is displayed
         self._no_line = ctx.meta.get('no_line', False)
+        # subplot grid
         self._nrows = ctx.meta.get('n_row', 1)
         self._ncols = ctx.meta.get('n_col', 1)
+        # do not display dev histo
         self._hide_dev = ctx.meta.get('hide_dev', False)
         # dev hist are displayed next to eval hist
         self._ncols *= 1 if self._hide_dev else 2
-        self._nlegends = ctx.meta.get('legends_ncol', 10)
+        self._nlegends = ctx.meta.get('legends_ncol', 3)
         self._legend_loc = self._legend_loc or 'upper center'
+        # number of subplot on one page
         self._step_print = int(self._nrows * self._ncols)
         self._title_base = 'Scores'
         self._y_label = 'Probability density'
@@ -586,6 +590,7 @@ class Hist(PlotBase):
         self._end_setup_plot = False
         if self._legends is not None and len(self._legends) == self.n_systems \
            and not self._hide_dev:
+            # use same legend for dev and eval if needed
             self._legends = [x for pair in zip(self._legends,self._legends)
                              for x in pair]
 
@@ -605,6 +610,7 @@ class Hist(PlotBase):
                                 not self._no_line, dflt_title="Eval scores")
 
     def _print_subplot(self, idx, neg, pos, threshold, draw_line, dflt_title):
+        ''' print a subplot for the given score and subplot index'''
         n = idx % self._step_print
         col = n % self._ncols
         sub_plot_idx = n + 1
@@ -624,16 +630,20 @@ class Hist(PlotBase):
         )
         if draw_line:
             self._lines(threshold, label, neg, pos, idx)
-        if sub_plot_idx == 1:
-            self._plot_legends()
+
         mult = 2 if self._eval and not self._hide_dev else 1
+        # if it was the last subplot of the page or the last subplot
+        # to display, save figure
         if self._step_print == sub_plot_idx or idx == self.n_systems * mult - 1:
+            # print legend on the page
+            self.plot_legends()
             mpl.tight_layout()
             self._pdf_page.savefig(mpl.gcf(), bbox_inches='tight')
             mpl.clf()
             mpl.figure()
 
     def _get_title(self, idx, dflt=None):
+        ''' Get the histo title for the given idx'''
         title = self._legends[idx] if self._legends is not None \
             and idx < len(self._legends) else dflt
         title = title or self._title_base
@@ -641,21 +651,27 @@ class Hist(PlotBase):
             ' ', '') else title
         return title or ''
 
-    def _plot_legends(self):
+    def plot_legends(self):
+        ''' Print legend on current page'''
         lines = []
         labels = []
         for ax in mpl.gcf().get_axes():
-            li, la = ax.get_legend_handles_labels()
-            lines += li
-            labels += la
+            ali, ala = ax.get_legend_handles_labels()
+            # avoid duplicates in legend
+            for li, la in zip(ali, ala):
+                if la not in labels:
+                    lines.append(li)
+                    labels.append(la)
+
         if self._disp_legend:
             mpl.gcf().legend(
                 lines, labels, loc=self._legend_loc, fancybox=True,
                 framealpha=0.5, ncol=self._nlegends,
-                bbox_to_anchor=(0.55, 1.06),
+                bbox_to_anchor=(0.55, 1.1),
             )
 
     def _get_neg_pos_thres(self, idx, input_scores, input_names):
+        ''' Get scores and threshod for the given system at index idx'''
         neg_list, pos_list, _ = utils.get_fta_list(input_scores)
         length = len(neg_list)
         # can have several files for one system
@@ -672,6 +688,7 @@ class Hist(PlotBase):
         return dev_neg, dev_pos, eval_neg, eval_pos, threshold
 
     def _density_hist(self, scores, n, **kwargs):
+        ''' Plots one density histo'''
         n, bins, patches = mpl.hist(
             scores, density=True,
             bins=self._nbins[n],
@@ -681,6 +698,7 @@ class Hist(PlotBase):
 
     def _lines(self, threshold, label=None, neg=None, pos=None,
                idx=None, **kwargs):
+        ''' Plots vertical line at threshold '''
         label = label or 'Threshold'
         kwargs.setdefault('color', 'C3')
         kwargs.setdefault('linestyle', '--')
@@ -689,7 +707,11 @@ class Hist(PlotBase):
         mpl.axvline(x=threshold, ymin=0, ymax=1, **kwargs)
 
     def _setup_hist(self, neg, pos):
-        ''' This function can be overwritten in derived classes'''
+        ''' This function can be overwritten in derived classes
+
+        Plots all the density histo required in one plot. Here negative and
+        positive scores densities.
+        '''
         self._density_hist(
             neg[0], n=0,
             label='Negatives', alpha=0.5, color='C3'
-- 
GitLab