diff --git a/cal_tools/cal_tools/agipdlib.py b/cal_tools/cal_tools/agipdlib.py
index 691a3c41836fcdcb04fd14d35a5990c82a05a019..558ec084bdc7f843f6f653a7f6b44cb4a88afc90 100644
--- a/cal_tools/cal_tools/agipdlib.py
+++ b/cal_tools/cal_tools/agipdlib.py
@@ -1,20 +1,23 @@
 import copy
 from enum import Enum
+import os
 
 import h5py
 import numpy as np
-from cal_tools.enums import BadPixels
-from cal_tools.tools import get_constant_from_db, get_constant_from_db_and_time
-from iCalibrationDB import Constants, Conditions, Detectors
 from scipy.signal import cwt, ricker
 from sklearn.mixture import GaussianMixture
 from sklearn.preprocessing import StandardScaler
 
+from cal_tools.agipdutils import assemble_constant_dict
+from cal_tools.enums import BadPixels
+from cal_tools.tools import get_constant_from_db, get_constant_from_db_and_time
+from iCalibrationDB import Constants, Conditions, Detectors
+
 
 def get_num_cells(fname, loc, module):
     with h5py.File(fname, "r") as f:
         cells = \
-        f["INSTRUMENT/{}/DET/{}CH0:xtdf/image/cellId".format(loc, module)][()]
+            f[f"INSTRUMENT/{loc}/DET/{module}CH0:xtdf/image/cellId"][()]
         if cells.shape[0] == 0:
             return None
         maxcell = np.max(cells)
@@ -27,8 +30,7 @@ def get_acq_rate(fname, loc, module):
     with h5py.File(fname, "r") as f:
         try:
             pulses = \
-                np.squeeze(f["INSTRUMENT/{}/DET/{}CH0:xtdf/image/pulseId".format(
-                    loc, module)][:2])
+                np.squeeze(f[f"INSTRUMENT/{loc}/DET/{module}CH0:xtdf/image/pulseId"][:2])  # noqa
             diff = pulses[1] - pulses[0]
         except:
             diff = 0
@@ -60,7 +62,7 @@ def get_gain_setting(fname, h5path_ctrl):
         if (setupr == 0 and pattern_type_idx < 4) or (
                 setupr == 32 and pattern_type_idx == 4):
             return 0
-        elif(setupr == 8 and pattern_type_idx < 4) or (
+        elif (setupr == 8 and pattern_type_idx < 4) or (
                 setupr == 40 and pattern_type_idx == 4):
             return 1
         else:
@@ -204,6 +206,13 @@ class AgipdCorrections:
             raise Exception('Correction Booleans: {} are not available!'
                     .format(list(set(corr_bools) - set(tot_corr_bools))))
 
+        # Flags allowing for pulse capacitor constant retrieval.
+        self.pc_bools = [self.corr_bools.get("rel_gain"),
+                         self.corr_bools.get("adjust_mg_baseline"),
+                         self.corr_bools.get('blc_noise'),
+                         self.corr_bools.get('blc_hmatch'),
+                         self.corr_bools.get('blc_stripes'),
+                         self.melt_snow]
     def get_iteration_range(self):
         """Returns a range expression over which to iterate in chunks
         """
@@ -373,7 +382,7 @@ class AgipdCorrections:
         sh_idxs = self.get_shadowed_stripe(dd, 30, 0.95)
 
         # collect all shadowed regions excluding double pixels
-        idx = []       
+        idx = []
         for sh_idx in sh_idxs:
             if len(sh_idx)>2:
                 idx += sh_idx
@@ -1020,9 +1029,9 @@ class AgipdCorrections:
         # before doing relative gain correction we need to evaluate any
         # baseline shifts
         # as they are effectively and additional offset in the data
-        if (self.corr_bools.get('blc_noise') or 
-            self.corr_bools.get('blc_hmatch') or 
-            self.corr_bools.get('blc_stripes') ):
+        if (self.corr_bools.get('blc_noise') or
+            self.corr_bools.get('blc_hmatch') or
+            self.corr_bools.get('blc_stripes')):
 
             # do this image wise, as the shift is per image
             for i in range(im.shape[0]):
@@ -1463,67 +1472,43 @@ class AgipdCorrections:
                 (self.low_edges, self.high_edges, self.signal_edges,
                  self.dig_signal_edges))
 
-    def initialize_from_db(self, dbparms, qm, only_dark=False):
-        """ Initialize calibration constants from the calibration database
-
-        :param dbparms: a tuple containing relevant database parameters,
-        can be either:
-            * cal_db_interface, creation_time, max_cells_db, bias_voltage,
-              photon_energy in which case the db timeout is set to 300 seconds,
-              the cells to query dark image derived constants from the
-              database is set to the global value
-
-            * cal_db_interface, creation_time, max_cells_db, bias_voltage,
-              photon_energy, max_cells_db_dark here the number of memory
-              cells to query dark derived data differs from the global
-              value, the db timeout is set to 300 seconds
-
-            * cal_db_interface, creation_time, max_cells_db, bias_voltage,
-              photon_energy, max_cells_db_dark, timeout_cal_db
-              additionally a timeout is given
-
-        :param qm: quadrant and module of the constants to load in Q1M1
-        notation
-        :param only_dark: load only dark image derived constants. This
-            implies that a `calfile` is used to load the remaining
-            constants. Useful to reduce DB traffic and interactions
-            for non-frequently changing constants, i.e. such which are
-            not usually updated during a beamtime.
-
-        The `cal_db_interface` parameter in the `dbparms` tuple may be in
-        one of the following notations:
-            * tcp://host:port to directly identify the host and port to
-              connect to
-            * tcp://host:port_low#port_high to specify a port range from
-              which a random port will be picked. E.g. specifying
-
-              tcp://max-exfl016:8015#8025
-
-              will randomly pick an address in the range max-exfl016:8015 and
-              max-exfl016:8025.
-
-        The latter notation allows for load-balancing.
-
-        This routine loads the following constants as given in
-        `iCalibrationDB`:
-
-            Dark Image Derived
-            ------------------
-
-            * Constants.AGIPD.Offset
-            * Constants.AGIPD.Noise
-            * Constants.AGIPD.BadPixelsDark
-            * Constants.AGIPD.ThresholdsDark
-
-            Pulse Capacitor Derived
-            -----------------------
-
-            * Constants.AGIPD.SlopesPC
-
-            Flat-Field Derived
-
-            * Constants.AGIPD.SlopesFF
+    def retrieve_constant_and_time(self, const_dict, device,
+                                   cal_db_interface, creation_time):
+        """
+        A wrapper around get_constant_from_db_and_time to get
+        constant image and creation time using a dictionary of
+        the names of the required constant from the data base
+        and their parameter operating conditions.
+
+        :param const_dict: (Dict) A dictionary with constants to retrieve
+        and their operating conditions.
+        e.g.{ "Offset": ["zeros", (128, 512, memory_cells, 3), "Dark"]
+              "Dark-cond": {Condition-name}: condition-value }
+        :param device: (iCalibrationDB obj) Object for AGIPD's device
+        :param cal_db_interface: (Str) The port interface for
+        calibrationDBRemote
+        :param creation_time: (Str) Raw data creation time
+        :return:
+            cons_data: (np.array) Constant data
+            when: (Dict) A dictionary with all retrieved constants
+            and their creation-time. {"Constant-Name": "creation-time"}
+        """
+        when = {}
+        cons_data = {}
+        for cname, cval in const_dict.items():
+            condition = \
+                getattr(Conditions, cval[2][0]).AGIPD(**cval[2][1])
+            cons_data[cname], when[cname] = \
+                get_constant_from_db_and_time(device,
+                                              getattr(Constants.AGIPD, cname)(),  # noqa
+                                              condition,
+                                              getattr(np, cval[0])(cval[1]),
+                                              cal_db_interface,
+                                              creation_time)
+        return cons_data, when
 
+    def init_constants(self, cons_data, only_dark):
+        """
         For CI derived gain, a mean multiplication factor of 4.48 compared
         to medium gain is used, as no reliable CI data for all memory cells
         exists of the current AGIPD instances.
@@ -1566,142 +1551,23 @@ class AgipdCorrections:
             medium gain = ff * rfpc
             low gain = medium gain / 4.48
 
