Skip to content

Code Documentation

Classes

Mask

Class that allows to do a mineralogical classification of a sample provided the elemental data and a spreadsheet containing the elemental information needed for each mineral. The classification indicates the minerals, the percentage of each, the percentage of pixels that are classified more than once. It also indicates the percentage of the number of pixel that are not indexed. It also enables to extract a binaray image per mineral in order to use it as a mask onto the datacube (.rpl file) to facilitate quantitative analysis by the software.

The spreadsheet can contain elemental and ratios querries and also a line for the color querries.

Attributes:

Name Type Description
prefix str

Common name to all data files (eg: lead_ore_).

suffix str

Type of the data file.

table str

Name of spreadsheet containing thresholds (eg: Mask.xlsx).

normalization str

Indicate if data are normalized or not, only valid

test str

test

Methods

create_mineral_mask(self)

Create a 2D array that associate each pixel to a mask by assigning a value to each pixel. It also creates a dictionnary containing the relative proportion of a value compared to others.

Source code in marcia/mask.py
def create_mineral_mask(self):
    """
    Create a 2D array that associate each pixel to a mask
    by assigning a value to each pixel. It also creates a
    dictionnary containing the relative proportion of a value
    compared to others.
    """
    # Creation of proportion dictionnary
    proportion = {}

    # Initialization of 2D array
    array = np.zeros((self.data_cube.shape[0], self.data_cube.shape[1]))

    # Convert the array to nan values
    array[np.isfinite(array)] = np.nan

    # Loop over the mask to check pixels that are assigned more than once
    for indice in range(len(self.Minerals)):
        array[(np.isfinite(self.mineral_cube[:, :, indice])) & (
            np.nansum(self.mineral_cube, axis=2) == 1)] = indice
    array[np.where(np.nansum(self.mineral_cube, axis=2) > 1)
          ] = len(self.Minerals) + 1
    for indice in range(len(self.Minerals)):
        proportion[indice] = np.where(array == indice)[
            0].shape[0] / np.sum(np.isfinite(array)) * 100
    return array
cube_masking_keep(self, mineral)

Recreates a raw datacube containing data only in the wanted mask.

Parameters:

Name Type Description Default
mineral str

Name of the wanted mask (eg: 'Galene').

required
Source code in marcia/mask.py
def cube_masking_keep(self, mineral: str):
    """
    Recreates a raw datacube containing data only
    in the wanted mask.

    Args:
        mineral: Name of the wanted mask (eg: 'Galene').

    """
    # Conversion of given string indices to integer indice of the cube
    mineral = list(self.Minerals.values()).index(str(mineral))
    cube = hs.load(self.prefix[:-1] + ".rpl",
                   signal_type="EDS_SEM",
                   lazy=True)
    array = np.asarray(cube)
    array[np.isnan(self.mineral_cube[:, :, mineral])] = 0
    cube = hs.signals.Signal1D(array)
    cube.save(self.prefix[:-1] + '_mask_kept_' +
              self.Minerals[mineral] + ".rpl",
              encoding='utf8')
    f = open(self.prefix[:-1] + ".rpl", "r")
    output = open(self.prefix[:-1] + '_mask_kept_' +
                  self.Minerals[mineral] + ".rpl",
                  'w')
    output.write(f.read())
    f.close()
    output.close()
cube_masking_remove(self, mineral)

Recreates a raw datacube containing all the data without the mask not wanted.

Parameters:

Name Type Description Default
mineral str

Name of the wanted mask (eg: 'Galene')

