diff --git a/src/fig_utils/__init__.py b/src/fig_utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..361cb37b77e1ae966fbccd81d2bba2fa79ecf0ee
--- /dev/null
+++ b/src/fig_utils/__init__.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+# -*- coding: UTF-8 -*-
+
+"""
+Description:
+"""
\ No newline at end of file
diff --git a/src/fig_utils/stat_annot.py b/src/fig_utils/stat_annot.py
new file mode 100644
index 0000000000000000000000000000000000000000..092e17546c01335b40dd587022dff99703201819
--- /dev/null
+++ b/src/fig_utils/stat_annot.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+
+# -*- coding: UTF-8 -*-
+
+"""
+Description:
+    Add annotation on figure.
+    Script adapted from
+    github.com/webermarcolivier/statannot/blob/master/statannot/statannot.py.
+"""
+
+
+import matplotlib.pyplot as plt
+from matplotlib import lines
+import matplotlib.transforms as mtransforms
+from matplotlib.font_manager import FontProperties
+import numpy as np
+import seaborn as sns
+from seaborn.utils import remove_na
+
+
+def add_stat_annotation(ax, data=None, x=None, y=None, hue=None, order=None,
+                        hue_order=None, box_pair_list=None, loc='inside',
+                        use_fixed_offset=False,
+                        liney_offset2box_axes_coord=None,
+                        liney_offset_axes_coord=None,
+                        line_height_axes_coord=0.02,
+                        text_y_offset_points=1, color='0.2', linewidth=1.5,
+                        fontsize='medium', verbose=1):
+    """
+    User should use the same argument for the data, x, y, hue, order, \
+    hue_order as the seaborn boxplot function.
+    box_pair_list can be of either form:
+    boxplot: [(cat1, cat2, pval), (cat3, cat4, pval)]
+    """
+
+    def find_x_position_box(box_plotter, box_name):
+        """
+        box_name can be either a name "cat" or a tuple ("cat", "hue")
+        """
+        if box_plotter.plot_hues is None:
+            cat = box_name
+            hue_offset = 0
+        else:
+            cat = box_name[0]
+            chue = box_name[1]
+            hue_offset = box_plotter.hue_offsets[
+                box_plotter.hue_names.index(chue)]
+
+        group_pos = box_plotter.group_names.index(cat)
+        box_pos = group_pos + hue_offset
+        return box_pos
+
+    def get_box_data(cbox_plotter, box_name):
+        """
+        box_name can be either a name "cat" or a tuple ("cat", "hue")
+        Here we really have to duplicate seaborn code, because there is not
+        direct access to the
+        box_data in the BoxPlotter class.
+        """
+        cat = box_name
+        if cbox_plotter.plot_hues is None:
+            box_max_l = []
+            for cat in cbox_plotter.group_names:
+                i = cbox_plotter.group_names.index(cat)
+                group_data = cbox_plotter.plot_data[i]
+                box_data = remove_na(group_data)
+                box_max_l.append(np.mean(box_data))
+            return max(box_max_l)
+        else:
+            i = cbox_plotter.group_names.index(cat[0])
+            group_data = cbox_plotter.plot_data[i]
+            box_data = []
+            for chue in cbox_plotter.hue_names:
+                hue_mask = cbox_plotter.plot_hues[i] == chue
+                box_data.append(np.max(remove_na(group_data[hue_mask])))
+            return max(box_data)
+
+    fig = plt.gcf()
+
+    valid_list = ['inside', 'outside']
+    if loc not in valid_list:
+        raise ValueError(f"loc value should be one of the following: "
+                         f"{', '.join(valid_list)}.")
+
+    # Create the same BoxPlotter object as seaborn's boxplot
+    box_plotter = sns.categorical._BoxPlotter(x, y, hue, data, order,
+                                              hue_order, orient=None, width=.8,
+                                              color=None, palette=None,
+                                              saturation=.75, dodge=True,
+                                              fliersize=5, linewidth=None)
+    ylim = ax.get_ylim()
+    y_range = ylim[1] - ylim[0]
+
+    if liney_offset_axes_coord is None:
+        if loc == 'inside':
+            liney_offset_axes_coord = 0.005
+            if liney_offset2box_axes_coord is None:
+                liney_offset2box_axes_coord = 0.1
+        elif loc == 'outside':
+            liney_offset_axes_coord = 0.03
+            liney_offset2box_axes_coord = liney_offset_axes_coord
+    else:
+        if loc == 'inside':
+            if liney_offset2box_axes_coord is None:
+                liney_offset2box_axes_coord = 0.06
+        elif loc == 'outside':
+            liney_offset2box_axes_coord = liney_offset_axes_coord
+    y_offset = liney_offset_axes_coord * y_range
+    y_offset_to_box = liney_offset2box_axes_coord * y_range
+
+    y_stack = []
+    ann_list = []
+    for box1, box2, pval in box_pair_list:
+
+        group_names = box_plotter.group_names
+        cat1 = box1
+        cat2 = box2
+        if isinstance(cat1, tuple):
+            hue_names = box_plotter.hue_names
+            valid = cat1[0] in group_names and cat2[0] in group_names and \
+                cat1[1] in hue_names and cat2[1] in hue_names
+        else:
+            valid = cat1 in group_names and cat2 in group_names
+
+        if valid:
+            # Get position of boxes
+            x1 = find_x_position_box(box_plotter, box1)
+            x2 = find_x_position_box(box_plotter, box2)
+            box_data1 = get_box_data(box_plotter, box1)
+            box_data2 = get_box_data(box_plotter, box2)
+            ymax1 = box_data1
+            ymax2 = box_data2
+
+            if pval > 1e-16:
+                text = "p = {:.2e}".format(pval)
+            else:
+                text = "p < 1e-16"
+
+            if loc == 'inside':
+                y_ref = max(ymax1, ymax2)
+            else:
+                y_ref = ylim[1]
+            if len(y_stack) > 0:
+                y_ref2 = max(y_ref, max(y_stack))
+            else:
+                y_ref2 = y_ref
+
+            if len(y_stack) == 0:
+                y = y_ref2 + y_offset_to_box
+            else:
+                y = y_ref2 + y_offset
+            h = line_height_axes_coord * y_range
+            line_x, line_y = [x1, x1, x2, x2], [y, y + h, y + h, y]
+            if loc == 'inside':
+                ax.plot(line_x, line_y, lw=linewidth, c=color)
+            elif loc == 'outside':
+                line = lines.Line2D(line_x, line_y, lw=linewidth, c=color,
+                                    transform=ax.transData)
+                line.set_clip_on(False)
+                ax.add_line(line)
+
+            if text is not None:
+                ann = ax.annotate(text, xy=(np.mean([x1, x2]), y + h),
+                                  xytext=(0, text_y_offset_points),
+                                  textcoords='offset points',
+                                  xycoords='data', ha='center', va='bottom',
+                                  fontsize=fontsize, clip_on=False,
+                                  annotation_clip=False)
+                ann_list.append(ann)
+
+            new_max_ylim = 1.1*(y + h)
+            if new_max_ylim > ylim[1]:
+                ax.set_ylim((ylim[0], 1.1*(y + h)))
+
+            if text is not None:
+                plt.draw()
+                y_top_annot = None
+                got_matplotlib_error = False
+                if not use_fixed_offset:
+                    try:
+                        bbox = ann.get_window_extent()
+                        bbox_data = bbox.transformed(ax.transData.inverted())
+                        y_top_annot = bbox_data.ymax
+                    except RuntimeError:
+                        got_matplotlib_error = True
+
+                if use_fixed_offset or got_matplotlib_error:
+                    if verbose >= 1:
+                        print("Warning: cannot get the text bounding box. "
+                              "Falling back to a fixed y offset. Layout may "
+                              "be not optimal.")
+                    #  We will apply a fixed offset in points,
+                    #  based on the font size of the annotation.
+                    fontsize_points = FontProperties(size='medium'
+                                                     ).get_size_in_points()
+                    offset_trans = mtransforms.offset_copy(
+                        ax.transData, fig=fig, x=0,
+                        y=1.0 * fontsize_points + text_y_offset_points,
+                        units='points')
+                    y_top_display = offset_trans.transform((0, y + h))
+                    y_top_annot = ax.transData.inverted().\
+                        transform(y_top_display)[1]
+            else:
+                y_top_annot = y + h
+
+            y_stack.append(y_top_annot)
+        else:
+            raise ValueError("box_pair_list contains an unvalid box pair.")
+
+    y_stack_max = max(y_stack)
+    if loc == 'inside':
+        if ylim[1] < 1.03 * y_stack_max:
+            ax.set_ylim((ylim[0], 1.03 * y_stack_max))
+    elif loc == 'outside':
+        ax.set_ylim((ylim[0], ylim[1]))
+    return ax