Repository: cta-observatory/ctapipe
Pull request #341 (2017-02-28 10:56:48)

Charge resolution component
Converted the charge resolution class to a Component, and updated the examples  of obtaining the charge resolution to be Tools, and also split into two parts: 

1 - extracting the charge resolution numpy array and saving the ChargeResolutionCalculator object into a pickle file
2 - plot multiple charge resolution numpy arrays by opening multiple ChargeResolutionCalculator pickle files


Patch:
diff --git a/ctapipe/analysis/camera/chargeresolution.py b/ctapipe/analysis/camera/chargeresolution.py
index 520388a5..837ab601 100644
--- a/ctapipe/analysis/camera/chargeresolution.py
+++ b/ctapipe/analysis/camera/chargeresolution.py
@@ -1,10 +1,14 @@
-from astropy import log
+from os.path import dirname, exists
+from os import makedirs
 import numpy as np
 from math import log10, sqrt
-from scipy.stats import binned_statistic
+from scipy.stats import binned_statistic as bs
+from traitlets import Int, Bool
+import pickle
+from ctapipe.core import Component
 
 
-class ChargeResolution:
+class ChargeResolutionCalculator(Component):
     """
     Class to handle the calculation of Charge Resolution.
 
@@ -26,14 +30,40 @@ class ChargeResolution:
     variation_yedges : ndarray
         Edges of the Y bins for the variation histogram.
     """
-    def __init__(self, max_pe):
+    name = 'ChargeResolutionCalculator'
+
+    max_pe = Int(2000, help='Maximum pe to calculate the charge resolution '
+                            'up to').tag(config=True)
+    binning = Int(60, allow_none=True,
+                  help='Number of bins for the Charge Resolution. If None, '
+                       'no binning is performed.').tag(config=True)
+    log_bins = Bool(True, help='Bin the x axis linearly instead of '
+                               'logarithmic.').tag(config=True)
+
+    def __init__(self, config, tool, **kwargs):
         """
+        Calculator of charge resolution.
+
         Parameters
         ----------
-        max_pe : int
-            Maximum pe to calculate the charge resolution up to.
+        config : traitlets.loader.Config
+            Configuration specified by config file or cmdline arguments.
+            Used to set traitlet values.
+            Set to None if no configuration to pass.
+        tool : ctapipe.core.Tool
+            Tool executable that is calling this component.
+            Passes the correct logger to the component.
+            Set to None if no Tool to pass.
+        reductor : ctapipe.calib.camera.reductors.Reductor
+            The reductor to use to reduce the waveforms in the event.
+            By default no data volume reduction is applied, and the dl0 samples
+            will equal the r1 samples.
+        kwargs
         """
-        self.max_pe = max_pe
+        super().__init__(config=config, parent=tool, **kwargs)
+
+        self.sum_array = np.zeros(self.max_pe)
+        self.n_array = np.zeros(self.max_pe)
         self.sum_dict = {}
         self.n_dict = {}
 
@@ -57,70 +87,25 @@ class ChargeResolution:
         ----------
         true_charge : ndarray
             Array of true (MC) charge.
+            Obtained from event.mc.tel[telid].image[channel]
         measured_charge : ndarray
-               Array of measured (calibrated) charge.
+            Array of measured (dl1 calibrated) charge.
+            Obtained from event.mc.tel[tel_id].photo_electron_image
         """
-        x = true_charge[(measured_charge > 0) & (true_charge > 0)]
-        y = measured_charge[(measured_charge > 0) & (true_charge > 0)]
+        above_0 = (measured_charge > 0) & (true_charge > 0)
+        x = true_charge[above_0]
+        y = measured_charge[above_0]
         h, _, _ = np.histogram2d(np.log10(y),
                                  np.log10(x),
                                  bins=self.variation_hist_nbins,
                                  range=self.variation_hist_range)
         self.variation_hist += h
-        for trueq in np.unique(true_charge):
-            if trueq == 0 or trueq > self.max_pe:
-                continue
-            relevant_q = measured_charge[true_charge == trueq]
-            if trueq not in self.sum_dict:
-                self.sum_dict[trueq] = 0
-                self.n_dict[trueq] = 0
-            self.sum_dict[trueq] += np.sum(np.power(relevant_q - trueq, 2))
-            self.n_dict[trueq] += relevant_q.size
-
-    def add_source(self, calibrated_source, telescopes=None):
-        """
-        Fill the class parameters with a calibrated source.
 
-        Parameters
-        ----------
-        calibrated_source : ndarray
-            `ctapipe.calib.camera.calibrators.calibrate_source` generator.
-        telescopes : list
-               List of telescopes to include into the charge resolution.
-               If None, all telescopes will be included.
-        """
-        log.info('[chargeres] Adding source')
-        for event in calibrated_source:
-            tels = list(event.dl0.tels_with_data)
-            if telescopes is not None:
-                tels = []
-                for tel in telescopes:
-                    if tel in event.dl0.tels_with_data:
-                        tels.append(tel)
-
-            # Check source has required information
-            if event.count == 0:
-                # Check source is calibrated
-                try:
-                    if 'dl1' not in event:
-                        raise KeyError
-                except KeyError:
-                    log.exception('[chargeres] Source has not been calibrated')
-                    raise
-
-                # Check events have true charge included
-                try:
-                    if np.all(event.mc.tel[tels[0]].photo_electron_image == 0):
-                        raise KeyError
-                except KeyError:
-                    log.exception('[chargeres] Source does not '
-                                  'contain true charge')
-                    raise
-
-            for telid in tels:
-                true_charge = event.mc.tel[telid].photo_electron_image
-                measured_charge = event.dl1.tel[telid].calibrated_image
-                self.add_charges(true_charge, measured_charge)
+        in_range = (true_charge > 0) & (true_charge <= self.max_pe)
+        true_q = true_charge[in_range]
+        measured_q = measured_charge[in_range]
+        np.add.at(self.sum_array, true_q - 1, np.power(measured_q - true_q, 2))
+        np.add.at(self.n_array, true_q - 1, 1)
 
     def get_charge_resolution(self):
         """