required
Source code in marcia/mask.py
def cube_masking_remove(self, mineral: str):
    """
    Recreates a raw datacube containing all the
    data without the mask not wanted.

    Args:
        mineral : Name of the wanted mask (eg: 'Galene')

    """
    # Conversion of given string indices to integer indice of the cube
    if mineral == 'mixed':
        a = self.create_mineral_mask()[0]
        mixed = np.where(a < np.nanmax(a), np.nan, a)
        cube = hs.load(self.prefix[:-1] + ".rpl",
                       signal_type="EDS_SEM",
                       lazy=True)
        array = np.asarray(cube)
        array[np.isfinite(mixed)] = 0
        cube = hs.signals.Signal1D(array)
        cube.save(self.prefix[:-1] + '_mask_removed_mixed' + ".rpl",
                  encoding='utf8')
        f = open(self.prefix[:-1] + ".rpl", "r")
        output = open(self.prefix[:-1] + '_mask_removed_mixed' + ".rpl",
                      'w')
        output.write(f.read())
        f.close()
        output.close()

    elif mineral == 'not indexed':
        a = self.create_mineral_mask()[0]
        nan = np.where(np.isnan(a), 0, a)
        cube = hs.load(self.prefix[:-1] + ".rpl",
                       signal_type="EDS_SEM",
                       lazy=True)
        array = np.asarray(cube)
        array[np.isfinite(nan)] = 0
        cube = hs.signals.Signal1D(array)
        cube.save(self.prefix[:-1] + '_mask_removed_nan' +
                  ".rpl",
                  encoding='utf8')
        f = open(self.prefix[:-1] + ".rpl", "r")
        output = open(self.prefix[:-1] + '_mask_removed_nan' +
                      + ".rpl",
                      'w')
        output.write(f.read())
        f.close()
        output.close()
    else:
        mineral = list(self.Minerals.values()).index(str(mineral))
        cube = hs.load(self.prefix[:-1] + ".rpl",
                       signal_type="EDS_SEM",
                       lazy=True)
        array = np.asarray(cube)
        array[np.isfinite(self.mineral_cube[:, :, mineral])] = 0
        cube = hs.signals.Signal1D(array)
        cube.save(self.prefix[:-1] + '_mask_removed_' +
                  self.Minerals[mineral] + ".rpl",
                  encoding='utf8')
        f = open(self.prefix[:-1] + ".rpl", "r")
        output = open(self.prefix[:-1] + '_mask_removed_' +
                      self.Minerals[mineral] + ".rpl",
                      'w')
        output.write(f.read())
        f.close()
        output.close()
datacube_creation(self)

Create a 3D array (X and Y are the dimensions of the sample and Z dimension is the number of elements/emission lines taken into account for the classification) It stacks the information contained in the elemental files given ranked according to the spreasheet ranking. If the normalization is asked or if the elemental map is an image, the data in the array are between 0 and 100. If there is a scalebar, the corresponding pixels are non assigned.

Three types of elemental files are accepted
  • Imges (.bmp of .tif), which are RGB files : each pixel contains 3 values between 0 and 255. The rgb is put into greyscale calculated by the norm 2.

  • Textfile (.txt), which is already the elemental array where the values are the number of counts.

  • Raw file (.rpl), wich is the datacube containing all the spectra for every pixel. The hyperspy library is used to extract the emission lines corresponding to the wanted elements.

Textfiles and raw files can be normalized or not, the spreadsheet must be written according to that.

The function also creates a dictionnary containing the Z position of the element in the 3D array created.

2 class files created in that function.

