Example #1
0
def _process_thickness_metadata(mdict, base):
    abs_thick = _try_get_dict_val(
        mdict, base + ['Thickness', 'Absolute', 'Measurement'])
    abs_units = _try_get_dict_val(mdict,
                                  base + ['Thickness', 'Absolute', 'Units'])
    abs_mfp = _try_get_dict_val(
        mdict, base + ['Thickness', 'Absolute', 'Mean Free Path'])
    rel_thick = _try_get_dict_val(
        mdict, base + ['Thickness', 'Relative', 'Measurement'])
    if abs_thick != 'not found':
        _set_nest_dict_val(
            mdict,
            ['nx_meta', 'EELS', f'Thickness (absolute) ['
             f'{abs_units}]'], abs_thick)
    if abs_mfp != 'not found':
        _set_nest_dict_val(
            mdict,
            ['nx_meta', 'EELS', 'Thickness (absolute) mean '
             'free path'], abs_mfp[0])
    if rel_thick != 'not found':
        _set_nest_dict_val(mdict,
                           ['nx_meta', 'EELS', 'Thickness (relative) [t/λ]'],
                           rel_thick)

    return mdict
Example #2
0
def parse_system_info(mdict):
    """
    Parses the `System` portion of the metadata dictionary from the Quanta to
    get values such as software version, chamber config, etc.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_quanta_metadata`

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    to_parse = [(['System', 'Chamber'], ['Chamber ID']),
                (['System', 'Pump'], ['Vacuum Pump']),
                (['System', 'SystemType'], ['System Type']),
                (['System', 'Stage'], ['Stage Description'])]

    for m_in, m_out in to_parse:
        val = _try_get_dict_val(mdict, m_in)
        if val != 'not found':
            _set_nest_dict_val(mdict, ['nx_meta'] + m_out, val)

    # Parse software info into one output tag:
    output_vals = []
    val = _try_get_dict_val(mdict, ['System', 'Software'])
    if val != 'not found':
        output_vals.append(val)
    val = _try_get_dict_val(mdict, ['System', 'BuildNr'])
    if val != 'not found':
        output_vals.append(f'(build {val})')
    if len(output_vals) > 0:
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Software Version'],
                           ' '.join(output_vals))

    # parse column and type into one output tag:
    output_vals = []
    val = _try_get_dict_val(mdict, ['System', 'Column'])
    if val != 'not found':
        output_vals.append(val)
    val = _try_get_dict_val(mdict, ['System', 'Type'])
    if val != 'not found':
        output_vals.append(val)
    if len(output_vals) > 0:
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Column Type'],
                           ' '.join(output_vals))

    return mdict
Example #3
0
def get_pre_path(mdict):
    """
    Get the path into a dictionary where the important DigitalMicrograph
    metadata is expected to be found. If the .dm3/.dm4 file contains a stack
    of images, the important metadata for NexusLIMS is not at its usual place
    and is instead under a `plan info` tag, so this method will determine if the
    stack metadata is present and return the correct path. ``pre_path`` will
    be something like ``['ImageList', 'TagGroup0', 'ImageTags', 'plane
    info', 'TagGroup0', 'source tags']``.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_dm3_metadata`

    Returns
    -------
    pre_path : list
        A list containing the subsequent keys that need to be traversed to
        get to the point in the `mdict` where the important metadata is stored
    """
    # test if we have a stack
    stack_val = _try_get_dict_val(
        mdict, ['ImageList', 'TagGroup0', 'ImageTags', 'plane info'])
    if stack_val != 'not found':
        # we're in a stack
        pre_path = [
            'ImageList', 'TagGroup0', 'ImageTags', 'plane info', 'TagGroup0',
            'source tags'
        ]
    else:
        pre_path = ['ImageList', 'TagGroup0', 'ImageTags']

    return pre_path
Example #4
0
def parse_experimental_description(metadata):
    """
    Parse the metadata that is saved at specific places within
    the .emi tag structure into a consistent place in the metadata dictionary
    returned by :py:meth:`get_ser_metadata`. Specifically looks at the
    "ExperimentalDescription" node of the metadata structure.

    Parameters
    ----------
    metadata : dict
        A metadata dictionary as returned by :py:meth:`get_ser_metadata`

    Returns
    -------
    metadata : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key

    Notes
    -----
    The terms to extract in this section were
    """
    # These terms were captured by looping through a selection of
    # representative .ser/.emi datafiles and running something like the
    # following
    base = ['ObjectInfo', 'ExperimentalDescription']

    experimental_description = _try_get_dict_val(metadata, base)
    if experimental_description != 'not found' and isinstance(
            experimental_description, dict):
        term_mapping = {}
        for k in metadata['ObjectInfo']['ExperimentalDescription'].keys():
            term, unit = split_fei_metadata_units(k)
            if unit:
                unit = unit.replace('uA', 'μA').\
                            replace('um', 'μm').\
                            replace('deg', '°')
            term_mapping[(k, )] = f'{term}' + (f' ({unit})' if unit else '')
            # Make stage position a nested list
            if 'Stage' in term:
                term = term.replace('Stage ', '')
                term_mapping[(k, )] = [
                    'Stage Position',
                    f'{term}' + (f' ({unit})' if unit else '')
                ]
            # Make filter settings a nested list
            if 'Filter ' in term:
                term = term.replace('Filter ', '')
                term_mapping[(k, )] = [
                    'Tecnai Filter',
                    f'{term.title()}' + (f' ({unit})' if unit else '')
                ]

        metadata = map_keys(term_mapping, base, metadata)

        # Microscope Mode often has excess spaces, so fix that if needed:
        if 'Mode' in metadata['nx_meta']:
            metadata['nx_meta']['Mode'] = metadata['nx_meta']['Mode'].strip()

    return metadata
Example #5
0
def parse_acquire_info(metadata):
    """
    Parse the metadata that is saved at specific places within
    the .emi tag structure into a consistent place in the metadata dictionary
    returned by :py:meth:`get_ser_metadata`. Specifically looks at the
    "AcquireInfo" node of the metadata structure.

    Parameters
    ----------
    metadata : dict
        A metadata dictionary as returned by :py:meth:`get_ser_metadata`

    Returns
    -------
    metadata : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    term_mapping = {
        ('AcceleratingVoltage', ): 'Microscope Accelerating Voltage (V)',
        ('Tilt1', ): 'Microscope Tilt 1',
        ('Tilt2', ): 'Microscope Tilt 2',
    }
    base = ['ObjectInfo', 'ExperimentalConditions', 'MicroscopeConditions']

    if _try_get_dict_val(metadata, base) != 'not found':
        metadata = map_keys(term_mapping, base, metadata)

    return metadata
Example #6
0
def map_keys(term_mapping, base, metadata):
    """
    Given a term mapping dictionary and a metadata dictionary, translate
    the input keys within the "raw" metadata into a parsed value in the
    "nx_meta" metadata structure.

    Parameters
    ----------
    term_mapping : dict
        Dictionary where keys are tuples of strings (the input terms),
        and values are either a single string or a list of strings (the
        output terms).
    base : list
        The 'root' path within the metadata dictionary of where to start
        applying the input terms
    metadata : dict
        A metadata dictionary as returned by :py:meth:`get_ser_metadata`

    Returns
    -------
    metadata : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key, as specified by ``term_mapping``

    Notes
    -----
    The ``term_mapping`` parameter should be a dictionary of the form:

    .. code-block:: python

        {
            ('val1_1', 'val1_2') : 'output_val_1',
            ('val1_1', 'val2_2') : 'output_val_2',
            etc.
        }

    Assuming ``base`` is ``['ObjectInfo', 'AcquireInfo']``, this would map
    the term present at ``ObjectInfo.AcquireInfo.val1_1.val1_2`` into
    ``nx_meta.output_val_1``, and ``ObjectInfo.AcquireInfo.val1_1.val2_2`` into
    ``nx_meta.output_val_2``, and so on. If one of the output terms is a list,
    the resulting metadata will be nested. `e.g.` ``['output_val_1',
    'output_val_2']`` would get mapped to ``nx_meta.output_val_1.output_val_2``.
    """
    for in_term in term_mapping.keys():
        out_term = term_mapping[in_term]
        if isinstance(in_term, tuple):
            in_term = list(in_term)
        if isinstance(out_term, str):
            out_term = [out_term]
        val = _try_get_dict_val(metadata, base + in_term)
        # only add the value to this list if we found it
        if val != 'not found':
            _set_nest_dict_val(metadata, ['nx_meta'] + out_term,
                               _convert_to_numeric(val))

    return metadata
Example #7
0
def parse_det_info(mdict, det_name):
    """
    Parses the `Detector` portion of the metadata dictionary from the Quanta to
    get values such as brightness, contrast, signal, etc.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_quanta_metadata`
    det_name : str
        The "detector name" read from the root-level ``Beam`` node of the
        metadata dictionary

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    to_parse = [([det_name, 'Brightness'], ['Detector Brightness Setting']),
                ([det_name, 'Contrast'], ['Detector Contrast Setting']),
                ([det_name, 'EnhancedContrast'],
                 ['Detector Enhanced Contrast '
                  'Setting']), ([det_name, 'Signal'], ['Detector Signal']),
                ([det_name, 'Grid'], ['Detector Grid Voltage (V)']),
                ([det_name, 'Setting'], ['Detector Setting'])]

    for m_in, m_out in to_parse:
        val = _try_get_dict_val(mdict, m_in)
        if val != 'not found':
            try:
                val = _Decimal(val)
                if m_in == [det_name, 'Setting']:
                    # if "Setting" value is numeric, it's just the Grid
                    # voltage so skip it
                    continue
            except (ValueError, _invalidOp):
                pass
            _set_nest_dict_val(
                mdict, ['nx_meta'] + m_out,
                float(val) if isinstance(val, _Decimal) else val)

    _set_nest_dict_val(mdict, ['nx_meta'] + ['Detector Name'], det_name)

    return mdict
Example #8
0
def parse_scan_info(mdict, scan_name):
    """
    Parses the `Scan` portion of the metadata dictionary (on a Quanta this is
    always `"EScan"`) to get values such as dwell time, field width, and pixel
    size

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_quanta_metadata`
    scan_name : str
        The "scan name" read from the root-level ``Beam`` node of the
        metadata dictionary

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    # Values are in SI units, but we want easy to display, so include the
    # exponential factor that will get us from input unit (such as seconds)
    # to output unit (such as μs -- meaning factor = 6)
    to_parse = [
        ([scan_name, 'Dwell'], ['Pixel Dwell Time (μs)'], 6),
        ([scan_name, 'FrameTime'], ['Total Frame Time (s)'], 0),
        ([scan_name, 'HorFieldsize'], ['Horizontal Field Width (μm)'], 6),
        ([scan_name, 'VerFieldsize'], ['Vertical Field Width (μm)'], 6),
        ([scan_name, 'PixelHeight'], ['Pixel Width (nm)'], 9),
        ([scan_name, 'PixelWidth'], ['Pixel Height (nm)'], 9),
    ]

    for m_in, m_out, factor in to_parse:
        val = _try_get_dict_val(mdict, m_in)
        if val != 'not found' and val != '':
            val = _Decimal(val) * _Decimal(str(10**factor))
            _set_nest_dict_val(
                mdict, ['nx_meta'] + m_out,
                float(val) if isinstance(val, _Decimal) else val)

    return mdict
Example #9
0
def parse_experimental_conditions(metadata):
    """
    Parse the metadata that is saved at specific places within
    the .emi tag structure into a consistent place in the metadata dictionary
    returned by :py:meth:`get_ser_metadata`. Specifically looks at the
    "ExperimentalConditions" node of the metadata structure.

    Parameters
    ----------
    metadata : dict
        A metadata dictionary as returned by :py:meth:`get_ser_metadata`

    Returns
    -------
    metadata : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    term_mapping = {
        ('DwellTimePath', ): 'Dwell Time Path (s)',
        ('FrameTime', ): 'Frame Time (s)',
        ('CameraNamePath', ): 'Camera Name Path',
        ('Binning', ): 'Binning',
        ('BeamPosition', ): 'Beam Position (μm)',
        ('EnergyResolution', ): 'Energy Resolution (eV)',
        ('IntegrationTime', ): 'Integration Time (s)',
        ('NumberSpectra', ): 'Number of Spectra',
        ('ShapingTime', ): 'Shaping Time (s)',
        ('ScanArea', ): 'Scan Area',
    }
    base = ['ObjectInfo', 'AcquireInfo']

    if _try_get_dict_val(metadata, base) != 'not found':
        metadata = map_keys(term_mapping, base, metadata)

    # remove units from beam position (if present)
    if 'Beam Position (μm)' in metadata['nx_meta']:
        metadata['nx_meta']['Beam Position (μm)'] = \
            metadata['nx_meta']['Beam Position (μm)'].replace(' um', '')

    return metadata
Example #10
0
def parse_dm3_spectrum_image_info(mdict):
    """
    Parses metadata from the DigitalMicrograph tag structure that concerns any
    spectrum imaging information (from the "SI" tag) and places it in a
    "Spectrum Imaging" dictionary underneath the root-level ``nx_meta`` node.
    Metadata values that are commonly incorrect or may be placeholders are
    specified in a list under the ``nx_meta.warnings`` node.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_dm3_metadata`

    Returns
    -------
    mdict : dict
        The metadata dictionary with all the "EDS-specific" metadata
        added as sub-node under the ``nx_meta`` root level dictionary
    """
    pre_path = get_pre_path(mdict)

    # Spectrum imaging .dm3 tags of interest:
    base = pre_path + ['SI']

    for m_in, m_out in \
            [(['Acquisition', 'Pixel time (s)'], ['Pixel time (s)']),
             (['Acquisition', 'SI Application Mode', 'Name'], ['Scan Mode']),
             (['Acquisition', 'Spatial Sampling', 'Height (pixels)'],
              ['Spatial Sampling (Vertical)']),
             (['Acquisition', 'Spatial Sampling', 'Width (pixels)'],
              ['Spatial Sampling (Horizontal)']),
             (['Acquisition', 'Scan Options', 'Sub-pixel sampling'],
              ['Sub-pixel Sampling Factor'])]:
        val = _try_get_dict_val(mdict, base + m_in)
        # only add the value to this list if we found it, and it's not
        # one of the "facility-wide" set values that do not have any meaning:
        if val != 'not found':
            # add last value of each parameter to the "EDS" sub-tree of nx_meta
            _set_nest_dict_val(mdict, ['nx_meta', 'Spectrum Imaging'] + m_out,
                               val)

    # Check spatial drift correction separately:
    drift_per_val = _try_get_dict_val(
        mdict, base +
        ['Acquisition', 'Artefact Correction', 'Spatial Drift', 'Periodicity'])
    drift_unit_val = _try_get_dict_val(
        mdict, base +
        ['Acquisition', 'Artefact Correction', 'Spatial Drift', 'Units'])
    if drift_per_val != 'not found' and drift_unit_val != 'not found':
        val_to_set = f"Spatial drift correction every {drift_per_val} " \
                     f"{drift_unit_val}"
        # make sure statement looks gramatically correct
        if drift_per_val == 1:
            val_to_set = val_to_set.replace('(s)', '')
        else:
            val_to_set = val_to_set.replace('(s)', 's')
        # fix for "seconds(s)" (stupid DM...)
        if val_to_set[-2:] == 'ss':
            val_to_set = val_to_set[:-1]
        _set_nest_dict_val(
            mdict, ['nx_meta', 'Spectrum Imaging', 'Artefact Correction'],
            val_to_set)

    # Calculate acquisition duration:
    start_val = _try_get_dict_val(mdict, base + ['Acquisition', 'Start time'])
    end_val = _try_get_dict_val(mdict, base + ['Acquisition', 'End time'])
    if start_val != 'not found' and end_val != 'not found':
        start_dt = _dt.strptime(start_val, '%I:%M:%S %p')
        end_dt = _dt.strptime(end_val, '%I:%M:%S %p')
        duration = (end_dt - start_dt).seconds
        _set_nest_dict_val(
            mdict, ['nx_meta', 'Spectrum Imaging', 'Acquisition Duration (s)'],
            duration)

    # Set the dataset type to SpectrumImage if it is already a Spectrum (
    # otherwise it's just a STEM image) and any Spectrum Imaging tags were
    # added
    if 'Spectrum Imaging' in mdict['nx_meta']:
        if mdict['nx_meta']['DatasetType'] == 'Spectrum':
            _logger.info('Detected file as SpectrumImage type based on '
                         'presence of spectral metadata and spectrum imaging '
                         'info')
            mdict['nx_meta']['DatasetType'] = 'SpectrumImage'
            mdict['nx_meta']['Data Type'] = 'Spectrum_Imaging'
            if 'EELS' in mdict['nx_meta']:
                mdict['nx_meta']['Data Type'] = 'EELS_Spectrum_Imaging'
            if 'EDS' in mdict['nx_meta']:
                mdict['nx_meta']['Data Type'] = 'EDS_Spectrum_Imaging'

    return mdict
Example #11
0
def get_ser_metadata(filename):
    """
    Returns metadata (as a dict) from an FEI .ser file + its associated .emi
    files, with some non-relevant information stripped.

    Parameters
    ----------
    filename : str
        Path to FEI .ser file

    Returns
    -------
    metadata : dict
        Metadata of interest which is extracted from the passed files. If
        files cannot be opened, at least basic metadata will be returned (
        creation time, etc.)
    """
    # ObjectInfo present in emi; ser_header_parameters present in .ser
    # ObjectInfo should contain all the interesting metadata,
    # while ser_header_parameters is mostly technical stuff not really of
    # interest to anyone
    warning = None
    emi_filename = None
    ser_error = False
    try:
        # approach here is for every .ser we want to examine, load the
        # metadata from the corresponding .emi file. If multiple .ser files
        # are related to this emi, HyperSpy returns a list, so we select out
        # the right signal from that list if that's what is returned

        emi_filename, ser_index = get_emi_from_ser(filename)
        # make sure to load with "only_valid_data" so data shape is correct
        # loading the emi with HS will try loading the .ser too, so this will
        # fail if there's an issue with the .ser file
        emi_s = _hs_load(emi_filename, lazy=True, only_valid_data=True)
        emi_loaded = True

        # if there is more than one dataset, emi_s will be a list, so pick
        # out the matching signal from the list, which will be the "index"
        # from the filename minus 1:
        if isinstance(emi_s, list):
            s = emi_s[ser_index - 1]

        # otherwise we should just have a regular signal, so make s the same
        # as the data loaded from the .emi
        elif isinstance(emi_s, _BaseSignal):
            s = emi_s

    except FileNotFoundError:
        # if emi wasn't found, specifically mention that
        warning = 'NexusLIMS could not find a corresponding .emi metadata ' + \
                  'file for this .ser file. Metadata extraction will be ' + \
                  'limited.'
        _logger.warning(warning)
        emi_loaded = False
        emi_filename = None

    except Exception:
        # otherwise, HyperSpy could not load the .emi, so give generic warning
        # that .emi could not be loaded for some reason:
        warning = 'The .emi metadata file associated with this ' + \
                  '.ser file could not be opened by NexusLIMS. ' + \
                  'Metadata extraction will be limited.'
        _logger.warning(warning)
        emi_loaded = False

    if not emi_loaded:
        # if we couldn't load the emi, lets at least open the .ser to pull
        # out the ser_header_info
        try:
            s = _hs_load(filename, only_valid_data=True, lazy=True)
        except Exception:
            warning = f'The .ser file could not be opened (perhaps file is ' + \
                      f'corrupted?); Metadata extraction is not possible.'
            _logger.warning(warning)
            # set s to an empty signal just so we can process some basic
            # metadata using same syntax as if we had read it correctly
            s = _BaseSignal(_np.zeros(1))
            ser_error = True

    metadata = s.original_metadata.as_dictionary()
    metadata['nx_meta'] = {}

    # if we've already encountered a warning, add that to the metadata,
    if warning:
        metadata['nx_meta']['Extractor Warning'] = warning
    # otherwise check to ensure we actually have some metadata read from .emi
    elif 'ObjectInfo' not in metadata or \
            ('ExperimentalConditions' not in metadata['ObjectInfo'] and
             'ExperimentalDescription' not in metadata['ObjectInfo']):
        warning = 'No experimental metadata was found in the ' + \
                  'corresponding .emi file for this .ser. ' + \
                  'Metadata extraction will be limited.'
        _logger.warning(warning)
        metadata['nx_meta']['Extractor Warning'] = warning

    # if we successfully found the .emi file, add it to the metadata
    if emi_filename:
        rel_emi_fname = emi_filename.replace(
            _os.environ["mmfnexus_path"] + '/', '') if emi_filename else None
        metadata['nx_meta']['emi Filename'] = rel_emi_fname
    else:
        metadata['nx_meta']['emi Filename'] = None

    # Get the instrument object associated with this file
    instr = _get_instr(filename)
    # get the modification time (as ISO format):
    mtime = _os.path.getmtime(filename)
    mtime_iso = _dt.fromtimestamp(mtime).isoformat()

    # if we found the instrument, then store the name as string, else None
    instr_name = instr.name if instr is not None else None
    metadata['nx_meta']['fname'] = filename
    metadata['nx_meta']['Creation Time'] = mtime_iso
    metadata['nx_meta']['Instrument ID'] = instr_name

    # we could not read the signal, so add some basic metadata and return
    if ser_error:
        metadata['nx_meta']['DatasetType'] = 'Misc'
        metadata['nx_meta']['Data Type'] = '**REMOVED**'
        metadata['nx_meta']['warnings'] = []
        # sort the nx_meta dictionary (recursively) for nicer display
        metadata['nx_meta'] = _sort_dict(metadata['nx_meta'])
        del metadata['nx_meta']['fname']
        return metadata

    # try to set creation time to acquisition time from metadata
    acq_time = _try_get_dict_val(metadata, ['ObjectInfo', 'AcquireDate'])
    if acq_time != 'not found':
        metadata['nx_meta']['Creation Time'] = \
            _dt.strptime(acq_time, '%a %b %d %H:%M:%S %Y').isoformat()

    # manufacturer is at high level, so parse it now
    manufacturer = _try_get_dict_val(metadata, ['ObjectInfo', 'Manufacturer'])
    if manufacturer != 'not found':
        metadata['nx_meta']['Manufacturer'] = \
            manufacturer

    metadata['nx_meta']['Data Dimensions'] = str(s.data.shape)
    metadata['nx_meta']['warnings'] = []

    # set type to STEM Image by default (this seems to be most common)
    metadata['nx_meta']['DatasetType'] = 'Image'
    metadata['nx_meta']['Data Type'] = 'STEM_Imaging'

    metadata = parse_acquire_info(metadata)
    metadata = parse_experimental_conditions(metadata)
    metadata = parse_experimental_description(metadata)

    metadata['nx_meta']['Data Type'], metadata['nx_meta']['DatasetType'] = \
        parse_data_type(s, metadata)

    # we don't need to save the filename, it's just for internal processing
    del metadata['nx_meta']['fname']

    # sort the nx_meta dictionary (recursively) for nicer display
    metadata['nx_meta'] = _sort_dict(metadata['nx_meta'])

    return metadata
Example #12
0
def parse_dm3_eds_info(mdict):
    """
    Parses metadata from the DigitalMicrograph tag structure that concerns any
    EDS acquisition or spectrometer settings, placing it in an ``EDS``
    dictionary underneath the root-level ``nx_meta`` node. Metadata values
    that are commonly incorrect or may be placeholders are specified in a
    list under the ``nx_meta.warnings`` node.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_dm3_metadata`

    Returns
    -------
    mdict : dict
        The metadata dictionary with all the "EDS-specific" metadata
        added as sub-node under the ``nx_meta`` root level dictionary
    """
    pre_path = get_pre_path(mdict)

    # EELS .dm3 tags of interest:
    base = pre_path + ['EDS']

    for m in [['Acquisition', 'Continuous Mode'],
              ['Acquisition', 'Count Rate Unit'],
              ['Acquisition', 'Dispersion (eV)'],
              ['Acquisition', 'Energy Cutoff (V)'],
              ['Acquisition', 'Exposure (s)'], ['Count rate'],
              ['Detector Info', 'Active layer'],
              ['Detector Info', 'Azimuthal angle'],
              ['Detector Info', 'Dead layer'],
              ['Detector Info', 'Detector type'],
              ['Detector Info', 'Elevation angle'], ['Detector Info', 'Fano'],
              ['Detector Info', 'Gold layer'],
              ['Detector Info', 'Incidence angle'],
              ['Detector Info', 'Solid angle'],
              ['Detector Info', 'Stage tilt'],
              ['Detector Info', 'Window thickness'],
              ['Detector Info', 'Window type'], ['Detector Info', 'Zero fwhm'],
              ['Live time'], ['Real time']]:
        val = _try_get_dict_val(mdict, base + m)
        # only add the value to this list if we found it, and it's not
        # one of the "facility-wide" set values that do not have any meaning:
        if val != 'not found':
            # add last value of each parameter to the "EDS" sub-tree of nx_meta
            _set_nest_dict_val(mdict, ['nx_meta', 'EDS'] +
                               [m[-1] if len(m) > 1 else m[0]], val)

    # test to see if the SI attribute is present in the metadata dictionary.
    # If so, then some of the relevant EDS values are located there, rather
    # than in the root-level EDS tag (all of the EDS.Acquisition tags from
    # above)
    if _try_get_dict_val(mdict, pre_path + ['SI']) != 'not found':
        for m in [['Acquisition', 'Continuous Mode'],
                  ['Acquisition', 'Count Rate Unit'],
                  ['Acquisition', 'Dispersion (eV)'],
                  ['Acquisition', 'Energy Cutoff (V)'],
                  ['Acquisition', 'Exposure (s)']]:
            val = _try_get_dict_val(mdict, pre_path + ['SI'] + m)
            if val != 'not found':
                # add last value of each parameter to the "EDS" sub-tree of
                # nx_meta
                _set_nest_dict_val(mdict, ['nx_meta', 'EDS'] + [m[-1]], val)
        # for an SI EDS dataset, set "Live time", "Real time" and "Count rate"
        # to the averages stored in the ImageList.TagGroup0.ImageTags.EDS.Images
        # values
        im_dict = _try_get_dict_val(mdict, pre_path + ['EDS', 'Images'])
        if isinstance(im_dict, dict):
            for k, v in im_dict.items():
                if k in mdict['nx_meta']['EDS']:
                    del mdict['nx_meta']['EDS'][k]
                # this should work for 2D (spectrum image) as well as 1D
                # (linescan) datasets since DM saves this information as a 1D
                # list regardless of original data shape
                avg_val = _np.array(v).mean()
                _set_nest_dict_val(mdict,
                                   ['nx_meta', 'EDS'] + [f'{k} (SI Average)'],
                                   avg_val)

    # Add the .dm3 EDS values to the warnings list, since they might not be
    # accurate
    for m in [['Count rate'], ['Detector Info', 'Active layer'],
              ['Detector Info', 'Azimuthal angle'],
              ['Detector Info', 'Dead layer'],
              ['Detector Info', 'Detector type'],
              ['Detector Info', 'Elevation angle'], ['Detector Info', 'Fano'],
              ['Detector Info', 'Gold layer'],
              ['Detector Info', 'Incidence angle'],
              ['Detector Info', 'Solid angle'],
              ['Detector Info', 'Stage tilt'],
              ['Detector Info', 'Window thickness'],
              ['Detector Info', 'Window type'], ['Detector Info', 'Zero fwhm'],
              ['Live time'], ['Real time']]:
        if _try_get_dict_val(mdict, base + m) != 'not found':
            mdict['nx_meta']['warnings'].append(
                ['EDS'] + [m[-1] if len(m) > 1 else m[0]])

    # Set the dataset type to Spectrum if any EDS tags were added
    if 'EDS' in mdict['nx_meta']:
        _logger.info('Detected file as Spectrum type based on presence of '
                     'EDS metadata')
        mdict['nx_meta']['DatasetType'] = 'Spectrum'
        if 'STEM' in mdict['nx_meta']['Illumination Mode']:
            mdict['nx_meta']['Data Type'] = 'STEM_EDS'
        else:
            # no known files match this mode, so skip for coverage
            mdict['nx_meta']['Data Type'] = 'TEM_EDS'  # pragma: no cover

    return mdict
Example #13
0
def parse_dm3_eels_info(mdict):
    """
    Parses metadata from the DigitalMicrograph tag structure that concerns any
    EELS acquisition or spectrometer settings, placing it in an ``EELS``
    dictionary underneath the root-level ``nx_meta`` node.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_dm3_metadata`

    Returns
    -------
    mdict : dict
        The metadata dictionary with all the "EELS-specific" metadata added as
        sub-node under the ``nx_meta`` root level dictionary
    """
    pre_path = get_pre_path(mdict)

    # EELS .dm3 tags of interest:
    base = pre_path + ['EELS']
    for m in [['Acquisition', 'Exposure (s)'],
              ['Acquisition', 'Integration time (s)'],
              ['Acquisition', 'Number of frames'],
              ["Experimental Conditions", "Collection semi-angle (mrad)"],
              ["Experimental Conditions", "Convergence semi-angle (mrad)"]]:
        val = _try_get_dict_val(mdict, base + m)
        # only add the value to this list if we found it, and it's not
        # one of the "facility-wide" set values that do not have any meaning:
        if val != 'not found':
            # add last value of each parameter to the "EELS" sub-tree of nx_meta
            _set_nest_dict_val(mdict, ['nx_meta', 'EELS'] + [m[-1]], val)

    # different instruments have the spectrometer information in different
    # places...
    if mdict['nx_meta']['Instrument ID'] == '**REMOVED**':
        base = pre_path + ['EELS', 'Acquisition', 'Spectrometer']
    elif mdict['nx_meta']['Instrument ID'] == '**REMOVED**':
        base = pre_path + ['EELS Spectrometer']
    else:
        base = None
    if base is not None:
        for m in [
                "Aperture label", "Dispersion (eV/ch)", "Energy loss (eV)",
                "Instrument name", "Drift tube enabled",
                "Drift tube voltage (V)", "Slit inserted", "Slit width (eV)",
                "Prism offset (V)", "Prism offset enabled "
        ]:
            m = [m]
            val = _try_get_dict_val(mdict, base + m)
            if val != 'not found':
                # add last value of each param to the "EELS" sub-tree of nx_meta
                _set_nest_dict_val(mdict, ['nx_meta', 'EELS'] +
                                   ["Spectrometer " + m[0]], val)

    # Process known tags under "processing":
    #   ImageTags.Processing will be a list of things done (in multiple
    #   TagGroups) - things like Compute thickness, etc.
    val = _try_get_dict_val(mdict, pre_path + ['Processing'])
    if val != 'not found' and isinstance(val, dict):
        # if val is a dict, then there were processing steps applied
        eels_ops = []
        for k, v in val.items():
            # k will be TagGroup0, TagGroup1, etc.
            # v will be dictionaries specifying the process step
            # AlignSIByPeak, DataPicker, SpectrumCalibrate,
            # Compute Thickness, Background Removal, Signal Integration
            op = v['Operation']
            param = v['Parameters']
            if op == 'AlignSIByPeak':
                eels_ops.append('Aligned parent SI By Peak')
            elif op == 'Background Removal':
                val = _try_get_dict_val(param, ['Model'])
                if val != 'not found':
                    _set_nest_dict_val(
                        mdict, ['nx_meta', 'EELS', 'Background Removal Model'],
                        val)
                eels_ops.append(op)
            elif op == 'SpectrumCalibrate':
                eels_ops.append('Calibrated Post-acquisition')
            elif op == 'Compute Thickness':
                mdict = _process_thickness_metadata(mdict, pre_path + ['EELS'])
                eels_ops.append(op)
            elif op == 'DataPicker':
                eels_ops.append('Extracted from SI')
            elif op == 'Signal Integration':
                eels_ops.append(op)
        if eels_ops:
            # remove duplicates (convert to set) and sort alphabetically:
            _set_nest_dict_val(mdict, ['nx_meta', 'EELS', 'Processing Steps'],
                               ', '.join(sorted(set(eels_ops))))

    # Set the dataset type to Spectrum if any EELS tags were added
    if 'EELS' in mdict['nx_meta']:
        _logger.info('Detected file as Spectrum type based on presence of '
                     'EELS metadata')
        mdict['nx_meta']['DatasetType'] = 'Spectrum'
        if 'STEM' in mdict['nx_meta']['Illumination Mode']:
            mdict['nx_meta']['Data Type'] = 'STEM_EELS'
        else:
            mdict['nx_meta']['Data Type'] = 'TEM_EELS'

    return mdict
Example #14
0
def parse_dm3_microscope_info(mdict):
    """
    Parse the "important" metadata that is saved at specific places within
    the DM3 tag structure into a consistent place in the metadata dictionary
    returned by :py:meth:`get_dm3_metadata`. Specifically looks at the
    "Microscope Info", "Session Info", and "Meta Data" nodes of the tag
    structure (these are not present on every microscope)

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_dm3_metadata`

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    if 'nx_meta' not in mdict: mdict['nx_meta'] = {}

    pre_path = get_pre_path(mdict)

    # General "microscope info" .dm3 tags (not present on all instruments):
    for m in [
            'Indicated Magnification', 'Actual Magnification', 'Cs(mm)',
            'STEM Camera Length', 'Voltage', 'Operation Mode', 'Specimen',
            'Microscope', 'Operator', 'Imaging Mode', 'Illumination Mode',
            'Name', 'Field of View (\u00b5m)', 'Facility',
        ['Stage Position', 'Stage Alpha'], ['Stage Position', 'Stage Beta'],
        ['Stage Position', 'Stage X'], ['Stage Position', 'Stage Y'],
        ['Stage Position', 'Stage Z']
    ]:
        base = pre_path + ['Microscope Info']
        if isinstance(m, str):
            m = [m]
        val = _try_get_dict_val(mdict, base + m)
        # only add the value to this list if we found it, and it's not one of
        # the "facility-wide" set values that do not have any meaning:
        if val != 'not found' and val not in ['DO NOT EDIT', 'DO NOT ENTER'] \
                and val != []:
            # change output of "Stage Position" to unicode characters
            if 'Stage Position' in m:
                m[-1] = m[-1].replace('Alpha',
                                      'α').replace('Beta',
                                                   'β').replace('Stage ', '')
            _set_nest_dict_val(mdict, ['nx_meta'] + m, val)

    # General "session info" .dm3 tags (sometimes this information is stored
    # here instead of under "Microscope Info":
    for m in ['Detector', 'Microscope', 'Operator', 'Specimen']:
        base = pre_path + ['Session Info']
        if isinstance(m, str):
            m = [m]

        val = _try_get_dict_val(mdict, base + m)
        # only add the value to this list if we found it, and it's not
        # one of the "facility-wide" set values that do not have any meaning:
        if val != 'not found' and val not in ['DO NOT EDIT', 'DO NOT ENTER'] \
                and val != []:
            _set_nest_dict_val(mdict, ['nx_meta'] + m, val)

    # General "Meta Data" .dm3 tags
    for m in [
            'Acquisition Mode',
            'Format',
            'Signal',
            # this one is seen sometimes in EDS signals:
        ['Experiment keywords', 'TagGroup1', 'Label']
    ]:
        base = pre_path + ['Meta Data']
        if isinstance(m, str):
            m = [m]

        val = _try_get_dict_val(mdict, base + m)
        # only add the value to this list if we found it, and it's not
        # one of the "facility-wide" set values that do not have any meaning:
        if val != 'not found' and val not in ['DO NOT EDIT', 'DO NOT ENTER'] \
                and val != []:
            if 'Label' in m:
                _set_nest_dict_val(mdict, ['nx_meta'] + ['Analytic Label'],
                                   val)
            else:
                _set_nest_dict_val(mdict, ['nx_meta'] +
                                   [f'Analytic {lbl}' for lbl in m], val)

    # Get acquisition device name:
    val = _try_get_dict_val(mdict,
                            pre_path + ['Acquisition', 'Device', 'Name'])
    if val == 'not found':
        val = _try_get_dict_val(mdict, pre_path + ['DataBar', 'Device Name'])
    if val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Acquisition Device'], val)

    # Get exposure time:
    val = _try_get_dict_val(
        mdict,
        pre_path + ['Acquisition', 'Parameters', 'High Level', 'Exposure (s)'])
    if val == 'not found':
        val = _try_get_dict_val(mdict,
                                pre_path + ['DataBar', 'Exposure Time (s)'])
    if val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Exposure Time (s)'], val)

    # Get GMS version:
    val = _try_get_dict_val(mdict, pre_path + ['GMS Version', 'Created'])
    if val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'GMS Version'], val)

    # Get camera binning:
    val = _try_get_dict_val(
        mdict,
        pre_path + ['Acquisition', 'Parameters', 'High Level', 'Binning'])
    if val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Binning (Horizontal)'], val[0])
        _set_nest_dict_val(mdict, ['nx_meta', 'Binning (Vertical)'], val[1])

    # Get image processing:
    #   ImageTags.Acquisition.Parameters["High Level"].Processing will be
    #   something like "Gain normalized" - not just for EELS so move this to
    #   general
    val = _try_get_dict_val(
        mdict,
        pre_path + ['Acquisition', 'Parameters', 'High Level', 'Processing'])
    if val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Camera/Detector Processing'],
                           val)

    if 'Illumination Mode' in mdict['nx_meta']:
        if 'STEM' in mdict['nx_meta']['Illumination Mode']:
            mdict['nx_meta']['Data Type'] = 'STEM_Imaging'

    return mdict
Example #15
0
def parse_nx_meta(mdict):
    """
    Parse the "important" metadata that is saved at specific places within
    the Quanta tag structure into a consistent place in the metadata dictionary
    returned by :py:meth:`get_quanta_metadata`.

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_quanta_metadata`

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    # The name of the beam, scan, and detector will determine which sections are
    # present (have not seen more than one beam/detector -- although likely
    # will be the case for dual beam FIB/SEM)
    beam_name = _try_get_dict_val(mdict, ['Beam', 'Beam'])
    det_name = _try_get_dict_val(mdict, ['Detectors', 'Name'])
    scan_name = _try_get_dict_val(mdict, ['Beam', 'Scan'])

    # some parsers are broken off into helper methods:
    if beam_name != 'not found':
        mdict = parse_beam_info(mdict, beam_name)
    if scan_name != 'not found':
        mdict = parse_scan_info(mdict, scan_name)
    if det_name != 'not found':
        mdict = parse_det_info(mdict, det_name)
    if _try_get_dict_val(mdict, ['System']) != 'not found':
        mdict = parse_system_info(mdict)

    # process the rest of the metadata tags:

    # process beam spot size
    val = _try_get_dict_val(mdict, ['Beam', 'Spot'])
    if val != 'not found':
        try:
            val = _Decimal(val)
        except (ValueError, _invalidOp):
            pass
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Spot Size'],
                           float(val) if isinstance(val, _Decimal) else val)

    # process drift correction
    val = _try_get_dict_val(mdict, ['Image', 'DriftCorrected'])
    if val != 'not found':
        # set to true if the value is 'On'
        val = val == 'On'
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Drift Correction Applied'],
                           val)

    # process frame integration
    val = _try_get_dict_val(mdict, ['Image', 'Integrate'])
    if val != 'not found':
        try:
            val = int(val)
            if val > 1:
                _set_nest_dict_val(mdict, ['nx_meta'] + ['Frames Integrated'],
                                   val)
        except ValueError:
            pass

    # process mag mode
    val = _try_get_dict_val(mdict, ['Image', 'MagnificationMode'])
    if val != 'not found':
        try:
            val = int(val)
        except ValueError:
            pass
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Magnification Mode'], val)

    # Process "ResolutionX/Y" (data size)
    x_val = _try_get_dict_val(mdict, ['Image', 'ResolutionX'])
    y_val = _try_get_dict_val(mdict, ['Image', 'ResolutionY'])
    try:
        x_val = int(x_val)
        y_val = int(y_val)
    except ValueError:
        pass
    if x_val != 'not found' and y_val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Data Dimensions'],
                           str((x_val, y_val)))

    # test for specimen temperature value if present and non-empty
    temp_val = _try_get_dict_val(mdict, ['Specimen', 'Temperature'])
    if temp_val != 'not found' and temp_val != '':
        try:
            temp_val = _Decimal(temp_val)
        except (ValueError, _invalidOp):
            pass
        _set_nest_dict_val(
            mdict, ['nx_meta', 'Specimen Temperature (K)'],
            float(temp_val) if isinstance(temp_val, _Decimal) else temp_val)

    # # parse SpecTilt (think this is specimen pre-tilt, but not definite)
    # # tests showed that this is always the same value as StageT, so we do not
    # # need to parse this one
    # val = _try_get_dict_val(mdict, ['Stage', 'SpecTilt'])
    # if val != 'not found' and val != '0':
    #     _set_nest_dict_val(mdict, ['nx_meta', 'Stage Position',
    #                                'Specimen Tilt'], val)

    # Get user ID (sometimes it's not correct because the person left the
    # instrument logged in as the previous user, so make sure to add it to
    # the warnings list
    user_val = _try_get_dict_val(mdict, ['User', 'User'])
    if user_val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Operator'], user_val)
        mdict['nx_meta']['warnings'].append(['Operator'])

    # parse acquisition date and time
    acq_date_val = _try_get_dict_val(mdict, ['User', 'Date'])
    acq_time_val = _try_get_dict_val(mdict, ['User', 'Time'])
    if acq_date_val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Acquisition Date'],
                           acq_date_val)
    if acq_time_val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Acquisition Time'],
                           acq_time_val)

    # parse vacuum mode
    vac_val = _try_get_dict_val(mdict, ['Vacuum', 'UserMode'])
    if user_val != 'not found':
        _set_nest_dict_val(mdict, ['nx_meta', 'Vacuum Mode'], vac_val)

    # parse chamber pressure
    ch_pres_val = _try_get_dict_val(mdict, ['Vacuum', 'ChPressure'])
    if ch_pres_val != 'not found' and ch_pres_val != '':
        # keep track of original digits so we don't propagate float errors
        try:
            ch_pres_val = _Decimal(ch_pres_val)
        except _invalidOp:
            ch_pres_val = ch_pres_val
        if _try_get_dict_val(mdict,
                             ['nx_meta', 'Vacuum Mode']) == 'High vacuum':
            ch_pres_str = 'Chamber Pressure (mPa)'
            ch_pres_val = ch_pres_val * 10**3
        else:
            ch_pres_str = 'Chamber Pressure (Pa)'
            ch_pres_val = ch_pres_val
        _set_nest_dict_val(
            mdict, ['nx_meta', ch_pres_str],
            float(ch_pres_val)
            if isinstance(ch_pres_val, _Decimal) else ch_pres_val)

    return mdict