+        :param cons_data: A dictionary for each retrieved constant value.
+        :param only_dark: A flag for retrieving only dark constants.
+        :return:
         """
-        if len(dbparms) == 5:
-            (cal_db_interface, creation_time, max_cells_db,
-             bias_voltage, photon_energy) = dbparms
-            max_cells_db_dark = max_cells_db
-            timeout_cal_db = 30000
 
-        elif len(dbparms) == 6:
-            (cal_db_interface, creation_time, max_cells_db,
-             bias_voltage, photon_energy, max_cells_db_dark) = dbparms
+        bpixels = cons_data["BadPixelsDark"].astype(np.uint32)
 
-            if max_cells_db_dark is None:
-                max_cells_db_dark = max_cells_db
-            timeout_cal_db = 30000
-        else:
-            (cal_db_interface, creation_time, max_cells_db,
-             bias_voltage, photon_energy, max_cells_db_dark,
-             timeout_cal_db) = dbparms
-
-            if max_cells_db_dark is None:
-                max_cells_db_dark = max_cells_db
-
-        # if "#" in cal_db_interface:
-        #    prot, serv, ran = cal_db_interface.split(":")
-        #    r1, r2 = ran.split("#")
-        #    cal_db_interface = ":".join(
-        #        [prot, serv, str(np.random.randint(int(r1), int(r2)))])
-
-        dinstance = getattr(Detectors, self.cal_det_instance)
-        when = {}
-        if self.corr_bools.get('only_offset'):
-            only_dark = True
-
-        offset, when['offset'] = \
-            get_constant_from_db_and_time(getattr(dinstance, qm),
-                                          Constants.AGIPD.Offset(),
-                                          Conditions.Dark.AGIPD(
-                                              memory_cells=max_cells_db_dark,
-                                              bias_voltage=bias_voltage,
-                                              gain_setting=self.gain_setting,
-                                              acquisition_rate=self.acquisition_rate),  # noqa
-                                          np.zeros((128, 512,
-                                                    max_cells_db,
-                                                    3)),
-                                          cal_db_interface,
-                                          creation_time=creation_time,
-                                          timeout=timeout_cal_db)
-
-        noise, when['noise'] = \
-            get_constant_from_db_and_time(getattr(dinstance, qm),
-                                          Constants.AGIPD.Noise(),
-                                          Conditions.Dark.AGIPD(
-                                              memory_cells=max_cells_db_dark,
-                                              bias_voltage=bias_voltage,
-                                              gain_setting=self.gain_setting,
-                                              acquisition_rate=self.acquisition_rate),  # noqa
-                                          np.zeros(
-                                              (128, 512, max_cells_db, 3)),
-                                          cal_db_interface,
-                                          creation_time=creation_time,
-                                          timeout=timeout_cal_db)
-
-        bpixels, when['bpixels'] = \
-            get_constant_from_db_and_time(getattr(dinstance, qm),
-                                          Constants.AGIPD.BadPixelsDark(),
-                                          Conditions.Dark.AGIPD(
-                                              memory_cells=max_cells_db_dark,
-                                              bias_voltage=bias_voltage,
-                                              gain_setting=self.gain_setting,
-                                              acquisition_rate=self.acquisition_rate),  # noqa
-                                          np.zeros(
-                                              (128, 512, max_cells_db, 3)),
-                                          cal_db_interface,
-                                          creation_time=creation_time,
-                                          timeout=timeout_cal_db)
-        bpixels = bpixels.astype(np.uint32)
-
-        thresholds, when['thresholds'] = \
-            get_constant_from_db_and_time(getattr(dinstance, qm),
-                                          Constants.AGIPD.ThresholdsDark(),
-                                          Conditions.Dark.AGIPD(
-                                              memory_cells=max_cells_db_dark,
-                                              bias_voltage=bias_voltage,
-                                              gain_setting=self.gain_setting,
-                                              acquisition_rate=self.acquisition_rate),  # noqa
-                                          np.ones((128, 512, max_cells_db, 2)),
-                                          cal_db_interface,
-                                          creation_time=creation_time,
-                                          timeout=timeout_cal_db)
-
-        # we have all we need for dark image derived constants
-        # initialize and return
-        if only_dark:
-            self.initialize(offset=offset, rel_gain=None, mask=bpixels,
-                            noise=noise, thresholds=thresholds)
-            return when
+        if self.corr_bools.get('only_offset') or only_dark:
+            self.initialize(offset=cons_data["Offset"],
+                            rel_gain=None, mask=bpixels,
+                            noise=cons_data["Noise"],
+                            thresholds=cons_data["ThresholdsDark"])
+            return
 
         if self.corr_bools.get("xray_corr"):
-            bpff, when['bpff'] = \
-                get_constant_from_db_and_time(getattr(dinstance, qm),
-                                              Constants.AGIPD.BadPixelsFF(),
-                                              Conditions.Illuminated.AGIPD(
-                                                  max_cells_db,
-                                                  bias_voltage,
-                                                  photon_energy,
-                                                  pixels_x=512,
-                                                  pixels_y=128,
-                                                  beam_energy=None,
-                                                  gain_setting=self.gain_setting,
-                                                  acquisition_rate=self.acquisition_rate),
-                                              # noqa
-                                              np.zeros(
-                                                  (128, 512, max_cells_db)),
-                                              cal_db_interface,
-                                              creation_time=creation_time,
-                                              timeout=timeout_cal_db)
-
-            bpixels |= bpff.astype(np.uint32)[..., :bpixels.shape[2], None]
-
-            slopesFF, when['slopesFF'] = \
-                get_constant_from_db_and_time(getattr(dinstance, qm),
-                                              Constants.AGIPD.SlopesFF(),
-                                              Conditions.Illuminated.AGIPD(
-                                                  max_cells_db,
-                                                  bias_voltage,
-                                                  photon_energy,
-                                                  pixels_x=512,
-                                                  pixels_y=128,
-                                                  beam_energy=None,
-                                                  gain_setting=self.gain_setting,
-                                                  acquisition_rate=self.acquisition_rate),  # noqa
-                                              np.ones((128, 512, max_cells_db, 2)),
-                                              cal_db_interface,
-                                              creation_time=creation_time,
-                                              timeout=timeout_cal_db)
-            slopesFF = np.squeeze(slopesFF)
+            bpixels |= cons_data["BadPixelsFF"].astype(np.uint32)[..., :bpixels.shape[2], None]  # noqa
+            slopesFF = np.squeeze(cons_data["SlopesFF"])
 
             if len(slopesFF.shape) == 4:
                 slopesFF = slopesFF[..., 0]
@@ -1722,46 +1588,12 @@ class AgipdCorrections:
             xray_cor /= np.nanmedian(xray_cor)
         else:
             xray_cor = None
-
-        if (self.corr_bools.get("rel_gain") or
-                self.corr_bools.get("adjust_mg_baseline") or
-                self.corr_bools.get('blc_noise') or
-                self.corr_bools.get('blc_hmatch') or
-                self.corr_bools.get('blc_stripes') or
-                self.melt_snow is not False):
-
-            # add additonal bad pixel information
-            bppc, when['bppc'] = \
-                get_constant_from_db_and_time(getattr(dinstance, qm),
-                                              Constants.AGIPD.BadPixelsPC(),
-                                              Conditions.Dark.AGIPD(
-                                                  memory_cells=max_cells_db,
-                                                  bias_voltage=bias_voltage,
-                                                  gain_setting=self.gain_setting,
-                                                  acquisition_rate=self.acquisition_rate),
-                                              # noqa
-                                              np.zeros(
-                                                  (max_cells_db, 128, 512)),
-                                              cal_db_interface,
-                                              creation_time=creation_time,
-                                              timeout=timeout_cal_db)
-            bppc = np.moveaxis(bppc.astype(np.uint32), 0, 2)
+        # add additional bad pixel information
+        if any(self.pc_bools):
+            bppc = np.moveaxis(cons_data["BadPixelsPC"].astype(np.uint32), 0, 2)
             bpixels |= bppc[..., :bpixels.shape[2], None]
 
-            # load also non-dark constants from the database
-            slopesPC, when['slopesPC'] = \
-                get_constant_from_db_and_time(getattr(dinstance, qm),
-                                              Constants.AGIPD.SlopesPC(),
-                                              Conditions.Dark.AGIPD(
-                                                  memory_cells=max_cells_db,
-                                                  bias_voltage=bias_voltage,
-                                                  acquisition_rate=self.acquisition_rate),  # noqa
-                                              np.ones(
-                                                  (128, 512, max_cells_db, 10)),
-                                              cal_db_interface,
-                                              creation_time=creation_time,
-                                              timeout=timeout_cal_db)
-            slopesPC = slopesPC.astype(np.float32)
+            slopesPC = cons_data["SlopesPC"].astype(np.float32)
 
             # this will handle some historical data in a slighly different format
             # constant dimension injected first
@@ -1795,9 +1627,133 @@ class AgipdCorrections:
             rel_gain = None
             frac_high_med = None
 
-        self.initialize(offset, rel_gain, xray_cor, bpixels, noise, thresholds,
+        self.initialize(cons_data["Offset"], rel_gain, xray_cor, bpixels,
+                        cons_data["Noise"],
+                        cons_data["ThresholdsDark"],
                         frac_high_med=frac_high_med,
                         md_additional_offset=md_additional_offset)
+        return
+
+    def initialize_from_yaml(self, const_yaml, qm, only_dark=False):
+        """
+        Initialize calibration constants from a yaml file
+
+        :param const_yaml: (Dict) fromed from a yaml file in pre-notebook,
+        which consists of metadata of either the constant file path or the
+        empty constant shape, and the creation-time of the retrieved constants
+        :param qm: Module's virtual name (e.g. Q1M1)
+        :param only_dark: A flag for retrieving only dark constants.
+        :return:
+        """
+
+        device = getattr(getattr(Detectors, self.cal_det_instance), qm)
+
+        # string of the device name.
+        dname = device.device_name
+        cons_data = dict()
+        when = dict()
+        for cname, mdata in const_yaml[dname].items():
+            when[cname] = mdata["creation-time"]
+            if when[cname]:
+                cf = h5py.File(mdata["file-path"])
+                cons_data[cname] = np.copy(cf[f"{dname}/{cname}/0/data"])
+            else:
+                # Create empty constant using the list elements
+                cons_data[cname] = \
+                    getattr(np, mdata["file-path"][0])(mdata["file-path"][1])  # noqa
+
+        self.init_constants(cons_data, only_dark)
+
+        return when
+
+    def initialize_from_db(self, dbparms, qm, only_dark=False):
+        """ Initialize calibration constants from the calibration database
+
+        :param dbparms: a tuple containing relevant database parameters,
+        can be either:
+            * cal_db_interface, creation_time, max_cells_db, bias_voltage,
+              photon_energy in which case the db timeout is set to 300 seconds,
+              the cells to query dark image derived constants from the
+              database is set to the global value
+
+            * cal_db_interface, creation_time, max_cells_db, bias_voltage,
+              photon_energy, max_cells_db_dark, timeout_cal_db
+              additionally a timeout is given
+
+        :param qm: quadrant and module of the constants to load in Q1M1
+        notation
+        :param only_dark: load only dark image derived constants. This
+            implies that a `calfile` is used to load the remaining
+            constants. Useful to reduce DB traffic and interactions
+            for non-frequently changing constants, i.e. such which are
+            not usually updated during a beamtime.
+
+        The `cal_db_interface` parameter in the `dbparms` tuple may be in
+        one of the following notations:
+            * tcp://host:port to directly identify the host and port to
+              connect to
+            * tcp://host:port_low#port_high to specify a port range from
+              which a random port will be picked. E.g. specifying
+
+              tcp://max-exfl016:8015#8025
+
+              will randomly pick an address in the range max-exfl016:8015 and
+              max-exfl016:8025.
+
+        The latter notation allows for load-balancing.
+
+        This routine loads the following constants as given in
+        `iCalibrationDB`:
+
+            Dark Image Derived
+            ------------------
+
+            * Constants.AGIPD.Offset
+            * Constants.AGIPD.Noise
+            * Constants.AGIPD.BadPixelsDark
+            * Constants.AGIPD.ThresholdsDark
+
+            Pulse Capacitor Derived
+            -----------------------
+
+            * Constants.AGIPD.SlopesPC
+
+            Flat-Field Derived
+
+            * Constants.AGIPD.SlopesFF
+
+        """
+        max_cells_db_dark = None
+
+        # dbparms has a length of 5 in
+        # playground/QuickCorrect-SingleModule.ipynb
+        if len(dbparms) == 5:
+            (cal_db_interface, creation_time, max_cells_db,
+             bias_voltage, photon_energy) = dbparms
+        else:
+            (cal_db_interface, creation_time, max_cells_db,
+             bias_voltage, photon_energy, max_cells_db_dark,
+             timeout_cal_db) = dbparms
+
+        if max_cells_db_dark is None:
+            max_cells_db_dark = max_cells_db
+
+        # Device object for retrieving constants
+        device = getattr(getattr(Detectors, self.cal_det_instance), qm)
+
+        const_dict = \
+            assemble_constant_dict(self.corr_bools, self.pc_bools, max_cells_db_dark,
+                                   bias_voltage, self.gain_setting,
+                                   self.acquisition_rate, photon_energy,
+                                   beam_energy=None, only_dark=only_dark)
+
+        cons_data, when =\
+            self.retrieve_constant_and_time(const_dict, device,
+                                            cal_db_interface,
+                                            creation_time)
+
+        self.init_constants(cons_data, only_dark)
+
         return when
 
     def initialize_from_file(self, filename, qm, with_dark=True):
diff --git a/cal_tools/cal_tools/agipdutils.py b/cal_tools/cal_tools/agipdutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..7322580a04757fe23714dbc7ca728dc762c4011e
--- /dev/null
+++ b/cal_tools/cal_tools/agipdutils.py
@@ -0,0 +1,67 @@
+import h5py
+import numpy as np
+
+
+def assemble_constant_dict(corr_bools, pc_bools, memory_cells, bias_voltage,
+                           gain_setting, acquisition_rate,
+                           photon_energy, beam_energy=None, only_dark=False):
+    """
+    Assemble a dictionary with the iCalibrationDB constant names and
+    the operating conditions for retrieveing the required constants
+    for correction.
+
+    :param corr_bools: (Dict) A dict of booleans for applying
+    specific corrections
+    :param pc_bools: (List) A list of booleans to enable SlopesPC retrieval
+    :param memory_cells: (Int) Number of memory cells
+    :param bias_voltage: (Int) Bias Voltage
+    :param gain_setting: (Float) Gain setting
+    :param acquisition_rate: (Float) Acquisition rate
+    :param photon_energy: (Float) Photong energy
+    :param beam_energy: (Float) Beam Energy
+    :param only_dark: (Bool) Indicating a retrieval for dark
+    constants only from db
+    :return: const_dict: (Dict) An assembeld dictionary that can be used
+    to retrieve the required constants
+    """
+
+    darkcond = [
+        "Dark", {
+            "memory_cells": memory_cells,
+            "bias_voltage": bias_voltage,
+            "acquisition_rate": acquisition_rate,
+            "gain_setting": gain_setting,
+            "pixels_x": 512,
+            "pixels_y": 128, }
+    ]
+    const_dict = {
+        "Offset": ["zeros", (128, 512, memory_cells, 3), darkcond],
+        "Noise": ["zeros", (128, 512, memory_cells, 3), darkcond],
+        "ThresholdsDark": ["ones", (128, 512, memory_cells, 2), darkcond],
+        "BadPixelsDark": ["zeros", (128, 512, memory_cells, 3), darkcond],
+    }
+
+    if not (corr_bools.get('only_offset') or only_dark):
+
+        if any(pc_bools):
+            const_dict["BadPixelsPC"] = \
+                ["zeros", (memory_cells, 128, 512), darkcond]
+            const_dict["SlopesPC"] = \
+                ["ones", (128, 512, memory_cells, 10), darkcond]
+
+        if corr_bools.get("xray_corr"):
+            # Add illuminated conditions
+            illumcond = [
+                "Illuminated", {
+                    "beam_energy": beam_energy,
+                    "photon_energy": photon_energy
+                }
+            ]
+            illumcond[1].update(darkcond[1])
+
+            const_dict["BadPixelsFF"] = ["zeros", (128, 512, memory_cells),
+                                         illumcond]
+            const_dict["SlopesFF"] = ["ones", (128, 512, memory_cells, 2),
+                                      illumcond]
+
+    return const_dict
diff --git a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
index 69c91d7de01bf7fa5769fc9282a0a2949effb9a9..d0c8b178e465972ce86aade7d26f3d8502732e70 100644
--- a/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
+++ b/notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb
@@ -128,27 +128,30 @@
    },
    "outputs": [],
    "source": [
-    "import sys\n",
     "from collections import OrderedDict\n",
-    "\n",
-    "# make sure a cluster is running with ipcluster start --n=32, give it a while to start\n",
+    "from datetime import timedelta\n",
     "import os\n",
+    "import sys\n",
+    "\n",
+    "from dateutil import parser\n",
     "import h5py\n",
     "import numpy as np\n",
     "import matplotlib\n",
     "matplotlib.use(\"agg\")\n",
     "import matplotlib.pyplot as plt\n",
     "from ipyparallel import Client\n",
-    "print(f\"Connecting to profile {cluster_profile}\")\n",
-    "view = Client(profile=cluster_profile)[:]\n",
-    "view.use_dill()\n",
+    "import yaml\n",
     "\n",
     "from iCalibrationDB import ConstantMetaData, Constants, Conditions, Detectors, Versions\n",
     "from cal_tools.tools import (map_modules_from_folder, parse_runs, run_prop_seq_from_path, get_notebook_name,\n",
-    "                                 get_dir_creation_date, get_constant_from_db)\n",
+    "                             get_dir_creation_date, get_constant_from_db)\n",
+    "\n",
     "from cal_tools.agipdlib import get_gain_setting\n",
-    "from dateutil import parser\n",
-    "from datetime import timedelta\n",
+    "\n",
+    "# make sure a cluster is running with ipcluster start --n=32, give it a while to start\n",
+    "print(f\"Connecting to profile {cluster_profile}\")\n",
+    "view = Client(profile=cluster_profile)[:]\n",
+    "view.use_dill()\n",
     "\n",
     "il_mode = interlaced\n",
     "max_cells = mem_cells\n",
@@ -195,13 +198,6 @@
     "    dinstance = \"AGIPD1M2\"\n"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -366,7 +362,7 @@
      "end_time": "2019-02-21T11:30:16.057429Z",
      "start_time": "2019-02-21T11:30:10.082114Z"
     },
-    "scrolled": true
+    "scrolled": false
    },
    "outputs": [],
    "source": [
@@ -376,17 +372,19 @@
     "                   bins_gain_vs_signal, bins_signal_low_range, bins_signal_high_range,\n",
     "                   bins_dig_gain_vs_signal, max_pulses, dbparms, fileparms, nodb, chunk_size_idim,\n",
     "                   special_opts, il_mode, loc, dinstance, force_hg_if_below, force_mg_if_below,\n",
-    "                   mask_noisy_adc, acq_rate, gain_setting, corr_bools, h5path, h5path_idx, inp):\n",
+    "                   mask_noisy_adc, acq_rate, gain_setting, corr_bools, h5path, h5path_idx, const_yaml, inp):\n",
     "    print(\"foo\")\n",
     "    import numpy as np\n",
     "    import copy\n",
     "    import h5py\n",
     "    import socket\n",
+    "    import traceback\n",
     "    from datetime import datetime\n",
     "    import re\n",
     "    import os\n",
     "    from influxdb import InfluxDBClient\n",
     "    import subprocess\n",
+    "    from iCalibrationDB import Constants, Conditions, Detectors\n",
     "    from cal_tools.enums import BadPixels\n",
     "    from cal_tools.agipdlib import AgipdCorrections, SnowResolution\n",
     "    from cal_tools.agipdlib import get_num_cells, get_acq_rate\n",
@@ -441,11 +439,10 @@
     "        reason = \"\"\n",
     "        filename, filename_out, channel, qm = inp\n",
     "        print(\"Have input\")\n",
-    "        \n",
     "        if max_cells == 0:\n",
     "            max_cells = get_num_cells(filename, loc, channel)\n",
     "            if max_cells is None:\n",
-    "                raise ValueError(\"No raw images found for {}\".format(qm))\n",
+    "                raise ValueError(f\"No raw images found for {qm}\")\n",
     "            else:\n",
     "                cells = np.arange(max_cells)\n",
     "            \n",
@@ -459,8 +456,8 @@
     "        if dbparms[5] == 0:\n",
     "            dbparms[5] = dbparms[2]\n",
     "\n",
-    "        print(\"Set memory cells to {}\".format(max_cells))\n",
-    "        print(\"Set acquistion rate cells to {} MHz\".format(acq_rate))\n",
+    "        print(f\"Set memory cells to {max_cells}\")\n",
+    "        print(f\"Set acquistion rate cells to {acq_rate} MHz\")\n",
     "\n",
     "        # AGIPD correction requires path without the leading \"/\"\n",
     "        if h5path[0] == '/':\n",
@@ -494,8 +491,18 @@
     "                agipd_corr.get_valid_image_idx()\n",
     "            except IOError:\n",
     "                return\n",
+    "            device = getattr(getattr(Detectors, dinstance), qm)\n",
+    "            \n",
+    "            # check if there is a yaml file in out_folder that has the device constants.\n",
     "            if not nodb:\n",
-    "                when = agipd_corr.initialize_from_db(dbparms, qm, only_dark=(fileparms != \"\"))\n",
+    "                if const_yaml and device.device_name in const_yaml:\n",
+    "                    print(fileparms != \"\")\n",
+    "                    agipd_corr.initialize_from_yaml(const_yaml, qm,\n",
+    "                                                    only_dark=((fileparms != \"\")))\n",
+    "                else:\n",
+    "                    when = agipd_corr.initialize_from_db(dbparms, qm,\n",
+    "                                                         only_dark=(fileparms != \"\"))\n",
+    "\n",
     "            if fileparms != \"\" and not corr_bools[\"only_offset\"]:\n",
     "                agipd_corr.initialize_from_file(fileparms, qm, with_dark=nodb)\n",
     "            print(\"Initialized constants\")\n",
@@ -503,7 +510,6 @@
     "            for irange in agipd_corr.get_iteration_range():\n",
     "                agipd_corr.correct_agipd(irange)\n",
     "                print(\"Iterated\")\n",
-    "\n",
     "            print(\"All iterations are finished\")\n",
     "            hists, edges = agipd_corr.get_histograms()\n",
     "            hists_signal_low, hists_signal_high, hists_gain_vs_signal, hists_dig_gain_vs_signal, hist_pulses = hists\n",
@@ -516,8 +522,8 @@
     "            print(\"Closed files\")\n",
     "        \n",
     "    except Exception as e:\n",
-    "        print(e)\n",
-    "        err = e\n",
+    "        err = f\"Error: {e}\\nError traceback: {traceback.format_exc()}\"\n",
+    "        print(err)\n",
     "        success = False\n",
     "        reason = \"Error\"\n",
     "        \n",
@@ -530,7 +536,7 @@
     "        #influx = create_influx_entry(run, proposal, qm, sequence, filesize, CHUNK_SIZE, total_sequences, success, duration, reason)\n",
     "        #client.write_points([influx])\n",
     "    return (hists_signal_low, hists_signal_high, hists_gain_vs_signal, hists_dig_gain_vs_signal, hist_pulses,\n",
-    "            low_edges, high_edges, signal_edges, dig_signal_edges, gain_stats, max_cells, when, qm, err)\n",
+    "            low_edges, high_edges, signal_edges, dig_signal_edges, gain_stats, max_cells, acq_rate, when, qm, err)\n",
     "    \n",
     "done = False\n",
     "first_files = []\n",
@@ -568,17 +574,25 @@
     "all_cells = []\n",
     "whens = []\n",
     "errors = []\n",
+    "const_yaml = None\n",
+    "\n",
+    "if os.path.isfile(f'{out_folder}/retrieved_constants.yml'):\n",
+    "    with open(f'{out_folder}/retrieved_constants.yml', \"r\") as f:\n",
+    "        const_yaml = yaml.load(f.read(), Loader=yaml.FullLoader)\n",
+    "\n",
+    "mod_dev = dict()\n",
     "while not done:\n",
-    "    \n",
     "    dones = []\n",
     "    first = True\n",
-    "    for i in range(16):\n",
-    "        qm = \"Q{}M{}\".format(i//4 +1, i % 4 + 1)\n",
+    "    for i in range(len(modules)):\n",
+    "        qm = f\"Q{i//4+1}M{i%4+1}\"\n",
     "        if qm in mapped_files and not mapped_files[qm].empty():\n",
     "            fname_in = str(mapped_files[qm].get())\n",
     "            dones.append(mapped_files[qm].empty())\n",
+    "            device = getattr(getattr(Detectors, dinstance), qm)\n",
+    "            mod_dev[qm] = device.device_name\n",
     "        else:\n",
-    "            print(\"Skipping {}\".format(qm))\n",
+    "            print(f\"Skipping {qm}\")\n",
     "            first_files.append((None, None))\n",
     "            continue\n",
     "        fout = os.path.abspath(\"{}/{}\".format(out_folder, (os.path.split(fname_in)[-1]).replace(\"RAW\", \"CORR\")))\n",
@@ -586,16 +600,17 @@
     "            first_files.append((fname_in, fout))\n",
     "        inp.append((fname_in, fout, i,  qm))\n",
     "    first = False\n",
+    "    \n",
+    "\n",
     "    if len(inp) >= min(MAX_PAR, left):\n",
-    "        print(\"Running {} tasks parallel\".format(len(inp)))\n",
+    "        print(f\"Running {len(inp)} tasks parallel\")\n",
     "        p = partial(correct_module, max_cells, index_v, CHUNK_SIZE, total_sequences,\n",
     "                    sequences_qm, bins_gain_vs_signal, bins_signal_low_range, bins_signal_high_range,\n",
     "                    bins_dig_gain_vs_signal, max_pulses, dbparms, fileparms, nodb, chunk_size_idim,\n",
     "                    special_opts, il_mode, karabo_id, dinstance, force_hg_if_below, force_mg_if_below,\n",
-    "                    mask_noisy_adc, acq_rate, gain_setting, corr_bools, h5path, h5path_idx)\n",
+    "                    mask_noisy_adc, acq_rate, gain_setting, corr_bools, h5path, h5path_idx, const_yaml)\n",
     "\n",
     "        r = view.map_sync(p, inp)\n",
-    "\n",
     "        #r = list(map(p, inp))\n",
     "\n",
     "        inp = []\n",
@@ -604,7 +619,7 @@
     "        init_hist = False\n",
     "        for rr in r:\n",
     "            if rr is not None:\n",
-    "                hl, hh, hg, hdg, hp, low_edges, high_edges, signal_edges, dig_signal_edges, gs, cells, when, qm, err = rr\n",
+    "                hl, hh, hg, hdg, hp, low_edges, high_edges, signal_edges, dig_signal_edges, gs, cells, acq_rate, when, qm, err = rr\n",
     "                all_cells.append(cells)\n",
     "                whens.append((qm, when))\n",
     "                errors.append(err)\n",
@@ -620,44 +635,64 @@
     "                    hists_dig_gain_vs_signal += hdg.astype(np.float64)\n",
     "                    gain_stats += gs\n",
     "    \n",
-    "    done = all(dones)"
+    "    done = all(dones)\n",
+    "\n",
+    "print(f\"Corrected raw data of {cells} memory cells and {acq_rate} MHz acquisition rate\")"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {},
+   "metadata": {
+    "scrolled": false
+   },
    "outputs": [],
    "source": [
-    "print(\"Constants were injected on: \")\n",
+    "# if there is a yml file that means a leading notebook got processed\n",
+    "# and the reporting would be generated from it.\n",
+    "fst_print = True\n",
+    "\n",
     "to_store = []\n",
+    "line = []\n",
     "for i, (qm, when) in enumerate(whens):\n",
-    "    print(qm)\n",
-    "    line = [qm]\n",
-    "    # If correction is crashed\n",
-    "    if errors[i] is not None:\n",
+    "    # expose errors while applying correction\n",
+    "    if errors[i]:\n",
     "        print(\"Error: {}\".format(errors[i]) )\n",
-    "    else:\n",
-    "        for key, item in when.items():\n",
-    "            if hasattr(item, 'strftime'):\n",
-    "                item = item.strftime('%y-%m-%d %H:%M')\n",
-    "            when[key] = item\n",
-    "            print('{:.<12s}'.format(key), item)\n",
-    "            \n",
-    "    # Store few time stamps if exists\n",
-    "    # Add NA to keep array structure\n",
-    "    for key in ['offset', 'slopesPC', 'slopesFF']:\n",
-    "        if when and key in when:\n",
-    "            line.append(when[key])\n",
-    "        else:\n",
-    "            if errors[i] is not None:\n",
-    "                line.append('Err')\n",
-    "            else:\n",
-    "                line.append('NA')\n",
-    "    to_store.append(line)\n",
     "\n",
-    "np.savetxt(\"{}/tmp_const_beginat_S{:05d}.csv\".format(out_folder, sequences[0]), \n",
-    "           np.array(to_store).astype(str), fmt='%s', delimiter = ',')"
+    "    if not const_yaml or mod_dev[qm] not in const_yaml:\n",
+    "        if fst_print:\n",
+    "            print(\"Constants are retrieved with creation time: \")\n",
+    "            fst_print = False\n",
+    "    \n",
+    "        line = [qm]\n",
+    "\n",
+    "        # If correction is crashed\n",
+    "        if not errors[i]:\n",
+    "            print(f\"{qm}:\")\n",
+    "            for key, item in when.items():\n",
+    "                if hasattr(item, 'strftime'):\n",
+    "                    item = item.strftime('%y-%m-%d %H:%M')\n",
+    "                when[key] = item\n",
+    "                print('{:.<12s}'.format(key), item)\n",
+    "\n",
+    "        # Store few time stamps if exists\n",
+    "        # Add NA to keep array structure\n",
+    "        for key in ['Offset', 'SlopesPC', 'SlopesFF']:\n",
+    "            if when and key in when and when[key]:\n",
+    "                line.append(when[key])\n",
+    "            else:\n",
+    "                if errors[i] is not None:\n",
+    "                    line.append('Err')\n",
+    "                else:\n",
+    "                    line.append('NA')\n",
+    "\n",
+    "        if len(line) > 0:\n",
+    "            to_store.append(line)\n",
+    "\n",
+    "seq = sequences[0] if sequences else 0\n",
+    "if len(to_store) > 0:\n",
+    "    with open(f\"{out_folder}/retrieved_constants_s{seq}.yml\",\"w\") as fyml:\n",
+    "        yaml.safe_dump({\"time-summary\": {f\"S{seq}\":to_store}}, fyml)"
    ]
   },
   {
diff --git a/notebooks/AGIPD/AGIPD_Correct_and_Verify_Summary_NBC.ipynb b/notebooks/AGIPD/AGIPD_Correct_and_Verify_Summary_NBC.ipynb
index 7fb5ed4748dda8eb326c8339fb2e29071e704ce4..b38add62968d46d7a31145f6d160ca5ba0315618 100644
--- a/notebooks/AGIPD/AGIPD_Correct_and_Verify_Summary_NBC.ipynb
+++ b/notebooks/AGIPD/AGIPD_Correct_and_Verify_Summary_NBC.ipynb
@@ -14,8 +14,9 @@
    "outputs": [],
    "source": [
     "cluster_profile = \"noDB\" # The ipcluster profile to use\n",
-    "run = 119 # runs to process, required\n",
-    "out_folder = \"/gpfs/exfel/exp/SPB/201802/p002157/usr/test/test5/r0119\" # path to output to, required"
+    "run = 11 # runs to process, required\n",
+    "out_folder = \"/gpfs/exfel/data/scratch/ahmedk/test/AGIPD_Corr\" # path to output to, required\n",
+    "modules = [-1]"
    ]
   },
   {
@@ -24,11 +25,13 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "import os\n",
+    "\n",
     "import dateutil.parser\n",
     "import glob\n",
     "import numpy as np\n",
     "import re\n",
-    "import os\n",
+    "import yaml\n",
     "import warnings\n",
     "warnings.filterwarnings('ignore')\n",
     "\n",
@@ -44,28 +47,48 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# load temporary data from csv files\n",
-    "fnames = sorted(glob.glob('{}/tmp_const_beginat_*'.format(out_folder)))\n",
+    "if os.path.isfile(f'{out_folder}/retrieved_constants.yml'):\n",
+    "    with open(f\"{out_folder}/retrieved_constants.yml\",\"r\") as fyml:\n",
+    "        main_dict = yaml.load(fyml)\n",
+    "else:\n",
+    "    main_dict = {\"time-summary\":dict()}\n",
     "\n",
+    "if modules[0] == -1:\n",
+    "    modules = list(range(16))\n",
+    "\n",
+    "# This is needed only if AGIPD Correction notebook had no precorrection notebooks for retrieving constants\n",
+    "# gather all generated sequence yml files for time summary of retrieved constant in retrieved_constants.yml\n",
+    "fnames = sorted(glob.glob(f'{out_folder}/retrieved_constants_*yml'))  \n",
+    "for f in fnames:\n",
+    "    with open(f,\"r\") as fyml:\n",
+    "        fdict = yaml.load(fyml)\n",
+    "    # append different sequences's time summary to the main yaml\n",
+    "    for k, v in fdict[\"time-summary\"].items():\n",
+    "        main_dict[\"time-summary\"][k] = v\n",
+    "    os.remove(f)\n",
+    "\n",
+    "with open(f\"{out_folder}/retrieved_constants.yml\",\"w\") as fyml:\n",
+    "        yaml.safe_dump(main_dict, fyml)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "with open(f\"{out_folder}/retrieved_constants.yml\",\"r\") as fyml:\n",
+    "    time_summary = yaml.load(fyml)\n",
+    "# check if pre-notebook has retrieved constants for all modules.\n",
     "const_times = []\n",
     "seq = []\n",
-    "for fname in fnames:\n",
-    "    # read sequence number and array of timestamps\n",
-    "    s = int(re.findall(r'.*S([0-9]{5}).*', fname)[0])\n",
-    "    arr = np.loadtxt(fname, dtype=str, delimiter = ',')\n",
-    "    # job can run across several sequences\n",
-    "    # number of modules is fixed to 16\n",
-    "    if arr.shape[0]>16:\n",
-    "        seq += range(int(s), int(s)+arr.shape[0]//16)\n",
-    "        arr = arr.reshape(arr.shape[0]//16, 16, arr.shape[1])\n",
-    "        const_times = const_times + list(arr)\n",
-    "        \n",
-    "    else:\n",
-    "        const_times.append(arr)\n",
-    "        seq.append(s)\n",
-    "    os.remove(fname)\n",
-    "\n",
-    "const_times = np.array(const_times)\n"
+    "for k, v  in sorted(time_summary[\"time-summary\"].items()):\n",
+    "    arr = np.array(v)\n",
+    "    arr = arr.reshape(arr.shape[0]//len(modules), len(modules), arr.shape[1])\n",
+    "    const_times = const_times + list(arr)\n",
+    "    seq.append(k)\n",
+    "      \n",
+    "const_times = np.array(const_times)"
    ]
   },
   {
@@ -76,20 +99,24 @@
    "source": [
     "# Function print summary of constant injection time\n",
     "# To reduce printouts only unique entries are shown.\n",
-    "def plot_const_table(const, pos):\n",
-    "    print(\"{} were injected on: \".format(const))\n",
+    "def const_table(const, pos):\n",
+    "    \"\"\"\n",
+    "    Create a summary table for the creation time differences for\n",
+    "    the retrieved constants (Offset, SlopesPC, SlopesFF).\n",
+    "    \"\"\"\n",
+    "    print(f\"{const} were injected on: \")\n",
     "\n",
+    "    # catch timing difference in retrieve constants\n",
     "    unique, idx, counts = np.unique(const_times[:,:,pos], return_inverse=True, return_counts=True)\n",
-    "    idx = idx.reshape((const_times.shape[0],16))\n",
+    "    idx = idx.reshape((const_times.shape[0], len(modules)))\n",
     "    \n",
     "    table = []\n",
-    "\n",
     "    for i in range(0, counts.shape[0]):\n",
     "        line = [ const_times[:,:,pos][idx==i][0]  ]\n",
     "        mods = ''\n",
     "        for i_s, s in enumerate(seq):\n",
-    "            if( const_times[i_s,:,0][idx[i_s]==i].shape[0]>0 ):\n",
-    "                mods = mods+ 'S{}: {}, '.format(seq[i_s], const_times[i_s,:,0][idx[i_s]==i])\n",
+    "            if(const_times[i_s,:,0][idx[i_s]==i].shape[0] > 0):\n",
+    "                mods = mods+ '{}: {}, '.format(s, const_times[i_s,:,0][idx[i_s]==i])\n",
     "        line.append(mods)\n",
     "        table.append(line)\n",
     "\n",
@@ -98,13 +125,19 @@
     "    else:\n",
     "        table[np.argmax(counts)][1] = 'Rest of the modules'\n",
     "\n",
-    "    md = display(Latex(tabulate.tabulate(table, tablefmt='latex', \n",
-    "                                         headers=[\"Time stamps\", \"Modules and sequences\"])))  \n",
-    "    \n",
-    "for i_key, key in enumerate(['offset', 'slopesPC', 'slopesFF']):\n",
-    "    if const_times.shape[2]>i_key+1:\n",
-    "        plot_const_table(key, i_key+1)\n"
+    "    md = display(Latex(tabulate.tabulate(table, tablefmt='latex',\n",
+    "                                         headers=[\"Time stamps\", \"Modules and sequences\"])))\n",
+    "for i_key, key in enumerate(['Offset', 'SlopesPC', 'SlopesFF']):\n",
+    "    if const_times.shape[2] > i_key+1:\n",
+    "        const_table(key, i_key+1)"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
diff --git a/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..13ebd016af104dc0ce815bd2ee8926d1ee606cc6
--- /dev/null
+++ b/notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb
@@ -0,0 +1,461 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# AGIPD Retrieving Constants Pre-correction #\n",
+    "\n",
+    "Author: K.Ahmed, Version: 1.0\n",
+    "\n",
+    "Retrieving Required Constants for Offline Calibration of the AGIPD Detector"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "ExecuteTime": {
+     "end_time": "2019-02-21T11:30:06.730220Z",
+     "start_time": "2019-02-21T11:30:06.658286Z"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "cluster_profile = \"noDB\"\n",
+    "in_folder = \"/gpfs/exfel/exp/MID/201931/p900107/raw\" # the folder to read data from, required\n",
+    "out_folder =  \"/gpfs/exfel/data/scratch/ahmedk/test/AGIPD_Corr\"  # the folder to output to, required\n",
+    "sequences =  [-1] # sequences to correct, set to -1 for all, range allowed\n",
+    "modules = [-1] # modules to correct, set to -1 for all, range allowed\n",
+    "run = 11 # runs to process, required\n",
+    "\n",
+    "karabo_id = \"MID_DET_AGIPD1M-1\" # karabo karabo_id\n",
+    "karabo_da = ['-1']  # a list of data aggregators names, Default [-1] for selecting all data aggregators\n",
+    "path_template = 'RAW-R{:04d}-{}-S{:05d}.h5' # the template to use to access data\n",
+    "h5path_ctrl = '/CONTROL/{}/MDL/FPGA_COMP_TEST' # path to control information\n",
+    "karabo_id_control = \"SPB_IRU_AGIPD1M1\" # karabo-id for control device\n",
+    "karabo_da_control = 'DA02' # karabo DA for control infromation\n",
+    "\n",
+    "use_dir_creation_date = True # use the creation data of the input dir for database queries\n",
+    "cal_db_interface = \"tcp://max-exfl016:8015#8045\" # the database interface to use\n",
+    "creation_date_offset = \"00:00:00\" # add an offset to creation date, e.g. to get different constants\n",
+    "\n",
+    "calfile =  \"\" # path to calibration file. Leave empty if all data should come from DB\n",
+    "nodb = False # if set only file-based constants will be used\n",
+    "mem_cells = 0 # number of memory cells used, set to 0 to automatically infer\n",
+    "bias_voltage = 300\n",
+    "acq_rate = 0. # the detector acquisition rate, use 0 to try to auto-determine\n",
+    "gain_setting = 0.1 # the gain setting, use 0.1 to try to auto-determine\n",
+    "photon_energy = 9.2 # photon energy in keV\n",
+    "max_cells_db_dark = 0  # set to a value different than 0 to use this value for dark data DB queries\n",
+    "max_cells_db = 0 # set to a value different than 0 to use this value for DB queries\n",
+    "\n",
+    "# Correction Booleans\n",
+    "only_offset = False # Apply only Offset correction. if False, Offset is applied by Default. if True, Offset is only applied.\n",
+    "rel_gain = False # do relative gain correction based on PC data\n",
+    "xray_gain = True # do relative gain correction based on xray data\n",
+    "blc_noise = False # if set, baseline correction via noise peak location is attempted\n",
+    "blc_stripes = False # if set, baseline corrected via stripes\n",
+    "blc_hmatch = False # if set, base line correction via histogram matching is attempted \n",
+    "match_asics = False # if set, inner ASIC borders are matched to the same signal level\n",
+    "adjust_mg_baseline = False # adjust medium gain baseline to match highest high gain value"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Fill dictionaries comprising bools and arguments for correction and data analysis\n",
+    "\n",
+    "# Here the herarichy and dependability for correction booleans are defined \n",
+    "corr_bools = {}\n",
+    "\n",
+    "# offset is at the bottom of AGIPD correction pyramid.\n",
+    "corr_bools[\"only_offset\"] = only_offset\n",
+    "\n",
+    "# Dont apply any corrections if only_offset is requested \n",
+    "if not only_offset:\n",
+    "    corr_bools[\"adjust_mg_baseline\"] = adjust_mg_baseline\n",
+    "    corr_bools[\"rel_gain\"] = rel_gain\n",
+    "    corr_bools[\"xray_corr\"] = xray_gain\n",
+    "    corr_bools[\"blc_noise\"] = blc_noise\n",
+    "    corr_bools[\"blc_hmatch\"] = blc_hmatch"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "from collections import OrderedDict\n",
+    "\n",
+    "# make sure a cluster is running with ipcluster start --n=32, give it a while to start\n",
+    "import os\n",
+    "import h5py\n",
+    "import numpy as np\n",
+    "import matplotlib\n",
+    "matplotlib.use(\"agg\")\n",
+    "import matplotlib.pyplot as plt\n",
+    "from ipyparallel import Client\n",
+    "print(f\"Connecting to profile {cluster_profile}\")\n",
+    "view = Client(profile=cluster_profile)[:]\n",
+    "view.use_dill()\n",
+    "\n",
+    "from iCalibrationDB import Constants, Conditions, Detectors\n",
+    "from cal_tools.tools import (map_modules_from_folder, get_dir_creation_date)\n",
+    "from cal_tools.agipdlib import get_gain_setting\n",
+    "from dateutil import parser\n",
+    "from datetime import timedelta"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "ExecuteTime": {
+     "end_time": "2019-02-21T11:30:07.086286Z",
+     "start_time": "2019-02-21T11:30:06.929722Z"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "max_cells = mem_cells\n",
+    "\n",
+    "creation_time = None\n",
+    "if use_dir_creation_date:\n",
+    "    creation_time = get_dir_creation_date(in_folder, run)\n",
+    "    offset = parser.parse(creation_date_offset)\n",
+    "    delta = timedelta(hours=offset.hour, minutes=offset.minute, seconds=offset.second)\n",
+    "    creation_time += delta\n",
+    "    print(f\"Using {creation_time} as creation time\")\n",
+    "\n",
+    "if sequences[0] == -1:\n",
+    "    sequences = None\n",
+    "\n",
+    "if in_folder[-1] == \"/\":\n",
+    "    in_folder = in_folder[:-1]\n",
+    "print(f\"Outputting to {out_folder}\")\n",
+    "\n",
+    "os.makedirs(out_folder, exist_ok=True)\n",
+    "\n",
+    "import warnings\n",
+    "warnings.filterwarnings('ignore')\n",
+    "\n",
+    "from cal_tools.agipdlib import SnowResolution\n",
+    "\n",
+    "melt_snow = False if corr_bools[\"only_offset\"] else SnowResolution.NONE"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "control_fname = f'{in_folder}/RAW-R{run:04d}-{karabo_da_control}-S00000.h5'\n",
+    "\n",
+    "if gain_setting == 0.1:\n",
+    "    if creation_time.replace(tzinfo=None) < parser.parse('2020-01-31'):\n",
+    "        print(\"Set gain-setting to None for runs taken before 2020-01-31\")\n",
+    "        gain_setting = None\n",
+    "    else:\n",
+    "        try:\n",
+    "            gain_setting = get_gain_setting(control_fname, h5path_ctrl)\n",
+    "        except Exception as e:\n",
+    "            print(f'Error while reading gain setting: {e}\\n')\n",
+    "            \n",
+    "print(f\"Gain setting: {gain_setting}\")\n",
+    "print(f\"Detector in use is {karabo_id}\")\n",
+    "\n",
+    "if karabo_da[0] == '-1':\n",
+    "    if modules[0] == -1:\n",
+    "        modules = list(range(16))\n",
+    "    karabo_da = [\"AGIPD{:02d}\".format(i) for i in modules]\n",
+    "else:\n",
+    "    modules = [int(x[-2:]) for x in karabo_da]\n",
+    "\n",
+    "\n",
+    "h5path_ctrl = h5path_ctrl.format(karabo_id_control)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "ExecuteTime": {
+     "end_time": "2019-02-21T11:30:07.974174Z",
+     "start_time": "2019-02-21T11:30:07.914832Z"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "# set everything up filewise\n",
+    "print(f\"Checking the files before retrieving constants\")\n",
+    "mmf = map_modules_from_folder(in_folder, run, path_template, karabo_da, sequences)\n",
+    "\n",
+    "mapped_files, mod_ids, total_sequences, sequences_qm, _ = mmf"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Retrieve Constants ##"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from functools import partial\n",
+    "import yaml\n",
+    "\n",
+    "def retrieve_constants(karabo_id, bias_voltage, max_cells, acq_rate, \n",
+    "                       gain_setting, photon_energy, only_dark, nodb_with_dark, \n",
+    "                       cal_db_interface, creation_time, \n",
+    "                       corr_bools, pc_bools, inp):\n",
+    "    \"\"\"\n",
+    "    Retreive constant for each module in parallel and produce a dictionary\n",
+    "    with the creation-time and constant file path.\n",
+    "    \n",
+    "    :param karabo_id: (STR) Karabo ID\n",
+    "    :param bias_voltage: (FLOAT) Bias Voltage\n",
+    "    :param max_cells: (INT) Memory cells\n",
+    "    :param acq_rate: (FLOAT) Acquisition Rate\n",
+    "    :param gain_setting: (FLOAT) Gain setting\n",
+    "    :param photon_energy: (FLOAT) Photon Energy\n",
+    "    :param only_dark: (BOOL) only retrieve dark constants\n",
+    "    :param nodb_with_dark: (BOOL) no constant retrieval even for dark\n",
+    "    :param cal_db_interface: (STR) the database interface port\n",
+    "    :param creation_time: (STR) raw data creation time\n",
+    "    :param corr_bools: (DICT) A dictionary with bools for applying requested corrections\n",
+    "    :param pc_bools: (LIST) list of bools to retrieve pulse capacitor constants\n",
+    "    :param inp: (LIST) input for the parallel cluster of the partial function\n",
+    "    :return:\n",
+    "            mdata_dict: (DICT) dictionary with the metadata for the retrieved constants\n",
+    "            dev.device_name: (STR) device name\n",
+    "    \"\"\"\n",
+    "\n",
+    "    import numpy as np\n",
+    "    import sys\n",
+    "    import traceback\n",
+    "    \n",
+    "    from cal_tools.agipdlib import get_num_cells, get_acq_rate\n",
+    "    from cal_tools.agipdutils import assemble_constant_dict\n",
+    "    from cal_tools.tools import get_from_db\n",
+    "\n",
+    "    from iCalibrationDB import Constants, Conditions, Detectors\n",
+    "\n",
+    "    err = None\n",
+    "\n",
+    "    qm_files, qm, dev, idx = inp\n",
+    "    # get number of memory cells from a sequence file with image data\n",
+    "    for f in qm_files:\n",
+    "        if max_cells == 0:\n",
+    "            max_cells = get_num_cells(f, karabo_id, idx)\n",
+    "            if max_cells is None:\n",
+    "                if f != qm_files[-1]:\n",
+    "                    continue\n",
+    "                else:\n",
+    "                    raise ValueError(f\"No raw images found for {qm} for all sequences\")\n",
+    "            else:\n",
+    "                cells = np.arange(max_cells)\n",
+    "                # get out of the loop,\n",
+    "                # if max_cells is successfully calculated. \n",
+    "                break\n",
+    "\n",
+    "    if acq_rate == 0.:\n",
+    "        acq_rate = get_acq_rate(f, karabo_id, idx)\n",
+    "    else:\n",
+    "        acq_rate = None\n",
+    "\n",
+    "    print(f\"Set memory cells to {max_cells}\")\n",
+    "    print(f\"Set acquistion rate cells to {acq_rate} MHz\")\n",
+    "\n",
+    "    # avoid creating retireving constant, if requested.\n",
+    "    if not nodb_with_dark:  \n",
+    "        const_dict = assemble_constant_dict(corr_bools, pc_bools, max_cells, bias_voltage,\n",
+    "                                            gain_setting, acq_rate, photon_energy,\n",
+    "                                            beam_energy=None, only_dark=only_dark)\n",
+    "\n",
+    "        # Retrieve multiple constants through an input dictionary\n",
+    "        # to return a dict of useful metadata.\n",
+    "        mdata_dict = dict()\n",
+    "        for cname, cval in const_dict.items():\n",
+    "            try:\n",
+    "                condition = getattr(Conditions, cval[2][0]).AGIPD(**cval[2][1])\n",
+    "                co, mdata = \\\n",
+    "                    get_from_db(dev, getattr(Constants.AGIPD, cname)(),\n",
+    "                                condition, getattr(np, cval[0])(cval[1]),\n",
+    "                                cal_db_interface, creation_time, meta_only=True)\n",
+    "                mdata_const = mdata.calibration_constant_version\n",
+    "                # saving metadata in a dict\n",
+    "                mdata_dict[cname] = dict()\n",
+    "                # check if constant was sucessfully retrieved.\n",
+    "                if mdata.comm_db_success:                       \n",
+    "                    mdata_dict[cname][\"file-path\"] = f\"{mdata_const.hdf5path}\" \\\n",
+    "                                                     f\"{mdata_const.filename}\"\n",
+    "                    mdata_dict[cname][\"creation-time\"] = f\"{mdata_const.begin_at}\"\n",
+    "                else:\n",
+    "                    mdata_dict[cname][\"file-path\"] = const_dict[cname]\n",
+    "                    mdata_dict[cname][\"creation-time\"] = None\n",
+    "            except Exception as e:\n",
+    "                err = f\"Error: {e}, Traceback: {traceback.format_exc()}\"\n",
+    "                print(err)\n",
+    "\n",
+    "    return qm, mdata_dict, dev.device_name, acq_rate, max_cells, err\n",
+    "\n",
+    "pc_bools = [corr_bools.get(\"rel_gain\"),\n",
+    "            corr_bools.get(\"adjust_mg_baseline\"),\n",
+    "            corr_bools.get('blc_noise'),\n",
+    "            corr_bools.get('blc_hmatch'),\n",
+    "            corr_bools.get('blc_stripes'),\n",
+    "            melt_snow]\n",
+    "\n",
+    "# Extracting Instrument string\n",
+    "instrument = karabo_id.split(\"_\")[0]\n",
+    "if instrument == \"SPB\":\n",
+    "    dinstance = \"AGIPD1M1\"\n",
+    "else:\n",
+    "    dinstance = \"AGIPD1M2\"\n",
+    "\n",
+    "print(f\"Instrument {instrument}\")\n",
+    "print(f\"Detector instance {dinstance}\")\n",
+    "\n",
+    "inp = []\n",
+    "only_dark = False\n",
+    "nodb_with_dark = False\n",
+    "if not nodb:\n",
+    "    only_dark=(calfile != \"\")\n",
+    "if calfile != \"\" and not corr_bools[\"only_offset\"]:\n",
+    "    nodb_with_dark = nodb\n",
+    "\n",
+    "# A dict to connect virtual device\n",
+    "# to actual device name.\n",
+    "for i in range(len(modules)):\n",
+    "    qm = f\"Q{i//4+1}M{i%4+1}\"\n",
+    "    if qm in mapped_files and not mapped_files[qm].empty():\n",
+    "        device = getattr(getattr(Detectors, dinstance), qm)\n",
+    "        qm_files = [str(mapped_files[qm].get()) for _ in range(mapped_files[qm].qsize())]\n",
+    "\n",
+    "    else:\n",
+    "        print(f\"Skipping {qm}\")\n",
+    "        continue\n",
+    "\n",
+    "    inp.append((qm_files, qm, device, i))\n",
+    "\n",
+    "p = partial(retrieve_constants, karabo_id, bias_voltage, max_cells, \n",
+    "            acq_rate, gain_setting, photon_energy, only_dark, nodb_with_dark, \n",
+    "            cal_db_interface, creation_time, \n",
+    "            corr_bools, pc_bools)\n",
+    "\n",
+    "results = view.map_sync(p, inp)\n",
+    "#results = list(map(p, inp))\n",
+    "mod_dev = dict()\n",
+    "mdata_dict = dict()\n",
+    "for r in results:\n",
+    "    if r:\n",
+    "        qm, md_dict, dname, acq_rate, max_cells, err = r\n",
+    "        mod_dev[dname] = {\"mod\": qm, \"err\": err}\n",
+    "        if err:\n",
+    "            print(f\"Error for module {qm}: {err}\")\n",
+    "        mdata_dict[dname] = md_dict\n",
+    "# check if it is requested not to retrieve any constants from the database\n",
+    "if not nodb_with_dark:\n",
+    "    with open(f\"{out_folder}/retrieved_constants.yml\", \"w\") as outfile:\n",
+    "        yaml.dump(mdata_dict, outfile)\n",
+    "    print(\"\\nRetrieved constants for modules: \",\n",
+    "          f\"{[', '.join([f'Q{x//4+1}M{x%4+1}' for x in modules])]}\")\n",
+    "    print(f\"Operating conditions are:\\n• Bias voltage: {bias_voltage}\\n• Memory cells: {max_cells}\\n\"\n",
+    "          f\"• Acquisition rate: {acq_rate}\\n• Gain setting: {gain_setting}\\n• Photon Energy: {photon_energy}\\n\")\n",
+    "    print(f\"Constant metadata is saved in retrieved_constants.yml\\n\")\n",
+    "else:\n",
+    "    print(\"No constants were retrieved as calibrated files will be used.\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\"Constants are retrieved with creation time: \")\n",
+    "i = 0\n",
+    "when = dict()\n",
+    "to_store = []\n",
+    "\n",
+    "for dname, dinfo in mod_dev.items():\n",
+    "    print(dinfo[\"mod\"], \":\")\n",
+    "    line = [dinfo[\"mod\"]]\n",
+    "    if dname in mdata_dict:\n",
+    "        for cname, mdata in mdata_dict[dname].items():\n",
+    "            if hasattr(mdata[\"creation-time\"], 'strftime'):\n",
+    "                mdata[\"creation-time\"] = mdata[\"creation-time\"].strftime('%y-%m-%d %H:%M')\n",
+    "            print(f'{cname:.<12s}', mdata[\"creation-time\"])\n",
+    "        # Store few time stamps if exists\n",
+    "        # Add NA to keep array structure\n",
+    "    for cname in ['Offset', 'SlopesPC', 'SlopesFF']:\n",
+    "        if not dname in mdata_dict or dinfo[\"err\"]:\n",
+    "            line.append('Err')\n",
+    "        else:\n",
+    "            if cname in mdata_dict[dname]:\n",
+    "                if mdata_dict[dname][cname][\"creation-time\"]:\n",
+    "                    line.append(mdata_dict[dname][cname][\"creation-time\"])\n",
+    "                else:\n",
+    "                    line.append('NA')\n",
+    "            else:\n",
+    "                line.append('NA')\n",
+    "    to_store.append(line)\n",
+    "\n",
+    "    i += 1\n",
+    "    if sequences:\n",
+    "        seq_num = sequences[0]\n",
+    "    else:\n",
+    "        # if sequences[0] changed to None as it was -1\n",
+    "        seq_num = 0\n",
+    "        \n",
+    "with open(f\"{out_folder}/retrieved_constants.yml\",\"r\") as fyml:\n",
+    "    time_summary = yaml.load(fyml)\n",
+    "    time_summary.update({\"time-summary\": {\n",
+    "                                          \"SAll\":to_store\n",
+    "                                        }})\n",
+    "with open(f\"{out_folder}/retrieved_constants.yml\",\"w\") as fyml:\n",
+    "        yaml.safe_dump(time_summary, fyml)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.7"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/xfel_calibrate/calibrate.py b/xfel_calibrate/calibrate.py
index fd4e7a3c37fff97bb166b28e089da01ef0727a09..6a9e40efde73e4ccb8c0beb115ad856a292cf0b3 100755
--- a/xfel_calibrate/calibrate.py
+++ b/xfel_calibrate/calibrate.py
@@ -423,7 +423,7 @@ elif len(sys.argv) >= 3:
             func = get_notebook_function(nb, ext_func)
 
             if func is None:
-                warnings.warn("Didn't find concurrency function {} in notebook".format(ext_func),
+                warnings.warn(f"Didn't find concurrency function {ext_func} in notebook",
                               RuntimeWarning)
 
             else:
@@ -528,7 +528,7 @@ def create_finalize_script(fmt_args, temp_path, job_list):
                     echo 'Running finalize script'
                     python3 -c "from xfel_calibrate.finalize import finalize; 
                     finalize(joblist={{joblist}}, 
-                             finaljob=$1, 
+                             finaljob=$1,
                              run_path='{{run_path}}',
                              out_path='{{out_path}}',
                              project='{{project}}', 
@@ -567,13 +567,12 @@ def save_executed_command(run_tmp_path, version):
         finfile.write(' '.join(sys.argv))
 
 
-def get_launcher_command(args, temp_path, dependent, job_list):
+def get_launcher_command(args, temp_path, dep_jids):
     """
     Return a slurm launcher command
     :param args: Command line arguments
     :param temp_path: Temporary path to run job