Source code in marcia/mask.py
def datacube_creation(self):
    """
    Create a 3D array (X and Y are the dimensions of the
    sample and Z dimension is the number of elements/emission lines taken
    into account for the classification)
    It stacks the information contained in the elemental files given ranked
    according to the spreasheet ranking.
    If the normalization is asked or if the elemental map is an image,
    the data in the array are between 0 and 100.
    If there is a scalebar, the corresponding pixels are non assigned.

    Three types of elemental files are accepted
    -------------------------------------------
    - Imges (.bmp of .tif), which are RGB files : each pixel contains 3
    values between 0 and 255. The rgb is put into greyscale calculated
    by the norm 2.

    - Textfile (.txt), which is already the elemental array where the
    values are the number of counts.

    - Raw file (.rpl), wich is the datacube containing all the spectra
    for every pixel. The hyperspy library is used to extract the
    emission lines corresponding to the wanted elements.

    Textfiles and raw files can be normalized or not, the spreadsheet
    must be written according to that.

    The function also creates a dictionnary containing the Z position
    of the element in the 3D array created.

    2 class files created in that function.

    """
    # Check if the data files are images
    if self.suffix in ('.bmp', '.tif', '.jpg', '.png'):

        # Set automatic normalization to True
        self.normalization = True

        # Creation of element names dictionnary
        self.Elements = {}

        # Read the first image to know the dimensions
        test_image = np.linalg.norm(
            imread(
                self.prefix
                + self.table.iloc[0][0]
                + self.suffix),
            axis=2)

        self.data_cube = np.zeros(
            (test_image.shape[0],
             test_image.shape[1],
             self.table.shape[0]))

        test_image[:, :] = 0

        # Loop over elements in the table
        for element in range(self.table.shape[0]):
            self.Elements[element] = self.table.iloc[element]['Element']

            # Check if the element is not a ratio of two elements
            if '/' not in self.table.iloc[element]['Element']:

                # Load of the  RGB image and normalization to one component
                self.data_cube[:, :, element] = np.linalg.norm(
                    imread(
                        self.prefix
                        + self.table.iloc[element][0]
                        + self.suffix),
                    axis=2)

            # If the element is actually a ratio of two elements
            else:

                # Load of the two images
                image_over = imread(
                    self.prefix
                    + self.table['Element'][element].split('/')[0]
                    + self.suffix)
                image_under = imread(
                    self.prefix
                    + self.table['Element'][element].split('/')[1]
                    + self.suffix)

                # Normalization of the two images
                image_over_grey = np.linalg.norm(image_over, axis=2)
                image_under_grey = np.linalg.norm(image_under, axis=2)

                # Set 0 values to 0.01 in denominator image in order to
                # avoid division by 0
                image_under_grey[image_under_grey == 0.] = 0.01
                self.data_cube[
                    :, :, element] = image_over_grey / image_under_grey

        # Normalization over 100 to every element of the cube
        for i in range(len(self.Elements)):
            self.data_cube[:, :, i] = self.data_cube[
                :, :, i] / np.nanmax(self.data_cube[:, :, i]) * 100

    # Check if data are textfiles consisting of raw count data per pixel
    # per energy
    elif self.suffix == '.txt':
        self.Elements = {}
        # Read the first image to know the dimensions
        test_image = np.loadtxt(
            self.prefix
            + self.table.iloc[0][0]
            + self.suffix,
            delimiter=';')

        self.data_cube = np.zeros(
            (test_image.shape[0],
             test_image.shape[1],
             self.table.shape[0]))
        test_image[:, :] = 0

        # Loop over elements in the table
        for element in range(self.table.shape[0]):
            self.Elements[element] = self.table.iloc[element]['Element']

            # Check if the element is not a ratio of two elements
            if '/' not in self.table.iloc[element]['Element']:
                # Load of the data count per element
                self.data_cube[:, :, element] = np.loadtxt(
                    self.prefix
                    + self.table.iloc[element][0]
                    + self.suffix,
                    delimiter=';')

            # If the element is actually a ratio of two elements
            else:
                image_over_grey = np.loadtxt(
                    self.prefix
                    + self.table['Element'][element].split('/')[0]
                    + self.suffix,
                    delimiter=';')
                image_under_grey = np.loadtxt(
                    self.prefix
                    + self.table['Element'][element].split('/')[1]
                    + self.suffix,
                    delimiter=';')

                self.data_cube[
                    :, :, element] = image_over_grey / image_under_grey
        # If user wants to see normalized over 100 data
        # This option makes impossible intensity comparison over element
        if self.normalization:
            for i in range(len(self.Elements)):
                self.data_cube[:, :, i] = self.data_cube[
                    :, :, i] / np.nanmax(self.data_cube[:, :, i]) * 100

    # Check if data are .rpl file, that is complete datacube
    # Load of the file using HyperSpy library
    elif self.suffix == '.rpl':
        cube = hs.load(self.prefix + ".rpl",
                       signal_type="EDS_SEM",
                       lazy=True)
        cube.axes_manager[-1].name = 'E'
        cube.axes_manager['E'].units = 'keV'
        cube.axes_manager['E'].scale = 0.01
        cube.axes_manager['E'].offset = -0.97
        self.Elements = {}
        self.data_cube = np.zeros((cube.axes_manager.shape[1],
                                   cube.axes_manager.shape[0],
                                   self.table.shape[0]))

        for element in range(self.table.shape[0]):
            self.Elements[element] = self.table.iloc[element]['Element']

            if '/' not in self.table.iloc[element]['Element']:
                cube.set_elements([self.table.iloc[element]['Element']])
                array = cube.get_lines_intensity()
                self.data_cube[:, :, element] = np.asarray(array[0])

            else:
                cube.set_elements(
                    [self.table['Element'][element].split('/')[0]])
                array = cube.get_lines_intensity()
                image_over = np.asarray(array[0])
                cube.set_elements(
                    [self.table['Element'][element].split('/')[1]])
                array = cube.get_lines_intensity()
                image_under = np.asarray(array[0])

                image_under[image_under == 0.] = 0.001
                self.data_cube[
                    :, :, element] = image_over / image_under

        if self.normalization:
            for i in range(len(self.Elements)):
                self.data_cube[:, :, i] = self.data_cube[
                    :, :, i] / np.nanmax(self.data_cube[:, :, i]) * 100

    # Raise Exception to provide valide data type
    else:
        raise Exception(f"{self.prefix} invalid data type. "
                        f"Valid data types are: "
                        f".png, .bmp, .tif, .txt or .rpl ")
