Module src.PyOghma_ML.Input

This module provides a framework for handling and processing input data from various laboratories.

It includes a base class, Input, which defines a structure for parsing and standardizing data. Subclasses are implemented for specific laboratories, allowing for tailored data parsing and characterization. The module supports various characterization types, such as JV, TPV, CE, and CELIV, and provides methods for calculating device parameters and standardizing inputs for further analysis.

Classes

class Brabec (device_dir: str, characterisation_type: str, **kwargs: Any)
Expand source code
class Brabec(Input):
    """
    Subclass for handling data from the Brabec laboratory.

    This class provides methods for parsing and processing data specific
    to the Brabec laboratory.
    """
    _source_laboratory = 'Brabec'

    def __init__(self, device_dir: str, characterisation_type: str, **kwargs: Any) -> None:
        """
        Initialize a Brabec instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., 'JV').
            **kwargs: Additional keyword arguments for subclass initialization.
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.kwargs = kwargs
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        Raises:
            ValueError: If the characterisation type is not supported.
        """
        match self.characterisation_type:
            case 'JV':
                self.JV()
            case 'TPV':
                self.TPV()
            case 'CE':
                self.CE()
            case 'CELIV':
                self.CELIV()
            case _:
                raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def JV(self) -> None:
        """
        Parse JV data.

        Returns:
            None
        """
        self.Device_Population = 1
        self.metadata = {}
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=1)
        hour = self.kwargs['hour']
        data_sorted = data.iloc[(data[0] - hour).abs().argsort()].iloc[0][1:].to_numpy()
        self.current_density = data_sorted * 10
        self.voltage = pd.read_csv(self.device_dir, delimiter='\t').columns[1:].astype(float).to_numpy()
        self.calcluate_JV_Params(self.voltage, self.current_density)

        return

    def TPV(self) -> None:
        """
        Parse TPV data.

        Returns:
            None
        """
        self.Device_Population = 1
        self.metadata = {}
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=1).dropna(axis=1).to_numpy()[0]
        OD = data
        if 'OD' in self.kwargs:
            OD_idx = np.argsort(np.abs(OD - self.kwargs['OD']))[0]
            OD = OD[OD_idx]
        idx = (OD_idx) * 2
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=1)
        self.time = data[idx].to_numpy()
        self.voltage = data[idx + 1].to_numpy()
        return

    def CE(self) -> None:
        """
        Parse CE data.

        Returns:
            None
        """
        self.Device_Population = 1
        self.metadata = {}
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=2).T.set_index(0).to_dict(orient='dict')[1]
        self.metadata = data
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=1, skiprows=2).dropna(axis=1).to_numpy()[0]
        self.time = data
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=3)
        if 'hour' in self.kwargs:
            hour = self.kwargs['hour']
        else:
            raise Warning('Degradation Hour must be specified!')
        data_sorted = data.iloc[(data[0] - hour).abs().argsort()].iloc[0][1:].to_numpy()
        self.current = data_sorted
        return

    def CELIV(self) -> None:
        """
        Parse CELIV data.

        Returns:
            None
        """
        self.Device_Population = 1
        self.metadata = {}
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=9).dropna(axis=1).set_index(0).to_dict(orient='dict')[1]
        self.metadata = data
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=10, nrows=1)
        data = (data.drop(columns=[0, data.shape[1] - 1]).to_numpy() * 1e-6)[0]
        self.delay_time = data
        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=11, nrows=1)
        data = data.drop(columns=[0, data.shape[1] - 1]).to_numpy()[0]
        self.voc = data

        match self.kwargs:
            case k if 'voc' in k:
                idx = np.argsort(np.abs(self.voc - k['voc']))[0] + 1
            case k if 'delay' in k:
                idx = np.argsort(np.abs(self.delay_time - k['delay']))[0] + 1
            case _:
                idx = 1

        data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=12)
        self.time = data[0].to_numpy()
        self.current_desnity = data[idx].to_numpy()
        return

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        match self.characterisation_type:
            case 'JV':
                self.x = self.voltage
                self.y = self.current_density
            case 'CELIV':
                self.x = self.time
                self.y = self.current_desnity
            case 'CE':
                self.x = self.time
                self.y = self.current
            case 'TPV':
                self.x = self.time
                self.y = self.voltage

Subclass for handling data from the Brabec laboratory.

This class provides methods for parsing and processing data specific to the Brabec laboratory.

Initialize a Brabec instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., 'JV').
**kwargs
Additional keyword arguments for subclass initialization.

Ancestors

Methods

def CE(self) ‑> None
Expand source code
def CE(self) -> None:
    """
    Parse CE data.

    Returns:
        None
    """
    self.Device_Population = 1
    self.metadata = {}
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=2).T.set_index(0).to_dict(orient='dict')[1]
    self.metadata = data
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=1, skiprows=2).dropna(axis=1).to_numpy()[0]
    self.time = data
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=3)
    if 'hour' in self.kwargs:
        hour = self.kwargs['hour']
    else:
        raise Warning('Degradation Hour must be specified!')
    data_sorted = data.iloc[(data[0] - hour).abs().argsort()].iloc[0][1:].to_numpy()
    self.current = data_sorted
    return

Parse CE data.

Returns

None

def CELIV(self) ‑> None
Expand source code
def CELIV(self) -> None:
    """
    Parse CELIV data.

    Returns:
        None
    """
    self.Device_Population = 1
    self.metadata = {}
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=9).dropna(axis=1).set_index(0).to_dict(orient='dict')[1]
    self.metadata = data
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=10, nrows=1)
    data = (data.drop(columns=[0, data.shape[1] - 1]).to_numpy() * 1e-6)[0]
    self.delay_time = data
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=11, nrows=1)
    data = data.drop(columns=[0, data.shape[1] - 1]).to_numpy()[0]
    self.voc = data

    match self.kwargs:
        case k if 'voc' in k:
            idx = np.argsort(np.abs(self.voc - k['voc']))[0] + 1
        case k if 'delay' in k:
            idx = np.argsort(np.abs(self.delay_time - k['delay']))[0] + 1
        case _:
            idx = 1

    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=12)
    self.time = data[0].to_numpy()
    self.current_desnity = data[idx].to_numpy()
    return

Parse CELIV data.

Returns

None

def JV(self) ‑> None
Expand source code
def JV(self) -> None:
    """
    Parse JV data.

    Returns:
        None
    """
    self.Device_Population = 1
    self.metadata = {}
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=1)
    hour = self.kwargs['hour']
    data_sorted = data.iloc[(data[0] - hour).abs().argsort()].iloc[0][1:].to_numpy()
    self.current_density = data_sorted * 10
    self.voltage = pd.read_csv(self.device_dir, delimiter='\t').columns[1:].astype(float).to_numpy()
    self.calcluate_JV_Params(self.voltage, self.current_density)

    return

Parse JV data.

Returns

None

def TPV(self) ‑> None
Expand source code
def TPV(self) -> None:
    """
    Parse TPV data.

    Returns:
        None
    """
    self.Device_Population = 1
    self.metadata = {}
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, nrows=1).dropna(axis=1).to_numpy()[0]
    OD = data
    if 'OD' in self.kwargs:
        OD_idx = np.argsort(np.abs(OD - self.kwargs['OD']))[0]
        OD = OD[OD_idx]
    idx = (OD_idx) * 2
    data = pd.read_csv(self.device_dir, delimiter='\t', header=None, skiprows=1)
    self.time = data[idx].to_numpy()
    self.voltage = data[idx + 1].to_numpy()
    return