@@ -139,67 +124,41 @@ class ChargeResolution:
         scaled_chargeres_error : ndarray
             The error on the charge resolution divided by the Goal.
         """
-        log.debug('[chargeres] Calculating charge resolution')
-        true_charge = np.fromiter(iter(self.sum_dict.keys()), dtype=int)
-        summed_charge = np.fromiter(iter(self.sum_dict.values()), dtype=float)
-        num = np.fromiter(iter(self.n_dict.values()), dtype=int)
+        self.log.debug('[chargeres] Calculating charge resolution')
 
-        chargeres = np.sqrt((summed_charge / num) + true_charge) / true_charge
-        chargeres_error = chargeres * (1 / np.sqrt(2 * num))
+        n_1 = self.n_array > 0
+        n = self.n_array[n_1]
+        true = (np.arange(self.max_pe) + 1)[n_1]
+        sum_ = self.sum_array[n_1]
 
-        scale = self.goal(true_charge)
-        scaled_chargeres = chargeres/scale
-        scaled_chargeres_error = chargeres_error/scale
+        res = np.sqrt((sum_ / n) + true) / true
+        res_error = res * (1 / np.sqrt(2 * n))
 
-        return true_charge, chargeres, chargeres_error, \
-            scaled_chargeres, scaled_chargeres_error
+        scale = self.goal(true)
+        scaled_res = res / scale
+        scaled_res_error = res_error / scale
 
-    def get_binned_charge_resolution(self, logarithmic_binning=True):
-        """
-        Calculate and obtain the charge resolution graph arrays, and bin them.
+        if self.binning is not None:
+            x = true
+            if self.log_bins:
+                x = np.log10(true)
 
-        Parameters
-        ----------
-        logarithmic_binning : bool
-            If True, binning is performed in logarithmic space.
+            def binning(array):
+                return bs(x, array, 'mean', bins=self.binning)
 
-        Returns
-        -------
-        bin_true_charge : ndarray
-            The X axis true charges.
-        bin_chargeres : ndarray
-            The Y axis charge resolution values.
-        bin_chargeres_error : ndarray
-            The error on the charge resolution.
-        bin_scaled_chargeres : ndarray
-            The Y axis charge resolution divided by the Goal.
-        bin_scaled_chargeres_error : ndarray
-            The error on the charge resolution divided by the Goal.
-        """
-        true_charge, chargeres, chargeres_error, scaled_chargeres, \
-            scaled_chargeres_error = self.get_charge_resolution()
-
-        x = true_charge
-        if logarithmic_binning:
-            x = np.log10(true_charge)
-
-        def binning(array):
-            return binned_statistic(x, array, 'mean', bins=60)
+            def sum_errors(array):
+                return np.sqrt(np.sum(np.power(array, 2))) / array.size
 
-        def sum_errors(array):
-            return np.sqrt(np.sum(np.power(array, 2)))/array.size
+            def bin_errors(array):
+                return bs(x, array, sum_errors, bins=self.binning)
 
-        def bin_errors(array):
-            return binned_statistic(x, array, sum_errors, bins=60)
+            true, _, _ = binning(true)
+            res, _, _ = binning(res)
+            res_error, _, _ = bin_errors(res_error)
+            scaled_res, _, _ = binning(scaled_res)
+            scaled_res_error, _, _ = bin_errors(scaled_res_error)
 
-        bin_true_charge, _, _ = binning(true_charge)
-        bin_chargeres, _, _ = binning(chargeres)
-        bin_chargeres_error, _, _ = bin_errors(chargeres_error)
-        bin_scaled_chargeres, _, _ = binning(scaled_chargeres)
-        bin_scaled_chargeres_error, _, _ = bin_errors(scaled_chargeres_error)
-
-        return bin_true_charge, bin_chargeres, bin_chargeres_error, \
-            bin_scaled_chargeres, bin_scaled_chargeres_error
+        return true, res, res_error, scaled_res, scaled_res_error
 
     @staticmethod
     def limit_curves(npe, n_nsb, n_add, enf, sigma2):
@@ -223,7 +182,7 @@ class ChargeResolution:
             Percentage ofmultiplicative errors.
         """
         return (np.sqrt((n_nsb + n_add) + np.power(enf, 2) * npe +
-                np.power(sigma2 * npe, 2)) / npe).astype(float)
+                        np.power(sigma2 * npe, 2)) / npe).astype(float)
 
     @staticmethod
     def requirement(npe):
@@ -244,8 +203,8 @@ class ChargeResolution:
         # If npe is not an array, temporarily convert it to one
         npe = np.array([npe])
 
-        requirement = ChargeResolution.limit_curves(npe, n_nsb, n_add, enf,
-                                                    sigma2)
+        lc = ChargeResolutionCalculator.limit_curves
+        requirement = lc(npe, n_nsb, n_add, enf, sigma2)
         requirement[npe > defined_npe] = np.nan
 
         return requirement[0]
@@ -269,7 +228,8 @@ class ChargeResolution:
         # If npe is not an array, temporarily convert it to one
         npe = np.array([npe])
 
-        goal = ChargeResolution.limit_curves(npe, n_nsb, n_add, enf, sigma2)
+        lc = ChargeResolutionCalculator.limit_curves
+        goal = lc(npe, n_nsb, n_add, enf, sigma2)
         goal[npe > defined_npe] = np.nan
 
         return goal[0]
@@ -289,3 +249,35 @@ class ChargeResolution:
         poisson = np.sqrt(npe) / npe
 
         return poisson[0]
