Пример #1
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
Пример #2
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
Пример #3
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
Пример #4
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
Пример #5
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
Пример #6
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
Пример #7
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
Пример #8
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
Пример #9
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
Пример #10
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
Пример #11
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
Пример #12
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