Parse TPV data.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    Raises:
        ValueError: If the characterisation type is not supported.
    """
    match self.characterisation_type:
        case 'JV':
            self.JV()
        case 'TPV':
            self.TPV()
        case 'CE':
            self.CE()
        case 'CELIV':
            self.CELIV()
        case _:
            raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

Raises

ValueError
If the characterisation type is not supported.
def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    match self.characterisation_type:
        case 'JV':
            self.x = self.voltage
            self.y = self.current_density
        case 'CELIV':
            self.x = self.time
            self.y = self.current_desnity
        case 'CE':
            self.x = self.time
            self.y = self.current
        case 'TPV':
            self.x = self.time
            self.y = self.voltage

Standardize inputs for further processing.

Returns

None

Inherited members

class Deibel (device_dir: str, characterisation_type: str)
Expand source code
class Deibel(Input):
    """
    Subclass for handling data from the Deibel laboratory.

    This class provides methods for parsing and processing data specific
    to the Deibel laboratory.
    """
    _source_laboratory = 'Deibel'

    def __init__(self, device_dir: str, characterisation_type: str) -> None:
        """
        Initialize a Deibel instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., 'JV').
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        This method acts as a dispatcher, calling the appropriate parsing
        method based on the characterisation type specified during initialization.
        Supported types include 'IV', 'JV', 'JV_I4', and 'batch_JV'.

        Raises:
            ValueError: If the characterisation type is not supported by this laboratory.
        """
        match self.characterisation_type:
            case 'IV':
                self.current_voltage_area()
            case 'JV':
                self.current_voltage()
            case 'JV_I4':
                self.current_voltage_I4()
            case 'batch_JV':
                self.batch_current_voltage()
            case _:
                raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def batch_current_voltage(self) -> None:
        """
        Parse batch current-voltage data for multiple devices.

        This method processes JV data for multiple devices, storing voltage,
        current density, and metadata for each device in the batch. It validates
        that multiple devices are present before processing.

        Raises:
            ValueError: If one or fewer devices are detected in the batch.

        Sets:
            self.Device_Population (int): Number of devices in the batch
            self.voltage (numpy.ndarray): Voltage data for all devices
            self.current_density (numpy.ndarray): Current density data for all devices
            self.metadata (numpy.ndarray): Metadata for all devices
        """
        Device_Population = len(self.device_dir)
        self.Device_Population = Device_Population
        if Device_Population <= 1:
            raise ValueError('One or less devices detected!')
        else:
            voltage = np.zeros(Device_Population, dtype=object)
            current_density = np.zeros(Device_Population, dtype=object)
            metadata = np.zeros(Device_Population, dtype=object)

            for index in range(Device_Population):
                self.current_voltage(index)
                self.Device_Population = Device_Population
                voltage[index] = self.voltage
                current_density[index] = self.current_density
                metadata[index] = self.metadata

        self.voltage = voltage
        self.current_density = current_density
        self.metadata = metadata

    def current_voltage_area(self, index: Optional[int] = None) -> None:
        """
        Parse current-voltage data with area correction.

        Args:
            index (int, optional): Index of the device directory.

        Returns:
            None
        """
        self.Device_Population = 1
        metadata_list = []

        if index is not None:
            d = self.device_dir[index]
        else:
            d = self.device_dir

        with open(d, 'r') as f:
            for line in f:

                if line[0:9] == '##columns':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace(';', '')
                    line = line.replace(' ', ',')
                    metadata_list.append([line])
                else:
                    if line[0] == '#':
                        line = line[2:]
                        line = line.replace('\n', '')
                        line = line.replace('\t', '')
                        line = line.replace(' ', '')
                        metadata_list.append(line.split(';')[:-1])
        metadata_list = np.concatenate(metadata_list)

        group_metadata = {}
        for parameter in metadata_list:

            parameter = parameter.split('=')

            match parameter[0]:

                case 'SMU|ILLUMINATOR':
                    temporary_keys = parameter[0].split('|')
                    temporary_values = parameter[1].split('|')

                    for i in range(len(temporary_keys)):
                        group_metadata[temporary_keys[i]] = temporary_values[i]

                case 'columns':
                    parameter[-1] = parameter[-1].split(',')
                    group_metadata['columns'] = parameter[1:]
                    group_metadata['columns'] = group_metadata['columns'][0]

                case _:
                    temporary_keys = parameter[1].split(',')

                    if "/" in temporary_keys[0]:
                        temporary_keys = temporary_keys[0].split('/')

                    if len(temporary_keys) == 1:
                        group_metadata[parameter[0]] = temporary_keys[0]
                    else:
                        group_metadata[parameter[0]] = temporary_keys
        data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

        voltage = data['V'].to_numpy()
        current_density = data['I'].to_numpy()
        current_density = current_density * 1000 / 0.04 * 10

        metadata = {}

        self.voltage = voltage
        self.current_density = current_density
        self.metadata = metadata

    def current_voltage(self, index: Optional[int] = None) -> None:
        """
        Parse current-voltage data.

        Args:
            index (int, optional): Index of the device directory.

        Returns:
            None
        """
        self.Device_Population = 1
        metadata_list = []

        if index is not None:
            d = self.device_dir[index]
        else:
            d = self.device_dir

        with open(d, 'r') as f:
            for line in f:

                if line[0:9] == '##columns':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace(';', '')
                    line = line.replace(' ', ',')
                    metadata_list.append([line])
                else:
                    if line[0] == '#':
                        line = line[2:]
                        line = line.replace('\n', '')
                        line = line.replace('\t', '')
                        line = line.replace(' ', '')
                        metadata_list.append(line.split(';')[:-1])
        metadata_list = np.concatenate(metadata_list)

        group_metadata = {}
        for parameter in metadata_list:

            parameter = parameter.split('=')

            match parameter[0]:

                case 'SMU|ILLUMINATOR':
                    temporary_keys = parameter[0].split('|')
                    temporary_values = parameter[1].split('|')

                    for i in range(len(temporary_keys)):
                        group_metadata[temporary_keys[i]] = temporary_values[i]

                case 'columns':
                    parameter[-1] = parameter[-1].split(',')
                    group_metadata['columns'] = parameter[1:]
                    group_metadata['columns'] = group_metadata['columns'][0]

                case _:
                    temporary_keys = parameter[1].split(',')

                    if "/" in temporary_keys[0]:
                        temporary_keys = temporary_keys[0].split('/')

                    if len(temporary_keys) == 1:
                        group_metadata[parameter[0]] = temporary_keys[0]
                    else:
                        group_metadata[parameter[0]] = temporary_keys
        data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

        voltage = data['V'].to_numpy()
        current_density = data['J'].to_numpy()

        metadata = {}
        light_source = group_metadata['ILLUMINATOR']
        if group_metadata['incidentIntensity[%]'] == 'dark':
            group_metadata['incidentIntensity[%]'] = 0
        elif group_metadata['incidentIntensity[%]'] == light_source:
            group_metadata['incidentIntensity[%]'] = 1

        match group_metadata['incidentIntensity[%]']:
            case 0:
                metadata['intensity'] = 0
            case 1:
                metadata['intensity'] = 1
            case _:
                metadata['intensity'] = group_metadata['incidentIntensity[%]'] / 100

        metadata = metadata

        self.voltage = voltage
        self.current_density = current_density
        self.metadata = metadata

    def current_voltage_I4(self, index: Optional[int] = None) -> None:
        """
        Parse current-voltage data with I4 scaling.

        Args:
            index (int, optional): Index of the device directory.

        Returns:
            None
        """
        self.Device_Population = 1
        metadata_list = []

        if index is not None:
            d = self.device_dir[index]
        else:
            d = self.device_dir
        d = d.replace(' ', '')
        with open(d, 'r') as f:
            for line in f:

                if line[0:9] == '##columns':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace(';', '')
                    line = line.replace(' ', ',')
                    metadata_list.append([line])
                else:
                    if line[0] == '#':
                        line = line[2:]
                        line = line.replace('\n', '')
                        line = line.replace('\t', '')
                        line = line.replace(' ', '')
                        metadata_list.append(line.split(';')[:-1])
        metadata_list = np.concatenate(metadata_list)

        group_metadata = {}
        for parameter in metadata_list:

            parameter = parameter.split('=')

            match parameter[0]:

                case 'SMU|ILLUMINATOR':
                    temporary_keys = parameter[0].split('|')
                    temporary_values = parameter[1].split('|')

                    for i in range(len(temporary_keys)):
                        group_metadata[temporary_keys[i]] = temporary_values[i]

                case 'columns':
                    parameter[-1] = parameter[-1].split(',')
                    group_metadata['columns'] = parameter[1:]
                    group_metadata['columns'] = group_metadata['columns'][0]

                case _:
                    temporary_keys = parameter[1].split(',')

                    if "/" in temporary_keys[0]:
                        temporary_keys = temporary_keys[0].split('/')

                    if len(temporary_keys) == 1:
                        group_metadata[parameter[0]] = temporary_keys[0]
                    else:
                        group_metadata[parameter[0]] = temporary_keys
        data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

        voltage = data['V'].to_numpy()
        current = data['I'].to_numpy() * 1000 / 0.04 * 10 # Convert to current density in mA/cm^2
        # voltage, current_density, light_intensity = self.I4_scaler(voltage, current)

        metadata = {}
        # metadata['intensity'] = light_intensity

        self.voltage = voltage
        self.current_density = current
        self.metadata = metadata

    def I4_scaler(self, voltage: np.ndarray, current: np.ndarray) -> tuple[np.ndarray, np.ndarray, float]:
        """
        Scale current-voltage data using I4 methodology.

        Args:
            voltage (numpy.ndarray): Array of voltage values.
            current (numpy.ndarray): Array of current values.

        Returns:
            tuple: Scaled voltage, current density, and light intensity.
        """
        dir = os.path.dirname(self.device_dir)
        name = os.path.basename(self.device_dir)
        #name = name.split('_')
        #name = name[0:-1]

        # illumination = name[-1].replace('uIllu', '')
        # illumination = float(illumination) / 1e6

        # name[-1] = 'sunsVoc.dat'
        # name_sunsVoc = '_'.join(name)

        # name[-1] = 'am15.dat'
        # name = [name[0], name[-1]]

        # name_JV = '_'.join(name)
        data_JV = pd.read_csv(os.path.join(dir, name), comment='#', sep='\t', names=['V', 'I', 'J'])
        JV_voltage = data_JV['V'].to_numpy()
        JV_current = data_JV['I'].to_numpy()
        self.calcluate_JV_Params(-JV_voltage, JV_current)
        self.Jsc = -self.Jsc

        data_sunsVoc = pd.read_csv(os.path.join(dir, name), comment='#', sep='\t',
                                   names=['filters', 'power', 'relIllu', 'Voc', 'Isc', 'Iphoto'])
        data_sunsVoc = data_sunsVoc.drop(columns=['power'])
        data_sunsVoc['Isc'] = data_sunsVoc['Isc'] * -1
        data_sunsVoc['Isc'] = np.log(data_sunsVoc['Isc'])
        function = interpolate.PchipInterpolator(data_sunsVoc['Voc'], data_sunsVoc['Isc'], extrapolate=True)
        y = np.linspace(0, 0.8, 1000)
        x = function(self.Voc)
        sfactor = (1e3 / (self.Jsc)) * (np.exp(x))
        current_density = current * (1e3 / sfactor)

        data_sunsVoc['Iphoto'] = np.log(data_sunsVoc['Iphoto'])
        function = interpolate.PchipInterpolator(data_sunsVoc['Voc'], data_sunsVoc['Iphoto'], extrapolate=True)
        light_intensity = np.exp(data_sunsVoc['Iphoto']) / np.exp(function(self.Voc))

        function = interpolate.PchipInterpolator(data_sunsVoc['relIllu'], light_intensity, extrapolate=True)
        light_intensity = function(illumination)

        return voltage, current_density, light_intensity

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        self.x = np.array(self.voltage)
        self.y = np.array(self.current_density)
        self.metadata = self.metadata

Subclass for handling data from the Deibel laboratory.

This class provides methods for parsing and processing data specific to the Deibel laboratory.

Initialize a Deibel instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., 'JV').

Ancestors

Methods

def I4_scaler(self, voltage: numpy.ndarray, current: numpy.ndarray) ‑> tuple[numpy.ndarray, numpy.ndarray, float]
Expand source code
def I4_scaler(self, voltage: np.ndarray, current: np.ndarray) -> tuple[np.ndarray, np.ndarray, float]:
    """
    Scale current-voltage data using I4 methodology.

    Args:
        voltage (numpy.ndarray): Array of voltage values.
        current (numpy.ndarray): Array of current values.

    Returns:
        tuple: Scaled voltage, current density, and light intensity.
    """
    dir = os.path.dirname(self.device_dir)
    name = os.path.basename(self.device_dir)
    #name = name.split('_')
    #name = name[0:-1]

    # illumination = name[-1].replace('uIllu', '')
    # illumination = float(illumination) / 1e6

    # name[-1] = 'sunsVoc.dat'
    # name_sunsVoc = '_'.join(name)

    # name[-1] = 'am15.dat'
    # name = [name[0], name[-1]]

    # name_JV = '_'.join(name)
    data_JV = pd.read_csv(os.path.join(dir, name), comment='#', sep='\t', names=['V', 'I', 'J'])
    JV_voltage = data_JV['V'].to_numpy()
    JV_current = data_JV['I'].to_numpy()
    self.calcluate_JV_Params(-JV_voltage, JV_current)
    self.Jsc = -self.Jsc

    data_sunsVoc = pd.read_csv(os.path.join(dir, name), comment='#', sep='\t',
                               names=['filters', 'power', 'relIllu', 'Voc', 'Isc', 'Iphoto'])
    data_sunsVoc = data_sunsVoc.drop(columns=['power'])
    data_sunsVoc['Isc'] = data_sunsVoc['Isc'] * -1
    data_sunsVoc['Isc'] = np.log(data_sunsVoc['Isc'])
    function = interpolate.PchipInterpolator(data_sunsVoc['Voc'], data_sunsVoc['Isc'], extrapolate=True)
    y = np.linspace(0, 0.8, 1000)
    x = function(self.Voc)
    sfactor = (1e3 / (self.Jsc)) * (np.exp(x))
    current_density = current * (1e3 / sfactor)

    data_sunsVoc['Iphoto'] = np.log(data_sunsVoc['Iphoto'])
    function = interpolate.PchipInterpolator(data_sunsVoc['Voc'], data_sunsVoc['Iphoto'], extrapolate=True)
    light_intensity = np.exp(data_sunsVoc['Iphoto']) / np.exp(function(self.Voc))

    function = interpolate.PchipInterpolator(data_sunsVoc['relIllu'], light_intensity, extrapolate=True)
    light_intensity = function(illumination)

    return voltage, current_density, light_intensity

Scale current-voltage data using I4 methodology.

Args

voltage : numpy.ndarray
Array of voltage values.
current : numpy.ndarray
Array of current values.

Returns

tuple
Scaled voltage, current density, and light intensity.
def batch_current_voltage(self) ‑> None
Expand source code
def batch_current_voltage(self) -> None:
    """
    Parse batch current-voltage data for multiple devices.

    This method processes JV data for multiple devices, storing voltage,
    current density, and metadata for each device in the batch. It validates
    that multiple devices are present before processing.

    Raises:
        ValueError: If one or fewer devices are detected in the batch.

    Sets:
        self.Device_Population (int): Number of devices in the batch
        self.voltage (numpy.ndarray): Voltage data for all devices
        self.current_density (numpy.ndarray): Current density data for all devices
        self.metadata (numpy.ndarray): Metadata for all devices
    """
    Device_Population = len(self.device_dir)
    self.Device_Population = Device_Population
    if Device_Population <= 1:
        raise ValueError('One or less devices detected!')
    else:
        voltage = np.zeros(Device_Population, dtype=object)
        current_density = np.zeros(Device_Population, dtype=object)
        metadata = np.zeros(Device_Population, dtype=object)

        for index in range(Device_Population):
            self.current_voltage(index)
            self.Device_Population = Device_Population
            voltage[index] = self.voltage
            current_density[index] = self.current_density
            metadata[index] = self.metadata

    self.voltage = voltage
    self.current_density = current_density
    self.metadata = metadata

Parse batch current-voltage data for multiple devices.

This method processes JV data for multiple devices, storing voltage, current density, and metadata for each device in the batch. It validates that multiple devices are present before processing.

Raises

ValueError
If one or fewer devices are detected in the batch.

Sets

self.Device_Population (int): Number of devices in the batch self.voltage (numpy.ndarray): Voltage data for all devices self.current_density (numpy.ndarray): Current density data for all devices self.metadata (numpy.ndarray): Metadata for all devices

def current_voltage(self, index: int | None = None) ‑> None
Expand source code
def current_voltage(self, index: Optional[int] = None) -> None:
    """
    Parse current-voltage data.

    Args:
        index (int, optional): Index of the device directory.

    Returns:
        None
    """
    self.Device_Population = 1
    metadata_list = []

    if index is not None:
        d = self.device_dir[index]
    else:
        d = self.device_dir

    with open(d, 'r') as f:
        for line in f:

            if line[0:9] == '##columns':
                line = line[2:]
                line = line.replace('\n', '')
                line = line.replace(';', '')
                line = line.replace(' ', ',')
                metadata_list.append([line])
            else:
                if line[0] == '#':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace('\t', '')
                    line = line.replace(' ', '')
                    metadata_list.append(line.split(';')[:-1])
    metadata_list = np.concatenate(metadata_list)

    group_metadata = {}
    for parameter in metadata_list:

        parameter = parameter.split('=')

        match parameter[0]:

            case 'SMU|ILLUMINATOR':
                temporary_keys = parameter[0].split('|')
                temporary_values = parameter[1].split('|')

                for i in range(len(temporary_keys)):
                    group_metadata[temporary_keys[i]] = temporary_values[i]

            case 'columns':
                parameter[-1] = parameter[-1].split(',')
                group_metadata['columns'] = parameter[1:]
                group_metadata['columns'] = group_metadata['columns'][0]

            case _:
                temporary_keys = parameter[1].split(',')

                if "/" in temporary_keys[0]:
                    temporary_keys = temporary_keys[0].split('/')

                if len(temporary_keys) == 1:
                    group_metadata[parameter[0]] = temporary_keys[0]
                else:
                    group_metadata[parameter[0]] = temporary_keys
    data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

    voltage = data['V'].to_numpy()
    current_density = data['J'].to_numpy()

    metadata = {}
    light_source = group_metadata['ILLUMINATOR']
    if group_metadata['incidentIntensity[%]'] == 'dark':
        group_metadata['incidentIntensity[%]'] = 0
    elif group_metadata['incidentIntensity[%]'] == light_source:
        group_metadata['incidentIntensity[%]'] = 1

    match group_metadata['incidentIntensity[%]']:
        case 0:
            metadata['intensity'] = 0
        case 1:
            metadata['intensity'] = 1
        case _:
            metadata['intensity'] = group_metadata['incidentIntensity[%]'] / 100

    metadata = metadata

    self.voltage = voltage
    self.current_density = current_density
    self.metadata = metadata

Parse current-voltage data.

Args

index : int, optional
Index of the device directory.

Returns

None

def current_voltage_I4(self, index: int | None = None) ‑> None
Expand source code
def current_voltage_I4(self, index: Optional[int] = None) -> None:
    """
    Parse current-voltage data with I4 scaling.

    Args:
        index (int, optional): Index of the device directory.

    Returns:
        None
    """
    self.Device_Population = 1
    metadata_list = []

    if index is not None:
        d = self.device_dir[index]
    else:
        d = self.device_dir
    d = d.replace(' ', '')
    with open(d, 'r') as f:
        for line in f:

            if line[0:9] == '##columns':
                line = line[2:]
                line = line.replace('\n', '')
                line = line.replace(';', '')
                line = line.replace(' ', ',')
                metadata_list.append([line])
            else:
                if line[0] == '#':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace('\t', '')
                    line = line.replace(' ', '')
                    metadata_list.append(line.split(';')[:-1])
    metadata_list = np.concatenate(metadata_list)

    group_metadata = {}
    for parameter in metadata_list:

        parameter = parameter.split('=')

        match parameter[0]:

            case 'SMU|ILLUMINATOR':
                temporary_keys = parameter[0].split('|')
                temporary_values = parameter[1].split('|')

                for i in range(len(temporary_keys)):
                    group_metadata[temporary_keys[i]] = temporary_values[i]

            case 'columns':
                parameter[-1] = parameter[-1].split(',')
                group_metadata['columns'] = parameter[1:]
                group_metadata['columns'] = group_metadata['columns'][0]

            case _:
                temporary_keys = parameter[1].split(',')

                if "/" in temporary_keys[0]:
                    temporary_keys = temporary_keys[0].split('/')

                if len(temporary_keys) == 1:
                    group_metadata[parameter[0]] = temporary_keys[0]
                else:
                    group_metadata[parameter[0]] = temporary_keys
    data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

    voltage = data['V'].to_numpy()
    current = data['I'].to_numpy() * 1000 / 0.04 * 10 # Convert to current density in mA/cm^2
    # voltage, current_density, light_intensity = self.I4_scaler(voltage, current)

    metadata = {}
    # metadata['intensity'] = light_intensity

    self.voltage = voltage
    self.current_density = current
    self.metadata = metadata

Parse current-voltage data with I4 scaling.

Args

index : int, optional
Index of the device directory.

Returns

None

def current_voltage_area(self, index: int | None = None) ‑> None
Expand source code
def current_voltage_area(self, index: Optional[int] = None) -> None:
    """
    Parse current-voltage data with area correction.

    Args:
        index (int, optional): Index of the device directory.

    Returns:
        None
    """
    self.Device_Population = 1
    metadata_list = []

    if index is not None:
        d = self.device_dir[index]
    else:
        d = self.device_dir

    with open(d, 'r') as f:
        for line in f:

            if line[0:9] == '##columns':
                line = line[2:]
                line = line.replace('\n', '')
                line = line.replace(';', '')
                line = line.replace(' ', ',')
                metadata_list.append([line])
            else:
                if line[0] == '#':
                    line = line[2:]
                    line = line.replace('\n', '')
                    line = line.replace('\t', '')
                    line = line.replace(' ', '')
                    metadata_list.append(line.split(';')[:-1])
    metadata_list = np.concatenate(metadata_list)

    group_metadata = {}
    for parameter in metadata_list:

        parameter = parameter.split('=')

        match parameter[0]:

            case 'SMU|ILLUMINATOR':
                temporary_keys = parameter[0].split('|')
                temporary_values = parameter[1].split('|')

                for i in range(len(temporary_keys)):
                    group_metadata[temporary_keys[i]] = temporary_values[i]

            case 'columns':
                parameter[-1] = parameter[-1].split(',')
                group_metadata['columns'] = parameter[1:]
                group_metadata['columns'] = group_metadata['columns'][0]

            case _:
                temporary_keys = parameter[1].split(',')

                if "/" in temporary_keys[0]:
                    temporary_keys = temporary_keys[0].split('/')

                if len(temporary_keys) == 1:
                    group_metadata[parameter[0]] = temporary_keys[0]
                else:
                    group_metadata[parameter[0]] = temporary_keys
    data = pd.read_csv(d, sep='\t', header=None, comment='#', names=group_metadata['columns'])

    voltage = data['V'].to_numpy()
    current_density = data['I'].to_numpy()
    current_density = current_density * 1000 / 0.04 * 10

    metadata = {}

    self.voltage = voltage
    self.current_density = current_density
    self.metadata = metadata

Parse current-voltage data with area correction.

Args

index : int, optional
Index of the device directory.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    This method acts as a dispatcher, calling the appropriate parsing
    method based on the characterisation type specified during initialization.
    Supported types include 'IV', 'JV', 'JV_I4', and 'batch_JV'.

    Raises:
        ValueError: If the characterisation type is not supported by this laboratory.
    """
    match self.characterisation_type:
        case 'IV':
            self.current_voltage_area()
        case 'JV':
            self.current_voltage()
        case 'JV_I4':
            self.current_voltage_I4()
        case 'batch_JV':
            self.batch_current_voltage()
        case _:
            raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

