listmode.utils — listmode 0.5.0 documentation

# Source code for listmode.utils

    import json
    import time
    import datetime as dt

    from pathlib import Path

    import numpy as np
    from listmode import misc
    import listmode.exceptions as ex

    """
    Utils is a repository for generally useful functions and classes for thing such as reading and writing.

    """

    def delete_channel_data(data_path, base_name, cfg):
        """
        Used to delete channel files after parsing.
        :param data_path:
        :param base_name:
        :param cfg:
        :return:
        """
        timenames, enames, tnames, xnames = find_data_files(data_path, base_name, cfg, mode='channel')
        for fname in timenames + enames + tnames:
            if fname.exists():
                fname.unlink()
        for chnamelist in xnames:
            for fname in chnamelist:
                if fname is not None:
                    if fname.exists():
                        fname.unlink()

    def find_data_files(data_path, base_name, cfg, mode):
        """
        Generate filenames (paths) of all the data produced by config. You can select either 'event' or 'channel'
        type names.

        :param data_path:
        :param base_name:
        :param cfg:
        :param mode:
        :return: timing, energy, time and extras filenames as lists. For event data the first (and only) item in the list
                 is the relevant filename. For channel data there is a single name for every channel. Extra names are given
                 as a list of names, one per extra (for each channel as in other data).
        """

        extras = cfg.det['extras']

        if mode == 'event':
            timenames = [data_path / (base_name + '_timestamps.dat')]
            enames = [data_path / (base_name + '_events.dat')]
            tnames = [data_path / (base_name + '_timing.dat')]
            xnames = []
            try:
                for extra in extras:
                    xnames.append([data_path / (base_name + '_{}.dat'.format(extra['name']))])
            except TypeError:
                pass

        elif mode == 'channel':
            timenames = [data_path / (base_name + '_timestamps_ch{}.dat'.format(x)) for x in cfg.det['ch_list']]
            enames = [data_path / (base_name + '_events_ch{}.dat'.format(x)) for x in cfg.det['ch_list']]
            tnames = [data_path / (base_name + '_timing_ch{}.dat'.format(x)) for x in cfg.det['ch_list']]
            xnames = []
            if extras is not None:
                for extra in extras:
                    xnames.append([])  # all extras get a position in list
                    if extra['name'] not in ['latency', 'multihit']:
                        # this extra is present in channel data and will be combined either as
                        # a bit in a bitmask or a column in a matrix.
                        try:  # channel mask default is ones
                            ch_mask = np.array(extra['ch_mask'], dtype='bool')
                        except:
                            ch_mask = np.ones((len(cfg.det['ch_cfg']),), dtype='bool')

                        # Extra names are retrieved by channel index. In cases there is no extra defined
                        # for a channel we need to put something in to the list. Let it be None then.
                        for ch_idx in range(len(cfg.det['ch_cfg'])):
                            xnames[-1].append(None)
                            if ch_mask[ch_idx]:
                                efil = data_path / (base_name + '_{}_ch{}.dat'.format(extra['name'], ch_idx))
                                #assert(efil.exists())
                                xnames[-1][-1] = efil

        return timenames, enames, tnames, xnames

    def write_channel_metadata(data_path, base_name, channel, metadata):
        datetime_type = ['start', 'stop']
        # make sure data_path is a pathlib Path
        data_path = Path(data_path)
        try:
            temp_for_file = {key: value for key, value in metadata[channel].items() if key not in datetime_type}
        except:
            print(metadata[channel].items())
            raise ex.ListModeException('write metadata')

        if 'notes' in metadata[channel]:
            temp_for_file['notes'] = metadata[channel]['notes']
            pass
        else:
            temp_for_file['notes'] = 'Metadata defined by raw files.'

        if metadata[channel]['start'] is None:
            temp_for_file['notes'] += 'Missing start time substituted by compile time.'
            metadata[channel]['start'] = dt.datetime.fromtimestamp(time.time())

        if metadata[channel]['stop'] is None:
            temp_for_file['notes'] += 'Missing stop time calculated from timestamps.'
            metadata[channel]['stop'] = metadata[channel]['start'] + \
                                        dt.timedelta(seconds=int(metadata[channel]['total_time']*1e-9))

        for key in datetime_type:
            temp_for_file[key] = metadata[channel][key].isoformat()

        with (data_path / (base_name + '_metadata_ch{:02d}.json'.format(channel))).open('w') as fil:
            json.dump(temp_for_file, fil, indent=0, default=int)

    def read_channel_metadata(data_path, base_name, channel):
        with (data_path / (base_name + '_metadata_ch{:02d}.json'.format(channel))).open('r') as fil:
            temp_from_file = json.load(fil)

        datetime_type = ['start', 'stop']
        metadata = {key: value for key, value in temp_from_file.items() if key not in datetime_type}
        for key in datetime_type:
            #metadata[key] = dt.datetime.fromisoformat(temp_from_file[key]) # not present in python 3.6
            metadata[key] = misc.fromisoformat(temp_from_file[key])
        return metadata

    def find_path(config, name, suffix):
        home = Path(config.path['home'])
        filename = Path(name + suffix)  #name of the file
        if (home / filename).exists():
            loadpath = home / filename
            print('Found local file', loadpath)
        else:
            loadpath = Path(config.path['cfg_dir']) / suffix.split('.')[0][1:] / filename
        return loadpath

    def load_plot_config(config, plot_name):
        """
        Loads a plot configuration from a json file.

        :param config: Config data that has the config.path in it
        :param plot_name_list: plot config name without the _plotcfg.json postfix or a list of names
        :return: a plot config dictionary
        """

        try:
            with find_path(config, plot_name, '_plotcfg.json').open('r') as fil:
                plot_config = json.load(fil)
        except FileNotFoundError:
            raise ex.ListModeConfigurationError('Could not find plot configuration.')

        return plot_config

    def load_style_sheet(config, style_name_list):
        """
        Loads a matplotlib style sheet written as a json file.

        :param config: Detector configuration object
        :param style_name_list: style path or a list of paths
        :return: list of style dictionaries
        """
        style_cfg_list = []
        if isinstance(style_name_list, str):  # single stylesheet if string is given
            style_name_list = [style_name_list]
        while len(style_name_list) > 0:
            style_name = style_name_list.pop(0)
            try:
                with find_path(config, style_name, '_stylecfg.json').open('r') as fil:
                    style_config = json.load(fil)
                style_cfg_list.append(style_config)
            except FileNotFoundError:
                raise ex.ListModeConfigurationError('Could not find stylesheet.')

        return style_cfg_list

    def load_effcal(config, eff=None):
        if eff is None:
            eff = config.det['effcal']

        with find_path(config, eff, '_effcal.json').open('r') as fil:
            temp = json.load(fil)
            effcal = temp
        return effcal

    def load_strip_cal(config):
        """
        Load strip calibration files if they exist.
        """

        strips = []
        for ch in config.det['ch_cfg']:
            try:
                if ch['cal_array'] is not None:
                    strips.append(ch['cal_array'])
            except KeyError:
                pass

        if strips:
            strip_cal = []
            try:
                for cal in strips:
                    with find_path(config, cal, '_stripcal.json').open('r') as fil:
                        strip_cal.append(json.load(fil)['calibration'])
            except FileNotFoundError:
                print('Strip calibration file not found!')
                raise ex.ListModeConfigurationError('Strip calibration file not found!')
            out = np.stack(strip_cal, axis=0)
            return out
        return None