def load_spec(scan_id, spec_file, folder=""):
    """
    Load data from spec file.

    If `spec_file` is the file name, it will load the spec file internally
    which is time consuming.

    Parameters
    ----------
    scan_id : int
        Scan_id of the scan to be retrieved.
    spec_file : string or spec2nexus.spec.SpecDataFile
        Either the spec file name or a SpecDataFile instance.
    folder : string, optional
        Folder where spec file is located.

    Returns
    -------
    data : pandas.DataFrame
        Table with the data from scan.

    See also
    --------
    :func:`spec2nexus.spec.SpecDataFile`
    """

    if isinstance(spec_file, str):
        path = join(folder, spec_file)
        spec_file = SpecDataFile(path)

    return DataFrame(spec_file.getScan(scan_id).data)
Exemple #2
0
    def testName(self):
        spec_data = SpecDataFile(self.fname)
        self.assertTrue(isinstance(spec_data, SpecDataFile))
        scan = spec_data.getScan(1)

        with self.assertRaises(spec2nexus.plugins.uxml.UXML_Error) as context:
            scan.interpret()
        received = str(context.exception)
        expected = "UXML error: Element 'group': "
        expected += "Character content other than whitespace is not allowed "
        expected += "because the content type is 'element-only'."
        self.assertTrue(received.startswith(expected))