This method acts as a dispatcher, calling the appropriate parsing method based on the characterisation type specified during initialization. Supported types include 'IV', 'JV', 'JV_I4', and 'batch_JV'.

Raises

ValueError
If the characterisation type is not supported by this laboratory.
def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    self.x = np.array(self.voltage)
    self.y = np.array(self.current_density)
    self.metadata = self.metadata

Standardize inputs for further processing.

Returns

None

Inherited members

class Herzig (device_dir: str, characterisation_type: str)
Expand source code
class Herzig(Input):
    """
    Subclass for handling data from the Herzig laboratory.

    This class provides methods for parsing and processing data specific
    to the Herzig laboratory.
    """
    _source_laboratory = 'Herzig'

    def __init__(self, device_dir: str, characterisation_type: str) -> None:
        """
        Initialize a Herzig instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., '2D_JV').
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        Raises:
            ValueError: If the characterisation type is not supported.
        """
        match self.characterisation_type:
            case '2D_JV':
                self.mapped_current_voltage()
            case _:
                raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def current_voltage(self, index: Optional[int] = None) -> None:
        """
        Parse current-voltage data.

        Args:
            index (int, optional): Index of the data.

        Raises:
            ValueError: If the function is called for single JVs.

        Returns:
            None
        """
        if index is not None:
            d = self.data[index]
        else:
            raise ValueError('This function does not currently handle single JVs')

        metadata = {}
        metadata['intensity'] = self.intensity[index]

        self.voltage = -self.data[index, :, 0]
        self.current = -self.data[index, :, 1]
        self.metadata = metadata

    def mapped_current_voltage(self) -> None:
        """
        Parse mapped current-voltage data.

        Returns:
            None
        """
        data = h5py.File(self.device_dir, 'r')
        data = data['data']

        shape = data[:, :, 0, :, :].shape

        dark = data[:, :, 0, :, :].reshape((shape[0] * shape[1], shape[2], shape[3]))
        dark_intensity = np.zeros(len(dark))

        light = data[:, :, 2, :, :].reshape((shape[0] * shape[1], shape[2], shape[3]))
        light_intensity = np.ones(len(light))

        data = np.concatenate([dark, light])

        intensity = np.concatenate((dark_intensity, light_intensity))

        self.data = data
        self.intensity = intensity

        voltage = np.zeros((shape[0] * shape[1] * 2), dtype=object)
        current = np.zeros((shape[0] * shape[1] * 2), dtype=object)
        metadata = np.zeros((shape[0] * shape[1] * 2), dtype=object)

        for index in range(int(shape[0] * shape[1] * 2)):
            self.current_voltage(index)
            voltage[index] = self.voltage
            current[index] = self.current
            metadata[index] = self.metadata

        self.voltage = voltage
        self.current = current
        self.metadata = metadata

    def current_to_current_density(self, area: float = 8e-7) -> None:
        """
        Convert current to current density.

        Args:
            area (float): Area size for conversion.

        Returns:
            None
        """
        self.current_density = self.current / area
        self.current_density_flat = True

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        self.x = self.voltage

        if self.current_density_flat:
            self.y = self.current_density
        else:
            self.y = self.current

        self.metadata = self.metadata

