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)
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))
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
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
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
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()