Exemple #3
0
class Parser(object):
    '''parse the spec data file object'''
    def __init__(self, spec_data=None):
        ''':param obj spec_data: instance of :class:`spec2nexus.prjPySpec.SpecDataFile`'''
        self.SPECfile = spec_data
        self.progress_bar = spec_data.progress_bar
        self.update_progress = spec_data.update_progress

    def openFile(self, filename):
        '''open the SPEC file and get its data'''
        from spec2nexus.spec import SpecDataFile
        if os.path.exists(filename):
            self.SPECfile = SpecDataFile(filename)

    def toTree(self, scan_list=[]):
        '''
        convert scans from chosen SPEC file into NXroot object and structure
        
        called from nexpy.readers.readspec.ImportDialog.get_data__prjPySpec() after clicking <Ok> in dialog
        
        Each scan in the range from self.scanmin to self.scanmax (inclusive)
        will be converted to a NXentry.  Scan data will go in a NXdata where 
        the signal=1 is the last column and the corresponding axes= is the first column.
        
        :param [int] scanlist
        :raises: ValueError is Min or Max scan number are not given properly
        '''
        import spec2nexus
        from spec2nexus import utils
        # check that scan_list is valid
        if len(scan_list) == 0:
            return None

        if self.SPECfile is None:
            return None

        complete_scan_list = list(self.SPECfile.scans)
        for key in [str(s) for s in scan_list]:
            if key not in complete_scan_list:
                msg = 'scan ' + str(key) + ' was not found'
                raise ValueError(msg)

        root = NXroot()

        root.attrs['spec2nexus'] = str(spec2nexus.__version__)
        header0 = self.SPECfile.headers[0]
        root.attrs['SPEC_file'] = self.SPECfile.fileName
        root.attrs['SPEC_epoch'] = header0.epoch
        root.attrs['SPEC_date'] = utils.iso8601(header0.date)
        root.attrs['SPEC_comments'] = '\n'.join(header0.comments)
        try:
            c = header0.comments[0]
            user = c[c.find('User = '******'=')[1].strip()
            root.attrs['SPEC_user'] = user
        except:
            pass
        root.attrs['SPEC_num_headers'] = len(self.SPECfile.headers)

        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(scan_list[0], scan_list[-1])
        for key in [str(s) for s in scan_list]:
            scan = self.SPECfile.getScan(key)
            scan.interpret()
            entry = NXentry()
            entry.title = str(scan)
            entry.date = utils.iso8601(scan.date)
            entry.command = scan.scanCmd
            entry.scan_number = NXfield(scan.scanNum)
            entry.comments = '\n'.join(scan.comments)
            entry.data = self.scan_NXdata(scan)  # store the scan data
            entry.positioners = self.metadata_NXlog(
                scan.positioner, 'SPEC positioners (#P & #O lines)')
            if hasattr(scan, 'metadata') and len(scan.metadata) > 0:
                entry.metadata = self.metadata_NXlog(
                    scan.metadata,
                    'SPEC metadata (UNICAT-style #H & #V lines)')

            if len(scan.G) > 0:
                entry.G = NXlog()
                desc = "SPEC geometry arrays, meanings defined by SPEC diffractometer support"
                # e.g.: SPECD/four.mac
                # http://certif.com/spec_manual/fourc_4_9.html
                entry.G.attrs['description'] = desc
                for item, value in scan.G.items():
                    entry.G[item] = NXfield(list(map(float, value.split())))
            if scan.T != '':
                entry['counting_basis'] = NXfield(
                    'SPEC scan with constant counting time')
                entry['T'] = NXfield(float(scan.T))
                entry['T'].units = 'seconds'
                entry[
                    'T'].description = 'SPEC scan with constant counting time'
            elif scan.M != '':
                entry['counting_basis'] = NXfield(
                    'SPEC scan with constant monitor count')
                entry['M'] = NXfield(float(scan.M))
                entry['M'].units = 'counts'
                entry[
                    'M'].description = 'SPEC scan with constant monitor count'
            if scan.Q != '':
                entry['Q'] = NXfield(list(map(float, scan.Q)))
                entry['Q'].description = 'hkl at start of scan'

            root['scan_' + str(key)] = entry

            self.progress_bar.setValue(int(key))
            self.update_progress()

        return root

    def scan_NXdata(self, scan):
        '''
        return the scan data in an NXdata object
        '''
        nxdata = NXdata()

        if len(scan.data) == 0:  # what if no data?
            # since no data available, provide trivial, fake data
            # this keeps the NXdata base class compliant with the NeXus standard
            nxdata.attrs['description'] = 'SPEC scan has no data'
            nxdata['noSpecData_y'] = NXfield([0, 0])  # primary Y axis
            nxdata['noSpecData_x'] = NXfield([0, 0])  # primary X axis
            nxdata.nxsignal = nxdata['noSpecData_y']
            nxdata.nxaxes = [
                nxdata['noSpecData_x'],
            ]
            return nxdata

        nxdata.attrs['description'] = 'SPEC scan data'

        scan_type = scan.scanCmd.split()[0]
        if scan_type in ('mesh', 'hklmesh'):
            # hklmesh  H 1.9 2.1 100  K 1.9 2.1 100  -800000
            self.parser_mesh(nxdata, scan)
        elif scan_type in ('hscan', 'kscan', 'lscan', 'hklscan'):
            # hklscan  1.00133 1.00133  1.00133 1.00133  2.85 3.05  200 -400000
            h_0, h_N, k_0, k_N, l_0, l_N = scan.scanCmd.split()[1:7]
            if h_0 != h_N: axis = 'H'
            elif k_0 != k_N: axis = 'K'
            elif l_0 != l_N: axis = 'L'
            else: axis = 'H'
            self.parser_1D_columns(nxdata, scan)
            nxdata.nxaxes = nxdata[axis]
        else:
            self.parser_1D_columns(nxdata, scan)

        # these locations suggested to NIAC, easier to parse than attached to dataset!
        # but these are already set by the `nxsignal` and `nxaxes` assignments
        #nxdata.attrs['signal'] = nxdata.nxsignal.nxname
        #nxdata.attrs['axes'] = ':'.join([obj.nxname for obj in nxdata.nxaxes])
        return nxdata

    def parser_1D_columns(self, nxdata, scan):
        '''generic data parser for 1-D column data'''
        from spec2nexus import utils
        for column in scan.L:
            if column in scan.data:
                clean_name = utils.sanitize_name(nxdata, column)
                nxdata[clean_name] = NXfield(scan.data[column])
                nxdata[clean_name].original_name = column

        signal = utils.sanitize_name(nxdata,
                                     scan.column_last)  # primary Y axis
        axis = utils.sanitize_name(nxdata, scan.column_first)  # primary X axis
        nxdata.nxsignal = nxdata[signal]
        nxdata.nxaxes = nxdata[axis]

        self.parser_mca_spectra(nxdata, scan, axis)

    def parser_mca_spectra(self, nxdata, scan, primary_axis_label):
        '''parse for optional MCA spectra'''
        if '_mca_' in scan.data:  # check for it
            for mca_key, mca_data in scan.data['_mca_'].items():
                key = "__" + mca_key
                nxdata[key] = NXfield(mca_data)
                nxdata[key].units = "counts"
                ch_key = key + "_channel"
                nxdata[ch_key] = NXfield(range(1, len(mca_data[0]) + 1))
                nxdata[ch_key].units = 'channel'
                axes = (primary_axis_label, ch_key)
                nxdata[key].axes = ':'.join(axes)

    def parser_mesh(self, nxdata, scan):
        '''data parser for 2-D mesh and hklmesh'''
        # 2-D parser: http://www.certif.com/spec_help/mesh.html
        #  mesh motor1 start1 end1 intervals1 motor2 start2 end2 intervals2 time
        # 2-D parser: http://www.certif.com/spec_help/hklmesh.html
        #  hklmesh Q1 start1 end1 intervals1 Q2 start2 end2 intervals2 time
        # mesh:    nexpy/examples/33id_spec.dat  scan 22  (also has MCA, thus 3-D data)
        # hklmesh: nexpy/examples/33bm_spec.dat  scan 17  (no MCA data)
        from spec2nexus import utils
        label1, start1, end1, intervals1, label2, start2, end2, intervals2, time = scan.scanCmd.split(
        )[1:]
        if label1 not in scan.data:
            label1 = scan.L[0]  # mnemonic v. name
        if label2 not in scan.data:
            label2 = scan.L[1]  # mnemonic v. name
        axis1 = scan.data.get(label1)
        axis2 = scan.data.get(label2)
        intervals1, intervals2 = int(intervals1), int(intervals2)
        start1, end1 = float(start1), float(end1)
        start2, end2 = float(start2), float(end2)
        time = float(time)
        if len(axis1) < intervals1:  # stopped scan before second row started
            self.parser_1D_columns(nxdata, scan)  # fallback support
            # TODO: what about the MCA data in this case?
        else:
            axis1 = axis1[0:intervals1 + 1]
            axis2 = [
                axis2[row] for row in range(len(axis2))
                if row % (intervals1 + 1) == 0
            ]

            column_labels = scan.L
            column_labels.remove(label1)  # special handling
            column_labels.remove(label2)  # special handling
            if scan.scanCmd.startswith('hkl'):
                # find the reciprocal space axis held constant
                label3 = [
                    key for key in ('H', 'K', 'L')
                    if key not in (label1, label2)
                ][0]
                axis3 = scan.data.get(label3)[0]
                nxdata[label3] = NXfield(axis3)
                column_labels.remove(label3)  # already handled

            nxdata[label1] = NXfield(axis1)  # 1-D array
            nxdata[label2] = NXfield(axis2)  # 1-D array

            # build 2-D data objects (do not build label1, label2, [or label3] as 2-D objects)
            data_shape = [len(axis2), len(axis1)]
            for label in column_labels:
                axis = np.array(scan.data.get(label))
                clean_name = utils.sanitize_name(nxdata, label)
                nxdata[clean_name] = NXfield(
                    utils.reshape_data(axis, data_shape))
                nxdata[clean_name].original_name = label

            signal_axis_label = utils.sanitize_name(nxdata, scan.column_last)
            nxdata.nxsignal = nxdata[signal_axis_label]
            nxdata.nxaxes = [nxdata[label2], nxdata[label1]]

        if '_mca_' in scan.data:  # 3-D array
            # TODO: ?merge with parser_mca_spectra()?
            for mca_key, mca_data in scan.data['_mca_'].items():
                key = "__" + mca_key

                spectra_lengths = list(map(len, mca_data))
                num_channels = max(spectra_lengths)
                if num_channels != min(spectra_lengths):
                    msg = 'MCA spectra have different lengths'
                    msg += ' in scan #' + str(scan.scanNum)
                    msg += ' in file ' + str(scan.specFile)
                    raise ValueError(msg)

                data_shape += [
                    num_channels,
                ]
                mca = np.array(mca_data)
                nxdata[key] = NXfield(utils.reshape_data(mca, data_shape))
                nxdata[key].units = "counts"

                try:
                    # use MCA channel numbers as known at time of scan
                    chan1 = scan.MCA['first_saved']
                    chanN = scan.MCA['last_saved']
                    channel_range = range(chan1, chanN + 1)
                except:
                    # basic indices
                    channel_range = range(1, num_channels + 1)

                ch_key = key + "_channel"
                nxdata[ch_key] = NXfield(channel_range)
                nxdata[ch_key].units = 'channel'
                axes = (label1, label2, ch_key)
                nxdata[key].axes = ':'.join(axes)

    def metadata_NXlog(self, spec_metadata, description):
        '''
        return the specific metadata in an NXlog object
        '''
        from spec2nexus import utils
        nxlog = NXlog()
        nxlog.attrs['description'] = description
        for subkey, value in spec_metadata.items():
            clean_name = utils.sanitize_name(nxlog, subkey)
            nxlog[clean_name] = NXfield(value)
            nxlog[clean_name].original_name = subkey
        return nxlog
Exemple #4
0
class Parser(object):
    """parse the spec data file object"""

    def __init__(self, spec_data=None):
        """:param obj spec_data: instance of :class:`spec2nexus.prjPySpec.SpecDataFile`"""
        self.SPECfile = spec_data
        self.progress_bar = spec_data.progress_bar
        self.update_progress = spec_data.update_progress

    def openFile(self, filename):
        """open the SPEC file and get its data"""
        from spec2nexus.prjPySpec import SpecDataFile

        if os.path.exists(filename):
            self.SPECfile = SpecDataFile(filename)

    def toTree(self, scan_list=[]):
        """
        convert scans from chosen SPEC file into NXroot object and structure
        
        called from nexpy.readers.readspec.ImportDialog.get_data__prjPySpec() after clicking <Ok> in dialog
        
        Each scan in the range from self.scanmin to self.scanmax (inclusive)
        will be converted to a NXentry.  Scan data will go in a NXdata where 
        the signal=1 is the last column and the corresponding axes= is the first column.
        
        :param [int] scanlist
        :raises: ValueError is Min or Max scan number are not given properly
        """
        import spec2nexus
        from spec2nexus import utils

        # check that scan_list is valid
        if len(scan_list) == 0:
            return None

        if self.SPECfile is None:
            return None

        complete_scan_list = self.SPECfile.scans.keys()
        for key in scan_list:
            if key not in complete_scan_list:
                msg = "scan " + str(key) + " was not found"
                raise ValueError, msg

        root = NXroot()

        root.attrs["spec2nexus"] = str(spec2nexus.__version__)
        header0 = self.SPECfile.headers[0]
        root.attrs["SPEC_file"] = self.SPECfile.fileName
        root.attrs["SPEC_epoch"] = header0.epoch
        root.attrs["SPEC_date"] = utils.iso8601(header0.date)
        root.attrs["SPEC_comments"] = "\n".join(header0.comments)
        try:
            c = header0.comments[0]
            user = c[c.find("User = "******"=")[1].strip()
            root.attrs["SPEC_user"] = user
        except:
            pass
        root.attrs["SPEC_num_headers"] = len(self.SPECfile.headers)

        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(scan_list[0], scan_list[-1])
        for key in scan_list:
            scan = self.SPECfile.getScan(key)
            scan.interpret()
            entry = NXentry()
            entry.title = str(scan)
            entry.date = utils.iso8601(scan.date)
            entry.command = scan.scanCmd
            entry.scan_number = NXfield(scan.scanNum)
            entry.comments = "\n".join(scan.comments)
            entry.data = self.scan_NXdata(scan)  # store the scan data
            entry.positioners = self.metadata_NXlog(scan.positioner, "SPEC positioners (#P & #O lines)")
            if hasattr(scan, "metadata") and len(scan.metadata) > 0:
                entry.metadata = self.metadata_NXlog(scan.metadata, "SPEC metadata (UNICAT-style #H & #V lines)")

            if len(scan.G) > 0:
                entry.G = NXlog()
                desc = "SPEC geometry arrays, meanings defined by SPEC diffractometer support"
                # e.g.: SPECD/four.mac
                # http://certif.com/spec_manual/fourc_4_9.html
                entry.G.attrs["description"] = desc
                for item, value in scan.G.items():
                    entry.G[item] = NXfield(map(float, value.split()))
            if scan.T != "":
                entry["counting_basis"] = NXfield("SPEC scan with constant counting time")
                entry["T"] = NXfield(float(scan.T))
                entry["T"].units = "seconds"
                entry["T"].description = "SPEC scan with constant counting time"
            elif scan.M != "":
                entry["counting_basis"] = NXfield("SPEC scan with constant monitor count")
                entry["M"] = NXfield(float(scan.M))
                entry["M"].units = "counts"
                entry["M"].description = "SPEC scan with constant monitor count"
            if scan.Q != "":
                entry["Q"] = NXfield(map(float, scan.Q))
                entry["Q"].description = "hkl at start of scan"

            root["scan_" + str(key)] = entry

            self.progress_bar.setValue(key)
            self.update_progress()

        return root

    def scan_NXdata(self, scan):
        """
        return the scan data in an NXdata object
        """
        nxdata = NXdata()

        if len(scan.data) == 0:  # what if no data?
            # since no data available, provide trivial, fake data
            # this keeps the NXdata base class compliant with the NeXus standard
            nxdata.attrs["description"] = "SPEC scan has no data"
            nxdata["noSpecData_y"] = NXfield([0, 0])  # primary Y axis
            nxdata["noSpecData_x"] = NXfield([0, 0])  # primary X axis
            nxdata.nxsignal = nxdata["noSpecData_y"]
            nxdata.nxaxes = [nxdata["noSpecData_x"]]
            return nxdata

        nxdata.attrs["description"] = "SPEC scan data"

        scan_type = scan.scanCmd.split()[0]
        if scan_type in ("mesh", "hklmesh"):
            # hklmesh  H 1.9 2.1 100  K 1.9 2.1 100  -800000
            self.parser_mesh(nxdata, scan)
        elif scan_type in ("hscan", "kscan", "lscan", "hklscan"):
            # hklscan  1.00133 1.00133  1.00133 1.00133  2.85 3.05  200 -400000
            h_0, h_N, k_0, k_N, l_0, l_N = scan.scanCmd.split()[1:7]
            if h_0 != h_N:
                axis = "H"
            elif k_0 != k_N:
                axis = "K"
            elif l_0 != l_N:
                axis = "L"
            else:
                axis = "H"
            self.parser_1D_columns(nxdata, scan)
            nxdata.nxaxes = nxdata[axis]
        else:
            self.parser_1D_columns(nxdata, scan)

        # these locations suggested to NIAC, easier to parse than attached to dataset!
        nxdata.attrs["signal"] = nxdata.nxsignal.nxname
        nxdata.attrs["axes"] = ":".join([obj.nxname for obj in nxdata.nxaxes])
        return nxdata

    def parser_1D_columns(self, nxdata, scan):
        """generic data parser for 1-D column data"""
        from spec2nexus import utils

        for column in scan.L:
            if column in scan.data:
                clean_name = utils.sanitize_name(nxdata, column)
                nxdata[clean_name] = NXfield(scan.data[column])
                nxdata[clean_name].original_name = column

        signal = utils.sanitize_name(nxdata, scan.column_last)  # primary Y axis
        axis = utils.sanitize_name(nxdata, scan.column_first)  # primary X axis
        nxdata.nxsignal = nxdata[signal]
        nxdata.nxaxes = nxdata[axis]

        self.parser_mca_spectra(nxdata, scan, axis)

    def parser_mca_spectra(self, nxdata, scan, primary_axis_label):
        """parse for optional MCA spectra"""
        if "_mca_" in scan.data:  # check for it
            nxdata.mca__spectrum_ = NXfield(scan.data["_mca_"])
            nxdata.mca__spectrum_channel = NXfield(range(1, len(scan.data["_mca_"][0]) + 1))
            nxdata.mca__spectrum_channel.units = "channel"
            axes = (primary_axis_label, "mca__spectrum_channel")
            nxdata.mca__spectrum_.axes = ":".join(axes)

    def parser_mesh(self, nxdata, scan):
        """data parser for 2-D mesh and hklmesh"""
        # 2-D parser: http://www.certif.com/spec_help/mesh.html
        #  mesh motor1 start1 end1 intervals1 motor2 start2 end2 intervals2 time
        # 2-D parser: http://www.certif.com/spec_help/hklmesh.html
        #  hklmesh Q1 start1 end1 intervals1 Q2 start2 end2 intervals2 time
        # mesh:    nexpy/examples/33id_spec.dat  scan 22  (also has MCA, thus 3-D data)
        # hklmesh: nexpy/examples/33bm_spec.dat  scan 17  (no MCA data)
        from spec2nexus import utils

        label1, start1, end1, intervals1, label2, start2, end2, intervals2, time = scan.scanCmd.split()[1:]
        if label1 not in scan.data:
            label1 = scan.L[0]  # mnemonic v. name
        if label2 not in scan.data:
            label2 = scan.L[1]  # mnemonic v. name
        axis1 = scan.data.get(label1)
        axis2 = scan.data.get(label2)
        intervals1, intervals2 = map(int, (intervals1, intervals2))
        start1, end1, start2, end2, time = map(float, (start1, end1, start2, end2, time))
        if len(axis1) < intervals1:  # stopped scan before second row started
            self.parser_1D_columns(nxdata, scan)  # fallback support
            # TODO: what about the MCA data in this case?
        else:
            axis1 = axis1[0 : intervals1 + 1]
            axis2 = [axis2[row] for row in range(len(axis2)) if row % (intervals1 + 1) == 0]

            column_labels = scan.L
            column_labels.remove(label1)  # special handling
            column_labels.remove(label2)  # special handling
            if scan.scanCmd.startswith("hkl"):
                # find the reciprocal space axis held constant
                label3 = [key for key in ("H", "K", "L") if key not in (label1, label2)][0]
                axis3 = scan.data.get(label3)[0]
                nxdata[label3] = NXfield(axis3)
                column_labels.remove(label3)  # already handled

            nxdata[label1] = NXfield(axis1)  # 1-D array
            nxdata[label2] = NXfield(axis2)  # 1-D array

            # build 2-D data objects (do not build label1, label2, [or label3] as 2-D objects)
            data_shape = [len(axis2), len(axis1)]
            for label in column_labels:
                axis = np.array(scan.data.get(label))
                clean_name = utils.sanitize_name(nxdata, label)
                nxdata[clean_name] = NXfield(utils.reshape_data(axis, data_shape))
                nxdata[clean_name].original_name = label

            signal_axis_label = utils.sanitize_name(nxdata, scan.column_last)
            nxdata.nxsignal = nxdata[signal_axis_label]
            nxdata.nxaxes = [nxdata[label2], nxdata[label1]]

        if "_mca_" in scan.data:  # 3-D array
            # TODO: ?merge with parser_mca_spectra()?
            _num_spectra = len(scan.data["_mca_"])
            spectra_lengths = map(len, scan.data["_mca_"])
            num_channels = max(spectra_lengths)
            if num_channels != min(spectra_lengths):
                msg = "MCA spectra have different lengths"
                msg += " in scan #" + str(scan.scanNum)
                msg += " in file " + str(scan.specFile)
                raise ValueError(msg)
            data_shape += [num_channels]
            mca = np.array(scan.data["_mca_"])
            nxdata.mca__spectrum_ = NXfield(utils.reshape_data(mca, data_shape))
            try:
                # use MCA channel numbers as known at time of scan
                chan1 = scan.MCA["first_saved"]
                chanN = scan.MCA["last_saved"]
                channel_range = range(chan1, chanN + 1)
            except:
                # basic indices
                channel_range = range(1, num_channels + 1)
            nxdata.mca__spectrum_channel = NXfield(channel_range)
            nxdata.mca__spectrum_channel.units = "channel"
            axes = (label1, label2, "mca__spectrum_channel")
            nxdata.mca__spectrum_.axes = ":".join(axes)

    def metadata_NXlog(self, spec_metadata, description):
        """
        return the specific metadata in an NXlog object
        """
        from spec2nexus import utils

        nxlog = NXlog()
        nxlog.attrs["description"] = description
        for subkey, value in spec_metadata.items():
            clean_name = utils.sanitize_name(nxlog, subkey)
            nxlog[clean_name] = NXfield(value)
            nxlog[clean_name].original_name = subkey
        return nxlog
Exemple #5
0
class Parser(object):
    '''parse the spec data file object'''
    
    def __init__(self, spec_data = None):
        ''':param obj spec_data: instance of :class:`spec2nexus.prjPySpec.SpecDataFile`'''
        self.SPECfile = spec_data
        self.progress_bar = spec_data.progress_bar
        self.update_progress = spec_data.update_progress
    
    def openFile(self, filename):
        '''open the SPEC file and get its data'''
        from spec2nexus.spec import SpecDataFile
        if os.path.exists(filename):
            self.SPECfile = SpecDataFile(filename)
    
    def toTree(self, scan_list=[]):
        '''
        convert scans from chosen SPEC file into NXroot object and structure
        
        called from nexpy.readers.readspec.ImportDialog.get_data__prjPySpec() after clicking <Ok> in dialog
        
        Each scan in the range from self.scanmin to self.scanmax (inclusive)
        will be converted to a NXentry.  Scan data will go in a NXdata where 
        the signal=1 is the last column and the corresponding axes= is the first column.
        
        :param [int] scanlist
        :raises: ValueError is Min or Max scan number are not given properly
        '''
        import spec2nexus
        from spec2nexus import utils
        # check that scan_list is valid
        if len(scan_list) == 0:
            return None
        
        if self.SPECfile is None:
            return None

        complete_scan_list = list(self.SPECfile.scans)
        for key in [str(s) for s in scan_list]:
            if key not in complete_scan_list:
                msg = 'scan ' + str(key) + ' was not found'
                raise ValueError(msg)

        root = NXroot()

        root.attrs['spec2nexus'] = str(spec2nexus.__version__)
        header0 = self.SPECfile.headers[0]
        root.attrs['SPEC_file'] = self.SPECfile.fileName
        root.attrs['SPEC_epoch'] = header0.epoch
        root.attrs['SPEC_date'] = utils.iso8601(header0.date)
        root.attrs['SPEC_comments'] = '\n'.join(header0.comments)
        try:
            c = header0.comments[0]
            user = c[c.find('User = '******'=')[1].strip()
            root.attrs['SPEC_user'] = user
        except:
            pass
        root.attrs['SPEC_num_headers'] = len(self.SPECfile.headers)

        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(scan_list[0], scan_list[-1])
        for key in [str(s) for s in scan_list]:
            scan = self.SPECfile.getScan(key)
            scan.interpret()
            entry = NXentry()
            entry.title = str(scan)
            entry.date = utils.iso8601(scan.date)  
            entry.command = scan.scanCmd 
            entry.scan_number = NXfield(scan.scanNum)
            entry.comments = '\n'.join(scan.comments)
            entry.data = self.scan_NXdata(scan)            # store the scan data
            entry.positioners = self.metadata_NXlog(scan.positioner, 
                                                    'SPEC positioners (#P & #O lines)')
            if hasattr(scan, 'metadata') and len(scan.metadata) > 0:
                entry.metadata = self.metadata_NXlog(scan.metadata, 
                                                     'SPEC metadata (UNICAT-style #H & #V lines)')

            if len(scan.G) > 0:
                entry.G = NXlog()
                desc = "SPEC geometry arrays, meanings defined by SPEC diffractometer support"
                # e.g.: SPECD/four.mac
                # http://certif.com/spec_manual/fourc_4_9.html
                entry.G.attrs['description'] = desc
                for item, value in scan.G.items():
                    entry.G[item] = NXfield(list(map(float, value.split())))
            if scan.T != '':
                entry['counting_basis'] = NXfield('SPEC scan with constant counting time')
                entry['T'] = NXfield(float(scan.T))
                entry['T'].units = 'seconds'
                entry['T'].description = 'SPEC scan with constant counting time'
            elif scan.M != '':
                entry['counting_basis'] = NXfield('SPEC scan with constant monitor count')
                entry['M'] = NXfield(float(scan.M))
                entry['M'].units = 'counts'
                entry['M'].description = 'SPEC scan with constant monitor count'
            if scan.Q != '':
                entry['Q'] = NXfield(list(map(float,scan.Q)))
                entry['Q'].description = 'hkl at start of scan'

            root['scan_' + str(key)] = entry

            self.progress_bar.setValue(int(key))
            self.update_progress()

        return root
    
    def scan_NXdata(self, scan):
        '''
        return the scan data in an NXdata object
        '''
        nxdata = NXdata()

        if len(scan.data) == 0:       # what if no data?
            # since no data available, provide trivial, fake data
            # this keeps the NXdata base class compliant with the NeXus standard
            nxdata.attrs['description'] = 'SPEC scan has no data'
            nxdata['noSpecData_y'] = NXfield([0, 0])   # primary Y axis
            nxdata['noSpecData_x'] = NXfield([0, 0])   # primary X axis
            nxdata.nxsignal = nxdata['noSpecData_y']
            nxdata.nxaxes   = [nxdata['noSpecData_x'], ]
            return nxdata

        nxdata.attrs['description'] = 'SPEC scan data'
        
        scan_type = scan.scanCmd.split()[0]
        if scan_type in ('mesh', 'hklmesh'):
            # hklmesh  H 1.9 2.1 100  K 1.9 2.1 100  -800000
            self.parser_mesh(nxdata, scan)
        elif scan_type in ('hscan', 'kscan', 'lscan', 'hklscan'):
            # hklscan  1.00133 1.00133  1.00133 1.00133  2.85 3.05  200 -400000
            h_0, h_N, k_0, k_N, l_0, l_N = scan.scanCmd.split()[1:7]
            if   h_0 != h_N: axis = 'H'
            elif k_0 != k_N: axis = 'K'
            elif l_0 != l_N: axis = 'L'
            else: axis = 'H'
            self.parser_1D_columns(nxdata, scan)
            nxdata.nxaxes = nxdata[axis]
        else:
            self.parser_1D_columns(nxdata, scan)

        # these locations suggested to NIAC, easier to parse than attached to dataset!
        # but these are already set by the `nxsignal` and `nxaxes` assignments
        #nxdata.attrs['signal'] = nxdata.nxsignal.nxname         
        #nxdata.attrs['axes'] = ':'.join([obj.nxname for obj in nxdata.nxaxes])
        return nxdata
    
    def parser_1D_columns(self, nxdata, scan):
        '''generic data parser for 1-D column data'''
        from spec2nexus import utils
        for column in scan.L:
            if column in scan.data:
                clean_name = utils.sanitize_name(nxdata, column)
                nxdata[clean_name] = NXfield(scan.data[column])
                nxdata[clean_name].original_name = column

        signal = utils.sanitize_name(nxdata, scan.column_last)      # primary Y axis
        axis = utils.sanitize_name(nxdata, scan.column_first)       # primary X axis
        nxdata.nxsignal = nxdata[signal]
        nxdata.nxaxes = nxdata[axis]
        
        self.parser_mca_spectra(nxdata, scan, axis)
    
    def parser_mca_spectra(self, nxdata, scan, primary_axis_label):
        '''parse for optional MCA spectra'''
        if '_mca_' in scan.data:        # check for it
            for mca_key, mca_data in scan.data['_mca_'].items():
                key = "__" + mca_key
                nxdata[key] = NXfield(mca_data)
                nxdata[key].units = "counts"
                ch_key = key + "_channel"
                nxdata[ch_key] = NXfield(range(1, len(mca_data[0])+1))
                nxdata[ch_key].units = 'channel'
                axes = (primary_axis_label, ch_key)
                nxdata[key].axes = ':'.join( axes )
    
    def parser_mesh(self, nxdata, scan):
        '''data parser for 2-D mesh and hklmesh'''
        # 2-D parser: http://www.certif.com/spec_help/mesh.html
        #  mesh motor1 start1 end1 intervals1 motor2 start2 end2 intervals2 time
        # 2-D parser: http://www.certif.com/spec_help/hklmesh.html
        #  hklmesh Q1 start1 end1 intervals1 Q2 start2 end2 intervals2 time
        # mesh:    nexpy/examples/33id_spec.dat  scan 22  (also has MCA, thus 3-D data)
        # hklmesh: nexpy/examples/33bm_spec.dat  scan 17  (no MCA data)
        from spec2nexus import utils
        label1, start1, end1, intervals1, label2, start2, end2, intervals2, time = scan.scanCmd.split()[1:]
        if label1 not in scan.data:
            label1 = scan.L[0]      # mnemonic v. name
        if label2 not in scan.data:
            label2 = scan.L[1]      # mnemonic v. name
        axis1 = scan.data.get(label1)
        axis2 = scan.data.get(label2)
        intervals1, intervals2 = int(intervals1), int(intervals2)
        start1, end1 = float(start1), float(end1)
        start2, end2 = float(start2), float(end2)
        time = float(time)
        if len(axis1) < intervals1:     # stopped scan before second row started
            self.parser_1D_columns(nxdata, scan)        # fallback support
            # TODO: what about the MCA data in this case?
        else:
            axis1 = axis1[0:intervals1+1]
            axis2 = [axis2[row] for row in range(len(axis2)) if row % (intervals1+1) == 0]

            column_labels = scan.L
            column_labels.remove(label1)    # special handling
            column_labels.remove(label2)    # special handling
            if scan.scanCmd.startswith('hkl'):
                # find the reciprocal space axis held constant
                label3 = [key for key in ('H', 'K', 'L') if key not in (label1, label2)][0]
                axis3 = scan.data.get(label3)[0]
                nxdata[label3] = NXfield(axis3)
                column_labels.remove(label3)    # already handled

            nxdata[label1] = NXfield(axis1)    # 1-D array
            nxdata[label2] = NXfield(axis2)    # 1-D array

            # build 2-D data objects (do not build label1, label2, [or label3] as 2-D objects)
            data_shape = [len(axis2), len(axis1)]
            for label in column_labels:
                axis = np.array( scan.data.get(label) )
                clean_name = utils.sanitize_name(nxdata, label)
                nxdata[clean_name] = NXfield(utils.reshape_data(axis, data_shape))
                nxdata[clean_name].original_name = label

            signal_axis_label = utils.sanitize_name(nxdata, scan.column_last)
            nxdata.nxsignal = nxdata[signal_axis_label]
            nxdata.nxaxes = [nxdata[label2], nxdata[label1]]

        if '_mca_' in scan.data:    # 3-D array
            # TODO: ?merge with parser_mca_spectra()?
            for mca_key, mca_data in scan.data['_mca_'].items():
                key = "__" + mca_key

                spectra_lengths = list(map(len, mca_data))
                num_channels = max(spectra_lengths)
                if num_channels != min(spectra_lengths):
                    msg = 'MCA spectra have different lengths'
                    msg += ' in scan #' + str(scan.scanNum)
                    msg += ' in file ' + str(scan.specFile)
                    raise ValueError(msg)

                data_shape += [num_channels, ]
                mca = np.array(mca_data)
                nxdata[key] = NXfield(utils.reshape_data(mca, data_shape))
                nxdata[key].units = "counts"

                try:
                    # use MCA channel numbers as known at time of scan
                    chan1 = scan.MCA['first_saved']
                    chanN = scan.MCA['last_saved']
                    channel_range = range(chan1, chanN+1)
                except:
                    # basic indices
                    channel_range = range(1, num_channels+1)

                ch_key = key + "_channel"
                nxdata[ch_key] = NXfield(channel_range)
                nxdata[ch_key].units = 'channel'
                axes = (label1, label2, ch_key)
                nxdata[key].axes = ':'.join( axes )
    
    def metadata_NXlog(self, spec_metadata, description):
        '''
        return the specific metadata in an NXlog object
        '''
        from spec2nexus import utils
        nxlog = NXlog()
        nxlog.attrs['description'] = description
        for subkey, value in spec_metadata.items():
            clean_name = utils.sanitize_name(nxlog, subkey)
            nxlog[clean_name] = NXfield(value)
            nxlog[clean_name].original_name = subkey
        return nxlog
Exemple #6
0
class LogicWidgets(QObject):
    def __init__(self, status, options, plot):

        super(LogicWidgets, self).__init__()

        self.status = status
        self.spec = options.spec
        self.scan = options.scan
        self.fit = options.fit
        self.pressure = options.pressure
        self.plot = plot

        self.prepare_pseudovoigt()
        self.au_selected()

        self.popt = None
        self.fit_line = []
        self.axv_line = None
        self.spec_fname = ''

        self.make_connections()

    def make_connections(self):

        self.spec.load_button.clicked.connect(self.get_spec_fname)
        self.spec.load_button.clicked.connect(self.load_spec_file)
        self.spec.reload_button.clicked.connect(self.load_spec_file)

        self.scan.scans_box.activated[str].connect(self.selected_scan)

        self.scan.x_box.activated[str].connect(self.load_scan_wrap)
        self.scan.y_box.activated[str].connect(self.load_scan_wrap)

        self.scan.temp_box.activated[str].connect(self.update_temp)

        self.fit.pseudovoigt.toggled.connect(self.prepare_pseudovoigt)
        self.fit.gauss.toggled.connect(self.prepare_gauss)
        self.fit.lorentz.toggled.connect(self.prepare_lorentz)

        self.fit.fit_button.clicked.connect(self.fit_data)
        self.fit.reset_button.clicked.connect(self.reset_parameters)

        self.pressure.au.toggled.connect(self.au_selected)
        self.pressure.ag.toggled.connect(self.ag_selected)

        self.pressure.pressure_button.clicked.connect(self.pressure_calculator)

    def get_spec_fname(self):

        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        self.spec_fname, _ = QFileDialog.getOpenFileName(
            self.spec,
            "QFileDialog.getOpenFileName()",
            "",
            "All Files (*);;Spec Files (*.spec)",
            options=options)
        self.spec.fname.setText('{}'.format(self.spec_fname.split('/')[-1]))

    def load_spec_file(self):

        if self.spec_fname == '':
            self.status.showMessage('No file was loaded')
            return

        self.scan.scans_box.clear()
        try:
            self._spec_file = SpecDataFile(self.spec_fname)

            self._commands_list = self._spec_file.getScanCommands()
            for i in range(len(self._commands_list)):
                if '#S' in self._commands_list[i]:
                    self._commands_list[i] = self._commands_list[i].strip(
                        '#S ')
                self.scan.scans_box.addItem(self._commands_list[i])

            self.scan.scans_box.setCurrentIndex(len(self._commands_list) - 1)

            self.selected_scan(self._commands_list[-1])
            self.make_plot()
            self.pressure.print_pressure.setText('')
        except:
            self.status.showMessage('{} is not a spec file!!'.format(
                self.spec.fname.text()))

    def selected_scan(self, text):
        self._scan_number = int(text.split()[0])
        self._columns = self._spec_file.getScan(self._scan_number).L
        firstcol = self._spec_file.getScan(self._scan_number).column_first
        lastcol = self._spec_file.getScan(self._scan_number).column_last

        self.scan.x_box.clear()
        self.scan.x_box.addItems(self._columns)
        self.scan.x_box.setCurrentText(firstcol)

        self.scan.y_box.clear()
        self.scan.y_box.addItems(self._columns)
        self.scan.y_box.setCurrentText(lastcol)

        self.load_scan_wrap()

    def load_scan_wrap(self):
        try:
            self.x, self.y, self.temperature, self.energy = load_scan(
                self._spec_file, self._scan_number,
                self.scan.x_box.currentText(), self.scan.y_box.currentText())

            self.scan.energy_read.setText('{:0.4f}'.format(self.energy))

            self.scan.temp_box.clear()
            temp_list = list(self.temperature.keys())
            temp_list.sort()
            self.scan.temp_box.addItems(temp_list)
            self.scan.temp_box.setCurrentIndex(1)
            self.scan.temp_read.setText('{:0.1f}'.format(
                self.temperature[self.scan.temp_box.currentText()]))
            self.make_plot()
            self.popt = None
            self.fit_line = []
            self.axv_line = None
            self.update_params()
            self.status.showMessage('Loaded scan #{:d}'.format(
                self._scan_number))
        except:
            self.status.showMessage('Could not load scan #{:d}!!'.format(
                self._scan_number))

    def update_temp(self, text):
        self.scan.temp_read.setText('{:0.2f}'.format(self.temperature[text]))

    def make_plot(self):
        self.ax = plot_data(self.plot.figure,
                            self.plot.canvas,
                            self.x,
                            self.y,
                            xlabel=self.scan.x_box.currentText(),
                            ylabel=self.scan.y_box.currentText())

    def update_params(self):
        if self.popt is None:
            self.fit.tth_value.setText('{:.3f}'.format(
                (self.x.max() + self.x.min()) / 2.))
            self.fit.amplitude_value.setText('{:.3f}'.format(self.y.max()))
            self.fit.sigma_value.setText('{:.3f}'.format(
                np_abs((self.x.max() - self.x.min()) / 6.)))
            self.fit.constant_value.setText('{:.3f}'.format(
                (self.y[:5].mean() + self.y[-5:].mean()) / 2.))
        else:
            self.fit.tth_value.setText('{:.3f}'.format(self.popt[0]))
            self.fit.sigma_value.setText('{:.3f}'.format(self.popt[1]))
            self.fit.amplitude_value.setText('{:.3f}'.format(self.popt[2]))
            self.fit.constant_value.setText('{:.3f}'.format(self.popt[3]))
            self.fit.alpha_value.setText('{:.2f}'.format(self.popt[4]))

    def prepare_pseudovoigt(self):
        self.fit.alpha_value.setText('0.5')
        self.fit.alpha_value.setDisabled(False)
        self.fit_alpha = True

    def prepare_gauss(self):
        self.fit.alpha_value.setText('0.0')
        self.fit.alpha_value.setDisabled(True)
        self.fit_alpha = False

    def prepare_lorentz(self):
        self.fit.alpha_value.setText('1.0')
        self.fit.alpha_value.setDisabled(True)
        self.fit_alpha = False

    def fit_data(self):
        p0 = [
            float(self.fit.tth_value.toPlainText()),
            float(self.fit.sigma_value.toPlainText()),
            float(self.fit.amplitude_value.toPlainText()),
            float(self.fit.constant_value.toPlainText()),
            float(self.fit.alpha_value.toPlainText())
        ]

        try:
            self.popt = fit_pseudo_voigt(self.x,
                                         self.y,
                                         p0=p0,
                                         fit_alpha=self.fit_alpha,
                                         alpha_guess=p0[-1])

            self.yfit = pseudo_voigt(self.x, *self.popt)

            self.update_params()
            self.plot_fit()
            self.status.showMessage('Fit successful!!')
        except RuntimeError:
            self.status.showMessage('Could not fit the data!!!')

    def plot_fit(self):

        for line in self.fit_line:
            self.ax.lines.remove(line)

        self.fit_line = self.ax.plot(self.x, self.yfit, color='red')

        self.plot_vline(self.popt[0])

        self.plot.canvas.draw()

    def plot_vline(self, x0):
        if self.axv_line is not None:
            self.ax.lines.remove(self.axv_line)

        self.axv_line = self.ax.axvline(x=x0, ls='--', color='grey')

    def au_selected(self):
        self.calibrant = 'Au'

    def ag_selected(self):
        self.calibrant = 'Ag'

    def pressure_calculator(self):

        tth = float(self.fit.tth_value.toPlainText())
        temperature = float(self.scan.temp_read.toPlainText())
        energy = float(self.scan.energy_read.toPlainText())
        bragg_peak = self.pressure.hkl_box.currentText()
        calibrant = self.calibrant
        tth_off = float(self.pressure.tth_offset_value.toPlainText())

        self.plot_vline(tth)
        self.plot.canvas.draw()

        output = calculate_pressure(tth,
                                    temperature,
                                    energy,
                                    bragg_peak,
                                    calibrant,
                                    tth_off=tth_off)

        if type(output) is str:
            self.status.showMessage(output)
        else:
            self.pressure.print_pressure.setText(
                'P = {:.2f} GPa'.format(output))

        time.sleep(0.01)
        QApplication.processEvents()

    def reset_parameters(self):
        self.popt = None
        self.update_params()