Subclass for handling data from the Herzig laboratory.

This class provides methods for parsing and processing data specific to the Herzig laboratory.

Initialize a Herzig instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., '2D_JV').

Ancestors

Methods

def current_to_current_density(self, area: float = 8e-07) ‑> None
Expand source code
def current_to_current_density(self, area: float = 8e-7) -> None:
    """
    Convert current to current density.

    Args:
        area (float): Area size for conversion.

    Returns:
        None
    """
    self.current_density = self.current / area
    self.current_density_flat = True

Convert current to current density.

Args

area : float
Area size for conversion.

Returns

None

def current_voltage(self, index: int | None = None) ‑> None
Expand source code
def current_voltage(self, index: Optional[int] = None) -> None:
    """
    Parse current-voltage data.

    Args:
        index (int, optional): Index of the data.

    Raises:
        ValueError: If the function is called for single JVs.

    Returns:
        None
    """
    if index is not None:
        d = self.data[index]
    else:
        raise ValueError('This function does not currently handle single JVs')

    metadata = {}
    metadata['intensity'] = self.intensity[index]

    self.voltage = -self.data[index, :, 0]
    self.current = -self.data[index, :, 1]
    self.metadata = metadata

Parse current-voltage data.

Args

index : int, optional
Index of the data.