-    :param dependent: True if job is dependent
-    :param job_list: A list of dependent jobs
+    :param dep_jids: A list of dependent jobs
     :return: List of commands and parameters to be used by subprocess
     """
 
@@ -604,9 +603,9 @@ def get_launcher_command(args, temp_path, dependent, job_list):
 
     launcher_slurm += " --mem {}G".format(args.get('slurm_mem', '500'))
 
-    if dependent:
+    if len(dep_jids):
         srun_dep = " --dependency=afterok"
-        for jobid in job_list:
+        for jobid in dep_jids:
             srun_dep += ":{}".format(jobid)
         launcher_slurm += srun_dep
 
@@ -631,7 +630,7 @@ def remove_duplications(l):
 
 def concurrent_run(temp_path, nb, nbname, args, cparm=None, cval=None,
                    final_job=False, job_list=[], fmt_args={}, cluster_cores=8,
-                   sequential=False, dependent=False,
+                   sequential=False, dep_jids=[],
                    show_title=True, cluster_profile='NO_CLUSTER'):
     """ Launch a concurrent job on the cluster via SLURM
     """
@@ -666,7 +665,7 @@ def concurrent_run(temp_path, nb, nbname, args, cparm=None, cval=None,
     # then run an sbatch job
     srun_base = []
     if not sequential:
-        srun_base = get_launcher_command(args, temp_path, dependent, job_list)
+        srun_base = get_launcher_command(args, temp_path, dep_jids)
         print(" ".join(srun_base))
 
     srun_base += [os.path.abspath("{}/bin/slurm_calibrate.sh".format(os.path.dirname(__file__))),  # path to helper sh
@@ -773,6 +772,7 @@ def run():
         print("Not running on cluster")
 
     try:
+        pre_notebooks = notebooks[detector][caltype].get("pre_notebooks", [])
         notebook = notebooks[detector][caltype]["notebook"]
         dep_notebooks = notebooks[detector][caltype].get("dep_notebooks", [])
         notebook = os.path.abspath(
@@ -795,7 +795,7 @@ def run():
             func = get_notebook_function(nb, ext_func)
 
             if func is None:
-                warnings.warn("Didn't find concurrency function {} in notebook".format(ext_func),
+                warnings.warn(f"Didn't find concurrency function {ext_func} in notebook",
                               RuntimeWarning)
             else:
                 # remove help calls as they will cause the argument parser to exit
@@ -888,29 +888,43 @@ def run():
                     }
 
         joblist = []
-        if concurrency.get("parameter", None) is None:
-            cluster_cores = concurrency.get("cluster cores", 8)
+        cluster_cores = concurrency.get("cluster cores", 8)
+        # Check if there are pre-notebooks
+        for i, notebook in enumerate(pre_notebooks):
+            notebook_path = os.path.abspath(
+                "{}/{}".format(os.path.dirname(__file__), notebook))
+            with open(notebook_path, "r") as f:
+                lead_nb = nbformat.read(f, as_version=4)
+                jobid = concurrent_run(run_tmp_path, lead_nb,
+                                       os.path.basename(notebook),
+                                       args,
+                                       job_list=joblist, fmt_args=fmt_args,
+                                       cluster_cores=cluster_cores,
+                                       sequential=sequential,
+                                       cluster_profile=cluster_profile)
+                joblist.append(jobid)
 
+        if concurrency.get("parameter", None) is None:
             jobid = concurrent_run(run_tmp_path, nb,
                                    os.path.basename(notebook), args,
                                    final_job=True, job_list=joblist,
                                    fmt_args=fmt_args,
                                    cluster_cores=cluster_cores,
                                    sequential=sequential,
+                                   dep_jids=joblist,
                                    cluster_profile=cluster_profile)
-
             joblist.append(jobid)
         else:
             cvar = concurrency["parameter"]
             cvals = args.get(cvar, None)
-            cluster_cores = concurrency.get("cluster cores", 8)
 
             con_func = concurrency.get("use function", None)
             # Consider [-1] as None
             if cvals is None or cvals == [-1]:
                 defcval = concurrency.get("default concurrency", None)
                 if defcval is not None:
-                    print("Concurrency parameter '{}' is taken from notebooks.py".format(cvar))
+                    print(f"Concurrency parameter '{cvar}' "
+                          f"is taken from notebooks.py")
                     if not isinstance(defcval, (list, tuple)):
                         cvals = range(defcval)
                     else:
@@ -919,8 +933,8 @@ def run():
             if cvals is None:
                 defcval = get_par_attr(parms, cvar, 'value')
                 if defcval is not None:
-                    print("Concurrency parameter '{}' is taken from '{}'".format(
-                        cvar, notebook))
+                    print(f"Concurrency parameter '{cvar}' "
+                          f"is taken from '{notebook}'")
                     if not isinstance(defcval, (list, tuple)):
                         cvals = [defcval]
                     else:
@@ -929,7 +943,7 @@ def run():
             if con_func:
                 func = get_notebook_function(nb, con_func)
                 if func is None:
-                    warnings.warn("Didn't find concurrency function {} in notebook".format(con_func),
+                    warnings.warn(f"Didn't find concurrency function {con_func} in notebook",
                                   RuntimeWarning)
                 else:
                     df = {}
@@ -944,12 +958,13 @@ def run():
                     for arg in sig.parameters:
                         callargs.append(args[arg])
                     cvals = f(*callargs)
-                    print("Split concurrency into {}".format(cvals))
+                    print(f"Split concurrency into {cvals}")
 
             # get expected type
             cvtype = get_par_attr(parms, cvar, 'type', list)
             cvals = remove_duplications(cvals)
 
+            jlist = []
             for cnum, cval in enumerate(cvals):
                 show_title = cnum == 0
                 # Job is not final if there are dependent notebooks
@@ -962,9 +977,10 @@ def run():
                                        cluster_cores=cluster_cores,
                                        sequential=sequential,
                                        show_title=show_title,
+                                       dep_jids=joblist,
                                        cluster_profile=cluster_profile)
-                joblist.append(jobid)
-
+                jlist.append(jobid)
+            joblist.extend(jlist)
         # Run dependent notebooks
         for i, notebook in enumerate(dep_notebooks):
             notebook_path = os.path.abspath(
@@ -975,11 +991,11 @@ def run():
                 jobid = concurrent_run(run_tmp_path, nb,
                                        os.path.basename(notebook),
                                        args,
+                                       dep_jids=joblist,
                                        final_job=final_job,
                                        job_list=joblist, fmt_args=fmt_args,
                                        cluster_cores=cluster_cores,
                                        sequential=sequential,
-                                       dependent=True,
                                        cluster_profile=cluster_profile)
                 joblist.append(jobid)
 
diff --git a/xfel_calibrate/finalize.py b/xfel_calibrate/finalize.py
index 0a54fd134f255d630ebe95886ac0227cbe47c8ee..695eb293b710c4150934c6738a1c07201b3f6cf8 100644
--- a/xfel_calibrate/finalize.py
+++ b/xfel_calibrate/finalize.py
@@ -313,6 +313,11 @@ def make_report(run_path, tmp_path, out_path, project, author, version,
     for dtmp in temp_dirs:
         rmtree(f'{dtmp}/')
 
+    # Archiving files in slurm_tmp
+    if os.path.isfile(f'{out_path}/retrieved_constants.yml'):
+        move(f'{out_path}/retrieved_constants.yml',
+             f"{tmp_path}")
+
     # Moving temporary files to out-folder after successful execution
     # This helps in keeping elements needed for re-producibility.
     print(f"Moving temporary files to final location"
diff --git a/xfel_calibrate/notebooks.py b/xfel_calibrate/notebooks.py
index 12c97b96acbfe3b921b747f207f695a7b10f1955..494d3686fdc36fe2bbe7c02912d42e00a1ceac66 100644
--- a/xfel_calibrate/notebooks.py
+++ b/xfel_calibrate/notebooks.py
@@ -26,6 +26,7 @@ notebooks = {
                             "cluster cores": 16},
         },
         "CORRECT": {
+            "pre_notebooks": ["notebooks/AGIPD/AGIPD_Retrieve_Constants_Precorrection.ipynb"],
             "notebook": "notebooks/AGIPD/AGIPD_Correct_and_Verify.ipynb",
             "dep_notebooks": [
                 "notebooks/AGIPD/AGIPD_Correct_and_Verify_Summary_NBC.ipynb"],