get_biplot(self, indicex, indicey)

Plot one element against another one in a scatter plot Input is the indexes of each of the two element in the 3D array Useful function in order to see elemental ratios and some elemental thresholds.

Parameters:

Name Type Description Default
indicex str

Name of the wanted element on x axis (eg: 'Fe').

required
indicey str

Name of the wanted element on y axis (eg: 'Pb').

required
Source code in marcia/mask.py
def get_biplot(self, indicex: str, indicey: str):
    """
    Plot one element against another one in a scatter plot
    Input is the indexes of each of the two element in the 3D array
    Useful function in order to see elemental ratios and some
    elemental thresholds.

    Args:
        indicex: Name of the wanted element on x axis (eg: 'Fe').
        indicey: Name of the wanted element on y axis (eg: 'Pb').

    """
    # Conversion of given string indices to integer indices of the cubes
    indicex = list(self.Elements.values()).index(str(indicex))
    indicey = list(self.Elements.values()).index(str(indicey))
    fig, axes = plt.subplots()

    # Number of points limited to 100,000 for computationnal time

    Valuesx = self.data_cube[
        :, :, indicex][np.isfinite(self.data_cube[:, :, indicex])]
    Valuesy = self.data_cube[
        :, :, indicey][np.isfinite(self.data_cube[:, :, indicey])]

    data = {'x': Valuesx, 'y': Valuesy}
    df = pd.DataFrame(data)

    # Limit number of samples to 100,000
    if len(df) > 100000:
        print('Number of points limited to 100000')
        df = df.sample(n=100000)
        df = df.reset_index().drop(columns=['index'])
    plt.xlim(0, np.max(Valuesx))
    plt.ylim(0, np.max(Valuesy))
    plt.xlabel(str(self.Elements[indicex]))
    plt.ylabel(str(self.Elements[indicey]))
    sns.scatterplot(x=df.x, y=df.y, alpha=0.3, marker="+")
    fig.tight_layout()
    plt.show()
get_hist(self, indice)

Plot the elemental map on the left side an the corresponding hitogram of intensity on the right side Input is the index of the element in the 3D array Useful function in order to set the threshold in the spreadsheet.

Parameters:

Name Type Description Default
indice str

Name of the wanted element (eg: 'Fe')

required
Source code in marcia/mask.py
def get_hist(self, indice: str):
    """
    Plot the elemental map on the left side an
    the corresponding hitogram of intensity on the right side
    Input is the index of the element in the 3D array
    Useful function in order to set the threshold in the spreadsheet.

    Args:
        indice: Name of the wanted element (eg: 'Fe')

    """
    # Conversion of given string indices to integer indice of the cube
    indice = list(self.Elements.values()).index(str(indice))
    fig, axes = plt.subplots(1, 2, figsize=(10, 5))
    ax = axes.ravel()

    # Keep only finite values
    finite_data = self.data_cube[:, :, indice][np.isfinite(
        self.data_cube[:, :, indice])]
    im = ax[0].imshow(self.data_cube[:, :, indice])
    ax[0].grid()
    ax[0].set_title("Carte élémentaire : " + self.Elements[indice])
    fig.colorbar(im, ax=ax[0])
    plt.ylim(0, np.max(finite_data))
    sns.distplot(finite_data,
                 kde=False,
                 ax=axes[1],
                 hist_kws={'range': (0.0, np.max(finite_data))},
                 vertical=True)

    # Logarithm scale because background has a lof ot points and flatten
    # interesting information if linear
    ax[1].set_xscale('log')
    ax[1].set_title("Histograme d'intensité : " + self.Elements[indice])
    fig.tight_layout()
    plt.show()