Example #16
0
def parse_642_titan(mdict):
    """
    Add/adjust metadata specific to the 642 FEI Titan
    ('`**REMOVED** in **REMOVED**`') to the metadata dictionary

    Parameters
    ----------
    mdict : dict
        "raw" metadata dictionary as parsed by :py:func:`get_dm3_metadata`

    Returns
    -------
    mdict : dict
        The original metadata dictionary with added information specific to
        files originating from this microscope with "important" values contained
        under the ``nx_meta`` key at the root level
    """
    # DONE: complete 642 titan metadata parsing including Tecnai tag
    path_to_tecnai = _get_nest_dict_key(mdict, 'Tecnai')

    if path_to_tecnai is None:
        # For whatever reason, the expected Tecnai Tag is not present,
        # so return to prevent errors below
        return mdict

    tecnai_value = _get_nest_dict_val_by_path(mdict, path_to_tecnai)
    microscope_info = tecnai_value['Microscope Info']
    tecnai_value['Microscope Info'] = \
        process_tecnai_microscope_info(microscope_info)
    _set_nest_dict_val(mdict, path_to_tecnai, tecnai_value)

    # - Tecnai info:
    #     - ImageTags.Tecnai.Microscope_Info['Gun_Name']
    #     - ImageTags.Tecnai.Microscope_Info['Extractor_Voltage']
    #     - ImageTags.Tecnai.Microscope_Info['Gun_Lens_No']
    #     - ImageTags.Tecnai.Microscope_Info['Emission_Current']
    #     - ImageTags.Tecnai.Microscope_Info['Spot']
    #     - ImageTags.Tecnai.Microscope_Info['Mode']
    #     - C2, C3, Obj, Dif lens strength:
    #         - ImageTags.Tecnai.Microscope_Info['C2_Strength',
    #                                            'C3_Strength',
    #                                            'Obj_Strength',
    #                                            'Dif_Strength']
    #     - ImageTags.Tecnai.Microscope_Info['Image_Shift_x'/'Image_Shift_y'])
    #     - ImageTags.Tecnai.Microscope_Info['Stage_Position_x' (y/z/theta/phi)]
    #     - C1/C2/Objective/SA aperture sizes:
    #         - ImageTags.Tecnai.Microscope_Info['(C1/C2/Obj/SA)_Aperture']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings']['Mode']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings']['Dispersion']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings']['Aperture']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings']['Prism_Shift']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings']['Drift_Tube']
    #     - ImageTags.Tecnai.Microscope_Info['Filter_Settings'][
    #           'Total_Energy_Loss']

    term_mapping = {
        'Gun_Name':
        'Gun Name',
        'Extractor_Voltage':
        'Extractor Voltage (V)',
        'Camera_Length':
        'Camera Length (m)',
        'Gun_Lens_No':
        'Gun Lens #',
        'Emission_Current':
        'Emission Current (μA)',
        'Spot':
        'Spot',
        'Mode':
        'Tecnai Mode',
        'Defocus':
        'Defocus',
        'C2_Strength':
        'C2 Lens Strength (%)',
        'C3_Strength':
        'C3 Lens Strength (%)',
        'Obj_Strength':
        'Objective Lens Strength (%)',
        'Dif_Strength':
        'Diffraction Lens Strength (%)',
        'Microscope_Name':
        'Tecnai Microscope Name',
        'User':
        '******',
        'Image_Shift_x':
        'Image Shift X (μm)',
        'Image_Shift_y':
        'Image Shift Y (μm)',
        'Stage_Position_x': ['Stage Position', 'X (μm)'],
        'Stage_Position_y': ['Stage Position', 'Y (μm)'],
        'Stage_Position_z': ['Stage Position', 'Z (μm)'],
        'Stage_Position_theta': ['Stage Position', 'θ (°)'],
        'Stage_Position_phi': ['Stage Position', 'φ (°)'],
        'C1_Aperture':
        'C1 Aperture (μm)',
        'C2_Aperture':
        'C2 Aperture (μm)',
        'Obj_Aperture':
        'Objective Aperture (μm)',
        'SA_Aperture':
        'Selected Area Aperture (μm)',
        ('Filter_Settings', 'Mode'): ['Tecnai Filter', 'Mode'],
        ('Filter_Settings', 'Dispersion'):
        ['Tecnai Filter', 'Dispersion (eV/channel)'],
        ('Filter_Settings', 'Aperture'): ['Tecnai Filter', 'Aperture (mm)'],
        ('Filter_Settings', 'Prism_Shift'):
        ['Tecnai Filter', 'Prism Shift (eV)'],
        ('Filter_Settings', 'Drift_Tube'):
        ['Tecnai Filter', 'Drift Tube (eV)'],
        ('Filter_Settings', 'Total_Energy_Loss'):
        ['Tecnai Filter', 'Total Energy Loss (eV)'],
    }

    for in_term in term_mapping.keys():
        base = list(path_to_tecnai) + ['Microscope Info']
        out_term = term_mapping[in_term]
        if isinstance(in_term, str):
            in_term = [in_term]
        elif isinstance(in_term, tuple):
            in_term = list(in_term)
        if isinstance(out_term, str):
            out_term = [out_term]
        val = _try_get_dict_val(mdict, base + in_term)
        # only add the value to this list if we found it
        if val != 'not found' and val not in ['DO NOT EDIT', 'DO NOT ENTER']:
            _set_nest_dict_val(mdict, ['nx_meta'] + out_term, val)

    path = list(path_to_tecnai) + ['Specimen Info']
    val = _try_get_dict_val(mdict, path)
    if val != 'not found' and \
            val != 'Specimen information is not available yet':
        _set_nest_dict_val(mdict, ['nx_meta', 'Specimen'], val)

    # If `Tecnai Mode` is `STEM nP SA Zoom Diffraction`, it's diffraction
    if 'Tecnai Mode' in mdict['nx_meta'] and \
            mdict['nx_meta']['Tecnai Mode'] == 'STEM nP SA Zoom Diffraction':
        _logger.info('Detected file as Diffraction type based on "Tecnai '
                     'Mode" == "STEM nP SA Zoom Diffraction"')
        mdict['nx_meta']['DatasetType'] = 'Diffraction'
        mdict['nx_meta']['Data Type'] = 'STEM_Diffraction'

    # also, if `Operation Mode` is `DIFFRACTION`, it's diffraction
    elif 'Operation Mode' in mdict['nx_meta'] and \
            mdict['nx_meta']['Operation Mode'] == 'DIFFRACTION':
        _logger.info('Detected file as Diffraction type based on "Operation '
                     'Mode" == "DIFFRACTION"')
        mdict['nx_meta']['DatasetType'] = 'Diffraction'
        mdict['nx_meta']['Data Type'] = 'TEM_Diffraction'

    return mdict