Raises

ValueError
If the function is called for single JVs.

Returns

None

def mapped_current_voltage(self) ‑> None
Expand source code
def mapped_current_voltage(self) -> None:
    """
    Parse mapped current-voltage data.

    Returns:
        None
    """
    data = h5py.File(self.device_dir, 'r')
    data = data['data']

    shape = data[:, :, 0, :, :].shape

    dark = data[:, :, 0, :, :].reshape((shape[0] * shape[1], shape[2], shape[3]))
    dark_intensity = np.zeros(len(dark))

    light = data[:, :, 2, :, :].reshape((shape[0] * shape[1], shape[2], shape[3]))
    light_intensity = np.ones(len(light))

    data = np.concatenate([dark, light])

    intensity = np.concatenate((dark_intensity, light_intensity))

    self.data = data
    self.intensity = intensity

    voltage = np.zeros((shape[0] * shape[1] * 2), dtype=object)
    current = np.zeros((shape[0] * shape[1] * 2), dtype=object)
    metadata = np.zeros((shape[0] * shape[1] * 2), dtype=object)

    for index in range(int(shape[0] * shape[1] * 2)):
        self.current_voltage(index)
        voltage[index] = self.voltage
        current[index] = self.current
        metadata[index] = self.metadata

    self.voltage = voltage
    self.current = current
    self.metadata = metadata