get_mask(self, indice)

Plot the mineral mask wanted Input is the index of the mineral in the 3D array (cube).

Parameters:

Name Type Description Default
indice str

Name of the wanted mask (eg: 'Galene').

required
Source code in marcia/mask.py
def get_mask(self, indice: str):
    """
    Plot the mineral mask wanted
    Input is the index of the mineral in the 3D array (cube).

    Args:
        indice: Name of the wanted mask (eg: 'Galene').

    """
    # Conversion of given string indices to integer indice of the cube
    indice = list(self.Minerals.values()).index(str(indice))
    fig = plt.figure()
    plt.imshow(self.mineral_cube[:, :, indice])
    plt.title(self.Minerals[indice])
    plt.grid()
    plt.show()
get_masked_element(self, element, mineral)

Plot the elemental map and the histogram associated only in a specific mask.

Parameters:

Name Type Description Default
element str

Name of the wanted element (eg: 'Fe').

required
mineral str

Name of the wanted mask (eg: 'Galene').

required
Source code in marcia/mask.py
def get_masked_element(self, element: str, mineral: str):
    """
    Plot the elemental map and the histogram
    associated only in a specific mask.

    Args:
        element: Name of the wanted element (eg: 'Fe').
        mineral: Name of the wanted mask (eg: 'Galene').

    """
    # Conversion of given string indices to integer indices of the cubes
    element = list(self.Elements.values()).index(str(element))
    mineral = list(self.Minerals.values()).index(str(mineral))
    fig, axes = plt.subplots(1, 2, figsize=(10, 5))
    ax = axes.ravel()
    Anan = self.data_cube[:, :, element][np.isfinite(
        self.data_cube[:, :, element])]
    array = self.data_cube[:, :, element]
    array[np.isnan(self.mineral_cube[:, :, mineral])] = 0
    im = ax[0].imshow(array)
    ax[0].grid()
    ax[0].set_title("Carte élémentaire de {} masquéé par {}".format(
        self.Elements[element], self.Minerals[mineral]))
    fig.colorbar(im, ax=ax[0])
    plt.ylim(0, np.max(Anan))
    sns.distplot(Anan, kde=False, ax=axes[1], hist_kws={
                 'range': (0.0, np.max(Anan))}, vertical=True)
    ax[1].set_xscale('log')
    ax[1].set_title("Histograme d'intensité : " + self.Elements[element])
    fig.tight_layout()
    plt.show()
get_triplot(self, indicex, indicey, indicez)

Plot one element against another one in a scatter plot Input is the indexes of each of the two element in the 3D array Useful function in order to see elemental ratios and some elemental thresholds.

Parameters:

Name Type Description Default
indicex str

Name of the wanted element on x axis(eg: 'Fe').

required
indicey str

Name of the wanted element on y axis (eg: 'Pb').

required
indicez str

Name of the wanted element on colorscale (eg: 'Cu').