+
+    def save(self, path):
+        d = dict(max_pe=self.max_pe,
+                 sum_array=self.sum_array,
+                 n_array=self.n_array,
+                 variation_hist_nbins=self.variation_hist_nbins,
+                 variation_hist_range=self.variation_hist_range,
+                 variation_hist=self.variation_hist,
+                 variation_xedges=self.variation_xedges,
+                 variation_yedges=self.variation_yedges)
+        output_dir = dirname(path)
+        if not exists(output_dir):
+            self.log.info("[output] Creating directory: {}".format(output_dir))
+            makedirs(output_dir)
+        with open(path, 'wb') as f:
+            self.log.info("Saving Charge Resolution file: {}".format(path))
+            pickle.dump(d, f, pickle.HIGHEST_PROTOCOL)
+
+    def load(self, path):
+        if not path.endswith("pickle"):
+            self.log.error("File is not a pickle file: {}".format(path))
+        with open(path, 'rb') as f:
+            self.log.info("Loading Charge Resolution file: {}".format(path))
+            d = pickle.load(f)
+            self.max_pe = d['max_pe']
+            self.sum_array = d['sum_array']
+            self.n_array = d['n_array']
+            self.variation_hist_nbins = d['variation_hist_nbins']
+            self.variation_hist_range = d['variation_hist_range']
+            self.variation_hist = d['variation_hist']
+            self.variation_xedges = d['variation_xedges']
+            self.variation_yedges = d['variation_yedges']
diff --git a/examples/extract_charge_resolution.py b/examples/extract_charge_resolution.py
new file mode 100644
index 00000000..7aac2291
--- /dev/null
+++ b/examples/extract_charge_resolution.py
@@ -0,0 +1,119 @@
+import os
+import numpy as np
+from traitlets import Dict, List, Int, Unicode
+from ctapipe.core import Tool
+from ctapipe.io.eventfilereader import HessioFileReader
+from ctapipe.calib.camera.r1 import CameraR1CalibratorFactory
+from ctapipe.calib.camera.dl0 import CameraDL0Reducer
+from ctapipe.calib.camera.dl1 import CameraDL1Calibrator
+from ctapipe.calib.camera.charge_extractors import ChargeExtractorFactory
+from ctapipe.analysis.camera.chargeresolution import ChargeResolutionCalculator
+from tqdm import tqdm
+
+
+class ChargeResolutionGenerator(Tool):
+    name = "ChargeResolutionGenerator"
+    description = "Generate the a pickle file of ChargeResolutionFile for " \
+                  "a MC file."
+
+    telescopes = List(Int, None, allow_none=True,
+                      help='Telescopes to include from the event file. '
+                           'Default = All telescopes').tag(config=True)
+    output_name = Unicode('charge_resolution',
+                          help='Name of the output charge resolution pickle '
+                               'file').tag(config=True)
+
+    aliases = Dict(dict(f='HessioFileReader.input_path',
+                        max_events='HessioFileReader.max_events',
+                        extractor='ChargeExtractorFactory.extractor',
+                        window_width='ChargeExtractorFactory.window_width',
+                        window_start='ChargeExtractorFactory.window_start',
+                        window_shift='ChargeExtractorFactory.window_shift',
+                        sig_amp_cut_HG='ChargeExtractorFactory.sig_amp_cut_HG',
+                        sig_amp_cut_LG='ChargeExtractorFactory.sig_amp_cut_LG',
+                        lwt='ChargeExtractorFactory.lwt',
+                        clip_amplitude='CameraDL1Calibrator.clip_amplitude',
+                        radius='CameraDL1Calibrator.radius',
+                        max_pe='ChargeResolutionCalculator.max_pe',
+                        T='ChargeResolutionGenerator.telescopes',
+                        O='ChargeResolutionGenerator.output_name',
+                        ))
+    classes = List([HessioFileReader,
+                    ChargeExtractorFactory,
+                    CameraDL1Calibrator,
+                    ChargeResolutionCalculator
+                    ])
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.file_reader = None
+        self.r1 = None
+        self.dl0 = None
+        self.dl1 = None
+        self.calculator = None
+
+    def setup(self):
+        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"
+        kwargs = dict(config=self.config, tool=self)
+
+        self.file_reader = HessioFileReader(**kwargs)
+
+        extractor_factory = ChargeExtractorFactory(**kwargs)
+        extractor_class = extractor_factory.get_class()
+        extractor = extractor_class(**kwargs)
+
+        r1_factory = CameraR1CalibratorFactory(origin=self.file_reader.origin,
+                                               **kwargs)
+        r1_class = r1_factory.get_class()
+        self.r1 = r1_class(**kwargs)
+
+        self.dl0 = CameraDL0Reducer(**kwargs)
+
+        self.dl1 = CameraDL1Calibrator(extractor=extractor, **kwargs)
+
+        self.calculator = ChargeResolutionCalculator(**kwargs)
+
+    def start(self):
+        desc = "Filling Charge Resolution"
+        with tqdm(desc=desc) as pbar:
+            source = self.file_reader.read()
+            for event in source:
+                pbar.update(1)
+                tels = list(event.dl0.tels_with_data)
+
+                # Check events have true charge included
+                if event.count == 0:
+                    try:
+                        if np.all(event.mc.tel[
+                                      tels[0]].photo_electron_image == 0):
+                            raise KeyError
+                    except KeyError:
+                        self.log.exception('Source does not contain '
+                                           'true charge!')
+                        raise
+
+                self.r1.calibrate(event)
+                self.dl0.reduce(event)
+                self.dl1.calibrate(event)
+
+                if self.telescopes:
+                    tels = []
+                    for tel in self.telescopes:
+                        if tel in event.dl0.tels_with_data:
+                            tels.append(tel)
+
+                for telid in tels:
+                    true_charge = event.mc.tel[telid].photo_electron_image
+                    measured_charge = event.dl1.tel[telid].image[0]
+                    self.calculator.add_charges(true_charge, measured_charge)
+
+    def finish(self):
+        directory = self.file_reader.output_directory
+        name = "{}.pickle".format(self.output_name)
+        ouput_path = os.path.join(directory, name)
+        self.calculator.save(ouput_path)
+
+
+if __name__ == '__main__':
+    exe = ChargeResolutionGenerator()
+    exe.run()
diff --git a/examples/plot_charge_resolution.py b/examples/plot_charge_resolution.py
index 641e3b00..c7f6c708 100644
--- a/examples/plot_charge_resolution.py
+++ b/examples/plot_charge_resolution.py
@@ -1,425 +1,202 @@
-import argparse
-import glob
-import os
-
-import sys
-from astropy import log
-from os.path import basename, splitext, dirname
-import numpy as np
+from os.path import dirname, basename, expanduser, exists, splitext
+from os import makedirs
 from matplotlib import pyplot as plt
-from matplotlib.colors import LogNorm
-from math import log10, floor, ceil
+from math import log10
 import warnings