Parse mapped current-voltage data.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    Raises:
        ValueError: If the characterisation type is not supported.
    """
    match self.characterisation_type:
        case '2D_JV':
            self.mapped_current_voltage()
        case _:
            raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

Raises

ValueError
If the characterisation type is not supported.
def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    self.x = self.voltage

    if self.current_density_flat:
        self.y = self.current_density
    else:
        self.y = self.current

    self.metadata = self.metadata

Standardize inputs for further processing.

Returns

None

Inherited members

class Input
Expand source code
class Input:
    """
    Base class for handling input data from various laboratories.

    This class provides a framework for parsing and processing data from
    different laboratories. It includes methods for registering subclasses
    and calculating JV parameters.
    """
    subclasses = {}

    def __init_subclass__(cls, **kwargs: Any) -> None:
        """
        Automatically register subclasses for different laboratories.

        Args:
            **kwargs: Additional keyword arguments for subclass initialization.
        """
        super().__init_subclass__(**kwargs)
        cls.subclasses[cls.__name__] = cls

    @classmethod
    def experiment(cls, device_dir: str, characterisation_type: Optional[str] = None, source_laboratory: Optional[str] = None, **kwargs: Any) -> "Input":
        """
        Factory method to create an instance of a specific laboratory subclass.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str, optional): Type of characterisation (e.g., 'JV').
            source_laboratory (str, optional): Name of the source laboratory.
            **kwargs: Additional keyword arguments for subclass initialization.

        Returns:
            Input: An instance of the appropriate subclass.

        Raises:
            ValueError: If the source laboratory is not recognized.
        """
        if source_laboratory not in cls.subclasses:
            raise ValueError('Source laboratory: {} Not recognized'.format(source_laboratory))
        return cls.subclasses[source_laboratory](device_dir, characterisation_type, **kwargs)

    def calcluate_JV_Params(self, voltage: np.ndarray, current: np.ndarray) -> None:
        """
        Calculate JV parameters such as Jsc, Voc, P_max, FF, and PCE.

        This method calculates key photovoltaic parameters from JV curves:
        - Jsc (Short-circuit current density): Current at V=0
        - Voc (Open-circuit voltage): Voltage at J=0  
        - P_max (Maximum power): Peak power point
        - FF (Fill factor): Ratio of maximum power to theoretical maximum
        - PCE (Power conversion efficiency): Overall efficiency percentage

        Args:
            voltage (numpy.ndarray): Array of voltage values in volts.
            current (numpy.ndarray): Array of current density values in mA/cm².

        Returns:
            None: Parameters are stored as instance attributes.

        Note:
            The calculated parameters are accessible as instance attributes:
            self.Jsc, self.Voc, self.P_max, self.FF, self.PCE
        """
        V = voltage
        J = current

        self.Jsc = -J[np.argmin(np.abs(V - 0))]
        self.Voc = V[np.argmin(np.abs(J - 0))]

        self.P_max = np.abs(np.min(V * J))

        self.FF = self.P_max / (self.Voc * self.Jsc)

        self.PCE = self.Jsc * self.Voc * self.FF / 10
        return

Base class for handling input data from various laboratories.

This class provides a framework for parsing and processing data from different laboratories. It includes methods for registering subclasses and calculating JV parameters.

Subclasses

Class variables

var subclasses

Static methods

def experiment(device_dir: str,
characterisation_type: str | None = None,
source_laboratory: str | None = None,
**kwargs: Any) ‑> Input

Factory method to create an instance of a specific laboratory subclass.

Args

device_dir : str
Directory containing device data.
characterisation_type : str, optional
Type of characterisation (e.g., 'JV').
source_laboratory : str, optional
Name of the source laboratory.
**kwargs
Additional keyword arguments for subclass initialization.

Returns

Input
An instance of the appropriate subclass.

Raises

ValueError
If the source laboratory is not recognized.

Methods

def calcluate_JV_Params(self, voltage: numpy.ndarray, current: numpy.ndarray) ‑> None
Expand source code
def calcluate_JV_Params(self, voltage: np.ndarray, current: np.ndarray) -> None:
    """
    Calculate JV parameters such as Jsc, Voc, P_max, FF, and PCE.

    This method calculates key photovoltaic parameters from JV curves:
    - Jsc (Short-circuit current density): Current at V=0
    - Voc (Open-circuit voltage): Voltage at J=0  
    - P_max (Maximum power): Peak power point
    - FF (Fill factor): Ratio of maximum power to theoretical maximum
    - PCE (Power conversion efficiency): Overall efficiency percentage

    Args:
        voltage (numpy.ndarray): Array of voltage values in volts.
        current (numpy.ndarray): Array of current density values in mA/cm².

    Returns:
        None: Parameters are stored as instance attributes.

    Note:
        The calculated parameters are accessible as instance attributes:
        self.Jsc, self.Voc, self.P_max, self.FF, self.PCE
    """
    V = voltage
    J = current

    self.Jsc = -J[np.argmin(np.abs(V - 0))]
    self.Voc = V[np.argmin(np.abs(J - 0))]

    self.P_max = np.abs(np.min(V * J))

    self.FF = self.P_max / (self.Voc * self.Jsc)

    self.PCE = self.Jsc * self.Voc * self.FF / 10
    return

Calculate JV parameters such as Jsc, Voc, P_max, FF, and PCE.

This method calculates key photovoltaic parameters from JV curves: - Jsc (Short-circuit current density): Current at V=0 - Voc (Open-circuit voltage): Voltage at J=0
- P_max (Maximum power): Peak power point - FF (Fill factor): Ratio of maximum power to theoretical maximum - PCE (Power conversion efficiency): Overall efficiency percentage

Args

voltage : numpy.ndarray
Array of voltage values in volts.
current : numpy.ndarray
Array of current density values in mA/cm².

Returns

None
Parameters are stored as instance attributes.

Note

The calculated parameters are accessible as instance attributes: self.Jsc, self.Voc, self.P_max, self.FF, self.PCE

class Oghma (device_dir: str, characterisation_type: str)
Expand source code
class Oghma(Input):
    """
    Subclass for handling data from the Oghma laboratory.

    This class provides methods for parsing and processing data specific
    to the Oghma laboratory.
    """
    _source_laboratory = 'Oghma'

    def __init__(self, device_dir: str, characterisation_type: str) -> None:
        """
        Initialize an Oghma instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., 'JV').
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        Raises:
            ValueError: If the characterisation type is not supported.
        """
        match self.characterisation_type:
            case 'JV':
                self.current_voltage()
            case 'batch_JV':
                self.batch_current_voltage()
            case _:
                raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def current_voltage(self, index: Optional[int] = None) -> None:
        """
        Parse current-voltage data.

        Args:
            index (int, optional): Index of the device directory.

        Returns:
            None
        """
        self.Device_Population = 1
        metadata_list = []

        if index is not None:
            d = self.device_dir[index]
        else:
            d = self.device_dir

        data = pd.read_csv(d, sep=' ', header=None, comment='#', skiprows=2, names=['V', 'J'])

        voltage = data['V'].to_numpy()
        current_density = data['J'].to_numpy()

        metadata = {}

        self.voltage = voltage
        self.current_density = current_density
        self.metadata = metadata

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        self.x = np.array(self.voltage)
        self.y = np.array(self.current_density)
        self.metadata = self.metadata

Subclass for handling data from the Oghma laboratory.

This class provides methods for parsing and processing data specific to the Oghma laboratory.

Initialize an Oghma instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., 'JV').

Ancestors

Methods

def current_voltage(self, index: int | None = None) ‑> None
Expand source code
def current_voltage(self, index: Optional[int] = None) -> None:
    """
    Parse current-voltage data.

    Args:
        index (int, optional): Index of the device directory.

    Returns:
        None
    """
    self.Device_Population = 1
    metadata_list = []

    if index is not None:
        d = self.device_dir[index]
    else:
        d = self.device_dir

    data = pd.read_csv(d, sep=' ', header=None, comment='#', skiprows=2, names=['V', 'J'])

    voltage = data['V'].to_numpy()
    current_density = data['J'].to_numpy()

    metadata = {}

    self.voltage = voltage
    self.current_density = current_density
    self.metadata = metadata

Parse current-voltage data.

Args

index : int, optional
Index of the device directory.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    Raises:
        ValueError: If the characterisation type is not supported.
    """
    match self.characterisation_type:
        case 'JV':
            self.current_voltage()
        case 'batch_JV':
            self.batch_current_voltage()
        case _:
            raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