required
Source code in marcia/mask.py
def get_triplot(self, indicex: str, indicey: str, indicez: str):
    """
    Plot one element against another one in a scatter plot
    Input is the indexes of each of the two element in the 3D array
    Useful function in order to see elemental ratios and some elemental
    thresholds.

    Args:
        indicex: Name of the wanted element on x axis(eg: 'Fe').
        indicey: Name of the wanted element on y axis (eg: 'Pb').
        indicez: Name of the wanted element on colorscale (eg: 'Cu').

    """
    # Conversion of given string indices to integer indices of the cubes
    indicex = list(self.Elements.values()).index(str(indicex))
    indicey = list(self.Elements.values()).index(str(indicey))
    indicez = list(self.Elements.values()).index(str(indicez))
    fig, axes = plt.subplots()
    Valuesx = self.data_cube[
        :, :, indicex][np.isfinite(self.data_cube[:, :, indicex])]
    Valuesy = self.data_cube[
        :, :, indicey][np.isfinite(self.data_cube[:, :, indicey])]
    Valuesz = self.data_cube[
        :, :, indicez][np.isfinite(self.data_cube[:, :, indicez])]

    data = {'x': Valuesx, 'y': Valuesy, 'z': Valuesz}
    df = pd.DataFrame(data)

    if len(df) > 100000:
        print('Number of points limited to 100000')
        df = df.sample(n=100000)
        df = df.reset_index().drop(columns=['index'])

    plt.xlim(0, np.max(Valuesx))
    plt.ylim(0, np.max(Valuesy))

    plt.title(str(self.Elements[indicez]))
    sns.scatterplot(x=df.x,
                    y=df.y,
                    hue=df.z,
                    alpha=0.3,
                    marker="+")
    plt.xlabel(str(self.Elements[indicex]))
    plt.ylabel(str(self.Elements[indicey]))
    fig.tight_layout()
    plt.show()
load_table(self)

Load the spreadsheet into the programm. Verification if information of colors are required for the classification. Colors are also specified in the spreadsheet.

Source code in marcia/mask.py
def load_table(self):
    """
    Load the spreadsheet into the programm.
    Verification if information of colors are required for
    the classification. Colors are also specified in the spreadsheet.

    """
    # Check if table is csv/txt or xlsx
    if self.table_name.split('.')[-1] in ('csv', 'txt'):
        self.table = pd.read_csv(self.table_name)

    elif 'xls' in self.table_name.split('.')[-1]:
        self.table = pd.read_excel(self.table_name)

    else:
        raise Exception(
            f"{self.table_name.split('.')[-1]} "
            f"invalid Table format."
            f"Valid data types are: .csv, .txt, or .xls ")

    # Check if table has specific colors for the masks
    if self.table['Element'].str.contains('ouleur|olor').any():
        indice = np.where(
            self.table['Element'].str.contains('ouleur|olor'))[0][0]

        # Creation of dictionnary containing the colors
        self.colors = {}
        for coul in range(1, self.table.iloc[indice].shape[0]):
            if isinstance(self.table.iloc[indice][coul], str):
                self.colors[coul - 1] = self.table.iloc[indice][coul]

        # For simplicity in the process, color column is then removed
        self.table = self.table.drop([indice])
mineralcube_creation(self)

Create a 3D numpy array (X and Y are the dimensions of the sample and Z dimension is the number of minerals wanted for the classification). The minerals are defined by the columns in the spreadsheet. The 2D array create per mineral depends on the threshold specified in the spreadsheet. If one value is given, it corresponds to the minimum threshold to be in the mineral. If two values separated by a dash, it corresponds to the range of values for this element to be in the mineral. Given values are outside the range.

Each mineral array is binary with 1 where the pixel is in the mineral and NaN (non assigned) where the pixel is not in the mineral.

The function also creates a dictionnary containing the Z position of the minerals in the 3D array created.

2 class files created in that function.

