From 9305e6e88c30683c6c80abda71436e4c202edeff Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Mon, 11 Mar 2024 15:44:20 -0500 Subject: [PATCH 01/14] Update powderscanmapper.py replace np.int() with int() --- rsMap3D/mappers/powderscanmapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rsMap3D/mappers/powderscanmapper.py b/rsMap3D/mappers/powderscanmapper.py index e1a8034..d6e1ca7 100644 --- a/rsMap3D/mappers/powderscanmapper.py +++ b/rsMap3D/mappers/powderscanmapper.py @@ -215,7 +215,8 @@ def processMap(self): self.progressUpdater(int(progress)) #===== else: - nPasses = np.int(np.floor(imageSize*4*numImages/maxImageMem)+1) + # np.int() deprecated; use int() instead. ZZ + nPasses = int(np.floor(imageSize*4*numImages/maxImageMem)+1) for thisPass in range(nPasses): logger.info("Pass Number %d of %d" % \ (thisPass+1, nPasses)) From a517150f0a093a8b6f42a051e93561b174804cd5 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Wed, 13 Mar 2024 13:49:01 -0500 Subject: [PATCH 02/14] Update usesxmldetectorconfig.py In function _currentDetectorChanged(), add condition: if currentDetector != "": self.updateROIandNumAvg() In case the detector list is empty (e.g. when changing to a different detector config file, detSelect list will be cleaned out first), skip setting the ROI and others. --- rsMap3D/gui/input/usesxmldetectorconfig.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rsMap3D/gui/input/usesxmldetectorconfig.py b/rsMap3D/gui/input/usesxmldetectorconfig.py index ed14864..6cc37cf 100644 --- a/rsMap3D/gui/input/usesxmldetectorconfig.py +++ b/rsMap3D/gui/input/usesxmldetectorconfig.py @@ -135,7 +135,9 @@ def _createNumberOfPixelsToAverage(self, layout, row, silent=False): def _currentDetectorChanged(self, currentDetector): logger.debug(METHOD_ENTER_STR % str(currentDetector)) self.currentDetector = str(currentDetector) - self.updateROIandNumAvg() + # if the detector list is empty, let's not update ROI etc. + if currentDetector != "": + self.updateROIandNumAvg() logger.debug(METHOD_EXIT_STR % self.currentDetector) # @qtCore.pyqtSlot() From 93fdfac0eb24b791d8e7d93ce461f2c449529dd3 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Mon, 25 Mar 2024 17:20:40 -0500 Subject: [PATCH 03/14] For reading hdf5 files from NSLSII, ISR beamline. Not GUI interface entry added for this. Only for using with scripts. Very rudimentary, skipping the master file and only read the data file for the image. And the data file has to be organized similar as our .tiff file structure, under $(project_dir)/hdf5s/$(sample_name)/Snnn/. --- .../datasource/NSLSIISector4SpecDataSource.py | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 rsMap3D/datasource/NSLSIISector4SpecDataSource.py diff --git a/rsMap3D/datasource/NSLSIISector4SpecDataSource.py b/rsMap3D/datasource/NSLSIISector4SpecDataSource.py new file mode 100644 index 0000000..3474c8c --- /dev/null +++ b/rsMap3D/datasource/NSLSIISector4SpecDataSource.py @@ -0,0 +1,565 @@ +''' + Copyright (c) 2012, UChicago Argonne, LLC + See LICENSE file. +''' +import os +from spec2nexus.spec import SpecDataFile +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + ScanDataMissingException +from rsMap3D.gui.rsm3dcommonstrings import CANCEL_STR +from rsMap3D.datasource.pilatusbadpixelfile import PilatusBadPixelFile +from rsMap3D.mappers.abstractmapper import ProcessCanceledException +from rsMap3D.datasource.specxmldrivendatasource import SpecXMLDrivenDataSource +import numpy as np +import xrayutilities as xu +import time +import sys,traceback +import logging +import importlib +import h5py +try: + from PIL import Image +except ImportError: + import Image +logger = logging.getLogger(__name__) + + +# +++++++++++++++ +# Make changes to work with HDF5 from NSLSII 4-ID data, brutal forced +# I do need to generate folder structure simular to our Tiff case +# i.e. $(PROJECT_DIR)/images/$(SAMPLE_NAME)/Snnn/*.h5 +#IMAGE_DIR_MERGE_STR = "images/%s" +IMAGE_DIR_MERGE_STR = "hdf5s/%s" +SCAN_NUMBER_MERGE_STR = "S%03d" +TIFF_FILE_MERGE_STR = "S%%03d/%s_S%%03d_%%05d.tif" +#hdf5 filename format string +HDF5_FILE_MERGE_STR = "S%%03d/%s_%%03d_%%03d_data_000001.h5" +# +++++++++++++++ + +class NSLSIISector4SpecDataSource(SpecXMLDrivenDataSource): + ''' + Class to load data from spec file and configuration xml files from + for the way that data is collected at sector 33. + :members + ''' + + + def __init__(self, + projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs): + ''' + Constructor + :param projectDir: Directory holding the project file to open + :param projectName: First part of file name for the project + :param projectExt: File extension for the project file. + :param instConfigFile: Full path to Instrument configuration file. + :param detConfigFile: Full path to the detector configuration file + :param kwargs: Assorted keyword arguments + + :rtype: NSLSIISector4SpecDataSource + ''' + super(NSLSIISector4SpecDataSource, self).__init__(projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs) + + def _calc_eulerian_from_kappa(self, primaryAngles=None, + referenceAngles = None): + """ + Calculate the eulerian sample angles from the kappa stage angles. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + """ + + keta = np.deg2rad(referenceAngles[:,0]) + kappa = np.deg2rad(referenceAngles[:,1]) + kphi = np.deg2rad(referenceAngles[:,2]) + kappaParam = self.instConfig.getSampleAngleMappingParameter('kappa') + + try: + if kappaParam != None: + self.kalpha = np.deg2rad(float(kappaParam)) + else: + self.kalpha = np.deg2rad(50.000) + kappaInvertedParam = \ + self.instConfig.getSampleAngleMappingParameter('kappaInverted') + if kappaInvertedParam != None: + self.kappa_inverted = self.to_bool(kappaInvertedParam) + else: + self.kappa_inverted = False + except Exception as ex: + raise RSMap3DException("Error trying to get parameter for " + \ + "sampleAngleMappingFunction " + \ + "_calc_eulerian_from_kappa in inst config " + \ + "file\n" + \ + str(ex)) + + _t1 = np.arctan(np.tan(kappa / 2.0) * np.cos(self.kalpha)) + if self.kappa_inverted: + eta = np.rad2deg(keta + _t1) + phi = np.rad2deg(kphi + _t1) + else: + eta = np.rad2deg(keta - _t1) + phi = np.rad2deg(kphi - _t1) + chi = 2.0 * np.rad2deg(np.arcsin(np.sin(kappa / 2.0) * \ + np.sin(self.kalpha))) + + return eta, chi, phi + + def _calc_replace_angle_values(self , primaryAngles=None, + referenceAngles=None): + ''' + Fix a situation were some constant angle values have been + recorded incorrectly and need to be fixed. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + ''' + logger.info( "Running " + __name__) + angles = [] + logger.debug("referenceAngles " + str(referenceAngles)) + mappingAngles = self.instConfig.getSampleAngleMappingReferenceAngles() + + logger.debug( "mappingAngles" + str(mappingAngles)) + for ii in range(len(referenceAngles)): + replaceVal = \ + float(self.instConfig.getSampleAngleMappingReferenceAngleAttrib( \ + number= str(mappingAngles[ii]), \ + attribName='replaceValue')) + logger.debug( "primary Angles" + str(referenceAngles)) + angles.append(replaceVal* np.ones(len(referenceAngles[:,ii]),)) + logger.debug("Angles" + str( angles)) + return angles + + def fixGeoAngles(self, scan, angles): + ''' + Fix the angles using a user selected function. + :param scan: scan to set the angles for + :param angles: Array of angles to set for this scan + ''' + logger.debug( "starting " + __name__) + needToCorrect = False + refAngleNames = self.instConfig.getSampleAngleMappingReferenceAngles() + for refAngleName in refAngleNames: + alwaysFix = self.instConfig.getSampleAngleMappingAlwaysFix() + if refAngleName in scan.L or alwaysFix: + needToCorrect = True + + if needToCorrect: + logger.debug( __name__ + ": Fixing angles") + refAngles = self.getScanAngles(scan, refAngleNames) + primaryAngles = self.instConfig.getSampleAngleMappingPrimaryAngles() + functionName = self.instConfig.getSampleAngleMappingFunctionName() + functionModuleName = self.instConfig.getSampleAngleMappingFunctionModule() + logger.debug("sampleMappingFunction moduleName %s" % functionModuleName) + #Call a defined method to calculate angles from the reference angles. + moduleSource = self + if functionModuleName != None: + functionModule = importlib.import_module(functionModuleName) + logger.debug("dir(functionModule)" % dir(functionModule)) + moduleSource = functionModule + method = getattr(moduleSource, functionName) + fixedAngles = method(primaryAngles=primaryAngles, + referenceAngles=refAngles) + logger.debug ("fixed Angles: " + str(fixedAngles)) + for i in range(len(primaryAngles)): + logger.debug ("Fixing primaryAngles: %d " % primaryAngles[i]) + angles[:,primaryAngles[i]-1] = fixedAngles[i] + + def getGeoAngles(self, scan, angleNames): + """ + This function returns all of the geometry angles for the + for the scan as a N-by-num_geo array, where N is the number of scan + points and num_geo is the number of geometry motors. + """ +# scan = self.sd[scanNo] + geoAngles = self.getScanAngles(scan, angleNames) + if not (self.instConfig.getSampleAngleMappingFunctionName() == ""): + tb = None + try: + self.fixGeoAngles(scan, geoAngles) + except Exception as ex: + tb = traceback.format_exc() + raise RSMap3DException("Handling exception in getGeoAngles." + \ + "\n" + \ + str(ex) + \ + "\n" + \ + str(tb)) + logger.debug("getGeoAngles:\n" + str(geoAngles)) + return geoAngles + + def getUBMatrix(self, scan): + """ + Read UB matrix from the #G3 line from the spec file. + """ + try: + g3 = scan.G["G3"].strip().split() + g3 = np.array(list(map(float, g3))) + ub = g3.reshape(-1,3) + logger.debug("ub " +str(ub)) + return ub + except: + logger.error("Unable to read UB Matrix from G3") + logger.error( '-'*60) + traceback.print_exc(file=sys.stdout) + logger.error('-'*60) + + + def hotpixelkill(self, areaData): + """ + function to remove hot pixels from CCD frames + ADD REMOVE VALUES IF NEEDED! + :param areaData: area detector data + """ + + for pixel in self.getBadPixels(): + badLoc = pixel.getBadLocation() + replaceLoc = pixel.getReplacementLocation() + areaData[badLoc[0],badLoc[1]] = \ + areaData[replaceLoc[0],replaceLoc[1]] + + return areaData + + def loadSource(self, mapHKL=False): + ''' + This method does the work of loading data from the files. This has been + split off from the constructor to allow this to be threaded and later + canceled. + :param mapHKL: boolean to mark if the data should be mapped to HKL + ''' + # Load up the instrument configuration file + self.loadInstrumentXMLConfig() + #Load up the detector configuration file + self.loadDetectorXMLConfig() + + self.specFile = os.path.join(self.projectDir, self.projectName + \ + self.projectExt) + imageDir = os.path.join(self.projectDir, \ + IMAGE_DIR_MERGE_STR % self.projectName) + + # +++++++++++++++ + # for hdf5 file,use the same property but different format + self.imageFileTmp = os.path.join(imageDir, \ + HDF5_FILE_MERGE_STR % + (self.projectName)) + # +++++++++++++++ + # if needed load up the bad pixel file. + if not (self.badPixelFile is None): + + badPixelFile = PilatusBadPixelFile(self.badPixelFile) + self.badPixels = badPixelFile.getBadPixels() + + # id needed load the flat field file + if not (self.flatFieldFile is None): + self.flatFieldData = np.array(Image.open(self.flatFieldFile)).T + # Load scan information from the spec file + try: + self.sd = SpecDataFile(self.specFile) + self.mapHKL = mapHKL + maxScan = int(self.sd.getMaxScanNumber()) + logger.debug("Number of Scans" + str(maxScan)) + if self.scans is None: + self.scans = range(1, maxScan+1) + imagePath = os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName) + + self.imageBounds = {} + self.imageToBeUsed = {} + self.availableScans = [] + self.incidentEnergy = {} + self.ubMatrix = {} + self.scanType = {} + self.progress = 0 + self.progressInc = 1 + #======= ZZ, 2020/02/19, add initialization + self.progressMax = 100 + #======= + # Zero the progress bar at the beginning. + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + for scan in self.scans: + if (self.cancelLoad): + self.cancelLoad = False + raise LoadCanceledException(CANCEL_STR) + + else: + if (os.path.exists(os.path.join(imagePath, \ + SCAN_NUMBER_MERGE_STR % scan))): + try: + curScan = self.sd.scans[str(scan)] + self.scanType[scan] = \ + self.sd.scans[str(scan)].scanCmd.split()[0] + angles = self.getGeoAngles(curScan, self.angleNames) + self.availableScans.append(scan) + if self.mapHKL==True: + self.ubMatrix[scan] = self.getUBMatrix(curScan) + if self.ubMatrix[scan] is None: + raise Sector33SpecFileException("UB matrix " + \ + "not found.") + else: + self.ubMatrix[scan] = None + self.incidentEnergy[scan] = 12398.4 /float(curScan.G['G4'].split()[3]) + _start_time = time.time() + self.imageBounds[scan] = \ + self.findImageQs(angles, \ + self.ubMatrix[scan], \ + self.incidentEnergy[scan]) + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + logger.info (('Elapsed time for Finding qs for scan %d: ' + + '%.3f seconds') % \ + (scan, (time.time() - _start_time))) + except ScanDataMissingException: + logger.error( "Scan " + str(scan) + " has no data") + #Make sure to show 100% completion + if self.progressUpdater is not None: + self.progressUpdater(self.progressMax, self.progressMax) + except IOError: + raise IOError( "Cannot open file " + str(self.specFile)) + if len(self.getAvailableScans()) == 0: + raise ScanDataMissingException("Could not find scan data for " + \ + "input file \n" + self.specFile + \ + "\nOne possible reason for this " + \ + "is that the image files are " + \ + "missing. Images are assumed " + \ + "to be in " + \ + os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName)) + + self.availableScanTypes = set(self.scanType.values()) + + + + def to_bool(self, value): + """ + Note this method found in answer to: + http://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python + Converts 'something' to boolean. Raises exception if it gets a string + it doesn't handle. + Case is ignored for strings. These string values are handled: + True: 'True', "1", "TRue", "yes", "y", "t" + False: "", "0", "faLse", "no", "n", "f" + Non-string values are passed to bool. + """ + if type(value) == type(''): + if value.lower() in ("yes", "y", "true", "t", "1"): + return True + if value.lower() in ("no", "n", "false", "f", "0", ""): + return False + raise Exception('Invalid value for boolean conversion: ' + value) + return bool(value) + + def rawmap(self,scans, angdelta=[0,0,0,0,0], + adframes=None, mask = None): + """ + read ad frames and and convert them in reciprocal space + angular coordinates are taken from the spec file + or read from the edf file header when no scan number is given (scannr=None) + """ + + if mask is None: + mask_was_none = True + #mask = [True] * len(self.getImageToBeUsed()[scans[0]]) + else: + mask_was_none = False + #sd = spec.SpecDataFile(self.specFile) + intensity = np.array([]) + + # fourc goniometer in fourc coordinates + # convention for coordinate system: + # x: upwards; + # y: along the incident beam; + # z: "outboard" (makes coordinate system right-handed). + # QConversion will set up the goniometer geometry. + # So the first argument describes the sample rotations, the second the + # detector rotations and the third the primary beam direction. + qconv = xu.experiment.QConversion(self.getSampleCircleDirections(), \ + self.getDetectorCircleDirections(), \ + self.getPrimaryBeamDirection()) + + # define experimental class for angle conversion + # + # ipdir: inplane reference direction (ipdir points into the primary beam + # direction at zero angles) + # ndir: surface normal of your sample (ndir points in a direction + # perpendicular to the primary beam and the innermost detector + # rotation axis) + en = self.getIncidentEnergy() + hxrd = xu.HXRD(self.getInplaneReferenceDirection(), \ + self.getSampleSurfaceNormalDirection(), \ + en=en[self.getAvailableScans()[0]], \ + qconv=qconv) + + + # initialize area detector properties + if (self.getDetectorPixelWidth() != None ) and \ + (self.getDistanceToDetector() != None): + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + pwidth1=self.getDetectorPixelWidth()[0], \ + pwidth2=self.getDetectorPixelWidth()[1], \ + distance=self.getDistanceToDetector(), \ + Nav=self.getNumPixelsToAverage(), \ + roi=self.getDetectorROI()) + else: + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + chpdeg1=self.getDetectorChannelsPerDegree()[0], \ + chpdeg2=self.getDetectorChannelsPerDegree()[1], \ + Nav=self.getNumPixelsToAverage(), + roi=self.getDetectorROI()) + + angleNames = self.getAngles() + scanAngle = {} + for i in range(len(angleNames)): + scanAngle[i] = np.array([]) + + offset = 0 + imageToBeUsed = self.getImageToBeUsed() + monitorName = self.getMonitorName() + monitorScaleFactor = self.getMonitorScaleFactor() + filterName = self.getFilterName() + filterScaleFactor = self.getFilterScaleFactor() + for scannr in scans: + if self.haltMap: + raise ProcessCanceledException("Process Canceled") + scan = self.sd.scans[str(scannr)] + angles = self.getGeoAngles(scan, angleNames) + scanAngle1 = {} + scanAngle2 = {} + for i in range(len(angleNames)): + scanAngle1[i] = angles[:,i] + scanAngle2[i] = [] + if monitorName != None: + monitor_data = scan.data.get(monitorName) + if monitor_data is None: + raise IOError("Did not find Monitor source '" + \ + monitorName + \ + "' in the Spec file. Make sure " + \ + "monitorName is correct in the " + \ + "instrument Config file") + if filterName != None: + filter_data = scan.data.get(filterName) + if filter_data is None: + raise IOError("Did not find filter source '" + \ + filterName + \ + "' in the Spec file. Make sure " + \ + "filterName is correct in the " + \ + "instrument Config file") + # read in the image data + arrayInitializedForScan = False + foundIndex = 0 + + if mask_was_none: + mask = [True] * len(self.getImageToBeUsed()[scannr]) + + for ind in range(len(scan.data[list(scan.data.keys())[0]])): + if imageToBeUsed[scannr][ind] and mask[ind]: + # +++++++++++++++ + # Read HDF5 file here + #im = Image.open(self.imageFileTmp % (scannr, scannr, ind)) + #img = np.array(im.getdata()).reshape(im.size[1],im.size[0]).T + h5file = self.imageFileTmp % (scannr, scannr, ind) + h5data = h5py.File(h5file, "r") + im = h5data["entry"]["data"]["data"][0,:,:] + img = np.array(im).T + # +++++++++++++++ + + img = self.hotpixelkill(img) + ff_data = self.getFlatFieldData() + if not (ff_data is None): + img = img * ff_data + # reduce data siz + img2 = xu.blockAverage2D(img, + self.getNumPixelsToAverage()[0], \ + self.getNumPixelsToAverage()[1], \ + roi=self.getDetectorROI()) + + # apply intensity corrections + if monitorName != None: + img2 = img2 / monitor_data[ind] * monitorScaleFactor + if filterName != None: + img2 = img2 / filter_data[ind] * filterScaleFactor + + # initialize data array + if not arrayInitializedForScan: + imagesToProcess = [imageToBeUsed[scannr][i] and mask[i] for i in range(len(imageToBeUsed[scannr]))] + if not intensity.shape[0]: + intensity = np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape) + arrayInitializedForScan = True + else: + offset = intensity.shape[0] + intensity = np.concatenate( + (intensity, + (np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape))), + axis=0) + arrayInitializedForScan = True + # add data to intensity array + intensity[foundIndex+offset,:,:] = img2 + for i in range(len(angleNames)): +# logger.debug("appending angles to angle2 " + +# str(scanAngle1[i][ind])) + scanAngle2[i].append(scanAngle1[i][ind]) + foundIndex += 1 + if len(scanAngle2[0]) > 0: + for i in range(len(angleNames)): + scanAngle[i] = \ + np.concatenate((scanAngle[i], np.array(scanAngle2[i])), \ + axis=0) + # transform scan angles to reciprocal space coordinates for all detector pixels + angleList = [] + for i in range(len(angleNames)): + angleList.append(scanAngle[i]) + if self.ubMatrix[scans[0]] is None: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage()) + else: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage(), \ + UB = self.ubMatrix[scans[0]]) + + + # apply selected transform + qxTrans, qyTrans, qzTrans = \ + self.transform.do3DTransform(qx, qy, qz) + + + return qxTrans, qyTrans, qzTrans, intensity + +class LoadCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + super(LoadCanceledException, self).__init__(message) + +class Sector33SpecFileException(RSMap3DException): + ''' + Exception class to be raised if there is a problem loading information + from a spec file + file + ''' + def __init__(self, message): + super(Sector33SpecFileException, self).__init__(message) + + + From b759afa5db8e843001c16161225671262fcd1609 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Mon, 25 Mar 2024 17:33:26 -0500 Subject: [PATCH 04/14] Newer version of Script to generate .vti file. Using json file as input. 2 input files are uploaded, too. --- Scripts/mapSpecAngleScan_v4.2.py | 382 ++++++++++++++++++ Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks.json | 32 ++ ...smconfig_v4.2_lsfo_IntegerPeaks_batch.json | 62 +++ 3 files changed, 476 insertions(+) create mode 100644 Scripts/mapSpecAngleScan_v4.2.py create mode 100644 Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks.json create mode 100644 Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks_batch.json diff --git a/Scripts/mapSpecAngleScan_v4.2.py b/Scripts/mapSpecAngleScan_v4.2.py new file mode 100644 index 0000000..065c4b3 --- /dev/null +++ b/Scripts/mapSpecAngleScan_v4.2.py @@ -0,0 +1,382 @@ +''' + +Script to rebuild reciprocal space map +Try to handle the mapping data from the specfile as it is taken. +Check the file as it goes, analysis the data + + Copyright (c) 2017, UChicago Argonne, LLC + See LICENSE file. + + Modified from earlier version and Mike's code -- ZZ 2022/10/07 + + And try to move the inputs to the external json file: config.json. + ========== + User parameters are loaded from the rsmconfig.json file, preferably in the + same folder as this script. If not, the full path and filename need to be + given as an argument, e.g. : + python mapSpecAngleScan_v2.py path/to/rsmconfig.json + ========== + + +Script to process a dataset using the Sector33SpecDataSource + +Change list: + + 2022/10/12 (ZZ): + - consolidated earlier version and generated v3 (input in the py file) + and v4 (use external json file for input). + + 2022/10/14 (ZZ): + - try to check the scan is done or not on realtime option with the + EPICS PV flag. Just implemented at 33-ID-D. + + 2024/01/03 (ZZ): + - add the grid range in the json file as entry "grid_range", in + the format of [h_min, h_max, k_min, k_max, l_min, l_max] + - accordingly, modify the code here to use the range. + - if it is value "null", use the default full range. + +To DO: + + - SPEC file reindex check file time stamp? No need to redo if not changed. + +''' + +import os +import numpy as np +import sys +import datetime +import time +import json +import argparse +from pathlib import Path +from spec2nexus import spec + +from rsMap3D.datasource.Sector33SpecDataSource import Sector33SpecDataSource +from rsMap3D.datasource.DetectorGeometryForXrayutilitiesReader import DetectorGeometryForXrayutilitiesReader as detReader +from rsMap3D.utils.srange import srange +from rsMap3D.config.rsmap3dconfigparser import RSMap3DConfigParser +from rsMap3D.constants import ENERGY_WAVELENGTH_CONVERT_FACTOR +from rsMap3D.mappers.gridmapper import QGridMapper +from rsMap3D.gui.rsm3dcommonstrings import BINARY_OUTPUT +from rsMap3D.transforms.unitytransform3d import UnityTransform3D +from rsMap3D.mappers.output.vtigridwriter import VTIGridWriter +from epics import PV + +def updateDataSourceProgress(value1, value2): + logger.info("\t\tDataLoading Progress -- Current set: %.3f%%/%s%%" % (value1, value2)) + +def updateMapperProgress(value1): + logger.info("\t\tMapper Progress -- Current volume: %.3f%%" % (value1)) + +def reindex_specfile(fullFilename): + logger.info('=============================') + logger.info(' Re-indexing the SPEC file...') + spec_data = spec.SpecDataFile(fullFilename) + scansInFile_list = spec_data.getScanNumbers() + logger.info(' Done. The lastest scan # is %s' % scansInFile_list[-1]) + return scansInFile_list, spec_data + +def parseArgs(): + parser = argparse.ArgumentParser(description='Default MPI run script. Expects a json config file pointed at the data.') + parser.add_argument('configPath', + nargs='?', + default=os.path.join(os.getcwd(), 'rsmconfig.json'), + help='Path to config file. If none supplied, directs to a config.json located in CWD') + return parser.parse_args() + +def generateScanLists(inputScanList): + # How many conditions/cycles in total + num_cycles = inputScanList["cycles"] + # Total number of scans at one condition, say each temperature + scans_in_1_cycle = inputScanList["scans_per_cycle"] + SetsOfRSM = inputScanList["rsm_sets"] + + scanListTop = [] + for i in range(0, num_cycles): + for oneSetRSM in SetsOfRSM: + scan_s = oneSetRSM["start"] + scan_e = oneSetRSM["end"] + scans_in_1_rsm = scan_e - scan_s + 1 + scanListTop = scanListTop + \ + ( [[f"{x}" if scans_in_1_rsm==1 else f"{x}-{x+scans_in_1_rsm-1}"] for \ + x in range(scan_s+i*scans_in_1_cycle, scan_e+i*scans_in_1_cycle+1, scans_in_1_rsm)] ) + return scanListTop + +def _is_scan_done(specfile_full, curr_scan=1): + specfile_pv = PV("33idSIS:spec:SPECFileName") + scann_pv = PV("33idSIS:spec:SCANNUM") + scanDone_pv = PV("33idSIS:spec:ISSCANDONE") + + # Check if PVs are there. If one cant be reached, stop there. + if (not scann_pv.wait_for_connection(timeout=2)) \ + or (not scanDone_pv.wait_for_connection(timeout=2)) \ + or (not specfile_pv.wait_for_connection(timeout=2)): + return -1 + + # If it is not the current file, this one is irrelavent. + if specfile_full != specfile_pv.char_value: + return 1 + # If scan number is lower than the current, go ahead + if (curr_scan < scann_pv.value): + return 1 + elif (curr_scan == scann_pv.value): + if scanDone_pv.value == 1: + return 1 + else: + return 0 + else: + return 0 + +def checkGridRange(gridRange, fullRange): + if (gridRange is None): + tmp = fullRange + elif (len(gridRange) != 6): + tmp = fullRange + else: + h_min = max(fullRange[0], min(gridRange[0], gridRange[1])) + h_max = min(fullRange[1], max(gridRange[0], gridRange[1])) + k_min = max(fullRange[2], min(gridRange[2], gridRange[3])) + k_max = min(fullRange[3], max(gridRange[2], gridRange[3])) + l_min = max(fullRange[4], min(gridRange[4], gridRange[5])) + l_max = min(fullRange[5], max(gridRange[4], gridRange[5])) + tmp = h_min, h_max, k_min, k_max, l_min, l_max + return tmp + +# Rocord the starting run time +startTime = datetime.datetime.now() +with open('time.log', 'a') as time_log: + time_log.write(f'Start: {startTime}\n') + +#================================================ +# try use the logger to do console display +import logging + +# create root_logger +root_logger = logging.getLogger() +root_logger.setLevel(logging.INFO) + +# create console handler and set level to debug +ch = logging.StreamHandler() +# create formatter +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +# add formatter to ch +ch.setFormatter(formatter) +# add ch to logger +root_logger.addHandler(ch) + +# create logger +logger = logging.getLogger('mapSpecAngleScan_RSM3D') +logger.setLevel(logging.INFO) +#================================================ + +#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ +# Get the input from the input file +args = parseArgs() +with open(args.configPath, 'r') as config_f: + config = json.load(config_f) + +# workpath and config file path +projectDir = config["project_dir"] +configDir = config["config_dir"] + +if configDir == None: + configDir = projectDir + +# Config files +detectorConfigName = os.path.join(configDir, config["detector_config"]) +instConfigName = os.path.join(configDir, config["instrument_config"]) +badPixelFile = os.path.join(configDir, config["badpixel_file"]) +flatfieldFile = config["flat_field"] + +# Detector settngs +detectorName = config["detector_name"] +bin = config["binning"] +roi = config["roi_setting"] +# Output grid number +nx = config["nx"] +ny = config["ny"] +nz = config["nz"] +# Output grid range +gridRange_input = config["grid_range"] +# Output selction +mapHKL = config["use_HKL"] +# Do realtime or not +realtime_flag = config["real_time"] + +datasets = config["datasets"] +one_scan_time = config["maxTime_1Scan"] + +#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ + +##################################### +# checking for validity of the inputs +if not os.path.exists(detectorConfigName): + raise Exception("Detector Config file does not exist: %s" % + detectorConfigName) +if not os.path.exists(instConfigName): + raise Exception("Instrument Config file does not exist: %s" % + instConfigName) +if not os.path.exists(badPixelFile): + raise Exception("Bad Pixel file does not exist: %s" % + badPixelFile) +if not (flatfieldFile is None): + flatfieldFile = os.path.join(configDir, flatfieldFile) + if not os.path.exists(flatfieldFile): + raise Exception("Flat field file does not exist: %s" % + flatfieldFile) + +# If no ROI set, get info from the config +if roi is None: + detector = dReader.getDetectorById(detectorName) + nPixels = dReader.getNpixels(detector) + roi = [1, nPixels[0], 1, nPixels[1]] +logger.info("ROI: %s " % roi) +# End checking for inputs +##################################### + +dReader = detReader(detectorConfigName) + +#===================================== +# Outer-most loop, iterate over different SPEC files +for idx, dataset in enumerate(datasets, 1): + # SPEC file name and scan numbers + specFile = dataset["spec_file"] + scanListTop = dataset["scan_list"] + + if scanListTop == None: + inputScanList = dataset["scan_range"] + scanListTop = generateScanLists(inputScanList) + + specfile_full = os.path.join(projectDir, specFile) + # Check if the SPEC file exists. Quit if not. + if not os.path.exists( specfile_full ): + print(f"File not found. Please check the path and name {specFile} is correct.") + print(f"Moving on...in a couple of seconds. ") + time.sleep(2) + continue + + specName, specExt = os.path.splitext(specFile) + # generate the output file folder now + outputFilePath = os.path.join(projectDir, \ + "analysis_runtime", specName) + Path(outputFilePath).mkdir(parents=True, exist_ok=True) + + logger.info('=============================') + logger.info(f"SPEC file #{idx}: {specfile_full}") + logger.info(f"Generated Scan List #{idx}: {scanListTop}") + + # (Re)index the SPEC file + scansInFile_list, spec_data = reindex_specfile(specfile_full) + + for scanList1 in scanListTop: + outputFileName = os.path.join(outputFilePath, \ + f"{specName}_{str(scanList1[0])}") + + if mapHKL == True: + outputFileName += '_hkl.vti' + else: + outputFileName += '_Qxyz.vti' + + appConfig = RSMap3DConfigParser() + maxImageMemory = appConfig.getMaxImageMemory() + + scanRange = [] + for scans in scanList1: + scanRange += srange(scans).list() + logger.info(f" --------------------------------") + logger.info("scanRange %s" % scanRange) + #logger.info("specName, specExt: %s, %s" % (specName, specExt)) + + #======================= + # Find the largest scan number in this set + curr_scan = max(scanRange) + # waiting time factor + _accu = 1 + last_len = 0 + while (realtime_flag): + # add my local way to check if scan is done + _scanDone_flag = _is_scan_done(specfile_full, curr_scan) + if _scanDone_flag == 1: + break + elif _scanDone_flag == 0: + sleep_time = 5 + else: + # check if the largest scan number is available yet. + if not (str(curr_scan) in scansInFile_list): + sleep_time = _accu*5 + logger.info(' Scan #%d not available yet. \ + Wait %d seconds' % (curr_scan, sleep_time)) + elif scansInFile_list.index(str(curr_scan)) == (len(scansInFile_list) - 1): + # Try to figure out the scan is done or not -- no good way as I see it. + scan = spec_data.getScan(curr_scan) + #scan.interpret() + if(len(scan.data)>0): + curr_len = len(scan.data[scan.L[0]]) + else: + curr_len = 0 + logger.info(' Data points current in the scan #%d is %d' \ + % (curr_scan, curr_len) ) + if(curr_len == 0): + pass + elif(curr_len != last_len): + last_len = curr_len + else: + if(_accu>4): + break + sleep_time = 5*_accu + logger.info(' Scan #%d may not be finished. Wait %d seconds...' \ + % (curr_scan, sleep_time) ) + else: + logger.info('\t--------------------------------') + logger.info(' Scan #%d ready.\n' % curr_scan) + break + _accu += 1 + + scansInFile_list, spec_data = \ + reindex_specfile(specfile_full) + + # this is in the outer loop, when the scan does not exist yet, wait sometime before resume loops + time.sleep(sleep_time) + + ds = Sector33SpecDataSource(projectDir, specName, specExt, + instConfigName, detectorConfigName, roi=roi, + pixelsToAverage=bin, scanList= scanRange, + badPixelFile=badPixelFile, + flatFieldFile=flatfieldFile, + appConfig=appConfig) + ds.setCurrentDetector(detectorName) + ds.setProgressUpdater(updateDataSourceProgress) + ds.loadSource(mapHKL=mapHKL) + + fullRange = ds.getOverallRanges() + if gridRange_input is None: + gridRange = fullRange + else: + gridRange = checkGridRange(gridRange_input, fullRange) + + ds.setRangeBounds(gridRange) + + imageToBeUsed = ds.getImageToBeUsed() + #print("imageToBeUsed %s" % imageToBeUsed) + imageSize = np.prod(ds.getDetectorDimensions()) + logger.info(' --------------------------------') + + gridMapper = QGridMapper(ds, + outputFileName, + outputType=BINARY_OUTPUT, + nx=nx, ny=ny, nz=nz, + transform=UnityTransform3D(), + gridWriter=VTIGridWriter(), + appConfig=appConfig) + + gridMapper.setProgressUpdater(updateMapperProgress) + gridMapper.doMap() + + with open('time.log', 'a') as time_log: + endTime = datetime.datetime.now() + time_log.write(f'End: {endTime}\n') + time_log.write(f'Diff: {endTime - startTime}\n') + + logger.info(' --------------------------------') +logger.info('=============================') diff --git a/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks.json b/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks.json new file mode 100644 index 0000000..5e49304 --- /dev/null +++ b/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks.json @@ -0,0 +1,32 @@ +{ + "project_dir":"C:\\work\\Users\\YCao\\LSFO\\", + "config_dir":null, + "detector_config":"NSLS_4ID_Eiger500k.xml", + "instrument_config":"NSLS_4ID_fourc.xml", + "badpixel_file":"badpixels_ISREiger500k.txt", + "flat_field":null, + "use_HKL":true, + "real_time":false, + "detector_name":"Eiger500k", + "binning":[1, 1], + "roi_setting":[10, 1020, 10, 504], + "nx":400, + "ny":400, + "nz":200, + "grid_range":null, + "maxTime_1Scan":150, + "datasets":[ + { + "spec_file":"LSFO_001_Cryo.spec", + "scan_list":[["316-318"]], + "scan_range": + { + "cycles":1, + "scans_per_cycle":30, + "rsm_sets":[ + {"start":117, "end":207} + ] + } + } + ] +} diff --git a/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks_batch.json b/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks_batch.json new file mode 100644 index 0000000..6cc8822 --- /dev/null +++ b/Scripts/rsmconfig_v4.2_lsfo_IntegerPeaks_batch.json @@ -0,0 +1,62 @@ +{ + "project_dir":"C:\\work\\Users\\YCao\\LSFO\\", + "config_dir":null, + "detector_config":"NSLS_4ID_Eiger500k.xml", + "instrument_config":"NSLS_4ID_fourc.xml", + "badpixel_file":"badpixels_ISREiger500k.txt", + "flat_field":null, + "use_HKL":true, + "real_time":false, + "detector_name":"Eiger500k", + "binning":[1, 1], + "roi_setting":[10, 1020, 10, 504], + "nx":200, + "ny":200, + "nz":200, + "grid_range":null, + "maxTime_1Scan":150, + "datasets":[ + { + "spec_file":"LSFO_001_Cryo.spec", + "scan_list":null, + "scan_range": + { + "cycles":1, + "scans_per_cycle":12, + "rsm_sets":[ + {"start":101, "end":103}, + {"start":104, "end":106}, + {"start":107, "end":109} + ] + } + }, + { + "spec_file":"LSFO_001_Cryo.spec", + "scan_list":null, + "scan_range": + { + "cycles":16, + "scans_per_cycle":12, + "rsm_sets":[ + {"start":114, "end":116}, + {"start":117, "end":119}, + {"start":120, "end":122} + ] + } + }, + { + "spec_file":"LSFO_001_Cryo.spec", + "scan_list":null, + "scan_range": + { + "cycles":3, + "scans_per_cycle":12, + "rsm_sets":[ + {"start":304, "end":306}, + {"start":307, "end":309}, + {"start":310, "end":312} + ] + } + } + ] +} From 7ac6966ac184b64641617d927fec3dbd27042d5c Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 5 Apr 2024 12:07:12 -0500 Subject: [PATCH 05/14] Add files via upload --- .../Sector12NSLSIISpecDataSource.py | 562 ++++++++++++++++++ 1 file changed, 562 insertions(+) create mode 100644 rsMap3D/datasource/Sector12NSLSIISpecDataSource.py diff --git a/rsMap3D/datasource/Sector12NSLSIISpecDataSource.py b/rsMap3D/datasource/Sector12NSLSIISpecDataSource.py new file mode 100644 index 0000000..eb78fa3 --- /dev/null +++ b/rsMap3D/datasource/Sector12NSLSIISpecDataSource.py @@ -0,0 +1,562 @@ +''' + Copyright (c) 2012, UChicago Argonne, LLC + See LICENSE file. +''' +import os +from spec2nexus.spec import SpecDataFile +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + ScanDataMissingException +from rsMap3D.gui.rsm3dcommonstrings import CANCEL_STR +from rsMap3D.datasource.pilatusbadpixelfile import PilatusBadPixelFile +from rsMap3D.mappers.abstractmapper import ProcessCanceledException +from rsMap3D.datasource.specxmldrivendatasource import SpecXMLDrivenDataSource +import numpy as np +import xrayutilities as xu +import time +import sys,traceback +import logging +import importlib +try: + from PIL import Image +except ImportError: + import Image +logger = logging.getLogger(__name__) + + + +IMAGE_DIR_MERGE_STR = "images/%s" +SCAN_NUMBER_MERGE_STR = "%s_%04d" +#TIFF_FILE_MERGE_STR = "S%%03d/%s_S%%03d_%%05d.tif" +#TIFF_FILE_MERGE_STR = "%s_%%04d/pil_%%05d.tif" +# This seems changed at some point, at least for data at NSLS-II 4ID ones. +TIFF_FILE_MERGE_STR = "%s_%%04d/%s_%%04d_%%05d.tif" +# Some messed up SPEC macro started the point # at 116 instead of 0 +# to account for that, here we have: +MESSYSCANS = [27, 28, 34, 45, 59] + +class Sector12NSLSIISpecDataSource(SpecXMLDrivenDataSource): + ''' + Class to load data from spec file and configuration xml files from + for the way that data is collected at sector 12. + :members + ''' + + + def __init__(self, + projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs): + ''' + Constructor + :param projectDir: Directory holding the project file to open + :param projectName: First part of file name for the project + :param projectExt: File extension for the project file. + :param instConfigFile: Full path to Instrument configuration file. + :param detConfigFile: Full path to the detector configuration file + :param kwargs: Assorted keyword arguments + + :rtype: Sector12NSLSIISpecDataSource + ''' + super(Sector12NSLSIISpecDataSource, self).__init__(projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs) + + def _calc_eulerian_from_kappa(self, primaryAngles=None, + referenceAngles = None): + """ + Calculate the eulerian sample angles from the kappa stage angles. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + """ + + keta = np.deg2rad(referenceAngles[:,0]) + kappa = np.deg2rad(referenceAngles[:,1]) + kphi = np.deg2rad(referenceAngles[:,2]) + kappaParam = self.instConfig.getSampleAngleMappingParameter('kappa') + + try: + if kappaParam != None: + self.kalpha = np.deg2rad(float(kappaParam)) + else: + self.kalpha = np.deg2rad(50.000) + kappaInvertedParam = \ + self.instConfig.getSampleAngleMappingParameter('kappaInverted') + if kappaInvertedParam != None: + self.kappa_inverted = self.to_bool(kappaInvertedParam) + else: + self.kappa_inverted = False + except Exception as ex: + raise RSMap3DException("Error trying to get parameter for " + \ + "sampleAngleMappingFunction " + \ + "_calc_eulerian_from_kappa in inst config " + \ + "file\n" + \ + str(ex)) + + _t1 = np.arctan(np.tan(kappa / 2.0) * np.cos(self.kalpha)) + if self.kappa_inverted: + eta = np.rad2deg(keta + _t1) + phi = np.rad2deg(kphi + _t1) + else: + eta = np.rad2deg(keta - _t1) + phi = np.rad2deg(kphi - _t1) + chi = 2.0 * np.rad2deg(np.arcsin(np.sin(kappa / 2.0) * \ + np.sin(self.kalpha))) + + return eta, chi, phi + + def _calc_replace_angle_values(self , primaryAngles=None, + referenceAngles=None): + ''' + Fix a situation were some constant angle values have been + recorded incorrectly and need to be fixed. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + ''' + logger.info( "Running " + __name__) + angles = [] + logger.debug("referenceAngles " + str(referenceAngles)) + mappingAngles = self.instConfig.getSampleAngleMappingReferenceAngles() + + logger.debug( "mappingAngles" + str(mappingAngles)) + for ii in range(len(referenceAngles)): + replaceVal = \ + float(self.instConfig.getSampleAngleMappingReferenceAngleAttrib( \ + number= str(mappingAngles[ii]), \ + attribName='replaceValue')) + logger.debug( "primary Angles" + str(referenceAngles)) + angles.append(replaceVal* np.ones(len(referenceAngles[:,ii]),)) + logger.debug("Angles" + str( angles)) + return angles + + def fixGeoAngles(self, scan, angles): + ''' + Fix the angles using a user selected function. + :param scan: scan to set the angles for + :param angles: Array of angles to set for this scan + ''' + logger.debug( "starting " + __name__) + needToCorrect = False + refAngleNames = self.instConfig.getSampleAngleMappingReferenceAngles() + for refAngleName in refAngleNames: + alwaysFix = self.instConfig.getSampleAngleMappingAlwaysFix() + if refAngleName in scan.L or alwaysFix: + needToCorrect = True + + if needToCorrect: + logger.debug( __name__ + ": Fixing angles") + refAngles = self.getScanAngles(scan, refAngleNames) + primaryAngles = self.instConfig.getSampleAngleMappingPrimaryAngles() + functionName = self.instConfig.getSampleAngleMappingFunctionName() + functionModuleName = self.instConfig.getSampleAngleMappingFunctionModule() + logger.debug("sampleMappingFunction moduleName %s" % functionModuleName) + #Call a defined method to calculate angles from the reference angles. + moduleSource = self + if functionModuleName != None: + functionModule = importlib.import_module(functionModuleName) + logger.debug("dir(functionModule)" % dir(functionModule)) + moduleSource = functionModule + method = getattr(moduleSource, functionName) + fixedAngles = method(primaryAngles=primaryAngles, + referenceAngles=refAngles) + logger.debug ("fixed Angles: " + str(fixedAngles)) + for i in range(len(primaryAngles)): + logger.debug ("Fixing primaryAngles: %d " % primaryAngles[i]) + angles[:,primaryAngles[i]-1] = fixedAngles[i] + + def getGeoAngles(self, scan, angleNames): + """ + This function returns all of the geometry angles for the + for the scan as a N-by-num_geo array, where N is the number of scan + points and num_geo is the number of geometry motors. + """ +# scan = self.sd[scanNo] + geoAngles = self.getScanAngles(scan, angleNames) + if not (self.instConfig.getSampleAngleMappingFunctionName() == ""): + tb = None + try: + self.fixGeoAngles(scan, geoAngles) + except Exception as ex: + tb = traceback.format_exc() + raise RSMap3DException("Handling exception in getGeoAngles." + \ + "\n" + \ + str(ex) + \ + "\n" + \ + str(tb)) + logger.debug("getGeoAngles:\n" + str(geoAngles)) + return geoAngles + + def getUBMatrix(self, scan): + """ + Read UB matrix from the #G3 line from the spec file. + """ + try: + g3 = scan.G["G3"].strip().split() + g3 = np.array(list(map(float, g3))) + ub = g3.reshape(-1,3) + logger.debug("ub " +str(ub)) + return ub + except: + logger.error("Unable to read UB Matrix from G3") + logger.error( '-'*60) + traceback.print_exc(file=sys.stdout) + logger.error('-'*60) + + + def hotpixelkill(self, areaData): + """ + function to remove hot pixels from CCD frames + ADD REMOVE VALUES IF NEEDED! + :param areaData: area detector data + """ + + for pixel in self.getBadPixels(): + badLoc = pixel.getBadLocation() + replaceLoc = pixel.getReplacementLocation() + areaData[badLoc[0],badLoc[1]] = \ + areaData[replaceLoc[0],replaceLoc[1]] + + return areaData + + def loadSource(self, mapHKL=False): + ''' + This method does the work of loading data from the files. This has been + split off from the constructor to allow this to be threaded and later + canceled. + :param mapHKL: boolean to mark if the data should be mapped to HKL + ''' + # Load up the instrument configuration file + self.loadInstrumentXMLConfig() + #Load up the detector configuration file + self.loadDetectorXMLConfig() + + self.specFile = os.path.join(self.projectDir, self.projectName + \ + self.projectExt) + imageDir = os.path.join(self.projectDir, \ + IMAGE_DIR_MERGE_STR % self.projectName) + self.imageFileTmp = os.path.join(imageDir, \ + TIFF_FILE_MERGE_STR % + (self.projectName, self.projectName)) + # if needed load up the bad pixel file. + if not (self.badPixelFile is None): + + badPixelFile = PilatusBadPixelFile(self.badPixelFile) + self.badPixels = badPixelFile.getBadPixels() + + # id needed load the flat field file + if not (self.flatFieldFile is None): + self.flatFieldData = np.array(Image.open(self.flatFieldFile)).T + # Load scan information from the spec file + try: + self.sd = SpecDataFile(self.specFile) + self.mapHKL = mapHKL + maxScan = int(self.sd.getMaxScanNumber()) + logger.debug("Number of Scans" + str(maxScan)) + if self.scans is None: + self.scans = range(1, maxScan+1) + imagePath = os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName) + + self.imageBounds = {} + self.imageToBeUsed = {} + self.availableScans = [] + self.incidentEnergy = {} + self.ubMatrix = {} + self.scanType = {} + self.progress = 0 + self.progressInc = 1 + #======= ZZ, 2020/02/19, add initialization + self.progressMax = 100 + #======= + # Zero the progress bar at the beginning. + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + for scan in self.scans: + if (self.cancelLoad): + self.cancelLoad = False + raise LoadCanceledException(CANCEL_STR) + + else: + if (os.path.exists(os.path.join(imagePath, \ + SCAN_NUMBER_MERGE_STR % (self.projectName, scan) ))): + try: + curScan = self.sd.scans[str(scan)] + self.scanType[scan] = \ + self.sd.scans[str(scan)].scanCmd.split()[0] + angles = self.getGeoAngles(curScan, self.angleNames) + self.availableScans.append(scan) + if self.mapHKL==True: + self.ubMatrix[scan] = self.getUBMatrix(curScan) + if self.ubMatrix[scan] is None: + raise Sector12SpecFileException("UB matrix " + \ + "not found.") + else: + self.ubMatrix[scan] = None + self.incidentEnergy[scan] = 12398.4 /float(curScan.G['G4'].split()[3]) + _start_time = time.time() + self.imageBounds[scan] = \ + self.findImageQs(angles, \ + self.ubMatrix[scan], \ + self.incidentEnergy[scan]) + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + logger.info (('Elapsed time for Finding qs for scan %d: ' + + '%.3f seconds') % \ + (scan, (time.time() - _start_time))) + except ScanDataMissingException: + logger.error( "Scan " + str(scan) + " has no data") + #Make sure to show 100% completion + if self.progressUpdater is not None: + self.progressUpdater(self.progressMax, self.progressMax) + except IOError: + raise IOError( "Cannot open file " + str(self.specFile)) + if len(self.getAvailableScans()) == 0: + raise ScanDataMissingException("Could not find scan data for " + \ + "input file \n" + self.specFile + \ + "\nOne possible reason for this " + \ + "is that the image files are " + \ + "missing. Images are assumed " + \ + "to be in " + \ + os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName)) + + self.availableScanTypes = set(self.scanType.values()) + + + + def to_bool(self, value): + """ + Note this method found in answer to: + http://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python + Converts 'something' to boolean. Raises exception if it gets a string + it doesn't handle. + Case is ignored for strings. These string values are handled: + True: 'True', "1", "TRue", "yes", "y", "t" + False: "", "0", "faLse", "no", "n", "f" + Non-string values are passed to bool. + """ + if type(value) == type(''): + if value.lower() in ("yes", "y", "true", "t", "1"): + return True + if value.lower() in ("no", "n", "false", "f", "0", ""): + return False + raise Exception('Invalid value for boolean conversion: ' + value) + return bool(value) + + def rawmap(self,scans, angdelta=[0,0,0,0,0], + adframes=None, mask = None): + """ + read ad frames and and convert them in reciprocal space + angular coordinates are taken from the spec file + or read from the edf file header when no scan number is given (scannr=None) + """ + + if mask is None: + mask_was_none = True + #mask = [True] * len(self.getImageToBeUsed()[scans[0]]) + else: + mask_was_none = False + #sd = spec.SpecDataFile(self.specFile) + intensity = np.array([]) + + # fourc goniometer in fourc coordinates + # convention for coordinate system: + # x: upwards; + # y: along the incident beam; + # z: "outboard" (makes coordinate system right-handed). + # QConversion will set up the goniometer geometry. + # So the first argument describes the sample rotations, the second the + # detector rotations and the third the primary beam direction. + qconv = xu.experiment.QConversion(self.getSampleCircleDirections(), \ + self.getDetectorCircleDirections(), \ + self.getPrimaryBeamDirection()) + + # define experimental class for angle conversion + # + # ipdir: inplane reference direction (ipdir points into the primary beam + # direction at zero angles) + # ndir: surface normal of your sample (ndir points in a direction + # perpendicular to the primary beam and the innermost detector + # rotation axis) + en = self.getIncidentEnergy() + hxrd = xu.HXRD(self.getInplaneReferenceDirection(), \ + self.getSampleSurfaceNormalDirection(), \ + en=en[self.getAvailableScans()[0]], \ + qconv=qconv) + + + # initialize area detector properties + if (self.getDetectorPixelWidth() != None ) and \ + (self.getDistanceToDetector() != None): + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + pwidth1=self.getDetectorPixelWidth()[0], \ + pwidth2=self.getDetectorPixelWidth()[1], \ + distance=self.getDistanceToDetector(), \ + Nav=self.getNumPixelsToAverage(), \ + roi=self.getDetectorROI()) + else: + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + chpdeg1=self.getDetectorChannelsPerDegree()[0], \ + chpdeg2=self.getDetectorChannelsPerDegree()[1], \ + Nav=self.getNumPixelsToAverage(), + roi=self.getDetectorROI()) + + angleNames = self.getAngles() + scanAngle = {} + for i in range(len(angleNames)): + scanAngle[i] = np.array([]) + + offset = 0 + imageToBeUsed = self.getImageToBeUsed() + monitorName = self.getMonitorName() + monitorScaleFactor = self.getMonitorScaleFactor() + filterName = self.getFilterName() + filterScaleFactor = self.getFilterScaleFactor() + for scannr in scans: + if self.haltMap: + raise ProcessCanceledException("Process Canceled") + scan = self.sd.scans[str(scannr)] + angles = self.getGeoAngles(scan, angleNames) + scanAngle1 = {} + scanAngle2 = {} + for i in range(len(angleNames)): + scanAngle1[i] = angles[:,i] + scanAngle2[i] = [] + if monitorName != None: + monitor_data = scan.data.get(monitorName) + if monitor_data is None: + raise IOError("Did not find Monitor source '" + \ + monitorName + \ + "' in the Spec file. Make sure " + \ + "monitorName is correct in the " + \ + "instrument Config file") + if filterName != None: + filter_data = scan.data.get(filterName) + if filter_data is None: + raise IOError("Did not find filter source '" + \ + filterName + \ + "' in the Spec file. Make sure " + \ + "filterName is correct in the " + \ + "instrument Config file") + # read in the image data + arrayInitializedForScan = False + foundIndex = 0 + + if mask_was_none: + mask = [True] * len(self.getImageToBeUsed()[scannr]) + + for ind in range(len(scan.data[list(scan.data.keys())[0]])): + if imageToBeUsed[scannr][ind] and mask[ind]: + # read tif image + # Now here is something really messy for Hua's data: + # to handle the filename sequence number not starting at 0 + # This is hard coded in, it does not worth the trouble to + # change the class to be more generic. + #------------------- + if scannr in MESSYSCANS: + POINT_NUM_OFFSET = 116 + else: + POINT_NUM_OFFSET = 0 + im = Image.open(self.imageFileTmp % (scannr, scannr, ind+POINT_NUM_OFFSET)) + #------------------- + img = np.array(im.getdata()).reshape(im.size[1],im.size[0]).T + img = self.hotpixelkill(img) + ff_data = self.getFlatFieldData() + if not (ff_data is None): + img = img * ff_data + # reduce data siz + img2 = xu.blockAverage2D(img, + self.getNumPixelsToAverage()[0], \ + self.getNumPixelsToAverage()[1], \ + roi=self.getDetectorROI()) + + # apply intensity corrections + if monitorName != None: + img2 = img2 / monitor_data[ind] * monitorScaleFactor + if filterName != None: + img2 = img2 / filter_data[ind] * filterScaleFactor + + # initialize data array + if not arrayInitializedForScan: + imagesToProcess = [imageToBeUsed[scannr][i] and mask[i] for i in range(len(imageToBeUsed[scannr]))] + if not intensity.shape[0]: + intensity = np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape) + arrayInitializedForScan = True + else: + offset = intensity.shape[0] + intensity = np.concatenate( + (intensity, + (np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape))), + axis=0) + arrayInitializedForScan = True + # add data to intensity array + intensity[foundIndex+offset,:,:] = img2 + for i in range(len(angleNames)): +# logger.debug("appending angles to angle2 " + +# str(scanAngle1[i][ind])) + scanAngle2[i].append(scanAngle1[i][ind]) + foundIndex += 1 + if len(scanAngle2[0]) > 0: + for i in range(len(angleNames)): + scanAngle[i] = \ + np.concatenate((scanAngle[i], np.array(scanAngle2[i])), \ + axis=0) + # transform scan angles to reciprocal space coordinates for all detector pixels + angleList = [] + for i in range(len(angleNames)): + angleList.append(scanAngle[i]) + if self.ubMatrix[scans[0]] is None: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage()) + else: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage(), \ + UB = self.ubMatrix[scans[0]]) + + + # apply selected transform + qxTrans, qyTrans, qzTrans = \ + self.transform.do3DTransform(qx, qy, qz) + + + return qxTrans, qyTrans, qzTrans, intensity + +class LoadCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + super(LoadCanceledException, self).__init__(message) + +class Sector12SpecFileException(RSMap3DException): + ''' + Exception class to be raised if there is a problem loading information + from a spec file + file + ''' + def __init__(self, message): + super(Sector12SpecFileException, self).__init__(message) + + + From 95efb9367582bb7d54ecb4a4f96a2bbdcfc41c31 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 5 Apr 2024 12:11:59 -0500 Subject: [PATCH 06/14] scripts for doing paraview with slicing Keep them here for display slices of vti files. But it could be very Paraview version dependent. One is doing X, Y, Z slice through a point; the other tiling multiple vti file side by side with a single slice from each file. --- Scripts/paraviewPlot.py | 230 ++++++++++++++++++++++++++ Scripts/paraviewPlot_tile.py | 301 +++++++++++++++++++++++++++++++++++ 2 files changed, 531 insertions(+) create mode 100644 Scripts/paraviewPlot.py create mode 100644 Scripts/paraviewPlot_tile.py diff --git a/Scripts/paraviewPlot.py b/Scripts/paraviewPlot.py new file mode 100644 index 0000000..5806145 --- /dev/null +++ b/Scripts/paraviewPlot.py @@ -0,0 +1,230 @@ +#### import the simple module from the paraview +from paraview.simple import * +import numpy as np +import os +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'XML Image Data Reader' +#PathName = 'X:\\data\\caoyue\\20240304_NSLS_ISR\\woods\\analysis_runtime' +PathName = 'Z:\\Data\\ChoiMinju\\202404_NSLSII\\analysis_runtime' +# this is the sample name, typically SPEC filename without the suffix +SampleName = 'Continuous_Pd_film_on_STO_001' +# scan numbers exactly as they are in the .vti file name. +scans = ['27-28'] +# Is the grid based on "hkl" or "qxyz"? +grid_type = "hkl" + +# Setup some variables to use for plot slices and tile them along one main axis direction. +# this is the center of the RSM volume and where the slice would go through +slice_Origin = [0, 0, 1.9] +# normal direction of the slice +slice_normals = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] +# clipping the RSM3D volume so that we have better control of the tiling. This is the 'box' size here +clip_size = [0.4, 0.4, 0.6] + +# view from this direction in reciprocal space +view_direction= slice_normals[0] +# color scale low and high limits +color_scale_low = 1.2 +color_scale_high = 5.2 + +# Check the existence of the .vti files first; skip those that nonexist +FileNames = [] +for ScanN in scans: + # build vti file name here for each RSM volume + FileName = SampleName + f'_{ScanN}_{grid_type}.vti' + thisFileName = os.path.join(PathName, SampleName, FileName) + if os.path.isfile(thisFileName): + FileNames.append(FileName) + else: + print(f"Make sure the filename {thisFileName} is valid. ") + +_ind0 = 1 +for _ind1, FileName in enumerate(FileNames): + # build vti file name here for each RSM volume + thisFileName = os.path.join(PathName, SampleName, FileName) + vti_data = XMLImageDataReader(FileName=thisFileName) + + # get active source. + xMLImageDataReader = GetActiveSource() + + # rename source object + RenameSource(FileName, xMLImageDataReader) + + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + # uncomment following to set a specific view size + # renderView1.ViewSize = [1217, 918] + + # show data in view + vti_dataDisplay = Show(vti_data, renderView1) + # trace defaults for the display properties. + vti_dataDisplay.Representation = 'Outline' + + # reset view to fit data + renderView1.ResetCamera() + + # update the view to ensure updated data information + renderView1.Update() + + # create a new 'Calculator' + calculator1 = Calculator(Input=vti_data) + # Properties modified on calculator1 + calculator1.Function = 'log10(Scalars_)' + # show data in view + calculator1Display = Show(calculator1, renderView1) + # trace defaults for the display properties. + calculator1Display.Representation = 'Outline' + # hide data in view + Hide(vti_data, renderView1) + # hide data in view + Hide(calculator1, renderView1) + + # update the view to ensure updated data information + renderView1.Update() + + # create a new 'Clip' + clip1 = Clip(registrationName=f'Clip{_ind1+1}', Input=calculator1) + # Properties modified on clip1 + clip1.ClipType = 'Box' + # Properties modified on clip1.ClipType + clip1.ClipType.Position = list(np.array(slice_Origin) - np.array(clip_size)/2) + clip1.ClipType.Length = clip_size + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + # show data in view + clip1Display = Show(clip1, renderView1, 'UnstructuredGridRepresentation') + # get color transfer function/color map for 'Result' + resultLUT = GetColorTransferFunction('Result') + + # hide data in view + Hide(clip1, renderView1) + + # create a new 'Slice' + for slice_normal in slice_normals: + slice1 = Slice(registrationName=f'Slice{_ind0}', Input=clip1) + slice1.SliceType = 'Plane' + slice1.HyperTreeGridSlicer = 'Plane' + slice1.SliceOffsetValues = [0.0] + + # Properties modified on slice1.SliceType + slice1.SliceType.Origin = slice_Origin + slice1.SliceType.Normal = slice_normal + + # show data in view + slice1Display = Show(slice1, renderView1) + # trace defaults for the display properties. + slice1Display.Representation = 'Surface' + + # show color bar/color legend + slice1Display.SetScalarBarVisibility(renderView1, True) + + # update the view to ensure updated data information + renderView1.Update() + _ind0 += 1 + + # init the 'GridAxesRepresentation' selected for 'DataAxesGrid' + slice1Display.DataAxesGrid.GridColor = [0.0, 0.6666666666666666, 1.0] + slice1Display.DataAxesGrid.ShowTicks = 0 + slice1Display.DataAxesGrid.AxesToLabel = 0 + slice1Display.DataAxesGrid.ShowEdges = 0 + # settings for font and size for labels and titles + slice1Display.DataAxesGrid.XTitle = 'H (rlu)' + slice1Display.DataAxesGrid.YTitle = 'K (rlu)' + slice1Display.DataAxesGrid.ZTitle = 'L (rlu)' + slice1Display.DataAxesGrid.XTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.XTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.XTitleFontSize = 28 + slice1Display.DataAxesGrid.XLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.XLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.XLabelFontSize = 20 + slice1Display.DataAxesGrid.XAxisPrecision = 0 + slice1Display.DataAxesGrid.YTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.YTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.YTitleFontSize = 28 + slice1Display.DataAxesGrid.YLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.YLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.YLabelFontSize = 20 + slice1Display.DataAxesGrid.YAxisPrecision = 0 + slice1Display.DataAxesGrid.ZTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.ZTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.ZTitleFontSize = 28 + slice1Display.DataAxesGrid.ZLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.ZLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.ZLabelFontSize = 20 + slice1Display.DataAxesGrid.ZAxisPrecision = 0 + + _labelFlag = np.array([1, 2, 4]) + + # figure out what the label values would be on each axis + _tmp1 = slice_Origin[2] + _tmp2 = clip_size[2]/2 + slice1Display.DataAxesGrid.ZAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + _tmp1 = slice_Origin[0] + _tmp2 = clip_size[0]/2 + slice1Display.DataAxesGrid.XAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + _tmp1 = slice_Origin[1] + _tmp2 = clip_size[1]/2 + slice1Display.DataAxesGrid.YAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + # display all 3 lower axis side labels/titles + slice1Display.DataAxesGrid.AxesToLabel = 7 + # show axis labels + slice1Display.DataAxesGrid.GridAxesVisibility = 1 + + # show color bar/color legend + slice1Display.SetScalarBarVisibility(renderView1, True) + + # toggle 3D widget visibility (only when running from the GUI) + Hide3DWidgets(proxy=slice1.SliceType) + +# update the view to ensure updated data information +renderView1.Update() + +# Apply a preset using its name. Note this may not work as expected when presets have duplicate names. +resultLUT.ApplyPreset('Jet', True) + +#paraview.simple._DisableFirstRenderCameraReset() +# +# get active view +renderView1 = GetActiveViewOrCreate('RenderView') +# uncomment following to set a specific view size +# renderView1.ViewSize = [1217, 918] + +# reset view to fit data +renderView1.ResetCamera() + +# get color transfer function/color map for 'Result' +resultLUT = GetColorTransferFunction('Result') + +# Rescale transfer function +resultLUT.RescaleTransferFunction(color_scale_low, color_scale_high) + +# get opacity transfer function/opacity map for 'Result' +resultPWF = GetOpacityTransferFunction('Result') + +# Rescale transfer function +resultPWF.RescaleTransferFunction(color_scale_low, color_scale_high) + +# Properties modified on renderView1 +renderView1.UseColorPaletteForBackground = 0 +# Properties modified on renderView1 +renderView1.Background = [1.0, 1.0, 1.0] + +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.CameraPosition = list(np.array(slice_Origin) + slice_normals[0]) +renderView1.CameraFocalPoint = slice_Origin +#renderView1.CameraPosition = [0.0249742791056633, -1.6546449800997225, 3.9885722398757935] +#renderView1.CameraFocalPoint = [0.0249742791056633, 0.00796423852443695, 3.9885722398757935] +renderView1.CameraViewUp = [0.0, 0.0, 1.0] +renderView1.CameraParallelScale = 0.5 + + + + + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). diff --git a/Scripts/paraviewPlot_tile.py b/Scripts/paraviewPlot_tile.py new file mode 100644 index 0000000..78bc70d --- /dev/null +++ b/Scripts/paraviewPlot_tile.py @@ -0,0 +1,301 @@ +#### import the simple module from the paraview +from paraview.simple import * +import numpy as np +import os +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'XML Image Data Reader' +#PathName = 'X:\\data\\caoyue\\20240304_NSLS_ISR\\woods\\analysis_runtime' +PathName = 'Z:\\Data\\ChoiMinju\\202404_NSLSII\\analysis_runtime' +# this is the sample name, typically SPEC filename without the suffix +SampleName_list = ['Continuous_Pd_film_on_STO_001', + ] +# scan numbers exactly as they are in the .vti file name. +#scans = ['104-106', '117-119', '129-131', '141-143', '153-155', '165-167', '177-179', '189-191', '201-203', '213-215', '331-333'] +scans_list = [['27-28', '34-35', '44-45', '49-50', '55-56', '59-60']] +# Is the grid based on "hkl" or "qxyz"? +grid_type = "hkl" + +# Setup some variables to use for plot slices and tile them along one main axis direction. +# this is the center of the RSM volume and where the slice would go through +slice_Origin = [0, 0, 1.93] +# normal direction of the slice +slice_normal = [0, 0, 1] +# tiling multiple images along this direction; and default viewing direction would be another in-plane one +slice_offSet_direction = [1, 0, 0] +# clipping the RSM3D volume so that we have better control of the tiling. This is the 'box' size here +clip_size = [0.4, 0.4, 0.6] +# use the 'box' dimension in this direction as the tiling offset +offset_unit = clip_size[slice_offSet_direction.index(1)] + +# view from this direction in reciprocal space +view_direction= slice_normal +# color scale low and high limits +color_scale_low = 1.2 +color_scale_high = 5.2 + +# Check the existence of the .vti files first +FileNames = [] +LabelNames = [] +for (scans, SampleName) in zip(scans_list, SampleName_list): + for ScanN in scans: + # build vti file name here for each RSM volume + FileName = SampleName + f'_{ScanN}_{grid_type}.vti' + thisFileName = os.path.join(PathName, SampleName, FileName) + if os.path.isfile(thisFileName): + LabelNames.append(FileName) + FileNames.append(thisFileName) + else: + print(f"Make sure the filename {thisFileName} is valid. ") + +# How many slices +num_plot = len(FileNames) +# the largest offset value +offset_max = offset_unit*int(num_plot/2) + +for _ind1, (FileName, LabelName) in enumerate(zip(FileNames, LabelNames)): + # build vti file name here for each RSM volume + #thisFileName = os.path.join(PathName, SampleName, FileName) + vti_data = XMLImageDataReader(FileName=FileName) + + # offset value for this slice + _this_offset = -offset_max + offset_unit * _ind1 + # get active source. + xMLImageDataReader = GetActiveSource() + + # rename source object + RenameSource(LabelName, xMLImageDataReader) + + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + + # create a new 'Calculator' + calculator1 = Calculator(Input=vti_data) + + # Properties modified on calculator1 + calculator1.Function = 'log10(Scalars_)' + + # hide data in view + Hide(vti_data, renderView1) + + # hide data in view + Hide(calculator1, renderView1) + + # update the view to ensure updated data information + renderView1.Update() + + # create a new 'Clip' + clip1 = Clip(registrationName=f'Clip{_ind1+1}', Input=calculator1) + + # Properties modified on clip1 + clip1.ClipType = 'Box' + + # Properties modified on clip1.ClipType + clip1.ClipType.Position = list(np.array(slice_Origin) - np.array(clip_size)/2) + clip1.ClipType.Length = clip_size + + # get active view + renderView1 = GetActiveViewOrCreate('RenderView') + + # show data in view + clip1Display = Show(clip1, renderView1, 'UnstructuredGridRepresentation') + + # get color transfer function/color map for 'Result' + resultLUT = GetColorTransferFunction('Result') + + # hide data in view + Hide(clip1, renderView1) + + # update the view to ensure updated data information + renderView1.Update() + + # reset view to fit data + renderView1.ResetCamera(False) + + # create a new 'Slice' + slice1 = Slice(registrationName=f'Slice{_ind1+1}', Input=clip1) + slice1.SliceType = 'Plane' + slice1.HyperTreeGridSlicer = 'Plane' + slice1.SliceOffsetValues = [0.0] + + # init the 'Plane' selected for 'SliceType' + slice1.SliceType.Origin = slice_Origin + slice1.SliceType.Normal = slice_normal + + # init the 'Plane' selected for 'HyperTreeGridSlicer' + slice1.HyperTreeGridSlicer.Origin = slice_Origin + + # show data in view + slice1Display = Show(slice1, renderView1, 'GeometryRepresentation') + + # trace defaults for the display properties. + slice1Display.Representation = 'Surface' + slice1Display.ColorArrayName = ['POINTS', 'Result'] + slice1Display.LookupTable = resultLUT + slice1Display.SelectTCoordArray = 'None' + slice1Display.SelectNormalArray = 'None' + slice1Display.SelectTangentArray = 'None' + slice1Display.OSPRayScaleArray = 'Result' + slice1Display.OSPRayScaleFunction = 'PiecewiseFunction' + slice1Display.SelectOrientationVectors = 'None' + slice1Display.ScaleFactor = 0.02399998903274536 + slice1Display.SelectScaleArray = 'Result' + slice1Display.GlyphType = 'Arrow' + slice1Display.GlyphTableIndexArray = 'Result' + slice1Display.GaussianRadius = 0.0011999994516372682 + slice1Display.SetScaleArray = ['POINTS', 'Result'] + slice1Display.ScaleTransferFunction = 'PiecewiseFunction' + slice1Display.OpacityArray = ['POINTS', 'Result'] + slice1Display.OpacityTransferFunction = 'PiecewiseFunction' + slice1Display.DataAxesGrid = 'GridAxesRepresentation' + slice1Display.PolarAxes = 'PolarAxesRepresentation' + + # init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' + slice1Display.ScaleTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 8.617164190771732, 1.0, 0.5, 0.0] + + # init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' + slice1Display.OpacityTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 8.617164190771732, 1.0, 0.5, 0.0] + + # init the 'GridAxesRepresentation' selected for 'DataAxesGrid' + slice1Display.DataAxesGrid.GridColor = [0.0, 0.6666666666666666, 1.0] + slice1Display.DataAxesGrid.ShowTicks = 0 + slice1Display.DataAxesGrid.AxesToLabel = 0 + slice1Display.DataAxesGrid.ShowEdges = 0 + # settings for font and size for labels and titles + slice1Display.DataAxesGrid.XTitle = 'H (rlu)' + slice1Display.DataAxesGrid.YTitle = 'K (rlu)' + slice1Display.DataAxesGrid.ZTitle = 'L (rlu)' + slice1Display.DataAxesGrid.XTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.XTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.XTitleFontSize = 28 + slice1Display.DataAxesGrid.XLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.XLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.XLabelFontSize = 20 + slice1Display.DataAxesGrid.XAxisPrecision = 0 + slice1Display.DataAxesGrid.YTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.YTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.YTitleFontSize = 28 + slice1Display.DataAxesGrid.YLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.YLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.YLabelFontSize = 20 + slice1Display.DataAxesGrid.YAxisPrecision = 0 + slice1Display.DataAxesGrid.ZTitleColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.ZTitleFontFamily = 'Times' + slice1Display.DataAxesGrid.ZTitleFontSize = 28 + slice1Display.DataAxesGrid.ZLabelColor = [0.0, 0.0, 0.0] + slice1Display.DataAxesGrid.ZLabelFontFamily = 'Times' + slice1Display.DataAxesGrid.ZLabelFontSize = 20 + slice1Display.DataAxesGrid.ZAxisPrecision = 0 + + # OK, this below might not be correct, i don't quite understand + # how it works. Maybe it is not fixed? + # which axis label to display: 1 - z_min; 2 - y_min; 4 - x_min; + # 8 - z_max; 16 - y_max; 32 - x_max + #_labelFlag = np.array([4, 2, 1]) + _labelFlag = np.array([1, 2, 4]) + + # figure out what the label values would be on each axis + _tmp1 = slice_Origin[2] + _tmp2 = clip_size[2]/2 + slice1Display.DataAxesGrid.ZAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + _tmp1 = slice_Origin[0] + _tmp2 = clip_size[0]/2 + slice1Display.DataAxesGrid.XAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + _tmp1 = slice_Origin[1] + _tmp2 = clip_size[1]/2 + slice1Display.DataAxesGrid.YAxisLabels = [_tmp1-_tmp2, _tmp1, _tmp1+_tmp2] + # try to figure out which axes to label, probably not working well + if _ind1 == 0: + # label the axis-min on the lowest side of offset direction + _tmp1 = np.array(slice_offSet_direction)!=0 + _axesToLable = sum( np.multiply(_tmp1, _labelFlag) ) + slice1Display.DataAxesGrid.AxesToLabel = _axesToLable + elif _this_offset==0: + # lable the axis-min orthorgonal to both offset and slice normal direction. + _tmp1 = ( np.array(slice_offSet_direction) + np.array(slice_normal) ) == 0 + _axesToLable = sum( np.multiply(_tmp1, _labelFlag) ) + slice1Display.DataAxesGrid.AxesToLabel = _axesToLable + + # show axis labels + slice1Display.DataAxesGrid.GridAxesVisibility = 1 + + # show color bar/color legend + slice1Display.SetScalarBarVisibility(renderView1, True) + + # update the view to ensure updated data information + renderView1.Update() + + # toggle 3D widget visibility (only when running from the GUI) + Hide3DWidgets(proxy=slice1.SliceType) + + # set active source + SetActiveSource(slice1) + + # show data in view + slice1Display = Show(slice1, renderView1, 'GeometryRepresentation') + + # show color bar/color legend + slice1Display.SetScalarBarVisibility(renderView1, True) + + # Calculate how much offset this slice needs + this_slice_offset = list( np.array(slice_offSet_direction) * (_this_offset) ) + + # Properties modified on slice1Display + slice1Display.Position = this_slice_offset + + # Properties modified on slice1Display.DataAxesGrid + slice1Display.DataAxesGrid.Position = this_slice_offset + + # Properties modified on slice1Display.PolarAxes + slice1Display.PolarAxes.Translation = this_slice_offset + + # reset view to fit data + renderView1.ResetCamera(False) + + # update the view to ensure updated data information + renderView1.Update() + + +# Apply a preset using its name. Note this may not work as expected when presets have duplicate names. +resultLUT.ApplyPreset('Jet', True) + +# reset view to fit data +renderView1.ResetCamera() + +# get color transfer function/color map for 'Result' +resultLUT = GetColorTransferFunction('Result') + +# Rescale transfer function +resultLUT.RescaleTransferFunction(color_scale_low, color_scale_high) + +# Properties modified on renderView1 +renderView1.UseColorPaletteForBackground = 0 + +# Properties modified on renderView1 +renderView1.Background = [1.0, 1.0, 1.0] + +""" +# get opacity transfer function/opacity map for 'Result' +resultPWF = GetOpacityTransferFunction('Result') + +# Rescale transfer function +resultPWF.RescaleTransferFunction(color_scale_low, color_scale_high) +""" +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.CameraPosition = list(np.array(slice_Origin) + view_direction) +renderView1.CameraFocalPoint = slice_Origin +#renderView1.CameraPosition = [0.0249742791056633, -1.6546449800997225, 3.9885722398757935] +#renderView1.CameraFocalPoint = [0.0249742791056633, 0.00796423852443695, 3.9885722398757935] +renderView1.CameraViewUp = [0.0, 0.0, 1.0] +renderView1.CameraParallelScale = 0.25 + + + + + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). From 8e695b68e76c4d8d44abbecd5e83488bf15d54db Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Tue, 4 Jun 2024 13:12:06 -0500 Subject: [PATCH 07/14] Add files via upload Edit the output message showing Qx, Qy, Qz to Qx/H, Qy/K, Qz/L, as we use HKL more often. --- rsMap3D/mappers/abstractmapper.py | 280 +++++++++++++++--------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/rsMap3D/mappers/abstractmapper.py b/rsMap3D/mappers/abstractmapper.py index 9618d90..a1d3207 100644 --- a/rsMap3D/mappers/abstractmapper.py +++ b/rsMap3D/mappers/abstractmapper.py @@ -1,140 +1,140 @@ -''' - Copyright (c) 2014, UChicago Argonne, LLC - See LICENSE file. -''' -import abc -import time -from rsMap3D.transforms.unitytransform3d import UnityTransform3D -from rsMap3D.exception.rsmap3dexception import RSMap3DException - - - -class AbstractGridMapper(object): - __metaclass__ = abc.ABCMeta - ''' - This class is an abstract class around which to build a reciprocal space - mapping class using the xrayutilities module. It requires an input of the - type AbstractXrayUtilitiesDataSource provided in the rsMap3D.datasource - package. - ''' - - - def __init__(self, dataSource, \ - outputFileName, \ - nx=200, ny=201, nz=202, \ - transform = None, \ - gridWriter = None, - appConfig = None): - ''' - Constructor - :param dataSource: source of scan data - :param outputFileName: filename for output - :param nx: number of points in x direction for griidder - :param ny: number of points in y direction for griidder - :param nz: number of points in z direction for griidder - :param transform: Transform to be applied to the axes before gridding - ''' - self.appConfig = None - if not (appConfig is None): - self.appConfig = appConfig - else: - raise RSMap3DException("no AppConfig object received.") - - self.dataSource = dataSource - self.outputFileName = outputFileName - self.nx = nx - self.ny = ny - self.nz = nz - self.haltMap = False - self.progressUpdater = None - self.gridWriter = gridWriter - if transform is None: - self.transform = UnityTransform3D() - else: - self.transform = transform - - def doMap(self): - ''' - Produce a q map of the data. This is the method typically called to - run the mapper. This method calls the processMap method which is an - abstract method which needs to be defined in subclasses. - ''' - - # read and grid data with helper function - _start_time = time.time() - #rangeBounds = self.dataSource.getRangeBounds() - qx, qy, qz, gint, gridder = \ - self.processMap() - print ('Elapsed time for gridding: %.3f seconds' % \ - (time.time() - _start_time)) - - # print some information - print ('qx: ', qx.min(), ' .... ', qx.max()) - print ('qy: ', qy.min(), ' .... ', qy.max()) - print ('qz: ', qz.min(), ' .... ', qz.max()) - self.gridWriter.setData(qx, qy, qz, gint) - self.gridWriter.setFileInfo(self.getFileInfo()) - self.gridWriter.write() - - def getFileInfo(self): - return (self.dataSource.projectName, - self.dataSource.availableScans[0], - self.nx, self.ny, self.nz, - self.outputFileName) - - @abc.abstractmethod - def processMap(self, **kwargs): - """ - Abstract method for processing data. This method is called by the - method doMap. Typical access to this method is through the doMap - method. - """ - raise NotImplementedError("Subclasses should define this method") - - def setGridWriter(self, writer): - self.gridWriter = writer - - def setGridSize(self, nx, ny, nz): - """ - Set the grid size to be used for outputting data. - :param nx: number of points in x direction for griidder - :param ny: number of points in y direction for griidder - :param nz: number of points in z direction for griidder - """ - self.nx = nx - self.ny = ny - self.nz = nz - - def setProgressUpdater(self, updater): - ''' - Set the updater that will be used to maintain the progress bar value - ''' - self.progressUpdater = updater - - def setTransform(self, transform): - ''' - Set a transform which will define a mapping of q space to some other - system. The transform set here should be a subclass of - AbstractTransform3D which is defined in - rsMap3D.transform.abstracttransform3D. - :param tranfoem: - ''' - self.transform = transform - - def stopMap(self): - ''' - Set a flag that will be used to halt processing the scan using - ''' - self.haltMap = True - self.dataSource.stopMap() - -class ProcessCanceledException(RSMap3DException): - ''' - Exception Thrown when loading data is canceled. - ''' - def __init__(self, message): - ''' - Constructor - :param message: Message to be carried conveyed with exception - ''' - super(ProcessCanceledException, self).__init__(message) +''' + Copyright (c) 2014, UChicago Argonne, LLC + See LICENSE file. +''' +import abc +import time +from rsMap3D.transforms.unitytransform3d import UnityTransform3D +from rsMap3D.exception.rsmap3dexception import RSMap3DException + + + +class AbstractGridMapper(object): + __metaclass__ = abc.ABCMeta + ''' + This class is an abstract class around which to build a reciprocal space + mapping class using the xrayutilities module. It requires an input of the + type AbstractXrayUtilitiesDataSource provided in the rsMap3D.datasource + package. + ''' + + + def __init__(self, dataSource, \ + outputFileName, \ + nx=200, ny=201, nz=202, \ + transform = None, \ + gridWriter = None, + appConfig = None): + ''' + Constructor + :param dataSource: source of scan data + :param outputFileName: filename for output + :param nx: number of points in x direction for griidder + :param ny: number of points in y direction for griidder + :param nz: number of points in z direction for griidder + :param transform: Transform to be applied to the axes before gridding + ''' + self.appConfig = None + if not (appConfig is None): + self.appConfig = appConfig + else: + raise RSMap3DException("no AppConfig object received.") + + self.dataSource = dataSource + self.outputFileName = outputFileName + self.nx = nx + self.ny = ny + self.nz = nz + self.haltMap = False + self.progressUpdater = None + self.gridWriter = gridWriter + if transform is None: + self.transform = UnityTransform3D() + else: + self.transform = transform + + def doMap(self): + ''' + Produce a q map of the data. This is the method typically called to + run the mapper. This method calls the processMap method which is an + abstract method which needs to be defined in subclasses. + ''' + + # read and grid data with helper function + _start_time = time.time() + #rangeBounds = self.dataSource.getRangeBounds() + qx, qy, qz, gint, gridder = \ + self.processMap() + print ('Elapsed time for gridding: %.3f seconds' % \ + (time.time() - _start_time)) + + # print some information + print ('Qx/H: ', qx.min(), ' .... ', qx.max()) + print ('Qy/K: ', qy.min(), ' .... ', qy.max()) + print ('Qz/L: ', qz.min(), ' .... ', qz.max()) + self.gridWriter.setData(qx, qy, qz, gint) + self.gridWriter.setFileInfo(self.getFileInfo()) + self.gridWriter.write() + + def getFileInfo(self): + return (self.dataSource.projectName, + self.dataSource.availableScans[0], + self.nx, self.ny, self.nz, + self.outputFileName) + + @abc.abstractmethod + def processMap(self, **kwargs): + """ + Abstract method for processing data. This method is called by the + method doMap. Typical access to this method is through the doMap + method. + """ + raise NotImplementedError("Subclasses should define this method") + + def setGridWriter(self, writer): + self.gridWriter = writer + + def setGridSize(self, nx, ny, nz): + """ + Set the grid size to be used for outputting data. + :param nx: number of points in x direction for griidder + :param ny: number of points in y direction for griidder + :param nz: number of points in z direction for griidder + """ + self.nx = nx + self.ny = ny + self.nz = nz + + def setProgressUpdater(self, updater): + ''' + Set the updater that will be used to maintain the progress bar value + ''' + self.progressUpdater = updater + + def setTransform(self, transform): + ''' + Set a transform which will define a mapping of q space to some other + system. The transform set here should be a subclass of + AbstractTransform3D which is defined in + rsMap3D.transform.abstracttransform3D. + :param tranfoem: + ''' + self.transform = transform + + def stopMap(self): + ''' + Set a flag that will be used to halt processing the scan using + ''' + self.haltMap = True + self.dataSource.stopMap() + +class ProcessCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + ''' + Constructor + :param message: Message to be carried conveyed with exception + ''' + super(ProcessCanceledException, self).__init__(message) From c594b25df449b55b78cc2f23d0d56f421614cc5d Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Thu, 26 Mar 2026 21:58:15 -0500 Subject: [PATCH 08/14] Add a few different data sources. For S8 and S28, hdf5 files are used, one file per scan. S8 kept the Sxxx/ folder structure, S28 got rid of it. --- .../datasource/NSLSIISector4SpecDataSource.py | 1 + rsMap3D/datasource/s28waxpcsSpecDataSource.py | 585 ++++++++++++++++++ rsMap3D/datasource/s8waxpcsSpecDataSource.py | 574 +++++++++++++++++ 3 files changed, 1160 insertions(+) create mode 100644 rsMap3D/datasource/s28waxpcsSpecDataSource.py create mode 100644 rsMap3D/datasource/s8waxpcsSpecDataSource.py diff --git a/rsMap3D/datasource/NSLSIISector4SpecDataSource.py b/rsMap3D/datasource/NSLSIISector4SpecDataSource.py index 3474c8c..ca29e9d 100644 --- a/rsMap3D/datasource/NSLSIISector4SpecDataSource.py +++ b/rsMap3D/datasource/NSLSIISector4SpecDataSource.py @@ -17,6 +17,7 @@ import logging import importlib import h5py +import hdf5plugin try: from PIL import Image except ImportError: diff --git a/rsMap3D/datasource/s28waxpcsSpecDataSource.py b/rsMap3D/datasource/s28waxpcsSpecDataSource.py new file mode 100644 index 0000000..e1c94a0 --- /dev/null +++ b/rsMap3D/datasource/s28waxpcsSpecDataSource.py @@ -0,0 +1,585 @@ +''' + Copyright (c) 2012, UChicago Argonne, LLC + See LICENSE file. +''' +import os +from spec2nexus.spec import SpecDataFile +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + ScanDataMissingException +from rsMap3D.gui.rsm3dcommonstrings import CANCEL_STR +from rsMap3D.datasource.pilatusbadpixelfile import PilatusBadPixelFile +from rsMap3D.mappers.abstractmapper import ProcessCanceledException +from rsMap3D.datasource.specxmldrivendatasource import SpecXMLDrivenDataSource +import numpy as np +import xrayutilities as xu +import time +import sys,traceback +import logging +import importlib +import h5py +import hdf5plugin +try: + from PIL import Image +except ImportError: + import Image +logger = logging.getLogger(__name__) + + +# +++++++++++++++ +# Zhan Zhang (2026-03-26) +# Make changes to work with HDF5 file generated at CHEX 28-ID beamline. +# I do need to generate folder structure different to the Tiff case, +# which is : +# $(PROJECT_DIR)/images/$(SAMPLE_NAME)/Snnn/$(SAMPLE_NAME)_Sxxx_nnnnn.tiff +# Here it is : +# $(PROJECT_DIR)/images/$(SAMPLE_NAME)/$(SAMPLE_NAME)_Sxxx_00000.h5 +IMAGE_DIR_MERGE_STR = "images/%s" +#IMAGE_DIR_MERGE_STR = "hdf5s/%s" +SCAN_NUMBER_MERGE_STR = "S%03d" +#TIFF_FILE_MERGE_STR = "S%%03d/%s_S%%03d_%%05d.tif" +#hdf5 filename format string: one h5 file per scan here +# This one is when we still had Sxxx/ folder in place. +#HDF5_FILE_MERGE_STR = "S%%03d/%s_S%%03d_00000.h5" +HDF5_FILE_MERGE_STR = "%s_S%%03d_00000.h5" +# +++++++++++++++ + +class s28waxpcsSpecDataSource(SpecXMLDrivenDataSource): + ''' + Class to load data from spec file and configuration xml files from + for the way that data is collected at sector 33. + :members + ''' + + + def __init__(self, + projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs): + ''' + Constructor + :param projectDir: Directory holding the project file to open + :param projectName: First part of file name for the project + :param projectExt: File extension for the project file. + :param instConfigFile: Full path to Instrument configuration file. + :param detConfigFile: Full path to the detector configuration file + :param kwargs: Assorted keyword arguments + + :rtype: s28waxpcsSpecDataSource + ''' + super(s28waxpcsSpecDataSource, self).__init__(projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs) + + def _calc_eulerian_from_kappa(self, primaryAngles=None, + referenceAngles = None): + """ + Calculate the eulerian sample angles from the kappa stage angles. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + """ + + keta = np.deg2rad(referenceAngles[:,0]) + kappa = np.deg2rad(referenceAngles[:,1]) + kphi = np.deg2rad(referenceAngles[:,2]) + kappaParam = self.instConfig.getSampleAngleMappingParameter('kappa') + + try: + if kappaParam != None: + self.kalpha = np.deg2rad(float(kappaParam)) + else: + self.kalpha = np.deg2rad(50.000) + kappaInvertedParam = \ + self.instConfig.getSampleAngleMappingParameter('kappaInverted') + if kappaInvertedParam != None: + self.kappa_inverted = self.to_bool(kappaInvertedParam) + else: + self.kappa_inverted = False + except Exception as ex: + raise RSMap3DException("Error trying to get parameter for " + \ + "sampleAngleMappingFunction " + \ + "_calc_eulerian_from_kappa in inst config " + \ + "file\n" + \ + str(ex)) + + _t1 = np.arctan(np.tan(kappa / 2.0) * np.cos(self.kalpha)) + if self.kappa_inverted: + eta = np.rad2deg(keta + _t1) + phi = np.rad2deg(kphi + _t1) + else: + eta = np.rad2deg(keta - _t1) + phi = np.rad2deg(kphi - _t1) + chi = 2.0 * np.rad2deg(np.arcsin(np.sin(kappa / 2.0) * \ + np.sin(self.kalpha))) + + return eta, chi, phi + + def _calc_replace_angle_values(self , primaryAngles=None, + referenceAngles=None): + ''' + Fix a situation were some constant angle values have been + recorded incorrectly and need to be fixed. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + ''' + logger.info( "Running " + __name__) + angles = [] + logger.debug("referenceAngles " + str(referenceAngles)) + mappingAngles = self.instConfig.getSampleAngleMappingReferenceAngles() + + logger.debug( "mappingAngles" + str(mappingAngles)) + for ii in range(len(referenceAngles)): + replaceVal = \ + float(self.instConfig.getSampleAngleMappingReferenceAngleAttrib( \ + number= str(mappingAngles[ii]), \ + attribName='replaceValue')) + logger.debug( "primary Angles" + str(referenceAngles)) + angles.append(replaceVal* np.ones(len(referenceAngles[:,ii]),)) + logger.debug("Angles" + str( angles)) + return angles + + def fixGeoAngles(self, scan, angles): + ''' + Fix the angles using a user selected function. + :param scan: scan to set the angles for + :param angles: Array of angles to set for this scan + ''' + logger.debug( "starting " + __name__) + needToCorrect = False + refAngleNames = self.instConfig.getSampleAngleMappingReferenceAngles() + for refAngleName in refAngleNames: + alwaysFix = self.instConfig.getSampleAngleMappingAlwaysFix() + if refAngleName in scan.L or alwaysFix: + needToCorrect = True + + if needToCorrect: + logger.debug( __name__ + ": Fixing angles") + refAngles = self.getScanAngles(scan, refAngleNames) + primaryAngles = self.instConfig.getSampleAngleMappingPrimaryAngles() + functionName = self.instConfig.getSampleAngleMappingFunctionName() + functionModuleName = self.instConfig.getSampleAngleMappingFunctionModule() + logger.debug("sampleMappingFunction moduleName %s" % functionModuleName) + #Call a defined method to calculate angles from the reference angles. + moduleSource = self + if functionModuleName != None: + functionModule = importlib.import_module(functionModuleName) + logger.debug("dir(functionModule)" % dir(functionModule)) + moduleSource = functionModule + method = getattr(moduleSource, functionName) + fixedAngles = method(primaryAngles=primaryAngles, + referenceAngles=refAngles) + logger.debug ("fixed Angles: " + str(fixedAngles)) + for i in range(len(primaryAngles)): + logger.debug ("Fixing primaryAngles: %d " % primaryAngles[i]) + angles[:,primaryAngles[i]-1] = fixedAngles[i] + + def getGeoAngles(self, scan, angleNames): + """ + This function returns all of the geometry angles for the + for the scan as a N-by-num_geo array, where N is the number of scan + points and num_geo is the number of geometry motors. + """ +# scan = self.sd[scanNo] + geoAngles = self.getScanAngles(scan, angleNames) + if not (self.instConfig.getSampleAngleMappingFunctionName() == ""): + tb = None + try: + self.fixGeoAngles(scan, geoAngles) + except Exception as ex: + tb = traceback.format_exc() + raise RSMap3DException("Handling exception in getGeoAngles." + \ + "\n" + \ + str(ex) + \ + "\n" + \ + str(tb)) + logger.debug("getGeoAngles:\n" + str(geoAngles)) + return geoAngles + + def getUBMatrix(self, scan): + """ + Read UB matrix from the #G3 line from the spec file. + """ + try: + g3 = scan.G["G3"].strip().split() + g3 = np.array(list(map(float, g3))) + ub = g3.reshape(-1,3) + logger.debug("ub " +str(ub)) + return ub + except: + logger.error("Unable to read UB Matrix from G3") + logger.error( '-'*60) + traceback.print_exc(file=sys.stdout) + logger.error('-'*60) + + + def hotpixelkill(self, areaData): + """ + function to remove hot pixels from CCD frames + ADD REMOVE VALUES IF NEEDED! + :param areaData: area detector data + """ + + for pixel in self.getBadPixels(): + badLoc = pixel.getBadLocation() + replaceLoc = pixel.getReplacementLocation() + areaData[badLoc[0],badLoc[1]] = \ + areaData[replaceLoc[0],replaceLoc[1]] + + return areaData + + def loadSource(self, mapHKL=False): + ''' + This method does the work of loading data from the files. This has been + split off from the constructor to allow this to be threaded and later + canceled. + :param mapHKL: boolean to mark if the data should be mapped to HKL + ''' + # Load up the instrument configuration file + self.loadInstrumentXMLConfig() + #Load up the detector configuration file + self.loadDetectorXMLConfig() + + self.specFile = os.path.join(self.projectDir, self.projectName + \ + self.projectExt) + imageDir = os.path.join(self.projectDir, \ + IMAGE_DIR_MERGE_STR % self.projectName) + + # +++++++++++++++ + # for hdf5 file,use the same property but different format + self.imageFileTmp = os.path.join(imageDir, \ + HDF5_FILE_MERGE_STR % + (self.projectName)) + # +++++++++++++++ + # if needed load up the bad pixel file. + if not (self.badPixelFile is None): + + badPixelFile = PilatusBadPixelFile(self.badPixelFile) + self.badPixels = badPixelFile.getBadPixels() + + # id needed load the flat field file + if not (self.flatFieldFile is None): + self.flatFieldData = np.array(Image.open(self.flatFieldFile)).T + # Load scan information from the spec file + try: + self.sd = SpecDataFile(self.specFile) + self.mapHKL = mapHKL + maxScan = int(self.sd.getMaxScanNumber()) + logger.debug("Number of Scans" + str(maxScan)) + if self.scans is None: + self.scans = range(1, maxScan+1) + imagePath = os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName) + + self.imageBounds = {} + self.imageToBeUsed = {} + self.availableScans = [] + self.incidentEnergy = {} + self.ubMatrix = {} + self.scanType = {} + self.progress = 0 + self.progressInc = 1 + #======= ZZ, 2020/02/19, add initialization + self.progressMax = 100 + #======= + # Zero the progress bar at the beginning. + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + for scan in self.scans: + if (self.cancelLoad): + self.cancelLoad = False + raise LoadCanceledException(CANCEL_STR) + + else: + # Ok, this needs to be changed, too, if we get rid of Sxxx/ folder. + # I don't think it looks for the images in this section, but just to + # make sure they do exsit. + # ZZ 2026-03-26 + #if (os.path.exists(os.path.join(imagePath, \ + # SCAN_NUMBER_MERGE_STR % scan))): + if (os.path.exists( self.imageFileTmp % (scan) )): + try: + curScan = self.sd.scans[str(scan)] + self.scanType[scan] = \ + self.sd.scans[str(scan)].scanCmd.split()[0] + angles = self.getGeoAngles(curScan, self.angleNames) + self.availableScans.append(scan) + if self.mapHKL==True: + self.ubMatrix[scan] = self.getUBMatrix(curScan) + if self.ubMatrix[scan] is None: + raise s28waxpcsSpecFileException("UB matrix " + \ + "not found.") + else: + self.ubMatrix[scan] = None + self.incidentEnergy[scan] = 12398.4 /float(curScan.G['G4'].split()[3]) + _start_time = time.time() + self.imageBounds[scan] = \ + self.findImageQs(angles, \ + self.ubMatrix[scan], \ + self.incidentEnergy[scan]) + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + logger.info (('Elapsed time for Finding qs for scan %d: ' + + '%.3f seconds') % \ + (scan, (time.time() - _start_time))) + except ScanDataMissingException: + logger.error( "Scan " + str(scan) + " has no data") + #Make sure to show 100% completion + if self.progressUpdater is not None: + self.progressUpdater(self.progressMax, self.progressMax) + except IOError: + raise IOError( "Cannot open file " + str(self.specFile)) + if len(self.getAvailableScans()) == 0: + raise ScanDataMissingException("Could not find scan data for " + \ + "input file \n" + self.specFile + \ + "\nOne possible reason for this " + \ + "is that the image files are " + \ + "missing. Images are assumed " + \ + "to be in " + \ + os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName)) + + self.availableScanTypes = set(self.scanType.values()) + + + + def to_bool(self, value): + """ + Note this method found in answer to: + http://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python + Converts 'something' to boolean. Raises exception if it gets a string + it doesn't handle. + Case is ignored for strings. These string values are handled: + True: 'True', "1", "TRue", "yes", "y", "t" + False: "", "0", "faLse", "no", "n", "f" + Non-string values are passed to bool. + """ + if type(value) == type(''): + if value.lower() in ("yes", "y", "true", "t", "1"): + return True + if value.lower() in ("no", "n", "false", "f", "0", ""): + return False + raise Exception('Invalid value for boolean conversion: ' + value) + return bool(value) + + def rawmap(self,scans, angdelta=[0,0,0,0,0], + adframes=None, mask = None): + """ + read ad frames and and convert them in reciprocal space + angular coordinates are taken from the spec file + or read from the edf file header when no scan number is given (scannr=None) + """ + + if mask is None: + mask_was_none = True + #mask = [True] * len(self.getImageToBeUsed()[scans[0]]) + else: + mask_was_none = False + #sd = spec.SpecDataFile(self.specFile) + intensity = np.array([]) + + # fourc goniometer in fourc coordinates + # convention for coordinate system: + # x: upwards; + # y: along the incident beam; + # z: "outboard" (makes coordinate system right-handed). + # QConversion will set up the goniometer geometry. + # So the first argument describes the sample rotations, the second the + # detector rotations and the third the primary beam direction. + qconv = xu.experiment.QConversion(self.getSampleCircleDirections(), \ + self.getDetectorCircleDirections(), \ + self.getPrimaryBeamDirection()) + + # define experimental class for angle conversion + # + # ipdir: inplane reference direction (ipdir points into the primary beam + # direction at zero angles) + # ndir: surface normal of your sample (ndir points in a direction + # perpendicular to the primary beam and the innermost detector + # rotation axis) + en = self.getIncidentEnergy() + hxrd = xu.HXRD(self.getInplaneReferenceDirection(), \ + self.getSampleSurfaceNormalDirection(), \ + en=en[self.getAvailableScans()[0]], \ + qconv=qconv) + + + # initialize area detector properties + if (self.getDetectorPixelWidth() != None ) and \ + (self.getDistanceToDetector() != None): + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + pwidth1=self.getDetectorPixelWidth()[0], \ + pwidth2=self.getDetectorPixelWidth()[1], \ + distance=self.getDistanceToDetector(), \ + Nav=self.getNumPixelsToAverage(), \ + roi=self.getDetectorROI()) + else: + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + chpdeg1=self.getDetectorChannelsPerDegree()[0], \ + chpdeg2=self.getDetectorChannelsPerDegree()[1], \ + Nav=self.getNumPixelsToAverage(), + roi=self.getDetectorROI()) + + angleNames = self.getAngles() + scanAngle = {} + for i in range(len(angleNames)): + scanAngle[i] = np.array([]) + + offset = 0 + imageToBeUsed = self.getImageToBeUsed() + monitorName = self.getMonitorName() + monitorScaleFactor = self.getMonitorScaleFactor() + filterName = self.getFilterName() + filterScaleFactor = self.getFilterScaleFactor() + for scannr in scans: + if self.haltMap: + raise ProcessCanceledException("Process Canceled") + scan = self.sd.scans[str(scannr)] + angles = self.getGeoAngles(scan, angleNames) + scanAngle1 = {} + scanAngle2 = {} + for i in range(len(angleNames)): + scanAngle1[i] = angles[:,i] + scanAngle2[i] = [] + if monitorName != None: + monitor_data = scan.data.get(monitorName) + if monitor_data is None: + raise IOError("Did not find Monitor source '" + \ + monitorName + \ + "' in the Spec file. Make sure " + \ + "monitorName is correct in the " + \ + "instrument Config file") + if filterName != None: + filter_data = scan.data.get(filterName) + if filter_data is None: + raise IOError("Did not find filter source '" + \ + filterName + \ + "' in the Spec file. Make sure " + \ + "filterName is correct in the " + \ + "instrument Config file") + # read in the image data + arrayInitializedForScan = False + foundIndex = 0 + + if mask_was_none: + mask = [True] * len(self.getImageToBeUsed()[scannr]) + + # +++++++++++++++ + # Read HDF5 file here, brutal force. Need better handle. + h5file = self.imageFileTmp % (scannr) + h5data = h5py.File(h5file, "r") + scan_images = h5data["entry"]["data"]["data"] + # +++++++++++++++ + + + for ind in range(len(scan.data[list(scan.data.keys())[0]])): + if imageToBeUsed[scannr][ind] and mask[ind]: + # +++++++++++++++ + # Read HDF5 file here + #im = Image.open(self.imageFileTmp % (scannr, scannr, ind)) + #img = np.array(im.getdata()).reshape(im.size[1],im.size[0]).T + #h5file = self.imageFileTmp % (scannr, scannr, ind) + #h5data = h5py.File(h5file, "r") + #im = h5data["entry"]["data"]["data"][0,:,:] + img = np.array(scan_images[ind,:,:]).T + # +++++++++++++++ + + img = self.hotpixelkill(img) + ff_data = self.getFlatFieldData() + if not (ff_data is None): + img = img * ff_data + # reduce data siz + img2 = xu.blockAverage2D(img, + self.getNumPixelsToAverage()[0], \ + self.getNumPixelsToAverage()[1], \ + roi=self.getDetectorROI()) + + # apply intensity corrections + if monitorName != None: + img2 = img2 / monitor_data[ind] * monitorScaleFactor + if filterName != None: + img2 = img2 / filter_data[ind] * filterScaleFactor + + # initialize data array + if not arrayInitializedForScan: + imagesToProcess = [imageToBeUsed[scannr][i] and mask[i] for i in range(len(imageToBeUsed[scannr]))] + if not intensity.shape[0]: + intensity = np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape) + arrayInitializedForScan = True + else: + offset = intensity.shape[0] + intensity = np.concatenate( + (intensity, + (np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape))), + axis=0) + arrayInitializedForScan = True + # add data to intensity array + intensity[foundIndex+offset,:,:] = img2 + for i in range(len(angleNames)): +# logger.debug("appending angles to angle2 " + +# str(scanAngle1[i][ind])) + scanAngle2[i].append(scanAngle1[i][ind]) + foundIndex += 1 + if len(scanAngle2[0]) > 0: + for i in range(len(angleNames)): + scanAngle[i] = \ + np.concatenate((scanAngle[i], np.array(scanAngle2[i])), \ + axis=0) + # transform scan angles to reciprocal space coordinates for all detector pixels + angleList = [] + for i in range(len(angleNames)): + angleList.append(scanAngle[i]) + if self.ubMatrix[scans[0]] is None: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage()) + else: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage(), \ + UB = self.ubMatrix[scans[0]]) + + + # apply selected transform + qxTrans, qyTrans, qzTrans = \ + self.transform.do3DTransform(qx, qy, qz) + + + return qxTrans, qyTrans, qzTrans, intensity + +class LoadCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + super(LoadCanceledException, self).__init__(message) + +class s28waxpcsSpecFileException(RSMap3DException): + ''' + Exception class to be raised if there is a problem loading information + from a spec file + file + ''' + def __init__(self, message): + super(s28waxpcsSpecFileException, self).__init__(message) + + + diff --git a/rsMap3D/datasource/s8waxpcsSpecDataSource.py b/rsMap3D/datasource/s8waxpcsSpecDataSource.py new file mode 100644 index 0000000..bde8dbd --- /dev/null +++ b/rsMap3D/datasource/s8waxpcsSpecDataSource.py @@ -0,0 +1,574 @@ +''' + Copyright (c) 2012, UChicago Argonne, LLC + See LICENSE file. +''' +import os +from spec2nexus.spec import SpecDataFile +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + ScanDataMissingException +from rsMap3D.gui.rsm3dcommonstrings import CANCEL_STR +from rsMap3D.datasource.pilatusbadpixelfile import PilatusBadPixelFile +from rsMap3D.mappers.abstractmapper import ProcessCanceledException +from rsMap3D.datasource.specxmldrivendatasource import SpecXMLDrivenDataSource +import numpy as np +import xrayutilities as xu +import time +import sys,traceback +import logging +import importlib +import h5py +import hdf5plugin +try: + from PIL import Image +except ImportError: + import Image +logger = logging.getLogger(__name__) + + +# +++++++++++++++ +# Make changes to work with HDF5 from 8-ID-E WA-XPCS data, brutal forced +# I do need to generate folder structure simular to our Tiff case +# i.e. $(PROJECT_DIR)/images/$(SAMPLE_NAME)/Snnn/*.h5 +IMAGE_DIR_MERGE_STR = "images/%s" +#IMAGE_DIR_MERGE_STR = "hdf5s/%s" +SCAN_NUMBER_MERGE_STR = "S%03d" +#TIFF_FILE_MERGE_STR = "S%%03d/%s_S%%03d_%%05d.tif" +#hdf5 filename format string: one h5 file per scan here +HDF5_FILE_MERGE_STR = "S%%03d/%s_S%%03d_00000.h5" +# +++++++++++++++ + +class s8waxpcsSpecDataSource(SpecXMLDrivenDataSource): + ''' + Class to load data from spec file and configuration xml files from + for the way that data is collected at sector 33. + :members + ''' + + + def __init__(self, + projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs): + ''' + Constructor + :param projectDir: Directory holding the project file to open + :param projectName: First part of file name for the project + :param projectExt: File extension for the project file. + :param instConfigFile: Full path to Instrument configuration file. + :param detConfigFile: Full path to the detector configuration file + :param kwargs: Assorted keyword arguments + + :rtype: s8waxpcsSpecDataSource + ''' + super(s8waxpcsSpecDataSource, self).__init__(projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs) + + def _calc_eulerian_from_kappa(self, primaryAngles=None, + referenceAngles = None): + """ + Calculate the eulerian sample angles from the kappa stage angles. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + """ + + keta = np.deg2rad(referenceAngles[:,0]) + kappa = np.deg2rad(referenceAngles[:,1]) + kphi = np.deg2rad(referenceAngles[:,2]) + kappaParam = self.instConfig.getSampleAngleMappingParameter('kappa') + + try: + if kappaParam != None: + self.kalpha = np.deg2rad(float(kappaParam)) + else: + self.kalpha = np.deg2rad(50.000) + kappaInvertedParam = \ + self.instConfig.getSampleAngleMappingParameter('kappaInverted') + if kappaInvertedParam != None: + self.kappa_inverted = self.to_bool(kappaInvertedParam) + else: + self.kappa_inverted = False + except Exception as ex: + raise RSMap3DException("Error trying to get parameter for " + \ + "sampleAngleMappingFunction " + \ + "_calc_eulerian_from_kappa in inst config " + \ + "file\n" + \ + str(ex)) + + _t1 = np.arctan(np.tan(kappa / 2.0) * np.cos(self.kalpha)) + if self.kappa_inverted: + eta = np.rad2deg(keta + _t1) + phi = np.rad2deg(kphi + _t1) + else: + eta = np.rad2deg(keta - _t1) + phi = np.rad2deg(kphi - _t1) + chi = 2.0 * np.rad2deg(np.arcsin(np.sin(kappa / 2.0) * \ + np.sin(self.kalpha))) + + return eta, chi, phi + + def _calc_replace_angle_values(self , primaryAngles=None, + referenceAngles=None): + ''' + Fix a situation were some constant angle values have been + recorded incorrectly and need to be fixed. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + ''' + logger.info( "Running " + __name__) + angles = [] + logger.debug("referenceAngles " + str(referenceAngles)) + mappingAngles = self.instConfig.getSampleAngleMappingReferenceAngles() + + logger.debug( "mappingAngles" + str(mappingAngles)) + for ii in range(len(referenceAngles)): + replaceVal = \ + float(self.instConfig.getSampleAngleMappingReferenceAngleAttrib( \ + number= str(mappingAngles[ii]), \ + attribName='replaceValue')) + logger.debug( "primary Angles" + str(referenceAngles)) + angles.append(replaceVal* np.ones(len(referenceAngles[:,ii]),)) + logger.debug("Angles" + str( angles)) + return angles + + def fixGeoAngles(self, scan, angles): + ''' + Fix the angles using a user selected function. + :param scan: scan to set the angles for + :param angles: Array of angles to set for this scan + ''' + logger.debug( "starting " + __name__) + needToCorrect = False + refAngleNames = self.instConfig.getSampleAngleMappingReferenceAngles() + for refAngleName in refAngleNames: + alwaysFix = self.instConfig.getSampleAngleMappingAlwaysFix() + if refAngleName in scan.L or alwaysFix: + needToCorrect = True + + if needToCorrect: + logger.debug( __name__ + ": Fixing angles") + refAngles = self.getScanAngles(scan, refAngleNames) + primaryAngles = self.instConfig.getSampleAngleMappingPrimaryAngles() + functionName = self.instConfig.getSampleAngleMappingFunctionName() + functionModuleName = self.instConfig.getSampleAngleMappingFunctionModule() + logger.debug("sampleMappingFunction moduleName %s" % functionModuleName) + #Call a defined method to calculate angles from the reference angles. + moduleSource = self + if functionModuleName != None: + functionModule = importlib.import_module(functionModuleName) + logger.debug("dir(functionModule)" % dir(functionModule)) + moduleSource = functionModule + method = getattr(moduleSource, functionName) + fixedAngles = method(primaryAngles=primaryAngles, + referenceAngles=refAngles) + logger.debug ("fixed Angles: " + str(fixedAngles)) + for i in range(len(primaryAngles)): + logger.debug ("Fixing primaryAngles: %d " % primaryAngles[i]) + angles[:,primaryAngles[i]-1] = fixedAngles[i] + + def getGeoAngles(self, scan, angleNames): + """ + This function returns all of the geometry angles for the + for the scan as a N-by-num_geo array, where N is the number of scan + points and num_geo is the number of geometry motors. + """ +# scan = self.sd[scanNo] + geoAngles = self.getScanAngles(scan, angleNames) + if not (self.instConfig.getSampleAngleMappingFunctionName() == ""): + tb = None + try: + self.fixGeoAngles(scan, geoAngles) + except Exception as ex: + tb = traceback.format_exc() + raise RSMap3DException("Handling exception in getGeoAngles." + \ + "\n" + \ + str(ex) + \ + "\n" + \ + str(tb)) + logger.debug("getGeoAngles:\n" + str(geoAngles)) + return geoAngles + + def getUBMatrix(self, scan): + """ + Read UB matrix from the #G3 line from the spec file. + """ + try: + g3 = scan.G["G3"].strip().split() + g3 = np.array(list(map(float, g3))) + ub = g3.reshape(-1,3) + logger.debug("ub " +str(ub)) + return ub + except: + logger.error("Unable to read UB Matrix from G3") + logger.error( '-'*60) + traceback.print_exc(file=sys.stdout) + logger.error('-'*60) + + + def hotpixelkill(self, areaData): + """ + function to remove hot pixels from CCD frames + ADD REMOVE VALUES IF NEEDED! + :param areaData: area detector data + """ + + for pixel in self.getBadPixels(): + badLoc = pixel.getBadLocation() + replaceLoc = pixel.getReplacementLocation() + areaData[badLoc[0],badLoc[1]] = \ + areaData[replaceLoc[0],replaceLoc[1]] + + return areaData + + def loadSource(self, mapHKL=False): + ''' + This method does the work of loading data from the files. This has been + split off from the constructor to allow this to be threaded and later + canceled. + :param mapHKL: boolean to mark if the data should be mapped to HKL + ''' + # Load up the instrument configuration file + self.loadInstrumentXMLConfig() + #Load up the detector configuration file + self.loadDetectorXMLConfig() + + self.specFile = os.path.join(self.projectDir, self.projectName + \ + self.projectExt) + imageDir = os.path.join(self.projectDir, \ + IMAGE_DIR_MERGE_STR % self.projectName) + + # +++++++++++++++ + # for hdf5 file,use the same property but different format + self.imageFileTmp = os.path.join(imageDir, \ + HDF5_FILE_MERGE_STR % + (self.projectName)) + # +++++++++++++++ + # if needed load up the bad pixel file. + if not (self.badPixelFile is None): + + badPixelFile = PilatusBadPixelFile(self.badPixelFile) + self.badPixels = badPixelFile.getBadPixels() + + # id needed load the flat field file + if not (self.flatFieldFile is None): + self.flatFieldData = np.array(Image.open(self.flatFieldFile)).T + # Load scan information from the spec file + try: + self.sd = SpecDataFile(self.specFile) + self.mapHKL = mapHKL + maxScan = int(self.sd.getMaxScanNumber()) + logger.debug("Number of Scans" + str(maxScan)) + if self.scans is None: + self.scans = range(1, maxScan+1) + imagePath = os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName) + + self.imageBounds = {} + self.imageToBeUsed = {} + self.availableScans = [] + self.incidentEnergy = {} + self.ubMatrix = {} + self.scanType = {} + self.progress = 0 + self.progressInc = 1 + #======= ZZ, 2020/02/19, add initialization + self.progressMax = 100 + #======= + # Zero the progress bar at the beginning. + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + for scan in self.scans: + if (self.cancelLoad): + self.cancelLoad = False + raise LoadCanceledException(CANCEL_STR) + + else: + if (os.path.exists(os.path.join(imagePath, \ + SCAN_NUMBER_MERGE_STR % scan))): + try: + curScan = self.sd.scans[str(scan)] + self.scanType[scan] = \ + self.sd.scans[str(scan)].scanCmd.split()[0] + angles = self.getGeoAngles(curScan, self.angleNames) + self.availableScans.append(scan) + if self.mapHKL==True: + self.ubMatrix[scan] = self.getUBMatrix(curScan) + if self.ubMatrix[scan] is None: + raise s8waxpcsSpecFileException("UB matrix " + \ + "not found.") + else: + self.ubMatrix[scan] = None + self.incidentEnergy[scan] = 12398.4 /float(curScan.G['G4'].split()[3]) + _start_time = time.time() + self.imageBounds[scan] = \ + self.findImageQs(angles, \ + self.ubMatrix[scan], \ + self.incidentEnergy[scan]) + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + logger.info (('Elapsed time for Finding qs for scan %d: ' + + '%.3f seconds') % \ + (scan, (time.time() - _start_time))) + except ScanDataMissingException: + logger.error( "Scan " + str(scan) + " has no data") + #Make sure to show 100% completion + if self.progressUpdater is not None: + self.progressUpdater(self.progressMax, self.progressMax) + except IOError: + raise IOError( "Cannot open file " + str(self.specFile)) + if len(self.getAvailableScans()) == 0: + raise ScanDataMissingException("Could not find scan data for " + \ + "input file \n" + self.specFile + \ + "\nOne possible reason for this " + \ + "is that the image files are " + \ + "missing. Images are assumed " + \ + "to be in " + \ + os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName)) + + self.availableScanTypes = set(self.scanType.values()) + + + + def to_bool(self, value): + """ + Note this method found in answer to: + http://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python + Converts 'something' to boolean. Raises exception if it gets a string + it doesn't handle. + Case is ignored for strings. These string values are handled: + True: 'True', "1", "TRue", "yes", "y", "t" + False: "", "0", "faLse", "no", "n", "f" + Non-string values are passed to bool. + """ + if type(value) == type(''): + if value.lower() in ("yes", "y", "true", "t", "1"): + return True + if value.lower() in ("no", "n", "false", "f", "0", ""): + return False + raise Exception('Invalid value for boolean conversion: ' + value) + return bool(value) + + def rawmap(self,scans, angdelta=[0,0,0,0,0], + adframes=None, mask = None): + """ + read ad frames and and convert them in reciprocal space + angular coordinates are taken from the spec file + or read from the edf file header when no scan number is given (scannr=None) + """ + + if mask is None: + mask_was_none = True + #mask = [True] * len(self.getImageToBeUsed()[scans[0]]) + else: + mask_was_none = False + #sd = spec.SpecDataFile(self.specFile) + intensity = np.array([]) + + # fourc goniometer in fourc coordinates + # convention for coordinate system: + # x: upwards; + # y: along the incident beam; + # z: "outboard" (makes coordinate system right-handed). + # QConversion will set up the goniometer geometry. + # So the first argument describes the sample rotations, the second the + # detector rotations and the third the primary beam direction. + qconv = xu.experiment.QConversion(self.getSampleCircleDirections(), \ + self.getDetectorCircleDirections(), \ + self.getPrimaryBeamDirection()) + + # define experimental class for angle conversion + # + # ipdir: inplane reference direction (ipdir points into the primary beam + # direction at zero angles) + # ndir: surface normal of your sample (ndir points in a direction + # perpendicular to the primary beam and the innermost detector + # rotation axis) + en = self.getIncidentEnergy() + hxrd = xu.HXRD(self.getInplaneReferenceDirection(), \ + self.getSampleSurfaceNormalDirection(), \ + en=en[self.getAvailableScans()[0]], \ + qconv=qconv) + + + # initialize area detector properties + if (self.getDetectorPixelWidth() != None ) and \ + (self.getDistanceToDetector() != None): + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + pwidth1=self.getDetectorPixelWidth()[0], \ + pwidth2=self.getDetectorPixelWidth()[1], \ + distance=self.getDistanceToDetector(), \ + Nav=self.getNumPixelsToAverage(), \ + roi=self.getDetectorROI()) + else: + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + chpdeg1=self.getDetectorChannelsPerDegree()[0], \ + chpdeg2=self.getDetectorChannelsPerDegree()[1], \ + Nav=self.getNumPixelsToAverage(), + roi=self.getDetectorROI()) + + angleNames = self.getAngles() + scanAngle = {} + for i in range(len(angleNames)): + scanAngle[i] = np.array([]) + + offset = 0 + imageToBeUsed = self.getImageToBeUsed() + monitorName = self.getMonitorName() + monitorScaleFactor = self.getMonitorScaleFactor() + filterName = self.getFilterName() + filterScaleFactor = self.getFilterScaleFactor() + for scannr in scans: + if self.haltMap: + raise ProcessCanceledException("Process Canceled") + scan = self.sd.scans[str(scannr)] + angles = self.getGeoAngles(scan, angleNames) + scanAngle1 = {} + scanAngle2 = {} + for i in range(len(angleNames)): + scanAngle1[i] = angles[:,i] + scanAngle2[i] = [] + if monitorName != None: + monitor_data = scan.data.get(monitorName) + if monitor_data is None: + raise IOError("Did not find Monitor source '" + \ + monitorName + \ + "' in the Spec file. Make sure " + \ + "monitorName is correct in the " + \ + "instrument Config file") + if filterName != None: + filter_data = scan.data.get(filterName) + if filter_data is None: + raise IOError("Did not find filter source '" + \ + filterName + \ + "' in the Spec file. Make sure " + \ + "filterName is correct in the " + \ + "instrument Config file") + # read in the image data + arrayInitializedForScan = False + foundIndex = 0 + + if mask_was_none: + mask = [True] * len(self.getImageToBeUsed()[scannr]) + + # +++++++++++++++ + # Read HDF5 file here + h5file = self.imageFileTmp % (scannr, scannr) + h5data = h5py.File(h5file, "r") + scan_images = h5data["entry"]["data"]["data"] + # +++++++++++++++ + + + for ind in range(len(scan.data[list(scan.data.keys())[0]])): + if imageToBeUsed[scannr][ind] and mask[ind]: + # +++++++++++++++ + # Read HDF5 file here + #im = Image.open(self.imageFileTmp % (scannr, scannr, ind)) + #img = np.array(im.getdata()).reshape(im.size[1],im.size[0]).T + #h5file = self.imageFileTmp % (scannr, scannr, ind) + #h5data = h5py.File(h5file, "r") + #im = h5data["entry"]["data"]["data"][0,:,:] + img = np.array(scan_images[ind,:,:]).T + # +++++++++++++++ + + img = self.hotpixelkill(img) + ff_data = self.getFlatFieldData() + if not (ff_data is None): + img = img * ff_data + # reduce data siz + img2 = xu.blockAverage2D(img, + self.getNumPixelsToAverage()[0], \ + self.getNumPixelsToAverage()[1], \ + roi=self.getDetectorROI()) + + # apply intensity corrections + if monitorName != None: + img2 = img2 / monitor_data[ind] * monitorScaleFactor + if filterName != None: + img2 = img2 / filter_data[ind] * filterScaleFactor + + # initialize data array + if not arrayInitializedForScan: + imagesToProcess = [imageToBeUsed[scannr][i] and mask[i] for i in range(len(imageToBeUsed[scannr]))] + if not intensity.shape[0]: + intensity = np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape) + arrayInitializedForScan = True + else: + offset = intensity.shape[0] + intensity = np.concatenate( + (intensity, + (np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape))), + axis=0) + arrayInitializedForScan = True + # add data to intensity array + intensity[foundIndex+offset,:,:] = img2 + for i in range(len(angleNames)): +# logger.debug("appending angles to angle2 " + +# str(scanAngle1[i][ind])) + scanAngle2[i].append(scanAngle1[i][ind]) + foundIndex += 1 + if len(scanAngle2[0]) > 0: + for i in range(len(angleNames)): + scanAngle[i] = \ + np.concatenate((scanAngle[i], np.array(scanAngle2[i])), \ + axis=0) + # transform scan angles to reciprocal space coordinates for all detector pixels + angleList = [] + for i in range(len(angleNames)): + angleList.append(scanAngle[i]) + if self.ubMatrix[scans[0]] is None: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage()) + else: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage(), \ + UB = self.ubMatrix[scans[0]]) + + + # apply selected transform + qxTrans, qyTrans, qzTrans = \ + self.transform.do3DTransform(qx, qy, qz) + + + return qxTrans, qyTrans, qzTrans, intensity + +class LoadCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + super(LoadCanceledException, self).__init__(message) + +class s8waxpcsSpecFileException(RSMap3DException): + ''' + Exception class to be raised if there is a problem loading information + from a spec file + file + ''' + def __init__(self, message): + super(s8waxpcsSpecFileException, self).__init__(message) + + + From 5070d06850454d41d72e283b3aa44639ac91e93d Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 11:50:21 -0500 Subject: [PATCH 09/14] Add S28 entry to the list --- rsMap3D/gui/input/fileinputcontroller.py | 2 + rsMap3D/gui/input/s28specscanfileform.py | 400 +++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 rsMap3D/gui/input/s28specscanfileform.py diff --git a/rsMap3D/gui/input/fileinputcontroller.py b/rsMap3D/gui/input/fileinputcontroller.py index f55d7c4..739e175 100644 --- a/rsMap3D/gui/input/fileinputcontroller.py +++ b/rsMap3D/gui/input/fileinputcontroller.py @@ -19,6 +19,7 @@ from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D # Input forms Looking for a way to set these up. from rsMap3D.gui.input.s33specscanfileform import S33SpecScanFileForm +from rsMap3D.gui.input.s28specscanfileform import S28SpecScanFileForm from rsMap3D.gui.input.s12specscanfileform import S12SpecScanFileForm from rsMap3D.gui.input.s34hdfescanfileform import S34HDFEScanFileForm from rsMap3D.gui.input.s1highenergydiffractionform import S1HighEnergyDiffractionForm @@ -59,6 +60,7 @@ def __init__(self, parent=None, appConfig=None): #Build a list of fileForms self.fileForms = [] self.fileForms.append(S33SpecScanFileForm) + self.fileForms.append(S28SpecScanFileForm) self.fileForms.append(S12SpecScanFileForm) self.fileForms.append(S34HDFEScanFileForm) self.fileForms.append(S1HighEnergyDiffractionForm) diff --git a/rsMap3D/gui/input/s28specscanfileform.py b/rsMap3D/gui/input/s28specscanfileform.py new file mode 100644 index 0000000..9db968e --- /dev/null +++ b/rsMap3D/gui/input/s28specscanfileform.py @@ -0,0 +1,400 @@ +''' + Copyright (c) 2014, UChicago Argonne, LLC + See LICENSE file. +''' +import os + +import logging +from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR +from rsMap3D.gui.output.processpowderscanform import ProcessPowderScanForm +logger = logging.getLogger(__name__) +import PyQt5.QtGui as qtGui +import PyQt5.QtCore as qtCore +import PyQt5.QtWidgets as qtWidgets + +from rsMap3D.gui.rsm3dcommonstrings import WARNING_STR, BROWSE_STR,\ + COMMA_STR, QLINEEDIT_COLOR_STYLE, BLACK, RED, EMPTY_STR,\ + BAD_PIXEL_FILE_FILTER, SELECT_BAD_PIXEL_TITLE, SELECT_FLAT_FIELD_TITLE,\ + TIFF_FILE_FILTER +from rsMap3D.datasource.Sector28SpecDataSource import Sector28SpecDataSource +from rsMap3D.transforms.unitytransform3d import UnityTransform3D +from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D +from rsMap3D.gui.input.specxmldrivenfileform import SpecXMLDrivenFileForm +from rsMap3D.gui.output.processvtioutputform import ProcessVTIOutputForm +from rsMap3D.gui.output.processimagestackform import ProcessImageStackForm +from PyQt5.QtWidgets import QAbstractButton + +class S28SpecScanFileForm(SpecXMLDrivenFileForm): + ''' + This class presents information for selecting input files + ''' + + FORM_TITLE = "Sector 28 Spec/XML Setup" + + #UPDATE_PROGRESS_SIGNAL = "updateProgress" + # Regular expressions for string validation + PIX_AVG_REGEXP_1 = "^(\d*,*)+$" + PIX_AVG_REGEXP_2 = "^((\d)+,*){2}$" + #Strings for Text Widgets + + NONE_RADIO_NAME = "None" + BAD_PIXEL_RADIO_NAME = "Bad Pixel File" + FLAT_FIELD_RADIO_NAME = "Flat Field Correction" + + @staticmethod + def createInstance(parent=None, appConfig=None): + return S28SpecScanFileForm(parent = parent, appConfig=appConfig) + + def __init__(self, **kwargs): + ''' + Constructor - Layout Widgets on the page and link actions + ''' + logger.debug(METHOD_ENTER_STR) + super(S28SpecScanFileForm, self).__init__(**kwargs) + + #Initialize parameters + self.projectionDirection = [0,0,1] + + #Initialize a couple of widgets to do setup. + self.noFieldRadio.setChecked(True) + self._fieldCorrectionTypeChanged(*(self.noFieldRadio,)) + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _badPixelFileChanged(self): + ''' + Do some verification when the bad pixel file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.badPixelFileTxt.text()) or \ + self.badPixelFileTxt.text() == EMPTY_STR: + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the bad pixel " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + + @qtCore.pyqtSlot() + def _browseBadPixelFileName(self): + ''' + Launch file browser for bad pixel file + ''' + logger.debug(METHOD_ENTER_STR) + if self.badPixelFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.badPixelFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.badPixelFileTxt.setText(fileName) + self.badPixelFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _browseFlatFieldFileName(self): + ''' + Launch file browser for Flat field file + ''' + logger.debug(METHOD_ENTER_STR) + if self.flatFieldFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_FLAT_FIELD_TITLE, \ + filter=TIFF_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.flatFieldFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, + SELECT_FLAT_FIELD_TITLE, + filter=TIFF_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.flatFieldFileTxt.setText(fileName) + self.flatFieldFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + def checkOkToLoad(self): + ''' + Make sure we have valid file names for project, instrument config, + and the detector config. If we do enable load button. If not disable + the load button + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + if os.path.isfile(self.projNameTxt.text()) and \ + os.path.isfile(self.instConfigTxt.text()) and \ + os.path.isfile(self.detConfigTxt.text()) and \ + (self.noFieldRadio.isChecked() or \ + (self.badPixelRadio.isChecked() and \ + not (str(self.badPixelFileTxt.text()) == "")) or \ + (self.flatFieldRadio.isChecked() and \ + not (str(self.flatFieldFileTxt.text()) == ""))) and \ + self.pixAvgValid(self.pixAvgTxt.text()) and \ + self.detROIValid(self.detROITxt.text()): + retVal = True + self.loadButton.setEnabled(retVal) + else: + retVal = False + self.loadButton.setDisabled(not retVal) + self.okToLoad.emit(retVal) + logger.debug(METHOD_EXIT_STR) + return retVal + + def _createControlBox(self): + ''' + Create Layout holding controls widgets + ''' + logger.debug(METHOD_ENTER_STR) + controlBox = super(S28SpecScanFileForm, self)._createControlBox() + logger.debug(METHOD_EXIT_STR) + return controlBox + + def _createDataBox(self): + ''' + Create widgets for collecting data + ''' + logger.debug(METHOD_ENTER_STR) + dataBox = super(S28SpecScanFileForm, self)._createDataBox() + dataLayout = dataBox.layout() + row = dataLayout.rowCount() + self._createInstConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createDetConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self.fieldCorrectionGroup = qtWidgets.QButtonGroup(self) + self.noFieldRadio = qtWidgets.QRadioButton(self.NONE_RADIO_NAME) + self.badPixelRadio = qtWidgets.QRadioButton(self.BAD_PIXEL_RADIO_NAME) + self.flatFieldRadio = qtWidgets.QRadioButton(self.FLAT_FIELD_RADIO_NAME) + self.fieldCorrectionGroup.addButton(self.noFieldRadio, 1) + self.fieldCorrectionGroup.addButton(self.badPixelRadio, 2) + self.fieldCorrectionGroup.addButton(self.flatFieldRadio, 3) + self.badPixelFileTxt = qtWidgets.QLineEdit() + self.flatFieldFileTxt = qtWidgets.QLineEdit() + self.badPixelFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + self.flatFieldFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + + + dataLayout.addWidget(self.noFieldRadio, row, 0) + row += 1 + dataLayout.addWidget(self.badPixelRadio, row, 0) + dataLayout.addWidget(self.badPixelFileTxt, row, 1) + dataLayout.addWidget(self.badPixelFileBrowseButton, row, 2) + row += 1 + dataLayout.addWidget(self.flatFieldRadio, row, 0) + dataLayout.addWidget(self.flatFieldFileTxt, row, 1) + dataLayout.addWidget(self.flatFieldFileBrowseButton, row, 2) + + row += 1 + label = qtWidgets.QLabel("Number of Pixels To Average:"); + self.pixAvgTxt = qtWidgets.QLineEdit("1,1") + rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) + self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.pixAvgTxt, row, 1) + + row = dataLayout.rowCount() + 1 + self._createDetectorROIInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createScanNumberInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createOutputType(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createHKLOutput(dataLayout, row) + + # Add Signals between widgets + self.fieldCorrectionGroup.buttonClicked\ + .connect(self._fieldCorrectionTypeChanged) + + self.badPixelFileTxt.editingFinished.connect(self._badPixelFileChanged) + self.badPixelFileBrowseButton.clicked\ + .connect(self._browseBadPixelFileName) + + self.flatFieldFileTxt.editingFinished\ + .connect(self._flatFieldFileChanged) + self.flatFieldFileBrowseButton.clicked.\ + connect(self._browseFlatFieldFileName) + + self.pixAvgTxt.textChanged.connect(self._pixAvgTxtChanged) + + dataBox.setLayout(dataLayout) + logger.debug(METHOD_EXIT_STR) + return dataBox + + @qtCore.pyqtSlot(QAbstractButton) + def _fieldCorrectionTypeChanged(self, *fieldCorrType): + ''' + React when the field type radio buttons change. Disable/Enable other + widgets as appropriate + ''' + logger.debug(METHOD_ENTER_STR) + if fieldCorrType[0].text() == self.NONE_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.BAD_PIXEL_RADIO_NAME: + self.badPixelFileTxt.setDisabled(False) + self.badPixelFileBrowseButton.setDisabled(False) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.FLAT_FIELD_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(False) + self.flatFieldFileBrowseButton.setDisabled(False) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _flatFieldFileChanged(self): + ''' + Do some verification when the flat field file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.flatFieldFileTxt.text()) or \ + self.flatFieldFileTxt.text() == "": + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the flat field " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + def getBadPixelFileName(self): + ''' + Return the badPixel file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.badPixelFileTxt.text()) == EMPTY_STR) or \ + (not self.badPixelRadio.isChecked()): + retVal = None + else: + retVal = str(self.badPixelFileTxt.text()) + logger.debug("Exit " + str(retVal)) + return retVal + + def getDataSource(self): + logger.debug(METHOD_ENTER_STR) + if self.getOutputType() == self.SIMPLE_GRID_MAP_STR: + self.transform = UnityTransform3D() + elif self.getOutputType() == self.POLE_MAP_STR: + self.transform = \ + PoleMapTransform3D(projectionDirection=\ + self.getProjectionDirection()) + else: + self.transform = None + + self.dataSource = \ + Sector28SpecDataSource(str(self.getProjectDir()), \ + str(self.getProjectName()), \ + str(self.getProjectExtension()), \ + str(self.getInstConfigName()), \ + str(self.getDetConfigName()), \ + transform = self.transform, \ + scanList = self.getScanList(), \ + roi = self.getDetectorROI(), \ + pixelsToAverage = \ + self.getPixelsToAverage(), \ + badPixelFile = \ + self.getBadPixelFileName(), \ + flatFieldFile = \ + self.getFlatFieldFileName(), \ + appConfig = self.appConfig + ) + self.dataSource.setProgressUpdater(self.updateProgress) + self.dataSource.setCurrentDetector(self.currentDetector) + self.dataSource.loadSource(mapHKL = self.getMapAsHKL()) + logger.debug(METHOD_EXIT_STR) + return self.dataSource + + def getFlatFieldFileName(self): + ''' + Return the flat field file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.flatFieldFileTxt.text()) == EMPTY_STR) or \ + (not self.flatFieldRadio.isChecked()): + retVal = None + else: + retVal = str(self.flatFieldFileTxt.text()) + logger.debug(METHOD_EXIT_STR) + return retVal + + def getOutputForms(self): + logger.debug(METHOD_ENTER_STR) + outputForms = [] + outputForms.append(ProcessVTIOutputForm) + outputForms.append(ProcessImageStackForm) + outputForms.append(ProcessPowderScanForm) + logger.debug(METHOD_EXIT_STR) + return outputForms + + def getPixelsToAverage(self): + ''' + :return: the pixels to average as a list + ''' + logger.debug(METHOD_ENTER_STR) + pixelStrings = str(self.pixAvgTxt.text()).split(COMMA_STR) + pixels = [] + for value in pixelStrings: + pixels.append(int(value)) + logger.debug(METHOD_EXIT_STR) + return pixels + + def getProjectionDirection(self): + ''' + Return projection direction for stereographic projections + ''' + logger.debug(METHOD_ENTER_STR) + logger.debug(METHOD_EXIT_STR) + return self.projectionDirection + + + def _pixAvgTxtChanged(self, text): + ''' + Check to make sure the text for pix to average is valid and indicate + by a color change + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + if self.pixAvgValid(text): + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) + else: + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + def pixAvgValid(self, text): + ''' + Check to make sure that the pixAvgText is valid + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + rxPixAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_2) + validator = qtGui.QRegExpValidator(rxPixAvg, None) + pos = 0 + if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: + retVal = True + else: + retVal = False + logger.debug("Exit " + str(retVal)) + return retVal From e6229bb8b92e96c9037423315ea61089693fc7c4 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 11:54:12 -0500 Subject: [PATCH 10/14] Update __init__.py minor version advance --- rsMap3D/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsMap3D/__init__.py b/rsMap3D/__init__.py index 63b9f6b..16fa8ca 100644 --- a/rsMap3D/__init__.py +++ b/rsMap3D/__init__.py @@ -1,3 +1,3 @@ # import gui # import datasource -__version__ = '1.3.0' \ No newline at end of file +__version__ = '1.3.1' From ea75f483188bef184982fb31a126d01d2048ae73 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 12:18:15 -0500 Subject: [PATCH 11/14] Make the file name same format as others --- rsMap3D/datasource/Sector28SpecDataSource.py | 585 +++++++++++++++++++ 1 file changed, 585 insertions(+) create mode 100644 rsMap3D/datasource/Sector28SpecDataSource.py diff --git a/rsMap3D/datasource/Sector28SpecDataSource.py b/rsMap3D/datasource/Sector28SpecDataSource.py new file mode 100644 index 0000000..e1c94a0 --- /dev/null +++ b/rsMap3D/datasource/Sector28SpecDataSource.py @@ -0,0 +1,585 @@ +''' + Copyright (c) 2012, UChicago Argonne, LLC + See LICENSE file. +''' +import os +from spec2nexus.spec import SpecDataFile +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + ScanDataMissingException +from rsMap3D.gui.rsm3dcommonstrings import CANCEL_STR +from rsMap3D.datasource.pilatusbadpixelfile import PilatusBadPixelFile +from rsMap3D.mappers.abstractmapper import ProcessCanceledException +from rsMap3D.datasource.specxmldrivendatasource import SpecXMLDrivenDataSource +import numpy as np +import xrayutilities as xu +import time +import sys,traceback +import logging +import importlib +import h5py +import hdf5plugin +try: + from PIL import Image +except ImportError: + import Image +logger = logging.getLogger(__name__) + + +# +++++++++++++++ +# Zhan Zhang (2026-03-26) +# Make changes to work with HDF5 file generated at CHEX 28-ID beamline. +# I do need to generate folder structure different to the Tiff case, +# which is : +# $(PROJECT_DIR)/images/$(SAMPLE_NAME)/Snnn/$(SAMPLE_NAME)_Sxxx_nnnnn.tiff +# Here it is : +# $(PROJECT_DIR)/images/$(SAMPLE_NAME)/$(SAMPLE_NAME)_Sxxx_00000.h5 +IMAGE_DIR_MERGE_STR = "images/%s" +#IMAGE_DIR_MERGE_STR = "hdf5s/%s" +SCAN_NUMBER_MERGE_STR = "S%03d" +#TIFF_FILE_MERGE_STR = "S%%03d/%s_S%%03d_%%05d.tif" +#hdf5 filename format string: one h5 file per scan here +# This one is when we still had Sxxx/ folder in place. +#HDF5_FILE_MERGE_STR = "S%%03d/%s_S%%03d_00000.h5" +HDF5_FILE_MERGE_STR = "%s_S%%03d_00000.h5" +# +++++++++++++++ + +class s28waxpcsSpecDataSource(SpecXMLDrivenDataSource): + ''' + Class to load data from spec file and configuration xml files from + for the way that data is collected at sector 33. + :members + ''' + + + def __init__(self, + projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs): + ''' + Constructor + :param projectDir: Directory holding the project file to open + :param projectName: First part of file name for the project + :param projectExt: File extension for the project file. + :param instConfigFile: Full path to Instrument configuration file. + :param detConfigFile: Full path to the detector configuration file + :param kwargs: Assorted keyword arguments + + :rtype: s28waxpcsSpecDataSource + ''' + super(s28waxpcsSpecDataSource, self).__init__(projectDir, + projectName, + projectExtension, + instConfigFile, + detConfigFile, + **kwargs) + + def _calc_eulerian_from_kappa(self, primaryAngles=None, + referenceAngles = None): + """ + Calculate the eulerian sample angles from the kappa stage angles. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + """ + + keta = np.deg2rad(referenceAngles[:,0]) + kappa = np.deg2rad(referenceAngles[:,1]) + kphi = np.deg2rad(referenceAngles[:,2]) + kappaParam = self.instConfig.getSampleAngleMappingParameter('kappa') + + try: + if kappaParam != None: + self.kalpha = np.deg2rad(float(kappaParam)) + else: + self.kalpha = np.deg2rad(50.000) + kappaInvertedParam = \ + self.instConfig.getSampleAngleMappingParameter('kappaInverted') + if kappaInvertedParam != None: + self.kappa_inverted = self.to_bool(kappaInvertedParam) + else: + self.kappa_inverted = False + except Exception as ex: + raise RSMap3DException("Error trying to get parameter for " + \ + "sampleAngleMappingFunction " + \ + "_calc_eulerian_from_kappa in inst config " + \ + "file\n" + \ + str(ex)) + + _t1 = np.arctan(np.tan(kappa / 2.0) * np.cos(self.kalpha)) + if self.kappa_inverted: + eta = np.rad2deg(keta + _t1) + phi = np.rad2deg(kphi + _t1) + else: + eta = np.rad2deg(keta - _t1) + phi = np.rad2deg(kphi - _t1) + chi = 2.0 * np.rad2deg(np.arcsin(np.sin(kappa / 2.0) * \ + np.sin(self.kalpha))) + + return eta, chi, phi + + def _calc_replace_angle_values(self , primaryAngles=None, + referenceAngles=None): + ''' + Fix a situation were some constant angle values have been + recorded incorrectly and need to be fixed. + :param primaryAngles: list of sample axis numbers to be handled by + the conversion + :param referenceAngles: list of reference angles to be used in angle + conversion + ''' + logger.info( "Running " + __name__) + angles = [] + logger.debug("referenceAngles " + str(referenceAngles)) + mappingAngles = self.instConfig.getSampleAngleMappingReferenceAngles() + + logger.debug( "mappingAngles" + str(mappingAngles)) + for ii in range(len(referenceAngles)): + replaceVal = \ + float(self.instConfig.getSampleAngleMappingReferenceAngleAttrib( \ + number= str(mappingAngles[ii]), \ + attribName='replaceValue')) + logger.debug( "primary Angles" + str(referenceAngles)) + angles.append(replaceVal* np.ones(len(referenceAngles[:,ii]),)) + logger.debug("Angles" + str( angles)) + return angles + + def fixGeoAngles(self, scan, angles): + ''' + Fix the angles using a user selected function. + :param scan: scan to set the angles for + :param angles: Array of angles to set for this scan + ''' + logger.debug( "starting " + __name__) + needToCorrect = False + refAngleNames = self.instConfig.getSampleAngleMappingReferenceAngles() + for refAngleName in refAngleNames: + alwaysFix = self.instConfig.getSampleAngleMappingAlwaysFix() + if refAngleName in scan.L or alwaysFix: + needToCorrect = True + + if needToCorrect: + logger.debug( __name__ + ": Fixing angles") + refAngles = self.getScanAngles(scan, refAngleNames) + primaryAngles = self.instConfig.getSampleAngleMappingPrimaryAngles() + functionName = self.instConfig.getSampleAngleMappingFunctionName() + functionModuleName = self.instConfig.getSampleAngleMappingFunctionModule() + logger.debug("sampleMappingFunction moduleName %s" % functionModuleName) + #Call a defined method to calculate angles from the reference angles. + moduleSource = self + if functionModuleName != None: + functionModule = importlib.import_module(functionModuleName) + logger.debug("dir(functionModule)" % dir(functionModule)) + moduleSource = functionModule + method = getattr(moduleSource, functionName) + fixedAngles = method(primaryAngles=primaryAngles, + referenceAngles=refAngles) + logger.debug ("fixed Angles: " + str(fixedAngles)) + for i in range(len(primaryAngles)): + logger.debug ("Fixing primaryAngles: %d " % primaryAngles[i]) + angles[:,primaryAngles[i]-1] = fixedAngles[i] + + def getGeoAngles(self, scan, angleNames): + """ + This function returns all of the geometry angles for the + for the scan as a N-by-num_geo array, where N is the number of scan + points and num_geo is the number of geometry motors. + """ +# scan = self.sd[scanNo] + geoAngles = self.getScanAngles(scan, angleNames) + if not (self.instConfig.getSampleAngleMappingFunctionName() == ""): + tb = None + try: + self.fixGeoAngles(scan, geoAngles) + except Exception as ex: + tb = traceback.format_exc() + raise RSMap3DException("Handling exception in getGeoAngles." + \ + "\n" + \ + str(ex) + \ + "\n" + \ + str(tb)) + logger.debug("getGeoAngles:\n" + str(geoAngles)) + return geoAngles + + def getUBMatrix(self, scan): + """ + Read UB matrix from the #G3 line from the spec file. + """ + try: + g3 = scan.G["G3"].strip().split() + g3 = np.array(list(map(float, g3))) + ub = g3.reshape(-1,3) + logger.debug("ub " +str(ub)) + return ub + except: + logger.error("Unable to read UB Matrix from G3") + logger.error( '-'*60) + traceback.print_exc(file=sys.stdout) + logger.error('-'*60) + + + def hotpixelkill(self, areaData): + """ + function to remove hot pixels from CCD frames + ADD REMOVE VALUES IF NEEDED! + :param areaData: area detector data + """ + + for pixel in self.getBadPixels(): + badLoc = pixel.getBadLocation() + replaceLoc = pixel.getReplacementLocation() + areaData[badLoc[0],badLoc[1]] = \ + areaData[replaceLoc[0],replaceLoc[1]] + + return areaData + + def loadSource(self, mapHKL=False): + ''' + This method does the work of loading data from the files. This has been + split off from the constructor to allow this to be threaded and later + canceled. + :param mapHKL: boolean to mark if the data should be mapped to HKL + ''' + # Load up the instrument configuration file + self.loadInstrumentXMLConfig() + #Load up the detector configuration file + self.loadDetectorXMLConfig() + + self.specFile = os.path.join(self.projectDir, self.projectName + \ + self.projectExt) + imageDir = os.path.join(self.projectDir, \ + IMAGE_DIR_MERGE_STR % self.projectName) + + # +++++++++++++++ + # for hdf5 file,use the same property but different format + self.imageFileTmp = os.path.join(imageDir, \ + HDF5_FILE_MERGE_STR % + (self.projectName)) + # +++++++++++++++ + # if needed load up the bad pixel file. + if not (self.badPixelFile is None): + + badPixelFile = PilatusBadPixelFile(self.badPixelFile) + self.badPixels = badPixelFile.getBadPixels() + + # id needed load the flat field file + if not (self.flatFieldFile is None): + self.flatFieldData = np.array(Image.open(self.flatFieldFile)).T + # Load scan information from the spec file + try: + self.sd = SpecDataFile(self.specFile) + self.mapHKL = mapHKL + maxScan = int(self.sd.getMaxScanNumber()) + logger.debug("Number of Scans" + str(maxScan)) + if self.scans is None: + self.scans = range(1, maxScan+1) + imagePath = os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName) + + self.imageBounds = {} + self.imageToBeUsed = {} + self.availableScans = [] + self.incidentEnergy = {} + self.ubMatrix = {} + self.scanType = {} + self.progress = 0 + self.progressInc = 1 + #======= ZZ, 2020/02/19, add initialization + self.progressMax = 100 + #======= + # Zero the progress bar at the beginning. + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + for scan in self.scans: + if (self.cancelLoad): + self.cancelLoad = False + raise LoadCanceledException(CANCEL_STR) + + else: + # Ok, this needs to be changed, too, if we get rid of Sxxx/ folder. + # I don't think it looks for the images in this section, but just to + # make sure they do exsit. + # ZZ 2026-03-26 + #if (os.path.exists(os.path.join(imagePath, \ + # SCAN_NUMBER_MERGE_STR % scan))): + if (os.path.exists( self.imageFileTmp % (scan) )): + try: + curScan = self.sd.scans[str(scan)] + self.scanType[scan] = \ + self.sd.scans[str(scan)].scanCmd.split()[0] + angles = self.getGeoAngles(curScan, self.angleNames) + self.availableScans.append(scan) + if self.mapHKL==True: + self.ubMatrix[scan] = self.getUBMatrix(curScan) + if self.ubMatrix[scan] is None: + raise s28waxpcsSpecFileException("UB matrix " + \ + "not found.") + else: + self.ubMatrix[scan] = None + self.incidentEnergy[scan] = 12398.4 /float(curScan.G['G4'].split()[3]) + _start_time = time.time() + self.imageBounds[scan] = \ + self.findImageQs(angles, \ + self.ubMatrix[scan], \ + self.incidentEnergy[scan]) + if self.progressUpdater is not None: + self.progressUpdater(self.progress, self.progressMax) + logger.info (('Elapsed time for Finding qs for scan %d: ' + + '%.3f seconds') % \ + (scan, (time.time() - _start_time))) + except ScanDataMissingException: + logger.error( "Scan " + str(scan) + " has no data") + #Make sure to show 100% completion + if self.progressUpdater is not None: + self.progressUpdater(self.progressMax, self.progressMax) + except IOError: + raise IOError( "Cannot open file " + str(self.specFile)) + if len(self.getAvailableScans()) == 0: + raise ScanDataMissingException("Could not find scan data for " + \ + "input file \n" + self.specFile + \ + "\nOne possible reason for this " + \ + "is that the image files are " + \ + "missing. Images are assumed " + \ + "to be in " + \ + os.path.join(self.projectDir, + IMAGE_DIR_MERGE_STR % self.projectName)) + + self.availableScanTypes = set(self.scanType.values()) + + + + def to_bool(self, value): + """ + Note this method found in answer to: + http://stackoverflow.com/questions/715417/converting-from-a-string-to-boolean-in-python + Converts 'something' to boolean. Raises exception if it gets a string + it doesn't handle. + Case is ignored for strings. These string values are handled: + True: 'True', "1", "TRue", "yes", "y", "t" + False: "", "0", "faLse", "no", "n", "f" + Non-string values are passed to bool. + """ + if type(value) == type(''): + if value.lower() in ("yes", "y", "true", "t", "1"): + return True + if value.lower() in ("no", "n", "false", "f", "0", ""): + return False + raise Exception('Invalid value for boolean conversion: ' + value) + return bool(value) + + def rawmap(self,scans, angdelta=[0,0,0,0,0], + adframes=None, mask = None): + """ + read ad frames and and convert them in reciprocal space + angular coordinates are taken from the spec file + or read from the edf file header when no scan number is given (scannr=None) + """ + + if mask is None: + mask_was_none = True + #mask = [True] * len(self.getImageToBeUsed()[scans[0]]) + else: + mask_was_none = False + #sd = spec.SpecDataFile(self.specFile) + intensity = np.array([]) + + # fourc goniometer in fourc coordinates + # convention for coordinate system: + # x: upwards; + # y: along the incident beam; + # z: "outboard" (makes coordinate system right-handed). + # QConversion will set up the goniometer geometry. + # So the first argument describes the sample rotations, the second the + # detector rotations and the third the primary beam direction. + qconv = xu.experiment.QConversion(self.getSampleCircleDirections(), \ + self.getDetectorCircleDirections(), \ + self.getPrimaryBeamDirection()) + + # define experimental class for angle conversion + # + # ipdir: inplane reference direction (ipdir points into the primary beam + # direction at zero angles) + # ndir: surface normal of your sample (ndir points in a direction + # perpendicular to the primary beam and the innermost detector + # rotation axis) + en = self.getIncidentEnergy() + hxrd = xu.HXRD(self.getInplaneReferenceDirection(), \ + self.getSampleSurfaceNormalDirection(), \ + en=en[self.getAvailableScans()[0]], \ + qconv=qconv) + + + # initialize area detector properties + if (self.getDetectorPixelWidth() != None ) and \ + (self.getDistanceToDetector() != None): + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + pwidth1=self.getDetectorPixelWidth()[0], \ + pwidth2=self.getDetectorPixelWidth()[1], \ + distance=self.getDistanceToDetector(), \ + Nav=self.getNumPixelsToAverage(), \ + roi=self.getDetectorROI()) + else: + hxrd.Ang2Q.init_area(self.getDetectorPixelDirection1(), \ + self.getDetectorPixelDirection2(), \ + cch1=self.getDetectorCenterChannel()[0], \ + cch2=self.getDetectorCenterChannel()[1], \ + Nch1=self.getDetectorDimensions()[0], \ + Nch2=self.getDetectorDimensions()[1], \ + chpdeg1=self.getDetectorChannelsPerDegree()[0], \ + chpdeg2=self.getDetectorChannelsPerDegree()[1], \ + Nav=self.getNumPixelsToAverage(), + roi=self.getDetectorROI()) + + angleNames = self.getAngles() + scanAngle = {} + for i in range(len(angleNames)): + scanAngle[i] = np.array([]) + + offset = 0 + imageToBeUsed = self.getImageToBeUsed() + monitorName = self.getMonitorName() + monitorScaleFactor = self.getMonitorScaleFactor() + filterName = self.getFilterName() + filterScaleFactor = self.getFilterScaleFactor() + for scannr in scans: + if self.haltMap: + raise ProcessCanceledException("Process Canceled") + scan = self.sd.scans[str(scannr)] + angles = self.getGeoAngles(scan, angleNames) + scanAngle1 = {} + scanAngle2 = {} + for i in range(len(angleNames)): + scanAngle1[i] = angles[:,i] + scanAngle2[i] = [] + if monitorName != None: + monitor_data = scan.data.get(monitorName) + if monitor_data is None: + raise IOError("Did not find Monitor source '" + \ + monitorName + \ + "' in the Spec file. Make sure " + \ + "monitorName is correct in the " + \ + "instrument Config file") + if filterName != None: + filter_data = scan.data.get(filterName) + if filter_data is None: + raise IOError("Did not find filter source '" + \ + filterName + \ + "' in the Spec file. Make sure " + \ + "filterName is correct in the " + \ + "instrument Config file") + # read in the image data + arrayInitializedForScan = False + foundIndex = 0 + + if mask_was_none: + mask = [True] * len(self.getImageToBeUsed()[scannr]) + + # +++++++++++++++ + # Read HDF5 file here, brutal force. Need better handle. + h5file = self.imageFileTmp % (scannr) + h5data = h5py.File(h5file, "r") + scan_images = h5data["entry"]["data"]["data"] + # +++++++++++++++ + + + for ind in range(len(scan.data[list(scan.data.keys())[0]])): + if imageToBeUsed[scannr][ind] and mask[ind]: + # +++++++++++++++ + # Read HDF5 file here + #im = Image.open(self.imageFileTmp % (scannr, scannr, ind)) + #img = np.array(im.getdata()).reshape(im.size[1],im.size[0]).T + #h5file = self.imageFileTmp % (scannr, scannr, ind) + #h5data = h5py.File(h5file, "r") + #im = h5data["entry"]["data"]["data"][0,:,:] + img = np.array(scan_images[ind,:,:]).T + # +++++++++++++++ + + img = self.hotpixelkill(img) + ff_data = self.getFlatFieldData() + if not (ff_data is None): + img = img * ff_data + # reduce data siz + img2 = xu.blockAverage2D(img, + self.getNumPixelsToAverage()[0], \ + self.getNumPixelsToAverage()[1], \ + roi=self.getDetectorROI()) + + # apply intensity corrections + if monitorName != None: + img2 = img2 / monitor_data[ind] * monitorScaleFactor + if filterName != None: + img2 = img2 / filter_data[ind] * filterScaleFactor + + # initialize data array + if not arrayInitializedForScan: + imagesToProcess = [imageToBeUsed[scannr][i] and mask[i] for i in range(len(imageToBeUsed[scannr]))] + if not intensity.shape[0]: + intensity = np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape) + arrayInitializedForScan = True + else: + offset = intensity.shape[0] + intensity = np.concatenate( + (intensity, + (np.zeros((np.count_nonzero(imagesToProcess),) + img2.shape))), + axis=0) + arrayInitializedForScan = True + # add data to intensity array + intensity[foundIndex+offset,:,:] = img2 + for i in range(len(angleNames)): +# logger.debug("appending angles to angle2 " + +# str(scanAngle1[i][ind])) + scanAngle2[i].append(scanAngle1[i][ind]) + foundIndex += 1 + if len(scanAngle2[0]) > 0: + for i in range(len(angleNames)): + scanAngle[i] = \ + np.concatenate((scanAngle[i], np.array(scanAngle2[i])), \ + axis=0) + # transform scan angles to reciprocal space coordinates for all detector pixels + angleList = [] + for i in range(len(angleNames)): + angleList.append(scanAngle[i]) + if self.ubMatrix[scans[0]] is None: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage()) + else: + qx, qy, qz = hxrd.Ang2Q.area(*angleList, \ + roi=self.getDetectorROI(), + Nav=self.getNumPixelsToAverage(), \ + UB = self.ubMatrix[scans[0]]) + + + # apply selected transform + qxTrans, qyTrans, qzTrans = \ + self.transform.do3DTransform(qx, qy, qz) + + + return qxTrans, qyTrans, qzTrans, intensity + +class LoadCanceledException(RSMap3DException): + ''' + Exception Thrown when loading data is canceled. + ''' + def __init__(self, message): + super(LoadCanceledException, self).__init__(message) + +class s28waxpcsSpecFileException(RSMap3DException): + ''' + Exception class to be raised if there is a problem loading information + from a spec file + file + ''' + def __init__(self, message): + super(s28waxpcsSpecFileException, self).__init__(message) + + + From 4e2f0acdd8022c48179641d097807c5362ad399f Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 12:22:38 -0500 Subject: [PATCH 12/14] more changes needed --- rsMap3D/datasource/Sector28SpecDataSource.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rsMap3D/datasource/Sector28SpecDataSource.py b/rsMap3D/datasource/Sector28SpecDataSource.py index e1c94a0..00eea03 100644 --- a/rsMap3D/datasource/Sector28SpecDataSource.py +++ b/rsMap3D/datasource/Sector28SpecDataSource.py @@ -43,7 +43,7 @@ HDF5_FILE_MERGE_STR = "%s_S%%03d_00000.h5" # +++++++++++++++ -class s28waxpcsSpecDataSource(SpecXMLDrivenDataSource): +class Sector28SpecDataSource(SpecXMLDrivenDataSource): ''' Class to load data from spec file and configuration xml files from for the way that data is collected at sector 33. @@ -67,9 +67,9 @@ def __init__(self, :param detConfigFile: Full path to the detector configuration file :param kwargs: Assorted keyword arguments - :rtype: s28waxpcsSpecDataSource + :rtype: Sector28SpecDataSource ''' - super(s28waxpcsSpecDataSource, self).__init__(projectDir, + super(Sector28SpecDataSource, self).__init__(projectDir, projectName, projectExtension, instConfigFile, @@ -315,7 +315,7 @@ def loadSource(self, mapHKL=False): if self.mapHKL==True: self.ubMatrix[scan] = self.getUBMatrix(curScan) if self.ubMatrix[scan] is None: - raise s28waxpcsSpecFileException("UB matrix " + \ + raise Sector28SpecFileException("UB matrix " + \ "not found.") else: self.ubMatrix[scan] = None @@ -572,14 +572,14 @@ class LoadCanceledException(RSMap3DException): def __init__(self, message): super(LoadCanceledException, self).__init__(message) -class s28waxpcsSpecFileException(RSMap3DException): +class Sector28SpecFileException(RSMap3DException): ''' Exception class to be raised if there is a problem loading information from a spec file file ''' def __init__(self, message): - super(s28waxpcsSpecFileException, self).__init__(message) + super(Sector28SpecFileException, self).__init__(message) From 230b4e5f864560ffa14b78ac4cb875f62f94e575 Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 12:38:01 -0500 Subject: [PATCH 13/14] regexp warning --- rsMap3D/gui/output/processimagestackform.py | 346 +++++++-------- rsMap3D/gui/output/processpowderscanform.py | 460 ++++++++++---------- 2 files changed, 403 insertions(+), 403 deletions(-) diff --git a/rsMap3D/gui/output/processimagestackform.py b/rsMap3D/gui/output/processimagestackform.py index ffeec75..530773d 100644 --- a/rsMap3D/gui/output/processimagestackform.py +++ b/rsMap3D/gui/output/processimagestackform.py @@ -1,174 +1,174 @@ -''' - Copyright (c) 2016, UChicago Argonne, LLC - See LICENSE file. -''' -import PyQt5.QtGui as qtGui -import PyQt5.QtCore as qtCore -import PyQt5.QtWidgets as qtWidgets - -from PyQt5.QtCore import pyqtSlot as Slot - -from rsMap3D.gui.output.abstractgridoutputform import AbstractGridOutputForm -from rsMap3D.gui.rsm3dcommonstrings import BROWSE_STR, SAVE_DIR_STR, WARNING_STR,\ - BINARY_OUTPUT -import os -from rsMap3D.mappers.output.imagestackwriter import ImageStackWriter - -class ProcessImageStackForm(AbstractGridOutputForm): - ''' - Process grid data and output a stack of TIFF images. - ''' - FORM_TITLE = "Image Stack Output" - - @staticmethod - def createInstance(parent=None, appConfig=None): - ''' - A static method to create an instance of this class. The UI selects which processor method to use - from a menu so this method allows creating an instance without knowing what to create ahead of time. - ''' - return ProcessImageStackForm(parent=parent, appConfig=appConfig) - - def __init__(self, **kwargs): - ''' - Constructor. Typically instances should be created by createInstance method. - ''' - super(ProcessImageStackForm, self).__init__(**kwargs) - self.gridWriter = ImageStackWriter() - layout = qtWidgets.QVBoxLayout() - self.dataBox = self._createDataBox() - controlBox = self._createControlBox() - - layout.addWidget(self.dataBox) - layout.addWidget(controlBox) - self.setLayout(layout) - self.outputType = BINARY_OUTPUT - - @Slot() - def _browseForOutputDirectory(self): - ''' - Launch file browser to find location to write image files. - ''' - if self.outputDirTxt.text() == "": - dirName = str(qtWidgets.QFileDialog.getExistingDirectory(None, - SAVE_DIR_STR)[0]) - else: - curName = str(self.outputDirTxt.text()) - dirName = str(qtWidgets.QFileDialog.getExistingDirectory(None, - SAVE_DIR_STR, - directory = curName)[0]) - - if dirName != "": - if os.path.exists(str(dirName)): - self.outputDirTxt.setText(dirName) - self.outputDirName = dirName - self.outputDirTxt.editingFinished.emit() - else: - message = qtWidgets.QMessageBox() - message.warning(self, - WARNING_STR, - "The specified directory does not exist") - self.outputDirTxt.setText(dirName) - self.outputDirName = dirName - self.outputDirTxt.editingFinished.emit() - if not os.access(dirName, os.W_OK): - message = qtWidgets.QMessageBox() - message.warning(self, - WARNING_STR, - "The specified directory is not writable") - - def _createDataBox(self): - dataBox = super(ProcessImageStackForm, self)._createDataBox() - layout = dataBox.layout() - - row = layout.rowCount() - row += 1 - - label = qtWidgets.QLabel("Output Directory") - layout.addWidget(label, row,0) - self.outputDirName = "" - self.outputDirTxt = qtWidgets.QLineEdit() - self.outputDirTxt.setText(self.outputDirName) - layout.addWidget(self.outputDirTxt, row,1) - self.outputDirButton = qtWidgets.QPushButton(BROWSE_STR) - layout.addWidget(self.outputDirButton, row, 2) - - row += 1 - label = qtWidgets.QLabel("Image File Prefix") - layout.addWidget(label, row, 0) - self.imageFilePrefix = "" - self.imageFilePrefixTxt = qtWidgets.QLineEdit() - self.imageFilePrefixTxt.setText(self.imageFilePrefix) - layout.addWidget(self.imageFilePrefixTxt, row,1) - - row += 1 - label = qtWidgets.QLabel("Slice Axis") - layout.addWidget(label, row, 0) - self.axisChoices = ["0", "1", "2"] - self.axisSelector = qtWidgets.QComboBox() - self.axisSelector.addItems(self.axisChoices) - self.axisSelector.setCurrentIndex(self.gridWriter.getSliceIndex()) - layout.addWidget(self.axisSelector) - - #Connect signals for this class - self.outputDirButton.clicked.connect(self._browseForOutputDirectory) -# self.connect(self.outputDirButton, \ -# qtCore.SIGNAL(EDIT_FINISHED_SIGNAL), -# self._editFinishedOutputDir) - self.outputDirTxt.editingFinished.connect(self._editFinishedOutputDir) - self.setFileName[str].connect(self.outputDirTxt.setText) - self.imageFilePrefixTxt.editingFinished.connect( - self._editFinishedOutputDir) - self.axisSelector.currentIndexChanged[int].connect(self.updateSliceAxis) - - - return dataBox - - @Slot() - def _editFinishedOutputDir(self): - ''' - Process output directory name as inputs are completed. - ''' - dirName = str(self.outputDirTxt.text()) - imageFilePrefix = str(self.imageFilePrefixTxt.text()) - - "process directory" - if dirName != "": - if os.path.exists(dirName): #use specified dir if it exists - self.outputDirName = dirName - else: #use current path - if dirName == "": - dirName = os.path.realpath(os.path.curdir) - else: - message = qtWidgets.QMessageBox() - message.warning(self, - WARNING_STR, - "The specified directory \n" + - str(dirName) + - "\ndoes not exist") - - "process file prefix" - if imageFilePrefix != "": - for badChar in ['\\', '/', ':', '*', '?', '<', '>', '|']: - if badChar in imageFilePrefix: - message = qtWidgets.QMessageBox() - message.warning(self, - WARNING_STR, - "The specified imagePrefix conatins one " + - "of the following invalid characters \/:*?<>|") - else: - self.imageFilePrefix = imageFilePrefix - - def getOutputFileName(self): - ''' - Override from base class. In this case return a join of two of the inputs. This is used to - provide info during runMapper method. - ''' - return os.path.join(self.outputDirName, self.imageFilePrefix) - - - @Slot(int) - def updateSliceAxis(self, index): - ''' - record changes as the axis for slicing is changed - ''' +''' + Copyright (c) 2016, UChicago Argonne, LLC + See LICENSE file. +''' +import PyQt5.QtGui as qtGui +import PyQt5.QtCore as qtCore +import PyQt5.QtWidgets as qtWidgets + +from PyQt5.QtCore import pyqtSlot as Slot + +from rsMap3D.gui.output.abstractgridoutputform import AbstractGridOutputForm +from rsMap3D.gui.rsm3dcommonstrings import BROWSE_STR, SAVE_DIR_STR, WARNING_STR,\ + BINARY_OUTPUT +import os +from rsMap3D.mappers.output.imagestackwriter import ImageStackWriter + +class ProcessImageStackForm(AbstractGridOutputForm): + ''' + Process grid data and output a stack of TIFF images. + ''' + FORM_TITLE = "Image Stack Output" + + @staticmethod + def createInstance(parent=None, appConfig=None): + ''' + A static method to create an instance of this class. The UI selects which processor method to use + from a menu so this method allows creating an instance without knowing what to create ahead of time. + ''' + return ProcessImageStackForm(parent=parent, appConfig=appConfig) + + def __init__(self, **kwargs): + ''' + Constructor. Typically instances should be created by createInstance method. + ''' + super(ProcessImageStackForm, self).__init__(**kwargs) + self.gridWriter = ImageStackWriter() + layout = qtWidgets.QVBoxLayout() + self.dataBox = self._createDataBox() + controlBox = self._createControlBox() + + layout.addWidget(self.dataBox) + layout.addWidget(controlBox) + self.setLayout(layout) + self.outputType = BINARY_OUTPUT + + @Slot() + def _browseForOutputDirectory(self): + ''' + Launch file browser to find location to write image files. + ''' + if self.outputDirTxt.text() == "": + dirName = str(qtWidgets.QFileDialog.getExistingDirectory(None, + SAVE_DIR_STR)[0]) + else: + curName = str(self.outputDirTxt.text()) + dirName = str(qtWidgets.QFileDialog.getExistingDirectory(None, + SAVE_DIR_STR, + directory = curName)[0]) + + if dirName != "": + if os.path.exists(str(dirName)): + self.outputDirTxt.setText(dirName) + self.outputDirName = dirName + self.outputDirTxt.editingFinished.emit() + else: + message = qtWidgets.QMessageBox() + message.warning(self, + WARNING_STR, + "The specified directory does not exist") + self.outputDirTxt.setText(dirName) + self.outputDirName = dirName + self.outputDirTxt.editingFinished.emit() + if not os.access(dirName, os.W_OK): + message = qtWidgets.QMessageBox() + message.warning(self, + WARNING_STR, + "The specified directory is not writable") + + def _createDataBox(self): + dataBox = super(ProcessImageStackForm, self)._createDataBox() + layout = dataBox.layout() + + row = layout.rowCount() + row += 1 + + label = qtWidgets.QLabel("Output Directory") + layout.addWidget(label, row,0) + self.outputDirName = "" + self.outputDirTxt = qtWidgets.QLineEdit() + self.outputDirTxt.setText(self.outputDirName) + layout.addWidget(self.outputDirTxt, row,1) + self.outputDirButton = qtWidgets.QPushButton(BROWSE_STR) + layout.addWidget(self.outputDirButton, row, 2) + + row += 1 + label = qtWidgets.QLabel("Image File Prefix") + layout.addWidget(label, row, 0) + self.imageFilePrefix = "" + self.imageFilePrefixTxt = qtWidgets.QLineEdit() + self.imageFilePrefixTxt.setText(self.imageFilePrefix) + layout.addWidget(self.imageFilePrefixTxt, row,1) + + row += 1 + label = qtWidgets.QLabel("Slice Axis") + layout.addWidget(label, row, 0) + self.axisChoices = ["0", "1", "2"] + self.axisSelector = qtWidgets.QComboBox() + self.axisSelector.addItems(self.axisChoices) + self.axisSelector.setCurrentIndex(self.gridWriter.getSliceIndex()) + layout.addWidget(self.axisSelector) + + #Connect signals for this class + self.outputDirButton.clicked.connect(self._browseForOutputDirectory) +# self.connect(self.outputDirButton, \ +# qtCore.SIGNAL(EDIT_FINISHED_SIGNAL), +# self._editFinishedOutputDir) + self.outputDirTxt.editingFinished.connect(self._editFinishedOutputDir) + self.setFileName[str].connect(self.outputDirTxt.setText) + self.imageFilePrefixTxt.editingFinished.connect( + self._editFinishedOutputDir) + self.axisSelector.currentIndexChanged[int].connect(self.updateSliceAxis) + + + return dataBox + + @Slot() + def _editFinishedOutputDir(self): + ''' + Process output directory name as inputs are completed. + ''' + dirName = str(self.outputDirTxt.text()) + imageFilePrefix = str(self.imageFilePrefixTxt.text()) + + "process directory" + if dirName != "": + if os.path.exists(dirName): #use specified dir if it exists + self.outputDirName = dirName + else: #use current path + if dirName == "": + dirName = os.path.realpath(os.path.curdir) + else: + message = qtWidgets.QMessageBox() + message.warning(self, + WARNING_STR, + "The specified directory \n" + + str(dirName) + + "\ndoes not exist") + + "process file prefix" + if imageFilePrefix != "": + for badChar in ['\\', '/', ':', '*', '?', '<', '>', '|']: + if badChar in imageFilePrefix: + message = qtWidgets.QMessageBox() + message.warning(self, + WARNING_STR, + "The specified imagePrefix conatins one " + + "of the following invalid characters \\/:*?<>|") + else: + self.imageFilePrefix = imageFilePrefix + + def getOutputFileName(self): + ''' + Override from base class. In this case return a join of two of the inputs. This is used to + provide info during runMapper method. + ''' + return os.path.join(self.outputDirName, self.imageFilePrefix) + + + @Slot(int) + def updateSliceAxis(self, index): + ''' + record changes as the axis for slicing is changed + ''' self.gridWriter.setSliceIndex(index) \ No newline at end of file diff --git a/rsMap3D/gui/output/processpowderscanform.py b/rsMap3D/gui/output/processpowderscanform.py index a4578b7..684cd9d 100644 --- a/rsMap3D/gui/output/processpowderscanform.py +++ b/rsMap3D/gui/output/processpowderscanform.py @@ -1,231 +1,231 @@ -''' - Copyright (c) 2017 UChicago Argonne, LLC - See LICENSE file. -''' -import logging -from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR -from rsMap3D.gui.rsm3dcommonstrings import EMPTY_STR, SAVE_FILE_STR, WARNING_STR -import os -from rsMap3D.mappers.powderscanmapper import PowderScanMapper, X_COORD_OPTIONS,\ - Y_SCALING_OPTIONS -from rsMap3D.mappers.output.powderscanwriter import PowderScanWriter -logger = logging.getLogger(__name__) - -import PyQt5.QtGui as qtGui -import PyQt5.QtWidgets as qtWidgets - -from rsMap3D.gui.output.abstractoutputview import AbstractOutputView - -XYE_FILTER_STR = "*.xye" - -class ProcessPowderScanForm(AbstractOutputView): - ''' - This class gives a front end to an external script that has been used at - sector 33 for some time. The original powderscan script was written by - Christian Schleputz, (originally at the APS and now at PSI). This script - allows processing many scans but places the results of each into a separate - file. - ''' - FORM_TITLE = "Powder Scan Output" - - @staticmethod - def createInstance(parent=None, appConfig=None): - return ProcessPowderScanForm(parent=parent, appConfig=appConfig) - - def __init__(self, **kwargs): - super(ProcessPowderScanForm, self).__init__(**kwargs) - logger.debug(METHOD_ENTER_STR) - self.mapper = None - self.outputFileName = None - layout = qtWidgets.QVBoxLayout() - self.dataBox = self._createDataBox() - controlBox = self._createControlBox() - - layout.addWidget(self.dataBox) - layout.addWidget(controlBox) - self.setLayout(layout) - #self.outputType = BINARY_OUTPUT - logger.debug(METHOD_EXIT_STR) - - def _browseForOutputFile(self): - ''' - Launch File Browser to select output file name and directory. Checks - are done to make sure the selected directory exists and that the - selected file is writable - ''' - logger.debug(METHOD_ENTER_STR) - if self.outFileNameTxt == EMPTY_STR: - fileName = str(qtWidgets.QFileDialog.getSaveFileName(None, - SAVE_FILE_STR, - filter=XYE_FILTER_STR)[0]) - else: - inFileName = str(self.outFileNameTxt.text()) - fileName = str(qtWidgets.QFileDialog.getSaveFileName(None, - SAVE_FILE_STR, - filter=XYE_FILTER_STR, - directory=inFileName)[0]) - if fileName != EMPTY_STR: - if os.path.exists(os.path.dirname(str(fileName))): - self.outFileNameTxt.setText(fileName) - self.outputFileName = fileName - self.outFileNameTxt.editingFinished.emit() - else: - message = qtWidgets.QMessageBox() - message.warning(self, WARNING_STR, - "The specified directory does not exist") - - self.outFileNameTxt.setText(fileName) - self.outputFileName = fileName - self.outFileNameTxt.editingFinished.emit() - if not os.access(os.path.dirname(fileName), os.W_OK): - message = qtWidgets.QMessageBox() - message.warning(self, WARNING_STR, - "The specified file is not writable") - else: - self.outputFileName = EMPTY_STR - self.setFileName.emit(EMPTY_STR) - - def _createDataBox(self): - ''' - create the GUI for this processing. This should be run in __init__ - ''' - logger.debug(METHOD_ENTER_STR) - dataBox = super(ProcessPowderScanForm, self)._createDataBox() - dataLayout = dataBox.layout() - row = dataLayout.rowCount() - - label = qtWidgets.QLabel("Output File Name:") - self.outFileNameTxt = qtWidgets.QLineEdit() - self.outFileNameButton = qtWidgets.QPushButton("Browse") - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.outFileNameTxt, row, 1) - dataLayout.addWidget(self.outFileNameButton, row, 2) - - row = dataLayout.rowCount() - label = qtWidgets.QLabel("Data Coordinate:") - self.dataCoordinate = qtWidgets.QComboBox() - self.dataCoordinate.addItems(X_COORD_OPTIONS) - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.dataCoordinate, row, 1) - - row = dataLayout.rowCount() + 1 - label = qtWidgets.QLabel("Output Data Range - leave max/min blank for automatic range") - dataLayout.addWidget(label, row, 0, 2, -1) - - row = dataLayout.rowCount() - label = qtWidgets.QLabel("Min:") - self.outMinTxt = qtWidgets.QLineEdit() - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.outMinTxt, row, 1) - - row = dataLayout.rowCount() - label = qtWidgets.QLabel("Max:") - self.outMaxTxt = qtWidgets.QLineEdit() - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.outMaxTxt, row, 1) - - row = dataLayout.rowCount() - label = qtWidgets.QLabel("Step:") - self.outStepTxt = qtWidgets.QLineEdit("0.01") - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.outStepTxt, row, 1) - - row = dataLayout.rowCount() +1 - label = qtWidgets.QLabel("Plot Results:") - self.plotResults = qtWidgets.QCheckBox() - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.plotResults, row, 1) - - row = dataLayout.rowCount() - label = qtWidgets.QLabel("Y Scaling:") - self.yScaling = qtWidgets.QComboBox() - self.yScaling.addItems(Y_SCALING_OPTIONS) - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.yScaling, row, 1) - - row = dataLayout.rowCount() +1 - label = qtWidgets.QLabel("Write xye file:") - self.writeFile = qtWidgets.QCheckBox() - self.writeFile.setChecked(True) - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.writeFile, row, 1) - - self.outFileNameTxt.editingFinished.connect(self._editFinishedOutFileName) - self.outFileNameButton.clicked.connect(self._browseForOutputFile) - self.setFileName[str].connect(self.setOutFileNameText) - - - logger.debug(METHOD_EXIT_STR) - return dataBox - - def _editFinishedOutFileName(self): - ''' - process the output file name when user has finished entering this into - the text box. This may also be entered by browsing. - ''' - logger.debug(METHOD_ENTER_STR) - fileName = str(self.outFileNameTxt.text()) - if fileName != EMPTY_STR: - if os.path.exists(os.path.dirname(fileName)): - self.outputFileName = fileName - else: - if os.path.dirname(fileName) == EMPTY_STR: - curDir = os.path.realpath(os.path.curdir) - fileName = str(os.path.join(curDir, fileName)) - else: - message = qtWidgets.QMessageBox() - message.warning(self, - WARNING_STR, - "The specified directory \n" + \ - str(os.path.dirname(fileName)) + \ - "\does not exist") - self.setFileName.emit(fileName) - if not os.access(os.path.dirname(fileName), os.W_OK): - self.outputFileName = EMPTY_STR - #self.setFileName.emit(EMPTY_STR) - - logger.debug(METHOD_EXIT_STR) - - def runMapper(self, dataSource, transform, gridWriter=None): - ''' - This method is run by the runMapper method of the ProcessScanController, - This will launch proceesing by a mapper appropriate for the data that we - have & requested output. This ultimately runs the doMap of the selected - Mapper - ''' - logger.debug(METHOD_ENTER_STR) - self.dataSource = dataSource - -# if self.outputFileName == "" or self.outputFileName is None: -# self.outputFileName = os.path.join(dataSource.projectDir, -# "%s.xye" % dataSource.projectName) -# self.setFileName[str].emit(self.outputFileName) -# if os.access(os.path.dirname(self.outputFileName), os.W_OK): - logger.debug("Loading PowderScanMapper with xmin %s, xmax %s, xstep %s" % \ - (str(self.outMinTxt.text()), - str(self.outMaxTxt.text()), - str(self.outStepTxt.text()))) - self.mapper = PowderScanMapper(self.dataSource, - self.outputFileName, - transform=transform, - appConfig = self.appConfig, - dataCoord = str(self.dataCoordinate.currentText()), - xCoordMin = str(self.outMinTxt.text()), - xCoordMax = str(self.outMaxTxt.text()), - xCoordStep = str(self.outStepTxt.text()), - plotResults = self.plotResults.isChecked(), - yScaling = str(self.yScaling.currentText()), - writeXyeFile = self.writeFile.isChecked()) - self.mapper.setGridWriter(PowderScanWriter()) - self.mapper.setProgressUpdater(self._updateProgress) - self.mapper.doMap() - - - def setOutFileNameText(self, outFile): - ''' - Method to help with coordination between browsing for the fike and typing - it into the text box. - ''' - logger.debug(METHOD_ENTER_STR) - self.outFileNameTxt.setText(outFile) +''' + Copyright (c) 2017 UChicago Argonne, LLC + See LICENSE file. +''' +import logging +from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR +from rsMap3D.gui.rsm3dcommonstrings import EMPTY_STR, SAVE_FILE_STR, WARNING_STR +import os +from rsMap3D.mappers.powderscanmapper import PowderScanMapper, X_COORD_OPTIONS,\ + Y_SCALING_OPTIONS +from rsMap3D.mappers.output.powderscanwriter import PowderScanWriter +logger = logging.getLogger(__name__) + +import PyQt5.QtGui as qtGui +import PyQt5.QtWidgets as qtWidgets + +from rsMap3D.gui.output.abstractoutputview import AbstractOutputView + +XYE_FILTER_STR = "*.xye" + +class ProcessPowderScanForm(AbstractOutputView): + ''' + This class gives a front end to an external script that has been used at + sector 33 for some time. The original powderscan script was written by + Christian Schleputz, (originally at the APS and now at PSI). This script + allows processing many scans but places the results of each into a separate + file. + ''' + FORM_TITLE = "Powder Scan Output" + + @staticmethod + def createInstance(parent=None, appConfig=None): + return ProcessPowderScanForm(parent=parent, appConfig=appConfig) + + def __init__(self, **kwargs): + super(ProcessPowderScanForm, self).__init__(**kwargs) + logger.debug(METHOD_ENTER_STR) + self.mapper = None + self.outputFileName = None + layout = qtWidgets.QVBoxLayout() + self.dataBox = self._createDataBox() + controlBox = self._createControlBox() + + layout.addWidget(self.dataBox) + layout.addWidget(controlBox) + self.setLayout(layout) + #self.outputType = BINARY_OUTPUT + logger.debug(METHOD_EXIT_STR) + + def _browseForOutputFile(self): + ''' + Launch File Browser to select output file name and directory. Checks + are done to make sure the selected directory exists and that the + selected file is writable + ''' + logger.debug(METHOD_ENTER_STR) + if self.outFileNameTxt == EMPTY_STR: + fileName = str(qtWidgets.QFileDialog.getSaveFileName(None, + SAVE_FILE_STR, + filter=XYE_FILTER_STR)[0]) + else: + inFileName = str(self.outFileNameTxt.text()) + fileName = str(qtWidgets.QFileDialog.getSaveFileName(None, + SAVE_FILE_STR, + filter=XYE_FILTER_STR, + directory=inFileName)[0]) + if fileName != EMPTY_STR: + if os.path.exists(os.path.dirname(str(fileName))): + self.outFileNameTxt.setText(fileName) + self.outputFileName = fileName + self.outFileNameTxt.editingFinished.emit() + else: + message = qtWidgets.QMessageBox() + message.warning(self, WARNING_STR, + "The specified directory does not exist") + + self.outFileNameTxt.setText(fileName) + self.outputFileName = fileName + self.outFileNameTxt.editingFinished.emit() + if not os.access(os.path.dirname(fileName), os.W_OK): + message = qtWidgets.QMessageBox() + message.warning(self, WARNING_STR, + "The specified file is not writable") + else: + self.outputFileName = EMPTY_STR + self.setFileName.emit(EMPTY_STR) + + def _createDataBox(self): + ''' + create the GUI for this processing. This should be run in __init__ + ''' + logger.debug(METHOD_ENTER_STR) + dataBox = super(ProcessPowderScanForm, self)._createDataBox() + dataLayout = dataBox.layout() + row = dataLayout.rowCount() + + label = qtWidgets.QLabel("Output File Name:") + self.outFileNameTxt = qtWidgets.QLineEdit() + self.outFileNameButton = qtWidgets.QPushButton("Browse") + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.outFileNameTxt, row, 1) + dataLayout.addWidget(self.outFileNameButton, row, 2) + + row = dataLayout.rowCount() + label = qtWidgets.QLabel("Data Coordinate:") + self.dataCoordinate = qtWidgets.QComboBox() + self.dataCoordinate.addItems(X_COORD_OPTIONS) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.dataCoordinate, row, 1) + + row = dataLayout.rowCount() + 1 + label = qtWidgets.QLabel("Output Data Range - leave max/min blank for automatic range") + dataLayout.addWidget(label, row, 0, 2, -1) + + row = dataLayout.rowCount() + label = qtWidgets.QLabel("Min:") + self.outMinTxt = qtWidgets.QLineEdit() + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.outMinTxt, row, 1) + + row = dataLayout.rowCount() + label = qtWidgets.QLabel("Max:") + self.outMaxTxt = qtWidgets.QLineEdit() + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.outMaxTxt, row, 1) + + row = dataLayout.rowCount() + label = qtWidgets.QLabel("Step:") + self.outStepTxt = qtWidgets.QLineEdit("0.01") + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.outStepTxt, row, 1) + + row = dataLayout.rowCount() +1 + label = qtWidgets.QLabel("Plot Results:") + self.plotResults = qtWidgets.QCheckBox() + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.plotResults, row, 1) + + row = dataLayout.rowCount() + label = qtWidgets.QLabel("Y Scaling:") + self.yScaling = qtWidgets.QComboBox() + self.yScaling.addItems(Y_SCALING_OPTIONS) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.yScaling, row, 1) + + row = dataLayout.rowCount() +1 + label = qtWidgets.QLabel("Write xye file:") + self.writeFile = qtWidgets.QCheckBox() + self.writeFile.setChecked(True) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.writeFile, row, 1) + + self.outFileNameTxt.editingFinished.connect(self._editFinishedOutFileName) + self.outFileNameButton.clicked.connect(self._browseForOutputFile) + self.setFileName[str].connect(self.setOutFileNameText) + + + logger.debug(METHOD_EXIT_STR) + return dataBox + + def _editFinishedOutFileName(self): + ''' + process the output file name when user has finished entering this into + the text box. This may also be entered by browsing. + ''' + logger.debug(METHOD_ENTER_STR) + fileName = str(self.outFileNameTxt.text()) + if fileName != EMPTY_STR: + if os.path.exists(os.path.dirname(fileName)): + self.outputFileName = fileName + else: + if os.path.dirname(fileName) == EMPTY_STR: + curDir = os.path.realpath(os.path.curdir) + fileName = str(os.path.join(curDir, fileName)) + else: + message = qtWidgets.QMessageBox() + message.warning(self, + WARNING_STR, + "The specified directory \n" + \ + str(os.path.dirname(fileName)) + \ + "does not exist") + self.setFileName.emit(fileName) + if not os.access(os.path.dirname(fileName), os.W_OK): + self.outputFileName = EMPTY_STR + #self.setFileName.emit(EMPTY_STR) + + logger.debug(METHOD_EXIT_STR) + + def runMapper(self, dataSource, transform, gridWriter=None): + ''' + This method is run by the runMapper method of the ProcessScanController, + This will launch proceesing by a mapper appropriate for the data that we + have & requested output. This ultimately runs the doMap of the selected + Mapper + ''' + logger.debug(METHOD_ENTER_STR) + self.dataSource = dataSource + +# if self.outputFileName == "" or self.outputFileName is None: +# self.outputFileName = os.path.join(dataSource.projectDir, +# "%s.xye" % dataSource.projectName) +# self.setFileName[str].emit(self.outputFileName) +# if os.access(os.path.dirname(self.outputFileName), os.W_OK): + logger.debug("Loading PowderScanMapper with xmin %s, xmax %s, xstep %s" % \ + (str(self.outMinTxt.text()), + str(self.outMaxTxt.text()), + str(self.outStepTxt.text()))) + self.mapper = PowderScanMapper(self.dataSource, + self.outputFileName, + transform=transform, + appConfig = self.appConfig, + dataCoord = str(self.dataCoordinate.currentText()), + xCoordMin = str(self.outMinTxt.text()), + xCoordMax = str(self.outMaxTxt.text()), + xCoordStep = str(self.outStepTxt.text()), + plotResults = self.plotResults.isChecked(), + yScaling = str(self.yScaling.currentText()), + writeXyeFile = self.writeFile.isChecked()) + self.mapper.setGridWriter(PowderScanWriter()) + self.mapper.setProgressUpdater(self._updateProgress) + self.mapper.doMap() + + + def setOutFileNameText(self, outFile): + ''' + Method to help with coordination between browsing for the fike and typing + it into the text box. + ''' + logger.debug(METHOD_ENTER_STR) + self.outFileNameTxt.setText(outFile) self.outFileNameTxt.editingFinished.emit() \ No newline at end of file From 87c84cd08b3537a8b708e95d4d0cab5fb528da5d Mon Sep 17 00:00:00 2001 From: Zhan Zhang Date: Fri, 27 Mar 2026 12:40:32 -0500 Subject: [PATCH 14/14] regexp to raw string --- rsMap3D/gui/input/s12specscanfileform.py | 800 ++++++++++----------- rsMap3D/gui/input/s28specscanfileform.py | 4 +- rsMap3D/gui/input/s33specscanfileform.py | 800 ++++++++++----------- rsMap3D/gui/input/specxmldrivenfileform.py | 2 +- rsMap3D/gui/input/usesxmldetectorconfig.py | 636 ++++++++-------- rsMap3D/gui/input/xpcsspecscanfileform.py | 6 +- 6 files changed, 1124 insertions(+), 1124 deletions(-) diff --git a/rsMap3D/gui/input/s12specscanfileform.py b/rsMap3D/gui/input/s12specscanfileform.py index 3b252e4..0aaa7f7 100644 --- a/rsMap3D/gui/input/s12specscanfileform.py +++ b/rsMap3D/gui/input/s12specscanfileform.py @@ -1,400 +1,400 @@ -''' - Copyright (c) 2014, UChicago Argonne, LLC - See LICENSE file. -''' -import os - -import logging -from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR -from rsMap3D.gui.output.processpowderscanform import ProcessPowderScanForm -logger = logging.getLogger(__name__) -import PyQt5.QtGui as qtGui -import PyQt5.QtCore as qtCore -import PyQt5.QtWidgets as qtWidgets - -from rsMap3D.gui.rsm3dcommonstrings import WARNING_STR, BROWSE_STR,\ - COMMA_STR, QLINEEDIT_COLOR_STYLE, BLACK, RED, EMPTY_STR,\ - BAD_PIXEL_FILE_FILTER, SELECT_BAD_PIXEL_TITLE, SELECT_FLAT_FIELD_TITLE,\ - TIFF_FILE_FILTER -from rsMap3D.datasource.Sector12SpecDataSource import Sector12SpecDataSource -from rsMap3D.transforms.unitytransform3d import UnityTransform3D -from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D -from rsMap3D.gui.input.specxmldrivenfileform import SpecXMLDrivenFileForm -from rsMap3D.gui.output.processvtioutputform import ProcessVTIOutputForm -from rsMap3D.gui.output.processimagestackform import ProcessImageStackForm -from PyQt5.QtWidgets import QAbstractButton - -class S12SpecScanFileForm(SpecXMLDrivenFileForm): - ''' - This class presents information for selecting input files - ''' - - FORM_TITLE = "Sector 12 Spec/XML Setup" - - #UPDATE_PROGRESS_SIGNAL = "updateProgress" - # Regular expressions for string validation - PIX_AVG_REGEXP_1 = "^(\d*,*)+$" - PIX_AVG_REGEXP_2 = "^((\d)+,*){2}$" - #Strings for Text Widgets - - NONE_RADIO_NAME = "None" - BAD_PIXEL_RADIO_NAME = "Bad Pixel File" - FLAT_FIELD_RADIO_NAME = "Flat Field Correction" - - @staticmethod - def createInstance(parent=None, appConfig=None): - return S12SpecScanFileForm(parent = parent, appConfig=appConfig) - - def __init__(self, **kwargs): - ''' - Constructor - Layout Widgets on the page and link actions - ''' - logger.debug(METHOD_ENTER_STR) - super(S12SpecScanFileForm, self).__init__(**kwargs) - - #Initialize parameters - self.projectionDirection = [0,0,1] - - #Initialize a couple of widgets to do setup. - self.noFieldRadio.setChecked(True) - self._fieldCorrectionTypeChanged(*(self.noFieldRadio,)) - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _badPixelFileChanged(self): - ''' - Do some verification when the bad pixel file changes - ''' - logger.debug(METHOD_ENTER_STR) - if os.path.isfile(self.badPixelFileTxt.text()) or \ - self.badPixelFileTxt.text() == EMPTY_STR: - self.checkOkToLoad() - else: - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR, \ - "The filename entered for the bad pixel " + \ - "file is invalid") - logger.debug(METHOD_EXIT_STR) - - - @qtCore.pyqtSlot() - def _browseBadPixelFileName(self): - ''' - Launch file browser for bad pixel file - ''' - logger.debug(METHOD_ENTER_STR) - if self.badPixelFileTxt.text() == EMPTY_STR: - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_BAD_PIXEL_TITLE, \ - filter=BAD_PIXEL_FILE_FILTER)[0] - else: - fileDirectory = os.path.dirname(str(self.badPixelFileTxt.text())) - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_BAD_PIXEL_TITLE, \ - filter=BAD_PIXEL_FILE_FILTER, \ - directory = fileDirectory)[0] - if fileName != EMPTY_STR: - self.badPixelFileTxt.setText(fileName) - self.badPixelFileTxt.editingFinished.emit() - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _browseFlatFieldFileName(self): - ''' - Launch file browser for Flat field file - ''' - logger.debug(METHOD_ENTER_STR) - if self.flatFieldFileTxt.text() == EMPTY_STR: - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_FLAT_FIELD_TITLE, \ - filter=TIFF_FILE_FILTER)[0] - else: - fileDirectory = os.path.dirname(str(self.flatFieldFileTxt.text())) - fileName = qtWidgets.QFileDialog.getOpenFileName(None, - SELECT_FLAT_FIELD_TITLE, - filter=TIFF_FILE_FILTER, \ - directory = fileDirectory)[0] - if fileName != EMPTY_STR: - self.flatFieldFileTxt.setText(fileName) - self.flatFieldFileTxt.editingFinished.emit() - logger.debug(METHOD_EXIT_STR) - - def checkOkToLoad(self): - ''' - Make sure we have valid file names for project, instrument config, - and the detector config. If we do enable load button. If not disable - the load button - ''' - logger.debug(METHOD_ENTER_STR) - retVal = False - if os.path.isfile(self.projNameTxt.text()) and \ - os.path.isfile(self.instConfigTxt.text()) and \ - os.path.isfile(self.detConfigTxt.text()) and \ - (self.noFieldRadio.isChecked() or \ - (self.badPixelRadio.isChecked() and \ - not (str(self.badPixelFileTxt.text()) == "")) or \ - (self.flatFieldRadio.isChecked() and \ - not (str(self.flatFieldFileTxt.text()) == ""))) and \ - self.pixAvgValid(self.pixAvgTxt.text()) and \ - self.detROIValid(self.detROITxt.text()): - retVal = True - self.loadButton.setEnabled(retVal) - else: - retVal = False - self.loadButton.setDisabled(not retVal) - self.okToLoad.emit(retVal) - logger.debug(METHOD_EXIT_STR) - return retVal - - def _createControlBox(self): - ''' - Create Layout holding controls widgets - ''' - logger.debug(METHOD_ENTER_STR) - controlBox = super(S12SpecScanFileForm, self)._createControlBox() - logger.debug(METHOD_EXIT_STR) - return controlBox - - def _createDataBox(self): - ''' - Create widgets for collecting data - ''' - logger.debug(METHOD_ENTER_STR) - dataBox = super(S12SpecScanFileForm, self)._createDataBox() - dataLayout = dataBox.layout() - row = dataLayout.rowCount() - self._createInstConfig(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createDetConfig(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self.fieldCorrectionGroup = qtWidgets.QButtonGroup(self) - self.noFieldRadio = qtWidgets.QRadioButton(self.NONE_RADIO_NAME) - self.badPixelRadio = qtWidgets.QRadioButton(self.BAD_PIXEL_RADIO_NAME) - self.flatFieldRadio = qtWidgets.QRadioButton(self.FLAT_FIELD_RADIO_NAME) - self.fieldCorrectionGroup.addButton(self.noFieldRadio, 1) - self.fieldCorrectionGroup.addButton(self.badPixelRadio, 2) - self.fieldCorrectionGroup.addButton(self.flatFieldRadio, 3) - self.badPixelFileTxt = qtWidgets.QLineEdit() - self.flatFieldFileTxt = qtWidgets.QLineEdit() - self.badPixelFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) - self.flatFieldFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) - - - dataLayout.addWidget(self.noFieldRadio, row, 0) - row += 1 - dataLayout.addWidget(self.badPixelRadio, row, 0) - dataLayout.addWidget(self.badPixelFileTxt, row, 1) - dataLayout.addWidget(self.badPixelFileBrowseButton, row, 2) - row += 1 - dataLayout.addWidget(self.flatFieldRadio, row, 0) - dataLayout.addWidget(self.flatFieldFileTxt, row, 1) - dataLayout.addWidget(self.flatFieldFileBrowseButton, row, 2) - - row += 1 - label = qtWidgets.QLabel("Number of Pixels To Average:"); - self.pixAvgTxt = qtWidgets.QLineEdit("1,1") - rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) - self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.pixAvgTxt, row, 1) - - row = dataLayout.rowCount() + 1 - self._createDetectorROIInput(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createScanNumberInput(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createOutputType(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createHKLOutput(dataLayout, row) - - # Add Signals between widgets - self.fieldCorrectionGroup.buttonClicked\ - .connect(self._fieldCorrectionTypeChanged) - - self.badPixelFileTxt.editingFinished.connect(self._badPixelFileChanged) - self.badPixelFileBrowseButton.clicked\ - .connect(self._browseBadPixelFileName) - - self.flatFieldFileTxt.editingFinished\ - .connect(self._flatFieldFileChanged) - self.flatFieldFileBrowseButton.clicked.\ - connect(self._browseFlatFieldFileName) - - self.pixAvgTxt.textChanged.connect(self._pixAvgTxtChanged) - - dataBox.setLayout(dataLayout) - logger.debug(METHOD_EXIT_STR) - return dataBox - - @qtCore.pyqtSlot(QAbstractButton) - def _fieldCorrectionTypeChanged(self, *fieldCorrType): - ''' - React when the field type radio buttons change. Disable/Enable other - widgets as appropriate - ''' - logger.debug(METHOD_ENTER_STR) - if fieldCorrType[0].text() == self.NONE_RADIO_NAME: - self.badPixelFileTxt.setDisabled(True) - self.badPixelFileBrowseButton.setDisabled(True) - self.flatFieldFileTxt.setDisabled(True) - self.flatFieldFileBrowseButton.setDisabled(True) - elif fieldCorrType[0].text() == self.BAD_PIXEL_RADIO_NAME: - self.badPixelFileTxt.setDisabled(False) - self.badPixelFileBrowseButton.setDisabled(False) - self.flatFieldFileTxt.setDisabled(True) - self.flatFieldFileBrowseButton.setDisabled(True) - elif fieldCorrType[0].text() == self.FLAT_FIELD_RADIO_NAME: - self.badPixelFileTxt.setDisabled(True) - self.badPixelFileBrowseButton.setDisabled(True) - self.flatFieldFileTxt.setDisabled(False) - self.flatFieldFileBrowseButton.setDisabled(False) - self.checkOkToLoad() - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _flatFieldFileChanged(self): - ''' - Do some verification when the flat field file changes - ''' - logger.debug(METHOD_ENTER_STR) - if os.path.isfile(self.flatFieldFileTxt.text()) or \ - self.flatFieldFileTxt.text() == "": - self.checkOkToLoad() - else: - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR, \ - "The filename entered for the flat field " + \ - "file is invalid") - logger.debug(METHOD_EXIT_STR) - - def getBadPixelFileName(self): - ''' - Return the badPixel file name. If empty or if the bad pixel radio - button is not checked return None - ''' - logger.debug(METHOD_ENTER_STR) - retVal = None - if (str(self.badPixelFileTxt.text()) == EMPTY_STR) or \ - (not self.badPixelRadio.isChecked()): - retVal = None - else: - retVal = str(self.badPixelFileTxt.text()) - logger.debug("Exit " + str(retVal)) - return retVal - - def getDataSource(self): - logger.debug(METHOD_ENTER_STR) - if self.getOutputType() == self.SIMPLE_GRID_MAP_STR: - self.transform = UnityTransform3D() - elif self.getOutputType() == self.POLE_MAP_STR: - self.transform = \ - PoleMapTransform3D(projectionDirection=\ - self.getProjectionDirection()) - else: - self.transform = None - - self.dataSource = \ - Sector12SpecDataSource(str(self.getProjectDir()), \ - str(self.getProjectName()), \ - str(self.getProjectExtension()), \ - str(self.getInstConfigName()), \ - str(self.getDetConfigName()), \ - transform = self.transform, \ - scanList = self.getScanList(), \ - roi = self.getDetectorROI(), \ - pixelsToAverage = \ - self.getPixelsToAverage(), \ - badPixelFile = \ - self.getBadPixelFileName(), \ - flatFieldFile = \ - self.getFlatFieldFileName(), \ - appConfig = self.appConfig - ) - self.dataSource.setProgressUpdater(self.updateProgress) - self.dataSource.setCurrentDetector(self.currentDetector) - self.dataSource.loadSource(mapHKL = self.getMapAsHKL()) - logger.debug(METHOD_EXIT_STR) - return self.dataSource - - def getFlatFieldFileName(self): - ''' - Return the flat field file name. If empty or if the bad pixel radio - button is not checked return None - ''' - logger.debug(METHOD_ENTER_STR) - retVal = None - if (str(self.flatFieldFileTxt.text()) == EMPTY_STR) or \ - (not self.flatFieldRadio.isChecked()): - retVal = None - else: - retVal = str(self.flatFieldFileTxt.text()) - logger.debug(METHOD_EXIT_STR) - return retVal - - def getOutputForms(self): - logger.debug(METHOD_ENTER_STR) - outputForms = [] - outputForms.append(ProcessVTIOutputForm) - outputForms.append(ProcessImageStackForm) - outputForms.append(ProcessPowderScanForm) - logger.debug(METHOD_EXIT_STR) - return outputForms - - def getPixelsToAverage(self): - ''' - :return: the pixels to average as a list - ''' - logger.debug(METHOD_ENTER_STR) - pixelStrings = str(self.pixAvgTxt.text()).split(COMMA_STR) - pixels = [] - for value in pixelStrings: - pixels.append(int(value)) - logger.debug(METHOD_EXIT_STR) - return pixels - - def getProjectionDirection(self): - ''' - Return projection direction for stereographic projections - ''' - logger.debug(METHOD_ENTER_STR) - logger.debug(METHOD_EXIT_STR) - return self.projectionDirection - - - def _pixAvgTxtChanged(self, text): - ''' - Check to make sure the text for pix to average is valid and indicate - by a color change - :param text: new values as a text list - ''' - logger.debug(METHOD_ENTER_STR) - if self.pixAvgValid(text): - self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) - else: - self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) - self.checkOkToLoad() - logger.debug(METHOD_EXIT_STR) - - def pixAvgValid(self, text): - ''' - Check to make sure that the pixAvgText is valid - :param text: new values as a text list - ''' - logger.debug(METHOD_ENTER_STR) - retVal = False - rxPixAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_2) - validator = qtGui.QRegExpValidator(rxPixAvg, None) - pos = 0 - if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: - retVal = True - else: - retVal = False - logger.debug("Exit " + str(retVal)) - return retVal +''' + Copyright (c) 2014, UChicago Argonne, LLC + See LICENSE file. +''' +import os + +import logging +from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR +from rsMap3D.gui.output.processpowderscanform import ProcessPowderScanForm +logger = logging.getLogger(__name__) +import PyQt5.QtGui as qtGui +import PyQt5.QtCore as qtCore +import PyQt5.QtWidgets as qtWidgets + +from rsMap3D.gui.rsm3dcommonstrings import WARNING_STR, BROWSE_STR,\ + COMMA_STR, QLINEEDIT_COLOR_STYLE, BLACK, RED, EMPTY_STR,\ + BAD_PIXEL_FILE_FILTER, SELECT_BAD_PIXEL_TITLE, SELECT_FLAT_FIELD_TITLE,\ + TIFF_FILE_FILTER +from rsMap3D.datasource.Sector12SpecDataSource import Sector12SpecDataSource +from rsMap3D.transforms.unitytransform3d import UnityTransform3D +from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D +from rsMap3D.gui.input.specxmldrivenfileform import SpecXMLDrivenFileForm +from rsMap3D.gui.output.processvtioutputform import ProcessVTIOutputForm +from rsMap3D.gui.output.processimagestackform import ProcessImageStackForm +from PyQt5.QtWidgets import QAbstractButton + +class S12SpecScanFileForm(SpecXMLDrivenFileForm): + ''' + This class presents information for selecting input files + ''' + + FORM_TITLE = "Sector 12 Spec/XML Setup" + + #UPDATE_PROGRESS_SIGNAL = "updateProgress" + # Regular expressions for string validation + PIX_AVG_REGEXP_1 = r"^(\d*,*)+$" + PIX_AVG_REGEXP_2 = r"^((\d)+,*){2}$" + #Strings for Text Widgets + + NONE_RADIO_NAME = "None" + BAD_PIXEL_RADIO_NAME = "Bad Pixel File" + FLAT_FIELD_RADIO_NAME = "Flat Field Correction" + + @staticmethod + def createInstance(parent=None, appConfig=None): + return S12SpecScanFileForm(parent = parent, appConfig=appConfig) + + def __init__(self, **kwargs): + ''' + Constructor - Layout Widgets on the page and link actions + ''' + logger.debug(METHOD_ENTER_STR) + super(S12SpecScanFileForm, self).__init__(**kwargs) + + #Initialize parameters + self.projectionDirection = [0,0,1] + + #Initialize a couple of widgets to do setup. + self.noFieldRadio.setChecked(True) + self._fieldCorrectionTypeChanged(*(self.noFieldRadio,)) + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _badPixelFileChanged(self): + ''' + Do some verification when the bad pixel file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.badPixelFileTxt.text()) or \ + self.badPixelFileTxt.text() == EMPTY_STR: + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the bad pixel " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + + @qtCore.pyqtSlot() + def _browseBadPixelFileName(self): + ''' + Launch file browser for bad pixel file + ''' + logger.debug(METHOD_ENTER_STR) + if self.badPixelFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.badPixelFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.badPixelFileTxt.setText(fileName) + self.badPixelFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _browseFlatFieldFileName(self): + ''' + Launch file browser for Flat field file + ''' + logger.debug(METHOD_ENTER_STR) + if self.flatFieldFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_FLAT_FIELD_TITLE, \ + filter=TIFF_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.flatFieldFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, + SELECT_FLAT_FIELD_TITLE, + filter=TIFF_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.flatFieldFileTxt.setText(fileName) + self.flatFieldFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + def checkOkToLoad(self): + ''' + Make sure we have valid file names for project, instrument config, + and the detector config. If we do enable load button. If not disable + the load button + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + if os.path.isfile(self.projNameTxt.text()) and \ + os.path.isfile(self.instConfigTxt.text()) and \ + os.path.isfile(self.detConfigTxt.text()) and \ + (self.noFieldRadio.isChecked() or \ + (self.badPixelRadio.isChecked() and \ + not (str(self.badPixelFileTxt.text()) == "")) or \ + (self.flatFieldRadio.isChecked() and \ + not (str(self.flatFieldFileTxt.text()) == ""))) and \ + self.pixAvgValid(self.pixAvgTxt.text()) and \ + self.detROIValid(self.detROITxt.text()): + retVal = True + self.loadButton.setEnabled(retVal) + else: + retVal = False + self.loadButton.setDisabled(not retVal) + self.okToLoad.emit(retVal) + logger.debug(METHOD_EXIT_STR) + return retVal + + def _createControlBox(self): + ''' + Create Layout holding controls widgets + ''' + logger.debug(METHOD_ENTER_STR) + controlBox = super(S12SpecScanFileForm, self)._createControlBox() + logger.debug(METHOD_EXIT_STR) + return controlBox + + def _createDataBox(self): + ''' + Create widgets for collecting data + ''' + logger.debug(METHOD_ENTER_STR) + dataBox = super(S12SpecScanFileForm, self)._createDataBox() + dataLayout = dataBox.layout() + row = dataLayout.rowCount() + self._createInstConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createDetConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self.fieldCorrectionGroup = qtWidgets.QButtonGroup(self) + self.noFieldRadio = qtWidgets.QRadioButton(self.NONE_RADIO_NAME) + self.badPixelRadio = qtWidgets.QRadioButton(self.BAD_PIXEL_RADIO_NAME) + self.flatFieldRadio = qtWidgets.QRadioButton(self.FLAT_FIELD_RADIO_NAME) + self.fieldCorrectionGroup.addButton(self.noFieldRadio, 1) + self.fieldCorrectionGroup.addButton(self.badPixelRadio, 2) + self.fieldCorrectionGroup.addButton(self.flatFieldRadio, 3) + self.badPixelFileTxt = qtWidgets.QLineEdit() + self.flatFieldFileTxt = qtWidgets.QLineEdit() + self.badPixelFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + self.flatFieldFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + + + dataLayout.addWidget(self.noFieldRadio, row, 0) + row += 1 + dataLayout.addWidget(self.badPixelRadio, row, 0) + dataLayout.addWidget(self.badPixelFileTxt, row, 1) + dataLayout.addWidget(self.badPixelFileBrowseButton, row, 2) + row += 1 + dataLayout.addWidget(self.flatFieldRadio, row, 0) + dataLayout.addWidget(self.flatFieldFileTxt, row, 1) + dataLayout.addWidget(self.flatFieldFileBrowseButton, row, 2) + + row += 1 + label = qtWidgets.QLabel("Number of Pixels To Average:"); + self.pixAvgTxt = qtWidgets.QLineEdit("1,1") + rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) + self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.pixAvgTxt, row, 1) + + row = dataLayout.rowCount() + 1 + self._createDetectorROIInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createScanNumberInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createOutputType(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createHKLOutput(dataLayout, row) + + # Add Signals between widgets + self.fieldCorrectionGroup.buttonClicked\ + .connect(self._fieldCorrectionTypeChanged) + + self.badPixelFileTxt.editingFinished.connect(self._badPixelFileChanged) + self.badPixelFileBrowseButton.clicked\ + .connect(self._browseBadPixelFileName) + + self.flatFieldFileTxt.editingFinished\ + .connect(self._flatFieldFileChanged) + self.flatFieldFileBrowseButton.clicked.\ + connect(self._browseFlatFieldFileName) + + self.pixAvgTxt.textChanged.connect(self._pixAvgTxtChanged) + + dataBox.setLayout(dataLayout) + logger.debug(METHOD_EXIT_STR) + return dataBox + + @qtCore.pyqtSlot(QAbstractButton) + def _fieldCorrectionTypeChanged(self, *fieldCorrType): + ''' + React when the field type radio buttons change. Disable/Enable other + widgets as appropriate + ''' + logger.debug(METHOD_ENTER_STR) + if fieldCorrType[0].text() == self.NONE_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.BAD_PIXEL_RADIO_NAME: + self.badPixelFileTxt.setDisabled(False) + self.badPixelFileBrowseButton.setDisabled(False) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.FLAT_FIELD_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(False) + self.flatFieldFileBrowseButton.setDisabled(False) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _flatFieldFileChanged(self): + ''' + Do some verification when the flat field file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.flatFieldFileTxt.text()) or \ + self.flatFieldFileTxt.text() == "": + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the flat field " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + def getBadPixelFileName(self): + ''' + Return the badPixel file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.badPixelFileTxt.text()) == EMPTY_STR) or \ + (not self.badPixelRadio.isChecked()): + retVal = None + else: + retVal = str(self.badPixelFileTxt.text()) + logger.debug("Exit " + str(retVal)) + return retVal + + def getDataSource(self): + logger.debug(METHOD_ENTER_STR) + if self.getOutputType() == self.SIMPLE_GRID_MAP_STR: + self.transform = UnityTransform3D() + elif self.getOutputType() == self.POLE_MAP_STR: + self.transform = \ + PoleMapTransform3D(projectionDirection=\ + self.getProjectionDirection()) + else: + self.transform = None + + self.dataSource = \ + Sector12SpecDataSource(str(self.getProjectDir()), \ + str(self.getProjectName()), \ + str(self.getProjectExtension()), \ + str(self.getInstConfigName()), \ + str(self.getDetConfigName()), \ + transform = self.transform, \ + scanList = self.getScanList(), \ + roi = self.getDetectorROI(), \ + pixelsToAverage = \ + self.getPixelsToAverage(), \ + badPixelFile = \ + self.getBadPixelFileName(), \ + flatFieldFile = \ + self.getFlatFieldFileName(), \ + appConfig = self.appConfig + ) + self.dataSource.setProgressUpdater(self.updateProgress) + self.dataSource.setCurrentDetector(self.currentDetector) + self.dataSource.loadSource(mapHKL = self.getMapAsHKL()) + logger.debug(METHOD_EXIT_STR) + return self.dataSource + + def getFlatFieldFileName(self): + ''' + Return the flat field file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.flatFieldFileTxt.text()) == EMPTY_STR) or \ + (not self.flatFieldRadio.isChecked()): + retVal = None + else: + retVal = str(self.flatFieldFileTxt.text()) + logger.debug(METHOD_EXIT_STR) + return retVal + + def getOutputForms(self): + logger.debug(METHOD_ENTER_STR) + outputForms = [] + outputForms.append(ProcessVTIOutputForm) + outputForms.append(ProcessImageStackForm) + outputForms.append(ProcessPowderScanForm) + logger.debug(METHOD_EXIT_STR) + return outputForms + + def getPixelsToAverage(self): + ''' + :return: the pixels to average as a list + ''' + logger.debug(METHOD_ENTER_STR) + pixelStrings = str(self.pixAvgTxt.text()).split(COMMA_STR) + pixels = [] + for value in pixelStrings: + pixels.append(int(value)) + logger.debug(METHOD_EXIT_STR) + return pixels + + def getProjectionDirection(self): + ''' + Return projection direction for stereographic projections + ''' + logger.debug(METHOD_ENTER_STR) + logger.debug(METHOD_EXIT_STR) + return self.projectionDirection + + + def _pixAvgTxtChanged(self, text): + ''' + Check to make sure the text for pix to average is valid and indicate + by a color change + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + if self.pixAvgValid(text): + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) + else: + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + def pixAvgValid(self, text): + ''' + Check to make sure that the pixAvgText is valid + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + rxPixAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_2) + validator = qtGui.QRegExpValidator(rxPixAvg, None) + pos = 0 + if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: + retVal = True + else: + retVal = False + logger.debug("Exit " + str(retVal)) + return retVal diff --git a/rsMap3D/gui/input/s28specscanfileform.py b/rsMap3D/gui/input/s28specscanfileform.py index 9db968e..88195b2 100644 --- a/rsMap3D/gui/input/s28specscanfileform.py +++ b/rsMap3D/gui/input/s28specscanfileform.py @@ -33,8 +33,8 @@ class S28SpecScanFileForm(SpecXMLDrivenFileForm): #UPDATE_PROGRESS_SIGNAL = "updateProgress" # Regular expressions for string validation - PIX_AVG_REGEXP_1 = "^(\d*,*)+$" - PIX_AVG_REGEXP_2 = "^((\d)+,*){2}$" + PIX_AVG_REGEXP_1 = r"^(\d*,*)+$" + PIX_AVG_REGEXP_2 = r"^((\d)+,*){2}$" #Strings for Text Widgets NONE_RADIO_NAME = "None" diff --git a/rsMap3D/gui/input/s33specscanfileform.py b/rsMap3D/gui/input/s33specscanfileform.py index 8166b2d..4ce6d19 100644 --- a/rsMap3D/gui/input/s33specscanfileform.py +++ b/rsMap3D/gui/input/s33specscanfileform.py @@ -1,400 +1,400 @@ -''' - Copyright (c) 2014, UChicago Argonne, LLC - See LICENSE file. -''' -import os - -import logging -from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR -from rsMap3D.gui.output.processpowderscanform import ProcessPowderScanForm -logger = logging.getLogger(__name__) -import PyQt5.QtGui as qtGui -import PyQt5.QtCore as qtCore -import PyQt5.QtWidgets as qtWidgets - -from rsMap3D.gui.rsm3dcommonstrings import WARNING_STR, BROWSE_STR,\ - COMMA_STR, QLINEEDIT_COLOR_STYLE, BLACK, RED, EMPTY_STR,\ - BAD_PIXEL_FILE_FILTER, SELECT_BAD_PIXEL_TITLE, SELECT_FLAT_FIELD_TITLE,\ - TIFF_FILE_FILTER -from rsMap3D.datasource.Sector33SpecDataSource import Sector33SpecDataSource -from rsMap3D.transforms.unitytransform3d import UnityTransform3D -from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D -from rsMap3D.gui.input.specxmldrivenfileform import SpecXMLDrivenFileForm -from rsMap3D.gui.output.processvtioutputform import ProcessVTIOutputForm -from rsMap3D.gui.output.processimagestackform import ProcessImageStackForm -from PyQt5.QtWidgets import QAbstractButton - -class S33SpecScanFileForm(SpecXMLDrivenFileForm): - ''' - This class presents information for selecting input files - ''' - - FORM_TITLE = "Sector 33 Spec/XML Setup" - - #UPDATE_PROGRESS_SIGNAL = "updateProgress" - # Regular expressions for string validation - PIX_AVG_REGEXP_1 = "^(\d*,*)+$" - PIX_AVG_REGEXP_2 = "^((\d)+,*){2}$" - #Strings for Text Widgets - - NONE_RADIO_NAME = "None" - BAD_PIXEL_RADIO_NAME = "Bad Pixel File" - FLAT_FIELD_RADIO_NAME = "Flat Field Correction" - - @staticmethod - def createInstance(parent=None, appConfig=None): - return S33SpecScanFileForm(parent = parent, appConfig=appConfig) - - def __init__(self, **kwargs): - ''' - Constructor - Layout Widgets on the page and link actions - ''' - logger.debug(METHOD_ENTER_STR) - super(S33SpecScanFileForm, self).__init__(**kwargs) - - #Initialize parameters - self.projectionDirection = [0,0,1] - - #Initialize a couple of widgets to do setup. - self.noFieldRadio.setChecked(True) - self._fieldCorrectionTypeChanged(*(self.noFieldRadio,)) - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _badPixelFileChanged(self): - ''' - Do some verification when the bad pixel file changes - ''' - logger.debug(METHOD_ENTER_STR) - if os.path.isfile(self.badPixelFileTxt.text()) or \ - self.badPixelFileTxt.text() == EMPTY_STR: - self.checkOkToLoad() - else: - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR, \ - "The filename entered for the bad pixel " + \ - "file is invalid") - logger.debug(METHOD_EXIT_STR) - - - @qtCore.pyqtSlot() - def _browseBadPixelFileName(self): - ''' - Launch file browser for bad pixel file - ''' - logger.debug(METHOD_ENTER_STR) - if self.badPixelFileTxt.text() == EMPTY_STR: - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_BAD_PIXEL_TITLE, \ - filter=BAD_PIXEL_FILE_FILTER)[0] - else: - fileDirectory = os.path.dirname(str(self.badPixelFileTxt.text())) - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_BAD_PIXEL_TITLE, \ - filter=BAD_PIXEL_FILE_FILTER, \ - directory = fileDirectory)[0] - if fileName != EMPTY_STR: - self.badPixelFileTxt.setText(fileName) - self.badPixelFileTxt.editingFinished.emit() - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _browseFlatFieldFileName(self): - ''' - Launch file browser for Flat field file - ''' - logger.debug(METHOD_ENTER_STR) - if self.flatFieldFileTxt.text() == EMPTY_STR: - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_FLAT_FIELD_TITLE, \ - filter=TIFF_FILE_FILTER)[0] - else: - fileDirectory = os.path.dirname(str(self.flatFieldFileTxt.text())) - fileName = qtWidgets.QFileDialog.getOpenFileName(None, - SELECT_FLAT_FIELD_TITLE, - filter=TIFF_FILE_FILTER, \ - directory = fileDirectory)[0] - if fileName != EMPTY_STR: - self.flatFieldFileTxt.setText(fileName) - self.flatFieldFileTxt.editingFinished.emit() - logger.debug(METHOD_EXIT_STR) - - def checkOkToLoad(self): - ''' - Make sure we have valid file names for project, instrument config, - and the detector config. If we do enable load button. If not disable - the load button - ''' - logger.debug(METHOD_ENTER_STR) - retVal = False - if os.path.isfile(self.projNameTxt.text()) and \ - os.path.isfile(self.instConfigTxt.text()) and \ - os.path.isfile(self.detConfigTxt.text()) and \ - (self.noFieldRadio.isChecked() or \ - (self.badPixelRadio.isChecked() and \ - not (str(self.badPixelFileTxt.text()) == "")) or \ - (self.flatFieldRadio.isChecked() and \ - not (str(self.flatFieldFileTxt.text()) == ""))) and \ - self.pixAvgValid(self.pixAvgTxt.text()) and \ - self.detROIValid(self.detROITxt.text()): - retVal = True - self.loadButton.setEnabled(retVal) - else: - retVal = False - self.loadButton.setDisabled(not retVal) - self.okToLoad.emit(retVal) - logger.debug(METHOD_EXIT_STR) - return retVal - - def _createControlBox(self): - ''' - Create Layout holding controls widgets - ''' - logger.debug(METHOD_ENTER_STR) - controlBox = super(S33SpecScanFileForm, self)._createControlBox() - logger.debug(METHOD_EXIT_STR) - return controlBox - - def _createDataBox(self): - ''' - Create widgets for collecting data - ''' - logger.debug(METHOD_ENTER_STR) - dataBox = super(S33SpecScanFileForm, self)._createDataBox() - dataLayout = dataBox.layout() - row = dataLayout.rowCount() - self._createInstConfig(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createDetConfig(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self.fieldCorrectionGroup = qtWidgets.QButtonGroup(self) - self.noFieldRadio = qtWidgets.QRadioButton(self.NONE_RADIO_NAME) - self.badPixelRadio = qtWidgets.QRadioButton(self.BAD_PIXEL_RADIO_NAME) - self.flatFieldRadio = qtWidgets.QRadioButton(self.FLAT_FIELD_RADIO_NAME) - self.fieldCorrectionGroup.addButton(self.noFieldRadio, 1) - self.fieldCorrectionGroup.addButton(self.badPixelRadio, 2) - self.fieldCorrectionGroup.addButton(self.flatFieldRadio, 3) - self.badPixelFileTxt = qtWidgets.QLineEdit() - self.flatFieldFileTxt = qtWidgets.QLineEdit() - self.badPixelFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) - self.flatFieldFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) - - - dataLayout.addWidget(self.noFieldRadio, row, 0) - row += 1 - dataLayout.addWidget(self.badPixelRadio, row, 0) - dataLayout.addWidget(self.badPixelFileTxt, row, 1) - dataLayout.addWidget(self.badPixelFileBrowseButton, row, 2) - row += 1 - dataLayout.addWidget(self.flatFieldRadio, row, 0) - dataLayout.addWidget(self.flatFieldFileTxt, row, 1) - dataLayout.addWidget(self.flatFieldFileBrowseButton, row, 2) - - row += 1 - label = qtWidgets.QLabel("Number of Pixels To Average:"); - self.pixAvgTxt = qtWidgets.QLineEdit("1,1") - rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) - self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) - dataLayout.addWidget(label, row, 0) - dataLayout.addWidget(self.pixAvgTxt, row, 1) - - row = dataLayout.rowCount() + 1 - self._createDetectorROIInput(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createScanNumberInput(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createOutputType(dataLayout, row) - - row = dataLayout.rowCount() + 1 - self._createHKLOutput(dataLayout, row) - - # Add Signals between widgets - self.fieldCorrectionGroup.buttonClicked\ - .connect(self._fieldCorrectionTypeChanged) - - self.badPixelFileTxt.editingFinished.connect(self._badPixelFileChanged) - self.badPixelFileBrowseButton.clicked\ - .connect(self._browseBadPixelFileName) - - self.flatFieldFileTxt.editingFinished\ - .connect(self._flatFieldFileChanged) - self.flatFieldFileBrowseButton.clicked.\ - connect(self._browseFlatFieldFileName) - - self.pixAvgTxt.textChanged.connect(self._pixAvgTxtChanged) - - dataBox.setLayout(dataLayout) - logger.debug(METHOD_EXIT_STR) - return dataBox - - @qtCore.pyqtSlot(QAbstractButton) - def _fieldCorrectionTypeChanged(self, *fieldCorrType): - ''' - React when the field type radio buttons change. Disable/Enable other - widgets as appropriate - ''' - logger.debug(METHOD_ENTER_STR) - if fieldCorrType[0].text() == self.NONE_RADIO_NAME: - self.badPixelFileTxt.setDisabled(True) - self.badPixelFileBrowseButton.setDisabled(True) - self.flatFieldFileTxt.setDisabled(True) - self.flatFieldFileBrowseButton.setDisabled(True) - elif fieldCorrType[0].text() == self.BAD_PIXEL_RADIO_NAME: - self.badPixelFileTxt.setDisabled(False) - self.badPixelFileBrowseButton.setDisabled(False) - self.flatFieldFileTxt.setDisabled(True) - self.flatFieldFileBrowseButton.setDisabled(True) - elif fieldCorrType[0].text() == self.FLAT_FIELD_RADIO_NAME: - self.badPixelFileTxt.setDisabled(True) - self.badPixelFileBrowseButton.setDisabled(True) - self.flatFieldFileTxt.setDisabled(False) - self.flatFieldFileBrowseButton.setDisabled(False) - self.checkOkToLoad() - logger.debug(METHOD_EXIT_STR) - - @qtCore.pyqtSlot() - def _flatFieldFileChanged(self): - ''' - Do some verification when the flat field file changes - ''' - logger.debug(METHOD_ENTER_STR) - if os.path.isfile(self.flatFieldFileTxt.text()) or \ - self.flatFieldFileTxt.text() == "": - self.checkOkToLoad() - else: - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR, \ - "The filename entered for the flat field " + \ - "file is invalid") - logger.debug(METHOD_EXIT_STR) - - def getBadPixelFileName(self): - ''' - Return the badPixel file name. If empty or if the bad pixel radio - button is not checked return None - ''' - logger.debug(METHOD_ENTER_STR) - retVal = None - if (str(self.badPixelFileTxt.text()) == EMPTY_STR) or \ - (not self.badPixelRadio.isChecked()): - retVal = None - else: - retVal = str(self.badPixelFileTxt.text()) - logger.debug("Exit " + str(retVal)) - return retVal - - def getDataSource(self): - logger.debug(METHOD_ENTER_STR) - if self.getOutputType() == self.SIMPLE_GRID_MAP_STR: - self.transform = UnityTransform3D() - elif self.getOutputType() == self.POLE_MAP_STR: - self.transform = \ - PoleMapTransform3D(projectionDirection=\ - self.getProjectionDirection()) - else: - self.transform = None - - self.dataSource = \ - Sector33SpecDataSource(str(self.getProjectDir()), \ - str(self.getProjectName()), \ - str(self.getProjectExtension()), \ - str(self.getInstConfigName()), \ - str(self.getDetConfigName()), \ - transform = self.transform, \ - scanList = self.getScanList(), \ - roi = self.getDetectorROI(), \ - pixelsToAverage = \ - self.getPixelsToAverage(), \ - badPixelFile = \ - self.getBadPixelFileName(), \ - flatFieldFile = \ - self.getFlatFieldFileName(), \ - appConfig = self.appConfig - ) - self.dataSource.setProgressUpdater(self.updateProgress) - self.dataSource.setCurrentDetector(self.currentDetector) - self.dataSource.loadSource(mapHKL = self.getMapAsHKL()) - logger.debug(METHOD_EXIT_STR) - return self.dataSource - - def getFlatFieldFileName(self): - ''' - Return the flat field file name. If empty or if the bad pixel radio - button is not checked return None - ''' - logger.debug(METHOD_ENTER_STR) - retVal = None - if (str(self.flatFieldFileTxt.text()) == EMPTY_STR) or \ - (not self.flatFieldRadio.isChecked()): - retVal = None - else: - retVal = str(self.flatFieldFileTxt.text()) - logger.debug(METHOD_EXIT_STR) - return retVal - - def getOutputForms(self): - logger.debug(METHOD_ENTER_STR) - outputForms = [] - outputForms.append(ProcessVTIOutputForm) - outputForms.append(ProcessImageStackForm) - outputForms.append(ProcessPowderScanForm) - logger.debug(METHOD_EXIT_STR) - return outputForms - - def getPixelsToAverage(self): - ''' - :return: the pixels to average as a list - ''' - logger.debug(METHOD_ENTER_STR) - pixelStrings = str(self.pixAvgTxt.text()).split(COMMA_STR) - pixels = [] - for value in pixelStrings: - pixels.append(int(value)) - logger.debug(METHOD_EXIT_STR) - return pixels - - def getProjectionDirection(self): - ''' - Return projection direction for stereographic projections - ''' - logger.debug(METHOD_ENTER_STR) - logger.debug(METHOD_EXIT_STR) - return self.projectionDirection - - - def _pixAvgTxtChanged(self, text): - ''' - Check to make sure the text for pix to average is valid and indicate - by a color change - :param text: new values as a text list - ''' - logger.debug(METHOD_ENTER_STR) - if self.pixAvgValid(text): - self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) - else: - self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) - self.checkOkToLoad() - logger.debug(METHOD_EXIT_STR) - - def pixAvgValid(self, text): - ''' - Check to make sure that the pixAvgText is valid - :param text: new values as a text list - ''' - logger.debug(METHOD_ENTER_STR) - retVal = False - rxPixAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_2) - validator = qtGui.QRegExpValidator(rxPixAvg, None) - pos = 0 - if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: - retVal = True - else: - retVal = False - logger.debug("Exit " + str(retVal)) - return retVal +''' + Copyright (c) 2014, UChicago Argonne, LLC + See LICENSE file. +''' +import os + +import logging +from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR +from rsMap3D.gui.output.processpowderscanform import ProcessPowderScanForm +logger = logging.getLogger(__name__) +import PyQt5.QtGui as qtGui +import PyQt5.QtCore as qtCore +import PyQt5.QtWidgets as qtWidgets + +from rsMap3D.gui.rsm3dcommonstrings import WARNING_STR, BROWSE_STR,\ + COMMA_STR, QLINEEDIT_COLOR_STYLE, BLACK, RED, EMPTY_STR,\ + BAD_PIXEL_FILE_FILTER, SELECT_BAD_PIXEL_TITLE, SELECT_FLAT_FIELD_TITLE,\ + TIFF_FILE_FILTER +from rsMap3D.datasource.Sector33SpecDataSource import Sector33SpecDataSource +from rsMap3D.transforms.unitytransform3d import UnityTransform3D +from rsMap3D.transforms.polemaptransform3d import PoleMapTransform3D +from rsMap3D.gui.input.specxmldrivenfileform import SpecXMLDrivenFileForm +from rsMap3D.gui.output.processvtioutputform import ProcessVTIOutputForm +from rsMap3D.gui.output.processimagestackform import ProcessImageStackForm +from PyQt5.QtWidgets import QAbstractButton + +class S33SpecScanFileForm(SpecXMLDrivenFileForm): + ''' + This class presents information for selecting input files + ''' + + FORM_TITLE = "Sector 33 Spec/XML Setup" + + #UPDATE_PROGRESS_SIGNAL = "updateProgress" + # Regular expressions for string validation + PIX_AVG_REGEXP_1 = r"^(\d*,*)+$" + PIX_AVG_REGEXP_2 = r"^((\d)+,*){2}$" + #Strings for Text Widgets + + NONE_RADIO_NAME = "None" + BAD_PIXEL_RADIO_NAME = "Bad Pixel File" + FLAT_FIELD_RADIO_NAME = "Flat Field Correction" + + @staticmethod + def createInstance(parent=None, appConfig=None): + return S33SpecScanFileForm(parent = parent, appConfig=appConfig) + + def __init__(self, **kwargs): + ''' + Constructor - Layout Widgets on the page and link actions + ''' + logger.debug(METHOD_ENTER_STR) + super(S33SpecScanFileForm, self).__init__(**kwargs) + + #Initialize parameters + self.projectionDirection = [0,0,1] + + #Initialize a couple of widgets to do setup. + self.noFieldRadio.setChecked(True) + self._fieldCorrectionTypeChanged(*(self.noFieldRadio,)) + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _badPixelFileChanged(self): + ''' + Do some verification when the bad pixel file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.badPixelFileTxt.text()) or \ + self.badPixelFileTxt.text() == EMPTY_STR: + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the bad pixel " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + + @qtCore.pyqtSlot() + def _browseBadPixelFileName(self): + ''' + Launch file browser for bad pixel file + ''' + logger.debug(METHOD_ENTER_STR) + if self.badPixelFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.badPixelFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_BAD_PIXEL_TITLE, \ + filter=BAD_PIXEL_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.badPixelFileTxt.setText(fileName) + self.badPixelFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _browseFlatFieldFileName(self): + ''' + Launch file browser for Flat field file + ''' + logger.debug(METHOD_ENTER_STR) + if self.flatFieldFileTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_FLAT_FIELD_TITLE, \ + filter=TIFF_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.flatFieldFileTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, + SELECT_FLAT_FIELD_TITLE, + filter=TIFF_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.flatFieldFileTxt.setText(fileName) + self.flatFieldFileTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + def checkOkToLoad(self): + ''' + Make sure we have valid file names for project, instrument config, + and the detector config. If we do enable load button. If not disable + the load button + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + if os.path.isfile(self.projNameTxt.text()) and \ + os.path.isfile(self.instConfigTxt.text()) and \ + os.path.isfile(self.detConfigTxt.text()) and \ + (self.noFieldRadio.isChecked() or \ + (self.badPixelRadio.isChecked() and \ + not (str(self.badPixelFileTxt.text()) == "")) or \ + (self.flatFieldRadio.isChecked() and \ + not (str(self.flatFieldFileTxt.text()) == ""))) and \ + self.pixAvgValid(self.pixAvgTxt.text()) and \ + self.detROIValid(self.detROITxt.text()): + retVal = True + self.loadButton.setEnabled(retVal) + else: + retVal = False + self.loadButton.setDisabled(not retVal) + self.okToLoad.emit(retVal) + logger.debug(METHOD_EXIT_STR) + return retVal + + def _createControlBox(self): + ''' + Create Layout holding controls widgets + ''' + logger.debug(METHOD_ENTER_STR) + controlBox = super(S33SpecScanFileForm, self)._createControlBox() + logger.debug(METHOD_EXIT_STR) + return controlBox + + def _createDataBox(self): + ''' + Create widgets for collecting data + ''' + logger.debug(METHOD_ENTER_STR) + dataBox = super(S33SpecScanFileForm, self)._createDataBox() + dataLayout = dataBox.layout() + row = dataLayout.rowCount() + self._createInstConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createDetConfig(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self.fieldCorrectionGroup = qtWidgets.QButtonGroup(self) + self.noFieldRadio = qtWidgets.QRadioButton(self.NONE_RADIO_NAME) + self.badPixelRadio = qtWidgets.QRadioButton(self.BAD_PIXEL_RADIO_NAME) + self.flatFieldRadio = qtWidgets.QRadioButton(self.FLAT_FIELD_RADIO_NAME) + self.fieldCorrectionGroup.addButton(self.noFieldRadio, 1) + self.fieldCorrectionGroup.addButton(self.badPixelRadio, 2) + self.fieldCorrectionGroup.addButton(self.flatFieldRadio, 3) + self.badPixelFileTxt = qtWidgets.QLineEdit() + self.flatFieldFileTxt = qtWidgets.QLineEdit() + self.badPixelFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + self.flatFieldFileBrowseButton = qtWidgets.QPushButton(BROWSE_STR) + + + dataLayout.addWidget(self.noFieldRadio, row, 0) + row += 1 + dataLayout.addWidget(self.badPixelRadio, row, 0) + dataLayout.addWidget(self.badPixelFileTxt, row, 1) + dataLayout.addWidget(self.badPixelFileBrowseButton, row, 2) + row += 1 + dataLayout.addWidget(self.flatFieldRadio, row, 0) + dataLayout.addWidget(self.flatFieldFileTxt, row, 1) + dataLayout.addWidget(self.flatFieldFileBrowseButton, row, 2) + + row += 1 + label = qtWidgets.QLabel("Number of Pixels To Average:"); + self.pixAvgTxt = qtWidgets.QLineEdit("1,1") + rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) + self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) + dataLayout.addWidget(label, row, 0) + dataLayout.addWidget(self.pixAvgTxt, row, 1) + + row = dataLayout.rowCount() + 1 + self._createDetectorROIInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createScanNumberInput(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createOutputType(dataLayout, row) + + row = dataLayout.rowCount() + 1 + self._createHKLOutput(dataLayout, row) + + # Add Signals between widgets + self.fieldCorrectionGroup.buttonClicked\ + .connect(self._fieldCorrectionTypeChanged) + + self.badPixelFileTxt.editingFinished.connect(self._badPixelFileChanged) + self.badPixelFileBrowseButton.clicked\ + .connect(self._browseBadPixelFileName) + + self.flatFieldFileTxt.editingFinished\ + .connect(self._flatFieldFileChanged) + self.flatFieldFileBrowseButton.clicked.\ + connect(self._browseFlatFieldFileName) + + self.pixAvgTxt.textChanged.connect(self._pixAvgTxtChanged) + + dataBox.setLayout(dataLayout) + logger.debug(METHOD_EXIT_STR) + return dataBox + + @qtCore.pyqtSlot(QAbstractButton) + def _fieldCorrectionTypeChanged(self, *fieldCorrType): + ''' + React when the field type radio buttons change. Disable/Enable other + widgets as appropriate + ''' + logger.debug(METHOD_ENTER_STR) + if fieldCorrType[0].text() == self.NONE_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.BAD_PIXEL_RADIO_NAME: + self.badPixelFileTxt.setDisabled(False) + self.badPixelFileBrowseButton.setDisabled(False) + self.flatFieldFileTxt.setDisabled(True) + self.flatFieldFileBrowseButton.setDisabled(True) + elif fieldCorrType[0].text() == self.FLAT_FIELD_RADIO_NAME: + self.badPixelFileTxt.setDisabled(True) + self.badPixelFileBrowseButton.setDisabled(True) + self.flatFieldFileTxt.setDisabled(False) + self.flatFieldFileBrowseButton.setDisabled(False) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + @qtCore.pyqtSlot() + def _flatFieldFileChanged(self): + ''' + Do some verification when the flat field file changes + ''' + logger.debug(METHOD_ENTER_STR) + if os.path.isfile(self.flatFieldFileTxt.text()) or \ + self.flatFieldFileTxt.text() == "": + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR, \ + "The filename entered for the flat field " + \ + "file is invalid") + logger.debug(METHOD_EXIT_STR) + + def getBadPixelFileName(self): + ''' + Return the badPixel file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.badPixelFileTxt.text()) == EMPTY_STR) or \ + (not self.badPixelRadio.isChecked()): + retVal = None + else: + retVal = str(self.badPixelFileTxt.text()) + logger.debug("Exit " + str(retVal)) + return retVal + + def getDataSource(self): + logger.debug(METHOD_ENTER_STR) + if self.getOutputType() == self.SIMPLE_GRID_MAP_STR: + self.transform = UnityTransform3D() + elif self.getOutputType() == self.POLE_MAP_STR: + self.transform = \ + PoleMapTransform3D(projectionDirection=\ + self.getProjectionDirection()) + else: + self.transform = None + + self.dataSource = \ + Sector33SpecDataSource(str(self.getProjectDir()), \ + str(self.getProjectName()), \ + str(self.getProjectExtension()), \ + str(self.getInstConfigName()), \ + str(self.getDetConfigName()), \ + transform = self.transform, \ + scanList = self.getScanList(), \ + roi = self.getDetectorROI(), \ + pixelsToAverage = \ + self.getPixelsToAverage(), \ + badPixelFile = \ + self.getBadPixelFileName(), \ + flatFieldFile = \ + self.getFlatFieldFileName(), \ + appConfig = self.appConfig + ) + self.dataSource.setProgressUpdater(self.updateProgress) + self.dataSource.setCurrentDetector(self.currentDetector) + self.dataSource.loadSource(mapHKL = self.getMapAsHKL()) + logger.debug(METHOD_EXIT_STR) + return self.dataSource + + def getFlatFieldFileName(self): + ''' + Return the flat field file name. If empty or if the bad pixel radio + button is not checked return None + ''' + logger.debug(METHOD_ENTER_STR) + retVal = None + if (str(self.flatFieldFileTxt.text()) == EMPTY_STR) or \ + (not self.flatFieldRadio.isChecked()): + retVal = None + else: + retVal = str(self.flatFieldFileTxt.text()) + logger.debug(METHOD_EXIT_STR) + return retVal + + def getOutputForms(self): + logger.debug(METHOD_ENTER_STR) + outputForms = [] + outputForms.append(ProcessVTIOutputForm) + outputForms.append(ProcessImageStackForm) + outputForms.append(ProcessPowderScanForm) + logger.debug(METHOD_EXIT_STR) + return outputForms + + def getPixelsToAverage(self): + ''' + :return: the pixels to average as a list + ''' + logger.debug(METHOD_ENTER_STR) + pixelStrings = str(self.pixAvgTxt.text()).split(COMMA_STR) + pixels = [] + for value in pixelStrings: + pixels.append(int(value)) + logger.debug(METHOD_EXIT_STR) + return pixels + + def getProjectionDirection(self): + ''' + Return projection direction for stereographic projections + ''' + logger.debug(METHOD_ENTER_STR) + logger.debug(METHOD_EXIT_STR) + return self.projectionDirection + + + def _pixAvgTxtChanged(self, text): + ''' + Check to make sure the text for pix to average is valid and indicate + by a color change + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + if self.pixAvgValid(text): + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) + else: + self.pixAvgTxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + def pixAvgValid(self, text): + ''' + Check to make sure that the pixAvgText is valid + :param text: new values as a text list + ''' + logger.debug(METHOD_ENTER_STR) + retVal = False + rxPixAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_2) + validator = qtGui.QRegExpValidator(rxPixAvg, None) + pos = 0 + if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: + retVal = True + else: + retVal = False + logger.debug("Exit " + str(retVal)) + return retVal diff --git a/rsMap3D/gui/input/specxmldrivenfileform.py b/rsMap3D/gui/input/specxmldrivenfileform.py index 89109b9..4b20ba7 100644 --- a/rsMap3D/gui/input/specxmldrivenfileform.py +++ b/rsMap3D/gui/input/specxmldrivenfileform.py @@ -21,7 +21,7 @@ class SpecXMLDrivenFileForm(AbstractImagePerFileView, UsesXMLInstConfig, UsesXMLDetectorConfig): - SCAN_LIST_REGEXP = "((\d)+(-(\d)+)?\,( )?)+" + SCAN_LIST_REGEXP = r"((\d)+(-(\d)+)?\,( )?)+" def __init__(self, **kwargs): super(SpecXMLDrivenFileForm, self).__init__(**kwargs) diff --git a/rsMap3D/gui/input/usesxmldetectorconfig.py b/rsMap3D/gui/input/usesxmldetectorconfig.py index 6cc37cf..864c202 100644 --- a/rsMap3D/gui/input/usesxmldetectorconfig.py +++ b/rsMap3D/gui/input/usesxmldetectorconfig.py @@ -1,318 +1,318 @@ -# coding=utf-8 -''' - Copyright (c) 2017, UChicago Argonne, LLC - See LICENSE file. -''' -import os -import logging -from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR -logger = logging.getLogger(__name__) - -import PyQt5.QtGui as qtGui -import PyQt5.QtCore as qtCore -import PyQt5.QtWidgets as qtWidgets - -from rsMap3D.gui.input.abstractfileview import AbstractFileView -from rsMap3D.datasource.DetectorGeometryForXrayutilitiesReader \ - import DetectorGeometryForXrayutilitiesReader -from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ - DetectorConfigException -from rsMap3D.gui.rsm3dcommonstrings import COMMA_STR, EMPTY_STR,\ - QLINEEDIT_COLOR_STYLE, WARNING_STR, BROWSE_STR, \ - DETECTOR_CONFIG_FILE_FILTER,\ - SELECT_DETECTOR_CONFIG_TITLE, BLACK, RED - -class UsesXMLDetectorConfig(AbstractFileView): - ''' - class to provide functionality provided by the XML detector configuration - file. Designed for use along with other view classes. Use multiple inheritance - such as "class myView(specXmlDrivenFileForm, usesXMLDetectorConfig): - then add the gui blocks in _createDataBox to add fike selection stuff. - This provides gui that allows file selection, then detector selection since - multiple detectors can be defined in a file and then ROI selection. - ''' - DET_ROI_REGEXP_1 = "^(\d*,*)+$" - DET_ROI_REGEXP_2 = "^(\d)+,(\d)+,(\d)+,(\d)+$" - - #UPDATE_PROGRESS_SIGNAL = "updateProgress" - # Regular expressions for string validation - PIX_AVG_REGEXP_1 = "^(\d*,*)+$" - PIX_AVG_REGEXP_2 = "^((\d)+,*){2}$" - - def __init__(self, parent=None, **kwargs): - ''' - constructor - ''' - super(UsesXMLDetectorConfig, self).__init__(parent, **kwargs) - logger.debug(METHOD_ENTER_STR) - self.roixmin = 1 - self.roixmax = 680 - self.roiymin = 1 - self.roiymax = 480 - self.currentDetector = "" - self.detConfig = None - self.detFileOk = False - logger.debug(METHOD_EXIT_STR) - -# @qtCore.pyqtSlot() - def _browseForDetFile(self): - ''' - Launch file selection dialog for Detector file. - ''' - logger.debug(METHOD_ENTER_STR) - if self.detConfigTxt.text() == EMPTY_STR: - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_DETECTOR_CONFIG_TITLE, \ - filter=DETECTOR_CONFIG_FILE_FILTER)[0] - else: - fileDirectory = os.path.dirname(str(self.detConfigTxt.text())) - fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ - SELECT_DETECTOR_CONFIG_TITLE, \ - filter=DETECTOR_CONFIG_FILE_FILTER, \ - directory = fileDirectory)[0] - if fileName != EMPTY_STR: - self.detConfigTxt.setText(fileName) - self.detConfigTxt.editingFinished.emit() - logger.debug(METHOD_EXIT_STR) - - def _createDetConfig(self, layout, row): - ''' - Add in gui elements for selecting a detector config file and then - selecting from the list of detectors provided. - ''' - logger.debug(METHOD_ENTER_STR) - label = qtWidgets.QLabel("Detector Config File:"); - self.detConfigTxt = qtWidgets.QLineEdit() - self.detConfigFileButton = qtWidgets.QPushButton(BROWSE_STR) - layout.addWidget(label, row, 0) - layout.addWidget(self.detConfigTxt, row, 1) - layout.addWidget(self.detConfigFileButton, row, 2) - - row += 1 - label = qtWidgets.QLabel("Select Detector") - self.detSelect = qtWidgets.QComboBox() - layout.addWidget(label, row, 0) - layout.addWidget(self.detSelect, row, 1) - - # use new style to emit edit finished signal - self.detConfigFileButton.clicked.connect(self._browseForDetFile) - self.detConfigTxt.editingFinished.connect(self._detConfigChanged) - self.detSelect.currentIndexChanged[str].connect(self._currentDetectorChanged) - logger.debug(METHOD_EXIT_STR) - - - def _createDetectorROIInput(self, layout, row, silent=False): - ''' - Adds gui elements for entering the ROI - ''' - logger.debug(METHOD_ENTER_STR) - label = qtWidgets.QLabel("Detector ROI:"); - self.detROITxt = qtWidgets.QLineEdit() - self.updateROITxt() - rxROI = qtCore.QRegExp(self.DET_ROI_REGEXP_1) - self.detROITxt.setValidator(qtGui.QRegExpValidator(rxROI,self.detROITxt)) - - if (silent==False): - layout.addWidget(label, row, 0) - layout.addWidget(self.detROITxt, row, 1) - - # use new style to emit edit finished signal - self.detROITxt.textChanged.connect(self._detROITxtEntered) - logger.debug(METHOD_EXIT_STR) - - def _createNumberOfPixelsToAverage(self, layout, row, silent=False): - logger.debug(METHOD_ENTER_STR) - label = qtWidgets.QLabel("Number of Pixels To Average:"); - self.pixAvgTxt = qtWidgets.QLineEdit("1,1") - rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) - self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) - if (silent == False): - layout.addWidget(label, row, 0) - layout.addWidget(self.pixAvgTxt, row, 1) - logger.debug(METHOD_EXIT_STR) - -# @qtCore.pyqtSlot(str) - def _currentDetectorChanged(self, currentDetector): - logger.debug(METHOD_ENTER_STR % str(currentDetector)) - self.currentDetector = str(currentDetector) - # if the detector list is empty, let's not update ROI etc. - if currentDetector != "": - self.updateROIandNumAvg() - logger.debug(METHOD_EXIT_STR % self.currentDetector) - -# @qtCore.pyqtSlot() - def _detConfigChanged(self): - ''' - ''' - logger.debug(METHOD_ENTER_STR) - if self.detFileExists() or \ - self.detConfigTxt.text() == "": - if self.detConfigTxt.text() != "": - try: - self.detFileOk = self.isDetFileOk() - #self.updateDetectorList() - #self.updateROIandNumAvg() - except DetectorConfigException as ex: - logger.error( ex) - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR,\ - "Trouble getting ROI or Num average " + \ - "from the detector config file") - self.checkOkToLoad() - else: - message = qtWidgets.QMessageBox() - message.warning(self, \ - WARNING_STR,\ - "The filename entered for the detector " + \ - "configuration is invalid") - logger.debug(METHOD_EXIT_STR) - - - def detFileExists(self): - logger.debug(METHOD_ENTER_STR) - fileExists = os.path.isfile(self.detConfigTxt.text()) - logger.debug(METHOD_EXIT_STR + str(fileExists)) - return fileExists - - def isDetFileOk(self): - logger.debug(METHOD_ENTER_STR) - detFileExists = self.detFileExists() - if detFileExists: - try: - self.updateDetectorList() - #self.updateROIandNumAvg() - except DetectorConfigException: - # not a well formed deteector config - logger.debug("Exiting by Exception") - return False - logger.debug(METHOD_EXIT_STR + str(detFileExists)) - return detFileExists - -# @qtCore.pyqtSlot(str) - def _detROITxtChanged(self, text): - ''' - Check to make sure the text for detector roi is valid and indicate - by a color change - ''' - logger.debug(METHOD_ENTER_STR) - if self.detROIValid(text): - self.detROITxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) - else: - self.detROITxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) - logger.debug(METHOD_ENTER_STR) - - def _detROITxtEntered(self, text): - logger.debug(METHOD_ENTER_STR) - self._detROITxtChanged(text) - self.checkOkToLoad() - logger.debug(METHOD_EXIT_STR) - - def detROIValid(self, text): - ''' - Check to make sure the text for is a vaid detector roi - ''' - logger.debug(METHOD_ENTER_STR) - rxROI = qtCore.QRegExp(self.DET_ROI_REGEXP_2) - validator = qtGui.QRegExpValidator(rxROI, None) - pos = 0 - if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: - roiVals = self.getDetectorROI(rois=str(text)) - if (roiVals[0] <= roiVals[1]) and \ - (roiVals[2] <= roiVals[3]): - logger.debug(METHOD_EXIT_STR + str(True)) - return True - else: - logger.debug(METHOD_EXIT_STR + str(False)) - return False - else: - logger.debug(METHOD_EXIT_STR + str(False)) - return False - - def getDetConfigName(self): - ''' - Return the selected Detector Configuration file - ''' - logger.debug(METHOD_ENTER_STR) - nameText = self.detConfigTxt.text() - logger.debug(METHOD_EXIT_STR % nameText) - return nameText - - def getDetectorROI(self, rois=EMPTY_STR): - ''' - :param rois: a string list with the roi values - :return: The detector ROI as a list - :raises RSMap3DException: if the string is not a 4 element list - ''' - logger.debug(METHOD_ENTER_STR) - if rois == EMPTY_STR: - roiStrings = str(self.detROITxt.text()).split(COMMA_STR) - else: - roiStrings = rois.split(COMMA_STR) - - roi = [] - if len(roiStrings) != 4: - logger.debug("Exiting via exception" + - "Detector ROI needs 4 values. " + \ - str(len(roiStrings)) + \ - " were given.") - raise RSMap3DException("Detector ROI needs 4 values. " + \ - str(len(roiStrings)) + \ - " were given.") - for value in roiStrings: - roi.append(int(value)) - logger.debug(METHOD_EXIT_STR) - return roi - - def updateDetectorList(self): - logger.debug(METHOD_ENTER_STR) - oldNumDet = self.detSelect.count() - for index in reversed(range(oldNumDet)): - self.detSelect.removeItem(index) - detConfigFileName = str(self.detConfigTxt.text()) - logger.debug("detectorConfigFile - " + detConfigFileName) - self.detConfig = \ - DetectorGeometryForXrayutilitiesReader(detConfigFileName) - detectors = self.detConfig.getDetectors() - for detector in detectors: - detID = self.detConfig.getDetectorID(detector) - self.detSelect.addItem(detID) - logger.debug("updateDetectorList - " + str(detID)) - self.detSelect.itemText(0) - - self.detSelect.currentIndexChanged[str].emit(self.detSelect.itemText(0)) - logger.debug(METHOD_EXIT_STR) - - def updateROIandNumAvg(self): - ''' - Set default values into the ROI and number of pixel to average text - boxes - ''' - logger.debug(METHOD_ENTER_STR) -# detConfig = \ -# DetectorGeometryForXrayutilitiesReader(self.detConfigTxt.text()) - logger.debug("self.currentDetector " + str(self.currentDetector)) - detector = self.detConfig.getDetectorById(str(self.currentDetector)) - detSize = self.detConfig.getNpixels(detector) - xmax = detSize[0] - ymax = detSize[1] - self.roixmax = xmax - self.roiymax = ymax - self.updateROITxt() - logger.debug(METHOD_EXIT_STR) - - def updateROITxt(self): - ''' - Update the ROI string with the current value of the ROI - ''' - logger.debug(METHOD_ENTER_STR) - roiStr = str(self.roixmin) +\ - COMMA_STR +\ - str(self.roixmax) +\ - COMMA_STR +\ - str(self.roiymin) +\ - COMMA_STR +\ - str(self.roiymax) - self.detROITxt.setText(roiStr) - logger.debug(METHOD_EXIT_STR) - +# coding=utf-8 +''' + Copyright (c) 2017, UChicago Argonne, LLC + See LICENSE file. +''' +import os +import logging +from rsMap3D.config.rsmap3dlogging import METHOD_ENTER_STR, METHOD_EXIT_STR +logger = logging.getLogger(__name__) + +import PyQt5.QtGui as qtGui +import PyQt5.QtCore as qtCore +import PyQt5.QtWidgets as qtWidgets + +from rsMap3D.gui.input.abstractfileview import AbstractFileView +from rsMap3D.datasource.DetectorGeometryForXrayutilitiesReader \ + import DetectorGeometryForXrayutilitiesReader +from rsMap3D.exception.rsmap3dexception import RSMap3DException,\ + DetectorConfigException +from rsMap3D.gui.rsm3dcommonstrings import COMMA_STR, EMPTY_STR,\ + QLINEEDIT_COLOR_STYLE, WARNING_STR, BROWSE_STR, \ + DETECTOR_CONFIG_FILE_FILTER,\ + SELECT_DETECTOR_CONFIG_TITLE, BLACK, RED + +class UsesXMLDetectorConfig(AbstractFileView): + ''' + class to provide functionality provided by the XML detector configuration + file. Designed for use along with other view classes. Use multiple inheritance + such as "class myView(specXmlDrivenFileForm, usesXMLDetectorConfig): + then add the gui blocks in _createDataBox to add fike selection stuff. + This provides gui that allows file selection, then detector selection since + multiple detectors can be defined in a file and then ROI selection. + ''' + DET_ROI_REGEXP_1 = r"^(\d*,*)+$" + DET_ROI_REGEXP_2 = r"^(\d)+,(\d)+,(\d)+,(\d)+$" + + #UPDATE_PROGRESS_SIGNAL = "updateProgress" + # Regular expressions for string validation + PIX_AVG_REGEXP_1 = r"^(\d*,*)+$" + PIX_AVG_REGEXP_2 = r"^((\d)+,*){2}$" + + def __init__(self, parent=None, **kwargs): + ''' + constructor + ''' + super(UsesXMLDetectorConfig, self).__init__(parent, **kwargs) + logger.debug(METHOD_ENTER_STR) + self.roixmin = 1 + self.roixmax = 680 + self.roiymin = 1 + self.roiymax = 480 + self.currentDetector = "" + self.detConfig = None + self.detFileOk = False + logger.debug(METHOD_EXIT_STR) + +# @qtCore.pyqtSlot() + def _browseForDetFile(self): + ''' + Launch file selection dialog for Detector file. + ''' + logger.debug(METHOD_ENTER_STR) + if self.detConfigTxt.text() == EMPTY_STR: + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_DETECTOR_CONFIG_TITLE, \ + filter=DETECTOR_CONFIG_FILE_FILTER)[0] + else: + fileDirectory = os.path.dirname(str(self.detConfigTxt.text())) + fileName = qtWidgets.QFileDialog.getOpenFileName(None, \ + SELECT_DETECTOR_CONFIG_TITLE, \ + filter=DETECTOR_CONFIG_FILE_FILTER, \ + directory = fileDirectory)[0] + if fileName != EMPTY_STR: + self.detConfigTxt.setText(fileName) + self.detConfigTxt.editingFinished.emit() + logger.debug(METHOD_EXIT_STR) + + def _createDetConfig(self, layout, row): + ''' + Add in gui elements for selecting a detector config file and then + selecting from the list of detectors provided. + ''' + logger.debug(METHOD_ENTER_STR) + label = qtWidgets.QLabel("Detector Config File:"); + self.detConfigTxt = qtWidgets.QLineEdit() + self.detConfigFileButton = qtWidgets.QPushButton(BROWSE_STR) + layout.addWidget(label, row, 0) + layout.addWidget(self.detConfigTxt, row, 1) + layout.addWidget(self.detConfigFileButton, row, 2) + + row += 1 + label = qtWidgets.QLabel("Select Detector") + self.detSelect = qtWidgets.QComboBox() + layout.addWidget(label, row, 0) + layout.addWidget(self.detSelect, row, 1) + + # use new style to emit edit finished signal + self.detConfigFileButton.clicked.connect(self._browseForDetFile) + self.detConfigTxt.editingFinished.connect(self._detConfigChanged) + self.detSelect.currentIndexChanged[str].connect(self._currentDetectorChanged) + logger.debug(METHOD_EXIT_STR) + + + def _createDetectorROIInput(self, layout, row, silent=False): + ''' + Adds gui elements for entering the ROI + ''' + logger.debug(METHOD_ENTER_STR) + label = qtWidgets.QLabel("Detector ROI:"); + self.detROITxt = qtWidgets.QLineEdit() + self.updateROITxt() + rxROI = qtCore.QRegExp(self.DET_ROI_REGEXP_1) + self.detROITxt.setValidator(qtGui.QRegExpValidator(rxROI,self.detROITxt)) + + if (silent==False): + layout.addWidget(label, row, 0) + layout.addWidget(self.detROITxt, row, 1) + + # use new style to emit edit finished signal + self.detROITxt.textChanged.connect(self._detROITxtEntered) + logger.debug(METHOD_EXIT_STR) + + def _createNumberOfPixelsToAverage(self, layout, row, silent=False): + logger.debug(METHOD_ENTER_STR) + label = qtWidgets.QLabel("Number of Pixels To Average:"); + self.pixAvgTxt = qtWidgets.QLineEdit("1,1") + rxAvg = qtCore.QRegExp(self.PIX_AVG_REGEXP_1) + self.pixAvgTxt.setValidator(qtGui.QRegExpValidator(rxAvg,self.pixAvgTxt)) + if (silent == False): + layout.addWidget(label, row, 0) + layout.addWidget(self.pixAvgTxt, row, 1) + logger.debug(METHOD_EXIT_STR) + +# @qtCore.pyqtSlot(str) + def _currentDetectorChanged(self, currentDetector): + logger.debug(METHOD_ENTER_STR % str(currentDetector)) + self.currentDetector = str(currentDetector) + # if the detector list is empty, let's not update ROI etc. + if currentDetector != "": + self.updateROIandNumAvg() + logger.debug(METHOD_EXIT_STR % self.currentDetector) + +# @qtCore.pyqtSlot() + def _detConfigChanged(self): + ''' + ''' + logger.debug(METHOD_ENTER_STR) + if self.detFileExists() or \ + self.detConfigTxt.text() == "": + if self.detConfigTxt.text() != "": + try: + self.detFileOk = self.isDetFileOk() + #self.updateDetectorList() + #self.updateROIandNumAvg() + except DetectorConfigException as ex: + logger.error( ex) + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR,\ + "Trouble getting ROI or Num average " + \ + "from the detector config file") + self.checkOkToLoad() + else: + message = qtWidgets.QMessageBox() + message.warning(self, \ + WARNING_STR,\ + "The filename entered for the detector " + \ + "configuration is invalid") + logger.debug(METHOD_EXIT_STR) + + + def detFileExists(self): + logger.debug(METHOD_ENTER_STR) + fileExists = os.path.isfile(self.detConfigTxt.text()) + logger.debug(METHOD_EXIT_STR + str(fileExists)) + return fileExists + + def isDetFileOk(self): + logger.debug(METHOD_ENTER_STR) + detFileExists = self.detFileExists() + if detFileExists: + try: + self.updateDetectorList() + #self.updateROIandNumAvg() + except DetectorConfigException: + # not a well formed deteector config + logger.debug("Exiting by Exception") + return False + logger.debug(METHOD_EXIT_STR + str(detFileExists)) + return detFileExists + +# @qtCore.pyqtSlot(str) + def _detROITxtChanged(self, text): + ''' + Check to make sure the text for detector roi is valid and indicate + by a color change + ''' + logger.debug(METHOD_ENTER_STR) + if self.detROIValid(text): + self.detROITxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % BLACK) + else: + self.detROITxt.setStyleSheet(QLINEEDIT_COLOR_STYLE % RED) + logger.debug(METHOD_ENTER_STR) + + def _detROITxtEntered(self, text): + logger.debug(METHOD_ENTER_STR) + self._detROITxtChanged(text) + self.checkOkToLoad() + logger.debug(METHOD_EXIT_STR) + + def detROIValid(self, text): + ''' + Check to make sure the text for is a vaid detector roi + ''' + logger.debug(METHOD_ENTER_STR) + rxROI = qtCore.QRegExp(self.DET_ROI_REGEXP_2) + validator = qtGui.QRegExpValidator(rxROI, None) + pos = 0 + if validator.validate(text, pos)[0] == qtGui.QValidator.Acceptable: + roiVals = self.getDetectorROI(rois=str(text)) + if (roiVals[0] <= roiVals[1]) and \ + (roiVals[2] <= roiVals[3]): + logger.debug(METHOD_EXIT_STR + str(True)) + return True + else: + logger.debug(METHOD_EXIT_STR + str(False)) + return False + else: + logger.debug(METHOD_EXIT_STR + str(False)) + return False + + def getDetConfigName(self): + ''' + Return the selected Detector Configuration file + ''' + logger.debug(METHOD_ENTER_STR) + nameText = self.detConfigTxt.text() + logger.debug(METHOD_EXIT_STR % nameText) + return nameText + + def getDetectorROI(self, rois=EMPTY_STR): + ''' + :param rois: a string list with the roi values + :return: The detector ROI as a list + :raises RSMap3DException: if the string is not a 4 element list + ''' + logger.debug(METHOD_ENTER_STR) + if rois == EMPTY_STR: + roiStrings = str(self.detROITxt.text()).split(COMMA_STR) + else: + roiStrings = rois.split(COMMA_STR) + + roi = [] + if len(roiStrings) != 4: + logger.debug("Exiting via exception" + + "Detector ROI needs 4 values. " + \ + str(len(roiStrings)) + \ + " were given.") + raise RSMap3DException("Detector ROI needs 4 values. " + \ + str(len(roiStrings)) + \ + " were given.") + for value in roiStrings: + roi.append(int(value)) + logger.debug(METHOD_EXIT_STR) + return roi + + def updateDetectorList(self): + logger.debug(METHOD_ENTER_STR) + oldNumDet = self.detSelect.count() + for index in reversed(range(oldNumDet)): + self.detSelect.removeItem(index) + detConfigFileName = str(self.detConfigTxt.text()) + logger.debug("detectorConfigFile - " + detConfigFileName) + self.detConfig = \ + DetectorGeometryForXrayutilitiesReader(detConfigFileName) + detectors = self.detConfig.getDetectors() + for detector in detectors: + detID = self.detConfig.getDetectorID(detector) + self.detSelect.addItem(detID) + logger.debug("updateDetectorList - " + str(detID)) + self.detSelect.itemText(0) + + self.detSelect.currentIndexChanged[str].emit(self.detSelect.itemText(0)) + logger.debug(METHOD_EXIT_STR) + + def updateROIandNumAvg(self): + ''' + Set default values into the ROI and number of pixel to average text + boxes + ''' + logger.debug(METHOD_ENTER_STR) +# detConfig = \ +# DetectorGeometryForXrayutilitiesReader(self.detConfigTxt.text()) + logger.debug("self.currentDetector " + str(self.currentDetector)) + detector = self.detConfig.getDetectorById(str(self.currentDetector)) + detSize = self.detConfig.getNpixels(detector) + xmax = detSize[0] + ymax = detSize[1] + self.roixmax = xmax + self.roiymax = ymax + self.updateROITxt() + logger.debug(METHOD_EXIT_STR) + + def updateROITxt(self): + ''' + Update the ROI string with the current value of the ROI + ''' + logger.debug(METHOD_ENTER_STR) + roiStr = str(self.roixmin) +\ + COMMA_STR +\ + str(self.roixmax) +\ + COMMA_STR +\ + str(self.roiymin) +\ + COMMA_STR +\ + str(self.roiymax) + self.detROITxt.setText(roiStr) + logger.debug(METHOD_EXIT_STR) + diff --git a/rsMap3D/gui/input/xpcsspecscanfileform.py b/rsMap3D/gui/input/xpcsspecscanfileform.py index 8370fbf..4cca99b 100644 --- a/rsMap3D/gui/input/xpcsspecscanfileform.py +++ b/rsMap3D/gui/input/xpcsspecscanfileform.py @@ -33,9 +33,9 @@ class XPCSSpecScanFileForm(SpecXMLDrivenFileForm): ''' FORM_TITLE = "XPCS SPEC/XML Setup" - DET_ROI_REGEXP_1 = "^(\d*,*)+$" - DET_ROI_REGEXP_2 = "^(\d)+,(\d)+,(\d)+,(\d)+$" - SCAN_LIST_REGEXP = "((\d)+(-(\d)+)?\,( )?)+" + DET_ROI_REGEXP_1 = r"^(\d*,*)+$" + DET_ROI_REGEXP_2 = r"^(\d)+,(\d)+,(\d)+,(\d)+$" + SCAN_LIST_REGEXP = r"((\d)+(-(\d)+)?\,( )?)+" @staticmethod def createInstance(parent=None, appConfig=None):