Raises

ValueError
If the characterisation type is not supported.
def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    self.x = np.array(self.voltage)
    self.y = np.array(self.current_density)
    self.metadata = self.metadata

Standardize inputs for further processing.

Returns

None

Inherited members

class Riedl (device_dir: str, characterisation_type: str, **kwargs: Any)
Expand source code
class Riedl(Input):
    """
    Subclass for handling data from the Riedl laboratory.

    This class provides methods for parsing and processing data specific
    to the Riedl laboratory.
    """
    _source_laboratory = 'Riedl'

    def __init__(self, device_dir: str, characterisation_type: str, **kwargs: Any) -> None:
        """
        Initialize a Riedl instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., 'JV').
            **kwargs: Additional keyword arguments for subclass initialization.
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.kwargs = kwargs
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        Raises:
            ValueError: If the characterisation type is not supported.
        """
        match self.characterisation_type:
            case 'JV':
                self.JV()
            case _:
                raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def JV(self) -> None:
        """
        Parse JV data.

        Returns:
            None
        """
        self.Device_Population = 1
        data = pd.read_csv(self.device_dir, delimiter=';', comment='#', skiprows=5)
        pixel = self.kwargs['pixel']
        name_V = 'Cell ' + str(pixel)
        name_J = 'Unnamed: ' + str((pixel * 2) + 1)
        self.voltage = data[name_V].to_numpy(dtype=float)
        self.current_density = data[name_J].to_numpy(dtype=float) * 10
        self.calcluate_JV_Params(self.voltage, self.current_density)
        self.metadata = {}

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        self.x = self.voltage
        self.y = self.current_density
        self.metadata = self.metadata

Subclass for handling data from the Riedl laboratory.

This class provides methods for parsing and processing data specific to the Riedl laboratory.

Initialize a Riedl instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., 'JV').
**kwargs
Additional keyword arguments for subclass initialization.

Ancestors

Methods

def JV(self) ‑> None
Expand source code
def JV(self) -> None:
    """
    Parse JV data.

    Returns:
        None
    """
    self.Device_Population = 1
    data = pd.read_csv(self.device_dir, delimiter=';', comment='#', skiprows=5)
    pixel = self.kwargs['pixel']
    name_V = 'Cell ' + str(pixel)
    name_J = 'Unnamed: ' + str((pixel * 2) + 1)
    self.voltage = data[name_V].to_numpy(dtype=float)
    self.current_density = data[name_J].to_numpy(dtype=float) * 10
    self.calcluate_JV_Params(self.voltage, self.current_density)
    self.metadata = {}