Source code in marcia/mask.py
def mineralcube_creation(self):
    """
    Create a 3D numpy array (X and Y are the dimensions
    of the sample and Z dimension is the number of minerals wanted for
    the classification).
    The minerals are defined by the columns in the spreadsheet. The 2D
    array create per mineral depends on the threshold specified in the
    spreadsheet.
    If one value is given, it corresponds to the minimum threshold to
    be in the mineral.
    If two values separated by a dash, it corresponds to the range of
    values for this element to be in the mineral.
    Given values are outside the range.

    Each mineral array is binary with 1 where the pixel is in the
    mineral and NaN (non assigned) where the pixel is not in the mineral.

    The function also creates a dictionnary containing the Z position
    of the minerals in the 3D array created.

    2 class files created in that function.

    """
    # Creation of mineral/mask names dictionnary
    self.Minerals = {}

    # Intializing data cube
    self.mineral_cube = np.zeros((self.data_cube.shape[0],
                                  self.data_cube.shape[1],
                                  self.table.shape[1] - 1))

    # Loop over each mask in order to fill the cube and dictionnary
    for mask in range(1, self.table.shape[1]):

        # Extract name of the mask
        name = self.table.columns[mask]

        # Fill the dictionnary, the key being an integer index
        self.Minerals[mask - 1] = name

        # Values are convert to string in order to facilitate later split
        str_table = self.table[name].astype('str', copy=True)

        # Keeping indices of elements that are used in a mask
        index_str = np.where(self.table[name].notnull())[0]

        # Initializing intermediate 3D array
        mask_i_str = np.zeros((self.data_cube.shape[0],
                               self.data_cube.shape[1],
                               index_str.shape[0]))

        # Loop over elements of the mask
        for k in range(index_str.shape[0]):
            mask_i_str[:, :, k] = self.data_cube[:, :, index_str[k]]

            # If only one value in the table: it corresponds to minimum
            # threshold
            if len(str_table[index_str[k]].split('-')) == 1:
                threshold_min = float(
                    str_table[index_str[k]].split('-')[0])
                threshold_max = None

            # If more thant one value (should be 2): it corresponds to the
            # range of accepted values
            else:
                threshold_min = float(
                    str_table[index_str[k]].split('-')[0])
                threshold_max = float(
                    str_table[index_str[k]].split('-')[1])

            # If the value are normalized, the threshold is between 0 and
            # 1: need to compare to maximum value
            if self.normalization:
                mask_i_str[:, :, k][mask_i_str[
                    :, :, k] < threshold_min * np.nanmax(
                        mask_i_str[:, :, k])] = np.nan
                if threshold_max:
                    mask_i_str[:, :, k][mask_i_str[
                        :, :, k] > threshold_max * np.nanmax(
                            mask_i_str[:, :, k])] = np.nan

                # Values outside thresholds are nan, and valid values are
                # set to 1
                mask_i_str[np.isfinite(mask_i_str)] = 1

            # If not normalize, threshold is just the number of counts
            else:
                mask_i_str[:, :, k][mask_i_str[:, :, k]
                                    < threshold_min] = np.nan
                if threshold_max:
                    mask_i_str[:, :, k][mask_i_str[:, :, k]
                                        > threshold_max] = np.nan

                # Values outside thresholds are nan, and valid values are
                # set to 1
                mask_i_str[np.isfinite(mask_i_str)] = 1

        # 3D array is stacked
        mask_i_str = np.nansum(mask_i_str, axis=2)

        # Mask correspond to maximum values: ones that satisfied all
        # conditions
        mask_i_str[mask_i_str < np.max(mask_i_str)] = np.nan

        # Mask cube 2D slice is filled with 1 where mask is true
        self.mineral_cube[:, :, mask - 1] = mask_i_str / \
            np.nanmax(mask_i_str)
plot_mineral_mask(self)

For mineralogy purposes, valid only if all masks are minerals Plot all the mask onto one picture in order to visualize the classification. Each pixel correspond to only one mineral at the time, if not, it is classified as "mixed".