Example #17
0
def parse_beam_info(mdict, beam_name):
    """

    Parameters
    ----------
    mdict : dict
        A metadata dictionary as returned by :py:meth:`get_quanta_metadata`
    beam_name : str
        The "beam name" read from the root-level ``Beam`` node of the
        metadata dictionary

    Returns
    -------
    mdict : dict
        The same metadata dictionary with some values added under the
        root-level ``nx_meta`` key
    """
    # Values are in SI units, but we want easy to display, so include the
    # exponential factor that will get us from input unit (such as seconds)
    # to output unit (such as μs -- meaning factor = 6)
    to_parse = [
        ([beam_name, 'EmissionCurrent'], ['Emission Current (μA)'], 6),
        ([beam_name, 'HFW'], ['Horizontal Field Width (μm)'], 6),
        ([beam_name, 'HV'], ['Voltage (kV)'], -3),
        ([beam_name, 'SourceTiltX'], ['Beam Tilt X'], 0),
        ([beam_name, 'SourceTiltY'], ['Beam Tilt Y'], 0),
        ([beam_name, 'StageR'], ['Stage Position', 'R'], 0),
        ([beam_name, 'StageTa'], ['Stage Position', 'α'], 0),
        # all existing quanta images have a value of zero for beta
        # ([beam_name, 'StageTb'], ['Stage Position', 'β'], 0),
        ([beam_name, 'StageX'], ['Stage Position', 'X'], 0),
        ([beam_name, 'StageY'], ['Stage Position', 'Y'], 0),
        ([beam_name, 'StageZ'], ['Stage Position', 'Z'], 0),
        ([beam_name, 'StigmatorX'], ['Stigmator X Value'], 0),
        ([beam_name, 'StigmatorY'], ['Stigmator Y Value'], 0),
        ([beam_name, 'VFW'], ['Vertical Field Width (μm)'], 6),
        ([beam_name, 'WD'], ['Working Distance (mm)'], 3),
    ]
    for m_in, m_out, factor in to_parse:
        val = _try_get_dict_val(mdict, m_in)
        if val != 'not found' and val != '':
            val = _Decimal(val) * _Decimal(str(10**factor))
            _set_nest_dict_val(
                mdict, ['nx_meta'] + m_out,
                float(val) if isinstance(val, _Decimal) else val)

    # Add beam name to metadata:
    _set_nest_dict_val(mdict, ['nx_meta'] + ['Beam Name'], beam_name)

    # BeamShiftX and BeamShiftY require an additional test:
    bs_x_val = _try_get_dict_val(mdict, [beam_name, 'BeamShiftX'])
    bs_y_val = _try_get_dict_val(mdict, [beam_name, 'BeamShiftY'])
    if bs_x_val != 'not found' and _Decimal(bs_x_val) != 0:
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Beam Shift X'],
                           float(_Decimal(bs_x_val)))
    if bs_y_val != 'not found' and _Decimal(bs_y_val) != 0:
        _set_nest_dict_val(mdict, ['nx_meta'] + ['Beam Shift Y'],
                           float(_Decimal(bs_y_val)))

    # only parse scan rotation if value is not zero:
    # Not sure what the units of this value are... looks like radians because
    # unique values range from 0 to 6.24811 - convert to degrees for display
    scan_rot_val = _try_get_dict_val(mdict, [beam_name, 'ScanRotation'])
    if scan_rot_val != 'not found' and _Decimal(scan_rot_val) != 0:
        scan_rot_dec = _Decimal(scan_rot_val)  # make scan_rot a Decimal
        # get number of digits in Decimal value (so we don't artificially
        # introduce extra precision)
        digits = abs(scan_rot_dec.as_tuple().exponent)
        # round the final float value to that number of digits
        scan_rot_val = round(degrees(scan_rot_dec), digits)
        _set_nest_dict_val(mdict, ['nx_meta', 'Scan Rotation (°)'],
                           scan_rot_val)

    # TiltCorrectionAngle only if TiltCorrectionIsOn == 'yes'
    tilt_corr_on = _try_get_dict_val(mdict, [beam_name, 'TiltCorrectionIsOn'])
    if tilt_corr_on == 'yes':
        tilt_corr_val = _try_get_dict_val(mdict,
                                          [beam_name, 'TiltCorrectionAngle'])
        if tilt_corr_val != 'not found':
            tilt_corr_val = float(_Decimal(tilt_corr_val))
            _set_nest_dict_val(mdict, ['nx_meta'] + ['Tilt Correction Angle'],
                               tilt_corr_val)

    return mdict