Parse JV data.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    Raises:
        ValueError: If the characterisation type is not supported.
    """
    match self.characterisation_type:
        case 'JV':
            self.JV()
        case _:
            raise ValueError('The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

Raises

ValueError
If the characterisation type is not supported.
def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    self.x = self.voltage
    self.y = self.current_density
    self.metadata = self.metadata

Standardize inputs for further processing.

Returns

None

Inherited members

class Shoaee (device_dir: str, characterisation_type: str, **kwargs: Any)
Expand source code
class Shoaee(Input):
    """
    Subclass for handling data from the Shoaee laboratory.

    This class provides methods for parsing and processing data specific
    to the Shoaee laboratory.
    """
    _source_laboratory = 'Shoaee'

    def __init__(self, device_dir: str, characterisation_type: str, **kwargs: Any) -> None:
        """
        Initialize a Shoaee instance.

        Args:
            device_dir (str): Directory containing device data.
            characterisation_type (str): Type of characterisation (e.g., 'SolarSim JV').
            **kwargs: Additional keyword arguments for subclass initialization.
        """
        self.device_dir = device_dir
        self.characterisation_type = characterisation_type
        self.kwargs = kwargs
        self.parse()

    def parse(self) -> None:
        """
        Parse the data based on the characterisation type.

        Raises:
            ValueError: If the characterisation type is not supported.
        """
        match self.characterisation_type:
            case 'SolarSim JV':
                self.solar_sim_jv()
            case 'SPO JV':
                self.spo_jv()
            case 'Denoised':
                self.denosied()
            case _:
                raise ValueError(
                    'The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

    def solar_sim_jv(self) -> None:
        """
        Parse solar simulator JV data.

        Returns:
            None
        """
        self.Device_Population = 1
        d = self.device_dir
        pixel = self.kwargs['pixel'].lower()
        tag = self.kwargs['tag'].lower()

        metadata = {}
        maxline = 0
        with open(d, 'r', encoding="ISO-8859-1") as f:
            for idx, line in enumerate(f):
                if idx > maxline:
                    maxline = idx
                match idx:
                    case 4:
                        metadata['num_average'] = int(line.split(' ')[3].strip('\n'))
                    case 6:
                        metadata['area_size'] = float(line.split(pixel + ':')[1].split(',')[0])
                    case 7:
                        metadata['temperature'] = int(line.split(' ')[1].strip('°C\n'))
                    case 8:
                        if str(pixel + '_' + tag) in line:
                            metadata['pixel'] = pixel + '_' + tag
        calculated_params = pd.read_csv(d, sep='\t', skiprows=8, skipfooter=maxline - 16, encoding="ISO-8859-1",
                                        engine='python')
        calculated_params = calculated_params.set_index('Pixel')
        calculated_params = calculated_params[metadata['pixel']].to_dict()
        metadata.update(calculated_params)
        characteristic_values = pd.read_csv(d, sep='\t', skiprows=17, encoding="ISO-8859-1").drop(0, axis=0)
        self.voltage = characteristic_values['Voltage'].to_numpy(dtype=float)
        self.current_density = characteristic_values['j' + '_' + metadata['pixel']].to_numpy(dtype=float) * 10
        l = len(self.voltage)
        h = int(l / 2)
        self.voltage = self.voltage[:]
        self.current_density = self.current_density[:]
        self.metadata = metadata
        return

    def spo_jv(self) -> None:
        """
        Parse SPO JV data.

        Returns:
            None
        """
        self.Device_Population = 1
        d = self.device_dir
        data = pd.read_csv(d, sep='\t', skiprows=51, encoding="ISO-8859-1", skipfooter=5, engine='python').drop(0, axis=0)
        self.voltage = data['Voltage setup'].to_numpy(dtype=float)
        self.current_density = data['Current Density'].to_numpy(dtype=float) * 10
        data = pd.read_csv(d, sep='\t', skiprows=47, encoding="ISO-8859-1", skipfooter=170, engine='python', header=None).T.drop(0, axis=0)
        data.columns = ['Start Time', 'Start Date']

        data = pd.to_datetime(data['Start Date'] + ' ' + data['Start Time'], dayfirst=True).values[0]
        self.metadata = {}
        self.metadata['datetime'] = data
        return

    def denosied(self) -> None:
        """
        Parse denoised data.

        Returns:
            None
        """
        self.Device_Population = 1
        d = self.device_dir
        data = pd.read_csv(d)
        self.current_density = data['Mean'].to_numpy(dtype=float)
        self.voltage = np.linspace(0, 1, len(self.current_density))
        self.metadata = {}
        return

    def standardise_inputs(self) -> None:
        """
        Standardize inputs for further processing.

        Returns:
            None
        """
        self.x = self.voltage
        self.y = self.current_density
        self.metadata = self.metadata

Subclass for handling data from the Shoaee laboratory.

This class provides methods for parsing and processing data specific to the Shoaee laboratory.

Initialize a Shoaee instance.

Args

device_dir : str
Directory containing device data.
characterisation_type : str
Type of characterisation (e.g., 'SolarSim JV').
**kwargs
Additional keyword arguments for subclass initialization.

Ancestors

Methods

def denosied(self) ‑> None
Expand source code
def denosied(self) -> None:
    """
    Parse denoised data.

    Returns:
        None
    """
    self.Device_Population = 1
    d = self.device_dir
    data = pd.read_csv(d)
    self.current_density = data['Mean'].to_numpy(dtype=float)
    self.voltage = np.linspace(0, 1, len(self.current_density))
    self.metadata = {}
    return

Parse denoised data.

Returns

None

def parse(self) ‑> None
Expand source code
def parse(self) -> None:
    """
    Parse the data based on the characterisation type.

    Raises:
        ValueError: If the characterisation type is not supported.
    """
    match self.characterisation_type:
        case 'SolarSim JV':
            self.solar_sim_jv()
        case 'SPO JV':
            self.spo_jv()
        case 'Denoised':
            self.denosied()
        case _:
            raise ValueError(
                'The {} Laboratory does not support {}'.format(self._source_laboratory, self.characterisation_type))

Parse the data based on the characterisation type.

Raises

ValueError
If the characterisation type is not supported.
def solar_sim_jv(self) ‑> None
Expand source code
def solar_sim_jv(self) -> None:
    """
    Parse solar simulator JV data.

    Returns:
        None
    """
    self.Device_Population = 1
    d = self.device_dir
    pixel = self.kwargs['pixel'].lower()
    tag = self.kwargs['tag'].lower()

    metadata = {}
    maxline = 0
    with open(d, 'r', encoding="ISO-8859-1") as f:
        for idx, line in enumerate(f):
            if idx > maxline:
                maxline = idx
            match idx:
                case 4:
                    metadata['num_average'] = int(line.split(' ')[3].strip('\n'))
                case 6:
                    metadata['area_size'] = float(line.split(pixel + ':')[1].split(',')[0])
                case 7:
                    metadata['temperature'] = int(line.split(' ')[1].strip('°C\n'))
                case 8:
                    if str(pixel + '_' + tag) in line:
                        metadata['pixel'] = pixel + '_' + tag
    calculated_params = pd.read_csv(d, sep='\t', skiprows=8, skipfooter=maxline - 16, encoding="ISO-8859-1",
                                    engine='python')
    calculated_params = calculated_params.set_index('Pixel')
    calculated_params = calculated_params[metadata['pixel']].to_dict()
    metadata.update(calculated_params)
    characteristic_values = pd.read_csv(d, sep='\t', skiprows=17, encoding="ISO-8859-1").drop(0, axis=0)
    self.voltage = characteristic_values['Voltage'].to_numpy(dtype=float)
    self.current_density = characteristic_values['j' + '_' + metadata['pixel']].to_numpy(dtype=float) * 10
    l = len(self.voltage)
    h = int(l / 2)
    self.voltage = self.voltage[:]
    self.current_density = self.current_density[:]
    self.metadata = metadata
    return

Parse solar simulator JV data.

Returns

None

def spo_jv(self) ‑> None
Expand source code
def spo_jv(self) -> None:
    """
    Parse SPO JV data.

    Returns:
        None
    """
    self.Device_Population = 1
    d = self.device_dir
    data = pd.read_csv(d, sep='\t', skiprows=51, encoding="ISO-8859-1", skipfooter=5, engine='python').drop(0, axis=0)
    self.voltage = data['Voltage setup'].to_numpy(dtype=float)
    self.current_density = data['Current Density'].to_numpy(dtype=float) * 10
    data = pd.read_csv(d, sep='\t', skiprows=47, encoding="ISO-8859-1", skipfooter=170, engine='python', header=None).T.drop(0, axis=0)
    data.columns = ['Start Time', 'Start Date']

    data = pd.to_datetime(data['Start Date'] + ' ' + data['Start Time'], dayfirst=True).values[0]
    self.metadata = {}
    self.metadata['datetime'] = data
    return

Parse SPO JV data.

Returns

None

def standardise_inputs(self) ‑> None
Expand source code
def standardise_inputs(self) -> None:
    """
    Standardize inputs for further processing.

    Returns:
        None
    """
    self.x = self.voltage
    self.y = self.current_density
    self.metadata = self.metadata

Standardize inputs for further processing.

Returns

None

Inherited members