Source code in marcia/mask.py
def plot_mineral_mask(self):
    """
    For mineralogy purposes, valid only if all masks are minerals
    Plot all the mask onto one picture in order to visualize
    the classification. Each pixel correspond to only one mineral
    at the time, if not, it is classified as "mixed".

    """
    fig = plt.figure()

    array, proportion = self._create_mineral_mask_and_prop()

    # First plot to generate random colors
    im = plt.imshow(array, cmap='Paired')

    # Store finite values for later purpose
    finite_values_array = array[np.isfinite(array)]

    # Check if mixed pixels, need to add one more value
    if np.nansum(
            self.mineral_cube,
            axis=2).max() > 1:
        values = np.arange(len(self.Minerals) + 1)
    else:
        values = np.arange(len(self.Minerals))

    colors = [im.cmap(im.norm(value)) for value in values]
    plt.close()
    # Test if colors where specify in the table
    if self.colors:
        # If true, specified values are replaced
        for value in self.colors:
            colors[value] = self.colors[value]

    # Generating the new colormap
    new_colormap = ListedColormap(colors)

    # Open new figure
    fig = plt.figure()
    im = plt.imshow(array,
                    cmap=new_colormap,
                    vmin=values.min(),
                    vmax=values.max())

    # create a patch for every color
    # If true, there are mixed pixels: need to add a patch of mixte
    if np.nanmax(array) > len(self.Minerals):
        patches = [
            mpatches.Patch(
                color=colors[np.where(
                    values == int(i))[0][0]],
                label="{} : {} %".format(
                    self.Minerals[int(i)],
                    str(
                        round(
                            proportion[
                                int(i)],
                            2)))) for i in values[
                :-1] if round(
                proportion[
                    int(i)], 2) > 0]

        patches.append(mpatches.Patch(
            color=colors[-1],
            label="{} : {} %".format(
                'Misclassified',
                str(round(
                    np.where(array == np.nanmax(
                        array))[0].shape[0] / np.sum(
                        np.isfinite(array)) * 100,
                    2)))))

    # If False, just add patches of corresponding masks
    else:
        patches = [
            mpatches.Patch(
                color=colors[
                    np.where(
                        values == int(i))[0][0]],
                label="{} : {} %".format(
                    self.Minerals[
                        int(i)], str(
                        round(
                            proportion[
                                int(i)], 2)))) for i in values[:] if round(
                                    proportion[
                                        int(i)], 2) > 0]

    # Finally add a patch to specify proporty of non-classified pixel
    # Two reasons : images is bigger than sample or misclassification
    patches.append(
        mpatches.Patch(
            color='white',
            label="{} : {} %".format(
                'Not classified', str(
                    round(
                        (self.data_cube.shape[0]
                            * self.data_cube.shape[1]
                            - len(finite_values_array))
                        / (self.data_cube.shape[0]
                           * self.data_cube.shape[1])
                        * 100, 2)))))

    # Add patches to the legend
    plt.legend(handles=patches,
               bbox_to_anchor=(1.05, 1),
               loc=2,
               borderaxespad=0.)
    plt.grid(True)
    plt.title("Mineralogical classification - " + self.prefix[:-1])
    plt.tight_layout()
    plt.show()
save_mask(self, indice, raw=False)

Save the mineral mask wanted as a .tif file. Input is the index of the mineral in the 3D array (cube).

Parameters:

Name Type Description Default
indice str

Name of the wanted element (eg: 'Fe')

required
Source code in marcia/mask.py
def save_mask(self, indice: str, raw: bool = False):
    """
    Save the mineral mask wanted as a .tif file.
    Input is the index of the mineral in the 3D array (cube).

    Args:
        indice: Name of the wanted element (eg: 'Fe')

    """
    indice = list(self.Minerals.values()).index(str(indice))
    if not raw:
        # Conversion of given string indices to integer indice of the cube
        plt.imshow(self.mineral_cube[:, :, indice])
        plt.title(self.Minerals[indice])
        plt.savefig('Mask_' + self.Minerals[indice] + '.tif')
        plt.close()
    else:
        test_array = (
            self.mineral_cube[
                :,
                :,
                indice] * 255).astype(
            np.uint8)
        image = Image.fromarray(test_array)
        image.save('Mask_' + self.Minerals[indice] + '.tif')
save_mask_spectrum(self, mask)

Save the mean spectrum of a given mask as a txt file First column is channel Second column is counts

Parameters:

Name Type Description Default
mask str

Name of the wanted mask (eg: 'Galene')

required
Source code in marcia/mask.py
def save_mask_spectrum(self, mask: str):
    """Save the mean spectrum of a given mask as a txt file
    First column is channel
    Second column is counts

    Args:
        mask: Name of the wanted mask (eg: 'Galene')

    """

    mineral = list(self.Minerals.values()).index(
        str(mask))
    cube = hs.load(self.prefix[:-1] + ".rpl",
                   signal_type="EDS_SEM",
                   lazy=True)
    array = np.asarray(cube)
    array[np.isnan(self.mineral_cube[:, :, mineral])] = 0
    cube = hs.signals.Signal1D(array)
    spectrum = cube.sum().data
    d = {'Counts': spectrum}
    dataframe = pd.DataFrame(data=d)
    dataframe.index.name = 'channel'
    dataframe.to_csv(mask + '_mean_spectrum.txt')