-
-from ctapipe.utils.datasets import get_path
-from ctapipe.io.files import InputFile
-from ctapipe.calib.camera.dl1 import calibration_parameters, \
-    calibrate_source
-from ctapipe.analysis.camera.chargeresolution import ChargeResolution
-
-
-def argparsing():
-    parser = argparse.ArgumentParser(
-        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-    parser.add_argument('-f', '--file', dest='input_paths',
-                        default=[get_path('gamma_test.simtel.gz')], nargs='*',
-                        help='path to the input files to be combined for a '
-                             'single charge resolution.')
-    parser.add_argument('-O', '--origin', dest='origin', action='store',
-                        choices=InputFile.origin_list(),
-                        default='hessio', help='origin of the file')
-    parser.add_argument('-t', '--telescope', dest='tel', action='store',
-                        type=int, default=None, nargs='*',
-                        help='list of telecopes to be included. '
-                             'Default = All')
-    parser.add_argument('-o', '--output', dest='output_path', action='store',
-                        default=None,
-                        help='path to store a pdf output of the plots. '
-                             'default = display on screen instead')
-    parser.add_argument('--comparison', dest='comparison', action='store',
-                        default=None,
-                        help='output path for a True Charge versus Measured'
-                             'Charge graph. Default = do not plot graph')
-    parser.add_argument('-M', '--maxpe', dest='maxpe', action='store',
-                        default=None, type=float,
-                        help='maximum pe to calculate the charge resolution'
-                             ' up to. Default = maximum pe in file')
-    parser.add_argument('--maxpeplot', dest='maxpeplot', action='store',
-                        default=None, type=float,
-                        help='maximum pe to plot up to. Default = maxpe')
-    parser.add_argument('-B', '--binning', dest='binning', action='store',
-                        default="log", choices=['none', 'normal', 'log'],
-                        help='binning of the charge resoltion graph: '
-                             '"none" = no binning, "normal" = bin, '
-                             '"log" = bin in log space.')
-    parser.add_argument('--normalx', dest='normalx', action='store_true',
-                        default=False,
-                        help='Use a normal x axis instead of the defualt log'
-                             'axis')
-    parser.add_argument('--normaly', dest='normaly', action='store_true',
-                        default=False,
-                        help='Use a normal y axis instead of the defualt log'
-                             'axis')
-    parser.add_argument('-E', '--example', dest='example', action='store_true',
-                        default=False,
-                        help='print an example runcard')
-    parser.add_argument('-R', '--runcard', dest='runcard', action='store',
-                        default=None,
-                        help='path to a runcard text file with arguments that '
-                             'override command line arguments. This run card '
-                             'can allow complex combinations of files and '
-                             'calibrations to compare the charge resolution '
-                             'against each other.')
-    parser.add_argument('--chargeres-names', dest='chargeres_names',
-                        default=['default'], nargs='*',
-                        help='chargres calculation to include in plot. '
-                             'Only used for runcards.')
-    parser.add_argument('--calib-help', dest='calib_help', action='store_true',
-                        default=False,
-                        help='display the arguments used for the camera '
-                             'calibration')
-
-    logger_detail = parser.add_mutually_exclusive_group()
-    logger_detail.add_argument('-q', '--quiet', dest='quiet',
-                               action='store_true', default=False,
-                               help='Quiet mode')
-    logger_detail.add_argument('-v', '--verbose', dest='verbose',
-                               action='store_true', default=False,
-                               help='Verbose mode')
-    logger_detail.add_argument('-d', '--debug', dest='debug',
-                               action='store_true', default=False,
-                               help='Debug mode')
-
-    args, excess_args = parser.parse_known_args()
-
-    if args.quiet:
-        log.setLevel(40)
-    if args.verbose:
-        log.setLevel(20)
-    if args.debug:
-        log.setLevel(10)
-
-    if args.calib_help:
-        params, unknown_args = calibration_parameters(excess_args,
-                                                      args.origin,
-                                                      args.calib_help)
-
-    if args.example:
-        print("""
-# Each charge resolution block starts with [chargeres] and the names for
-# this charge resolution.
-# The options in each block are equivalent to the scripts help message.
-# Options that seem to apply to plotting will only have effect in a
-# plotting block.
-
-[chargeres] test_file_local
-#-f gamma_test.simtel.gz
--f ~/Software/outputs/sim_telarray/simtel_run4_gcts_hnsb.gz
--O hessio
---integrator 4
---integration-window 7 3
---integration-sigamp 2 4
---integration-lwt 0
---integration-calib_scale 1.05
---comparison ~/Downloads/test_file_local.pdf
-
-
-# A second charge resolution block to also calculate the resolution with
-# a different integrator so the two resolutions can be plotted against
-# each other.
-
-[chargeres] test_file_nei
-#-f gamma_test.simtel.gz
--f ~/Software/outputs/sim_telarray/simtel_run4_gcts_hnsb.gz
--O hessio
---integrator 5
---integration-window 7 3
---integration-sigamp 2 4
---integration-lwt 0
---integration-calib_scale 1.05
---comparison ~/Downloads/test_file_nei.pdf
-
-# A plotting block configures an output plot
-
-[plot] normal_plot
---chargeres-names test_file_local test_file_nei
--o ~/Downloads/normal_plot.pdf
---binning normal
---normalx
---normaly
-
-[plot] log_plot
---chargeres-names test_file_local test_file_nei
--o ~/Downloads/log_plot.pdf""")
-        exit()
-
-    chargeres_cmdlines = {}
-    plot_cmdlines = {}
-
-    if args.runcard is None:
-        name = args.chargeres_names[0]
-        chargeres_cmdlines[name] = sys.argv[1:]
-        plot_cmdlines[name] = sys.argv[1:]
-        chargeres_args = {name: args}
-        plot_args = {name: args}
-    else:
-        chargeres_args = {}
-        plot_args = {}
-        current = None
-        runcard = open(args.runcard)
-        for line in runcard:
-            if line.strip() and not line.startswith('#'):
-                argument = line.split()[0]
-                if argument == '[chargeres]':
-                    name = line.split()[1:][0]
-                    chargeres_cmdlines[name] = []
-                    current = chargeres_cmdlines[name]
-                    continue
-                elif argument == '[plot]':
-                    name = line.split()[1:][0]
-                    plot_cmdlines[name] = []
-                    current = plot_cmdlines[name]
-                    continue
-                current.extend(line.split())
-
-        # Temp fill for checks
-        for name, cmdline in chargeres_cmdlines.items():
-            chargeres_args[name], unknown = parser.parse_known_args(cmdline)
-        for name, cmdline in plot_cmdlines.items():
-            plot_args[name], unknown = parser.parse_known_args(cmdline)
-
-    # Check all chargeres_names exist
-    for plot_name, args in plot_args.items():
-        for name in args.chargeres_names:
-            try:
-                if name not in chargeres_args:
-                    raise IndexError
-            except IndexError:
-                log.exception("[chargeres_names] For plot: {}, no chargeres "
-                              "has the name: {}".format(plot_name, name))
-                raise 
-
-    return parser, chargeres_cmdlines, plot_cmdlines
-
-
-def calculate_charge_resolutions(parser, chargeres_cmdlines):
-
-    run = 0
-    num_runs = len(chargeres_cmdlines)
-    chargeres_dict = {}
-
-    for name, cmdline in chargeres_cmdlines.items():
-
-        args, excess_args = parser.parse_known_args(cmdline)
-
-        run += 1
-        log.info("[run] Calculating charge resolution {}/{}"
-                 .format(run, num_runs))
-
-        # Print event/args values
-        log.info("[files] {}".format(args.input_paths))
-        telescopes = "All" if args.tel is None else args.tel
-        log.info("[origin] {}".format(args.origin))
-        log.info("[telescopes] {}".format(telescopes))
-
-        # Obtain InputFiles (to check files exist)
-        input_file_list = []
-        for filepath in args.input_paths:
-            tilde_filepath = os.path.expanduser(filepath)
-            for globfile in glob.glob(tilde_filepath):
-                input_file_list.append(InputFile(globfile, args.origin))
-
-        # Find maxpe
-        maxpe = args.maxpe
-        if maxpe is None:
-            maxpe = 0
-            for input_file in input_file_list:
-                file_maxpe = input_file.find_max_true_npe(args.tel)
-                if file_maxpe > maxpe:
-                    maxpe = file_maxpe
-        log.info("[maxpe] {}".format(maxpe))
-
-        params, unknown_args = calibration_parameters(excess_args,
-                                                      args.origin,
-                                                      args.calib_help)
-
-        chargeres = ChargeResolution(maxpe)
-
-        for input_file in input_file_list:
-            source = input_file.read()
-            calibrated_source = calibrate_source(source, params)
-            chargeres.add_source(calibrated_source, args.tel)
-
-        # Plot comparison graphs
-        if args.comparison is not None:
-            hist = chargeres.variation_hist
-            hist[hist == 0.0] = np.nan
-            xedges = chargeres.variation_xedges
-            yedges = chargeres.variation_yedges
-
-            fig = plt.figure(figsize=(10, 7))
-            ax = fig.add_subplot(111)
-            ax.set_title(name)
-            x, y = np.meshgrid(xedges, yedges)
-            x = np.power(10, x)
-            y = np.power(10, y)
-            hist_mask = np.ma.masked_where(np.isnan(hist), hist)
-            im = ax.pcolormesh(x, y, hist_mask, norm=LogNorm(),
-                               cmap=plt.cm.viridis)
-            cb = plt.colorbar(im)
-            ax.set_aspect('equal')
-            ax.grid()
-            ax.set_xscale('log')
-            ax.set_yscale('log')
-            ax.set_xlabel(r'True Charge $Q_T$ (p.e.)')
-            ax.set_ylabel(r'Measured Charge $Q_M$ (p.e.)')
-            cb.ax.set_ylabel("Count")
-
-            line = np.linspace(*ax.get_xlim(), 100)
-            ax.plot(line, line, c='0.75', ls='--')
-
-            # Add minor ticks
-            lmin = floor(log10(hist_mask.min()))
-            lmax = ceil(log10(hist_mask.max()))
-            logticks = np.tile(np.arange(lmin, 10), lmax) * (
-                np.power(10, np.arange(lmax * 10) // 10))
-            logticks = im.norm(logticks[(logticks != 0) &
-                                        (logticks >= hist_mask.min()) &
-                                        (logticks <= hist_mask.max())])
-            cb.ax.yaxis.set_ticks(logticks, minor=True)
-            cb.ax.yaxis.set_tick_params(which='minor', length=5)
-            cb.ax.tick_params(length=10)
-
-            args.comparison = os.path.expanduser(args.comparison)
-            output_dir = dirname(args.comparison)
-            if not os.path.exists(output_dir):
-                log.info("[output] Creating directory: {}".format(output_dir))
-                os.makedirs(output_dir)
-            log.info("[output] {}".format(args.comparison))
-            warnings.filterwarnings("ignore", module="matplotlib")
-            with warnings.catch_warnings():
-                warnings.simplefilter("ignore")
-                fig.savefig(args.comparison, bbox_inches='tight')
-
-        chargeres_dict[name] = chargeres
-
-    log.info('[run] Charge resolution calculations finished')
-
-    return chargeres_dict
-
-
-def plot_charge_resolutions(chargeres_dict, parser, plot_cmdlines):
-    run = 0
-    for name, cmdline in plot_cmdlines.items():
-
-        args, excess_args = parser.parse_known_args(cmdline)
-
-        run += 1
-        log.info('[run] Plotting block: {}/{}'.format(run, len(plot_cmdlines)))
-
-        handles = []
-        labels = []
-
-        fig = plt.figure(figsize=(20, 8))
-        ax_l = fig.add_subplot(121)
-        ax_r = fig.add_subplot(122)
-
-        fig.subplots_adjust(left=0.05, right=0.95, wspace=0.6)
-        fig.suptitle(name)
-
-        max_x_charge = 0
-        for chargeres_name in args.chargeres_names:
-            chargeres = chargeres_dict[chargeres_name]
-            if args.binning == 'none':
-                x_charge, res, error, scaled_res, scaled_error = \
-                    chargeres.get_charge_resolution()
-            elif args.binning == 'normal':
-                x_charge, res, error, scaled_res, scaled_error = \
-                    chargeres.get_binned_charge_resolution(False)
-            else:
-                x_charge, res, error, scaled_res, scaled_error = \
-                    chargeres.get_binned_charge_resolution(True)
-
-            eb_l = ax_l.errorbar(x_charge, res, yerr=error, marker='x',
-                                 linestyle="None")
-            ax_r.errorbar(x_charge, scaled_res, yerr=scaled_error,
-                          marker='x', linestyle="None")
-
-            handles.append(eb_l)
-            labels.append(chargeres_name)
-
-            current_max = x_charge[np.invert(np.isnan(x_charge))].max()
-            if max_x_charge < current_max:
-                max_x_charge = current_max
-
-        # Get requirement and goal curves
-        x = np.logspace(log10(0.9), log10(max_x_charge*1.1), 100)
-        requirement = ChargeResolution.requirement(x)
-        goal = ChargeResolution.goal(x)
-        poisson = ChargeResolution.poisson(x)
-
-        r_p, = ax_l.plot(x, requirement, 'r', ls='--')
-        g_p, = ax_l.plot(x, goal, 'g', ls='--')
-        p_p, = ax_l.plot(x, poisson, c='0.75', ls='--')
-        ax_r.plot(x, requirement / goal, 'r')
-        ax_r.plot(x, goal / goal, 'g')
-        ax_r.plot(x, poisson / goal, c='0.75', ls='--')
-
-        handles.append(r_p)
-        labels.append("Requirement")
-        handles.append(g_p)
-        labels.append("Goal")
-        handles.append(p_p)
-        labels.append("Poisson")
-
-        if not args.normalx:
-            ax_l.set_xscale('log')
-            ax_r.set_xscale('log')
-        if not args.normaly:
-            ax_l.set_yscale('log')
-
-        ax_l.set_xlabel(r'True Charge $Q_T$ (p.e.)')
-        ax_l.set_ylabel('Charge Resolution')
-        ax_r.set_xlabel(r'True Charge $Q_T$ (p.e.)')
-        ax_r.set_ylabel('Charge Resolution/Goal')
-
-        ax_l.legend(handles, labels, bbox_to_anchor=(1.02, 1.), loc=2,
-                    borderaxespad=0., fontsize=10)
-
-        ax_l.set_xlim(0.9, max_x_charge*1.1)
-        ax_r.set_xlim(0.9, max_x_charge*1.1)
-        if max_x_charge > 2000:
-            ax_r.set_xlim(0.9, 2000*1.1)
-        if args.maxpeplot is not None:
-            ax_l.set_xlim(0.9, args.maxpeplot)
-            ax_r.set_xlim(0.9, args.maxpeplot)
+import numpy as np
+from traitlets import Dict, List, Unicode, Int, Bool
+from ctapipe.core import Tool, Component
+from ctapipe.analysis.camera.chargeresolution import ChargeResolutionCalculator
+
+
+class ChargeResolutionPlotter(Component):
+    name = 'ChargeResolutionPlotter'
+
+    output_path = Unicode(None, allow_none=True,
+                          help='Output path to save the '
+                               'plot.').tag(config=True)
+    max_pe = Int(2000, help='Maximum pe to plot').tag(config=True)
+    linear_x = Bool(False, help='Plot the x values on a linear axis, '
+                                'instead of log').tag(config=True)
+    linear_y = Bool(False, help='Plot the y values on a linear axis, '
+                                'instead of log').tag(config=True)
+
+    def __init__(self, config, tool, **kwargs):
+        """
+        Calculator of charge resolution.
+
+        Parameters
+        ----------
+        config : traitlets.loader.Config
+            Configuration specified by config file or cmdline arguments.
+            Used to set traitlet values.
+            Set to None if no configuration to pass.
+        tool : ctapipe.core.Tool
+            Tool executable that is calling this component.
+            Passes the correct logger to the component.
+            Set to None if no Tool to pass.
+        reductor : ctapipe.calib.camera.reductors.Reductor
+            The reductor to use to reduce the waveforms in the event.
+            By default no data volume reduction is applied, and the dl0 samples
+            will equal the r1 samples.
+        kwargs
+        """
+        super().__init__(config=config, parent=tool, **kwargs)
+
+        try:
+            if self.output_path is None:
+                raise ValueError
+        except ValueError:
+            self.log.exception('Please specify an output path')
+            raise
+
+        self.fig = plt.figure(figsize=(20, 8))
+        self.ax_l = self.fig.add_subplot(121)
+        self.ax_r = self.fig.add_subplot(122)
+
+        self.fig.subplots_adjust(left=0.05, right=0.95, wspace=0.6)
+
+        self.legend_handles = []
+        self.legend_labels = []
+
+    def plot_chargeres(self, name, x, res, error):
+        valid = (x <= self.max_pe)
+        x_v = x[valid]
+        res_v = res[valid]
+        error_v = error[valid]
+
+        eb_l, _, _ = self.ax_l.errorbar(x_v, res_v, yerr=error_v,
+                                        marker='x', linestyle="None")
+        self.legend_handles.append(eb_l)
+        self.legend_labels.append(splitext(name)[0])
+
+    def plot_scaled_chargeres(self, x, res, error):
+        valid = (x <= self.max_pe)
+        x_v = x[valid]
+        res_v = res[valid]
+        error_v = error[valid]
+
+        self.ax_r.errorbar(x_v, res_v, yerr=error_v,
+                           marker='x', linestyle="None")
+
+    def plot_limit_curves(self):
+        x = np.logspace(log10(0.9), log10(self.max_pe * 1.1), 100)
+        requirement = ChargeResolutionCalculator.requirement(x)
+        goal = ChargeResolutionCalculator.goal(x)
+        poisson = ChargeResolutionCalculator.poisson(x)
+
+        r_p, = self.ax_l.plot(x, requirement, 'r', ls='--')
+        g_p, = self.ax_l.plot(x, goal, 'g', ls='--')
+        p_p, = self.ax_l.plot(x, poisson, c='0.75', ls='--')
+        self.ax_r.plot(x, requirement / goal, 'r')
+        self.ax_r.plot(x, goal / goal, 'g')
+        self.ax_r.plot(x, poisson / goal, c='0.75', ls='--')
+
+        self.legend_handles.append(r_p)
+        self.legend_labels.append("Requirement")
+        self.legend_handles.append(g_p)
+        self.legend_labels.append("Goal")
+        self.legend_handles.append(p_p)
+        self.legend_labels.append("Poisson")
+
+    def save(self):
+        if not self.linear_x:
+            self.ax_l.set_xscale('log')
+            self.ax_r.set_xscale('log')
+        if not self.linear_y:
+            self.ax_l.set_yscale('log')
+
+        self.ax_l.set_xlabel(r'True Charge $Q_T$ (p.e.)')
+        self.ax_l.set_ylabel('Charge Resolution')
+        self.ax_r.set_xlabel(r'True Charge $Q_T$ (p.e.)')
+        self.ax_r.set_ylabel('Charge Resolution/Goal')
+
+        self.ax_l.legend(self.legend_handles, self.legend_labels,
+                         bbox_to_anchor=(1.02, 1.), loc=2,
+                         borderaxespad=0., fontsize=10)
+
+        self.fig.suptitle(splitext(basename(self.output_path))[0])
+
+        self.ax_l.set_xlim(0.9, self.max_pe * 1.1)
+        self.ax_r.set_xlim(0.9, self.max_pe * 1.1)
+        if self.max_pe > 2000:
+            self.ax_r.set_xlim(0.9, 2000 * 1.1)
+        # if args.maxpeplot is not None:
+        #     ax_l.set_xlim(0.9, args.maxpeplot)
+        #     ax_r.set_xlim(0.9, args.maxpeplot)
 
         warnings.filterwarnings("ignore", module="matplotlib")
         with warnings.catch_warnings():
             warnings.simplefilter("ignore")
-            if args.output_path is None:
+            if self.output_path is None:
                 plt.show()
             else:
-                args.output_path = os.path.expanduser(args.output_path)
-                output_dir = dirname(args.output_path)
-                if not os.path.exists(output_dir):
-                    log.info(
-                        "[output] Creating directory: {}".format(output_dir))
-                    os.makedirs(output_dir)
-                log.info("[output] {}".format(args.output_path))
-                fig.savefig(args.output_path, bbox_inches='tight')
-
-
-def main():
-    script = os.path.splitext(os.path.basename(__file__))[0]
-    log.info("[SCRIPT] {}".format(script))
-
-    parser, chargeres_cmdlines, plot_cmdlines = argparsing()
-    chargeres_dict = calculate_charge_resolutions(parser, chargeres_cmdlines)
-    plot_charge_resolutions(chargeres_dict, parser, plot_cmdlines)
-
-    log.info("[COMPLETE]")
+                self.output_path = expanduser(self.output_path)
+                output_dir = dirname(self.output_path)
+                if not exists(output_dir):
+                    self.log.info("[output] Creating directory: "
+                                  "{}".format(output_dir))
+                    makedirs(output_dir)
+                self.log.info("[output] {}".format(self.output_path))
+                self.fig.savefig(self.output_path, bbox_inches='tight')
+
+
+class ChargeResolutionViewer(Tool):
+    name = "ChargeResolutionViewer"
+    description = "Plot the charge resolution from " \
+                  "ChargeResolutionCalculator objects restored via " \
+                  "pickled dictionaries."
+
+    input_files = List(Unicode, None,
+                       help='Input pickle files that are produced from '
+                            'ChargeResolutionCalculator.save().'
+                            '').tag(config=True)
+
+    aliases = Dict(dict(f='ChargeResolutionViewer.input_files',
+                        B='ChargeResolutionCalculator.binning',
+                        max_pe='ChargeResolutionPlotter.max_pe',
+                        O='ChargeResolutionPlotter.output_path',
+                        ))
+    flags = Dict(dict(L=({'ChargeResolutionCalculator': {'log_bins': False}},
+                         'Bin the x axis linearly instead of logarithmic.'),
+                      linx=({'ChargeResolutionPlotter': {'linear_x': True}},
+                            'Plot the x values on a linear axis, '
+                            'instead of log.'),
+                      liny=({'ChargeResolutionPlotter': {'linear_y': True}},
+                            'Plot the x values on a linear axis, '
+                            'instead of log.')
+                      ))
+    classes = List([ChargeResolutionCalculator,
+                    ChargeResolutionPlotter
+                    ])
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.calculator = None
+        self.plotter = None
+
+    def setup(self):
+        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"
+        kwargs = dict(config=self.config, tool=self)
+
+        self.calculator = ChargeResolutionCalculator(**kwargs)
+        self.plotter = ChargeResolutionPlotter(**kwargs)
+
+    def start(self):
+        self.plotter.plot_limit_curves()
+        for fp in self.input_files:
+            self.calculator.load(fp)
+            x, res, res_error, scaled_res, scaled_res_error = \
+                self.calculator.get_charge_resolution()
+
+            name = basename(fp)
+            self.plotter.plot_chargeres(name, x, res, res_error)
+            self.plotter.plot_scaled_chargeres(x, scaled_res, scaled_res_error)
+
+    def finish(self):
+        self.plotter.save()
 
 
 if __name__ == '__main__':
-    main()
+    exe = ChargeResolutionViewer()
+    exe.run()
diff --git a/examples/plot_charge_resolution_variation_hist.py b/examples/plot_charge_resolution_variation_hist.py
new file mode 100644
index 00000000..8e724f07
--- /dev/null
+++ b/examples/plot_charge_resolution_variation_hist.py
@@ -0,0 +1,149 @@
+from os.path import dirname, exists, splitext, basename
+from os import makedirs
+from math import ceil, floor
+from matplotlib import pyplot as plt
+from math import log10
+import warnings
+import numpy as np
+from matplotlib.colors import LogNorm
+from traitlets import Dict, List, Unicode
+from ctapipe.core import Tool, Component
+from ctapipe.analysis.camera.chargeresolution import ChargeResolutionCalculator
+
+
+class ChargeResolutionVariationPlotter(Component):
+    name = 'ChargeResolutionVariationPlotter'
+
+    output_path = Unicode(None, allow_none=True,
+                          help='Output path to save the '
+                               'plot.').tag(config=True)
+
+    def __init__(self, config, tool, **kwargs):
+        """
+        Calculator of charge resolution.
+
+        Parameters
+        ----------
+        config : traitlets.loader.Config
+            Configuration specified by config file or cmdline arguments.
+            Used to set traitlet values.
+            Set to None if no configuration to pass.
+        tool : ctapipe.core.Tool
+            Tool executable that is calling this component.
+            Passes the correct logger to the component.
+            Set to None if no Tool to pass.
+        reductor : ctapipe.calib.camera.reductors.Reductor
+            The reductor to use to reduce the waveforms in the event.
+            By default no data volume reduction is applied, and the dl0 samples
+            will equal the r1 samples.
+        kwargs
+        """
+        super().__init__(config=config, parent=tool, **kwargs)
+
+        try:
+            if self.output_path is None:
+                raise ValueError
+        except ValueError:
+            self.log.exception('Please specify an output path')
+            raise
+
+        self.fig = plt.figure(figsize=(20, 8))
+        self.ax_l = self.fig.add_subplot(121)
+        self.ax_r = self.fig.add_subplot(122)
+
+        self.fig.subplots_adjust(left=0.05, right=0.95, wspace=0.6)
+
+        self.legend_handles = []
+        self.legend_labels = []
+
+    def plot_hist(self, hist, xedges, yedges):
+
+        hist[hist == 0.0] = np.nan
+
+        fig = plt.figure(figsize=(10, 7))
+        ax = fig.add_subplot(111)
+        ax.set_title(splitext(basename(self.output_path))[0])
+        x, y = np.meshgrid(xedges, yedges)
+        x = np.power(10, x)
+        y = np.power(10, y)
+        hist_mask = np.ma.masked_where(np.isnan(hist), hist)
+        im = ax.pcolormesh(x, y, hist_mask, norm=LogNorm(),
+                           cmap=plt.cm.viridis)
+        cb = plt.colorbar(im)
+        ax.set_aspect('equal')
+        ax.grid()
+        ax.set_xscale('log')
+        ax.set_yscale('log')
+        ax.set_xlabel(r'True Charge $Q_T$ (p.e.)')
+        ax.set_ylabel(r'Measured Charge $Q_M$ (p.e.)')
+        cb.ax.set_ylabel("Count")
+
+        line = np.linspace(*ax.get_xlim(), 100)
+        ax.plot(line, line, c='0.75', ls='--')
+
+        # Add minor ticks
+        lmin = floor(log10(hist_mask.min()))
+        lmax = ceil(log10(hist_mask.max()))
+        logticks = np.tile(np.arange(lmin, 10), lmax) * (
+            np.power(10, np.arange(lmax * 10) // 10))
+        logticks = im.norm(logticks[(logticks != 0) &
+                                    (logticks >= hist_mask.min()) &
+                                    (logticks <= hist_mask.max())])
+        cb.ax.yaxis.set_ticks(logticks, minor=True)
+        cb.ax.yaxis.set_tick_params(which='minor', length=5)
+        cb.ax.tick_params(length=10)
+
+        output_dir = dirname(self.output_path)
+        if not exists(output_dir):
+            self.log.info("[output] Creating directory: {}".format(output_dir))
+            makedirs(output_dir)
+        self.log.info("[output] {}".format(self.output_path))
+        warnings.filterwarnings("ignore", module="matplotlib")
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            fig.savefig(self.output_path, bbox_inches='tight')
+
+
+class ChargeResolutionVariationViewer(Tool):
+    name = "ChargeResolutionVariationViewer"
+    description = "Plot the charge resolution from " \
+                  "ChargeResolutionCalculator objects restored via " \
+                  "pickled dictionaries."
+
+    input_path = Unicode(None, allow_none=True,
+                         help='Path to the pickle file produced from'
+                              'ChargeResolutionCalculator.save()'
+                              '').tag(config=True)
+
+    aliases = Dict(dict(f='ChargeResolutionVariationViewer.input_path',
+                        O='ChargeResolutionVariationPlotter.output_path',
+                        ))
+    classes = List([ChargeResolutionCalculator,
+                    ChargeResolutionVariationPlotter
+                    ])
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.calculator = None
+        self.plotter = None
+
+    def setup(self):
+        self.log_format = "%(levelname)s: %(message)s [%(name)s.%(funcName)s]"
+        kwargs = dict(config=self.config, tool=self)
+
+        self.calculator = ChargeResolutionCalculator(**kwargs)
+        self.plotter = ChargeResolutionVariationPlotter(**kwargs)
+
+    def start(self):
+        self.calculator.load(self.input_path)
+
+    def finish(self):
+        hist = self.calculator.variation_hist
+        xedges = self.calculator.variation_xedges
+        yedges = self.calculator.variation_yedges
+        self.plotter.plot_hist(hist, xedges, yedges)
+
+
+if __name__ == '__main__':
+    exe = ChargeResolutionVariationViewer()
+    exe.run()


Test patch:
diff --git a/ctapipe/analysis/camera/tests/test_chargeresolution.py b/ctapipe/analysis/camera/tests/test_chargeresolution.py
index 46e054e7..926bfc11 100644
--- a/ctapipe/analysis/camera/tests/test_chargeresolution.py
+++ b/ctapipe/analysis/camera/tests/test_chargeresolution.py
@@ -1,9 +1,9 @@
-from ..chargeresolution import ChargeResolution
+from ctapipe.analysis.camera.chargeresolution import ChargeResolutionCalculator
 import numpy as np
 
 
 def test_add_charges():
-    chargeres = ChargeResolution(100)
+    chargeres = ChargeResolutionCalculator(None, None)
     true_charge = np.arange(100)
     measured_charge = np.arange(100)
     chargeres.add_charges(true_charge, measured_charge)
@@ -17,7 +17,7 @@ def test_add_charges():
 
 
 def test_get_charge_resolution():
-    chargeres = ChargeResolution(100)
+    chargeres = ChargeResolutionCalculator(None, None, binning=None)
     true_charge = np.arange(100)
     measured_charge = np.arange(100)
     chargeres.add_charges(true_charge, measured_charge)
@@ -33,13 +33,13 @@ def test_get_charge_resolution():
 
 
 def test_get_binned_charge_resolution():
-    chargeres = ChargeResolution(100)
+    chargeres = ChargeResolutionCalculator(None, None)
     true_charge = np.arange(100)
     measured_charge = np.arange(100)
     chargeres.add_charges(true_charge, measured_charge)
     true_charge, chargeres, chargeres_error, \
         scaled_chargeres, scaled_chargeres_error = \
-        chargeres.get_binned_charge_resolution()
+        chargeres.get_charge_resolution()
 
     assert(true_charge[0] == 1)
     assert(chargeres[0] == 1)
@@ -49,19 +49,19 @@ def test_get_binned_charge_resolution():
 
 
 def test_limit_curves():
-    value = ChargeResolution.limit_curves(1, 2, 3, 4, 5)
+    value = ChargeResolutionCalculator.limit_curves(1, 2, 3, 4, 5)
     assert(round(value, 7) == 6.78233)
 
 
 def test_requirement():
-    value = ChargeResolution.requirement(1)
-    value2 = ChargeResolution.requirement(np.arange(1, 10))[-1]
+    value = ChargeResolutionCalculator.requirement(1)
+    value2 = ChargeResolutionCalculator.requirement(np.arange(1, 10))[-1]
     assert(round(value, 7) == 2.0237963)
     assert(round(value2, 7) == 0.4501817)
 
 
 def test_goal():
-    value = ChargeResolution.goal(1)
-    value2 = ChargeResolution.goal(np.arange(1, 10))[-1]
+    value = ChargeResolutionCalculator.goal(1)
+    value2 = ChargeResolutionCalculator.goal(np.arange(1, 10))[-1]
     assert(round(value, 7) == 1.8017134)
     assert(round(value2, 7) == 0.4066657)