pax_global_header00006660000000000000000000000064141251011320014500gustar00rootroot0000000000000052 comment=7b3395e343716aa7eb18ee902d86a68bb227dd1d binoculars-0.0.10/000077500000000000000000000000001412510113200137175ustar00rootroot00000000000000binoculars-0.0.10/.gitignore000066400000000000000000000000131412510113200157010ustar00rootroot00000000000000*.pyc *.swpbinoculars-0.0.10/AUTHORS000066400000000000000000000005721412510113200147730ustar00rootroot00000000000000Written by Willem Onderwaater and Sander Roobol as part of a collaboration between the ID03 beamline at the European Synchrotron Radiation Facility and the Interface Physics group at Leiden University. Contributions, Debian packaging and Maintainance done by Picca Frédéric-Emmanuel of the Coordination et Opération transverses group from the the French SOLEIL Synchrotron. binoculars-0.0.10/README.md000066400000000000000000000037311412510113200152020ustar00rootroot00000000000000BINoculars ========== BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a 2D X-ray detector. The intensity of each pixel of a 2D-detector is projected onto a 3-dimensional grid in reciprocal lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution datasets and results in a significant reduction of the size of the dataset. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques. This work has been [published](http://dx.doi.org/10.1107/S1600576715009607) with open access in the Journal of Applied Crystallography Volume 48, Part 4 (August 2015) ## Installation Grab the [latest sourcecode as zip](https://github.com/id03/binoculars/archive/master.zip) or clone the Git repository. Run `binoculars`, `binoculars-fitaid`, `binoculars-gui` or `binoculars-processgui` directly from the command line. ## Usage The [BINoculars wiki](https://github.com/id03/binoculars/wiki) contains a detailed tutorial to get started. ## Scripting If you want more complex operations than offered by the command line or GUI tools, you can manipulate BINoculars data directly from Python. Some examples with detailed comments can be found in the [repository](https://github.com/id03/binoculars/tree/master/examples/scripts). The API documentation on the `binoculars` and `binoculars.space` modules can be accessed via pydoc, e.g. run `pydoc -w binoculars binoculars.space` to generate HTML files. ## Extending BINoculars If you want to use BINoculars with your beamline, you need to write a `backend` module. The code contains an [example implementation](https://github.com/id03/binoculars/blob/master/BINoculars/backends/example.py) with many hints and comments. binoculars-0.0.10/binoculars/000077500000000000000000000000001412510113200160605ustar00rootroot00000000000000binoculars-0.0.10/binoculars/__init__.py000066400000000000000000000255461412510113200202050ustar00rootroot00000000000000from __future__ import print_function, with_statement, division import os def run(args): """Parameters args: string String as if typed in terminal. The string must consist of the location of the configuration file and the command for specifying the jobs that need to be processed. All additonal configuration file overides can be included Returns A tuple of binoculars spaces Examples: >>> space = binoculars.run('config.txt 10') >>> space[0] Axes (3 dimensions, 2848 points, 33.0 kB) { Axis qx (min=-0.01, max=0.0, res=0.01, count=2) Axis qy (min=-0.04, max=-0.01, res=0.01, count=4) Axis qz (min=0.48, max=4.03, res=0.01, count=356) } """ import binoculars.main binoculars.util.register_python_executable(__file__) main = binoculars.main.Main.from_args(args.split(" ")) if isinstance(main.result, binoculars.space.Multiverse): return main.result.spaces if type(main.result) == bool: filenames = main.dispatcher.config.destination.final_filenames() return tuple(binoculars.space.Space.fromfile(fn) for fn in filenames) def load(filename, key=None): """ Parameters filename: string Only hdf5 files are acceptable key: a tuple with slices in as much dimensions as the space is Returns A binoculars space Examples: >>> space = binoculars.load('test.hdf5') >>> space Axes (3 dimensions, 2848 points, 33.0 kB) { Axis qx (min=-0.01, max=0.0, res=0.01, count=2) Axis qy (min=-0.04, max=-0.01, res=0.01, count=4) Axis qz (min=0.48, max=4.03, res=0.01, count=356) } """ import binoculars.space if os.path.exists(filename): return binoculars.space.Space.fromfile(filename, key=key) else: raise IOError("File '{0}' does not exist".format(filename)) def save(filename, space): """ Save a space to file Parameters filename: string filename to which the data is saved. '.txt', '.hdf5' are supported. space: binoculars space the space containing the data that needs to be saved Examples: >>> space Axes (3 dimensions, 2848 points, 33.0 kB) { Axis qx (min=-0.01, max=0.0, res=0.01, count=2) Axis qy (min=-0.04, max=-0.01, res=0.01, count=4) Axis qz (min=0.48, max=4.03, res=0.01, count=356) } >>> binoculars.save('test.hdf5', space) """ import binoculars.space import binoculars.util if isinstance(space, binoculars.space.Space): ext = os.path.splitext(filename)[-1] if ext == ".txt": binoculars.util.space_to_txt(space, filename) elif ext == ".edf": binoculars.util.space_to_edf(space, filename) else: space.tofile(filename) else: raise TypeError("'{0!r}' is not a binoculars space".format(space)) def plotspace( space, log=True, clipping=0.0, fit=None, norm=None, colorbar=True, labels=True, **plotopts ): """plots a space with the correct axes. The space can be either one or two dimensinal. Parameters space: binoculars space the space containing the data that needs to be plotted log: bool axes or colorscale logarithmic clipping: 0 < float < 1 cuts a lowest and highst value on the color scale fit: numpy.ndarray same shape and the space. If one dimensional the fit will be overlayed. norm: matplotlib.colors object defining the colorscale colorbar: bool show or not show the colorbar labels: bool show or not show the labels plotopts: keyword arguments keywords that will be accepted by matplotlib.pyplot.plot or matplotlib.pyplot.imshow Examples: >>> space Axes (3 dimensions, 2848 points, 33.0 kB) { Axis qx (min=-0.01, max=0.0, res=0.01, count=2) Axis qy (min=-0.04, max=-0.01, res=0.01, count=4) Axis qz (min=0.48, max=4.03, res=0.01, count=356) } >>> binoculars.plotspace('test.hdf5') """ import matplotlib.pyplot as pyplot import binoculars.plot import binoculars.space if isinstance(space, binoculars.space.Space): if space.dimension == 3: from mpl_toolkits.mplot3d import Axes3D # noqa ax = pyplot.gcf().gca(projection="3d") return binoculars.plot.plot( space, pyplot.gcf(), ax, log=log, clipping=clipping, fit=None, norm=norm, colorbar=colorbar, labels=labels, **plotopts ) if fit is not None and space.dimension == 2: ax = pyplot.gcf().add_subplot(121) binoculars.plot.plot( space, pyplot.gcf(), ax, log=log, clipping=clipping, fit=None, norm=norm, colorbar=colorbar, labels=labels, **plotopts ) ax = pyplot.gcf().add_subplot(122) return binoculars.plot.plot( space, pyplot.gcf(), ax, log=log, clipping=clipping, fit=fit, norm=norm, colorbar=colorbar, labels=labels, **plotopts ) else: return binoculars.plot.plot( space, pyplot.gcf(), pyplot.gca(), log=log, clipping=clipping, fit=fit, norm=norm, colorbar=colorbar, labels=labels, **plotopts ) else: raise TypeError("'{0!r}' is not a binoculars space".format(space)) def transform(space, labels, resolutions, exprs): """transformation of the coordinates. Parameters space: binoculars space labels: list a list of length N with the labels resolutions: list a list of length N with the resolution per label exprs: list a list of length N with strings containing the expressions that will be evaluated. all numpy funtions can be called without adding 'numpy.' to the functions. Returns A binoculars space of dimension N with labels and resolutions specified in the input Examples: >>> space = binoculars.load('test.hdf5') >>> space Axes (3 dimensions, 2848 points, 33.0 kB) { Axis qx (min=-0.01, max=0.0, res=0.01, count=2) Axis qy (min=-0.04, max=-0.01, res=0.01, count=4) Axis qz (min=0.48, max=4.03, res=0.01, count=356) } >>> newspace = binoculars.transform(space, ['twotheta'], [0.003], ['2 * arcsin(0.51 * (sqrt(qx**2 + qy**2 + qz**2) / (4 * pi)) / (pi * 180))']) # noqa >>> newspace Axes (1 dimensions, 152 points, 1.0 kB) { Axis twotheta (min=0.066, max=0.519, res=0.003, count=152) } """ import binoculars.util import binoculars.space if isinstance(space, binoculars.space.Space): transformation = binoculars.util.transformation_from_expressions(space, exprs) newspace = space.transform_coordinates(resolutions, labels, transformation) else: raise TypeError("'{0!r}' is not a binoculars space".format(space)) return newspace def fitspace(space, function, guess=None): """ fit the space data. Parameters space: binoculars space function: list a string with the name of the desired function. supported are: lorentzian (automatically selects 1d or 2d), gaussian1d and voigt1d guess: list a list of length N with the resolution per label Returns A binoculars fit object. Examples: >>> fit = binoculars.fitspace(space, 'lorentzian') >>> print(fit.summary) I: 1.081e-07 +/- inf loc: 0.3703 +/- inf gamma: 0.02383 +/- inf slope: 0.004559 +/- inf offset: -0.001888 +/- inf >>> parameters = fit.parameters >>> data = fit.fitdata >>> binoculars.plotspace(space, fit = data) """ import binoculars.fit if isinstance(space, binoculars.space.Space): fitclass = binoculars.fit.get_class_by_name(function) return fitclass(space, guess) else: raise TypeError("'{0!r}' is not a binoculars space".format(space)) def info(filename): """ Explore the file without loading the file, or after loading the file Parameters filename: filename or space Examples: >>> print binoculars.info('test.hdf5') Axes (3 dimensions, 46466628 points, 531.0 MB) { Axis H (min=-0.1184, max=0.0632, res=0.0008, count=228) Axis K (min=-1.1184, max=-0.9136, res=0.0008, count=257) Axis L (min=0.125, max=4.085, res=0.005, count=793) } ConfigFile{ [dispatcher] [projection] [input] } origin = test.hdf5 >>> space = binoculars.load('test.hdf5') >>> print binoculars.info(space) Axes (3 dimensions, 46466628 points, 531.0 MB) { Axis H (min=-0.1184, max=0.0632, res=0.0008, count=228) Axis K (min=-1.1184, max=-0.9136, res=0.0008, count=257) Axis L (min=0.125, max=4.085, res=0.005, count=793) } ConfigFile{ [dispatcher] [projection] [input] } origin = test.hdf5 """ import binoculars.space ret = "" if isinstance(filename, binoculars.space.Space): ret += "{!r}\n{!r}".format(filename, filename.config) elif type(filename) == str: if os.path.exists(filename): try: axes = binoculars.space.Axes.fromfile(filename) except Exception as e: raise IOError( "{0}: unable to load Space: {1!r}".format(filename, e) ) # noqa ret += "{!r}\n".format(axes) try: config = binoculars.util.ConfigFile.fromfile(filename) except Exception as e: raise IOError( "{0}: unable to load util.ConfigFile: {1!r}".format(filename, e) ) # noqa ret += "{!r}".format(config) else: raise IOError("File '{0}' does not exist".format(filename)) return ret binoculars-0.0.10/binoculars/backend.py000066400000000000000000000122501412510113200200210ustar00rootroot00000000000000from . import util, errors, dispatcher class ProjectionBase(util.ConfigurableObject): def parse_config(self, config): super(ProjectionBase, self).parse_config(config) res = config.pop("resolution") # or just give 1 number for # all dimensions Optional, set the limits of the space object # in projected coordinates. Syntax is same as numpy # e.g. 'limits = [:0,-1:,:], [0:,:-1,:], [:0,:-1,:], # [0:,-1:,:]' self.config.limits = util.parse_pairs(config.pop("limits", None)) labels = self.get_axis_labels() if self.config.limits is not None: for lim in self.config.limits: if len(lim) != len(labels): raise errors.ConfigError( "dimension mismatch between projection axes ({0}) and limits specification ({1}) in {2}".format( # noqa labels, self.config.limits, self.__class__.__name__ ) ) # noqa if "," in res: self.config.resolution = util.parse_tuple(res, type=float) if not len(labels) == len(self.config.resolution): raise errors.ConfigError( "dimension mismatch between projection axes ({0}) and resolution specification ({1}) in {2}".format( # noqa labels, self.config.resolution, self.__class__.__name__ ) ) # noqa else: self.config.resolution = tuple([float(res)] * len(labels)) def project(self, *args): raise NotImplementedError def get_axis_labels(self): raise NotImplementedError class Job(object): weight = 1.0 # estimate of job difficulty (arbitrary units) def __init__(self, **kwargs): self.__dict__.update(kwargs) class InputBase(util.ConfigurableObject): """Generate and process Job()s. Note: there is no guarantee that generate_jobs() and process_jobs() will be called on the same instance, not even in the same process or on the same computer!""" def parse_config(self, config): super(InputBase, self).parse_config(config) # approximate number of images per job, only useful when # running on the oar cluster self.config.target_weight = int(config.pop("target_weight", 1000)) def generate_jobs(self, command): """Receives command from user, yields Job() instances""" raise NotImplementedError def process_job(self, job): """Receives a Job() instance, yields (intensity, args_to_be_sent_to_a_Projection_instance) Job()s could have been pickle'd and distributed over a cluster """ self.metadata = util.MetaBase("job", job.__dict__) def get_destination_options(self, command): """Receives the same command as generate_jobs(), but returns dictionary that will be used to .format() the dispatcher:destination configuration value.""" return {} def get_dispatcher(config, main, default=None): return _get_backend( config, "dispatcher", dispatcher.DispatcherBase, default=default, args=[main] ) def get_input(config, default=None): return _get_backend(config, "input", InputBase, default=default) def get_projection(config, default=None): return _get_backend(config, "projection", ProjectionBase, default=default) def _get_backend(config, section, basecls, default=None, args=[], kwargs={}): if isinstance(config, util.ConfigSection): return config.class_(config, *args, **kwargs) type = config.pop("type", default) if type is None: raise errors.ConfigError( "required option 'type' not given in section '{0}'".format(section) # noqa ) type = type.strip() if ":" in type: try: modname, clsname = type.split(":") except ValueError: raise errors.ConfigError( "invalid type '{0}' in section '{1}'".format(type, section) ) try: backend = __import__( "backends.{0}".format(modname), globals(), locals(), [], 1 ) except ImportError as e: raise errors.ConfigError( "unable to import module backends.{0}: {1}".format(modname, e) # noqa ) module = getattr(backend, modname) elif section == "dispatcher": module = dispatcher clsname = type else: raise errors.ConfigError( "invalid type '{0}' in section '{1}'".format(type, section) ) clsname = clsname.lower() names = dict((name.lower(), name) for name in dir(module)) if clsname in names: cls = getattr(module, names[clsname]) if issubclass(cls, basecls): return cls(config, *args, **kwargs) else: raise errors.ConfigError( "type '{0}' not compatible in section '{1}': expected class derived from '{2}', got '{3}'".format( # noqa type, section, basecls.__name__, cls.__name__ ) ) # noqa else: raise errors.ConfigError( "invalid type '{0}' in section '{1}'".format(type, section) ) # noqa binoculars-0.0.10/binoculars/backends/000077500000000000000000000000001412510113200176325ustar00rootroot00000000000000binoculars-0.0.10/binoculars/backends/__init__.py000066400000000000000000000000001412510113200217310ustar00rootroot00000000000000binoculars-0.0.10/binoculars/backends/bm25.py000066400000000000000000000241271412510113200207570ustar00rootroot00000000000000""" BINocular backend for beamline BM25, branch B first endstation [1] This backend should serve as a basic implementation of a backend based on xrayutilities [2]. It uses the information from the edf files (motors position and detector image) ignoring the spec file, except for using its scan numbers to identify images belonging to the same scan. You should use CCD file names generated with the following pattern: filename_#n_#p_#r.edf (n: spec-scan number, p: point number, r: image number) Binning (2,2) The backend is called 'EH2SCD'. Created on 2014-10-28 [1] http://www.esrf.eu/UsersAndScience/Experiments/CRG/BM25/BeamLine/experimentalstations/Single_Crystal_Diffraction [2] http://xrayutilities.sourceforge.net/ author: Dominik Kriegner (dominik.kriegner@gmail.com) """ import sys import os import glob import numpy import xrayutilities as xu from .. import backend, errors, util class HKLProjection(backend.ProjectionBase): # scalars: mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, wavelength # 3x3 matrix: UB def project( self, mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, wavelength, UB, qconv, ): qconv.wavelength = wavelength h, k, l = qconv.area( mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, UB=UB.reshape((3, 3)) ) return (h, k, l) def get_axis_labels(self): return "H", "K", "L" class HKProjection(HKLProjection): def project( self, mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, wavelength, UB, qconv, ): H, K, L = super(HKProjection, self).project( mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, wavelength, UB, qconv, ) return (H, K) def get_axis_labels(self): return "H", "K" class QProjection(backend.ProjectionBase): def project( self, mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, wavelength, UB, qconv, ): qconv.wavelength = wavelength qx, qy, qz = qconv.area( mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, UB=numpy.identity(3) ) return (qx, qy, qz) def get_axis_labels(self): return "qx", "qy", "qz" class QinpProjection(backend.ProjectionBase): def project( self, mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, wavelength, UB, qconv, ): qconv.wavelength = wavelength qx, qy, qz = qconv.area( mu, theta, phi, chi, ccdty, ccdtx, ccdtz, ccdth, ccdtr, UB=numpy.identity(3) ) return (numpy.sqrt(qx ** 2 + qy ** 2), qz) def get_axis_labels(self): return "qinp", "qz" class EDFInput(backend.InputBase): # OFFICIAL API def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) imgs = self.list_images(scans) imgcount = len(imgs) if not len(imgs): sys.stderr.write("error: no images selected, nothing to do\n") # next(self.get_images(imgs, 0, imgcount-1, dry_run=True))# dryrun for s in util.chunk_slicer(imgcount, self.config.target_weight): yield backend.Job( images=imgs, firstimage=s.start, lastimage=s.stop - 1, weight=s.stop - s.start, ) def process_job(self, job): super(EDFInput, self).process_job(job) images = self.get_images(job.images, job.firstimage, job.lastimage) # iterator! for image in images: yield self.process_image(image) def parse_config(self, config): super(EDFInput, self).parse_config(config) self.config.xmask = util.parse_multi_range(config.pop("xmask")) self.config.ymask = util.parse_multi_range(config.pop("ymask")) self.config.imagefile = config.pop("imagefile") self.config.UB = config.pop("ub", None) if self.config.UB: self.config.UB = util.parse_tuple(self.config.UB, length=9, type=float) self.config.sddx = float(config.pop("sddx_offset")) self.config.sddy = float(config.pop("sddy_offset")) self.config.sddz = float(config.pop("sddz_offset")) self.config.ccdth0 = float(config.pop("ccdth_offset")) self.config.pixelsize = util.parse_tuple( config.pop("pixelsize"), length=2, type=float ) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=float ) def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict(first=min(scans), last=max(scans), range=",".join(command)) # CONVENIENCE FUNCTIONS @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] # MAIN LOGIC def list_images(self, scannrs): pattern = self.config.imagefile imgfiles = [] # check if necessary image-files exist for nr in scannrs: try: fpattern = pattern.format(scannr=nr) except Exception as e: raise errors.ConfigError( "invalid 'imagefile' specification '{0}': {1}".format( self.config.imagefile, e ) ) files = glob.glob(fpattern) if len(files) == 0: raise errors.FileError( "needed file do not exist: scannr {0}".format(nr) ) else: imgfiles += files return imgfiles def get_images(self, imgs, first, last, dry_run=False): for i in range(first, last + 1): img = imgs[i] if dry_run: yield else: edf = xu.io.EDFFile(img) yield edf class EH2SCD(EDFInput): monitor_counter = "C_mont" # define BM25 goniometer, SIXC geometry? with 2D detector mounted on # translation-axes # see http://www.esrf.eu/UsersAndScience/Experiments/CRG/BM25/BeamLine/experimentalstations/Single_Crystal_Diffraction # The geometry is: 4S + translations and one det. rotation # sample axis: mu, th, chi, phi # detector axis: translations + theta rotation (to make beam perpendicular # to the detector plane in symmetric arrangement) qconv = xu.experiment.QConversion( ["x+", "z+", "y+", "x+"], ["ty", "tx", "tz", "x+", "ty"], [0, 1, 0] ) # convention for coordinate system: y downstream; x in bound; z upwards # (righthanded) # 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. def parse_config(self, config): super(EH2SCD, self).parse_config(config) centralpixel = self.config.centralpixel # define detector parameters roi = ( self.config.ymask[0], self.config.ymask[-1] + 1, self.config.xmask[0], self.config.xmask[-1] + 1, ) self.qconv.init_area( "z-", "x+", cch1=centralpixel[1], cch2=centralpixel[0], Nch1=1912, Nch2=3825, pwidth1=self.config.pixelsize[1], pwidth2=self.config.pixelsize[0], distance=1e-10, roi=roi, ) print( ( "{:>20} {:>9} {:>10} {:>9} {:>9} {:>9}".format( " ", "Mu", "Theta", "CCD_Y", "CCD_X", "CCD_Z" ) ) ) def process_image(self, image): # motor positions mu = float(image.header["M_mu"]) th = float(image.header["M_th"]) chi = float(image.header["M_chi"]) phi = float(image.header["M_phi"]) # distance 'ctr' corresponds to distance of the detector chip from # the CCD_TH rotation axis. The rest is handled by the translations ctr = -270.0 # measured by ruler only!!! cty = float(image.header["M_CCD_Y"]) - self.config.sddy - ctr ctx = float(image.header["M_CCD_X"]) - self.config.sddx ctz = float(image.header["M_CCD_Z"]) - self.config.sddz cth = float(image.header["M_CCD_TH"]) - self.config.ccdth0 # filter correction transm = 1.0 # no filter correction! (Filters are manual on BM25!) mon = float(image.header[self.monitor_counter]) wavelength = float(image.header["WAVELENGTH"]) if self.config.UB: UB = self.config.UB else: UB = self._get_UB(image.header) # normalization data = image.data / mon / transm print( ( "{:>20} {:9.4f} {:10.4f} {:9.1f} {:9.1f} {:9.1f}".format( os.path.split(image.filename)[-1], mu, th, cty, ctx, ctz ) ) ) # masking intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) return ( intensity, numpy.ones_like(intensity), ( mu, th, phi, chi, cty, ctx, ctz, cth, ctr, ## weights added to API. Treated here like before wavelength, UB, self.qconv, ), ) @staticmethod def _get_UB(header): ub = numpy.zeros(9) for i in range(9): ub[i] = float(header["UB{:d}".format(i)]) return ub binoculars-0.0.10/binoculars/backends/bm32.py000066400000000000000000000453421412510113200207570ustar00rootroot00000000000000import sys import os import glob import numpy import time from PyMca5.PyMca import specfilewrapper, EdfFile, SixCircle, specfile from .. import backend, errors, util class pixels(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): y, x = numpy.mgrid[slice(None, gamma.shape[0]), slice(None, delta.shape[0])] return (y, x) def get_axis_labels(self): return "y", "x" class HKLProjection(backend.ProjectionBase): # arrays: gamma, delta # scalars: theta, mu, chi, phi def project(self, wavelength, UB, beta, delta, omega, alfa, chi, phi): R = SixCircle.getHKL( wavelength, UB, gamma=beta, delta=delta, theta=omega, mu=alfa, chi=chi, phi=phi, ) shape = beta.size, delta.size H = R[0, :].reshape(shape) K = R[1, :].reshape(shape) L = R[2, :].reshape(shape) return (H, K, L) def get_axis_labels(self): return "H", "K", "L" class HKProjection(HKLProjection): def project(self, wavelength, UB, beta, delta, omega, alfa, chi, phi): H, K, L = super(HKProjection, self).project( wavelength, UB, beta, delta, omega, alfa, chi, phi ) return (H, K) def get_axis_labels(self): return "H", "K" class ThetaLProjection(backend.ProjectionBase): # arrays: gamma, delta # scalars: theta, mu, chi, phi def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): R = SixCircle.getHKL( wavelength, UB, gamma=gamma, delta=delta, theta=theta, mu=mu, chi=chi, phi=phi, ) shape = gamma.size, delta.size L = R[2, :].reshape(shape) theta_array = numpy.ones_like(L) * theta return (theta_array, L) def get_axis_labels(self): return "Theta", "L" class QProjection(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): shape = gamma.size, delta.size sixc = SixCircle.SixCircle() sixc.setLambda(wavelength) sixc.setUB(UB) R = sixc.getQSurface( gamma=gamma, delta=delta, theta=theta, mu=mu, chi=chi, phi=phi ) qz = R[0, :].reshape(shape) qy = R[1, :].reshape(shape) qx = R[2, :].reshape(shape) return (qz, qy, qx) def get_axis_labels(self): return "qx", "qy", "qz" class SphericalQProjection(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qz, qy, qx = super(SphericalQProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) q = numpy.sqrt(qx ** 2 + qy ** 2 + qz ** 2) theta = numpy.arccos(qz / q) phi = numpy.arctan2(qy, qx) return (q, theta, phi) def get_axis_labels(self): return "Q", "Theta", "Phi" class CylindricalQProjection(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qz, qy, qx = super(CylindricalQProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) qpar = numpy.sqrt(qx ** 2 + qy ** 2) phi = numpy.arctan2(qy, qx) return (qpar, qz, phi) def get_axis_labels(self): return "qpar", "qz", "Phi" class nrQProjection(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): k0 = 2 * numpy.pi / wavelength delta, gamma = numpy.meshgrid(delta, gamma) mu *= numpy.pi / 180 delta *= numpy.pi / 180 gamma *= numpy.pi / 180 qy = k0 * ( numpy.cos(gamma) * numpy.cos(delta) - numpy.cos(mu) ) ## definition of qx, and qy same as spec at theta = 0 qx = k0 * (numpy.cos(gamma) * numpy.sin(delta)) qz = k0 * (numpy.sin(gamma) + numpy.sin(mu)) return (qx, qy, qz) def get_axis_labels(self): return "qx", "qy", "qz" class TwoThetaProjection(SphericalQProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): q, theta, phi = super(TwoThetaProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) return ( 2 * numpy.arcsin(q * wavelength / (4 * numpy.pi)) / numpy.pi * 180, ) # note: we need to return a 1-tuple? def get_axis_labels(self): return "TwoTheta" class Qpp(nrQProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qx, qy, qz = super(Qpp, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) qpar = numpy.sqrt(qx ** 2 + qy ** 2) qpar[numpy.sign(qx) == -1] *= -1 return (qpar, qz) def get_axis_labels(self): return "Qpar", "Qz" class GammaDeltaTheta( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) theta = theta * numpy.ones_like(delta) return (gamma, delta, theta) def get_axis_labels(self): return "Gamma", "Delta", "Theta" class GammaDelta( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) return (gamma, delta) def get_axis_labels(self): return "Gamma", "Delta" class GammaDeltaMu( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) mu = mu * numpy.ones_like(delta) return (gamma, delta, mu) def get_axis_labels(self): return "Gamma", "Delta", "Mu" class BM32Input(backend.InputBase): # OFFICIAL API dbg_scanno = None dbg_pointno = None def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) if not len(scans): sys.stderr.write("error: no scans selected, nothing to do\n") for scanno in scans: util.status("processing scan {0}...".format(scanno)) scan = self.get_scan(scanno) if self.config.pr: pointcount = self.config.pr[1] - self.config.pr[0] + 1 start = self.config.pr[0] else: start = 0 try: pointcount = scan.lines() except specfile.error: # no points continue next(self.get_images(scan, 0, pointcount - 1, dry_run=True)) # dryrun if pointcount > self.config.target_weight * 1.4: for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=start + s.start, lastpoint=start + s.stop - 1, weight=s.stop - s.start, ) else: yield backend.Job( scan=scanno, firstpoint=start, lastpoint=start + pointcount - 1, weight=pointcount, ) def process_job(self, job): super(BM32Input, self).process_job(job) scan = self.get_scan(job.scan) self.metadict = dict() try: scanparams = self.get_scan_params(scan) # wavelength, UB pointparams = self.get_point_params( scan, job.firstpoint, job.lastpoint ) # 2D array of diffractometer angles + mon + transm images = self.get_images(scan, job.firstpoint, job.lastpoint) # iterator! for pp, image in zip(pointparams, images): yield self.process_image(scanparams, pp, image) util.statuseol() except Exception as exc: # exc.args = errors.addmessage(exc.args, ', An error occured for scan {0} at point {1}. See above for more information'.format(self.dbg_scanno, self.dbg_pointno)) raise self.metadata.add_section("id03_backend", self.metadict) def parse_config(self, config): super(BM32Input, self).parse_config(config) self.config.xmask = util.parse_multi_range( config.pop("xmask", None) ) # Optional, select a subset of the image range in the x direction. all by default self.config.ymask = util.parse_multi_range( config.pop("ymask", None) ) # Optional, select a subset of the image range in the y direction. all by default self.config.specfile = config.pop("specfile") # Location of the specfile self.config.imagefolder = config.pop( "imagefolder", None ) # Optional, takes specfile folder tag by default self.config.pr = util.parse_tuple( config.pop("pr", None), length=2, type=int ) # Optional, all range by default self.config.background = config.pop( "background", None ) # Optional, if supplied a space of this image is constructed if self.config.xmask is None: self.config.xmask = slice(None) if self.config.ymask is None: self.config.ymask = slice(None) self.config.maskmatrix = load_matrix( config.pop("maskmatrix", None) ) # Optional, if supplied pixels where the mask is 0 will be removed self.config.sdd = config.pop("sdd", None) # sample to detector distance (mm) if self.config.sdd is not None: self.config.sdd = float(self.config.sdd) self.config.pixelsize = util.parse_tuple( config.pop("pixelsize", None), length=2, type=float ) # pixel size x/y (mm) (same dimension as sdd) def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict( first=min(scans), last=max(scans), range=",".join(str(scan) for scan in scans), ) # CONVENIENCE FUNCTIONS _spec = None def get_scan(self, scannumber): if self._spec is None: self._spec = specfilewrapper.Specfile(self.config.specfile) return self._spec.select("{0}.1".format(scannumber)) def find_edfs(self, pattern): files = glob.glob(pattern) ret = {} for file in files: try: filename = os.path.basename(file).split(".")[0] imno = int(filename.split("_")[-1].split("-")[-1]) ret[imno] = file except ValueError: continue return ret @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] # MAIN LOGIC def get_scan_params(self, scan): self.dbg_scanno = scan.number() UB = numpy.array(scan.header("G")[2].split(" ")[-9:], dtype=numpy.float) wavelength = float(scan.header("G")[1].split(" ")[-1]) self.metadict["UB"] = UB self.metadict["wavelength"] = wavelength return wavelength, UB def get_images(self, scan, first, last, dry_run=False): imagenos = ( numpy.array(scan.datacol("img")[slice(first, last + 1)], dtype=numpy.int) + 1 ) ##error in spec?! if self.config.background: if not os.path.exists(self.config.background): raise errors.FileError( "could not find background file {0}".format(self.config.background) ) if dry_run: yield else: edf = EdfFile.EdfFile(self.config.background) for i in range(first, last + 1): self.dbg_pointno = i yield edf else: try: uccdtagline = scan.header("M")[0].split()[-1] UCCD = os.path.dirname(uccdtagline).split(os.sep) except: print( "warning: UCCD tag not found, use imagefolder for proper file specification" ) UCCD = [] pattern = self._get_pattern(UCCD) matches = self.find_edfs(pattern) if not set(imagenos).issubset(set(matches.keys())): raise errors.FileError( "incorrect number of matches for scan {0} using pattern {1}".format( scan.number(), pattern ) ) if dry_run: yield else: for i in imagenos: self.dbg_pointno = i edf = EdfFile.EdfFile(matches[i]) yield edf def _get_pattern(self, UCCD): imagefolder = self.config.imagefolder if imagefolder: try: imagefolder = imagefolder.format(UCCD=UCCD, rUCCD=list(reversed(UCCD))) except Exception as e: raise errors.ConfigError( "invalid 'imagefolder' specification '{0}': {1}".format( self.config.imagefolder, e ) ) else: if not os.path.exists(imagefolder): raise errors.ConfigError( "invalid 'imagefolder' specification '{0}'. Path {1} does not exist".format( self.config.imagefolder, imagefolder ) ) else: imagefolder = os.path.join(*UCCD) if not os.path.exists(imagefolder): raise errors.ConfigError( "invalid UCCD tag '{0}'. The UCCD tag in the specfile does not point to an existing folder. Specify the imagefolder in the configuration file.".format( imagefolder ) ) return os.path.join(imagefolder, "*") class EH1(BM32Input): def parse_config(self, config): super(EH1, self).parse_config(config) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel", None), length=2, type=int ) self.config.UB = util.parse_tuple(config.pop("ub", None), length=9, type=float) def process_image(self, scanparams, pointparams, edf): delta, omega, alfa, beta, chi, phi, mon, transm = pointparams wavelength, UB = scanparams image = edf.GetData(0) header = edf.GetHeader(0) weights = numpy.ones_like(image) if not self.config.centralpixel: self.config.centralpixel = (int(header["y_beam"]), int(header["x_beam"])) if not self.config.sdd: self.config.sdd = float(header["det_sample_dist"]) if self.config.background: data = image / mon else: data = image / mon / transm if mon == 0: raise errors.BackendError( "Monitor is zero, this results in empty output. Scannumber = {0}, pointnumber = {1}. Did you forget to open the shutter?".format( self.dbg_scanno, self.dbg_pointno ) ) util.status( "{4}| beta: {0:.3f}, delta: {1:.3f}, omega: {2:.3f}, alfa: {3:.3f}".format( beta, delta, omega, alfa, time.ctime(time.time()) ) ) # pixels to angles pixelsize = numpy.array(self.config.pixelsize) sdd = self.config.sdd app = numpy.arctan(pixelsize / sdd) * 180 / numpy.pi centralpixel = self.config.centralpixel # (column, row) = (delta, gamma) beta_range = -app[1] * (numpy.arange(data.shape[1]) - centralpixel[1]) + beta delta_range = app[0] * (numpy.arange(data.shape[0]) - centralpixel[0]) + delta # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix delta_range = delta_range[self.config.ymask] beta_range = beta_range[self.config.xmask] weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) intensity = numpy.rot90(intensity) intensity = numpy.fliplr(intensity) intensity = numpy.flipud(intensity) weights = numpy.rot90(weights) weights = numpy.fliplr(weights) weights = numpy.flipud(weights) # polarisation correction delta_grid, beta_grid = numpy.meshgrid(delta_range, beta_range) Pver = ( 1 - numpy.sin(delta_grid * numpy.pi / 180.0) ** 2 * numpy.cos(beta_grid * numpy.pi / 180.0) ** 2 ) # intensity /= Pver return ( intensity, weights, (wavelength, UB, beta_range, delta_range, omega, alfa, chi, phi), ) def get_point_params(self, scan, first, last): sl = slice(first, last + 1) DEL, OME, ALF, BET, CHI, PHI, MON, TRANSM = list(range(8)) params = numpy.zeros( (last - first + 1, 8) ) # gamma delta theta chi phi mu mon transm params[:, CHI] = 0 # scan.motorpos('CHI') params[:, PHI] = 0 # scan.motorpos('PHI') params[:, OME] = scan.datacol("omecnt")[sl] params[:, BET] = scan.datacol("betcnt")[sl] params[:, DEL] = scan.datacol("delcnt")[sl] params[:, MON] = scan.datacol("Monitor")[sl] # params[:, TRANSM] = scan.datacol('transm')[sl] params[:, TRANSM] = 1 params[:, ALF] = scan.datacol("alfcnt")[sl] return params def load_matrix(filename): if filename == None: return None if os.path.exists(filename): ext = os.path.splitext(filename)[-1] if ext == ".txt": return numpy.array(numpy.loadtxt(filename), dtype=numpy.bool) elif ext == ".npy": return numpy.array(numpy.load(filename), dtype=numpy.bool) elif ext == ".edf": return numpy.array(EdfFile.EdfFile(filename).getData(0), dtype=numpy.bool) else: raise ValueError( "unknown extension {0}, unable to load matrix!\n".format(ext) ) else: raise IOError( "filename: {0} does not exist. Can not load matrix".format(filename) ) binoculars-0.0.10/binoculars/backends/diffabs.py000066400000000000000000000175611412510113200216140ustar00rootroot00000000000000"""This file is part of the binoculars project. The BINoculars library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The BINoculars library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the hkl library. If not, see . Copyright (C) 2015-2019 Synchrotron SOLEIL L'Orme des Merisiers Saint-Aubin BP 48 91192 GIF-sur-YVETTE CEDEX Copyright (C) 2012-2015 European Synchrotron Radiation Facility Grenoble, France Authors: Willem Onderwaater Picca Frédéric-Emmanuel """ from typing import Dict, NamedTuple, Optional, Tuple import numpy import math import os from gi.repository import Hkl from h5py import Dataset, File from numpy import ndarray from pyFAI.detectors import ALL_DETECTORS from .soleil import DatasetPathContains, DatasetPathWithAttribute, HItem, get_dataset # put hkl_matrix_to_numpy into soleil from .sixs import DataFrame, M, PDataFrame, SIXS, WRONG_ATTENUATION, hkl_matrix_to_numpy from ..util import status ################## # Input Backends # ################## class Values(NamedTuple): image: ndarray attenuation: Optional[float] timestamp: float values: Tuple[float] class Diffabs(SIXS): HPATH = { "image": DatasetPathWithAttribute("interpretation", b"image"), "mu": DatasetPathWithAttribute("long_name", b"d13-1-cx1/ex/dif.1-mu/position"), "komega": DatasetPathWithAttribute( "long_name", b"d13-1-cx1/ex/dif.1-komega/position" ), "kappa": DatasetPathWithAttribute( "long_name", b"d13-1-cx1/ex/dif.1-kappa/position" ), "kphi": DatasetPathWithAttribute( "long_name", b"d13-1-cx1/ex/dif.1-kphi/position" ), "gamma": DatasetPathWithAttribute( "long_name", b"d13-1-cx1/ex/dif.1-gamma/position" ), "delta": DatasetPathWithAttribute( "long_name", b"d13-1-cx1/ex/dif.1-delta/position" ), "attenuation": HItem("attenuation", True), "timestamp": DatasetPathContains("sensors_timestamps"), } def get_pointcount(self, scanno: int) -> int: # just open the file in order to extract the number of step with File(self.get_filename(scanno), "r") as scan: path = self.HPATH["image"] return get_dataset(scan, path).shape[0] def get_attenuation( self, index: int, h5_nodes: Dict[str, Dataset], offset: int ) -> Optional[float]: attenuation = None if self.config.attenuation_coefficient is not None: try: try: node = h5_nodes["attenuation"] if node is not None: attenuation = node[index + offset] else: raise Exception( "you asked for attenuation but the file does not contain attenuation informations." ) # noqa except KeyError: attenuation = 1.0 except ValueError: attenuation = WRONG_ATTENUATION return attenuation def get_timestamp(self, index: int, h5_nodes: Dict[str, Dataset]) -> float: timestamp = None try: timestamp = h5_nodes["timestamp"][index] except KeyError: timestamp = index return timestamp def get_values(self, index: int, h5_nodes: Dict[str, Dataset]) -> Values: image = h5_nodes["image"][index] mu = h5_nodes["mu"][index] komega = h5_nodes["komega"][index] kappa = h5_nodes["kappa"][index] kphi = h5_nodes["kphi"][index] gamma = h5_nodes["gamma"][index] delta = h5_nodes["delta"][index] + 16.004 attenuation = self.get_attenuation(index, h5_nodes, index) timestamp = self.get_timestamp(index, h5_nodes) return Values( image, attenuation, timestamp, (mu, komega, kappa, kphi, gamma, delta) ) def process_image( self, index: int, dataframe: DataFrame, pixels: ndarray, mask: ndarray ) -> Tuple[ndarray, ndarray, Tuple[int, PDataFrame]]: status(str(index)) # extract the data from the h5 nodes h5_nodes = dataframe.h5_nodes intensity, attenuation, timestamp, values = self.get_values(index, h5_nodes) # BEWARE in order to avoid precision problem we convert the # uint16 -> float32. (the size of the mantis is on 23 bits) # enought to contain the uint16. If one day we use uint32, it # should be necessary to convert into float64. intensity = intensity.astype("float32") weights = None if self.config.attenuation_coefficient is not None: if attenuation != WRONG_ATTENUATION: intensity *= self.config.attenuation_coefficient ** attenuation weights = numpy.ones_like(intensity) weights *= ~mask else: weights = numpy.zeros_like(intensity) else: weights = numpy.ones_like(intensity) weights *= ~mask k = 2 * math.pi / dataframe.source.wavelength hkl_geometry = dataframe.diffractometer.geometry hkl_geometry.axis_values_set(values, Hkl.UnitEnum.USER) # sample hkl_sample = dataframe.sample.sample q_sample = hkl_geometry.sample_rotation_get(hkl_sample) R = hkl_matrix_to_numpy(q_sample.to_matrix()) # detector hkl_detector = dataframe.detector.detector q_detector = hkl_geometry.detector_rotation_get(hkl_detector) P = hkl_matrix_to_numpy(q_detector.to_matrix()) if self.config.detrot is not None: P = numpy.dot(P, M(math.radians(self.config.detrot), [1, 0, 0])) surface_orientation = self.config.surface_orientation pdataframe = PDataFrame( pixels, k, dataframe.diffractometer.ub, R, P, index, timestamp, surface_orientation, ) return intensity, weights, (index, pdataframe) def get_pixels(self, detector): # works only for flat detector. detector = ALL_DETECTORS[detector.name]() y, x, _ = detector.calc_cartesian_positions() y0 = y[self.config.centralpixel[1], self.config.centralpixel[0]] x0 = x[self.config.centralpixel[1], self.config.centralpixel[0]] z = numpy.ones(x.shape) * -1 * self.config.sdd # return converted to the hkl library coordinates # x -> -y # y -> z # z -> -x return numpy.array([-z, -(x - x0), (y - y0)]) def get_filename(self, scanno): filename = None if self.config.nexusdir: dirname = self.config.nexusdir files = [ f for f in os.listdir(dirname) if ( (str(scanno).zfill(5) in f) and (os.path.splitext(f)[1] in [".hdf5", ".nxs"]) ) ] if files is not []: filename = os.path.join(dirname, files[0]) else: print(self.config.nexusfile) filename = self.config.nexusfile.format(scanno) # noqa if not os.path.exists(filename): raise Exception( "nexus filename does not exist: {0}".format(filename) ) # noqa return filename binoculars-0.0.10/binoculars/backends/example.py000066400000000000000000000166371412510113200216540ustar00rootroot00000000000000import sys import os import itertools import numpy from .. import backend, errors, util """ This example backend contains the minimal set of functions needed to construct a backend. It consists of a child of a backend.InputBase class and a child of a backend.ProjectionBase class. The backend.Inputbase is collects the data from the measurement. The backend.ProjectionBase class calculates the new coordinates per pixel. You can write as much input classes and as much projections in one backend as you prefer, provided that the output of the inputclass is compatible with projection class. Otherwise you will be served best by writing a new backend, for the incompatibility will create errors that break the script. In the configuration file you specify the inputclass and projection needed for the treatment of the dataset. """ class QProjection(backend.ProjectionBase): def project(self, wavelength, af, delta, omega, ai): """ This class takes as input the tuple of coordinates returned by the process_job method in the backend.InputBase class. Here you specify how to project the coordinates that belong to every datapoint. The number of input arguments should match the second tuple returned by process_job. The shape of each returned array should match the shape of the first argument returned by process_job """ k0 = 2 * numpy.pi / wavelength qy = k0 * (numpy.cos(af) * numpy.cos(delta) - numpy.cos(ai) * numpy.cos(omega)) qx = k0 * (numpy.cos(af) * numpy.sin(delta) - numpy.cos(ai) * numpy.sin(omega)) qz = k0 * (numpy.sin(af) + numpy.sin(ai)) return ( qx.flatten(), qy.flatten(), qz.flatten(), ) # a tuple of numpy.arrays with the same dimension as the number of labels def get_axis_labels(self): """ Specify the names of the axes. The number of labels should be equal to the number of arrays returned in the project method. """ return "qx", "qy", "qz" class Input(backend.InputBase): def generate_jobs(self, command): """ Command is supplied when the program is started in the terminal. This can used to differentiate between separate datasets that will be processed independently. """ scans = util.parse_multi_range( ",".join(command).replace(" ", ",") ) # parse the command for scanno in scans: yield backend.Job(scan=scanno) def process_job(self, job): """ This methods is a generator that returns the intensity, the weights and a tuple of coordinates that will be used for projection. The input is a backend.job object. This objects contains attributes that are supplied as keyword arguments in the generate_jobs method when backend.Job is instantiated. You can wet here the weights according the behaviour of your detector. To select normal averaging give the weights the value of ones. This array should be the same shape as the intensity array. This example backend simulates a random path through angular space starting at the origin. an example image will be generated using a three dimensional 10-slit interference function. The angles are with respect to the sample where af and delta are the angular coordinates of the pixels and ai and omega are the in plane and out of plane angles of the incoming beam. """ super(Input, self).process_job(job) # call super to fix metadeta handling scan = job.scan # reflects a scan with 100 datapoints aaf = numpy.linspace(0, numpy.random.random() * 20, 100) adelta = numpy.linspace(0, numpy.random.random() * 20, 100) aai = numpy.linspace(0, numpy.random.random() * 20, 100) aomega = numpy.linspace(0, numpy.random.random() * 20, 100) for af, delta, ai, omega in zip(aaf, adelta, aai, aomega): print( "af: {0}, delta: {1}, ai: {2}, omega: {3}".format(af, delta, ai, omega) ) # caculating the angles per pixel. The values specified in the configuration file # can be used for calculating these values pixelsize = numpy.array(self.config.pixelsize) sdd = self.config.sdd app = numpy.arctan(pixelsize / sdd) * 180 / numpy.pi # create an image of 100 x 100 pixels and calculate the coordinates corresponding to every pixel centralpixel = self.config.centralpixel # (column, row) = (delta, af) af_range = -app[1] * (numpy.arange(100) - centralpixel[1]) + af delta_range = app[0] * (numpy.arange(100) - centralpixel[0]) + delta # calculating the coordinates for simulating the image. This is only included # in this example for simulating of the images. It has no other use. k0 = 2 * numpy.pi / self.config.wavelength delta, af = numpy.meshgrid(delta_range, af_range) ai *= numpy.pi / 180 delta *= numpy.pi / 180 af *= numpy.pi / 180 omega *= numpy.pi / 180 qy = k0 * ( numpy.cos(af) * numpy.cos(delta) - numpy.cos(ai) * numpy.cos(omega) ) qx = k0 * ( numpy.cos(af) * numpy.sin(delta) - numpy.cos(ai) * numpy.sin(omega) ) qz = k0 * (numpy.sin(af) + numpy.sin(ai)) # simulating the image data = ( numpy.abs( numpy.sin(qx * 10) / numpy.sin(qx) * numpy.sin(qy * 10) / numpy.sin(qy) * numpy.sin(qz * 10) / numpy.sin(qz) ) ** 2 ) weights = numpy.ones_like(data) yield data, weights, (self.config.wavelength, af, delta, omega, ai) def parse_config(self, config): """ To collect and process data you need the values provided in the configuration file. These you can access locally through the provided config object. This is a dict with as keys the labels given in the configfile. To use them outside the parse_config method you attribute them to the self.config object which can be used throughout the input class. A warning will be generated afterwards for config values not popped out of the dict. """ super(Input, self).parse_config(config) self.config.sdd = float(config.pop("sdd")) self.config.pixelsize = util.parse_tuple( config.pop("pixelsize"), length=2, type=float ) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) self.config.wavelength = float(config.pop("wavelength")) def get_destination_options(self, command): """ Creates the arguments that you can use to construct an output filename. This method returns a dict object with keys that will can be used in the configfile. In the configfile the output filename can now be described as 'destination = demo_{first}-{last}.hdf5'. This helps to organise the output automatically. """ if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict(first=min(scans), last=max(scans), range=",".join(command)) binoculars-0.0.10/binoculars/backends/id03.py000066400000000000000000001266111412510113200207520ustar00rootroot00000000000000import sys import os import itertools import glob import numpy import time from PyMca5.PyMca import EdfFile, SixCircle, specfile, specfilewrapper from .. import backend, errors, util class pixels(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): y, x = numpy.mgrid[slice(None, gamma.shape[0]), slice(None, delta.shape[0])] return (y, x) def get_axis_labels(self): return "y", "x" class HKLProjection(backend.ProjectionBase): # arrays: gamma, delta # scalars: theta, mu, chi, phi def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): R = SixCircle.getHKL( wavelength, UB, gamma=gamma, delta=delta, theta=theta, mu=mu, chi=chi, phi=phi, ) shape = gamma.size, delta.size H = R[0, :].reshape(shape) K = R[1, :].reshape(shape) L = R[2, :].reshape(shape) return (H, K, L) def get_axis_labels(self): return "H", "K", "L" class HKProjection(HKLProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): H, K, L = super(HKProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) return (H, K) def get_axis_labels(self): return "H", "K" class specularangles(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) mu *= numpy.pi / 180 delta *= numpy.pi / 180 gamma *= numpy.pi / 180 chi *= numpy.pi / 180 phi *= numpy.pi / 180 theta *= numpy.pi / 180 def mat(u, th): ux, uy, uz = u[0], u[1], u[2] sint = numpy.sin(th) cost = numpy.cos(th) mcost = 1 - numpy.cos(th) return numpy.matrix( [ [ cost + ux ** 2 * mcost, ux * uy * mcost - uz * sint, ux * uz * mcost + uy * sint, ], [ uy * ux * mcost + uz * sint, cost + uy ** 2 * mcost, uy * uz - ux * sint, ], [ uz * ux * mcost - uy * sint, uz * uy * mcost + ux * sint, cost + uz ** 2 * mcost, ], ] ) def rot(vx, vy, vz, u, th): R = mat(u, th) return ( R[0, 0] * vx + R[0, 1] * vy + R[0, 2] * vz, R[1, 0] * vx + R[1, 1] * vy + R[1, 2] * vz, R[2, 0] * vx + R[2, 1] * vy + R[2, 2] * vz, ) # what are the angles of kin and kout in the sample frame? # angles in the hexapod frame koutx, kouty, koutz = ( numpy.sin(-numpy.pi / 2 + gamma) * numpy.cos(delta), numpy.sin(-numpy.pi / 2 + gamma) * numpy.sin(delta), numpy.cos(-numpy.pi / 2 + gamma), ) kinx, kiny, kinz = numpy.sin(numpy.pi / 2 - mu), 0, numpy.cos(numpy.pi / 2 - mu) # now we rotate the frame around hexapod rotation th xaxis = numpy.array(rot(1, 0, 0, numpy.array([0, 0, 1]), theta)) yaxis = numpy.array(rot(0, 1, 0, numpy.array([0, 0, 1]), theta)) # first we rotate the sample around the xaxis koutx, kouty, koutz = rot(koutx, kouty, koutz, xaxis, chi) kinx, kiny, kinz = rot(kinx, kiny, kinz, xaxis, chi) yaxis = numpy.array( rot(yaxis[0], yaxis[1], yaxis[2], xaxis, chi) ) # we also have to rotate the yaxis # then we rotate the sample around the yaxis koutx, kouty, koutz = rot(koutx, kouty, koutz, yaxis, phi) kinx, kiny, kinz = rot(kinx, kiny, kinz, yaxis, phi) # to calculate the equivalent gamma, delta and mu in the sample frame we rotate the frame around the sample z which is 0,0,1 back = numpy.arctan2(kiny, kinx) koutx, kouty, koutz = rot(koutx, kouty, koutz, numpy.array([0, 0, 1]), -back) kinx, kiny, kinz = rot(kinx, kiny, kinz, numpy.array([0, 0, 1]), -back) mu = numpy.arctan2(kinz, kinx) * numpy.ones_like(delta) delta = numpy.pi - numpy.arctan2(kouty, koutx) gamma = numpy.pi - numpy.arctan2(koutz, koutx) delta[delta > numpy.pi] -= 2 * numpy.pi gamma[gamma > numpy.pi] -= 2 * numpy.pi mu *= 1 / numpy.pi * 180 delta *= 1 / numpy.pi * 180 gamma *= 1 / numpy.pi * 180 return (gamma - mu, gamma + mu, delta) def get_axis_labels(self): return "g-m", "g+m", "delta" class ThetaLProjection(backend.ProjectionBase): # arrays: gamma, delta # scalars: theta, mu, chi, phi def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): R = SixCircle.getHKL( wavelength, UB, gamma=gamma, delta=delta, theta=theta, mu=mu, chi=chi, phi=phi, ) shape = gamma.size, delta.size L = R[2, :].reshape(shape) theta_array = numpy.ones_like(L) * theta return (theta_array, L) def get_axis_labels(self): return "Theta", "L" class QProjection(backend.ProjectionBase): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): shape = gamma.size, delta.size sixc = SixCircle.SixCircle() sixc.setLambda(wavelength) sixc.setUB(UB) R = sixc.getQSurface( gamma=gamma, delta=delta, theta=theta, mu=mu, chi=chi, phi=phi ) qz = R[0, :].reshape(shape) qy = R[1, :].reshape(shape) qx = R[2, :].reshape(shape) return (qz, qy, qx) def get_axis_labels(self): return "qx", "qy", "qz" class SphericalQProjection(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qz, qy, qx = super(SphericalQProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) q = numpy.sqrt(qx ** 2 + qy ** 2 + qz ** 2) theta = numpy.arccos(qz / q) phi = numpy.arctan2(qy, qx) return (q, theta, phi) def get_axis_labels(self): return "Q", "Theta", "Phi" class CylindricalQProjection(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qz, qy, qx = super(CylindricalQProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) qpar = numpy.sqrt(qx ** 2 + qy ** 2) phi = numpy.arctan2(qy, qx) return (qpar, qz, phi) def get_axis_labels(self): return "qpar", "qz", "Phi" class nrQProjection(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qx, qy, qz = super(nrQProjection, self).project( wavelength, UB, gamma, delta, 0, mu, chi, phi ) return (qx, qy, qz) def get_axis_labels(self): return "qx", "qy", "qz" class TwoThetaProjection(SphericalQProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): q, theta, phi = super(TwoThetaProjection, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) return ( 2 * numpy.arcsin(q * wavelength / (4 * numpy.pi)) / numpy.pi * 180, ) # note: we need to return a 1-tuple? def get_axis_labels(self): return "TwoTheta" class Qpp(nrQProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qx, qy, qz = super(Qpp, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) qpar = numpy.sqrt(qx ** 2 + qy ** 2) qpar[numpy.sign(qx) == -1] *= -1 return (qpar, qz) def get_axis_labels(self): return "Qpar", "Qz" class GammaDeltaTheta( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) theta = theta * numpy.ones_like(delta) return (gamma, delta, theta) def get_axis_labels(self): return "Gamma", "Delta", "Theta" class GammaDelta( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) return (gamma, delta) def get_axis_labels(self): return "Gamma", "Delta" class GammaDeltaMu( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): delta, gamma = numpy.meshgrid(delta, gamma) mu = mu * numpy.ones_like(delta) return (gamma, delta, mu) def get_axis_labels(self): return "Gamma", "Delta", "Mu" class QTransformation(QProjection): def project(self, wavelength, UB, gamma, delta, theta, mu, chi, phi): qx, qy, qz = super(QTransformation, self).project( wavelength, UB, gamma, delta, theta, mu, chi, phi ) M = self.config.matrix q1 = qx * M[0] + qy * M[1] + qz * M[2] q2 = qx * M[3] + qy * M[4] + qz * M[5] q3 = qx * M[6] + qy * M[7] + qz * M[8] return (q1, q2, q3) def get_axis_labels(self): return "q1", "q2", "q3" def parse_config(self, config): super(QTransformation, self).parse_config(config) self.config.matrix = util.parse_tuple( config.pop("matrix"), length=9, type=float ) class ID03Input(backend.InputBase): # OFFICIAL API dbg_scanno = None dbg_pointno = None def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) if not len(scans): sys.stderr.write("error: no scans selected, nothing to do\n") for scanno in scans: util.status("processing scan {0}...".format(scanno)) if self.config.wait_for_data: for job in self.get_delayed_jobs(scanno): yield job else: scan = self.get_scan(scanno) if self.config.pr: pointcount = self.config.pr[1] - self.config.pr[0] + 1 start = self.config.pr[0] else: start = 0 try: pointcount = scan.lines() except specfile.error: # no points continue next(self.get_images(scan, 0, pointcount - 1, dry_run=True)) # dryrun if pointcount > self.config.target_weight * 1.4: for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=start + s.start, lastpoint=start + s.stop - 1, weight=s.stop - s.start, ) else: yield backend.Job( scan=scanno, firstpoint=start, lastpoint=start + pointcount - 1, weight=pointcount, ) def get_delayed_jobs(self, scanno): scan = self.get_delayed_scan(scanno) if self.config.pr: ( firstpoint, lastpoint, ) = ( self.config.pr ) # firstpoint is the first index to be included, lastpoint the last index to be included. else: firstpoint, lastpoint = 0, self.target(scan) - 1 pointcount = lastpoint - firstpoint + 1 if self.is_zap(scan): # wait until the scan is finished. if not self.wait_for_points( scanno, self.target(scan), timeout=self.config.timeout ): # wait for last datapoint for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=firstpoint + s.start, lastpoint=firstpoint + s.stop - 1, weight=s.stop - s.start, ) else: raise errors.BackendError( "Image collection timed out. Zapscan was probably aborted" ) elif lastpoint >= 0: # scanlength is known for s in util.chunk_slicer(pointcount, self.config.target_weight): if self.wait_for_points( scanno, firstpoint + s.stop, timeout=self.config.timeout ): stop = self.get_scan(scanno).lines() yield backend.Job( scan=scanno, firstpoint=firstpoint + s.start, lastpoint=stop - 1, weight=s.stop - s.start, ) break else: yield backend.Job( scan=scanno, firstpoint=firstpoint + s.start, lastpoint=firstpoint + s.stop - 1, weight=s.stop - s.start, ) else: # scanlength is unknown step = int(self.config.target_weight / 1.4) for start, stop in zip( itertools.count(0, step), itertools.count(step, step) ): if self.wait_for_points(scanno, stop, timeout=self.config.timeout): stop = self.get_scan(scanno).lines() yield backend.Job( scan=scanno, firstpoint=start, lastpoint=stop - 1, weight=stop - start, ) break else: yield backend.Job( scan=scanno, firstpoint=start, lastpoint=stop - 1, weight=stop - start, ) def process_job(self, job): super(ID03Input, self).process_job(job) scan = self.get_scan(job.scan) self.metadict = dict() try: scanparams = self.get_scan_params(scan) # wavelength, UB pointparams = self.get_point_params( scan, job.firstpoint, job.lastpoint ) # 2D array of diffractometer angles + mon + transm images = self.get_images(scan, job.firstpoint, job.lastpoint) # iterator! for pp, image in zip(pointparams, images): yield self.process_image(scanparams, pp, image) util.statuseol() except Exception as exc: exc.args = errors.addmessage( exc.args, ", An error occured for scan {0} at point {1}. See above for more information".format( self.dbg_scanno, self.dbg_pointno ), ) raise self.metadata.add_section("id03_backend", self.metadict) def parse_config(self, config): super(ID03Input, self).parse_config(config) self.config.xmask = util.parse_multi_range( config.pop("xmask", None) ) # Optional, select a subset of the image range in the x direction. all by default self.config.ymask = util.parse_multi_range( config.pop("ymask", None) ) # Optional, select a subset of the image range in the y direction. all by default self.config.specfile = config.pop("specfile") # Location of the specfile self.config.imagefolder = config.pop( "imagefolder", None ) # Optional, takes specfile folder tag by default self.config.pr = config.pop("pr", None) # Optional, all range by default self.config.background = config.pop( "background", None ) # Optional, if supplied a space of this image is constructed self.config.th_offset = float( config.pop("th_offset", 0) ) # Optional; Only used in zapscans, zero by default. self.config.wavelength = config.pop( "wavelength", None ) # Optional; Overrides wavelength from specfile. if self.config.wavelength is not None: self.config.wavelength = float(self.config.wavelength) if self.config.xmask is None: self.config.xmask = slice(None) if self.config.ymask is None: self.config.ymask = slice(None) self.config.maskmatrix = load_matrix( config.pop("maskmatrix", None) ) # Optional, if supplied pixels where the mask is 0 will be removed if self.config.pr: self.config.pr = util.parse_tuple(self.config.pr, length=2, type=int) self.config.sdd = float(config.pop("sdd")) # sample to detector distance (mm) self.config.pixelsize = util.parse_tuple( config.pop("pixelsize"), length=2, type=float ) # pixel size x/y (mm) (same dimension as sdd) self.config.wait_for_data = util.parse_bool( config.pop("wait_for_data", "false") ) # Optional, if true wait until the data appears self.config.timeout = int( config.pop("timeout", 180) ) # Optional, how long the script wait until it assumes the scan is not continuing def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict( first=min(scans), last=max(scans), range=",".join(str(scan) for scan in scans), ) # CONVENIENCE FUNCTIONS def get_scan(self, scannumber): spec = specfilewrapper.Specfile(self.config.specfile) return spec.select("{0}.1".format(scannumber)) def get_delayed_scan(self, scannumber, timeout=None): delay = util.loop_delayer(5) start = time.time() while 1: try: return self.get_scan(scannumber) # reload entire specfile except specfile.error: if timeout is not None and time.time() - start > timeout: raise errors.BackendError( "Scan timed out. There is no data to process" ) else: util.status("waiting for scan {0}...".format(scannumber)) next(delay) def wait_for_points(self, scannumber, stop, timeout=None): delay = util.loop_delayer(1) start = time.time() while 1: scan = self.get_scan(scannumber) try: if scan.lines() >= stop: next(delay) # time delay between specfile and edf file return False except specfile.error: pass finally: next(delay) util.status("waiting for scan {0}, point {1}...".format(scannumber, stop)) if ( timeout is not None and time.time() - start > timeout ) or self.is_aborted(scan): try: util.statusnl( "scan {0} aborted at point {1}".format(scannumber, scan.lines()) ) return True except specfile.error: raise errors.BackendError( "Scan was aborted before images were collected. There is no data to process" ) def target(self, scan): if any( tuple( scan.command().startswith(pattern) for pattern in ["hklscan", "a2scan", "ascan", "ringscan"] ) ): return int(scan.command().split()[-2]) + 1 elif scan.command().startswith("mesh"): return int(scan.command().split()[-6]) * int(scan.command().split()[-2]) + 1 elif scan.command().startswith("loopscan"): return int(scan.command().split()[-3]) elif scan.command().startswith("xascan"): params = numpy.array(scan.command().split()[-6:]).astype(float) return int(params[2] + 1 + (params[4] - 1) / params[5] * params[2]) elif self.is_zap(scan): return int(scan.command().split()[-2]) else: return -1 @staticmethod def is_zap(scan): return scan.command().startswith("zap") @staticmethod def is_aborted(scan): for line in scan.header("C"): if "Scan aborted" in line: return True return False def find_edfs(self, pattern, scanno): files = glob.glob(pattern) ret = {} for file in files: try: filename = os.path.basename(file).split(".")[0] scan, point, image = filename.split("_")[-3:] scan, point, image = int(scan), int(point), int(image) if scan == scanno and point not in list(ret.keys()): ret[point] = file except ValueError: continue return ret @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] def get_wavelength(self, G): for line in G: if line.startswith("#G4"): return float(line.split(" ")[4]) return None # MAIN LOGIC def get_scan_params(self, scan): self.dbg_scanno = scan.number() if self.is_zap(scan): # zapscans don't contain the UB matrix, this needs to be fixed at ID03 scanno = scan.number() UB = None while 1: # look back in spec file to locate a UB matrix try: ubscan = self.get_scan(scanno) except specfilewrapper.specfile.error: break try: UB = numpy.array( ubscan.header("G")[2].split(" ")[-9:], dtype=numpy.float ) except: scanno -= 1 else: break if UB is None: # fall back to UB matrix from the configfile if not self.config.UB: raise errors.ConfigError( "UB matrix must be specified in configuration file when processing zapscans" ) UB = numpy.array(self.config.UB) else: UB = numpy.array(scan.header("G")[2].split(" ")[-9:], dtype=numpy.float) if self.config.wavelength is None: wavelength = self.get_wavelength(scan.header("G")) if wavelength is None or wavelength == 0: raise errors.BackendError( "No or incorrect wavelength specified in the specfile. Please add wavelength to the configfile in the input section" ) else: wavelength = self.config.wavelength self.metadict["UB"] = UB self.metadict["wavelength"] = wavelength return wavelength, UB def get_images(self, scan, first, last, dry_run=False): if self.config.background: if not os.path.exists(self.config.background): raise errors.FileError( "could not find background file {0}".format(self.config.background) ) if dry_run: yield else: edf = EdfFile.EdfFile(self.config.background) for i in range(first, last + 1): self.dbg_pointno = i yield edf.GetData(0) else: if self.is_zap(scan): scanheaderC = scan.header("C") zapscanno = int( scanheaderC[2].split(" ")[-1] ) # is different from scanno should be changed in spec! try: uccdtagline = scanheaderC[0] UCCD = os.path.split(uccdtagline.split()[-1]) except: print( "warning: UCCD tag not found, use imagefolder for proper file specification" ) UCCD = [] pattern = self._get_pattern(UCCD) matches = self.find_edfs(pattern, zapscanno) if 0 not in matches: raise errors.FileError( "could not find matching edf for zapscannumber {0} using pattern {1}".format( zapscanno, pattern ) ) if dry_run: yield else: edf = EdfFile.EdfFile(matches[0]) for i in range(first, last + 1): self.dbg_pointno = i yield edf.GetData(i) else: try: uccdtagline = scan.header("UCCD")[0] UCCD = os.path.split(os.path.dirname(uccdtagline.split()[-1])) except: print( "warning: UCCD tag not found, use imagefolder for proper file specification" ) UCCD = [] pattern = self._get_pattern(UCCD) matches = self.find_edfs(pattern, scan.number()) if set(range(first, last + 1)) > set(matches.keys()): raise errors.FileError( "incorrect number of matches for scan {0} using pattern {1}".format( scan.number(), pattern ) ) if dry_run: yield else: for i in range(first, last + 1): self.dbg_pointno = i edf = EdfFile.EdfFile(matches[i]) yield edf.GetData(0) def _get_pattern(self, UCCD): imagefolder = self.config.imagefolder if imagefolder: try: imagefolder = imagefolder.format(UCCD=UCCD, rUCCD=list(reversed(UCCD))) except Exception as e: raise errors.ConfigError( "invalid 'imagefolder' specification '{0}': {1}".format( self.config.imagefolder, e ) ) else: if not os.path.exists(imagefolder): raise errors.ConfigError( "invalid 'imagefolder' specification '{0}'. Path {1} does not exist".format( self.config.imagefolder, imagefolder ) ) else: imagefolder = os.path.join(*UCCD) if not os.path.exists(imagefolder): raise errors.ConfigError( "invalid UCCD tag '{0}'. The UCCD tag in the specfile does not point to an existing folder. Specify the imagefolder in the configuration file.".format( imagefolder ) ) return os.path.join(imagefolder, "*") class EH1(ID03Input): monitor_counter = "mon" def parse_config(self, config): super(EH1, self).parse_config(config) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) # x,y self.config.hr = config.pop( "hr", None ) # Optional, hexapod rotations in miliradians. At the entered value the sample is assumed flat, if not entered the sample is assumed flat at the spec values. self.config.UB = config.pop( "ub", None ) # Optional, takes specfile matrix by default if self.config.UB: self.config.UB = util.parse_tuple(self.config.UB, length=9, type=float) if self.config.hr: self.config.hr = util.parse_tuple(self.config.hr, length=2, type=float) def process_image(self, scanparams, pointparams, image): gamma, delta, theta, chi, phi, mu, mon, transm, hrx, hry = pointparams wavelength, UB = scanparams weights = numpy.ones_like(image) if self.config.hr: zerohrx, zerohry = self.config.hr chi = (hrx - zerohrx) / numpy.pi * 180.0 / 1000 phi = (hry - zerohry) / numpy.pi * 180.0 / 1000 if self.config.background: data = image / mon else: data = image / mon / transm if mon == 0: raise errors.BackendError( "Monitor is zero, this results in empty output. Scannumber = {0}, pointnumber = {1}. Did you forget to open the shutter?".format( self.dbg_scanno, self.dbg_pointno ) ) util.status( "{4}| gamma: {0}, delta: {1}, theta: {2}, mu: {3}".format( gamma, delta, theta, mu, time.ctime(time.time()) ) ) # pixels to angles pixelsize = numpy.array(self.config.pixelsize) sdd = self.config.sdd app = numpy.arctan(pixelsize / sdd) * 180 / numpy.pi centralpixel = self.config.centralpixel # (column, row) = (delta, gamma) gamma_range = -app[1] * (numpy.arange(data.shape[1]) - centralpixel[1]) + gamma delta_range = app[0] * (numpy.arange(data.shape[0]) - centralpixel[0]) + delta # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix gamma_range = gamma_range[self.config.ymask] delta_range = delta_range[self.config.xmask] intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) # polarisation correction delta_grid, gamma_grid = numpy.meshgrid(delta_range, gamma_range) Pver = ( 1 - numpy.sin(delta_grid * numpy.pi / 180.0) ** 2 * numpy.cos(gamma_grid * numpy.pi / 180.0) ** 2 ) intensity /= Pver return ( intensity, weights, (wavelength, UB, gamma_range, delta_range, theta, mu, chi, phi), ) def get_point_params(self, scan, first, last): sl = slice(first, last + 1) GAM, DEL, TH, CHI, PHI, MU, MON, TRANSM, HRX, HRY = list(range(10)) params = numpy.zeros( (last - first + 1, 10) ) # gamma delta theta chi phi mu mon transm params[:, CHI] = scan.motorpos("Chi") params[:, PHI] = scan.motorpos("Phi") try: params[:, HRX] = scan.motorpos("hrx") params[:, HRY] = scan.motorpos("hry") except: raise errors.BackendError( "The specfile does not accept hrx and hry as a motor label. Have you selected the right hutch? Scannumber = {0}, pointnumber = {1}".format( self.dbg_scanno, self.dbg_pointno ) ) if self.is_zap(scan): if "th" in scan.alllabels(): th = scan.datacol("th")[sl] if len(th) > 1: sign = numpy.sign(th[1] - th[0]) else: sign = 1 # correction for difference between back and forth in th motor params[:, TH] = th + sign * self.config.th_offset else: params[:, TH] = scan.motorpos("Theta") params[:, GAM] = scan.motorpos("Gam") params[:, DEL] = scan.motorpos("Delta") params[:, MU] = scan.motorpos("Mu") params[:, MON] = scan.datacol("zap_mon")[sl] transm = scan.datacol("zap_transm") transm[-1] = transm[-2] # bug in specfile params[:, TRANSM] = transm[sl] else: if "hrx" in scan.alllabels(): params[:, HRX] = scan.datacol("hrx")[sl] if "hry" in scan.alllabels(): params[:, HRY] = scan.datacol("hry")[sl] params[:, TH] = scan.datacol("thcnt")[sl] params[:, GAM] = scan.datacol("gamcnt")[sl] params[:, DEL] = scan.datacol("delcnt")[sl] try: params[:, MON] = scan.datacol(self.monitor_counter)[ sl ] # differs in EH1/EH2 except: raise errors.BackendError( "The specfile does not accept {2} as a monitor label. Have you selected the right hutch? Scannumber = {0}, pointnumber = {1}".format( self.dbg_scanno, self.dbg_pointno, self.monitor_counter ) ) params[:, TRANSM] = scan.datacol("transm")[sl] params[:, MU] = scan.datacol("mucnt")[sl] return params class EH2(ID03Input): monitor_counter = "Monitor" def parse_config(self, config): super(EH2, self).parse_config(config) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) # x,y self.config.UB = config.pop( "ub", None ) # Optional, takes specfile matrix by default if self.config.UB: self.config.UB = util.parse_tuple(self.config.UB, length=9, type=float) def process_image(self, scanparams, pointparams, image): gamma, delta, theta, chi, phi, mu, mon, transm = pointparams wavelength, UB = scanparams weights = numpy.ones_like(image) if self.config.background: data = image / mon else: data = image / mon / transm if mon == 0: raise errors.BackendError( "Monitor is zero, this results in empty output. Scannumber = {0}, pointnumber = {1}. Did you forget to open the shutter?".format( self.dbg_scanno, self.dbg_pointno ) ) util.status( "{4}| gamma: {0}, delta: {1}, theta: {2}, mu: {3}".format( gamma, delta, theta, mu, time.ctime(time.time()) ) ) # area correction sdd = self.config.sdd / numpy.cos(gamma * numpy.pi / 180) data *= (self.config.sdd / sdd) ** 2 # pixels to angles pixelsize = numpy.array(self.config.pixelsize) app = numpy.arctan(pixelsize / sdd) * 180 / numpy.pi centralpixel = self.config.centralpixel # (row, column) = (gamma, delta) gamma_range = ( -1 * app[0] * (numpy.arange(data.shape[0]) - centralpixel[0]) + gamma ) delta_range = app[1] * (numpy.arange(data.shape[1]) - centralpixel[1]) + delta # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix gamma_range = gamma_range[self.config.xmask] delta_range = delta_range[self.config.ymask] intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) intensity = numpy.fliplr(intensity) intensity = numpy.rot90(intensity) weights = numpy.fliplr( weights ) # TODO: should be done more efficiently. Will prob change with new HKL calculations weights = numpy.rot90(weights) # polarisation correction delta_grid, gamma_grid = numpy.meshgrid(delta_range, gamma_range) Phor = ( 1 - ( numpy.sin(mu * numpy.pi / 180.0) * numpy.sin(delta_grid * numpy.pi / 180.0) * numpy.cos(gamma_grid * numpy.pi / 180.0) + numpy.cos(mu * numpy.pi / 180.0) * numpy.sin(gamma_grid * numpy.pi / 180.0) ) ** 2 ) intensity /= Phor return ( intensity, weights, (wavelength, UB, gamma_range, delta_range, theta, mu, chi, phi), ) def get_point_params(self, scan, first, last): sl = slice(first, last + 1) GAM, DEL, TH, CHI, PHI, MU, MON, TRANSM = list(range(8)) params = numpy.zeros( (last - first + 1, 8) ) # gamma delta theta chi phi mu mon transm params[:, CHI] = scan.motorpos("Chi") params[:, PHI] = scan.motorpos("Phi") if self.is_zap(scan): if "th" in scan.alllabels(): th = scan.datacol("th")[sl] if len(th) > 1: sign = numpy.sign(th[1] - th[0]) else: sign = 1 # correction for difference between back and forth in th motor params[:, TH] = th + sign * self.config.th_offset else: params[:, TH] = scan.motorpos("Theta") params[:, GAM] = scan.motorpos("Gamma") params[:, DEL] = scan.motorpos("Delta") params[:, MU] = scan.motorpos("Mu") params[:, MON] = scan.datacol("zap_mon")[sl] transm = scan.datacol("zap_transm") transm[-1] = transm[-2] # bug in specfile params[:, TRANSM] = transm[sl] else: params[:, TH] = scan.datacol("thcnt")[sl] params[:, GAM] = scan.datacol("gamcnt")[sl] params[:, DEL] = scan.datacol("delcnt")[sl] try: params[:, MON] = scan.datacol(self.monitor_counter)[ sl ] # differs in EH1/EH2 except: raise errors.BackendError( "The specfile does not accept {2} as a monitor label. Have you selected the right hutch? Scannumber = {0}, pointnumber = {1}".format( self.dbg_scanno, self.dbg_pointno, self.monitor_counter ) ) params[:, TRANSM] = scan.datacol("transm")[sl] params[:, MU] = scan.datacol("mucnt")[sl] return params class GisaxsDetector(ID03Input): monitor_counter = "mon" def process_image(self, scanparams, pointparams, image): ccdy, ccdz, theta, chi, phi, mu, mon, transm = pointparams weights = numpy.ones_like(image) wavelength, UB = scanparams if self.config.background: data = image / mon else: data = image / mon / transm if mon == 0: raise errors.BackendError( "Monitor is zero, this results in empty output. Scannumber = {0}, pointnumber = {1}. Did you forget to open the shutter?".format( self.dbg_scanno, self.dbg_pointno ) ) util.status( "{4}| ccdy: {0}, ccdz: {1}, theta: {2}, mu: {3}".format( ccdy, ccdz, theta, mu, time.ctime(time.time()) ) ) # pixels to angles pixelsize = numpy.array(self.config.pixelsize) sdd = self.config.sdd directbeam = ( self.config.directbeam[0] - (ccdy - self.config.directbeam_coords[0]) * pixelsize[0], self.config.directbeam[1] - (ccdz - self.config.directbeam_coords[1]) * pixelsize[1], ) gamma_distance = -pixelsize[1] * (numpy.arange(data.shape[1]) - directbeam[1]) delta_distance = -pixelsize[0] * (numpy.arange(data.shape[0]) - directbeam[0]) gamma_range = numpy.arctan2(gamma_distance, sdd) / numpy.pi * 180 - mu delta_range = numpy.arctan2(delta_distance, sdd) / numpy.pi * 180 # sample pixel distance spd = numpy.sqrt(gamma_distance ** 2 + delta_distance ** 2 + sdd ** 2) data *= spd ** 2 / sdd # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix gamma_range = gamma_range[self.config.ymask] delta_range = delta_range[self.config.xmask] intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) return ( intensity, weights, (wavelength, UB, gamma_range, delta_range, theta, mu, chi, phi), ) def parse_config(self, config): super(GisaxsDetector, self).parse_config(config) self.config.directbeam = util.parse_tuple( config.pop("directbeam"), length=2, type=int ) self.config.directbeam_coords = util.parse_tuple( config.pop("directbeam_coords"), length=2, type=float ) # Coordinates of ccdy and ccdz at the direct beam position def get_point_params(self, scan, first, last): sl = slice(first, last + 1) CCDY, CCDZ, TH, CHI, PHI, MU, MON, TRANSM = list(range(8)) params = numpy.zeros( (last - first + 1, 8) ) # gamma delta theta chi phi mu mon transm params[:, CHI] = scan.motorpos("Chi") params[:, PHI] = scan.motorpos("Phi") params[:, CCDY] = scan.motorpos("ccdy") params[:, CCDZ] = scan.motorpos("ccdz") params[:, TH] = scan.datacol("thcnt")[sl] try: params[:, MON] = scan.datacol(self.monitor_counter)[ sl ] # differs in EH1/EH2 except: raise errors.BackendError( "The specfile does not accept {2} as a monitor label. Have you selected the right hutch? Scannumber = {0}, pointnumber = {1}".format( self.dbg_scanno, self.dbg_pointno, self.monitor_counter ) ) params[:, TRANSM] = scan.datacol("transm")[sl] params[:, MU] = scan.datacol("mucnt")[sl] return params def find_edfs(self, pattern, scanno): files = glob.glob(pattern) ret = {} for file in files: try: filename = os.path.basename(file).split(".")[0] scan, point = filename.split("_")[-2:] scan, point = int(scan), int(point) if scan == scanno and point not in list(ret.keys()): ret[point] = file except ValueError: continue return ret def load_matrix(filename): if filename == None: return None if os.path.exists(filename): ext = os.path.splitext(filename)[-1] if ext == ".txt": return numpy.array(numpy.loadtxt(filename), dtype=numpy.bool) elif ext == ".npy": return numpy.array(numpy.load(filename), dtype=numpy.bool) elif ext == ".edf": return numpy.array(EdfFile.EdfFile(filename).getData(0), dtype=numpy.bool) else: raise ValueError( "unknown extension {0}, unable to load matrix!\n".format(ext) ) else: raise IOError( "filename: {0} does not exist. Can not load matrix".format(filename) ) binoculars-0.0.10/binoculars/backends/id03_xu.py000066400000000000000000000252101412510113200214570ustar00rootroot00000000000000""" BINocular backend for beamline ID03:EH2 This backend should serve as a basic example of a backend based on xrayutilities [1]. It still uses PyMCA for parsing the spec,edf files. The 'original' ID03 backend was used as a template. Created on 2014-10-16 [1] http://xrayutilities.sourceforge.net/ author: Dominik Kriegner (dominik.kriegner@gmail.com) """ import sys import os import glob import numpy import xrayutilities as xu from PyMca5.PyMca import EdfFile, specfile, specfilewrapper from .. import backend, errors, util class HKLProjection(backend.ProjectionBase): # scalars: mu, theta, [chi, phi, "omitted"] delta, gamR, gamT, ty, wavelength # 3x3 matrix: UB def project(self, mu, theta, delta, gamR, gamT, ty, wavelength, UB, qconv): qconv.wavelength = wavelength h, k, l = qconv.area( mu, theta, mu, delta, ty, gamT, gamR, UB=UB.reshape((3, 3)) ) return (h, k, l) def get_axis_labels(self): return "H", "K", "L" class HKProjection(HKLProjection): def project(self, mu, theta, delta, gamR, gamT, ty, wavelength, UB, qconv): H, K, L = super(HKProjection, self).project( mu, theta, delta, gamR, gamT, ty, wavelength, UB, qconv ) return (H, K) def get_axis_labels(self): return "H", "K" class QProjection(backend.ProjectionBase): def project(self, mu, theta, delta, gamR, gamT, ty, wavelength, UB, qconv): qconv.wavelength = wavelength qx, qy, qz = qconv.area( mu, theta, mu, delta, ty, gamT, gamR, UB=numpy.identity(3) ) return (qx, qy, qz) def get_axis_labels(self): return "qx", "qy", "qz" class ID03Input(backend.InputBase): # OFFICIAL API def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) if not len(scans): sys.stderr.write("error: no scans selected, nothing to do\n") for scanno in scans: scan = self.get_scan(scanno) try: pointcount = scan.lines() except specfile.error: # no points continue next(self.get_images(scan, 0, pointcount - 1, dry_run=True)) # dryrun if ( self.config.target_weight and pointcount > self.config.target_weight * 1.4 ): for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=s.start, lastpoint=s.stop - 1, weight=s.stop - s.start, ) else: yield backend.Job( scan=scanno, firstpoint=0, lastpoint=pointcount - 1, weight=pointcount, ) def process_job(self, job): super(ID03Input, self).process_job(job) scan = self.get_scan(job.scan) scanparams = self.get_scan_params(scan) # wavelength, UB pointparams = self.get_point_params( scan, job.firstpoint, job.lastpoint ) # 1D array of diffractometer angles + mon + transm images = self.get_images(scan, job.firstpoint, job.lastpoint) # iterator! for pp, image in zip(pointparams, images): yield self.process_image(scanparams, pp, image) def parse_config(self, config): super(ID03Input, self).parse_config(config) self.config.xmask = util.parse_multi_range(config.pop("xmask")) self.config.ymask = util.parse_multi_range(config.pop("ymask")) self.config.specfile = config.pop("specfile") self.config.imagefolder = config.pop("imagefolder", None) self.config.UB = config.pop("ub", None) if self.config.UB: self.config.UB = util.parse_tuple(self.config.UB, length=9, type=float) self.config.sdd = float(config.pop("sdd")) self.config.pixelsize = util.parse_tuple( config.pop("pixelsize"), length=2, type=float ) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict(first=min(scans), last=max(scans), range=",".join(command)) # CONVENIENCE FUNCTIONS _spec = None def get_scan(self, scannumber): if self._spec is None: self._spec = specfilewrapper.Specfile(self.config.specfile) return self._spec.select("{0}.1".format(scannumber)) def find_edfs(self, pattern, scanno): files = glob.glob(pattern) ret = {} for file in files: try: filename = os.path.basename(file).split(".")[0] scan, point, image = filename.split("_")[-3:] scan, point, image = int(scan), int(point), int(image) if scan == scanno and point not in list(ret.keys()): ret[point] = file except ValueError: continue return ret @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] # MAIN LOGIC def get_scan_params(self, scan): UB = numpy.array(scan.header("G")[2].split(" ")[-9:], dtype=numpy.float) wavelength = float(scan.header("G")[1].split(" ")[-1]) return wavelength, UB def get_images(self, scan, first, last, dry_run=False): try: uccdtagline = scan.header("UCCD")[0] UCCD = os.path.split(os.path.dirname(uccdtagline.split()[-1])) except: print( "warning: UCCD tag not found, use imagefolder for proper file specification" ) UCCD = [] pattern = self._get_pattern(UCCD) matches = self.find_edfs(pattern, scan.number()) if set(range(first, last + 1)) > set(matches.keys()): raise errors.FileError( "incorrect number of matches for scan {0} using pattern {1}".format( scan.number(), pattern ) ) if dry_run: yield else: for i in range(first, last + 1): edf = EdfFile.EdfFile(matches[i]) yield edf.GetData(0) def _get_pattern(self, UCCD): imagefolder = self.config.imagefolder if imagefolder: try: imagefolder = imagefolder.format(UCCD=UCCD, rUCCD=list(reversed(UCCD))) except Exception as e: raise errors.ConfigError( "invalid 'imagefolder' specification '{0}': {1}".format( self.config.imagefolder, e ) ) else: imagefolder = os.path.join(*UCCD) if not os.path.exists(imagefolder): raise ValueError( "invalid 'imagefolder' specification '{0}'. Path {1} does not exist".format( self.config.imagefolder, imagefolder ) ) return os.path.join(imagefolder, "*") class EH2(ID03Input): monitor_counter = "Monitor" # define ID03 goniometer, SIXC geometry with 2D detector mounted on a # translation-axis (distance changing with changing Gamma) # The geometry is: 1+3S+2D # sample axis mu, th, chi, phi -> here chi,phi are omitted # detector axis mu, del, gam # gam is realized by a translation along z (gamT) and rotation around x+ (gamR) qconv = xu.experiment.QConversion( ["x+", "z-"], ["x+", "z-", "ty", "tz", "x+"], [0, 1, 0] # 'y+', 'z+' ) # convention for coordinate system: y downstream; z outwards; x upwards # (righthanded) # 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. ty = 600.0 # mm def parse_config(self, config): super(EH2, self).parse_config(config) centralpixel = self.config.centralpixel # (row, column) = (gamma, delta) # define detector parameters roi = ( self.config.ymask[0], self.config.ymask[-1] + 1, self.config.xmask[0], self.config.xmask[-1] + 1, ) self.qconv.init_area( "x+", "z-", cch1=centralpixel[1], cch2=centralpixel[0], Nch1=516, Nch2=516, pwidth1=self.config.pixelsize[1], pwidth2=self.config.pixelsize[0], distance=self.config.sdd - self.ty, roi=roi, ) # distance sdd-600 corresponds to distance of the detector chip from # the gamR rotation axis (rest is handled by the translations ty and # gamT (along z)) print(("{:>9} {:>10} {:>9} {:>9}".format("Mu", "Theta", "Delta", "Gamma"))) def process_image(self, scanparams, pointparams, image): mu, theta, chi, phi, delta, gamma, mon, transm = pointparams wavelength, UB = scanparams data = image / mon / transm print(("{:9.4f} {:10.4f} {:9.4f} {:9.4f}".format(mu, theta, delta, gamma))) # recalculate detector translation (which should be saved!) gamT = self.ty * numpy.tan(numpy.radians(gamma)) # masking intensity = self.apply_mask(data, self.config.xmask, self.config.ymask) # no polarization correction for the moment! return ( intensity, numpy.ones_like(intensity), ( mu, theta, delta, gamma, gamT, # weights added to API. keeps functionality identical with wights of one self.ty, wavelength, UB, self.qconv, ), ) def get_point_params(self, scan, first, last): sl = slice(first, last + 1) MU, TH, CHI, PHI, DEL, GAM, MON, TRANSM = list(range(8)) params = numpy.zeros((last - first + 1, 8)) # Mu, Theta, Chi, Phi, Delta, Gamma, MON, transm params[:, CHI] = scan.motorpos("Chi") params[:, PHI] = scan.motorpos("Phi") params[:, TH] = scan.datacol("thcnt")[sl] params[:, GAM] = scan.datacol("gamcnt")[sl] params[:, DEL] = scan.datacol("delcnt")[sl] params[:, MON] = scan.datacol(self.monitor_counter)[sl] params[:, TRANSM] = scan.datacol("transm")[sl] params[:, MU] = scan.datacol("mucnt")[sl] return params binoculars-0.0.10/binoculars/backends/io7.py000066400000000000000000000401751412510113200207110ustar00rootroot00000000000000""" This file is part of the binoculars project. The BINoculars library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The BINoculars library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the hkl library. If not, see . Copyright (C) 2012-2015 European Synchrotron Radiation Facility Grenoble, France Authors: Willem Onderwaater Jonathan Rawle """ import sys import os import itertools import numpy import time import math import json from functools import reduce from scipy.misc import imread import scisoftpy as dnp from scisoftpy import sin, cos from .. import backend, errors, util class HKLProjection(backend.ProjectionBase): # scalars: mu, theta, [chi, phi, "omitted"] delta, gamR, gamT, ty, wavelength # 3x3 matrix: UB def project(self, energy, UB, pixels, gamma, delta, omega, alpha, nu): # put the detector at the right position dx, dy, dz = pixels # convert angles to radians gamma, delta, alpha, omega, nu = numpy.radians((gamma, delta, alpha, omega, nu)) RGam = numpy.matrix( [[1, 0, 0], [0, cos(gamma), -sin(gamma)], [0, sin(gamma), cos(gamma)]] ) RDel = ( numpy.matrix( [[cos(delta), -sin(delta), 0], [sin(delta), cos(delta), 0], [0, 0, 1]] ) ).getI() RNu = numpy.matrix([[cos(nu), 0, sin(nu)], [0, 1, 0], [-sin(nu), 0, cos(nu)]]) # calculate Cartesian coordinates for each pixel using clever matrix stuff M = numpy.mat( numpy.concatenate((dx.flatten(0), dy.flatten(0), dz.flatten(0))).reshape( 3, dx.shape[0] * dx.shape[1] ) ) XYZp = RGam * RDel * RNu * M xp = dnp.array(XYZp[0]).reshape(dx.shape) yp = dnp.array(XYZp[1]).reshape(dy.shape) zp = dnp.array(XYZp[2]).reshape(dz.shape) # don't bother with the part about slits... # Calculate effective gamma and delta for each pixel d_ds = dnp.sqrt(xp ** 2 + yp ** 2 + zp ** 2) Gam = dnp.arctan2(zp, yp) Del = -1 * dnp.arcsin(-xp / d_ds) # wavenumber k = 2 * math.pi / 12.398 * energy # Define the needed matrices. The notation follows the article by Bunk & # Nielsen. J.Appl.Cryst. (2004) 37, 216-222. M1 = k * numpy.matrix( cos(omega) * sin(Del) - sin(omega) * ( cos(alpha) * (cos(Gam) * cos(Del) - 1) + sin(alpha) * sin(Gam) * cos(Del) ) ) M2 = k * numpy.matrix( sin(omega) * sin(Del) + cos(omega) * ( cos(alpha) * (cos(Gam) * cos(Del) - 1) + sin(alpha) * sin(Gam) * cos(Del) ) ) M3 = k * numpy.matrix( -sin(alpha) * (cos(Gam) * cos(Del) - 1) + cos(alpha) * sin(Gam) * cos(Del) ) # invert UB matrix UBi = numpy.matrix(UB).getI() # calculate HKL H = UBi[0, 0] * M1 + UBi[0, 1] * M2 + UBi[0, 2] * M3 K = UBi[1, 0] * M1 + UBi[1, 1] * M2 + UBi[1, 2] * M3 L = UBi[2, 0] * M1 + UBi[2, 1] * M2 + UBi[2, 2] * M3 return (H, K, L) def get_axis_labels(self): return "H", "K", "L" class GammaDelta( HKLProjection ): # just passing on the coordinates, makes it easy to accurately test the theta correction def project(self, beamenergy, UB, gamma, delta, omega, alpha): delta, gamma = numpy.meshgrid(delta, gamma) return (gamma, delta) def get_axis_labels(self): return "Gamma", "Delta" class pixels(backend.ProjectionBase): def project(self, beamenergy, UB, gamma, delta, omega, alpha): y, x = numpy.mgrid[slice(None, gamma.shape[0]), slice(None, delta.shape[0])] return (y, x) def get_axis_labels(self): return "y", "x" class IO7Input(backend.InputBase): # OFFICIAL API dbg_scanno = None dbg_pointno = None def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) if not len(scans): sys.stderr.write("error: no scans selected, nothing to do\n") for scanno in scans: util.status("processing scan {0}...".format(scanno)) if self.config.pr: pointcount = self.config.pr[1] - self.config.pr[0] + 1 start = self.config.pr[0] else: scan = self.get_scan(scanno) pointcount = len(scan.file) start = 0 if pointcount > self.config.target_weight * 1.4: for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=start + s.start, lastpoint=start + s.stop - 1, weight=s.stop - s.start, ) else: yield backend.Job( scan=scanno, firstpoint=start, lastpoint=start + pointcount - 1, weight=pointcount, ) def process_job(self, job): super(IO7Input, self).process_job(job) scan = self.get_scan(job.scan) self.metadict = dict() try: scanparams = self.get_scan_params(scan) # wavelength, UB pointparams = self.get_point_params( scan, job.firstpoint, job.lastpoint ) # 2D array of diffractometer angles + mon + transm images = self.get_images(scan, job.firstpoint, job.lastpoint) # iterator! for pp, image in zip(pointparams, images): yield self.process_image(scan, scanparams, pp, image) util.statuseol() except Exception as exc: exc.args = errors.addmessage( exc.args, ", An error occured for scan {0} at point {1}. See above for more information".format( self.dbg_scanno, self.dbg_pointno ), ) raise self.metadata.add_section("id7_backend", self.metadict) def get_scan_params(self, scan): energy = scan.metadata.dcm1energy UB = numpy.array(json.loads(scan.metadata.diffcalc_ub)) self.metadict["UB"] = UB self.metadict["energy"] = energy return energy, UB def get_point_params(self, scan, first, last): sl = slice(first, last + 1) GAM, DEL, OMG, CHI, PHI, ALF, MON, TRANSM = list(range(8)) params = numpy.zeros( (last - first + 1, 8) ) # gamma delta theta chi phi mu mon transm params[:, CHI] = 0 params[:, PHI] = 0 params[:, OMG] = scan["omega"][sl] params[:, GAM] = scan["gamma"][sl] params[:, DEL] = scan["delta"][sl] params[:, ALF] = scan["alpha"][sl] return params def get_images(self, scan, first, last, dry_run=False): sl = slice(first, last + 1) for fn in scan.file[sl]: yield imread(self.get_imagefilename(fn)) def get_imagefilename(self, filename): if self.config.imagefolder is None: if os.path.exists(filename): return filename else: raise errors.ConfigError( "image filename specified in the datafile does not exist '{0}'".format( filename ) ) else: head, tail = os.path.split(filename) folders = head.split("/") try: imagefolder = self.config.imagefolder.format( folders=folders, rfolders=list(reversed(folders)) ) except Exception as e: raise errors.ConfigError( "invalid 'imagefolder' specification '{0}': {1}".format( self.config.imagefolder, e ) ) else: if not os.path.exists(imagefolder): raise errors.ConfigError( "invalid 'imagefolder' specification '{0}'. Path {1} does not exist".format( self.config.imagefolder, imagefolder ) ) fn = os.path.join(imagefolder, tail) if os.path.exists(fn): return fn else: raise errors.ConfigError( "image filename does not exist '{0}', either imagefolder is wrongly specified or image file does not exist".format( filename ) ) def parse_config(self, config): super(IO7Input, self).parse_config(config) self.config.xmask = util.parse_multi_range( config.pop("xmask", None) ) # Optional, select a subset of the image range in the x direction. all by default self.config.ymask = util.parse_multi_range( config.pop("ymask", None) ) # Optional, select a subset of the image range in the y direction. all by default self.config.datafilefolder = config.pop( "datafilefolder" ) # Folder with the datafiles self.config.imagefolder = config.pop( "imagefolder", None ) # Optional, takes datafile folder tag by default self.config.pr = config.pop("pr", None) # Optional, all range by default if self.config.xmask is None: self.config.xmask = slice(None) if self.config.ymask is None: self.config.ymask = slice(None) if self.config.pr: self.config.pr = util.parse_tuple(self.config.pr, length=2, type=int) self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) # x,y self.config.maskmatrix = config.pop( "maskmatrix", None ) # Optional, if supplied pixels where the mask is 0 will be removed self.config.pixelsize = util.parse_tuple( config.pop("pixelsize"), length=2, type=float ) # pixel size x/y (mm) (same dimension as sdd) def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict( first=min(scans), last=max(scans), range=",".join(str(scan) for scan in scans), ) # CONVENIENCE FUNCTIONS def get_scan(self, scanno): filename = os.path.join(self.config.datafilefolder, str(scanno) + ".dat") if not os.path.exists(filename): raise errors.ConfigError( "datafile filename does not exist: {0}".format(filename) ) return dnp.io.load(filename) @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] class EH2(IO7Input): def parse_config(self, config): super(IO7Input, self).parse_config(config) self.config.sdd = float( config.pop("sdd"), None ) # Sample to detector distance (mm) if self.config.sdd is not None: self.config.sdd = float(self.config.sdd) def process_image(self, scan, scanparams, pointparams, image): ( gamma, delta, omega, chi, phi, alpha, mon, transm, ) = pointparams # GAM, DEL, OMG, CHI, PHI, ALF, MON, TRANSM energy, UB = scanparams weights = numpy.ones_like(image) util.status( "{4}| gamma: {0}, delta: {1}, omega: {2}, mu: {3}".format( gamma, delta, omega, alpha, time.ctime(time.time()) ) ) # pixels to angles pixelsize = numpy.array(self.config.pixelsize) if self.config.sdd is None: sdd = scan.metadata.diff1detdist else: sdd = self.config.sdd nu = scan.metadata.diff2prot centralpixel = self.config.centralpixel # (column, row) = (delta, gamma) dz = (numpy.indices(image.shape)[1] - centralpixel[1]) * pixelsize[1] dx = (numpy.indices(image.shape)[0] - centralpixel[0]) * pixelsize[0] dy = numpy.ones(image.shape) * sdd # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix intensity = self.apply_mask(image, self.config.xmask, self.config.ymask) weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) dx = self.apply_mask(dx, self.config.xmask, self.config.ymask) dy = self.apply_mask(dy, self.config.xmask, self.config.ymask) dz = self.apply_mask(dz, self.config.xmask, self.config.ymask) # X,Y = numpy.meshgrid(x,y) # Z = numpy.ones(X.shape) * sdd pixels = dx, dy, dz return intensity, weights, (energy, UB, pixels, gamma, delta, omega, alpha, nu) class EH1(IO7Input): def parse_config(self, config): super(EH1, self).parse_config(config) self.config.sdd = float(config.pop("sdd")) # Sample to detector distance (mm) def process_image(self, scan, scanparams, pointparams, image): ( gamma, delta, omega, chi, phi, alpha, mon, transm, ) = pointparams # GAM, DEL, OMG, CHI, PHI, ALF, MON, TRANSM energy, UB = scanparams weights = numpy.ones_like(image) util.status( "{4}| gamma: {0}, delta: {1}, omega: {2}, mu: {3}".format( gamma, delta, omega, alpha, time.ctime(time.time()) ) ) # pixels to angles pixelsize = numpy.array(self.config.pixelsize) sdd = self.config.sdd nu = scan.metadata.diff1prot centralpixel = self.config.centralpixel # (column, row) = (delta, gamma) dz = (numpy.indices(image.shape)[1] - centralpixel[1]) * pixelsize[1] dx = (numpy.indices(image.shape)[0] - centralpixel[0]) * pixelsize[0] dy = numpy.ones(image.shape) * sdd # masking if self.config.maskmatrix is not None: if self.config.maskmatrix.shape != data.shape: raise errors.BackendError( "The mask matrix does not have the same shape as the images" ) weights *= self.config.maskmatrix intensity = self.apply_mask(image, self.config.xmask, self.config.ymask) weights = self.apply_mask(weights, self.config.xmask, self.config.ymask) dx = self.apply_mask(dx, self.config.xmask, self.config.ymask) dy = self.apply_mask(dy, self.config.xmask, self.config.ymask) dz = self.apply_mask(dz, self.config.xmask, self.config.ymask) pixels = dx, dy, dz return intensity, weights, (energy, UB, pixels, gamma, delta, omega, alpha, nu) def load_matrix(filename): if filename == None: return None if os.path.exists(filename): ext = os.path.splitext(filename)[-1] if ext == ".txt": return numpy.array(numpy.loadtxt(filename), dtype=numpy.bool) elif ext == ".npy": return numpy.array(numpy.load(filename), dtype=numpy.bool) else: raise ValueError( "unknown extension {0}, unable to load matrix!\n".format(ext) ) else: raise IOError( "filename: {0} does not exist. Can not load matrix".format(filename) ) binoculars-0.0.10/binoculars/backends/sixs.py000066400000000000000000001503611412510113200212000ustar00rootroot00000000000000"""This file is part of the binoculars project. The BINoculars library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The BINoculars library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with the hkl library. If not, see . Copyright (C) 2015-2021 Synchrotron SOLEIL L'Orme des Merisiers Saint-Aubin BP 48 91192 GIF-sur-YVETTE CEDEX Copyright (C) 2012-2015 European Synchrotron Radiation Facility Grenoble, France Authors: Willem Onderwaater Picca Frédéric-Emmanuel """ from typing import Any, Dict, NamedTuple, Optional, Tuple import numpy import math import os import sys import pyFAI from enum import Enum from math import cos, sin from gi.repository import GLib import gi gi.require_version("Hkl", "5.0") from gi.repository import Hkl from h5py import Dataset, File from numpy import ndarray from numpy.linalg import inv from pyFAI.detectors import ALL_DETECTORS from .soleil import ( DatasetPathContains, DatasetPathOr, DatasetPathWithAttribute, HItem, get_dataset, get_nxclass, node_as_string, ) from .. import backend, errors, util from ..util import ConfigSection # TODO # - Angles delta gamma. nom de 2 ou 3 moteurs. omega puis delta # gamma pour chaque pixels. # - aller cherche dans le fichier NeXuS le x0, y0 ainsi que le sdd. # - travailler en qx qy qz, il faut rajouter un paramètre optionnel # - qui permet de choisir une rotation azimuthal de Qx Qy. ################### # Common methodes # ################### WRONG_ATTENUATION = -100 class Diffractometer(NamedTuple): name: str # name of the hkl diffractometer ub: ndarray # the UB matrix geometry: Hkl.Geometry # the HklGeometry def get_diffractometer(hfile: File, config): """ Construct a Diffractometer from a NeXus file """ if config.geometry is not None: name = config.geometry ub = None else: node = get_nxclass(hfile, "NXdiffractometer") name = node_as_string(node["type"][()]) if name.endswith("\n"): # remove the last "\n" char name = name[:-1] try: ub = node["UB"][:] except AttributeError: ub = None factory = Hkl.factories()[name] hkl_geometry = factory.create_new_geometry() # wavelength = get_nxclass(hfile, 'NXmonochromator').wavelength[0] # geometry.wavelength_set(wavelength) return Diffractometer(name, ub, hkl_geometry) class Sample(NamedTuple): a: float b: float c: float alpha: float beta: float gamma: float ux: float uy: float uz: float ub: ndarray sample: Hkl.Sample def get_sample(hfile, config): """ Construct a Diffractometer from a NeXus file """ node = get_nxclass(hfile, "NXdiffractometer") def get_value(node, name, default, overwrite): if overwrite is not None: v = overwrite else: v = default try: v = node[name][()][0] except AttributeError: pass return v # hkl default sample a = get_value(node, "A", 1.54, config.a) b = get_value(node, "B", 1.54, config.b) c = get_value(node, "C", 1.54, config.c) alpha = get_value(node, "alpha", 90, config.alpha) beta = get_value(node, "beta", 90, config.beta) gamma = get_value(node, "gamma", 90, config.gamma) ux = get_value(node, "Ux", 0, config.ux) uy = get_value(node, "Uy", 0, config.uy) uz = get_value(node, "Uz", 0, config.uz) sample = Hkl.Sample.new("test") lattice = Hkl.Lattice.new( a, b, c, math.radians(alpha), math.radians(beta), math.radians(gamma) ) sample.lattice_set(lattice) parameter = sample.ux_get() parameter.value_set(ux, Hkl.UnitEnum.USER) sample.ux_set(parameter) parameter = sample.uy_get() parameter.value_set(uy, Hkl.UnitEnum.USER) sample.uy_set(parameter) parameter = sample.uz_get() parameter.value_set(uz, Hkl.UnitEnum.USER) sample.uz_set(parameter) ub = hkl_matrix_to_numpy(sample.UB_get()) return Sample(a, b, c, alpha, beta, gamma, ux, uy, uz, ub, sample) class Detector(NamedTuple): name: str detector: Hkl.Detector def get_detector(hfile, h5_nodes): detector = Hkl.Detector.factory_new(Hkl.DetectorType(0)) images = h5_nodes["image"] s = images.shape[-2:] if s == (960, 560) or s == (560, 960): det = Detector("xpad_flat", detector) elif s == (1065, 1030): det = Detector("eiger1m", detector) elif s == (120, 560): det = Detector("imxpads70", detector) elif s == (256, 257): det = Detector("ufxc", detector) else: det = Detector("imxpads140", detector) return det class Source(NamedTuple): wavelength: float def get_source(hfile): wavelength = None node = get_nxclass(hfile, "NXmonochromator") for attr in ["wavelength", "lambda"]: try: wavelength = node[attr][0] except KeyError: pass except IndexError: pass return Source(wavelength) class DataFrame(NamedTuple): diffractometer: Diffractometer sample: Sample detector: Detector source: Source h5_nodes: Dict[str, Dataset] def dataframes(hfile, data_path, config): h5_nodes = {k: get_dataset(hfile, v) for k, v in data_path.items()} diffractometer = get_diffractometer(hfile, config) sample = get_sample(hfile, config) detector = get_detector(hfile, h5_nodes) source = get_source(hfile) yield DataFrame(diffractometer, sample, detector, source, h5_nodes) def get_ki(wavelength): """ for now the direction is always along x """ TAU = 2 * math.pi return numpy.array([TAU / wavelength, 0, 0]) def normalized(a, axis=-1, order=2): l2 = numpy.atleast_1d(numpy.linalg.norm(a, order, axis)) l2[l2 == 0] = 1 return a / numpy.expand_dims(l2, axis) def hkl_matrix_to_numpy(m): M = numpy.empty((3, 3)) for i in range(3): for j in range(3): M[i, j] = m.get(i, j) return M def M(theta, u): """ :param theta: the axis value in radian :type theta: float :param u: the axis vector [x, y, z] :type u: [float, float, float] :return: the rotation matrix :rtype: numpy.ndarray (3, 3) """ c = cos(theta) one_minus_c = 1 - c s = sin(theta) return numpy.array( [ [ c + u[0] ** 2 * one_minus_c, u[0] * u[1] * one_minus_c - u[2] * s, u[0] * u[2] * one_minus_c + u[1] * s, ], [ u[0] * u[1] * one_minus_c + u[2] * s, c + u[1] ** 2 * one_minus_c, u[1] * u[2] * one_minus_c - u[0] * s, ], [ u[0] * u[2] * one_minus_c - u[1] * s, u[1] * u[2] * one_minus_c + u[0] * s, c + u[2] ** 2 * one_minus_c, ], ] ) ############### # Projections # ############### class SurfaceOrientation(Enum): VERTICAL = 1 HORIZONTAL = 2 class PDataFrame(NamedTuple): pixels: ndarray k: float ub: Optional[ndarray] R: ndarray P: ndarray index: int timestamp: int surface_orientation: SurfaceOrientation dataframe: DataFrame input_config: ConfigSection class RealSpace(backend.ProjectionBase): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: pixels = pdataframe.pixels P = pdataframe.P timestamp = pdataframe.timestamp if P is not None: pixels_ = numpy.tensordot(P, pixels, axes=1) else: pixels_ = pixels x = pixels_[1] y = pixels_[2] if timestamp is not None: z = numpy.ones_like(x) * timestamp else: z = pixels_[0] return (x, y, z) def get_axis_labels(self): return ("x", "y", "z") class Pixels(backend.ProjectionBase): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: pixels = pdataframe.pixels return numpy.meshgrid( numpy.arange(pixels[0].shape[1]), numpy.arange(pixels[0].shape[0]) ) def get_axis_labels(self) -> Tuple[str]: return "x", "y" class HKLProjection(backend.ProjectionBase): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: pixels = pdataframe.pixels k = pdataframe.k UB = pdataframe.ub R = pdataframe.R P = pdataframe.P if UB is None: raise Exception( "In order to compute the HKL projection, you need a valid ub matrix" ) ki = [1, 0, 0] RUB_1 = inv(numpy.dot(R, UB)) RUB_1P = numpy.dot(RUB_1, P) kf = normalized(pixels, axis=0) hkl_f = numpy.tensordot(RUB_1P, kf, axes=1) hkl_i = numpy.dot(RUB_1, ki) hkl = hkl_f - hkl_i[:, numpy.newaxis, numpy.newaxis] h, k, l = hkl * k return h, k, l def get_axis_labels(self) -> Tuple[str]: return "H", "K", "L" class HKProjection(HKLProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: h, k, l = super(HKProjection, self).project(index, pdataframe) return h, k def get_axis_labels(self) -> Tuple[str]: return "H", "K" class QxQyQzProjection(backend.ProjectionBase): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: pixels = pdataframe.pixels k = pdataframe.k R = pdataframe.R P = pdataframe.P surface_orientation = pdataframe.surface_orientation # TODO factorize with HklProjection. Here a trick in order to # compute Qx Qy Qz in the omega basis. if surface_orientation is SurfaceOrientation.VERTICAL: UB = numpy.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]]) if self.config.omega_offset is not None: UB = numpy.dot(UB, M(self.config.omega_offset, [0, 0, -1])) else: UB = numpy.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) if self.config.mu_offset is not None: UB = numpy.dot(UB, M(self.config.mu_offset, [0, 0, -1])) # the ki vector should be in the NexusFile or easily extracted # from the hkl library. ki = [1, 0, 0] RUB_1 = inv(numpy.dot(R, UB)) RUB_1P = numpy.dot(RUB_1, P) kf = normalized(pixels, axis=0) hkl_f = numpy.tensordot(RUB_1P, kf, axes=1) hkl_i = numpy.dot(RUB_1, ki) hkl = hkl_f - hkl_i[:, numpy.newaxis, numpy.newaxis] qx, qy, qz = hkl * k return qx, qy, qz def get_axis_labels(self) -> Tuple[str]: return "Qx", "Qy", "Qz" def parse_config(self, config) -> None: super(QxQyQzProjection, self).parse_config(config) # omega offset for the sample in degree then convert into radian omega_offset = config.pop("omega_offset", None) if omega_offset is not None: self.config.omega_offset = math.radians(float(omega_offset)) else: self.config.omega_offset = None # omega offset for the sample in degree then convert into radian mu_offset = config.pop("mu_offset", None) if mu_offset is not None: self.config.mu_offset = math.radians(float(mu_offset)) else: self.config.mu_offset = None class QxQyIndexProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: timestamp = pdataframe.timestamp qx, qy, qz = super(QxQyIndexProjection, self).project(index, pdataframe) return qx, qy, numpy.ones_like(qx) * timestamp def get_axis_labels(self) -> Tuple[str]: return "Qx", "Qy", "t" class QxQzIndexProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: timestamp = pdataframe.timestamp qx, qy, qz = super(QxQzIndexProjection, self).project(index, pdataframe) return qx, qz, numpy.ones_like(qx) * timestamp def get_axis_labels(self) -> Tuple[str]: return "Qx", "Qz", "t" class QyQzIndexProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: timestamp = pdataframe.timestamp qx, qy, qz = super(QyQzIndexProjection, self).project(index, pdataframe) return qy, qz, numpy.ones_like(qy) * timestamp def get_axis_labels(self) -> Tuple[str]: return "Qy", "Qz", "t" class QparQperProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: qx, qy, qz = super(QparQperProjection, self).project(index, pdataframe) return numpy.sqrt(qx * qx + qy * qy), qz def get_axis_labels(self) -> Tuple[str]: return "Qpar", "Qper" class QparQperIndexProjection(QparQperProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: timestamp = pdataframe.timestamp qpar, qper = super(QparQperIndexProjection, self).project(index, pdataframe) return qpar, qper, numpy.ones_like(qpar) * timestamp def get_axis_labels(self) -> Tuple[str]: return "Qpar", "Qper", "t" class Stereo(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: qx, qy, qz = super(Stereo, self).project(index, pdataframe) q = numpy.sqrt(qx * qx + qy * qy + qz * qz) ratio = qz + q xp = qx / ratio yp = qy / ratio return q, xp, yp def get_axis_labels(self) -> Tuple[str]: return "Q", "xp", "yp" class QzPolarProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: qx, qy, qz = super(QzPolarProjection, self).project(index, pdataframe) phi = numpy.rad2deg(numpy.arctan2(qx, qy)) q = numpy.sqrt(qx * qx + qy * qy + qz * qz) return phi, q, qz def get_axis_labels(self) -> Tuple[str]: return "Phi", "Q", "Qz" class QyPolarProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: qx, qy, qz = super(QyPolarProjection, self).project(index, pdataframe) phi = numpy.rad2deg(numpy.arctan2(qz, qx)) q = numpy.sqrt(qx * qx + qy * qy + qz * qz) return phi, q, qy def get_axis_labels(self) -> Tuple[str]: return "Phi", "Q", "Qy" class QxPolarProjection(QxQyQzProjection): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: qx, qy, qz = super(QxPolarProjection, self).project(index, pdataframe) phi = numpy.rad2deg(numpy.arctan2(qz, -qy)) q = numpy.sqrt(qx * qx + qy * qy + qz * qz) return phi, q, qx def get_axis_labels(self) -> Tuple[str]: return "Phi", "Q", "Qx" class QIndex(Stereo): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: timestamp = pdataframe.timestamp q, qx, qy = super(QIndex, self).project(index, pdataframe) return q, numpy.ones_like(q) * timestamp def get_axis_labels(self) -> Tuple[str]: return "Q", "Index" class AnglesProjection(backend.ProjectionBase): def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: # put the detector at the right position pixels = pdataframe.pixels geometry = pdataframe.dataframe.diffractometer.geometry detrot = pdataframe.input_config.detrot sdd = pdataframe.input_config.sdd try: axis = geometry.axis_get("eta_a") eta_a = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: eta_a = 0 try: axis = geometry.axis_get("omega") omega0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: omega0 = 0 try: axis = geometry.axis_get("delta") delta0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: delta0 = 0 try: axis = geometry.axis_get("gamma") gamma0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: gamma0 = 0 P = M(math.radians(eta_a), [1, 0, 0]) if detrot is not None: P = numpy.dot(P, M(math.radians(detrot), [1, 0, 0])) x, y, z = numpy.tensordot(P, pixels, axes=1) delta = numpy.rad2deg(numpy.arctan(z / sdd)) + delta0 gamma = numpy.rad2deg(numpy.arctan(y / sdd)) + gamma0 omega = numpy.ones_like(delta) * omega0 return (delta, gamma, omega) # # on calcule le vecteur de l'axes de rotation de l'angle qui # # nous interesse. (ici delta et gamma). example delta (0, 1, # # 0) (dans le repere du detecteur). Il faut donc calculer la # # matrice de transformation pour un axe donnée. C'est la liste # # de transformations qui sont entre cet axe et le detecteur. # axis_delta = None # axis_gamma = None # # il nous faut ensuite calculer la normale du plan dans lequel # # nous allons projeter les pixels. (C'est le produit vectoriel # # de k0, axis_xxx). # n_delta = None # n_gamma = None # # On calcule la projection sur la normale des plans en # # question. # p_delta = None # p_gamma = None # # On calcule la norme de chaque pixel. (qui pourra etre # # calcule une seule fois pour toutes les images). # l2 = numpy.linalg.norm(pixels, order=2, axis=-1) # # xxx0 is the angles of the diffractometer for the given # # image. # delta = numpy.arcsin(p_delta / l2) + delta0 # gamma = numpy.arcsin(p_gamma / l2) + gamma0 # omega = numpy.ones_like(delta) * omega0 # return (omega, delta, gamma) def get_axis_labels(self) -> Tuple[str]: return 'delta', 'gamma', 'omega' class AnglesProjection2(backend.ProjectionBase): # omega <> mu def project(self, index: int, pdataframe: PDataFrame) -> Tuple[ndarray]: # put the detector at the right position pixels = pdataframe.pixels geometry = pdataframe.dataframe.diffractometer.geometry detrot = pdataframe.input_config.detrot sdd = pdataframe.input_config.sdd try: axis = geometry.axis_get("eta_a") eta_a = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: eta_a = 0 try: axis = geometry.axis_get("mu") mu0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: mu0 = 0 try: axis = geometry.axis_get("delta") delta0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: delta0 = 0 try: axis = geometry.axis_get("gamma") gamma0 = axis.value_get(Hkl.UnitEnum.USER) except GLib.GError as err: gamma0 = 0 P = M(math.radians(eta_a), [1, 0, 0]) if detrot is not None: P = numpy.dot(P, M(math.radians(detrot), [1, 0, 0])) x, y, z = numpy.tensordot(P, pixels, axes=1) delta = numpy.rad2deg(numpy.arctan(z / sdd)) + delta0 gamma = numpy.rad2deg(numpy.arctan(y / sdd)) + gamma0 mu = numpy.ones_like(delta) * mu0 return (delta, gamma, mu) def get_axis_labels(self) -> Tuple[str]: return 'delta', 'gamma', 'mu' ################## # Input Backends # ################## class SIXS(backend.InputBase): # OFFICIAL API dbg_scanno = None dbg_pointno = None def generate_jobs(self, command): scans = util.parse_multi_range(",".join(command).replace(" ", ",")) if not len(scans): sys.stderr.write("error: no scans selected, nothing to do\n") for scanno in scans: util.status("processing scan {0}...".format(scanno)) if self.config.pr: pointcount = self.config.pr[1] - self.config.pr[0] + 1 start = self.config.pr[0] else: start = 0 pointcount = self.get_pointcount(scanno) if pointcount > self.config.target_weight * 1.4: for s in util.chunk_slicer(pointcount, self.config.target_weight): yield backend.Job( scan=scanno, firstpoint=start + s.start, lastpoint=start + s.stop - 1, weight=s.stop - s.start, ) else: yield backend.Job( scan=scanno, firstpoint=start, lastpoint=start + pointcount - 1, weight=pointcount, ) def process_job(self, job): super(SIXS, self).process_job(job) with File(self.get_filename(job.scan), "r") as scan: self.metadict = dict() try: for dataframe in dataframes(scan, self.HPATH, self.config): pixels = self.get_pixels(dataframe.detector) mask = self.get_mask(dataframe.detector, self.config.maskmatrix) for index in range(job.firstpoint, job.lastpoint + 1): res = self.process_image(index, dataframe, pixels, mask) # some frame could be skipped if res is None: util.status(f"skipped {index}") continue else: yield res util.statuseol() except Exception as exc: exc.args = errors.addmessage( exc.args, ", An error occured for scan {0} at point {1}. See above for more information".format( self.dbg_scanno, self.dbg_pointno ), ) # noqa raise self.metadata.add_section("sixs_backend", self.metadict) def parse_config(self, config): super(SIXS, self).parse_config(config) # Optional, select a subset of the image range in the x # direction. all by default self.config.xmask = util.parse_multi_range(config.pop("xmask", None)) # Optional, select a subset of the image range in the y # direction. all by default self.config.ymask = util.parse_multi_range(config.pop("ymask", None)) # location of the nexus files (take precedence on nexusfile) self.config.nexusdir = config.pop("nexusdir", None) # Location of the specfile self.config.nexusfile = config.pop("nexusfile", None) # Optional, all range by default self.config.pr = config.pop("pr", None) if self.config.xmask is None: self.config.xmask = slice(None) if self.config.ymask is None: self.config.ymask = slice(None) if self.config.pr: self.config.pr = util.parse_tuple( self.config.pr, length=2, type=int ) # noqa # sample to detector distance (mm) self.config.sdd = float(config.pop("sdd")) # x,y coordinates of the central pixel self.config.centralpixel = util.parse_tuple( config.pop("centralpixel"), length=2, type=int ) # noqa # Optional, if supplied pixels where the mask is 0 will be removed self.config.maskmatrix = config.pop("maskmatrix", None) # detector rotation around x (1, 0, 0) self.config.detrot = config.pop("detrot", None) if self.config.detrot is not None: try: self.config.detrot = float(self.config.detrot) except ValueError: self.config.detrot = None # attenuation_coefficient (Optional) attenuation_coefficient = config.pop("attenuation_coefficient", None) if attenuation_coefficient is not None: try: self.config.attenuation_coefficient = float( attenuation_coefficient ) # noqa except ValueError: self.config.attenuation_coefficient = None else: self.config.attenuation_coefficient = None # surface_orientation surface_orientation = config.pop("surface_orientation", None) surface_orientation_opt = SurfaceOrientation.VERTICAL if surface_orientation is not None: if surface_orientation.lower() == "horizontal": surface_orientation_opt = SurfaceOrientation.HORIZONTAL self.config.surface_orientation = surface_orientation_opt # sample self.config.a = util.parse_float(config, "a", None) self.config.b = util.parse_float(config, "b", None) self.config.c = util.parse_float(config, "c", None) self.config.alpha = util.parse_float(config, "alpha", None) self.config.beta = util.parse_float(config, "beta", None) self.config.gamma = util.parse_float(config, "gamma", None) self.config.ux = util.parse_float(config, "ux", None) self.config.uy = util.parse_float(config, "uy", None) self.config.uz = util.parse_float(config, "uz", None) # geometry self.config.geometry = config.pop("geometry", None) # overrided_axes_values self.config.overrided_axes_values = util.parse_dict(config, "overrided_axes_values", None) def get_destination_options(self, command): if not command: return False command = ",".join(command).replace(" ", ",") scans = util.parse_multi_range(command) return dict( first=min(scans), last=max(scans), range=",".join(str(scan) for scan in scans), ) # noqa # CONVENIENCE FUNCTIONS def get_filename(self, scanno): filename = None if self.config.nexusdir: dirname = self.config.nexusdir files = [ f for f in os.listdir(dirname) if ( (str(scanno).zfill(5) in f) and (os.path.splitext(f)[1] in [".hdf5", ".nxs"]) ) ] if files is not []: filename = os.path.join(dirname, files[0]) else: filename = self.config.nexusfile.format(scanno=str(scanno).zfill(5)) # noqa if not os.path.exists(filename): raise errors.ConfigError( "nexus filename does not exist: {0}".format(filename) ) # noqa return filename @staticmethod def apply_mask(data, xmask, ymask): roi = data[ymask, :] return roi[:, xmask] class FlyScanUHV(SIXS): HPATH = { "image": DatasetPathOr( HItem("xpad_image", True), DatasetPathOr( HItem("xpad_s140_image", True), HItem("xpad_S140_image", False))), "mu": HItem("UHV_MU", False), "omega": HItem("UHV_OMEGA", False), "delta": HItem("UHV_DELTA", False), "gamma": HItem("UHV_GAMMA", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } def get_pointcount(self, scanno): # just open the file in order to extract the number of step with File(self.get_filename(scanno), "r") as scan: return get_dataset(scan, self.HPATH["image"]).shape[0] def get_attenuation(self, index, h5_nodes, offset): attenuation = None if self.config.attenuation_coefficient is not None: try: try: node = h5_nodes["attenuation"] if node is not None: attenuation = node[index + offset] else: raise Exception( "you asked for attenuation but the file does not contain attenuation informations." ) # noqa except KeyError: attenuation = 1.0 except ValueError: attenuation = WRONG_ATTENUATION return attenuation def get_timestamp(self, index, h5_nodes): timestamp = index if "timestamp" in h5_nodes: node = h5_nodes["timestamp"] if node is not None: timestamp = node[index] return timestamp def get_value(self, key, index, h5_nodes, overrided_axes_values): if overrided_axes_values is not None: if key in overrided_axes_values: return overrided_axes_values[key] return h5_nodes[key][index] def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] mu = self.get_value("mu", index, h5_nodes, overrided_axes_values) omega = self.get_value("omega", index, h5_nodes, overrided_axes_values) delta = self.get_value("delta", index, h5_nodes, overrided_axes_values) gamma = self.get_value("gamma", index, h5_nodes, overrided_axes_values) attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (mu, omega, delta, gamma)) def process_image( self, index, dataframe, pixels, mask ) -> Optional[Tuple[ndarray, ndarray, Tuple[int, PDataFrame]]]: util.status(str(index)) # extract the data from the h5 nodes h5_nodes = dataframe.h5_nodes overrided_axes_values = self.config.overrided_axes_values intensity, attenuation, timestamp, values = self.get_values(index, h5_nodes, overrided_axes_values) # the next version of the Hkl library will raise an exception # if at least one of the values is Nan/-Inf or +Inf. Emulate # this until we backported the right hkl library. if not all([math.isfinite(v) for v in values]): return None if attenuation is not None: if not math.isfinite(attenuation): return None # BEWARE in order to avoid precision problem we convert the # uint16 -> float32. (the size of the mantis is on 23 bits) # enought to contain the uint16. If one day we use uint32, it # should be necessary to convert into float64. intensity = intensity.astype("float32") weights = None if self.config.attenuation_coefficient is not None: if attenuation != WRONG_ATTENUATION: intensity *= self.config.attenuation_coefficient ** attenuation weights = numpy.ones_like(intensity) weights *= ~mask else: weights = numpy.zeros_like(intensity) else: weights = numpy.ones_like(intensity) weights *= ~mask k = 2 * math.pi / dataframe.source.wavelength hkl_geometry = dataframe.diffractometer.geometry hkl_geometry.axis_values_set(values, Hkl.UnitEnum.USER) # sample hkl_sample = dataframe.sample.sample q_sample = hkl_geometry.sample_rotation_get(hkl_sample) R = hkl_matrix_to_numpy(q_sample.to_matrix()) # detector hkl_detector = dataframe.detector.detector q_detector = hkl_geometry.detector_rotation_get(hkl_detector) P = hkl_matrix_to_numpy(q_detector.to_matrix()) if self.config.detrot is not None: P = numpy.dot(P, M(math.radians(self.config.detrot), [1, 0, 0])) surface_orientation = self.config.surface_orientation pdataframe = PDataFrame( pixels, k, dataframe.sample.ub, R, P, index, timestamp, surface_orientation, dataframe, self.config ) return intensity, weights, (index, pdataframe) def get_pixels(self, detector): # works only for flat detector. if detector.name == "ufxc": max_shape = (256, 257) detector = pyFAI.detectors.Detector(75e-6, 75e-6, splineFile=None, max_shape=max_shape) else: detector = ALL_DETECTORS[detector.name]() y, x, _ = detector.calc_cartesian_positions() y0 = y[self.config.centralpixel[1], self.config.centralpixel[0]] x0 = x[self.config.centralpixel[1], self.config.centralpixel[0]] z = numpy.ones(x.shape) * -1 * self.config.sdd # return converted to the hkl library coordinates # x -> -y # y -> z # z -> -x return numpy.array([-z, -(x - x0), (y - y0)]) def get_mask(self, detector: Detector, fnmask: Optional[str]=None) -> ndarray: if detector.name == "ufxc": mask = numpy.zeros((256, 257)).astype(bool) else: detector = ALL_DETECTORS[detector.name]() mask = detector.mask.astype(numpy.bool) maskmatrix = load_matrix(fnmask) if maskmatrix is not None: mask = numpy.bitwise_or(mask, maskmatrix) return mask class FlyScanUHV2(FlyScanUHV): HPATH = { "image": DatasetPathOr( HItem("xpad_image", True), DatasetPathOr( HItem("xpad_s140_image", True), HItem("xpad_S140_image", False))), "mu": HItem("mu", False), "omega": HItem("omega", False), "delta": HItem("delta", False), "gamma": HItem("gamma", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class FlyScanUHVS70(FlyScanUHV): HPATH = { "image": HItem("xpad_s70_image", False), "mu": HItem("mu", False), "omega": HItem("omega", False), "delta": HItem("delta", False), "gamma": HItem("gamma", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class FlyScanUHVS70Andreazza(FlyScanUHV): HPATH = { "image": HItem("xpad_s70_image", False), # omega, mu et gamma dans overrided_axes_values "delta": HItem("delta_xps", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class FlyScanUHVUfxc(FlyScanUHV): HPATH = { "image": HItem("ufxc_sixs_image", False), "mu": HItem("mu", False), "omega": HItem("omega", False), "delta": HItem("delta", False), "gamma": HItem("gamma", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class GisaxUhvEiger(FlyScanUHV): HPATH = { "image": HItem("eiger_image", False), "attenuation": HItem("attenuation", True), "eix": DatasetPathOr( HItem("eix", True), DatasetPathContains("i14-c-cx1-dt-det_tx.1/position_pre")), "eiz": DatasetPathOr( HItem("eiz", True), DatasetPathContains("i14-c-cx1-dt-det_tz.1/position_pre")) } def get_translation(self, node, index, default): res = default if node: if node.shape[0] == 1: res = node[0] else: res = node[index] return res def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] eix = self.get_translation(h5_nodes["eix"], index, 0.0) # mm eiz = self.get_translation(h5_nodes["eiz"], index, 0.0) # mm attenuation = self.get_attenuation(index, h5_nodes, 2) return (image, attenuation, eix, eiz) def process_image( self, index, dataframe, pixels0, mask ) -> Optional[Tuple[ndarray, ndarray, Tuple[int, PDataFrame]]]: util.status(str(index)) # extract the data from the h5 nodes h5_nodes = dataframe.h5_nodes intensity, attenuation, eix, eiz = self.get_values(index, h5_nodes) # check if the image can be used depending on a method. if eiz < 11: return None if attenuation is not None: if not math.isfinite(attenuation): return None # BEWARE in order to avoid precision problem we convert the # uint16 -> float32. (the size of the mantis is on 23 bits) # enought to contain the uint16. If one day we use uint32, it # should be necessary to convert into float64. intensity = intensity.astype("float32") weights = None if self.config.attenuation_coefficient is not None: if attenuation != WRONG_ATTENUATION: intensity *= self.config.attenuation_coefficient ** attenuation weights = numpy.ones_like(intensity) weights *= ~mask else: weights = numpy.zeros_like(intensity) else: weights = numpy.ones_like(intensity) weights *= ~mask if self.config.detrot is not None: P = M(math.radians(self.config.detrot), [1, 0, 0]) pixels = numpy.tensordot(P, pixels0, axes=1) else: pixels = pixels0.copy() # TODO translate the detector, must be done after the detrot. if eix != 0.0: pixels[2] += -eix * 1e-3 if eiz != 0.0: pixels[1] += eiz * 1e-3 pdataframe = PDataFrame( pixels, None, None, None, None, None, None, None, dataframe, self.config ) return intensity, weights, (index, pdataframe) class FlyMedH(FlyScanUHV): HPATH = { "image": DatasetPathOr( HItem("xpad_image", True), DatasetPathOr( HItem("xpad_s140_image", True), HItem("xpad_S140_image", False))), "pitch": HItem("beta", True), "mu": HItem("mu", False), "gamma": HItem("gamma", False), "delta": HItem("delta", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] pitch = h5_nodes["pitch"][index] if h5_nodes["pitch"] else 0.3 mu = h5_nodes["mu"][index] gamma = h5_nodes["gamma"][index] delta = h5_nodes["delta"][index] attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (pitch, mu, gamma, delta)) class FlyMedHS70(FlyMedH): HPATH = { "image": HItem("xpad_s70_image", True), "pitch": HItem("beta", True), "mu": HItem("mu", False), "gamma": HItem("gamma", False), "delta": HItem("delta", False), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class SBSMedH(FlyScanUHV): HPATH = { "image": DatasetPathWithAttribute("long_name", b"i14-c-c00/dt/xpad.1/image"), "pitch": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/diff-med-tpp/pitch" ), "mu": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-h-dif-group.1/mu" ), "gamma": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-h-dif-group.1/gamma" ), "delta": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-h-dif-group.1/delta" ), "attenuation": DatasetPathWithAttribute("long_name", b"i14-c-c00/ex/roic/att"), "timestamp": HItem("sensors_timestamps", True), } def get_pointcount(self, scanno: int) -> int: # just open the file in order to extract the number of step with File(self.get_filename(scanno), "r") as scan: path = self.HPATH["image"] return get_dataset(scan, path).shape[0] def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] pitch = h5_nodes["pitch"][index] mu = h5_nodes["mu"][index] gamma = h5_nodes["gamma"][index] delta = h5_nodes["delta"][index] attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (pitch, mu, gamma, delta)) class SBSFixedDetector(FlyScanUHV): HPATH = { "image": HItem("data_11", False), "timestamp": HItem("sensors_timestamps", True), } def get_pointcount(self, scanno): # just open the file in order to extract the number of step with File(self.get_filename(scanno), "r") as scan: return get_nxclass(scan, "NXdata")["data_11"].shape[0] def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, None) def process_image( self, index, dataframe, pixels, mask ) -> Optional[Tuple[ndarray, ndarray, Tuple[int, PDataFrame]]]: util.status(str(index)) # extract the data from the h5 nodes h5_nodes = dataframe.h5_nodes intensity, attenuation, timestamp, _values = self.get_values(index, h5_nodes) if not math.isfinite(attenuation): return None # BEWARE in order to avoid precision problem we convert the # uint16 -> float32. (the size of the mantis is on 23 bits) # enought to contain the uint16. If one day we use uint32, it # should be necessary to convert into float64. intensity = intensity.astype("float32") weights = None if self.config.attenuation_coefficient is not None: if attenuation != WRONG_ATTENUATION: intensity *= self.config.attenuation_coefficient ** attenuation weights = numpy.ones_like(intensity) weights *= ~mask else: weights = numpy.zeros_like(intensity) else: weights = numpy.ones_like(intensity) weights *= ~mask k = 2 * math.pi / dataframe.source.wavelength I = numpy.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]]) if self.config.detrot is not None: P = M(math.radians(self.config.detrot), [1, 0, 0]) surface_orientation = self.config.surface_orientation pdataframe = PDataFrame(pixels, k, I, I, P, index, timestamp, surface_orientation, dataframe, self.config) return intensity, weights, (index, pdataframe) class FlyMedV(FlyScanUHV): HPATH = { "image": DatasetPathOr( HItem("xpad_image", True), DatasetPathOr( HItem("xpad_s140_image", True), HItem("xpad_S140_image", False))), "beta": HItem("beta", True), "mu": HItem("mu", False), "omega": HItem("omega", False), "gamma": HItem("gamma", False), "delta": HItem("delta", False), "etaa": HItem("etaa", True), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] beta = h5_nodes["beta"][index] if h5_nodes["beta"] else 0.0 mu = h5_nodes["mu"][index] omega = h5_nodes["omega"][index] gamma = h5_nodes["gamma"][index] delta = h5_nodes["delta"][index] etaa = h5_nodes["etaa"][index] if h5_nodes["etaa"] else 0.0 attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (beta, mu, omega, gamma, delta, etaa)) class FlyMedVS70(FlyMedV): HPATH = { "image": HItem("xpad_s70_image", False), "beta": HItem("beta", True), "mu": HItem("mu", False), "omega": HItem("omega", False), "gamma": HItem("gamma", False), "delta": HItem("delta", False), "etaa": HItem("etaa", True), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), } class FLYMedVEiger(FlyMedV): HPATH = { "image": HItem("eiger_image", False), "beta": HItem("beta", True), "mu": HItem("mu", False), "omega": HItem("omega", False), "gamma": HItem("gamma", False), "delta": HItem("delta", False), "etaa": HItem("etaa", True), "attenuation": HItem("attenuation", True), "timestamp": HItem("epoch", True), "eix": DatasetPathOr( HItem("eix", True), DatasetPathContains("i14-c-cx1-dt-det_tx.1/position_pre")), "eiz": DatasetPathOr( HItem("eiz", True), DatasetPathContains("i14-c-cx1-dt-det_tz.1/position_pre")) } def get_translation(self, node, index, default): res = default if node: if node.shape[0] == 1: res = node[0] else: res = node[index] return res def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] beta = h5_nodes["beta"][index] if h5_nodes["beta"] else 0.0 # degrees mu = h5_nodes["mu"][index] # degrees omega = h5_nodes["omega"][index] # degrees gamma = 0 # degrees delta = 0 # degrees etaa = 0 # degrees eix = self.get_translation(h5_nodes["eix"], index, 0.0) # mm eiz = self.get_translation(h5_nodes["eiz"], index, 0.0) # mm attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, eix, eiz, (beta, mu, omega, gamma, delta, etaa)) def process_image( self, index, dataframe, pixels0, mask ) -> Optional[Tuple[ndarray, ndarray, Tuple[int, PDataFrame]]]: util.status(str(index)) # extract the data from the h5 nodes h5_nodes = dataframe.h5_nodes intensity, attenuation, timestamp, eix, eiz, values = self.get_values(index, h5_nodes) # TODO translate the detector, must be done after the detrot. pixels = pixels0.copy() if eix != 0.0: pixels[2] += eix * 1e-3 if eiz != 0.0: pixels[1] += eiz * 1e-3 # the next version of the Hkl library will raise an exception # if at least one of the values is Nan/-Inf or +Inf. Emulate # this until we backported the right hkl library. if not all([math.isfinite(v) for v in values]): return None if attenuation is not None: if not math.isfinite(attenuation): return None # BEWARE in order to avoid precision problem we convert the # uint16 -> float32. (the size of the mantis is on 23 bits) # enought to contain the uint16. If one day we use uint32, it # should be necessary to convert into float64. intensity = intensity.astype("float32") weights = None if self.config.attenuation_coefficient is not None: if attenuation != WRONG_ATTENUATION: intensity *= self.config.attenuation_coefficient ** attenuation weights = numpy.ones_like(intensity) weights *= ~mask else: weights = numpy.zeros_like(intensity) else: weights = numpy.ones_like(intensity) weights *= ~mask k = 2 * math.pi / dataframe.source.wavelength hkl_geometry = dataframe.diffractometer.geometry hkl_geometry.axis_values_set(values, Hkl.UnitEnum.USER) # sample hkl_sample = dataframe.sample.sample q_sample = hkl_geometry.sample_rotation_get(hkl_sample) R = hkl_matrix_to_numpy(q_sample.to_matrix()) # detector hkl_detector = dataframe.detector.detector q_detector = hkl_geometry.detector_rotation_get(hkl_detector) P = hkl_matrix_to_numpy(q_detector.to_matrix()) if self.config.detrot is not None: P = numpy.dot(P, M(math.radians(self.config.detrot), [1, 0, 0])) surface_orientation = self.config.surface_orientation pdataframe = PDataFrame( pixels, k, dataframe.sample.ub, R, P, index, timestamp, surface_orientation, dataframe, self.config ) return intensity, weights, (index, pdataframe) class SBSMedV(FlyScanUHV): HPATH = { "image": DatasetPathWithAttribute("long_name", b"i14-c-c00/dt/xpad.1/image"), "beta": DatasetPathContains("i14-c-cx1-ex-diff-med-tpp/TPP/Orientation/pitch"), "mu": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/mu" ), "omega": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/omega" ), "gamma": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/gamma" ), "delta": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/delta" ), "etaa": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/etaa" ), "attenuation": DatasetPathWithAttribute("long_name", b"i14-c-c00/ex/roic/att"), "timestamp": HItem("sensors_timestamps", True), } def get_pointcount(self, scanno: int) -> int: # just open the file in order to extract the number of step with File(self.get_filename(scanno), "r") as scan: path = self.HPATH["image"] return get_dataset(scan, path).shape[0] def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] beta = h5_nodes["beta"][0] mu = h5_nodes["mu"][index] omega = h5_nodes["omega"][index] gamma = h5_nodes["gamma"][index] delta = h5_nodes["delta"][index] etaa = h5_nodes["etaa"][index] attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (beta, mu, omega, gamma, delta, etaa)) class SBSMedVFixDetector(SBSMedV): HPATH = { "image": DatasetPathWithAttribute("long_name", b"i14-c-c00/dt/eiger.1/image"), "beta": DatasetPathContains("i14-c-cx1-ex-diff-med-tpp/TPP/Orientation/pitch"), "mu": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/mu" ), "omega": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/omega" ), "gamma": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/gamma" ), "delta": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/delta" ), "etaa": DatasetPathWithAttribute( "long_name", b"i14-c-cx1/ex/med-v-dif-group.1/etaa" ), "attenuation": DatasetPathWithAttribute("long_name", b"i14-c-c00/ex/roic/att"), "timestamp": HItem("sensors_timestamps", True), } def get_values(self, index, h5_nodes, overrided_axes_values=None): image = h5_nodes["image"][index] beta = h5_nodes["beta"][0] mu = h5_nodes["mu"][index] omega = h5_nodes["omega"][index] gamma = 0 delta = 0 etaa = 0 attenuation = self.get_attenuation(index, h5_nodes, 2) timestamp = self.get_timestamp(index, h5_nodes) return (image, attenuation, timestamp, (beta, mu, omega, gamma, delta, etaa)) def load_matrix(filename): if filename is None: return None if os.path.exists(filename): ext = os.path.splitext(filename)[-1] if ext == ".txt": return numpy.array(numpy.loadtxt(filename), dtype=numpy.bool) elif ext == ".npy": mask = numpy.array(numpy.load(filename), dtype=numpy.bool) print("loaded mask sum: ", numpy.sum(mask)) return mask else: raise ValueError( "unknown extension {0}, unable to load matrix!\n".format(ext) ) # noqa else: raise IOError( "filename: {0} does not exist. Can not load matrix".format(filename) ) # noqa binoculars-0.0.10/binoculars/backends/soleil.py000066400000000000000000000054071412510113200215010ustar00rootroot00000000000000from typing import NamedTuple, Optional, Text, Union from os.path import join from functools import partial from h5py import Dataset, File, Group from ..util import as_string # Generic hdf5 access types. class DatasetPathContains(NamedTuple): path: Text class DatasetPathWithAttribute(NamedTuple): attribute: Text value: bytes class HItem(NamedTuple): name: Text optional: bool DatasetPath = Union[DatasetPathContains, DatasetPathWithAttribute, HItem] class DatasetPathOr(NamedTuple): path1: DatasetPath path2: DatasetPath def _v_attrs(attribute: Text, value: Text, _name: Text, obj) -> Dataset: """visite each node and check the attribute value""" if isinstance(obj, Dataset): if attribute in obj.attrs and obj.attrs[attribute] == value: return obj def _v_item(key: Text, name: Text, obj: Dataset) -> Dataset: """visite each node and check that the path contain the key""" if key in name: return obj def get_dataset(h5file: File, path: DatasetPath) -> Optional[Dataset]: res = None if isinstance(path, DatasetPathContains): res = h5file.visititems(partial(_v_item, path.path)) elif isinstance(path, DatasetPathWithAttribute): res = h5file.visititems(partial(_v_attrs, path.attribute, path.value)) elif isinstance(path, HItem): res = h5file.visititems(partial(_v_item, join("scan_data", path.name))) if not path.optional and res is None: raise Exception("Can not find : {}".format(path)) elif isinstance(path, DatasetPathOr): res = get_dataset(h5file, path.path1) if res is None: res = get_dataset(h5file, path.path2) return res # tables here... class GroupPathWithAttribute(NamedTuple): attribute: Text value: bytes class GroupPathNxClass(NamedTuple): value: bytes GroupPath = Union[GroupPathWithAttribute, GroupPathNxClass, str] def _g_attrs(attribute: Text, value: Text, _name: Text, obj) -> Optional[Group]: """visite each node and check the attribute value""" if isinstance(obj, Group): if attribute in obj.attrs and obj.attrs[attribute] == value: return obj return None def get_nxclass(h5file: File, gpath: GroupPath) -> Optional[Group]: res = None if isinstance(gpath, GroupPathWithAttribute): res = h5file.visititems(partial(_g_attrs, gpath.attribute, gpath.value)) elif isinstance(gpath, GroupPathNxClass): res = h5file.visititems(partial(_g_attrs, "NX_class", gpath.value)) elif isinstance(gpath, str): res = h5file.visititems(partial(_g_attrs, "NX_class", gpath.encode())) return res def node_as_string(node): if node.shape == (): content = node else: content = node[0] return as_string(content) binoculars-0.0.10/binoculars/dispatcher.py000077500000000000000000000340651412510113200205730ustar00rootroot00000000000000from typing import Any, Dict, Tuple import os import time import itertools import subprocess import multiprocessing from . import util, errors, space class Destination(object): type = filename = overwrite = value = config = limits = None opts = {} # type: Dict[Any, Any] def set_final_filename(self, filename, overwrite): self.type = "final" self.filename = filename self.overwrite = overwrite def set_final_options(self, opts): if opts is not False: self.opts = opts def set_limits(self, limits): self.limits = limits def set_config(self, conf): self.config = conf def set_tmp_filename(self, filename): self.type = "tmp" self.filename = filename def set_memory(self): self.type = "memory" def store(self, verse): self.value = None if verse.dimension == 0: raise ValueError("Empty output, Multiverse contains no spaces") if self.type == "memory": self.value = verse elif self.type == "tmp": verse.tofile(self.filename) # verse.tovti(self.filename + ".vti") elif self.type == "final": for sp, fn in zip(verse.spaces, self.final_filenames()): sp.config = self.config sp.tofile(fn) # sp.tovti(fn + ".vti") def retrieve(self): if self.type == "memory": return self.value def final_filenames(self): fns = [] if self.limits is not None: base, ext = os.path.splitext(self.filename) for limlabel in util.limit_to_filelabel(self.limits): fn = (base + "_" + limlabel + ext).format(**self.opts) if not self.overwrite: fn = util.find_unused_filename(fn) fns.append(fn) else: fn = self.filename.format(**self.opts) if not self.overwrite: fn = util.find_unused_filename(fn) fns.append(fn) return fns class DispatcherBase(util.ConfigurableObject): def __init__(self, config, main): self.main = main super(DispatcherBase, self).__init__(config) def parse_config(self, config): super(DispatcherBase, self).parse_config(config) self.config.destination = Destination() # optional 'output.hdf5' by default destination = config.pop("destination", "output.hdf5") # by default: numbered files in the form output_ # .hdf5: overwrite = util.parse_bool(config.pop("overwrite", "false")) # explicitly parsing the options first helps with the debugging self.config.destination.set_final_filename(destination, overwrite) # ip adress of the running gui awaiting the spaces self.config.host = config.pop("host", None) # port of the running gui awaiting the spaces self.config.port = config.pop("port", None) # previewing the data, if true, also specify host and port self.config.send_to_gui = util.parse_bool(config.pop("send_to_gui", "false")) # provides the possiblity to send the results to the gui over the network def send(self, verses): if self.config.send_to_gui or ( self.config.host is not None and self.config.host is not None ): # only continue of ip is specified and send_to_server is flagged for M in verses: if self.config.destination.limits is None: sp = M.spaces[0] if isinstance(sp, space.Space): util.socket_send( self.config.host, int(self.config.port), util.serialize(sp, ",".join(self.main.config.command)), ) # noqa else: for sp, label in zip( M.spaces, util.limit_to_filelabel(self.config.destination.limits), ): # noqa if isinstance(sp, space.Space): util.socket_send( self.config.host, int(self.config.port), util.serialize( sp, "{0}_{1}".format( ",".join(self.main.config.command), label ), ), ) # noqa yield M else: for M in verses: yield M def has_specific_task(self): return False def process_jobs(self, jobs): raise NotImplementedError def sum(self, results): raise NotImplementedError # The simplest possible dispatcher. Does the work all by itself on a single # thread/core/node. 'Local' will most likely suit your needs better. class SingleCore(DispatcherBase): def process_jobs(self, jobs): for job in jobs: yield self.main.process_job(job) def sum(self, results): return space.chunked_sum(self.send(results)) # Base class for Dispatchers using subprocesses to do some work. class ReentrantBase(DispatcherBase): actions = ("user",) # type: Tuple[str, ...] def parse_config(self, config): super(ReentrantBase, self).parse_config(config) self.config.action = config.pop("action", "user").lower() if self.config.action not in self.actions: raise errors.ConfigError( "action {0} not recognized for {1}".format( self.config.action, self.__class__.__name__ ) ) # noqa def has_specific_task(self): if self.config.action == "user": return False else: return True def run_specific_task(self, command): raise NotImplementedError # Dispatch multiple worker processes locally, while doing the # summation in the main process class Local(ReentrantBase): # OFFICIAL API actions = "user", "job" def parse_config(self, config): super(Local, self).parse_config(config) # optionally, specify number of cores (autodetect by default) self.config.ncores = int(config.pop("ncores", 0)) if self.config.ncores <= 0: self.config.ncores = multiprocessing.cpu_count() def process_jobs(self, jobs): # note: SingleCore will be marginally faster pool = multiprocessing.Pool(self.config.ncores) map = pool.imap_unordered configs = (self.prepare_config(job) for job in jobs) for result in map(self.main.get_reentrant(), configs): yield result def sum(self, results): return space.chunked_sum(self.send(results)) def run_specific_task(self, command): if command: raise errors.SubprocessError( "invalid command, too many parameters: '{0}'".format(command) ) # noqa if self.config.action == "job": result = self.main.process_job(self.config.job) self.config.destination.store(result) # UTILITY def prepare_config(self, job): config = self.main.clone_config() config.dispatcher.destination.set_memory() config.dispatcher.action = "job" config.dispatcher.job = job return config, () # Dispatch many worker processes on an Oar cluster. class Oar(ReentrantBase): # OFFICIAL API actions = "user", "process" def parse_config(self, config): super(Oar, self).parse_config(config) # Optional, current directory by default self.config.tmpdir = config.pop("tmpdir", os.getcwd()) # optionally, tweak oarsub parameters self.config.oarsub_options = config.pop("oarsub_options", "walltime=0:15") # optionally, override default location of python and/or # BINoculars installation self.config.executable = config.pop( "executable", " ".join(util.get_python_executable()) ) # noqa def process_jobs(self, jobs): self.configfiles = [] self.intermediates = [] clusters = util.cluster_jobs2(jobs, self.main.input.config.target_weight) for jobscluster in clusters: uniq = util.uniqid() jobconfig = os.path.join( self.config.tmpdir, "binoculars-{0}-jobcfg.zpi".format(uniq) ) self.configfiles.append(jobconfig) config = self.main.clone_config() interm = os.path.join( self.config.tmpdir, "binoculars-{0}-jobout.hdf5".format(uniq) ) self.intermediates.append(interm) config.dispatcher.destination.set_tmp_filename(interm) config.dispatcher.sum = () config.dispatcher.action = "process" config.dispatcher.jobs = jobscluster util.zpi_save(config, jobconfig) yield self.oarsub(jobconfig) # if all jobs are sent to the cluster send the process that # sums all other jobs uniq = util.uniqid() jobconfig = os.path.join( self.config.tmpdir, "binoculars-{0}-jobcfg.zpi".format(uniq) ) self.configfiles.append(jobconfig) config = self.main.clone_config() config.dispatcher.sum = self.intermediates config.dispatcher.action = "process" config.dispatcher.jobs = () util.zpi_save(config, jobconfig) yield self.oarsub(jobconfig) def sum(self, results): jobs = list(results) jobscopy = jobs[:] self.oarwait(jobs) self.oar_cleanup(jobscopy) return True def run_specific_task(self, command): if ( self.config.action != "process" or (not self.config.jobs and not self.config.sum) or command ): raise errors.SubprocessError( "invalid command, too many parameters or no jobs/sum given" ) # noqa jobs = sum = space.EmptyVerse() if self.config.jobs: jobs = space.verse_sum( self.send(self.main.process_job(job) for job in self.config.jobs) ) if self.config.sum: sum = space.chunked_sum( space.Multiverse.fromfile(src) for src in util.yield_when_exists(self.config.sum) ) # noqa self.config.destination.store(jobs + sum) # calling OAR @staticmethod def subprocess_run(*command): process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) output, unused_err = process.communicate() retcode = process.poll() return retcode, output def oarsub(self, *args): command = "{0} process {1}".format( self.config.executable, " ".join(args) ) # noqa ret, output = self.subprocess_run( "oarsub", "-l {0}".format(self.config.oarsub_options), command ) # noqa if ret == 0: lines = output.split("\n") for line in lines: if line.startswith("OAR_JOB_ID="): void, jobid = line.split("=") util.status( "{0}: Launched job {1}".format(time.ctime(), jobid) ) # noqa return jobid.strip() return False def oarstat(self, jobid): # % oarstat -s -j 5651374 # 5651374: Running # % oarstat -s -j 5651374 # 5651374: Finishing ret, output = self.subprocess_run("oarstat", "-s", "-j", str(jobid)) if ret == 0: for n in output.split("\n"): if n.startswith(str(jobid)): job, status = n.split(":") return status.strip() else: return "Unknown" def oarwait(self, jobs, remaining=0): if len(jobs) > remaining: util.status( "{0}: getting status of {1} jobs...".format(time.ctime(), len(jobs)) ) # noqa else: return delay = util.loop_delayer(30) while len(jobs) > remaining: next(delay) i = 0 R = 0 W = 0 U = 0 while i < len(jobs): state = self.oarstat(jobs[i]) if state == "Running": R += 1 elif state in ("Waiting", "toLaunch", "Launching"): W += 1 elif state == "Unknown": U += 1 else: # assume state == 'Finishing' or 'Terminated' # but don't wait on something unknown # noqa del jobs[i] i -= 1 # otherwise it skips a job i += 1 util.status( "{0}: {1} jobs to go. {2} waiting, {3} running, {4} unknown.".format( time.ctime(), len(jobs), W, R, U ) ) # noqa util.statuseol() def oar_cleanup(self, jobs): # cleanup: for f in itertools.chain(self.configfiles, self.intermediates): try: os.remove(f) except Exception as e: print("unable to remove {0}: {1}".format(f, e)) errorfn = [] for jobid in jobs: errorfilename = "OAR.{0}.stderr".format(jobid) if os.path.exists(errorfilename): with open(errorfilename, "r") as fp: errormsg = fp.read() if len(errormsg) > 0: errorfn.append(errorfilename) print( "Critical error: OAR Job {0} failed with the following error: \n{1}".format( jobid, errormsg ) ) # noqa if len(errorfn) > 0: print( "Warning! {0} job(s) failed. See above for the details or the error log files: {1}".format( len(errorfn), ", ".join(errorfn) ) ) # noqa binoculars-0.0.10/binoculars/errors.py000066400000000000000000000010441412510113200177450ustar00rootroot00000000000000# TODO: present exceptions based on errors.ExceptionBase in a gentle way to the user class ExceptionBase(Exception): pass class ConfigError(ExceptionBase): pass class FileError(ExceptionBase): pass class HDF5FileError(FileError): pass class SubprocessError(ExceptionBase): pass class BackendError(ExceptionBase): pass class CommunicationError(ExceptionBase): pass def addmessage(args, errormsg): if not args: arg0 = "" else: arg0 = args[0] arg0 += errormsg return (arg0,) binoculars-0.0.10/binoculars/fit.py000066400000000000000000000257561412510113200172330ustar00rootroot00000000000000import numpy import scipy.optimize import scipy.special import inspect import re class FitBase(object): parameters = None guess = None result = None summary = None fitdata = None def __init__(self, space, guess=None): self.space = space code = inspect.getsource(self.func) args = tuple( re.findall("\((.*?)\)", line)[0].split(",") for line in code.split("\n")[2:4] ) if space.dimension != len(args[0]): raise ValueError( "dimension mismatch: space has {0}, {1.__class__.__name__} expects {2}".format( space.dimension, self, len(args[0]) ) ) self.parameters = args[1] self.xdata, self.ydata, self.cxdata, self.cydata = self._prepare(self.space) if guess is not None: if len(guess) != len(self.parameters): raise ValueError( "invalid number of guess parameters {0!r} for {1!r}".format( guess, self.parameters ) ) self.guess = guess else: self._guess() self.success = self._fit() @staticmethod def _prepare(space): ydata = space.get_masked() cydata = ydata.compressed() imask = ~ydata.mask xdata = space.get_grid() cxdata = tuple(d[imask] for d in xdata) return xdata, ydata, cxdata, cydata def _guess(self): # the implementation should use space and/or self.xdata/self.ydata and/or the cxdata/cydata maskless versions to obtain guess raise NotImplementedError def _fitfunc(self, params): return self.cydata - self.func(self.cxdata, params) def _fit(self): result = scipy.optimize.leastsq( self._fitfunc, self.guess, full_output=True, epsfcn=0.000001 ) self.message = re.sub("\s{2,}", " ", result[3].strip()) self.result = result[0] errdata = result[2]["fvec"] if result[1] is None: self.variance = numpy.zeros(len(self.result)) else: self.variance = numpy.diagonal( result[1] * (errdata ** 2).sum() / (len(errdata) - len(self.result)) ) self.fitdata = numpy.ma.array( self.func(self.xdata, self.result), mask=self.ydata.mask ) self.summary = "\n".join( "%s: %.4g +/- %.4g" % (n, p, v) for (n, p, v) in zip(self.parameters, self.result, self.variance) ) return result[4] in ( 1, 2, 3, 4, ) # corresponds to True on success, False on failure def __str__(self): return "{0.__class__.__name__} fit on {1}\n{2}\n{3}".format( self, self.space, self.message, self.summary ) class PeakFitBase(FitBase): def __init__(self, space, guess=None, loc=None): if loc != None: self.argmax = tuple(loc) else: self.argmax = None super(PeakFitBase, self).__init__(space, guess) def _guess(self): maximum = self.cydata.max() # for background determination background = self.cydata < (numpy.median(self.cydata) + maximum) / 2 if any(background == True): # the fit will fail if background is flas for all linparams = self._linfit( list(grid[background] for grid in self.cxdata), self.cydata[background] ) else: linparams = numpy.zeros(len(self.cxdata) + 1) simbackground = linparams[-1] + numpy.sum( numpy.vstack( [ param * grid.flatten() for (param, grid) in zip(linparams[:-1], self.cxdata) ] ), axis=0, ) signal = self.cydata - simbackground if self.argmax != None: argmax = self.argmax else: argmax = tuple((signal * grid).sum() / signal.sum() for grid in self.cxdata) argmax_bkg = linparams[-1] + numpy.sum( numpy.vstack( [ param * grid.flatten() for (param, grid) in zip(linparams[:-1], argmax) ] ) ) try: maximum = self.space[argmax] - argmax_bkg except ValueError: maximum = self.cydata.max() if numpy.isnan(maximum): maximum = self.cydata.max() self.set_guess(maximum, argmax, linparams) def _linfit(self, coordinates, intensity): coordinates = list(coordinates) coordinates.append(numpy.ones_like(coordinates[0])) matrix = numpy.vstack([coords.flatten() for coords in coordinates]).T return numpy.linalg.lstsq(matrix, intensity, rcond=None)[0] class AutoDimensionFit(FitBase): def __new__(cls, space, guess=None): if space.dimension in cls.dimensions: return cls.dimensions[space.dimension](space, guess) else: raise TypeError( "{0}-dimensional space not supported for {1.__name__}".format( space.dimension, cls ) ) # utility functions def rot2d(x, y, th): xrot = x * numpy.cos(th) + y * numpy.sin(th) yrot = -x * numpy.sin(th) + y * numpy.cos(th) return xrot, yrot def rot3d(x, y, z, th, ph): xrot = ( numpy.cos(th) * x + numpy.sin(th) * numpy.sin(ph) * y + numpy.sin(th) * numpy.cos(ph) * z ) yrot = numpy.cos(ph) * y - numpy.sin(ph) * z zrot = ( -numpy.sin(th) * x + numpy.cos(th) * numpy.sin(ph) * y + numpy.cos(th) * numpy.cos(ph) * z ) return xrot, yrot, zrot def get_class_by_name(name): options = {} for k, v in globals().items(): if isinstance(v, type) and issubclass(v, FitBase): options[k.lower()] = v if name.lower() in options: return options[name.lower()] else: raise ValueError("unsupported fit function '{0}'".format(name)) # fitting functions class Lorentzian1D(PeakFitBase): @staticmethod def func(grid, params): (x,) = grid (I, loc, gamma, slope, offset) = params return I / ((x - loc) ** 2 + gamma ** 2) + offset + x * slope def set_guess(self, maximum, argmax, linparams): gamma0 = 5 * self.space.axes[0].res # estimated FWHM on 10 pixels self.guess = [maximum, argmax[0], gamma0, linparams[0], linparams[1]] class Lorentzian1DNoBkg(PeakFitBase): @staticmethod def func(grid, params): (x,) = grid (I, loc, gamma) = undefined # TODO return I / ((x - loc) ** 2 + gamma ** 2) def set_guess(self, maximum, argmax, linparams): gamma0 = 5 * self.space.axes[0].res # estimated FWHM on 10 pixels self.guess = [maximum, argmax[0], gamma0] class PolarLorentzian2Dnobkg(PeakFitBase): @staticmethod def func(grid, params): (x, y) = grid (I, loc0, loc1, gamma0, gamma1, th) = params a, b = tuple( grid - center for grid, center in zip(rot2d(x, y, th), rot2d(loc0, loc1, th)) ) return I / (1 + (a / gamma0) ** 2 + (b / gamma1) ** 2) def set_guess(self, maximum, argmax, linparams): gamma0 = self.space.axes[0].res # estimated FWHM on 10 pixels gamma1 = self.space.axes[1].res self.guess = [maximum, argmax[0], argmax[1], gamma0, gamma1, 0] class PolarLorentzian2D(PeakFitBase): @staticmethod def func(grid, params): (x, y) = grid (I, loc0, loc1, gamma0, gamma1, th, slope1, slope2, offset) = params a, b = tuple( grid - center for grid, center in zip(rot2d(x, y, th), rot2d(loc0, loc1, th)) ) return ( I / (1 + (a / gamma0) ** 2 + (b / gamma1) ** 2) + x * slope1 + y * slope2 + offset ) def set_guess(self, maximum, argmax, linparams): gamma0 = self.space.axes[0].res # estimated FWHM on 10 pixels gamma1 = self.space.axes[1].res self.guess = [ maximum, argmax[0], argmax[1], gamma0, gamma1, 0, linparams[0], linparams[1], linparams[2], ] def integrate_signal(self): return self.func( self.cxdata, ( self.result[0], self.result[1], self.result[2], self.result[3], self.result[4], self.result[5], 0, 0, 0, ), ).sum() class Lorentzian2D(PeakFitBase): @staticmethod def func(grid, params): (x, y) = grid (I, loc0, loc1, gamma0, gamma1, th, slope1, slope2, offset) = params a, b = tuple( grid - center for grid, center in zip(rot2d(x, y, th), rot2d(loc0, loc1, th)) ) return ( I / (1 + (a / gamma0) ** 2) * 1 / (1 + (b / gamma1) ** 2) + x * slope1 + y * slope2 + offset ) def set_guess(self, maximum, argmax, linparams): gamma0 = 5 * self.space.axes[0].res # estimated FWHM on 10 pixels gamma1 = 5 * self.space.axes[1].res self.guess = [ maximum, argmax[0], argmax[1], gamma0, gamma1, 0, linparams[0], linparams[1], linparams[2], ] class Lorentzian2Dnobkg(PeakFitBase): @staticmethod def func(grid, params): (x, y) = grid (I, loc0, loc1, gamma0, gamma1, th) = params a, b = tuple( grid - center for grid, center in zip(rot2d(x, y, th), rot2d(loc0, loc1, th)) ) return I / (1 + (a / gamma0) ** 2) * 1 / (1 + (b / gamma1) ** 2) def set_guess(self, maximum, argmax, linparams): gamma0 = 5 * self.space.axes[0].res # estimated FWHM on 10 pixels gamma1 = 5 * self.space.axes[1].res self.guess = [maximum, argmax[0], argmax[1], gamma0, gamma1, 0] class Lorentzian(AutoDimensionFit): dimensions = {1: Lorentzian1D, 2: PolarLorentzian2D} class Gaussian1D(PeakFitBase): @staticmethod def func(grid, params): (x,) = grid (loc, I, sigma, offset, slope) = params return I * numpy.exp(-(((x - loc) / sigma) ** 2) / 2) + offset + x * slope class Voigt1D(PeakFitBase): @staticmethod def func(grid, params): (x,) = grid (I, loc, sigma, gamma, slope, offset) = params z = (x - loc + numpy.complex(0, gamma)) / (sigma * numpy.sqrt(2)) return ( I * numpy.real(scipy.special.wofz(z)) / (sigma * numpy.sqrt(2 * numpy.pi)) + offset + x * slope ) def set_guess(self, maximum, argmax, linparams): gamma0 = 5 * self.space.axes[0].res # estimated FWHM on 10 pixels self.guess = [maximum, argmax[0], 0.01, gamma0, linparams[0], linparams[1]] binoculars-0.0.10/binoculars/main.py000077500000000000000000000164101412510113200173630ustar00rootroot00000000000000import os import sys import argparse from . import space, backend, util, errors def parse_args(args): parser = argparse.ArgumentParser(prog="binoculars process") parser.add_argument( "-c", metavar="SECTION:OPTION=VALUE", action="append", type=parse_commandline_config_option, default=[], help="additional configuration option in the form section:option=value", ) # noqa parser.add_argument("configfile", help="configuration file") parser.add_argument("command", nargs="*", default=[]) return parser.parse_args(args) def parse_commandline_config_option(s): try: key, value = s.split("=", 1) section, option = key.split(":") except ValueError: raise argparse.ArgumentTypeError( "configuration specification '{0}' not in the form section:option=value".format( s ) ) # noqa return section, option, value def multiprocessing_main(xxx_todo_changeme): """note the double parenthesis for map() convenience""" (config, command) = xxx_todo_changeme Main.from_object(config, command) return config.dispatcher.destination.retrieve() class Main(object): def __init__(self, config, command): if isinstance(config, util.ConfigSectionGroup): self.config = config.configfile.copy() elif isinstance(config, util.ConfigFile): self.config = config.copy() else: raise ValueError("Configfile is the wrong type") # distribute the configfile to space and to the metadata # instance spaceconf = self.config.copy() # input from either the configfile or the configsectiongroup # is valid self.dispatcher = backend.get_dispatcher( config.dispatcher, self, default="local" ) self.projection = backend.get_projection(config.projection) self.input = backend.get_input(config.input) self.dispatcher.config.destination.set_final_options( self.input.get_destination_options(command) ) # noqa if "limits" in self.config.projection: self.dispatcher.config.destination.set_limits( self.config.projection["limits"] ) # noqa if command: self.dispatcher.config.destination.set_config(spaceconf) self.run(command) @classmethod def from_args(cls, args): args = parse_args(args) if not os.path.exists(args.configfile): # wait up to 10 seconds if it is a zpi, it might take a # while for the file to appear accross the network if not args.configfile.endswith(".zpi") or not util.wait_for_file( args.configfile, 10 ): # noqa raise errors.FileError( "configuration file '{0}' does not exist".format( # noqa args.configfile ) ) configobj = False with open(args.configfile, "rb") as fp: if fp.read(2) == "\x1f\x8b": # gzip marker fp.seek(0) configobj = util.zpi_load(fp) if not configobj: # reopen args.configfile as text configobj = util.ConfigFile.fromtxtfile( args.configfile, command=args.command, overrides=args.c ) return cls(configobj, args.command) @classmethod def from_object(cls, config, command): config.command = command return cls(config, command) def run(self, command): if self.dispatcher.has_specific_task(): self.dispatcher.run_specific_task(command) else: jobs = self.input.generate_jobs(command) tokens = self.dispatcher.process_jobs(jobs) self.result = self.dispatcher.sum(tokens) if self.result is True: pass elif isinstance(self.result, space.EmptySpace): sys.stderr.write("error: output is an empty dataset\n") else: self.dispatcher.config.destination.store(self.result) def process_job(self, job): def generator(): res = self.projection.config.resolution labels = self.projection.get_axis_labels() for intensity, weights, params in self.input.process_job(job): coords = self.projection.project(*params) if self.projection.config.limits is None: yield space.Multiverse( ( space.Space.from_image( res, labels, coords, intensity, weights=weights ), ) ) # noqa else: yield space.Multiverse( space.Space.from_image( res, labels, coords, intensity, weights=weights, limits=limits, ) for limits in self.projection.config.limits ) # noqa jobverse = space.chunked_sum(generator(), chunksize=25) for sp in jobverse.spaces: if isinstance(sp, space.Space): sp.metadata.add_dataset(self.input.metadata) return jobverse def clone_config(self): config = util.ConfigSectionGroup() config.configfile = self.config config.dispatcher = self.dispatcher.config.copy() config.projection = self.projection.config.copy() config.input = self.input.config.copy() return config def get_reentrant(self): return multiprocessing_main class Split(Main): # completely ignores the dispatcher, just yields a space per image def __init__(self, config, command): self.command = command if isinstance(config, util.ConfigSectionGroup): self.config = config.configfile.copy() elif isinstance(config, util.ConfigFile): self.config = config.copy() else: raise ValueError("Configfile is the wrong type") # input from either the configfile or the configsectiongroup is valid self.projection = backend.get_projection(config.projection) self.input = backend.get_input(config.input) def process_job(self, job): res = self.projection.config.resolution labels = self.projection.get_axis_labels() for intensity, weights, params in self.input.process_job(job): coords = self.projection.project(*params) if self.projection.config.limits is None: yield space.Space.from_image( res, labels, coords, intensity, weights=weights ) # noqa else: yield space.Multiverse( space.Space.from_image( res, labels, coords, intensity, weights=weights, limits=limits ) for limits in self.projection.config.limits ) # noqa def run(self): for job in self.input.generate_jobs(self.command): for verse in self.process_job(job): yield verse binoculars-0.0.10/binoculars/plot.py000066400000000000000000000206041412510113200174120ustar00rootroot00000000000000from typing import List, Optional, Union import numpy import matplotlib.colors import matplotlib.cm import mpl_toolkits.mplot3d from matplotlib.axes import Axes from matplotlib.figure import Figure from matplotlib.lines import Line2D from binoculars.space import Space # Adapted from http://www.ster.kuleuven.be/~pieterd/python/html/plotting/interactive_colorbar.html # which in turn is based on an example from http://matplotlib.org/users/event_handling.html class DraggableColorbar(object): def __init__(self, cbar, mappable): self.cbar = cbar self.mappable = mappable self.press = None self.cycle = sorted( [i for i in dir(matplotlib.cm) if hasattr(getattr(matplotlib.cm, i), "N")] ) try: # matploltib 2.x cmap_name = cbar.get_cmap().name except AttributeError: # matplotlib 3.x cmap_name = mappable.get_cmap().name self.index = self.cycle.index(cmap_name) self.canvas = self.cbar.patch.figure.canvas def connect(self): self.cidpress = self.canvas.mpl_connect("button_press_event", self.on_press) self.cidrelease = self.canvas.mpl_connect( "button_release_event", self.on_release ) self.cidmotion = self.canvas.mpl_connect("motion_notify_event", self.on_motion) self.cidkeypress = self.canvas.mpl_connect("key_press_event", self.key_press) def disconnect(self): self.canvas.mpl_disconnect(self.cidpress) self.canvas.mpl_disconnect(self.cidrelease) self.canvas.mpl_disconnect(self.cidmotion) self.canvas.mpl_disconnect(self.cidkeypress) def on_press(self, event): if event.inaxes == self.cbar.ax: self.press = event.x, event.y def key_press(self, event): if event.key == "down": self.index += 1 elif event.key == "up": self.index -= 1 if self.index < 0: self.index = len(self.cycle) elif self.index >= len(self.cycle): self.index = 0 cmap = self.cycle[self.index] self.mappable.set_cmap(cmap) self.cbar.patch.figure.canvas.draw() def on_motion(self, event): if self.press is None or event.inaxes != self.cbar.ax: return xprev, yprev = self.press # dx = event.x - xprev # unused for now dy = event.y - yprev self.press = event.x, event.y if isinstance(self.cbar.norm, matplotlib.colors.LogNorm): scale = 0.999 * numpy.log10(self.cbar.norm.vmax / self.cbar.norm.vmin) if event.button == 1: self.cbar.norm.vmin *= scale ** numpy.sign(dy) self.cbar.norm.vmax *= scale ** numpy.sign(dy) elif event.button == 3: self.cbar.norm.vmin *= scale ** numpy.sign(dy) self.cbar.norm.vmax /= scale ** numpy.sign(dy) else: scale = 0.03 * (self.cbar.norm.vmax - self.cbar.norm.vmin) if event.button == 1: self.cbar.norm.vmin -= scale * numpy.sign(dy) self.cbar.norm.vmax -= scale * numpy.sign(dy) elif event.button == 3: self.cbar.norm.vmin -= scale * numpy.sign(dy) self.cbar.norm.vmax += scale * numpy.sign(dy) self.mappable.set_norm(self.cbar.norm) self.canvas.draw() def on_release(self, event): # force redraw on mouse release self.press = None self.mappable.set_norm(self.cbar.norm) self.canvas.draw() def get_clipped_norm(data, clipping=0.0, log=True): if hasattr(data, "compressed"): data = data.compressed() else: data = data.flatten() if log: data = data[data > 0] if numpy.alen(data) == 0: return matplotlib.colors.LogNorm(1, 10) if clipping: chop = int(round(data.size * clipping)) clip = sorted(data)[chop : -(1 + chop)] vmin, vmax = clip[0], clip[-1] else: vmin, vmax = data.min(), data.max() if log: return matplotlib.colors.LogNorm(vmin, vmax) else: return matplotlib.colors.Normalize(vmin, vmax) def plot( space : Space, fig: Figure, ax : Axes, log: bool=True, loglog: bool=False, clipping: float=0.0, fit: Optional[bool]=None, norm: Optional[float]=None, colorbar: bool=True, labels: bool=True, interpolation: str="nearest", **plotopts ) -> Union[List[Line2D]]: if space.dimension == 1: data = space.get_masked() xrange = numpy.ma.array(space.axes[0][:], mask=data.mask) if fit is not None: if log: p1 = ax.semilogy(xrange, data, "wo", **plotopts) p2 = ax.semilogy(xrange, fit, "r", linewidth=2, **plotopts) elif loglog: p1 = ax.loglog(xrange, data, "wo", **plotopts) p2 = ax.loglog(xrange, fit, "r", linewidth=2, **plotopts) else: p1 = ax.plot(xrange, data, "wo", **plotopts) p2 = ax.plot(xrange, fit, "r", linewidth=2, **plotopts) else: if log: p1 = ax.semilogy(xrange, data, **plotopts) elif loglog: p1 = ax.loglog(xrange, data, **plotopts) else: p1 = ax.plot(xrange, data, **plotopts) p2 = [] if labels: ax.set_xlabel(space.axes[0].label) ax.set_ylabel("Intensity (a.u.)") return p1 + p2 elif space.dimension == 2: data = space.get_masked() # 2D IMSHOW PLOT xmin = space.axes[0].min xmax = space.axes[0].max ymin = space.axes[1].min ymax = space.axes[1].max if not norm: norm = get_clipped_norm(data, clipping, log) if fit is not None: im = ax.imshow( fit.transpose(), origin="lower", extent=(xmin, xmax, ymin, ymax), aspect="auto", norm=norm, interpolation=interpolation, **plotopts ) else: im = ax.imshow( data.transpose(), origin="lower", extent=(xmin, xmax, ymin, ymax), aspect="auto", norm=norm, interpolation=interpolation, **plotopts ) if labels: ax.set_xlabel(space.axes[0].label) ax.set_ylabel(space.axes[1].label) if colorbar: cbarwidget = fig.colorbar(im) fig._draggablecbar = DraggableColorbar( cbarwidget, im ) # we need to store this instance somewhere fig._draggablecbar.connect() return im elif space.dimension == 3: if not isinstance(ax, mpl_toolkits.mplot3d.Axes3D): raise ValueError( "For 3D plots, the 'ax' parameter must be an Axes3D instance (use for example gca(projection='3d') to get one)" ) cmap = getattr(matplotlib.cm, plotopts.pop("cmap", "jet")) if norm is None: norm = get_clipped_norm(space.get_masked(), clipping, log) data = space.get() mask = numpy.bitwise_or(~numpy.isfinite(data), data == 0) gridx, gridy, gridz = tuple(grid[~mask] for grid in space.get_grid()) im = ax.scatter( gridx, gridy, gridz, c=cmap(norm(data[~mask])), marker=",", alpha=0.7, linewidths=0, ) # p1 = ax.plot_surface(gridx[0,:,:], gridy[0,:,:], gridz[0,:,:], facecolors=cmap(norm(space.project(0).get_masked())), shade=False, cstride=1, rstride=1) # p2 = ax.plot_surface(gridx[:,-1,:], gridy[:,-1,:], gridz[:,-1,:], facecolors=cmap(norm(space.project(1).get_masked())), shade=False, cstride=1, rstride=1) # p3 = ax.plot_surface(gridx[:,:,0], gridy[:,:,0], gridz[:,:,0], facecolors=cmap(norm(space.project(2).get_masked())), shade=False, cstride=1, rstride=1) if labels: ax.set_xlabel(space.axes[0].label) ax.set_ylabel(space.axes[1].label) ax.set_zlabel(space.axes[2].label) if fig._draggablecbar: fig._draggablecbar.disconnect() return im elif space.dimension > 3: raise ValueError( "Cannot plot 4 or higher dimensional spaces, use projections or slices to decrease dimensionality." ) binoculars-0.0.10/binoculars/space.py000077500000000000000000001222161412510113200175340ustar00rootroot00000000000000from typing import Generator, Iterable, List, Optional, Sequence, Tuple, Union import math import numpy from functools import reduce from itertools import chain from numbers import Number from numpy import ndarray from numpy.ma import MaskedArray from vtk import vtkImageData, vtkXMLImageDataWriter from vtk.util import numpy_support from . import util, errors basestring = (str, bytes) def silence_numpy_errors(): """Silence numpy warnings about zero division. Normal usage of Space() will trigger these warnings.""" numpy.seterr(divide="ignore", invalid="ignore") def sum_onto(a, axis): """Numpy convenience. Project all dimensions of an array onto an axis, i.e. apply sum() to all axes except the one given.""" for i in reversed(list(range(len(a.shape)))): if axis != i: a = a.sum(axis=i) return a class Axis(object): """Represents a single dimension finite discrete grid centered at 0. Important attributes: min lower bound max upper bound res step size / resolution label human-readable identifier min, max and res are floats, but internally only integer operations are used. In particular min = imin * res, max = imax * res """ def __init__(self, min, max, res, label=None): self.res = float(res) if isinstance(min, int): self.imin = min else: self.imin = math.floor(min / self.res) if isinstance(max, int): self.imax = max else: self.imax = math.ceil(max / self.res) self.label = label @property def max(self): return self.imax * self.res @property def min(self): return self.imin * self.res def __len__(self): return self.imax - self.imin + 1 def __iter__(self): return iter(self[index] for index in range(len(self))) def __getitem__(self, key): if isinstance(key, slice): if key.step is not None: raise IndexError("stride not supported") if key.start is None: start = 0 elif key.start < 0: raise IndexError("key out of range") elif isinstance(key.start, int): start = key.start else: raise IndexError("key start must be integer") if key.stop is None: stop = len(self) elif key.stop > len(self): raise IndexError("key out of range") elif isinstance(key.stop, int): stop = key.stop else: raise IndexError("slice stop must be integer") return self.__class__( self.imin + start, self.imin + stop - 1, self.res, self.label ) elif isinstance(key, int) or isinstance(key, numpy.int64): if key >= len(self): # to support iteration raise IndexError("key out of range") return (self.imin + key) * self.res else: raise IndexError("unknown key {0!r}".format(key)) def _get_index_float(self, value: float) -> int: intvalue = int(round(value / self.res)) if self.imin <= intvalue <= self.imax: return intvalue - self.imin raise ValueError( "cannot get index: value {0} not in range [{1}, {2}]".format( value, self.min, self.max ) ) def get_index(self, value: Union[float, slice, ndarray]) -> Union[int, slice, ndarray]: if isinstance(value, (int, float)): # nice mypy accept int as float but not isinstance return self._get_index_float(value) elif isinstance(value, slice): if value.step is not None: raise IndexError("stride not supported") if value.start is None: start = None else: start = self._get_index_float(value.start) if value.stop is None: stop = None else: stop = self._get_index_float(value.stop) if start is not None and stop is not None and start > stop: start, stop = stop, start return slice(start, stop) else: # TODO it seems that we have maskedarray here... intvalue = numpy.around(value / self.res).astype(int) if ((self.imin <= intvalue) & (intvalue <= self.imax)).all(): return intvalue - self.imin raise ValueError( "cannot get indices, values from [{0}, {1}], axes range [{2}, {3}]".format( value.min(), value.max(), self.min, self.max ) ) def __or__(self, other): # union operation if not isinstance(other, Axis): return NotImplemented if not self.is_compatible(other): raise ValueError("cannot unite axes with different resolution/label") return self.__class__( min(self.imin, other.imin), max(self.imax, other.imax), self.res, self.label ) def __eq__(self, other): if not isinstance(other, Axis): return NotImplemented return ( self.res == other.res and self.imin == other.imin and self.imax == other.imax and self.label == other.label ) def __hash__(self): return hash(self.imin) ^ hash(self.imax) ^ hash(self.res) ^ hash(self.label) def is_compatible(self, other): if not isinstance(other, Axis): return False return self.res == other.res and self.label == other.label def __contains__(self, other): if isinstance(other, Number): return self.min <= other <= self.max elif isinstance(other, Axis): return ( self.is_compatible(other) and self.imin <= other.imin and self.imax >= other.imax ) def rebound(self, min, max): return self.__class__(min, max, self.res, self.label) def rebin(self, factor): # for integers the following relations hold: a // b == floor(a / b), -(-a // b) == ceil(a / b) new = self.__class__( self.imin // factor, -(-self.imax // factor), factor * self.res, self.label ) return self.imin % factor, -self.imax % factor, new def __repr__(self): return "{0.__class__.__name__} {0.label} (min={0.min}, max={0.max}, res={0.res}, count={1})".format( self, len(self) ) def restrict_slice(self, value: slice) -> slice: # TODO rename into clip_slice if value.step is not None: raise IndexError("stride not supported") v = value.start # type: Optional[float] if value.start is None: start = None else: start = numpy.clip(value.start, self.min, self.max) if value.stop is None: stop = None else: stop = numpy.clip(value.stop, self.min, self.max) # TODO this produce a wrong slice sementic, the stop value can not be None. # if value.stop == self.max: # stop = None if start is not None and stop is not None and start > stop: start, stop = stop, start # TODO par of the code necessite that start and stop are not None. # this is not the usual slice semantic assert start is not None, f"1-{start}" assert stop is not None, f"2-{stop}" return slice(start, stop) def restrict(self, value: Union[float, slice]) -> Union[float, slice]: if isinstance(value, float): return numpy.clip(value, self.min, self.max) elif isinstance(value, slice): return self.restrict_slice(value) class Axes(object): """Luxurious tuple of Axis objects.""" def __init__(self, axes): self.axes = tuple(axes) if len(self.axes) > 1 and any(axis.label is None for axis in self.axes): raise ValueError("axis label is required for multidimensional space") def __iter__(self): return iter(self.axes) @property def dimension(self): return len(self.axes) @property def npoints(self): return numpy.array([len(ax) for ax in self.axes]).prod() @property def memory_size(self): # assuming double precision floats for photons, 32 bit integers for contributions return (8 + 4) * self.npoints @classmethod def fromfile(cls, filename: str) -> 'Axes': with util.open_h5py(filename, "r") as fp: try: if "axes" in fp and "axes_labels" in fp: # oldest style, float min/max return cls( tuple( Axis(min, max, res, lbl) for ((min, max, res), lbl) in zip( fp["axes"], fp["axes_labels"] ) ) ) elif "axes" in fp: # new try: axes = tuple( Axis(int(imin), int(imax), res, lbl) for ((index, fmin, fmax, res, imin, imax), lbl) in zip( fp["axes"].values(), fp["axes"].keys() ) ) return cls( tuple( axes[int(values[0])] for values in fp["axes"].values() ) ) # reorder the axes to the way in which they were saved except ValueError: return cls( tuple( Axis(int(imin), int(imax), res, lbl) for ((imin, imax, res), lbl) in zip( fp["axes"].values(), fp["axes"].keys() ) ) ) else: # older style, integer min/max return cls( tuple( Axis(imin, imax, res, lbl) for ((imin, imax), res, lbl) in zip( fp["axes_range"], fp["axes_res"], fp["axes_labels"] ) ) ) except (KeyError, TypeError) as e: raise errors.HDF5FileError( "unable to load axes definition from HDF5 file {0}, is it a valid BINoculars file? (original error: {1!r})".format( filename, e ) ) def tofile(self, filename): with util.open_h5py(filename, "w") as fp: axes = fp.create_group("axes") for index, ax in enumerate(self.axes): axes.create_dataset( ax.label, data=[index, ax.min, ax.max, ax.res, ax.imin, ax.imax] ) def toarray(self): return numpy.vstack( numpy.hstack([str(ax.imin), str(ax.imax), str(ax.res), ax.label]) for ax in self.axes ) @classmethod def fromarray(cls, arr): return cls( tuple( Axis(int(imin), int(imax), float(res), lbl) for (imin, imax, res, lbl) in arr ) ) def index(self, obj: Union[Axis, int, str]) -> int: if isinstance(obj, Axis): return self.axes.index(obj) elif isinstance(obj, int) and 0 <= obj < len(self.axes): return obj elif isinstance(obj, basestring): label = obj.lower() matches = tuple( i for i, axis in enumerate(self.axes) if axis.label.lower() == label ) if len(matches) == 0: raise ValueError("no matching axis found") elif len(matches) == 1: return matches[0] else: raise ValueError("ambiguous axis label {0}".format(label)) else: raise ValueError("invalid axis identifier {0!r}".format(obj)) def __contains__(self, obj): if isinstance(obj, Axis): return obj in self.axes elif isinstance(obj, int): return 0 <= obj < len(self.axes) elif isinstance(obj, basestring): label = obj.lower() return any(axis.label.lower() == label for axis in self.axes) else: raise ValueError("invalid axis identifier {0!r}".format(obj)) def __len__(self): return len(self.axes) def __getitem__(self, key): return self.axes[key] def __eq__(self, other): if not isinstance(other, Axes): return NotImplemented return self.axes == other.axes def __ne__(self, other): if not isinstance(other, Axes): return NotImplemented return self.axes != other.axes def __repr__(self): return "{0.__class__.__name__} ({0.dimension} dimensions, {0.npoints} points, {1}) {{\n {2}\n}}".format( self, util.format_bytes(self.memory_size), "\n ".join(repr(ax) for ax in self.axes), ) def restricted_key(self, key): if len(key) == 0: return None if len(key) == len(self.axes): return tuple(ax.restrict(s) for s, ax in zip(key, self.axes)) else: raise IndexError("dimension mismatch") class EmptySpace(object): """Convenience object for sum() and friends. Treated as zero for addition. Does not share a base class with Space for simplicity.""" def __init__(self, config=None, metadata=None): self.config = config self.metadata = metadata def __add__(self, other): if not isinstance(other, Space) and not isinstance(other, EmptySpace): return NotImplemented return other def __radd__(self, other): if not isinstance(other, Space) and not isinstance(other, EmptySpace): return NotImplemented return other def __iadd__(self, other): if not isinstance(other, Space) and not isinstance(other, EmptySpace): return NotImplemented return other def tofile(self, filename): """Store EmptySpace in HDF5 file.""" with util.atomic_write(filename) as tmpname: with util.open_h5py(tmpname, "w") as fp: fp.attrs["type"] = "Empty" def __repr__(self): return "{0.__class__.__name__}".format(self) class Space(object): """Main data-storing object in BINoculars. Data is represented on an n-dimensional rectangular grid. Per grid point, the number of photons (~ intensity) and the number of original data points (pixels) contribution is stored. Important attributes: axes Axes instances describing range and stepsizes of each of the dimensions photons n-dimension numpy float array, total intensity per grid point contribitions n-dimensional numpy integer array, number of original datapoints (pixels) per grid point dimension n""" def __init__(self, axes, config=None, metadata=None): if not isinstance(axes, Axes): self.axes = Axes(axes) else: self.axes = axes self.config = config self.metadata = metadata self.photons = numpy.zeros([len(ax) for ax in self.axes], order="C") self.contributions = numpy.zeros(self.photons.shape, order="C") @property def dimension(self): return self.axes.dimension @property def npoints(self): return self.photons.size @property def memory_size(self): """Returns approximate memory consumption of this Space. Only considers size of .photons and .contributions, does not take into account the overhead.""" return self.photons.nbytes + self.contributions.nbytes @property def config(self): """util.ConfigFile instance describing configuration file used to create this Space instance""" return self._config @config.setter def config(self, conf): if isinstance(conf, util.ConfigFile): self._config = conf elif not conf: self._config = util.ConfigFile() else: raise TypeError("'{0!r}' is not a util.ConfigFile".format(conf)) @property def metadata(self): """util.MetaData instance describing metadata used to create this Space instance""" return self._metadata @metadata.setter def metadata(self, metadata): if isinstance(metadata, util.MetaData): self._metadata = metadata elif not metadata: self._metadata = util.MetaData() else: raise TypeError("'{0!r}' is not a util.MetaData".format(metadata)) def copy(self): """Returns a copy of self. Numpy data is not shared, but the Axes object is.""" new = self.__class__(self.axes, self.config, self.metadata) new.photons[:] = self.photons new.contributions[:] = self.contributions return new def get(self): """Returns normalized photon count.""" return self.photons / self.contributions def __repr__(self): return "{0.__class__.__name__} ({0.dimension} dimensions, {0.npoints} points, {1}) {{\n {2}\n}}".format( self, util.format_bytes(self.memory_size), "\n ".join(repr(ax) for ax in self.axes), ) def __getitem__(self, key): """Slicing only! space[-0.2:0.2, 0.9:1.1] does exactly what the syntax implies. Ellipsis operator '...' is not supported.""" newkey = self.get_key(key) newaxes = tuple( ax[k] for k, ax in zip(newkey, self.axes) if isinstance(ax[k], Axis) ) if not newaxes: return self.photons[newkey] / self.contributions[newkey] newspace = self.__class__(newaxes, self.config, self.metadata) newspace.photons = self.photons[newkey].copy() newspace.contributions = self.contributions[newkey].copy() return newspace def get_key(self, key: Union[float, slice, Tuple[Union[float, slice, ndarray], ...], List[Union[float, slice, ndarray]]] ) -> Tuple[Union[int, slice, ndarray], ...]: # needed in the fitaid for visualising the interpolated data """Convert the n-dimensional interval described by key (as used by e.g. __getitem__()) from data coordinates to indices.""" if isinstance(key, (int, float)) or isinstance(key, slice): if not len(self.axes) == 1: raise IndexError("dimension mismatch") else: key = [key] elif not (isinstance(key, tuple) or isinstance(key, list)) or not len( key ) == len(self.axes): raise IndexError("dimension mismatch") return tuple(ax.get_index(k) for k, ax in zip(key, self.axes)) def project(self, axis: Union[str, int], *more_axes) -> 'Space': """Reduce dimensionality of Space by projecting onto 'axis'. All data (photons, contributions) is summed along this axis. axis the label of the axis or the index *more_axis also project on these axes""" index = self.axes.index(axis) newaxes = list(self.axes) newaxes.pop(index) newspace = self.__class__(newaxes, self.config, self.metadata) newspace.photons = self.photons.sum(axis=index) newspace.contributions = self.contributions.sum(axis=index) if more_axes: return newspace.project(more_axes[0], *more_axes[1:]) else: return newspace def slice(self, axis, key): """Single-axis slice. axis label or index of axis to slice key something like slice(lower_data_range, upper_data_range)""" axindex = self.axes.index(axis) newkey = list(slice(None) for ax in self.axes) newkey[axindex] = key return self.__getitem__(tuple(newkey)) def get_masked(self) -> MaskedArray: """Returns photons/contributions, but with divide-by-zero's masked out.""" return numpy.ma.array(data=self.get(), mask=(self.contributions == 0)) def get_variance(self): return numpy.ma.array( data=1 / self.contributions, mask=(self.contributions == 0) ) def get_grid(self) -> Tuple[ndarray]: """Returns the data coordinates of each grid point, as n-tuple of n-dimensinonal arrays. Basically numpy.mgrid() in data coordinates.""" igrid = numpy.mgrid[tuple(slice(0, len(ax)) for ax in self.axes)] grid = tuple( numpy.array((grid + ax.imin) * ax.res) for grid, ax in zip(igrid, self.axes) ) return grid def max(self, axis=None): """Returns maximum intensity.""" return self.get_masked().max(axis=axis) def argmax(self): """Returns data coordinates of grid point with maximum intensity.""" array = self.get_masked() return tuple( ax[key] for ax, key in zip( self.axes, numpy.unravel_index(numpy.argmax(array), array.shape) ) ) def __add__(self, other): if isinstance(other, Number): new = self.copy() new.photons += other * self.contributions return new if not isinstance(other, Space): return NotImplemented if not len(self.axes) == len(other.axes) or not all( a.is_compatible(b) for (a, b) in zip(self.axes, other.axes) ): raise ValueError( "cannot add spaces with different dimensionality or resolution" ) new = self.__class__([a | b for (a, b) in zip(self.axes, other.axes)]) new += self new += other return new def __iadd__(self, other): if isinstance(other, Number): self.photons += other * self.contributions return self if not isinstance(other, Space): return NotImplemented if not len(self.axes) == len(other.axes) or not all( a.is_compatible(b) for (a, b) in zip(self.axes, other.axes) ): raise ValueError( "cannot add spaces with different dimensionality or resolution" ) if not all( other_ax in self_ax for (self_ax, other_ax) in zip(self.axes, other.axes) ): return self.__add__(other) index = tuple( slice( self_ax.get_index(other_ax.min), self_ax.get_index(other_ax.min) + len(other_ax), ) for (self_ax, other_ax) in zip(self.axes, other.axes) ) self.photons[index] += other.photons self.contributions[index] += other.contributions self.metadata += other.metadata return self def __sub__(self, other): return self.__add__(other * -1) def __isub__(self, other): return self.__iadd__(other * -1) def __mul__(self, other): if isinstance(other, Number): new = self.__class__(self.axes, self.config, self.metadata) # we would like to keep 1/contributions as the variance # var(aX) = a**2var(X) new.photons = self.photons / other new.contributions = self.contributions / other ** 2 return new else: return NotImplemented def trim(self): """Reduce total size of Space by trimming zero-contribution data points on the boundaries.""" mask = self.contributions > 0 lims = ( numpy.flatnonzero(sum_onto(mask, i)) for (i, ax) in enumerate(self.axes) ) lims = tuple((i.min(), i.max()) for i in lims) self.axes = Axes( ax.rebound(min + ax.imin, max + ax.imin) for (ax, (min, max)) in zip(self.axes, lims) ) slices = tuple(slice(min, max + 1) for (min, max) in lims) self.photons = self.photons[slices].copy() self.contributions = self.contributions[slices].copy() def rebin(self, resolutions): """Change bin size. resolution n-tuple of floats, new resolution of each axis""" if not len(resolutions) == len(self.axes): raise ValueError("cannot rebin space with different dimensionality") if resolutions == tuple(ax.res for ax in self.axes): return self # gather data and transform labels = list(ax.label for ax in self.axes) coords = self.get_grid() intensity = self.get() weights = self.contributions return self.from_image(resolutions, labels, coords, intensity, weights) def reorder(self, labels): """Change order of axes.""" if not self.dimension == len(labels): raise ValueError("dimension mismatch") newindices = list(self.axes.index(label) for label in labels) new = self.__class__( tuple(self.axes[index] for index in newindices), self.config, self.metadata ) new.photons = numpy.transpose(self.photons, axes=newindices) new.contributions = numpy.transpose(self.contributions, axes=newindices) return new def transform_coordinates(self, resolutions, labels, transformation): # gather data and transform coords = self.get_grid() transcoords = transformation(*coords) intensity = self.get() weights = self.contributions # get rid of invalid coords valid = reduce( numpy.bitwise_and, chain((numpy.isfinite(t) for t in transcoords)), (weights > 0), ) transcoords = tuple(t[valid] for t in transcoords) return self.from_image( resolutions, labels, transcoords, intensity[valid], weights[valid] ) def process_image(self, coordinates, intensity, weights): """Load image data into Space. coordinates n-tuple of data coordinate arrays intensity data intensity array weights weights array, supply numpy.ones_like(intensity) for equal weights""" if len(coordinates) != len(self.axes): raise ValueError("dimension mismatch between coordinates and axes") intensity = numpy.nan_to_num( intensity ).flatten() # invalids can be handeled by setting weight to 0, this ensures the weights can do that weights = weights.flatten() indices = numpy.array( tuple(ax.get_index(coord) for (ax, coord) in zip(self.axes, coordinates)) ) for i in range(0, len(self.axes)): for j in range(i + 1, len(self.axes)): indices[i, :] *= len(self.axes[j]) indices = indices.sum(axis=0).astype(int).flatten() photons = numpy.bincount(indices, weights=intensity * weights) contributions = numpy.bincount(indices, weights=weights) self.photons.ravel()[: photons.size] += photons self.contributions.ravel()[: contributions.size] += contributions @classmethod def from_image( cls, resolutions, labels, coordinates, intensity, weights, limits=None ): """Create Space from image data. resolutions n-tuple of axis resolutions labels n-tuple of axis labels coordinates n-tuple of data coordinate arrays intensity data intensity array""" if limits is not None: invalid = numpy.zeros(intensity.shape).astype(numpy.bool) for coord, sl in zip(coordinates, limits): if sl.start is None and sl.stop is not None: invalid += coord > sl.stop elif sl.start is not None and sl.stop is None: invalid += coord < sl.start elif sl.start is not None and sl.stop is not None: invalid += numpy.bitwise_or(coord < sl.start, coord > sl.stop) if numpy.all(invalid == True): return EmptySpace() coordinates = tuple(coord[~invalid] for coord in coordinates) intensity = intensity[~invalid] weights = weights[~invalid] axes = tuple( Axis(coord.min(), coord.max(), res, label) for res, label, coord in zip(resolutions, labels, coordinates) ) newspace = cls(axes) newspace.process_image(coordinates, intensity, weights) return newspace def tovti(self, filename: str) -> None: assert self.dimension == 3, "only array with three dimensions are allowed" data = self.photons / self.contributions spacing = tuple(a.res for a in self.axes.axes) origin = tuple(a.min for a in self.axes.axes) name = str(tuple(a.label for a in self.axes.axes)) image_data = vtkImageData() image_data.SetSpacing(spacing) image_data.SetOrigin(origin) image_data.SetDimensions(data.shape) temp_array = numpy.transpose(numpy.flip(data, 2)).flatten() temp_array = numpy_support.numpy_to_vtk(temp_array, deep=1) pd = image_data.GetPointData() pd.SetScalars(temp_array) pd.GetArray(0).SetName(name) # export data to file writer = vtkXMLImageDataWriter() writer.SetFileName(filename) writer.SetInputData(image_data) writer.Write() def tofile(self, filename): """Store Space in HDF5 file.""" with util.atomic_write(filename) as tmpname: with util.open_h5py(tmpname, "w") as fp: fp.attrs["type"] = "Space" self.config.tofile(fp) self.axes.tofile(fp) self.metadata.tofile(fp) fp.create_dataset( "counts", self.photons.shape, dtype=self.photons.dtype, compression="gzip", ).write_direct(self.photons) fp.create_dataset( "contributions", self.contributions.shape, dtype=self.contributions.dtype, compression="gzip", ).write_direct(self.contributions) @classmethod def fromfile(cls, file: str, key: Optional[Sequence[slice]]=None) -> 'Space': """Load Space from HDF5 file. file filename string or h5py.Group instance key sliced (subset) loading, should be an n-tuple of slice()s in data coordinates""" try: with util.open_h5py(file, "r") as fp: if "type" in fp.attrs.keys(): if fp.attrs["type"] == "Empty": return EmptySpace() axes = Axes.fromfile(fp) config = util.ConfigFile.fromfile(fp) metadata = util.MetaData.fromfile(fp) if key: if len(axes) != len(key): raise ValueError( "dimensionality of 'key' does not match dimensionality of Space in HDF5 file {0}".format( file ) ) key = tuple(ax.get_index(k) for k, ax in zip(key, axes)) for index, sl in enumerate(key): if sl.start == sl.stop and sl.start is not None: raise KeyError("key results in empty space") axes = tuple( ax[k] for k, ax in zip(key, axes) if isinstance(k, slice) ) else: key = Ellipsis space = cls(axes, config, metadata) try: fp["counts"].read_direct(space.photons, key) fp["contributions"].read_direct(space.contributions, key) except (KeyError, TypeError) as e: raise errors.HDF5FileError( "unable to load Space from HDF5 file {0}, is it a valid BINoculars file? (original error: {1!r})".format( file, e ) ) except IOError as e: raise errors.HDF5FileError( "unable to open '{0}' as HDF5 file (original error: {1!r})".format( file, e ) ) return space class Multiverse(object): """A collection of spaces with basic support for addition. Only to be used when processing data. This makes it possible to process multiple limit sets in a combination of scans""" def __init__(self, spaces): self.spaces = list(spaces) @property def dimension(self): return len(self.spaces) def __add__(self, other): if not isinstance(other, Multiverse): return NotImplemented if not self.dimension == other.dimension: raise ValueError("cannot add multiverses with different dimensionality") return self.__class__(tuple(s + o for s, o in zip(self.spaces, other.spaces))) def __iadd__(self, other): if not isinstance(other, Multiverse): return NotImplemented if not self.dimension == other.dimension: raise ValueError("cannot add multiverses with different dimensionality") for index, o in enumerate(other.spaces): self.spaces[index] += o return self def tofile(self, filename): with util.atomic_write(filename) as tmpname: with util.open_h5py(tmpname, "w") as fp: fp.attrs["type"] = "Multiverse" for index, sp in enumerate(self.spaces): spacegroup = fp.create_group("space_{0}".format(index)) sp.tofile(spacegroup) @classmethod def fromfile(cls, file): """Load Multiverse from HDF5 file.""" try: with util.open_h5py(file, "r") as fp: if "type" in fp.attrs: if fp.attrs["type"] == "Multiverse": return cls(tuple(Space.fromfile(fp[label]) for label in fp)) else: raise TypeError("This is not a multiverse") else: raise TypeError("This is not a multiverse") except IOError as e: raise errors.HDF5FileError( "unable to open '{0}' as HDF5 file (original error: {1!r})".format( file, e ) ) def __repr__(self): return "{0.__class__.__name__}\n{1}".format(self, self.spaces) class EmptyVerse(object): """Convenience object for sum() and friends. Treated as zero for addition.""" spaces = [EmptySpace()] @property def dimension(self): return len(self.spaces) def __add__(self, other): if not isinstance(other, Multiverse): return NotImplemented return other def __radd__(self, other): if not isinstance(other, Multiverse): return NotImplemented return other def __iadd__(self, other): if not isinstance(other, Multiverse): return NotImplemented return other def union_axes(axes): axes = tuple(axes) if len(axes) == 1: return axes[0] if not all(isinstance(ax, Axis) for ax in axes): raise TypeError("not all objects are Axis instances") if len(set(ax.res for ax in axes)) != 1 or len(set(ax.label for ax in axes)) != 1: raise ValueError("cannot unite axes with different resolution/label") mi = min(ax.min for ax in axes) ma = max(ax.max for ax in axes) first = axes[0] return first.__class__(mi, ma, first.res, first.label) def union_unequal_axes(axes): axes = tuple(axes) if len(axes) == 1: return axes[0] if not all(isinstance(ax, Axis) for ax in axes): raise TypeError("not all objects are Axis instances") if len(set(ax.label for ax in axes)) != 1: raise ValueError("cannot unite axes with different label") mi = min(ax.min for ax in axes) ma = max(ax.max for ax in axes) res = min( ax.res for ax in axes ) # making it easier to use the sliderwidget otherwise this hase no meaning first = axes[0] return first.__class__(mi, ma, res, first.label) def sum(spaces): """Calculate sum of iterable of Space instances.""" spaces = tuple(space for space in spaces if not isinstance(space, EmptySpace)) if len(spaces) == 0: return EmptySpace() if len(spaces) == 1: return spaces[0] if len(set(space.dimension for space in spaces)) != 1: raise TypeError("dimension mismatch in spaces") first = spaces[0] axes = tuple( union_axes(space.axes[i] for space in spaces) for i in range(first.dimension) ) newspace = first.__class__(axes) for space in spaces: newspace += space return newspace def verse_sum(verses: Generator[Multiverse, None, None]) -> Multiverse: i = iter(M.spaces for M in verses) return Multiverse(sum(spaces) for spaces in zip(*i)) # hybrid sum() / __iadd__() def chunked_sum(verses: Iterable[Multiverse], chunksize=10) -> Union[EmptyVerse, Multiverse]: """Calculate sum of iterable of Multiverse instances. Creates intermediate sums to avoid growing a large space at every summation. verses iterable of Multiverse instances chunksize number of Multiverse instances in each intermediate sum""" result = EmptyVerse() for chunk in util.grouper(verses, chunksize): result += verse_sum(M for M in chunk) return result def iterate_over_axis(space, axis, resolution=None): ax = space.axes[space.axes.index(axis)] if resolution: bins = get_bins(ax, resolution) for start, stop in zip(bins[:-1], bins[1:]): yield space.slice(axis, slice(start, stop)) else: for value in ax: yield space.slice(axis, value) def get_axis_values(axes, axis, resolution=None) -> ndarray: ax = axes[axes.index(axis)] if resolution: bins = get_bins(ax, resolution) return (bins[:-1] + bins[1:]) / 2 else: return numpy.array(list(ax)) def iterate_over_axis_keys(axes, axis, resolution=None): axindex = axes.index(axis) ax = axes[axindex] k = [slice(None) for i in axes] if resolution: bins = get_bins(ax, resolution) for start, stop in zip(bins[:-1], bins[1:]): k[axindex] = slice(start, stop) yield k else: for value in ax: k[axindex] = value yield k def get_bins(ax: Axis, resolution: float) -> ndarray: if float(resolution) < ax.res: raise ValueError( "interval {0} to low, minimum interval is {1}".format(resolution, ax.res) ) mi, ma = ax.min, ax.max if(resolution >= (ma - mi)): return numpy.array([mi, ma]) else: return numpy.linspace(mi, ma, math.ceil(1.0 / resolution * (ma - mi))) def dstack(spaces, dindices, dlabel, dresolution): def transform(space, dindex): resolutions = list(ax.res for ax in space.axes) resolutions.append(dresolution) labels = list(ax.label for ax in space.axes) labels.append(dlabel) exprs = list(ax.label for ax in space.axes) exprs.append("ones_like({0}) * {1}".format(labels[0], dindex)) transformation = util.transformation_from_expressions(space, exprs) return space.transform_coordinates(resolutions, labels, transformation) return sum(transform(space, dindex) for space, dindex in zip(spaces, dindices)) def axis_offset(space, label, offset): exprs = list(ax.label for ax in space.axes) index = space.axes.index(label) exprs[index] += "+ {0}".format(offset) transformation = util.transformation_from_expressions(space, exprs) return space.transform_coordinates( (ax.res for ax in space.axes), (ax.label for ax in space.axes), transformation ) def bkgsubtract(space, bkg): if space.dimension == bkg.dimension: bkg.photons = bkg.photons * space.contributions / bkg.contributions bkg.photons[bkg.contributions == 0] = 0 bkg.contributions = space.contributions return space - bkg else: photons = numpy.broadcast_arrays(space.photons, bkg.photons)[1] contributions = numpy.broadcast_arrays(space.contributions, bkg.contributions)[ 1 ] bkg = Space(space.axes) bkg.photons = photons bkg.contributions = contributions return bkgsubtract(space, bkg) def make_compatible(spaces): if not numpy.alen(numpy.unique(len(space.axes) for space in spaces)) == 1: raise ValueError("cannot make spaces with different dimensionality compatible") ax0 = tuple(ax.label for ax in spaces[0].axes) resmax = tuple( numpy.vstack( tuple(ax.res for ax in space.reorder(ax0).axes) for space in spaces ).max(axis=0) ) resmin = tuple( numpy.vstack( tuple(ax.res for ax in space.reorder(ax0).axes) for space in spaces ).min(axis=0) ) if not resmax == resmin: print( "Warning: Not all spaces have the same resolution. Resolution will be changed to: {0}".format( resmax ) ) return tuple(space.reorder(ax0).rebin2(resmax) for space in spaces) binoculars-0.0.10/binoculars/util.py000077500000000000000000001053041412510113200174150ustar00rootroot00000000000000import argparse import binascii import configparser import contextlib import copy import glob import gzip import inspect import io import itertools import json import os import pickle import random import re import struct import socket import sys import time import h5py import numpy from ast import literal_eval from PyMca5.PyMca import EdfFile from . import errors ### ARGUMENT HANDLING def as_string(text): if hasattr(text, "decode"): text = text.decode() return text class OrderedOperation(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): oops = getattr(namespace, "ordered_operations", []) oops.append((self.dest, values)) setattr(namespace, "ordered_operations", oops) def argparse_common_arguments(parser, *args): for arg in args: # (ORDERED) OPERATIONS if arg == "project": parser.add_argument( "-p", "--project", metavar="AXIS", action=OrderedOperation, help="project space on AXIS", ) elif arg == "slice": parser.add_argument( "--slice", nargs=2, metavar=("AXIS", "START:STOP"), action=OrderedOperation, help="slice AXIS from START to STOP (replace minus signs by 'm')", ) elif arg == "pslice": parser.add_argument( "--pslice", nargs=2, metavar=("AXIS", "START:STOP"), action=OrderedOperation, help="like slice, but also project on AXIS after slicing", ) elif arg == "transform": parser.add_argument( "--transform", metavar="VAR@RES=EXPR;VAR2@RES2=EXPR2;...", action=OrderedOperation, help="perform coordinate transformation, rebinning data on new axis named VAR with resolution RES defined by EXPR, example: Q@0.1=sqrt(H**2+K**2+L**2)", ) elif arg == "rebin": parser.add_argument( "--rebin", metavar="N,M,...", action=OrderedOperation, help="reduce binsize by factor N in first dimension, M in second, etc", ) # SUBTRACT elif arg == "subtract": parser.add_argument( "--subtract", metavar="SPACE", help="subtract SPACE from input file" ) # PRESENTATION elif arg == "nolog": parser.add_argument( "--nolog", action="store_true", help="do not use logarithmic axis" ) elif arg == "clip": parser.add_argument( "-c", "--clip", metavar="FRACTION", default=0.00, help="clip color scale to remove FRACTION datapoints", ) # OUTPUT elif arg == "savepdf": parser.add_argument( "-s", "--savepdf", action="store_true", help="save output as pdf, automatic file naming", ) elif arg == "savefile": parser.add_argument( "--savefile", metavar="FILENAME", help="save output as FILENAME, autodetect filetype", ) # ERROR! else: raise ValueError("unsupported argument '{0}'".format(arg)) def parse_transform_args(transform): for t in transform.split(";"): lhs, expr = t.split("=") ax, res = lhs.split("@") yield ax.strip(), float(res), expr.strip() def handle_ordered_operations(space, args, auto3to2=False): info = [] for command, opts in getattr(args, "ordered_operations", []): if command == "slice" or command == "pslice": ax, key = opts axindex = space.axes.index(ax) axlabel = space.axes[axindex].label if ":" in key: start, stop = key.split(":") if start: start = float(start.replace("m", "-")) else: start = space.axes[axindex].min if stop: stop = float(stop.replace("m", "-")) else: stop = space.axes[axindex].max key = slice(start, stop) info.append( "sliced in {0} from {1} to {2}".format(axlabel, start, stop) ) else: key = float(key.replace("m", "-")) info.append("sliced in {0} at {1}".format(axlabel, key)) space = space.slice(axindex, key) if command == "pslice": try: projectaxis = space.axes.index(ax) except ValueError: pass else: info.append( "projected on {0}".format(space.axes[projectaxis].label) ) space = space.project(projectaxis) elif command == "project": projectaxis = space.axes.index(opts) info.append("projected on {0}".format(space.axes[projectaxis].label)) space = space.project(projectaxis) elif command == "transform": labels, resolutions, exprs = list(zip(*parse_transform_args(opts))) transformation = transformation_from_expressions(space, exprs) info.append( "transformed to {0}".format( ", ".join( "{0} = {1}".format(label, expr) for (label, expr) in zip(labels, exprs) ) ) ) space = space.transform_coordinates(resolutions, labels, transformation) elif command == "rebin": if "," in opts: factors = tuple(int(i) for i in opts.split(",")) else: factors = (int(opts),) * space.dimension space = space.rebin(factors) else: raise ValueError("unsported Ordered Operation '{0}'".format(command)) if auto3to2 and space.dimension == 3: # automatic projection on smallest axis projectaxis = numpy.argmin(space.photons.shape) info.append("projected on {0}".format(space.axes[projectaxis].label)) space = space.project(projectaxis) return space, info ### STATUS LINES _status_line_length = 0 def status(line, eol=False): """Prints a status line to sys.stdout, overwriting the previous one. Set eol to True to append a newline to the end of the line""" global _status_line_length sys.stdout.write("\r{0}\r{1}".format(" " * _status_line_length, line)) if eol: sys.stdout.write("\n") _status_line_length = 0 else: _status_line_length = len(line) sys.stdout.flush() def statusnl(line): """Shortcut for status(..., eol=True)""" return status(line, eol=True) def statuseol(): """Starts a new status line, keeping the previous one intact""" global _status_line_length _status_line_length = 0 sys.stdout.write("\n") sys.stdout.flush() def statuscl(): """Clears the status line, shortcut for status('')""" return status("") ### Dispatcher, projection and input finder def get_backends(): modules = glob.glob(os.path.join(os.path.dirname(__file__), "backends", "*.py")) names = list() for module in modules: if not module.endswith("__init__.py"): names.append(os.path.splitext(os.path.basename(module))[0]) return names def get_projections(module): from . import backend return get_base(module, backend.ProjectionBase) def get_inputs(module): from . import backend return get_base(module, backend.InputBase) def get_dispatchers(): from . import dispatcher from inspect import isclass items = dir(dispatcher) options = [] for item in items: obj = getattr(dispatcher, item) if isclass(obj): if issubclass(obj, dispatcher.DispatcherBase): options.append(item) return options def get_base(modname, base): from inspect import isclass if modname not in get_backends(): raise KeyError("{0} is not an available backend".format(modname)) try: backends = __import__( "backends.{0}".format(modname), globals(), locals(), [], 1 ) except ImportError as e: raise ImportError( "Unable to import module backends.{0}: {1}".format(modname, e) ) backend = getattr(backends, modname) items = dir(backend) options = [] for item in items: obj = getattr(backend, item) if isclass(obj): if issubclass(obj, base): options.append(item) return options ### Dispatcher, projection and input configuration options finder def get_dispatcher_configkeys(classname): from . import dispatcher cls = getattr(dispatcher, classname) return get_configkeys(cls) def get_projection_configkeys(modname, classname): return get_backend_configkeys(modname, classname) def get_input_configkeys(modname, classname): return get_backend_configkeys(modname, classname) def get_backend_configkeys(modname, classname): backends = __import__("backends.{0}".format(modname), globals(), locals(), [], 1) backend = getattr(backends, modname) cls = getattr(backend, classname) return get_configkeys(cls) def get_configkeys(cls): from inspect import getsource items = list() while hasattr(cls, "parse_config"): code = getsource(cls.parse_config) for line in code.split("\n"): key = parse_configcode(line) if key: if key not in items: items.append(key) cls = cls.__base__ return items def parse_configcode(line): try: comment = "#".join(line.split("#")[1:]) line = line.split("#")[0] index = line.index("config.pop") item = line[index:].split("'")[1] if item == "action": return # action is reserved for internal use! return item, comment except ValueError: pass ### CONFIGURATION MANAGEMENT def parse_float(config, option, default=None): value = default value_as_string = config.pop(option, None) if value_as_string is not None: try: value = float(value_as_string) # noqa except ValueError: pass return value def parse_range(r): if "-" in r: a, b = r.split("-") return list(range(int(a), int(b) + 1)) elif r: return [int(r)] else: return [] def parse_multi_range(s): if not s: return s out = [] ranges = s.split(",") for r in ranges: out.extend(parse_range(r)) return out def parse_tuple(s, length=None, type=str): if not s: return s t = tuple(type(i) for i in s.split(",")) if length is not None and len(t) != length: raise ValueError( "invalid tuple length: expected {0} got {1}".format(length, len(t)) ) return t def parse_bool(s): l = s.lower() if l in ("1", "true", "yes", "on"): return True elif l in ("0", "false", "no", "off"): return False raise ValueError("invalid input for boolean: '{0}'".format(s)) def parse_pairs(s): if not s: return s limits = [] for lim in re.findall("\[(.*?)\]", s): parsed = [] for pair in re.split(",", lim): mi, ma = tuple(m.strip() for m in pair.split(":")) if mi == "" and ma == "": parsed.append(slice(None)) elif mi == "": parsed.append(slice(None, float(ma))) elif ma == "": parsed.append(slice(float(mi), None)) else: if float(ma) < float(mi): raise ValueError( "invalid input. maximum is larger than minimum: '{0}'".format(s) ) else: parsed.append(slice(float(mi), float(ma))) limits.append(parsed) return limits def parse_dict(config, option, default=None) -> dict: value = default value_as_string = config.pop(option, None) if value_as_string is not None: try: value = literal_eval(value_as_string) # noqa except ValueError: pass return value def limit_to_filelabel(s): return tuple( "[{0}]".format(lim.replace("-", "m").replace(":", "-").replace(" ", "")) for lim in re.findall("\[(.*?)\]", s) ) class MetaBase(object): def __init__(self, label=None, section=None): self.sections = [] if label is not None and section is not None: self.sections.append(label) setattr(self, label, section) elif label is not None: self.sections.append(label) setattr(self, label, dict()) def add_section(self, label, section=None): self.sections.append(label) if section is not None: setattr(self, label, section) else: setattr(self, label, dict()) def __repr__(self): str = "{0.__class__.__name__}{{\n".format(self) for section in self.sections: str += " [{}]\n".format(section) s = getattr(self, section) for entry in s: str += " {} = {}\n".format(entry, s[entry]) str += "}\n" return str def copy(self): return copy.deepcopy(self) def serialize(self): sections = {} for section in self.sections: section_dict = {} attr = getattr(self, section) for key in list(attr.keys()): if isinstance( attr[key], numpy.ndarray ): # to be able to include numpy arrays in the serialisation sio = io.StringIO() numpy.save(sio, attr[key]) sio.seek(0) section_dict[key] = binascii.b2a_hex( sio.read() ) # hex codation is needed to let json work with the string else: section_dict[key] = attr[key] sections[section] = section_dict return json.dumps(sections) @classmethod def fromserial(cls, s): obj = cls() data = json.loads(s) for section in list(data.keys()): section_dict = data[section] for key in list(section_dict.keys()): if isinstance( section_dict[key], str ): # find and replace all the numpy serialised objects if section_dict[key].startswith( "934e554d505901004600" ): # numpy marker sio = io.StringIO() sio.write(binascii.a2b_hex(section_dict[key])) sio.seek(0) section_dict[key] = numpy.load(sio) setattr(obj, section, data[section]) if section not in obj.sections: obj.sections.append(section) return obj class MetaData(object): # a collection of metadata objects def __init__(self): self.metas = [] def add_dataset(self, dataset): if not isinstance(dataset, MetaBase) and not isinstance(dataset, ConfigFile): raise ValueError("MetaBase instance expected") else: self.metas.append(dataset) def __add__(self, other): new = self.__class__() new += self new += other return new def __iadd__(self, other): self.metas.extend(other.metas) return self @classmethod def fromfile(cls, filename): if isinstance(filename, str): if not os.path.exists(filename): raise IOError( "Error importing configuration file. filename {0} does not exist".format( filename ) ) metadataobj = cls() with open_h5py(filename, "r") as fp: try: metadata = fp["metadata"] except KeyError: metadata = [] # when metadata is not present, proceed without Error for label in metadata: meta = MetaBase() for section in list(metadata[label].keys()): group = metadata[label][section] setattr( meta, section, dict((key, group[key][()]) for key in group) ) meta.sections.append(section) metadataobj.metas.append(meta) return metadataobj def tofile(self, filename): with open_h5py(filename, "w") as fp: metadata = fp.create_group("metadata") for meta in self.metas: label = find_unused_label("metasection", list(metadata.keys())) metabase = metadata.create_group(label) for section in meta.sections: sectiongroup = metabase.create_group(section) s = getattr(meta, section) for key in list(s.keys()): sectiongroup.create_dataset(key, data=s[key]) def __repr__(self): str = "{0.__class__.__name__}{{\n".format(self) for meta in self.metas: for line in meta.__repr__().split("\n"): str += " " + line + "\n" str += "}\n" return str def serialize(self): return json.dumps(list(meta.serialize() for meta in self.metas)) @classmethod def fromserial(cls, s): obj = cls() for item in json.loads(s): obj.metas.append(MetaBase.fromserial(item)) return obj # Contains the unparsed config dicts class ConfigFile(MetaBase): def __init__(self, origin="n/a", command=[]): self.origin = origin self.command = command super(ConfigFile, self).__init__() self.sections = ["dispatcher", "projection", "input"] for section in self.sections: setattr(self, section, dict()) @classmethod def fromfile(cls, filename): if isinstance(filename, str): if not os.path.exists(filename): raise IOError( "Error importing configuration file. filename {0} does not exist".format( filename ) ) configobj = cls(str(filename)) with open_h5py(filename, "r") as fp: try: config = fp["configuration"] if "command" in config.attrs: configobj.command = json.loads(as_string(config.attrs["command"])) for section in config: if isinstance(config[section], h5py._hl.group.Group): # new setattr( configobj, section, dict( (key, config[section][key][()]) for key in config[section] ), ) else: # old setattr(configobj, section, dict(config[section])) except KeyError: pass # when config is not present, proceed without Error return configobj @classmethod def fromtxtfile(cls, filename, command=[], overrides=[]): if not os.path.exists(filename): raise IOError( "Error importing configuration file. filename {0} does not exist".format( filename ) ) config = configparser.RawConfigParser() config.read(filename) for section, option, value in overrides: config.set(section, option, value) configobj = cls(filename, command=command) for section in configobj.sections: setattr( configobj, section, dict((k, v.split("#")[0].strip()) for (k, v) in config.items(section)), ) return configobj def tofile(self, filename): with open_h5py(filename, "w") as fp: conf = fp.create_group("configuration") conf.attrs["origin"] = str(self.origin) conf.attrs["command"] = json.dumps(self.command) for section in self.sections: sectiongroup = conf.create_group(section) s = getattr(self, section) for key in list(s.keys()): sectiongroup.create_dataset(key, data=s[key]) def totxtfile(self, filename): with open(filename, "w") as fp: fp.write("# Configurations origin: {}\n".format(self.origin)) for section in self.sections: fp.write("[{}]\n".format(section)) s = getattr(self, section) for entry in s: fp.write("{} = {}\n".format(entry, s[entry])) def __repr__(self): str = super(ConfigFile, self).__repr__() str += "origin = {0}\n".format(self.origin) str += "command = {0}".format(",".join(self.command)) return str # contains one parsed dict, for distribution to dispatcher, input or projection class class ConfigSection(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) def copy(self): return copy.deepcopy(self) # contains the parsed configsections class ConfigSectionGroup(object): def __init__(self, origin="n/a"): self.origin = origin self.sections = "dispatcher", "projection", "input" for section in self.sections: setattr(self, section, ConfigSection()) self.configfile = ConfigFile() class ConfigurableObject(object): def __init__(self, config): if isinstance(config, ConfigSection): self.config = config elif not isinstance(config, dict): raise ValueError( "expecting dict or Configsection, not: {0}".format(type(config)) ) else: self.config = ConfigSection() try: allkeys = list(config.keys()) self.parse_config(config) except KeyError as exc: raise errors.ConfigError( "Configuration option {0} is missing from the configuration file. Please specify this option in the configuration file".format( exc ) ) except Exception as exc: missing = set( key for key in allkeys if key not in list(self.config.__dict__.keys()) ) - set(config.keys()) exc.args = errors.addmessage( exc.args, ". Unable to parse configuration option '{0}'. The error can quite likely be solved by modifying the option in the configuration file.".format( ",".join(missing) ), ) raise for k in config: print( "warning: unrecognized configuration option {0} for {1}".format( k, self.__class__.__name__ ) ) self.config.class_ = self.__class__ def parse_config(self, config): # every known option should be pop()'ed from config, converted to a # proper type and stored as property in self.config, for example: # self.config.foo = int(config.pop('foo', 1)) pass ### FILES def best_effort_atomic_rename(src, dest): if sys.platform == "win32" and os.path.exists(dest): os.remove(dest) os.rename(src, dest) def filename_enumerator(filename, start=0): base, ext = os.path.splitext(filename) for count in itertools.count(start): yield "{0}_{2}{1}".format(base, ext, count) def find_unused_filename(filename): if not os.path.exists(filename): return filename for f in filename_enumerator(filename, 2): if not os.path.exists(f): return f def label_enumerator(label, start=0): for count in itertools.count(start): yield "{0}_{1}".format(label, count) def find_unused_label(label, labellist): for l in label_enumerator(label): if not l in labellist: return l def yield_when_exists(filelist, timeout=None): """Wait for files in 'filelist' to appear, for a maximum of 'timeout' seconds, yielding them in arbitrary order as soon as they appear. If 'filelist' is a set, it will be modified in place, and on timeout it will contain the files that have not appeared yet.""" if not isinstance(filelist, set): filelist = set(filelist) delay = loop_delayer(5) start = time.time() while filelist: next(delay) exists = set(f for f in filelist if os.path.exists(f)) for e in exists: yield e filelist -= exists if timeout is not None and time.time() - start > timeout: break def wait_for_files(filelist, timeout=None): """Wait until the files in 'filelist' have appeared, for a maximum of 'timeout' seconds. Returns True on success, False on timeout.""" filelist = set(filelist) for i in yield_when_exists(filelist, timeout): pass return not filelist def wait_for_file(file, timeout=None): return wait_for_files([file], timeout=timeout) def space_to_edf(space, filename): header = {} for a in space.axes: header[str(a.label)] = "{0} {1} {2}".format(a.min, a.max, a.res) edf = EdfFile.EdfFile(filename) edf.WriteImage(header, space.get_masked().filled(0), DataType="Float") def space_to_txt(space, filename): data = [coord.flatten() for coord in space.get_grid()] data.append(space.get_masked().filled(0).flatten()) data = numpy.array(data).T with open(filename, "w") as fp: fp.write("\t".join(ax.label for ax in space.axes)) fp.write("\tintensity\n") numpy.savetxt(fp, data, fmt="%.6g", delimiter="\t") @contextlib.contextmanager def open_h5py(fn, mode): if isinstance(fn, h5py._hl.group.Group): yield fn else: with h5py.File(fn, mode) as fp: if mode == "w": fp.create_group("binoculars") yield fp["binoculars"] if mode == "r": if "binoculars" in fp: yield fp["binoculars"] else: yield fp ### VARIOUS def uniqid(): return "{0:08x}".format(random.randint(0, 2 ** 32 - 1)) def grouper(iterable, n): while True: chunk = list(itertools.islice(iterable, n)) if not chunk: break yield chunk _python_executable = None def register_python_executable(scriptname): global _python_executable _python_executable = sys.executable, scriptname def get_python_executable(): return _python_executable def chunk_slicer(count, chunksize): """yields slice() objects that split an array of length 'count' into equal sized chunks of at most 'chunksize'""" chunkcount = int(numpy.ceil(float(count) / chunksize)) realchunksize = int(numpy.ceil(float(count) / chunkcount)) for i in range(chunkcount): yield slice(i * realchunksize, min(count, (i + 1) * realchunksize)) def cluster_jobs(jobs, target_weight): jobs = sorted(jobs, key=lambda job: job.weight) # we cannot split jobs here, so just yield away all jobs that are overweight or just right while jobs and jobs[-1].weight >= target_weight: yield [jobs.pop()] while jobs: cluster = [jobs.pop()] # take the biggest remaining job size = cluster[0].weight for i in range( len(jobs) - 1, -1, -1 ): # and exhaustively search for all jobs that can accompany it (biggest first) if size + jobs[i].weight <= target_weight: size += jobs[i].weight cluster.append(jobs.pop(i)) yield cluster def cluster_jobs2(jobs, target_weight): """Taking the first n jobs that together add up to target_weight. Here as opposed to cluster_jobs the total number of jobs does not have to be known beforehand """ jobslist = [] for job in jobs: jobslist.append(job) if sum(j.weight for j in jobslist) >= target_weight: yield jobslist[:] jobslist = [] if len(jobslist) > 0: # yield the remainder of the jobs yield jobslist[:] def loop_delayer(delay): """Delay a loop such that it runs at most once every 'delay' seconds. Usage example: delay = loop_delayer(5) while some_condition: next(delay) do_other_tasks """ def generator(): polltime = 0 while 1: diff = time.time() - polltime if diff < delay: time.sleep(delay - diff) polltime = time.time() yield return generator() def transformation_from_expressions(space, exprs): def transformation(*coords): ns = dict((i, getattr(numpy, i)) for i in dir(numpy)) ns.update(**dict((ax.label, coord) for ax, coord in zip(space.axes, coords))) return tuple(eval(expr, ns) for expr in exprs) return transformation def format_bytes(bytes): units = "kB", "MB", "GB", "TB" exp = min(max(int(numpy.log(bytes) / numpy.log(1024.0)), 1), 4) return "{0:.1f} {1}".format(bytes / 1024 ** exp, units[exp - 1]) ### GZIP PICKLING (zpi) # handle old zpi's def _pickle_translate(module, name): if module in ("__main__", "ivoxoar.space") and name in ("Space", "Axis"): return "BINoculars.space", name return module, name if inspect.isbuiltin(pickle.Unpickler): # real cPickle: cannot subclass def _find_global(module, name): module, name = _pickle_translate(module, name) __import__(module) return getattr(sys.modules[module], name) def pickle_load(fileobj): unpickler = pickle.Unpickler(fileobj) unpickler.find_global = _find_global return unpickler.load() else: # pure python implementation class _Unpickler(pickle.Unpickler): def find_class(self, module, name): module, name = _pickle_translate(module, name) return pickle.Unpickler.find_class(self, module, name) def pickle_load(fileobj): unpickler = _Unpickler(fileobj) return unpickler.load() @contextlib.contextmanager def atomic_write(filename): """Atomically write data into 'filename' using a temporary file and os.rename() Rename on success, clean up on failure (any exception). Example: with atomic_write(filename) as tmpfile with open(tmpfile, 'w') as fp: fp.write(...) """ if isinstance(filename, h5py._hl.group.Group): yield filename else: tmpfile = "{0}-{1}.tmp".format(os.path.splitext(filename)[0], uniqid()) try: yield tmpfile except: raise else: best_effort_atomic_rename(tmpfile, filename) finally: if os.path.exists(tmpfile): os.remove(tmpfile) def zpi_save(obj, filename): with atomic_write(filename) as tmpfile: fp = gzip.open(tmpfile, "wb") try: pickle.dump(obj, fp, pickle.HIGHEST_PROTOCOL) finally: fp.close() def zpi_load(filename): if hasattr(filename, "read"): fp = gzip.GzipFile(filename.name, fileobj=filename) else: fp = gzip.open(filename, "rb") try: return pickle_load(fp) finally: fp.close() def serialize(space, command): # first 48 bytes contain length of the message, whereby the first 8 give the length of the command, the second 8 the length of the configfile etc.. message = io.StringIO() message.write(struct.pack("QQQQQQ", 0, 0, 0, 0, 0, 0)) message.write(command) commandlength = message.len - 48 message.write(space.config.serialize()) configlength = message.len - commandlength - 48 message.write(space.metadata.serialize()) metalength = message.len - configlength - commandlength - 48 numpy.save(message, space.axes.toarray()) arraylength = message.len - metalength - configlength - commandlength - 48 numpy.save(message, space.photons) photonlength = ( message.len - arraylength - metalength - configlength - commandlength - 48 ) numpy.save(message, space.contributions) contributionlength = ( message.len - photonlength - arraylength - metalength - configlength - commandlength - 48 ) message.seek(0) message.write( struct.pack( "QQQQQQ", commandlength, configlength, metalength, arraylength, photonlength, contributionlength, ) ) message.seek(0) return message def packet_slicer(length, size=1024): # limit the communication to 1024 bytes while length > size: length -= size yield size yield length def socket_send(ip, port, mssg): try: mssglengths = struct.unpack( "QQQQQQ", mssg.read(48) ) # the lengths of all the components mssg.seek(0) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) sock.send(mssg.read(48)) for l in mssglengths: for packet in packet_slicer(l): sock.send(mssg.read(packet)) sock.close() except socket.error: # in case of failure to send. The data will be saved anyway so any loss of communication unfortunate but not critical pass def socket_recieve(RequestHandler): # pass one the handler to deal with incoming data def get_msg(length): msg = io.StringIO() for packet in packet_slicer(length): p = RequestHandler.request.recv( packet, socket.MSG_WAITALL ) # wait for full mssg msg.write(p) if msg.len != length: raise errors.CommunicationError( "recieved message is too short. expected length {0}, recieved length {1}".format( length, msg.len ) ) msg.seek(0) return msg command, config, metadata, axes, photons, contributions = tuple( get_msg(msglength) for msglength in struct.unpack( "QQQQQQ", RequestHandler.request.recv(48, socket.MSG_WAITALL) ) ) return ( command.read(), config.read(), metadata.read(), numpy.load(axes), numpy.load(photons), numpy.load(contributions), ) binoculars-0.0.10/debian/000077500000000000000000000000001412510113200151415ustar00rootroot00000000000000binoculars-0.0.10/debian/changelog000066400000000000000000000027331412510113200170200ustar00rootroot00000000000000binoculars (0.0.10-1) UNRELEASED; urgency=medium * new snapshot -- Picca Frédéric-Emmanuel Mon, 26 Jul 2021 14:38:47 +0200 binoculars (0.0.6-1) unstable; urgency=medium * New upstream version 0.0.6 * Added autopkgtests -- Picca Frédéric-Emmanuel Mon, 17 May 2021 16:07:12 +0200 binoculars (0.0.5-1) unstable; urgency=medium * New upstream version 0.0.5 (Closes: #923859, #924024, #986785) * Bumped Standards-Version to 4.3.0 (nothing to do) * Bumped compat level to 12. * d/control Build-Depends: - Removed: python3-tables - Added: python3-h5py, python3-pymca5, python3-vtk7 * Upgrade to newer source format 3.0 (quilt). * Set debhelper-compat version in Build-Depends. * Update standards version to 4.5.0, no changes needed. -- Picca Frédéric-Emmanuel Thu, 14 Mar 2019 10:12:40 +0100 binoculars (0.0.4-1) unstable; urgency=medium * Imported upstream v0.0.4. -- Picca Frédéric-Emmanuel Mon, 18 Feb 2019 15:00:45 +0100 binoculars (0.0.3-1) unstable; urgency=medium * Imported upstream v0.0.3. * d/watch: Updated to target Github release tags. * d/control: Homepage point to Github.com/picca/binoculars. -- Picca Frédéric-Emmanuel Fri, 07 Dec 2018 11:55:41 +0100 binoculars (0.0.2-1) unstable; urgency=medium * Initial release (Closes: #910077) -- Picca Frédéric-Emmanuel Wed, 25 Nov 2015 14:25:10 +0200 binoculars-0.0.10/debian/control000066400000000000000000000074171412510113200165550ustar00rootroot00000000000000Source: binoculars Maintainer: Debian Science Maintainers Uploaders: Picca Frédéric-Emmanuel Section: science Priority: optional Build-Depends: debhelper-compat (= 12), dh-python, gir1.2-hkl-5.0, python3-all, python3-gi, python3-h5py, python3-numpy, python3-pyfai, python3-pymca5, python3-setuptools, python3-sphinx, python3-vtk7, python3-xrayutilities Standards-Version: 4.5.0 Vcs-Browser: https://salsa.debian.org/science-team/binoculars Vcs-Git: https://salsa.debian.org/science-team/binoculars.git Homepage: https://github.com/picca/binoculars Package: binoculars Architecture: all Section: python Depends: python3-binoculars (>= ${source:Version}), ${misc:Depends}, ${python3:Depends} Description: Surface X-ray diffraction 2D detector data reduction BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a two-dimensional X-ray detector. The intensity of each pixel of a two-dimensional detector is projected onto a three-dimensional grid in reciprocal-lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution data sets and results in a significant reduction of the size of the data set. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques. Package: python3-binoculars Architecture: all Section: python Depends: gir1.2-hkl-5.0, python3-vtk7, ${misc:Depends}, ${python3:Depends} Suggests: python3-xrayutilities Description: Surface X-ray diffraction 2D detector data reduction - Python3 BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a two-dimensional X-ray detector. The intensity of each pixel of a two-dimensional detector is projected onto a three-dimensional grid in reciprocal-lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution data sets and results in a significant reduction of the size of the data set. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques. . This is the Python 3 version of the package. Package: binoculars-doc Architecture: all Section: doc Depends: ${misc:Depends}, ${sphinxdoc:Depends} Built-Using: ${sphinxdoc:Built-Using} Description: Surface X-ray diffraction 2D detector data reduction - Documentation BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a two-dimensional X-ray detector. The intensity of each pixel of a two-dimensional detector is projected onto a three-dimensional grid in reciprocal-lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution data sets and results in a significant reduction of the size of the data set. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques. . This is the common documentation package. binoculars-0.0.10/debian/copyright000066400000000000000000000024361412510113200171010ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: binoculars Source: https://github.com/id03/binoculars/releases Files: * Copyright: 2012-2015 European Synchrotron Radiation Facility Willem Onderwaater Sander Roobol 2015-2018 Synchrotron SOLEIL Frédéric-Emmanuel Picca License: GPL-3.0+ Files: debian/* Copyright: 2015 Picca Frédéric-Emmanuel License: GPL-3.0+ License: GPL-3.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". binoculars-0.0.10/debian/gbp.conf000066400000000000000000000000371412510113200165600ustar00rootroot00000000000000[DEFAULT] debian-branch = next binoculars-0.0.10/debian/py3dist-overrides000066400000000000000000000000241412510113200204570ustar00rootroot00000000000000pyqt5 python3-pyqt5 binoculars-0.0.10/debian/rules000077500000000000000000000013311412510113200162170ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 export PYBUILD_NAME=binoculars export PYBUILD_AFTER_INSTALL=rm -rf {destdir}/usr/bin/ %: dh $@ --with python3,sphinxdoc --buildsystem=pybuild override_dh_auto_test: dh_auto_test -- --system=custom --test-args='{interpreter} -m unittest discover -s tests -t {dir} -v' override_dh_install: dh_numpy3 dh_install # install scripts into binoculars python3 setup.py install_scripts -d debian/binoculars/usr/bin override_dh_sphinxdoc: ifeq (,$(findstring nodocs, $(DEB_BUILD_OPTIONS))) PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml doc/source build/html # HTML generator dh_installdocs -p binoculars-doc "build/html" dh_sphinxdoc -O--buildsystem=pybuild endif binoculars-0.0.10/debian/source/000077500000000000000000000000001412510113200164415ustar00rootroot00000000000000binoculars-0.0.10/debian/source/format000066400000000000000000000000141412510113200176470ustar00rootroot000000000000003.0 (quilt) binoculars-0.0.10/debian/tests/000077500000000000000000000000001412510113200163035ustar00rootroot00000000000000binoculars-0.0.10/debian/tests/control000066400000000000000000000004571412510113200177140ustar00rootroot00000000000000Test-Command: set -efu ; cp -r tests examples "$AUTOPKGTEST_TMP" ; for py in $(py3versions -r 2>/dev/null) ; do cd "$AUTOPKGTEST_TMP" ; echo "Testing with $py:" ; $py -m unittest discover -s tests -v ; done Depends: python3-all, python3-binoculars Restrictions: allow-stderr, skip-not-installable binoculars-0.0.10/debian/watch000066400000000000000000000002621412510113200161720ustar00rootroot00000000000000version=4 opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%binoculars-$1.tar.gz%" \ https://github.com/picca/binoculars/tags \ (?:.*?/)?v?(\d[\d.]*)\.tar\.gz debian uupdatebinoculars-0.0.10/doc/000077500000000000000000000000001412510113200144645ustar00rootroot00000000000000binoculars-0.0.10/doc/Makefile000066400000000000000000000152031412510113200161250ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/binoculars.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/binoculars.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/binoculars" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/binoculars" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." binoculars-0.0.10/doc/source/000077500000000000000000000000001412510113200157645ustar00rootroot00000000000000binoculars-0.0.10/doc/source/api/000077500000000000000000000000001412510113200165355ustar00rootroot00000000000000binoculars-0.0.10/doc/source/api/binoculars.backends.rst000066400000000000000000000020251412510113200232000ustar00rootroot00000000000000binoculars.backends package =========================== Submodules ---------- binoculars.backends.bm25 module ------------------------------- .. automodule:: binoculars.backends.bm25 :members: :undoc-members: :show-inheritance: binoculars.backends.example module ---------------------------------- .. automodule:: binoculars.backends.example :members: :undoc-members: :show-inheritance: binoculars.backends.id03 module ------------------------------- .. automodule:: binoculars.backends.id03 :members: :undoc-members: :show-inheritance: binoculars.backends.id03_xu module ---------------------------------- .. automodule:: binoculars.backends.id03_xu :members: :undoc-members: :show-inheritance: binoculars.backends.sixs module ------------------------------- .. automodule:: binoculars.backends.sixs :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: binoculars.backends :members: :undoc-members: :show-inheritance: binoculars-0.0.10/doc/source/api/binoculars.rst000066400000000000000000000025351412510113200214350ustar00rootroot00000000000000binoculars package ================== Subpackages ----------- .. toctree:: binoculars.backends Submodules ---------- binoculars.backend module ------------------------- .. automodule:: binoculars.backend :members: :undoc-members: :show-inheritance: binoculars.dispatcher module ---------------------------- .. automodule:: binoculars.dispatcher :members: :undoc-members: :show-inheritance: binoculars.errors module ------------------------ .. automodule:: binoculars.errors :members: :undoc-members: :show-inheritance: binoculars.fit module --------------------- .. automodule:: binoculars.fit :members: :undoc-members: :show-inheritance: binoculars.main module ---------------------- .. automodule:: binoculars.main :members: :undoc-members: :show-inheritance: binoculars.plot module ---------------------- .. automodule:: binoculars.plot :members: :undoc-members: :show-inheritance: binoculars.space module ----------------------- .. automodule:: binoculars.space :members: :undoc-members: :show-inheritance: binoculars.util module ---------------------- .. automodule:: binoculars.util :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: binoculars :members: :undoc-members: :show-inheritance: binoculars-0.0.10/doc/source/api/config_q.txt000066400000000000000000000031151412510113200210630ustar00rootroot00000000000000### the DISPATCHER is responsible for job management [dispatcher] type = local # run local ncores = 16 # optionally, specify number of cores (autodetect by default) # specificy destination file using scan numbers destination = Al13Fe4_cut2_res001_omOff-70_{first}-{last}.hdf5 overwrite = false ### choose an appropriate INPUT class and specify custom options [input]## type = sixs:flyscanuhv2 # refers to class Sixs in BINoculars/backends/sixs.py nexusdir = /nfs/ruche-sixs/sixs-soleil/com-sixs/2019/Run3/20181682_Corentin/Al13Fe4_cut2/ ## approximate number of images per job, only useful when running on the oar cluster target_weight = 100 # technical data for this particular input class centralpixel = 308, 112 # x,y sdd = 1.16208 # sample to detector distance (m) detrot = 90.0 attenuation_coefficient = 1.825 maskmatrix = mask_nxs00043_20190709_19h58.npy ### choose PROJECTION plus resolution ## projections: realspace, pixels, hklprojection, hkprojection, qxqyqzprojection, qparqperprojection [projection] #type = sixs:qparqperprojection # refers to HKProjection in BINoculars/backends/sixs.py #type = sixs:hklprojection # refers to HKProjection in BINoculars/backends/sixs.py type = sixs:qxqyqzprojection # refers to HKProjection in BINoculars/backends/sixs.py #type = sixs:qparqperprojection # refers to HKProjection in BINoculars/backends/sixs.py resolution = 0.01 #resolution = 0.008, 0.008, 0.01 # or just give 1 number for all dimensions #limits = [-3.53:-1.68,-0.59:0.68,0.98:1.06] omega_offset = -70.31 #source /usr/local/applications/diffractions/binoculars/v0.0.1/env.sh binoculars-0.0.10/doc/source/api/modules.rst000066400000000000000000000001021412510113200207300ustar00rootroot00000000000000. = .. toctree:: :maxdepth: 4 binoculars setup test binoculars-0.0.10/doc/source/api/setup.rst000066400000000000000000000001521412510113200204250ustar00rootroot00000000000000setup module ============ .. automodule:: setup :members: :undoc-members: :show-inheritance: binoculars-0.0.10/doc/source/api/test.rst000066400000000000000000000010201412510113200202370ustar00rootroot00000000000000test package ============ Submodules ---------- test.cfg module --------------- .. automodule:: test.cfg :members: :undoc-members: :show-inheritance: test.id03 module ---------------- .. automodule:: test.id03 :members: :undoc-members: :show-inheritance: test.metadata module -------------------- .. automodule:: test.metadata :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: test :members: :undoc-members: :show-inheritance: binoculars-0.0.10/doc/source/conf.py000066400000000000000000000205731412510113200172720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # binoculars documentation build configuration file, created by # sphinx-quickstart on Wed Nov 25 15:03:57 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.join(os.path.abspath("."), os.pardir, os.pardir)) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = u"binoculars" copyright = u"2015, Willem Onderwaater, Sander Roobol\\" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "0.0.1" # The full version, including alpha/beta/rc tags. release = "0.0.1" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "binocularsdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( "index", "binoculars.tex", u"binoculars Documentation", u"Willem Onderwaater, Sander Roobol\\textbackslash{}", "manual", ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "index", "binoculars", u"binoculars Documentation", [u"Willem Onderwaater, Sander Roobol\\"], 1, ) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "binoculars", u"binoculars Documentation", u"Willem Onderwaater, Sander Roobol\\", "binoculars", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False binoculars-0.0.10/doc/source/index.rst000066400000000000000000000010361412510113200176250ustar00rootroot00000000000000.. binoculars documentation master file, created by sphinx-quickstart on Wed Nov 25 15:03:57 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to binoculars's documentation! ====================================== Contents: .. toctree:: :maxdepth: 2 readme api/binoculars api/binoculars.backends api/test api/modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` binoculars-0.0.10/doc/source/readme.rst000066400000000000000000000037041412510113200177570ustar00rootroot00000000000000BINoculars ========== BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a 2D X-ray detector. The intensity of each pixel of a 2D-detector is projected onto a 3-dimensional grid in reciprocal lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution datasets and results in a significant reduction of the size of the dataset. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques. This work has been [published](http://dx.doi.org/10.1107/S1600576715009607) with open access in the Journal of Applied Crystallography Volume 48, Part 4 (August 2015) ## Installation Grab the [latest sourcecode as zip](https://github.com/id03/binoculars/archive/master.zip) or clone the Git repository. Run `binoculars.py`, `fitaid.py`, `gui.py` or `processgui.py` directly from the command line. ## Usage The [BINoculars wiki](https://github.com/id03/binoculars/wiki) contains a detailed tutorial to get started. ## Scripting If you want more complex operations than offered by the command line or GUI tools, you can manipulate BINoculars data directly from Python. Some examples with detailed comments can be found in the [repository](https://github.com/id03/binoculars/tree/master/examples/scripts). The API documentation on the `BINoculars` and `BINoculars.space` modules can be accessed via pydoc, e.g. run `pydoc -w BINoculars BINoculars.space` to generate HTML files. ## Extending BINoculars If you want to use BINoculars with your beamline, you need to write a `backend` module. The code contains an [example implementation](https://github.com/id03/binoculars/blob/master/BINoculars/backends/example.py) with many hints and comments. binoculars-0.0.10/examples/000077500000000000000000000000001412510113200155355ustar00rootroot00000000000000binoculars-0.0.10/examples/configs/000077500000000000000000000000001412510113200171655ustar00rootroot00000000000000binoculars-0.0.10/examples/configs/example_config_bm25000066400000000000000000000024561412510113200227240ustar00rootroot00000000000000### To process measurements from BM25 this configuration is needed # typically one would execute: # python2 binoculars/binoculars.py process config_bm25 ### the DISPATCHER is responsible for job management [dispatcher] type = local # SingleCore # local # SingleCore can be aborted by Ctrl+c # ncores = 4 # optionally, specify number of cores (autodetect by default) # specify destination file using scan numbers destination=/data/visitor/XYZ/spaces/sample_{first}.hdf5 overwrite = true ### choose an appropriate INPUT class and specify custom options [input] type = bm25:eh2scd # refers to class EH2SCD in BINoculars/backends/bm25.py imagefile = /data/visitor/XYZ/bm25/sample_{scannr:03d}_*.edf ## approximate number of images per job target_weight = 50 # technical details for this particular input class centralpixel = 1892.0,1357.5 xmask = 1600-2200 # full size = 0-3824 ymask = 700-1300 # full size = 0-1911 sddy_offset= -1288.5 # sample detector distance Y offset sddx_offset= 0 # detector X offset sddz_offset= 0 # detector Z offset ccdth_offset= -0.05 pixelsize=0.06552, 0.06552 # in microns ### choose PROJECTION plus resolution [projection] type = bm25:qprojection # refers to QProjection in BINoculars/backends/bm25.py resolution = 0.004,0.001,0.002 # or just give one number for all binoculars-0.0.10/examples/configs/example_config_example000066400000000000000000000015741412510113200236120ustar00rootroot00000000000000### the DISPATCHER is responsible for job management [dispatcher] type = local # run locally ncores = 1 # optionally, specify number of cores (autodetect by default) # specificy destination file using scan numbers destination= test_{first}.hdf5 overwrite = true ### choose an appropriate INPUT class and specify custom options [input] type = example:input # refers to class Input in BINoculars/backends/example.py ## approximate number of images per job, only useful when running on the oar cluster target_weight = 4000 # technical details for this particular input class wavelength = 0.5 centralpixel = 50,50 sdd=636 #sample detector distance pixelsize=0.055, 0.055 ### choose PROJECTION plus resolution [projection] type = example:qprojection # refers to qprojection in BINoculars/backends/example.py ## for L-scans (previous values) resolution = 0.01 # or just give 1 number for all binoculars-0.0.10/examples/configs/example_config_id03000066400000000000000000000026511412510113200227130ustar00rootroot00000000000000### the DISPATCHER is responsible for job management [dispatcher] type = local # run locally #ncores = 4 # optionally, specify number of cores (autodetect by default) # to use the OAR cluster: #type = oar #tmpdir = /some/globally/available/path #oarsub_options = walltime=0:15 # optionally, tweak oarsub parameters #executable = python /data/id03/inhouse/binoculars/binoculars.py # optionally, override default location of python and/or BINoculars installation # specificy destination file using scan numbers destination = demo_{first}-{last}.hdf5 overwrite = true # or, by default: numbered files in the form output_###.hdf5: # destination = output.hdf5 # overwrite = false ### choose an appropriate INPUT class and specify custom options [input] type = id03:eh1 # refers to class EH1 in BINoculars/backends/id03.py specfile = /path/to/data/file.spec imagefolder = /path/to/data/images/{rUCCD[0]}/ ## approximate number of images per job, only useful when running on the oar cluster target_weight = 4000 # technical yadayada for this particular input class centralpixel = 40, 255 # x,y sdd = 1050 # sample to detector distance (mm) pixelsize = 0.055, 0.055 # pixel size x/y (mm) ymask = 185-253,262-400 xmask = 50-235 ### choose PROJECTION plus resolution [projection] type = id03:hklprojection # refers to HKLProjection in BINoculars/backends/id03.py resolution = 0.002, 0.002, 1 # or just give 1 number for all dimensions binoculars-0.0.10/examples/configs/example_config_id03_xrayutilities000066400000000000000000000023561412510113200257140ustar00rootroot00000000000000### To process measurements from ID03 using xrayutilities in the reciprocal space conversion this configuration is needed # typically one would execute: # python2 binoculars/binoculars.py process config_id03_xu ### the DISPATCHER is responsible for job management [dispatcher] type = local # run locally # ncores = 4 # optionally, specify number of cores (autodetect by default) # specificy destination file using scan numbers destination=/path/to/output/spaces/XYZ_xu_{first}-{last}.hdf5 overwrite = true ### choose an appropriate INPUT class and specify custom options [input] type = id03_xu:eh2 # refers to class EH2 in BINoculars/backends/id03_xu.py specfile=/DIRECTORY/hc1434/sixc_hc1434.spec imagefolder = /path/to/data/images/{rUCCD[0]} ## approximate number of images per job, only useful when running on the oar cluster target_weight = 4000 # technical details for this particular input class centralpixel = 366,328 ymask = 80-500 xmask = 182-500 sdd=636 #sample detector distance pixelsize=0.055, 0.055 ### choose PROJECTION plus resolution [projection] type = id03_xu:hklprojection # refers to HKLProjection in BINoculars/backends/id03_xu.py ## for L-scans (previous values) resolution = 0.002, 0.002, 0.0017 # or just give 1 number for all binoculars-0.0.10/examples/configs/example_config_io7000066400000000000000000000030311412510113200226430ustar00rootroot00000000000000### the DISPATCHER is responsible for job management [dispatcher] type = singlecore # run locally #ncores = 2 # optionally, specify number of cores (autodetect by default) #send_to_gui = true #host = 160.103.228.145 #port = 55294 # to use the OAR cluster: #type = oar #tmpdir = /some/globally/available/path #oarsub_options = walltime=0:15 # optionally, tweak oarsub parameters #executable = /path/to/custom/python /path/to/ivoxprocess # optionally, override default location of python and/or ivoxoar installation # specificy destination file using scan numbers destination = mesh_{first}_{last}.hdf5 overwrite = true # or, by default: numbered files in the form output_###.zpi: # destination = output.zpi #overwrite = false ### choose an appropriate INPUT class and specify custom options [input] type = io7:eh1 # refers to class EH1 in ivoxoar/backends/id03.py #specfile = test.spec datafilefolder = /home/willem/Documents/PhD/diamond imagefolder = /home/willem/Documents/PhD/diamond #wait_for_data = True ## approximate number of images per job, only useful when running on the oar cluster target_weight = 500 # technical yadayada for this particular input class centralpixel = 92, 215 xmask=130-330 ymask=14-165 pixelsize = 0.172, 0.172 sdd = 897 ### choose PROJECTION plus resolution [projection] #type = io7:gammadelta #resolution=0.01 type = io7:hklprojection # refers to HKLProjection in ivoxoar/backends/id03.py resolution = 0.001, 0.001, 0.001# or just give 1 number for all dimensions #limits = [:0,-1:,:], [0:,:-1,:], [:0,:-1,:], [0:,-1:,:] binoculars-0.0.10/examples/scripts/000077500000000000000000000000001412510113200172245ustar00rootroot00000000000000binoculars-0.0.10/examples/scripts/binoculars.mac000066400000000000000000000071351412510113200220550ustar00rootroot00000000000000global projection, resolution, binoculars_host, binoculars_port, configfilename binoculars_host = "160.103.228.220" binoculars_port = "58395" configfilename = "/users/onderwaa/ma2249/config_ma2249.txt" def binoculars '{ if ($# == 0){ print ("Usage: command (this works only for a number or variable, if you want to sepecify a range (140-150) use binoculars_str)") exit } local params, args n=split("$*",args) destination = (binoculars_host ":" binoculars_port) params["configfilename"] = configfilename params["command"] = $1 if (resolution != 0 ){ params["resolution"] = resolution } if (projection != 0){ params["projection"] = projection } for (i=1; i for example x[0]=h values ,x[1]=k values import numpy as np from os import path, getcwd from lmfit import Parameters from guidata.qt.QtCore import Qt from guidata.qt.QtGui import ( QListWidget, QListWidgetItem, QTableWidget, QTableWidgetItem, QLabel, QMenu, QAction, QCursor, QPixmap, QMessageBox, QFileDialog, QMainWindow, QSplitter, QWidget, QComboBox, QVBoxLayout, QHBoxLayout, ) from guiqwt.builder import PlotItemBuilder as GuiPlotItemBuilder from guiqwt.config import _ from guidata.configtools import add_image_path from guiqwt.curve import CurvePlot from guiqwt.histogram import ContrastAdjustment, lut_range_threshold from guiqwt.interfaces import IPanel from guiqwt.image import ImagePlot, ImageItem from guiqwt.plot import PlotManager from guiqwt._scaler import _histogram from guiqwt.styles import ImageParam from grafit.calculation import get_xywxwy_cxy from grafit.signals import ( SIG_TRY_CHANGED, SIG_TRIGGERED, SIG_MODEL_ADDED, SIG_MODEL_REMOVED, ) from grafit.tools import ( ShowWeightsTool, RunTool, SaveFitTool, PrefTool, CircularActionToolCXY, AddModelTool, MultiPointTool, ) from grafit.models import list_of_2D_models abspath = path.abspath(__file__) dirpath = path.dirname(abspath) # we add the current directory to have access to the images add_image_path(dirpath) ID_PARAMETER = "parameters" minimizationmethods = [ ("leastsq", "Levenberg-Marquardt"), ("least_squares", "Least-Squares"), ("differential_evolution", "Differential evolution"), ("brute", "Brute force"), ("nelder", "Nelder-Mead"), ("lbfgsb", "L-BFGS-B"), ("powell", "Powell"), # ('cg','Conjugate-Gradient'), :Jacobian is required # ('newton','Newton-Congugate-Gradient'), Jacobian is required ("cobyla", "Cobyla"), # ('tnc','Truncate Newton'),Jacobian is required # ('trust-ncg','Trust Newton-Congugate-Gradient'),Jacobian is required # ('dogleg','Dogleg'),Jacobian is required ("basinhopping", "Basin-hopping"), ("slsqp", "Sequential Linear Squares Programming"), ] def _nanmin(data): if data.dtype.name in ("float32", "float64", "float128"): return np.nanmin(data) else: return data.min() def _nanmax(data): if data.dtype.name in ("float32", "float64", "float128"): return np.nanmax(data) else: return data.max() class ImageNan(ImageItem): def auto_lut_scale(self): _min, _max = _nanmin(self.data), _nanmax(self.data) self.set_lut_range([_min, _max]) def auto_lut_scale_sym(self): _max = max(abs(_nanmin(self.data)), abs(_nanmax(self.data))) self.set_lut_range([-_max, _max]) def get_histogram(self, nbins): """interface de IHistDataSource""" if self.data is None: return [0,], [0, 1] if self.histogram_cache is None or nbins != self.histogram_cache[0].shape[0]: # from guidata.utils import tic, toc if False: # tic("histo1") res = np.histogram(self.data[~np.isnan(self.data)], nbins) # toc("histo1") else: # TODO: _histogram is faster, but caching is buggy # in this version # tic("histo2") _min = _nanmin(self.data) _max = _nanmax(self.data) if self.data.dtype in (np.float64, np.float32): bins = np.unique( np.array( np.linspace(_min, _max, nbins + 1), dtype=self.data.dtype ) ) else: bins = np.arange(_min, _max + 2, dtype=self.data.dtype) res2 = np.zeros((bins.size + 1,), np.uint32) _histogram(self.data.flatten(), bins, res2) # toc("histo2") res = res2[1:-1], bins self.histogram_cache = res else: res = self.histogram_cache return res class PlotItemBuilder(GuiPlotItemBuilder): def __init__(self): super(PlotItemBuilder, self).__init__() def imagenan( self, data=None, filename=None, title=None, alpha_mask=None, alpha=None, background_color=None, colormap=None, xdata=[None, None], ydata=[None, None], pixel_size=None, center_on=None, interpolation="linear", eliminate_outliers=None, xformat="%.1f", yformat="%.1f", zformat="%.1f", ): """ Make an image `plot item` from data (:py:class:`guiqwt.image.ImageItem` object or :py:class:`guiqwt.image.RGBImageItem` object if data has 3 dimensions) """ assert isinstance(xdata, (tuple, list)) and len(xdata) == 2 assert isinstance(ydata, (tuple, list)) and len(ydata) == 2 param = ImageParam(title="Image", icon="image.png") data, filename, title = self._get_image_data( data, filename, title, to_grayscale=True ) assert data.ndim == 2, "Data must have 2 dimensions" if pixel_size is None: assert center_on is None, ( "Ambiguous parameters: both `center_on`" " and `xdata`/`ydata` were specified" ) xmin, xmax = xdata ymin, ymax = ydata else: xmin, xmax, ymin, ymax = self.compute_bounds(data, pixel_size, center_on) self.__set_image_param( param, title, alpha_mask, alpha, interpolation, background=background_color, colormap=colormap, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, xformat=xformat, yformat=yformat, zformat=zformat, ) image = ImageNan(data, param) image.set_filename(filename) if eliminate_outliers is not None: image.set_lut_range(lut_range_threshold(image, 256, eliminate_outliers)) return image make = PlotItemBuilder() class ExtendedParams: # dictionnaire de la liste des courbes pour le fit # utilise pour comprendre les Parameters de lmfit # ne pilote jamais le tracage des courbes et l'ecriture dans le tableau def __init__(self): self.reset() def reset(self): self.modelclasses = ( list() ) # list of the Model classes that compose the Model for fitting the data self.models = ( list() ) # list of the Model instances that compose the Model for fitting the data self.composite_model = None self.ncv = len(self.models) self.partry = Parameters() # parametres d'essai self.paropt = Parameters() # parametres optimises self.tags = [] self.method = "leastsq" self.chi2opt = None self.dataimage = None # associated data to be fitted self.isfitted = False # is the data fitted? self.issaved = False # has the fit result been saved? self.tags = [] # tags is a list of (name,value) def printparams(self): print("printparams") for key in self.partry.keys(): print(key, self.partry[key].value, self.partry[key].vary) def make_composite_model(self, partry=True): # return a composite model based on the list of modelclasses # restart the building of models and make new try parameters if partry==True self.models = [] if len(self.modelclasses) == 0: self.composite_model = None return None else: model = self.modelclasses[0](prefix="cv0_") self.models.append(model) self.composite_model = self.models[0] if len(self.modelclasses) > 2: for k, modelclass in enumerate(self.modelclasses[1:], 1): model = self.modelclasses[0](prefix="cv%d_" % k) self.models.append(model) self.composite_model = self.composite_model + model if partry: self.partry = self.composite_model.make_params() self.update_tags(self.tags) def add_model_class(self, modelclass, paramvalues=None): # ajoute une courbe dans la liste parametres self.modelclasses.append(modelclass) k = len(self.modelclasses) - 1 model = modelclass(prefix="cv%d_" % k) self.models.append(model) if k > 0: # there is already a model self.composite_model = self.composite_model + model else: self.composite_model = model self.partry = self.composite_model.make_params() self.update_tags(self.tags) return model def remove_model_class(self, k): correspondance = range(k) + [-1] + range(k, len(self.modelclasses) - 1) self.modelclasses.pop(k) self.make_composite_model(partry=False) self.rebuild_partry(correspondance) def rebuild_partry(self, correspondance): # rebuild a set of try parameters from correspondance between indices # correspondance is the list of old indices pointing toward new ones or nothing if equal to -1 # first we set values and restrains parameters = Parameters() for old_entry in self.partry: k = self.cvnumber(old_entry) if k is not None: # this is a curve if correspondance[k] >= 0: # otherwise the curve has been suppressed old_txt = "cv%d" % k new_txt = "cv%d" % correspondance[k] new_entry = old_entry.replace(old_txt, new_txt) parameters.add( new_entry, value=self.partry[old_entry].value, vary=self.partry[old_entry].vary, min=self.partry[old_entry].min, max=self.partry[old_entry].max, ) else: # this is a fixed parameter (tag) parameters.add( old_entry, value=self.partry[old_entry].value, vary=self.partry[old_entry].vary, min=self.partry[old_entry].min, max=self.partry[old_entry].max, ) # then we set expressions that also have to be converted for old_entry in self.partry: j = self.cvnumber(old_entry) if j is not None: # this is a curve if correspondance[j] >= 0: # otherwise the curve has been suppressed old_txt = "cv%d" % j new_txt = "cv%d" % correspondance[j] new_entry = old_entry.replace(old_txt, new_txt) # new entry new_expr = self.partry[old_entry].expr # start with old expression if new_expr is not None and len(new_expr) > 0: for k in correspondance: old_txt = "cv%d" % k if old_txt in new_expr: if correspondance[k] == -1: # the curve referenced in expression has been removed! we cannot maintain the expression new_expr = "" else: new_txt = "cv%d" % correspondance[k] new_expr = new_expr.replace(old_txt, new_txt) parameters[new_entry].set(expr=new_expr) self.partry = parameters self.update_tags(self.tags) def set_dataimage(self, dataimage): self.dataimage = dataimage self.chi2opt = None self.isfitted = None self.issaved = None self.update_tags(dataimage.tags) def cvnumber(self, entry): if entry.startswith("cv"): # return the number of the curve return int(entry.split("_")[0][2:]) else: # entry is not a curve parameter return None def freezecurve(self, k): # freeze the given curve parameters for name in self.models[k].param_names(): self.partry[name].vary = False def releasecurve(self, k): # release the given curve parameters for name in self.models[k].param_names(): self.partry[name].vary = True def update_from_estimate(self, estimate): for p, v in estimate.iteritems(): if p in self.partry.keys(): self.partry[p].value = v def scale_from_estimate(self, estimate): for p, change in estimate.iteritems(): if p in self.partry.keys(): self.partry[p].value = change[1] * self.partry[p].value + change[0] def set_whole_as_try(self): for kk in self.partry: if kk in self.paropt: self.partry[kk].value = self.paropt[kk].value def eval_try(self, x): return self.composite_model.eval(self.partry, x=x) def eval_opt(self, x): return self.composite_model.eval(self.paropt, x=x) def do_fit(self, data, x, weights): if self.composite_model is None: return try: result = self.composite_model.fit( data, params=self.partry, method=self.method, weights=weights, x=x ) except Exception as e: print(e) QMessageBox.warning(self, "error", "error while trying to fit") self.paropt = result.params self.chi2opt = result.chisqr if result.success: self.isfitted = True self.issaved = False def eval_integrals_opt(self): # evaluate integrals of the different peaks return self.composite_model.eval_component_integrals(self.paropt) def save_opt_results(self): # return lines describing the results that may be saved in a result file # we compute the integrals # writing names of fitting parameters if self.chi2opt is None: return lig = "title " for kk in self.paropt: lig = lig + kk + " " integrals = self.eval_integrals_opt() print("computation of integrals", integrals) # OrderedDict of {prefix: (integral,error)} intnames = "" intvalues = "" interrors = "" for prefix in integrals.keys(): intnames = intnames + prefix + "integral " value = integrals[prefix] intvalues = intvalues + "%g " % (value[0]) interrors = interrors + "%g " % (value[1]) lig = lig + intnames + "chi2 model \n" # writing names of fitting parameters title = self.dataimage.title lig = lig + title + " " for kk in self.partry: lig = lig + "%g " % (self.paropt[kk].value) lig = lig + intvalues + "%g " % (self.chi2opt) model = self.composite_model.name model = model.replace(" ", "") lig = lig + model + "\n" # print lig # on ecrit une ligne avec les erreurs lig = lig + "sigma " for kk in self.partry: if self.paropt[kk].stderr is None: lig = lig + "None " else: lig = lig + "%g " % (self.paropt[kk].stderr) lig = lig + interrors + "0. " # no uncertainty on chi2 lig = lig + model + "\n" return lig def update_tags(self, tags): # on supprime les entrees precedentes: for tag in self.tags: # remove old list name = tag[0] if name in self.partry.keys(): del self.partry[name] # we suppress the old tag entries for tag in tags: # put new list name = tag[0] self.partry.add( name, value=tag[1], vary=False ) # we reinitialize the tag entries self.tags = tags class DataImage: # a class for describing 2D data to be fitted # contains x ranges, y ranges, values, errors def __init__( self, data, x_range=[0, 1], y_range=[0, 1], weights=None, title="", tags=[], xlabel="x", ylabel="y", ): self.data = data self.x_range = x_range self.y_range = y_range if weights is None: weights = np.ones_like(data) self.weights = weights self.title = title self.tags = tags # list of fixed parameters (name,value) associated to the data self.xlabel = xlabel self.ylabel = ylabel self.rf0 = np.sum( self.data[np.nonzero(np.isfinite(self.data))] * self.weights[np.nonzero(np.isfinite(self.data))] ) class ImageList(QListWidget): """ A specialized QListWidget that displays the list of all images that could be fitted """ def __init__(self, parent=None): QListWidget.__init__(self, parent) self.setSelectionMode(1) self.dataimages = [] def add_item(self, dataimage): """ add a dataimage item to the list """ self.dataimages.append(dataimage) item = QListWidgetItem(self) item.setText(dataimage.title) class FunctionList(QListWidget): """ A specialized QListWidget that displays the list of functions used in the model """ def __init__(self, parent=None): QListWidget.__init__(self, parent) self.setSelectionMode(1) self.current_item_number = -1 self.initcontexts() self.currentRowChanged.connect(self.row_changed) self.itemClicked.connect(self.item_clicked) def add_item(self, function_name): """ add a function name to the list """ item = QListWidgetItem(self) item.setText(function_name) self.current_item_number = self.count() - 1 def row_changed(self, i): # activated when selected row has changed self.current_item_number = i def item_clicked(self): self.context.popup(QCursor.pos()) def initcontexts(self): # menu pour la colonne 0 self.context = QMenu(self) self.removeAction = QAction("Remove", self) self.context.addAction(self.removeAction) self.connect(self.removeAction, SIG_TRIGGERED, self.remove_item) def remove_item(self): i = self.currentRow() self.takeItem(i) self.emit(SIG_MODEL_REMOVED, i) def initialize(self, function_names): self.clear() for function_name in function_names: self.add_item(function_name) class ParameterTable(QTableWidget): __implements__ = (IPanel,) PANEL_ID = ID_PARAMETER PANEL_TITLE = "parameters" PANEL_ICON = None # string # objet qui gere les entree de parametres, l'affichage des courbes, le lancement des fits, l'enregistrement du resultat def __init__(self, parent=None, extendedparams=None): QTableWidget.__init__(self, parent=parent) self.setColumnCount(8) self.setHorizontalHeaderLabels( ( "Parameters", "Estimation", "Fit result", "Sigma", "Restrains", "Min", "Max", "Expression", ) ) if extendedparams == None: self.extendedparams = ExtendedParams() else: self.extendedparams = extendedparams # on remet le tableau a zero : deux rangees pour un polynome du 1er degre self.reset() # on definit les differents menus contextuels self.initcontexts() # au cas ou self.int1 = 0 # On connecte les signaux self.cellChanged.connect(self.cellChanged) self.cellPressed.connect(self.cellPressed) self.horizontalHeader().sectionPressed.connect(self.sectionClicked) # pas de fit sauve self.isfitted = False self.issaved = False self.tags = list() self.saveint = False self.ints = [] self.updatestart = ( False # si coche, on active setwholeastry apres enregistrement ) self.cwd = abspath def register_plot(self, baseplot): pass def register_panel(self, manager): """Register panel to plot manager""" pass def configure_panel(self): """Configure panel""" pass def reset(self): self.extendedparams.reset() self.initparams(settry=True) self.savename = None self.isfitted = False def inititems(self, listrow): # met pour les lignes i de listrow un item vide non editable pour colonnes 0,2,3,4 for i in listrow: for j in range(8): self.setItem(i, j, QTableWidgetItem(0)) self.item(i, 0).setFlags(Qt.ItemFlags(33)) self.item(i, 2).setFlags(Qt.ItemFlags(33)) self.item(i, 3).setFlags(Qt.ItemFlags(33)) self.item(i, 4).setFlags(Qt.ItemFlags(33)) def initparams(self, settry=False, setopt=False): blocked = self.blockSignals(True) # on redessine le tableau a l'aide du dictionnaire extendedparams # si settry, on remplit aussi les valeurs d'essai, les bornes, les expressions oldrc = self.rowCount() newrc = len(self.extendedparams.partry) self.setRowCount(newrc) if newrc > oldrc: # on rajoute des rangees vides self.inititems(range(oldrc, newrc)) n = 0 for entry, value in self.extendedparams.partry.iteritems(): self.setrow(entry, n, settry, setopt) n += 1 self.blockSignals(blocked) def setrow(self, nom, n, settry=True, setopt=False): blocked = self.blockSignals(True) ptry = self.extendedparams.partry popt = self.extendedparams.paropt self.item(n, 0).setText(nom) if settry: vtry = ptry[nom].value mintry = ptry[nom].min maxtry = ptry[nom].max print(nom, mintry, maxtry) varytry = ptry[nom].vary exprtry = ptry[nom].expr self.item(n, 1).setText("%f" % (vtry)) if exprtry is not None: self.item(n, 4).setText("Expr") self.item(n, 7).setText(exprtry) elif varytry is True: self.item(n, 4).setText("Free") else: self.item(n, 4).setText("Fixed") if mintry is not None: self.item(n, 5).setText("%f" % (mintry)) if maxtry is not None: self.item(n, 6).setText("%f" % (maxtry)) if setopt: vopt = popt[nom].value self.item(n, 2).setText("%f" % (vopt)) verr = popt[nom].stderr if verr is not None: self.item(n, 3).setText("%f" % (verr)) else: self.item(n, 2).setText("") self.item(n, 3).setText("") self.blockSignals(blocked) # going back to previous setting def initcontexts(self): # initialise une liste de menus contextuels pour les cases, associes a chaque colonne self.contexts = list(None for i in range(self.columnCount())) # initialise une liste de menus contextuels pour les sectionheaders, associes a chaque colonne self.Contexts = list(None for i in range(self.columnCount())) # menu pour la colonne 0 context = QMenu(self) self.setastry = QAction("Set as try", self) context.addAction(self.setastry) self.connect(self.setastry, SIG_TRIGGERED, self.set_as_try) self.showfit = QAction("Display Fit", self) context.addAction(self.showfit) self.connect(self.showfit, SIG_TRIGGERED, self.show_fit) self.showtry = QAction("Display Try", self) context.addAction(self.showtry) self.connect(self.showtry, SIG_TRIGGERED, self.show_try) self.contexts[2] = context # menu pour la colonne 4 context = QMenu(self) self.setfixed = QAction("Fixed", self) context.addAction(self.setfixed) self.connect(self.setfixed, SIG_TRIGGERED, self.set_fixed) self.setfree = QAction("Free", self) context.addAction(self.setfree) self.connect(self.setfree, SIG_TRIGGERED, self.set_free) self.useexpr = QAction("Expr", self) context.addAction(self.useexpr) self.connect(self.useexpr, SIG_TRIGGERED, self.use_expr) self.contexts[4] = context # menu pour le header 2 context = QMenu(self) self.setwholeastry = QAction("Set whole as try", self) context.addAction(self.setwholeastry) self.connect(self.setwholeastry, SIG_TRIGGERED, self.set_whole_as_try) self.Contexts[2] = context # menu pour le header 4 context = QMenu(self) self.inverserestrains = QAction("Inverse restrains", self) context.addAction(self.inverserestrains) self.connect(self.inverserestrains, SIG_TRIGGERED, self.inverse_restrains) self.fixall = QAction("All fixed", self) context.addAction(self.fixall) self.connect(self.fixall, SIG_TRIGGERED, self.fix_all) self.releaseall = QAction("All free", self) context.addAction(self.releaseall) self.connect(self.releaseall, SIG_TRIGGERED, self.release_all) self.Contexts[4] = context def cellPressed(self, int1, int2): self.int1 = int1 context = self.contexts[int2] if context is not None: context.popup(QCursor.pos()) def cellChanged(self, int1, int2): if int2 == 1: # on change la valeur du parametre txt = str(self.item(int1, 1).text()) entry = str(self.item(int1, 0).text()) try: self.extendedparams.partry[entry].value = float(txt) self.emit(SIG_TRY_CHANGED) except ValueError: QMessageBox.warning("warning", "unable to convert text to float") if int2 == 5: # on change la borne inferieure txt = str(self.item(int1, 5).text()) entry = str(self.item(int1, 0).text()) try: self.extendedparams.partry[entry].min = float(txt) except ValueError: QMessageBox.warning("warning", "unable to convert text to float") if int2 == 6: # on change la borne superieure txt = str(self.item(int1, 6).text()) entry = str(self.item(int1, 0).text()) try: self.extendedparams.partry[entry].max = float(txt) except ValueError: QMessageBox.warning("warning", "unable to convert text to float") if int2 == 7: # on change l'expression txt = str(self.item(int1, 7).text()) entry = str(self.item(int1, 0).text()) if len(txt) == 0: self.extendedparams.partry[entry].expr = None self.extendedparams.partry[entry].vary = True else: self.extendedparams.partry[entry].expr = txt self.item(int1, 4).setText("Expr") def sectionClicked(self, int2): # quand une colonne entiere est selectionnee valable pour les colonnes 2 et 4 context = self.Contexts[int2] if context is not None: context.popup(QCursor.pos()) def freeze_cv(self): # on met fixe tous les parametres d'une courbe entry = str(self.item(self.int1, 0).text()) icurve = self.extendedparams.cv_number(entry) self.extendedparams.freezecurve(icurve) self.initparams(settry=True, setopt=True) def release_cv(self): # on met fixe tous les parametres d'une courbe entry = str(self.item(self.int1, 0).text()) icurve = self.extendedparams.cv_number(entry) self.extendedparams.releasecurve(icurve) self.initparams(settry=True, setopt=True) def release_all(self): # on met fixe tous les parametres du fond for int1 in range(self.rowCount()): item = self.item(int1, 4) item.setText("Free") entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = True def fix_all(self): # on met fixe tous les parametres du fond for int1 in range(self.rowCount()): item = self.item(int1, 4) item.setText("Fixed") entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = False def set_cv(self, curvetype=None, params=None, bg=0.0): # icurve numero de la courbe a changer le cas echeant -1 c'est la derniere pass def rm_cv(self): # on supprime la courbe selectionnee pass def ch_cv(self): # a partir de la souris, on retrace la courbe selectionnee pass def show_fit(self): pass def show_try(self): pass def set_fixed(self): item = self.item(self.int1, 4) item.setText("Fixed") entry = str(self.item(self.int1, 0).text()) self.extendedparams.partry[entry].vary = False self.extendedparams.partry[entry].expr = None def set_free(self): item = self.item(self.int1, 4) item.setText("Free") entry = str(self.item(self.int1, 0).text()) self.extendedparams.partry[entry].vary = True self.extendedparams.partry[entry].expr = None def use_expr(self): item = self.item(self.int1, 4) item.setText("Expr") entry = str(self.item(self.int1, 0).text()) txt = str(self.item(self.int1, 7).text()) self.extendedparams.partry[ entry ].express = txt # in principle, should have been already set if len(txt) != 0: # on met l'expression indiquee self.extendedparams.partry[entry].expr = txt self.extendedparams.partry[entry].vary = False def inverse_restrains(self): # inverse les contraintes: les parametres libres deviennent fixes et vice-versa for int1 in range(self.rowCount()): item = self.item(int1, 4) entry = str(self.item(int1, 0).text()) vary = self.extendedparams.partry[entry].vary if vary: self.extendedparams.partry[entry].vary = False item.setText("Fixed") else: self.extendedparams.partry[entry].vary = True item.setText("Free") def set_whole_as_try(self): self.extendedparams.set_whole_as_try() self.initparams(settry=True, setopt=True) def set_as_try(self): item = self.item(self.int1, 1) entry = str(self.item(self.int1, 0).text()) if entry in self.extendedparams.paropt: self.extendedparams.partry[entry].value = self.extendedparams.paropt[ entry ].value item.setText("%f" % (self.extendedparams.paropt[entry].value)) self.drawtry() # this widget does nothing by itself but contains a layout for plots class LayoutWidget(QWidget): def __init__(self, parent, orientation="horizontal"): self.parent = parent QWidget.__init__(self, parent=parent) if orientation == "horizontal": self.layout = QHBoxLayout() else: self.layout = QVBoxLayout() self.setLayout(self.layout) def addWidget(self, widget): self.layout.addWidget(widget) def save_widget(self, fname): """Grab widget's window and save it to filename (*.png)""" pixmap = QPixmap.grabWidget(self) pixmap.save(fname, "PNG") class FitWidget(QWidget): def __init__(self, parent=None, extendedparams=None): QWidget.__init__(self, parent=parent) self.extendedparams = extendedparams layout = QVBoxLayout() self.setLayout(layout) self.method = QComboBox(self) for couple in minimizationmethods: self.method.addItem(couple[1]) self.method.currentIndexChanged.connect(self.index_changed) self.chi2 = QLabel("chi2=") self.rf = QLabel("Rf=") self.di = QLabel("dI=") # signal connected in rodeo layout.addWidget(self.method) layout.addWidget(self.chi2) layout.addWidget(self.rf) layout.addWidget(self.di) def set_method_number(self, i): self.method.setCurrentIndex(i) def set_method_name(self): i = self.extendedparams.methods.index(self.extendedparams.method) self.method.setCurrentIndex(i) def index_changed(self, int): self.extendedparams.method = minimizationmethods[int][0] def set_chi2(self, chi2, rf, di): self.chi2.setText("chi2=%g" % chi2) self.rf.setText("Rf=%g" % rf) self.di.setText("dI=%g" % di) def set_difference(self, delta): self.difference.setText("DI=%g" % delta) def add_mem(self): # we memorize the current fit result, if any pass def remove_mem(self): # we suppress the corresponding entry pass # this window holds the toolbar and the centralwidget class Fit2DWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.extendedparams = ExtendedParams() self.configure_splitter() self.set_default_images() self.setGeometry(10, 30, 1260, 950) toolbar = self.addToolBar("tools") self.manager.add_toolbar(toolbar, id(toolbar)) # widget.manager.set_default_toolbar(toolbar) self.register_tools() self.create_connect() self.set_default_params() self.cwd = getcwd() self.savename = None # name where to save the fit def set_default_images(self): self.data_image = make.imagenan( np.zeros((1, 1)), title="data image", xdata=[0, 1], ydata=[0, 1] ) self.model_image = make.imagenan( np.zeros((1, 1)), title="model image", xdata=[0, 1], ydata=[0, 1] ) self.difference_image = make.imagenan( np.zeros((1, 1)), title="difference image", xdata=[0, 1], ydata=[0, 1], colormap="RdBu", ) z = np.empty((0,)) self.data_hprofile = make.curve( z, z, title="data horizontal profile", marker="Diamond", markerfacecolor="r", markeredgecolor="r", markersize=4, linestyle="NoPen", ) self.data_vprofile = make.curve( z, z, title="data vertical profile", marker="Diamond", markerfacecolor="r", markeredgecolor="r", markersize=4, linestyle="NoPen", ) self.model_hprofile = make.curve( z, z, title="model horizontal profile", color="k" ) self.model_vprofile = make.curve( z, z, title="model vertical profile", color="k" ) self.difference_hprofile = make.curve( z, z, title="model horizontal difference", color="b" ) self.difference_vprofile = make.curve( z, z, title="model vertical difference", color="b" ) self.data_plot.add_item(self.data_image) self.model_plot.add_item(self.model_image) self.difference_plot.add_item(self.difference_image) self.hprofile.add_item(self.data_hprofile) self.hprofile.add_item(self.model_hprofile) self.hprofile.add_item(self.difference_hprofile) self.vprofile.add_item(self.data_vprofile) self.vprofile.add_item(self.model_vprofile) self.vprofile.add_item(self.difference_vprofile) self.data = None def configure_splitter(self): centralwidget = QSplitter(Qt.Horizontal, self) splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(centralwidget) centralwidget.addWidget(splitter) self.data_plot = ImagePlot( self, title="data", yreverse=False, lock_aspect_ratio=False, xunit="", yunit="", ) self.model_plot = ImagePlot( self, title="model", yreverse=False, lock_aspect_ratio=False, xunit="", yunit="", ) self.difference_plot = ImagePlot( self, title="difference", yreverse=False, lock_aspect_ratio=False, xunit="", yunit="", ) self.contrast = ContrastAdjustment(self) self.hprofile = CurvePlot(self, title="horizontal profile") self.vprofile = CurvePlot(self, title="vertical profile") # adding layout widget with 3 plots and itemlist self.imagelayoutw = LayoutWidget(self, "horizontal") self.imagelayoutw.addWidget(self.data_plot) self.imagelayoutw.addWidget(self.model_plot) self.imagelayoutw.addWidget(self.difference_plot) splitter.addWidget(self.imagelayoutw) # adding layout widget with 3 plots with profiles self.plotlayoutw = LayoutWidget(self, "horizontal") self.plotlayoutw.addWidget(self.contrast) self.plotlayoutw.addWidget(self.hprofile) self.plotlayoutw.addWidget(self.vprofile) splitter.addWidget(self.plotlayoutw) # adding parameter table panel self.parametertable = ParameterTable(self, extendedparams=self.extendedparams) splitter.addWidget(self.parametertable) # adding a vertical layout with itemlist, fitwidget, functionlist self.vlayoutw = LayoutWidget(self, "vertical") self.image_list = ImageList(self) self.fitwidget = FitWidget(self, self.extendedparams) self.function_list = FunctionList(self) self.vlayoutw.addWidget(self.image_list) self.vlayoutw.addWidget(self.fitwidget) self.vlayoutw.addWidget(self.function_list) centralwidget.addWidget(self.vlayoutw) # registring panels to the plot manager self.manager = PlotManager(self) for plot in ( self.data_plot, self.model_plot, self.difference_plot, self.hprofile, self.vprofile, ): self.manager.add_plot(plot) for panel in (self.contrast, self.parametertable): self.manager.add_panel(panel) def register_tools(self): self.manager.register_all_image_tools() self.addmodeltool = self.manager.add_tool(AddModelTool, list_of_2D_models) self.spottool = self.manager.add_tool( CircularActionToolCXY, self.estimate_spot, self.scale_spot ) self.leveltool = self.manager.add_tool(MultiPointTool, self.estimate_bg) self.runtool = self.manager.add_tool(RunTool) self.savefittool = self.manager.add_tool(SaveFitTool) self.preftool = self.manager.add_tool(PrefTool) self.showweightstool = self.manager.add_tool(ShowWeightsTool) def estimate_bg(self, plot, pts): if plot == self.data_plot: intensities = [] for pt in pts: intensities.append(self.data_image.get_data(pt[0], pt[1])) i = self.function_list.current_item_number # number of the curve to ajust prefix = "cv%d_" % i estimated_values = {prefix + "bg": np.mean(np.array(intensities))} if len(pts) > 2: # try: mat = np.ones((3, 3)) mat[:, 0:2] = pts sol = np.linalg.solve(mat, intensities) estimated_values = { prefix + "slope_x": sol[0], prefix + "slope_y": sol[1], prefix + "bg": sol[2], } # except np.linalg.LinAlgError: # pass self.extendedparams.update_from_estimate(estimated_values) self.parametertable.initparams(settry=True) self.update_try() def estimate_spot(self, plot, p0, p1): # from graphical positions of the circle drawn, estimate the shape of the spot loc0, loc1, width0, width1 = get_xywxwy_cxy(plot, p0, p1) i = self.function_list.current_item_number # number of the curve to ajust prefix = "cv%d_" % i estimated_values = { prefix + "loc0": loc0, prefix + "loc1": loc1, prefix + "width0": width0, prefix + "width1": width1, } self.extendedparams.update_from_estimate(estimated_values) self.parametertable.initparams(settry=True) self.update_try() def scale_spot(self, plot, key): i = self.function_list.current_item_number # number of the curve to ajust prefix = "cv%d_" % i zaxis = plot.colormap_axis # _min,_max= _min, _max = plot.get_axis_limits(zaxis) shift = 0.0 scale = 1.0 if key == 42: scale = 1.1 elif key == 47: scale = 1.0 / 1.1 if key == 43: shift = (_max - _min) / 10.0 elif key == 45: shift = -(_max - _min) / 10.0 estimated_values = { prefix + "amp": (shift, scale), prefix + "bg": (shift, scale), } self.extendedparams.scale_from_estimate(estimated_values) self.parametertable.initparams(settry=True) self.update_try() def create_connect(self): self.connect(self.parametertable, SIG_TRY_CHANGED, self.update_try) self.connect(self.addmodeltool, SIG_MODEL_ADDED, self.add_model_class) self.runtool.SIG_VALIDATE_TOOL.connect(self.do_fit) self.savefittool.SIG_VALIDATE_TOOL.connect(self.save_fit) self.image_list.currentRowChanged.connect(self.image_list_row_changed) self.connect(self.preftool.showtagsaction, SIG_TRIGGERED, self.show_tags) self.connect( self.preftool.saveprefaction, SIG_TRIGGERED, self.set_save_filename ) self.connect(self.function_list, SIG_MODEL_REMOVED, self.remove_model) self.showweightstool.SIG_VALIDATE_TOOL.connect(self.show_weigths) self.connect(self.preftool.showdiffaction, SIG_TRIGGERED, self.show_diff) def set_default_params(self): # create a fit with a linear bg and a lorenzian polar function self.add_model_class(0) self.add_model_class(1) def add_model_class(self, i): modelclass = self.addmodeltool.list_of_models[i] added_model = self.extendedparams.add_model_class(modelclass) self.function_list.add_item(added_model.name) self.parametertable.initparams(settry=True) if self.data is not None: self.update_try() def remove_model(self, i): self.extendedparams.remove_model_class(i) self.parametertable.initparams( settry=True ) # refill the parametertable with new values self.function_list.initialize( list((model.name for model in self.extendedparams.models)) ) # refill the function table def set_save_filename(self): self.savename = str( QFileDialog.getSaveFileName( None, "Save fit parameters", self.cwd, filter="*.txt" ) ) if len(self.savename) == 0: self.savename = None else: self.cwd = path.dirname(path.realpath(self.savename)) def save_fit(self): # sauvegarde des donnees if not self.extendedparams.isfitted: # no fit has been performed! return if self.savename is None: self.set_save_filename() # definit le nom de fichier if self.savename is None: return fic = open(self.savename, "a") fic.write(self.extendedparams.save_opt_results()) fic.close() # on sauve la figure de fit ific = 1 figshortname = self.dataimage.title + "_%.3d.png" % (ific) figname = path.join(self.cwd, figshortname) while path.isfile(figname): ific = ific + 1 figshortname = self.dataimage.title + "_%.3d.png" % (ific) figname = path.join(self.cwd, figshortname) print(figname, " sauve") self.imagelayoutw.save_widget(figname) if self.preftool.restartaction.isChecked(): self.extendedparams.set_whole_as_try() self.parametertable.initparams(settry=True) self.extendedparams.issaved = True def show_tags(self): pass def add_data( self, data, x_range=[0.0, 1.0], y_range=[0.0, 1.0], weights=None, title="", tags=[], xlabel="x", ylabel="y", ): dataimage = DataImage( data, x_range, y_range, weights, title, tags, xlabel, ylabel ) self.image_list.add_item(dataimage) self.dataimage = dataimage # pointer to the current dataimage self.set_image(dataimage) def image_list_row_changed(self, i): # the current image list row has changed self.dataimage = self.image_list.dataimages[i] if self.extendedparams.isfitted and not self.extendedparams.issaved: # a fit has been performed but not saved i = QMessageBox.question( self, "save", "do you want to save the fit?", "Yes", "No", "Cancel" ) if i == 0: self.save_fit() self.set_image(self.dataimage) def set_image(self, dataimage): # set image to fit where dataimage contains data: a 2D array, xdata and ydata: the limits, weights: 1/sigma # update image representation self.data_image.set_xdata(*dataimage.x_range) self.data_image.set_ydata(*dataimage.y_range) self.data_image.set_data( np.array(dataimage.data) ) # ImageItem.set_data does not make a copy self.data_plot.set_title(dataimage.title) self.model_image.set_xdata(*dataimage.x_range) self.model_image.set_ydata(*dataimage.y_range) self.model_image.set_data(np.array(dataimage.data)) self.difference_image.set_xdata(*dataimage.x_range) self.difference_image.set_ydata(*dataimage.y_range) self.difference_image.set_data(np.array(dataimage.data)) # update axis labels self.data_plot.set_axis_title("left", dataimage.ylabel) self.data_plot.set_axis_title("bottom", dataimage.xlabel) self.model_plot.set_axis_title("left", dataimage.ylabel) self.model_plot.set_axis_title("bottom", dataimage.xlabel) self.difference_plot.set_axis_title("left", dataimage.ylabel) self.difference_plot.set_axis_title("bottom", dataimage.xlabel) # update values to be fitted self.fit_indices = np.nonzero(np.isfinite(dataimage.data)) # indices to fit self.model_image.data[self.fit_indices] = 0.0 # start from 0 self.data = dataimage.data[self.fit_indices] # value at indices, 1D array! if dataimage.weights is not None: self.weights = dataimage.weights[ self.fit_indices ] # error at indices, 1D array! else: self.weights = None sx = dataimage.data.shape[1] sy = dataimage.data.shape[0] fx = (dataimage.x_range[1] - dataimage.x_range[0]) / sx fy = (dataimage.y_range[1] - dataimage.y_range[0]) / sy # we put first x and y in the list self.x = [ self.fit_indices[1] * fx + dataimage.x_range[0] + fx / 2.0, self.fit_indices[0] * fy + dataimage.y_range[0] + fy / 2.0, ] hrange = np.arange(dataimage.x_range[0] + fx / 2, dataimage.x_range[1], fx) vrange = np.arange(dataimage.y_range[0] + fy / 2, dataimage.y_range[1], fy) array1 = np.nan_to_num(dataimage.data) array2 = np.isfinite(dataimage.data) sum1 = np.sum(array1, axis=0) / np.sum(array2, axis=0) sum2 = np.sum(array1, axis=1) / np.sum(array2, axis=1) hrange = hrange[np.isfinite(sum1)] vrange = vrange[np.isfinite(sum2)] self.data_hprofile.set_data(hrange, sum1[np.isfinite(sum1)]) self.data_vprofile.set_data(vrange, sum2[np.isfinite(sum2)]) if self.preftool.scaleprefaction.isChecked(): self.data_plot.do_autoscale(replot=True) else: self.data_plot.replot() self.data_plot.update_colormap_axis(self.data_image) self.extendedparams.set_dataimage(dataimage) self.parametertable.initparams( settry=True ) # if tags have changed, put them into the table self.update_try() def show_weigths(self): if self.showweightstool.action.isChecked(): self.data_image.set_data(self.dataimage.weights) else: self.data_image.set_data(self.dataimage.data) self.data_plot.replot() def show_diff(self): self.difference_hprofile.setVisible(self.preftool.showdiffaction.isChecked()) self.difference_vprofile.setVisible(self.preftool.showdiffaction.isChecked()) self.hprofile.replot() self.vprofile.replot() def update_try(self): # evaluate model with try parameters and update model and difference image values = self.extendedparams.eval_try(self.x) self.model_image.data[self.fit_indices] = values diff = values - self.data self.difference_image.data[self.fit_indices] = diff diff = diff * self.weights chi2 = np.sum((diff) ** 2) di = np.sum((diff)) rf = np.sum(np.absolute(diff)) / self.dataimage.rf0 self.fitwidget.set_chi2(chi2, rf, di) self.update_images_and_profiles() def update_opt(self): # evaluate model with try parameters and update model and difference image values = self.extendedparams.eval_opt(self.x) self.model_image.data[self.fit_indices] = values diff = values - self.data self.difference_image.data[self.fit_indices] = diff diff = diff * self.weights chi2 = np.sum((diff) ** 2) di = np.sum((diff)) rf = np.sum(np.absolute(diff)) / self.dataimage.rf0 self.fitwidget.set_chi2(chi2, rf, di) self.update_images_and_profiles() def update_images_and_profiles(self): hrange = self.data_hprofile.get_data()[0] vrange = self.data_vprofile.get_data()[0] array1 = np.nan_to_num(self.model_image.data) array2 = np.isfinite(self.model_image.data) sum1 = np.sum(array1, axis=0) / np.sum(array2, axis=0) sum2 = np.sum(array1, axis=1) / np.sum(array2, axis=1) self.model_hprofile.set_data(hrange, sum1[np.isfinite(sum1)]) self.model_vprofile.set_data(vrange, sum2[np.isfinite(sum2)]) array1 = np.nan_to_num(self.difference_image.data) array2 = np.isfinite(self.difference_image.data) sum1 = np.sum(array1, axis=0) / np.sum(array2, axis=0) sum2 = np.sum(array1, axis=1) / np.sum(array2, axis=1) self.difference_hprofile.set_data(hrange, sum1[np.isfinite(sum1)]) self.difference_vprofile.set_data(vrange, sum2[np.isfinite(sum2)]) if self.preftool.samescaleaction.isChecked(): _min, _max = _nanmin(self.dataimage.data), _nanmax(self.dataimage.data) self.model_image.set_lut_range([_min, _max]) elif self.preftool.scaleprefaction.isChecked(): self.model_image.auto_lut_scale() if self.preftool.scaleprefaction.isChecked(): self.difference_image.auto_lut_scale_sym() self.model_plot.do_autoscale(replot=True) self.difference_plot.do_autoscale(replot=True) self.hprofile.do_autoscale(replot=True) self.vprofile.do_autoscale(replot=True) else: self.model_plot.replot() self.difference_plot.replot() self.hprofile.replot() self.vprofile.replot() self.model_plot.update_colormap_axis(self.model_image) self.difference_plot.update_colormap_axis(self.difference_image) def do_fit(self): self.extendedparams.do_fit(self.data, self.x, self.weights) self.update_opt() self.parametertable.initparams(setopt=True) def test(win): # make a test model xmin = -1.0 xmax = 1.0 # bornes ymin = -1.0 ymax = 1.0 data = np.random.rand(100, 100) x = np.arange(xmin, xmax, 0.02) y = np.arange(ymin, ymax, 0.02) xv, yv = np.meshgrid(x, y) data = data + 10.0 / (1.0 + xv * xv + yv * yv) tags = [("Qz", 0.15)] win.add_data(data, (xmin, xmax), (ymin, ymax), title="test1", tags=tags) xmin = -1.2 xmax = 1.2 # bornes ymin = -1.5 ymax = 1.5 data = np.random.rand(150, 120) x = np.arange(xmin, xmax, 0.02) y = np.arange(ymin, ymax, 0.02) xv, yv = np.meshgrid(x, y) data = data + 5.0 / (1.0 + xv * xv + 0.5 * yv * yv) tags = [("l", 0.3)] win.add_data(data, (xmin, xmax), (ymin, ymax), title="test2", tags=tags) if __name__ == "__main__": from guidata import qapplication _app = qapplication() # win = FitDialog() win = Fit2DWindow() test(win) """ x=np.nonzero(data) fx=(xmax-xmin)/s fy=(ymax-ymin)/s xx=(np.array(x[0])+0.5)*fx+xmin xy=(np.array(x[1])+0.5)*fy+ymin models=(Linear2DModel,Lorenzian2DModel) win.extendedparams.add_model_class(Linear2DModel) win.extendedparams.add_model_class(Lorenzian2DModel) win.parametertable.initparams(settry=True) data[x]=win.extendedparams.composite_model.eval(win.extendedparams.partry,x=[xx,xy]) image1=make.image(data,title='test',xdata=[xmin,xmax],ydata=[ymin,ymax]) """ win.show() # test=ParameterTable(extendedparams=ExtendedParams()) # test.show() _app.exec_() binoculars-0.0.10/grafit/images/000077500000000000000000000000001412510113200164405ustar00rootroot00000000000000binoculars-0.0.10/grafit/images/Door.png000066400000000000000000000004631412510113200200540ustar00rootroot00000000000000PNG  IHDR sRGBgAMA a pHYs+IDAT8Oc d&(MH3Vgڼ[\ܺlT 4#g 2&M@ ͟ t@yM`,+(Ϳvf!Q5ԡ,TBf9✍Ms66lfD 9[m0`q̈́$P%ljdb`g+IENDB`binoculars-0.0.10/grafit/images/Eraser.png000066400000000000000000000014371412510113200203740ustar00rootroot00000000000000PNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxtR]HSa~܏nt%.FE$5"~&"X!^8.&y%]n(/BRkDa?w[3Am6w~ꅗy{qXZZB,C&Ass3Z[[qc, C{@$l6zTUUZ;  `za,YVi4Ȁc`BkE0`Ϸz2'`E!ex> gnt:UArW'˲>HT>Qdg] S].s8J zK,bw٬_[[$Iٲ9l6cN:m]>' ]9.(!ēaneIUf[,z=6-6X`WWWwYFԀ.Yd`).p8 Yʣ(? cieꍗWd%$# i/..б!N~hT2Y[R%drxF Y̼@ӶX{ Acp":y4[Pz/^d2{⡣Ԣ/ ($g udH!?͓ .8L{FAZGJW\\R77a`Ye os_;EIENDB`binoculars-0.0.10/grafit/images/Gauss.png000066400000000000000000000007111412510113200202270ustar00rootroot00000000000000PNG  IHDR sRGBgAMA a pHYs+^IDAT8Oc d&(>\ f vb]~?A$*jb(hW. h6<6($:t-@_^AE @σ`W=1 *łyn aϦO (O k7JIENDB`binoculars-0.0.10/grafit/images/GaussDer.png000066400000000000000000000003451412510113200206650ustar00rootroot00000000000000PNG  IHDR sRGBgAMA a pHYs+zIDAT8O 0хMT0H؏גt8.#|+eUN(AZMV9+nn2rd!#%*,LQ' LzU\=1.4RIENDB`binoculars-0.0.10/grafit/images/Run.png000066400000000000000000000006521412510113200177150ustar00rootroot00000000000000PNG  IHDRh6gAMA%`M cHRM浧5IDATxbd`*@L H`֬s 4ф;7kBvfB(dW@ h  &KHugt__)||gg:E( H//T$=E5TP" I?Cuwg`uĄM >@ PΝؔE*|9=$ `u>> 7D>}b()a8{̙gή T :q`4kER $9s&0r¢e&Ϝ aj@`oQT~IENDB`binoculars-0.0.10/grafit/images/Skew.png000066400000000000000000000006111412510113200200550ustar00rootroot00000000000000PNG  IHDR sRGBgAMA a pHYs+IDAT8Oc d&(>I1.mm@ϟ!hfA//0 No| N0 ~0ܱš\>\ š]~?A$* hW. ɪBY CFoeT,t-@^@ VpNk4ٌiPhFvP&x>c:9OA~f`+bfbIENDB`binoculars-0.0.10/grafit/images/Step.png000066400000000000000000000003551412510113200200640ustar00rootroot00000000000000PNG  IHDR sRGBgAMA a pHYs+IDAT8Oc d&(M د[>x Bsf204?7m12[&MCAYp mf u( ; 6Q-:~,_Q64|C i!nuIENDB`binoculars-0.0.10/grafit/images/lorenzgauss.jpg000066400000000000000000000043221412510113200215170ustar00rootroot00000000000000JFIF``C      C  @@" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?{.5˵ v jfկ-.TWUfK}w 䎵m#~6~osqnLm[IYlvL%S)a#<-fg[XquZ-?NU4vgWx*"_UTz`Ws ,/\k8鹏YYC;ߣy4_q~'VlX8(XWʿO ݤ²W |C>M3}0kѫ+0~xʙp9>3n{C,/Ml&n$!'!>96r 'ືLW#:f25px*ԣ(z>xX\ci}n.-wco㱴% 8aƭ m_dq_m0]Ɯѳ.6Qj?h;o:Whm L[y5SY⫕X~6-4ۤ8ɯwsxzu~g$xOp1id^gޟ?- ;A'5}W _>FAyavgZ̬pc~6|_6ao 32ȁNMc #O3{N_eQ2wV٣>0x6=SL{Wsm. <&!nk/BW\g'SI_O?o/~$ {㯵}C_Uo8 ޿I>x-_OI7 dzO|NcU8\3Ʊ}#9-_sॾQX-?^kû/\ceu>0 ڲ\‡hK+g^]h_7]LΛx'^~&If|;8MpOW,6 ċʏq>WGK-R0<-~:]/GjRDLv)qB,U,Jm-:.Cxd|1, Q53~%^ CJ̛pA4- FE^_rkOķ7WM)8Cs'.iάZR1RN5ukI,/m} I'ѐ12j(.-aZ7yÿxK+6eu=s־?jY9{K3FGVc8/✺Så%%~◅k1XEiUKX^9UCֺSB<՗tFpF$3_UxGuK%n23xV3_AxϕĜ ÏeAifXƲvcW+DQ[M4*ƹ%,Ku6>lW%(4Pp} 1f1rwٟ cU*zgbinoculars-0.0.10/grafit/images/lorenzpoly.jpg000066400000000000000000000043221412510113200213600ustar00rootroot00000000000000JFIF``C      C  @@" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?{.5˵ v jfկ-.TWUfK}w 䎵m#~6~osqnLm[IYlvL%S)a#<-fg[XquZ-?NU4vgWx*"_UTz`Ws ,/\k8鹏YYC;ߣy4_q~'VlX8(XWʿO ݤ²W |C>M3}0kѫ+0~xʙp9>3n{C,/Ml&n$!'!>96r 'ືLW#:f25px*ԣ(z>xX\ci}n.-wco㱴% 8aƭ m_dq_m0]Ɯѳ.6Qj?h;o:Whm L[y5SY⫕X~6-4ۤ8ɯwsxzu~g$xOp1id^gޟ?- ;A'5}W _>FAyavgZ̬pc~6|_6ao 32ȁNMc #O3{N_eQ2wV٣>0x6=SL{Wsm. <&!nk/BW\g'SI_O?o/~$ {㯵}C_Uo8 ޿I>x-_OI7 dzO|NcU8\3Ʊ}#9-_sॾQX-?^kû/\ceu>0 ڲ\‡hK+g^]h_7]LΛx'^~&If|;8MpOW,6 ċʏq>WGK-R0<-~:]/GjRDLv)qB,U,Jm-:.Cxd|1, Q53~%^ CJ̛pA4- FE^_rkOķ7WM)8Cs'.iάZR1RN5ukI,/m} I'ѐ12j(.-aZ7yÿxK+6eu=s־?jY9{K3FGVc8/✺Så%%~◅k1XEiUKX^9UCֺSB<՗tFpF$3_UxGuK%n23xV3_AxϕĜ ÏeAifXƲvcW+DQ[M4*ƹ%,Ku6>lW%(4Pp} 1f1rwٟ cU*zgbinoculars-0.0.10/grafit/images/lorenzpower.jpg000066400000000000000000000043221412510113200215310ustar00rootroot00000000000000JFIF``C      C  @@" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?{.5˵ v jfկ-.TWUfK}w 䎵m#~6~osqnLm[IYlvL%S)a#<-fg[XquZ-?NU4vgWx*"_UTz`Ws ,/\k8鹏YYC;ߣy4_q~'VlX8(XWʿO ݤ²W |C>M3}0kѫ+0~xʙp9>3n{C,/Ml&n$!'!>96r 'ືLW#:f25px*ԣ(z>xX\ci}n.-wco㱴% 8aƭ m_dq_m0]Ɯѳ.6Qj?h;o:Whm L[y5SY⫕X~6-4ۤ8ɯwsxzu~g$xOp1id^gޟ?- ;A'5}W _>FAyavgZ̬pc~6|_6ao 32ȁNMc #O3{N_eQ2wV٣>0x6=SL{Wsm. <&!nk/BW\g'SI_O?o/~$ {㯵}C_Uo8 ޿I>x-_OI7 dzO|NcU8\3Ʊ}#9-_sॾQX-?^kû/\ceu>0 ڲ\‡hK+g^]h_7]LΛx'^~&If|;8MpOW,6 ċʏq>WGK-R0<-~:]/GjRDLv)qB,U,Jm-:.Cxd|1, Q53~%^ CJ̛pA4- FE^_rkOķ7WM)8Cs'.iάZR1RN5ukI,/m} I'ѐ12j(.-aZ7yÿxK+6eu=s־?jY9{K3FGVc8/✺Så%%~◅k1XEiUKX^9UCֺSB<՗tFpF$3_UxGuK%n23xV3_AxϕĜ ÏeAifXƲvcW+DQ[M4*ƹ%,Ku6>lW%(4Pp} 1f1rwٟ cU*zgbinoculars-0.0.10/grafit/images/planar.jpg000066400000000000000000000031411412510113200204160ustar00rootroot00000000000000JFIF``C      C  @@" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?"u>]6|ktS]>eWG;?9;궾\Nי|Kqy ;I8GZr,z_IOLjjь(_9QeWį`|GWΟ1Y?wҾӄjq/'~&q_?x pí~|Phۤ_־of+^%Gƭ/G[++|IrJc??x;q^o&lGS+p<*nK-+/_L|*~9Ÿ~¯7nRie -4|u.2?@5h8H9uz^毚>Qc~=A=kۼ=aҿTϗQ"eB{z}ߚm/TG^mj]>p5&xm cvu΍bą޼[ÀK~bҵn5&GBBsyv##ŷ%ß#)2ZN&u_J|(w۽|?jl][k)V])gYr&7fXoPW?Äቧ)'ϊξU< dVc}/3/Q޿xۃcR2u(I zZƯnSҺ/T9{Yx{WVʷ ~)>]5'UB]VȮJ>yrQj#9N|ׇ^ ߓ?binoculars-0.0.10/grafit/images/weight.png000066400000000000000000000117311412510113200204400ustar00rootroot00000000000000PNG  IHDRAsRGBgAMA a pHYs+QtEXtCommentCopyright INCORS GmbH (www.iconexperience.com) - Unlicensed preview imagei8tEXtCopyrightCopyright INCORS GmbH (www.iconexperience.com)NΙNZzTXtCommentxs/,L(Qs VpMP(//LK(H-LKNKTULN+NMQ((J-L-WMLO$AzTXtCopyrightxs/,L(Qs VpMP(//LK(H-LKNK}6hIDATx^[U*&H$b\e&&O`O!j&x7*&J f' ^K 1"2 ;WuMOt]t}䤪OU>}>:jqQ׶Cc#HAƝѣGx|ޗ-A}}6~OG_+sB%*Ï7 8y#N˹J-8 Zhv*H:z+ׅH9 bqΗ8 q>8kב'ٗF_'u}}lC&ؿ8Ae iY t)*1As>G}IC@qrn|vH S RamT Ra4J (&HT Ra4J (&HT Ra4J (6"/v<'Nf̘N?twc=c9FvVX7|EI8|p\c9 pgn]nڴi9&v700vN8/6&ILJ@pӧOw ,p9cf,&Q_ݽ-[K= &ԎƑ#G|Nih"OڵkIA^vU V븖4H+B"O(*ΝX]nd o]… ԵfKRmoܸoF%KH&O ƤNqi6+*RIA2;IT3Ϋda"$A*RIA kgC-&;YN>-a>#SȘ% JА'V!D+φ zM?6ԕr2M6Y_}/}H$/hbP{~_;y$H4LúK|Ӕ/ejBvB)I(%ԩ``Oc6ŶظFN$HcDɝxX1)ƝDVaWmy+"ظHEl)ڐ:t 7½`-6d*ZsuU*r+M[w JFA`_(|w1F}uRaЖS [ Y,{3{wdC"^P116ؼrn?;%_~9!F~`kl 4 RB-[lcؼNv~z ocv QlA./_z9lͱ}ụyȁNrjV ~ɇv @7N ^z%_}67R(PgK"vӶDD6Rllݺկufڰ!hb8Q AuNla??׻ndPEhb;ڎ#v ^; C @hFøܺq$0 f#q!Agvܜ!@RiP^u_]F69Go%~'4{$HbvVoffxb$\r]h(@mӱzFqXّ<6> w UzHc/OVN oXmW{%oĈ)H\($6Ct&)'~p4ą\ZMĦEHO:qo[-U7{ا8Fl52VTSNDmĠEfMmؗ? u|(|S0#)Bղ- i(M\p<ݎĆV70^w c2͉ŇT] 'n8DPj3%FbSQ>(XzɚM$A(.D1GE.fˆzF Z>lЏICJF |"QF-ʐk'#lBQcVm몁>+ H!X'HsBQ%$=7~hQUQm&:QBE^h# װtIm5%omuyyOztU`41g.otXWֶX$y<;{LJx"H I )ͻGoY:<ÇyB'Q u$# X:<ÇyEbugǤ >w0OFk"Fl]tƋѬ0W &ŨG<"DJVYn=K!R]+؋׻|/i^u3A>fU"LkMVRX$ ( 1JE#ڐ!FNHӆ\v W $y R!br[P-5>A#E\!m@"1uEbjA[][ummum۶Yڈ@ hm% ׯ:6ҒZfF46ҒZш&FZR G.0My2MW__tJ @ hm% /bw饗Z)iD46ҒZ ~FYn'$\{oOU5QV|B>v- ZЫ%+MgVxŋ54l(Z{[|,h %:7KCCCk.fVOOO,Y͝;v4; R`I$oࠏ:a#2 gqKAFZb Q*LF0Ai Q*LF0Ai Q*LF0Ai .T56dܹ.q:>~v`g}r\z9.ύt>~P r>$g/dl:+td_oAǁ\B4ouYoC^d18x8 qtdx5ȧV4}V>m=cF>~K3ผ+@4|4΋hׅ-aYq4aSIENDB`binoculars-0.0.10/grafit/images/y_full_range.png000066400000000000000000000004631412510113200216170ustar00rootroot00000000000000PNG  IHDRagAMA a pHYs+tEXtSoftwarePaint.NET v3.36%IDAT8OA EKޥzL1ե{ϐtHI? pa]2)% ۦs=5!j[[fX%ol 3/|ϗj){POw^0F'V J&,.1[~SГсףP] T]$sPcE~04IENDB`binoculars-0.0.10/grafit/models.py000066400000000000000000000576721412510113200170510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Fri Feb 21 17:02:23 2020 @author: Prevot """ from collections import OrderedDict from copy import deepcopy import numpy as np from scipy.integrate import dblquad from scipy.special import erf from lmfit import lineshapes from lmfit import Model as LmfitModel from lmfit.models import COMMON_INIT_DOC, COMMON_GUESS_DOC, update_param_vals import operator def linear2D(x, slope_x=0.0, slope_y=0.0, bg=0.0): return bg + x[0] * slope_x + x[1] * slope_y """*****************************************************************************************************""" def lorenzianpower2D( x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, th=0.0, power=2.0 ): cth = np.cos(th) sth = np.sin(th) x0 = x[0] - loc0 x1 = x[1] - loc1 x0, x1 = x0 * cth - x1 * sth, x0 * sth + x1 * cth # rotation if power == 2.0: return amp / (1.0 + ((x0 / width0) ** 2 + ((x1 / width1) ** 2))) else: return amp / ( 1.0 + ((x0 / width0) ** 2 + ((x1 / width1) ** 2)) ** (power / 2.0) ) def lorenzianpower( y, x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, th=0.0, power=2.0 ): cth = np.cos(th) sth = np.sin(th) x0 = x - loc0 x1 = y - loc1 x0, x1 = x0 * cth - x1 * sth, x0 * sth + x1 * cth # rotation if power == 2.0: return amp / (1.0 + ((x0 / width0) ** 2 + ((x1 / width1) ** 2))) else: return amp / ( 1.0 + ((x0 / width0) ** 2 + ((x1 / width1) ** 2)) ** (power / 2.0) ) def lorenzianpowerint( amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, th=0.0, power=2.0 ): return dblquad( lambda y, x: lorenzianpoly(y, x, amp, loc0, loc1, width0, width1, th, power), -np.inf, np.inf, lambda x: -np.inf, lambda x: np.inf, ) """*****************************************************************************************************""" def lorenziangauss2D(x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, th=0.0): cth = np.cos(th) sth = np.sin(th) x0 = x[0] - loc0 x1 = x[1] - loc1 x0, x1 = x0 * cth - x1 * sth, x0 * sth + x1 * cth # rotation return amp * np.exp(-((x1 / width1) ** 2)) / (1.0 + ((x0 / width0) ** 2)) def lorenziangaussint(amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, th=0.0): return (amp * np.pi * np.sqrt(np.pi) * width0 * width1, 0.0) """*****************************************************************************************************""" def lorenzianpoly2D( x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, eta0=1.0, eta1=1.0, th=0.0 ): # eta is the ratio of lorenzian component, if eta=0, it is lorenzian, if eta=1 it is like 1/(1+x**4) cth = np.cos(th) sth = np.sin(th) x0 = x[0] - loc0 x1 = x[1] - loc1 x0, x1 = x0 * cth - x1 * sth, x0 * sth + x1 * cth # rotation u0 = (x0 / width0) ** 2 u1 = (x1 / width1) ** 2 return amp / (1.0 + (1.0 - eta0 + eta0 * u0) * u0 + (1.0 - eta1 + eta1 * u1) * u1) def lorenzianpoly( y, x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, eta0=1.0, eta1=1.0, th=0.0, ): # same with x y coordinates cth = np.cos(th) sth = np.sin(th) x0 = x - loc0 y0 = y - loc1 x0, y0 = x0 * cth - y0 * sth, x0 * sth + y0 * cth # rotation u0 = (x0 / width0) ** 2 u1 = (y0 / width1) ** 2 return amp / (1.0 + (1.0 - eta0 + eta0 * u0) * u0 + (1.0 - eta1 + eta1 * u1) * u1) def lorenzianpolyint( amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, eta0=1.0, eta1=1.0, th=0.0 ): return dblquad( lambda y, x: lorenzianpoly( y, x, amp, loc0, loc1, width0, width1, eta0, eta1, th ), -np.inf, np.inf, lambda x: -np.inf, lambda x: np.inf, ) """*****************************************************************************************************""" def lorenziandoor2D( x, amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, eta=1.0, th=0.0 ): cth = np.cos(th) sth = np.sin(th) x0 = x[0] - loc0 x1 = x[1] - loc1 x0, x1 = x0 * cth - x1 * sth, x0 * sth + x1 * cth # rotation return ( amp / (1.0 + ((x0 / width0) ** 2)) * ( erf((x1 + width1 / 2.0) / (eta * width1)) - erf((x1 - width1 / 2.0) / (eta * width1)) ) / 2.0 ) def lorenziandoorint( amp=1.0, loc0=0.0, loc1=0.0, width0=1.0, width1=1.0, eta=1.0, th=0.0 ): return (amp * np.pi * width0 * width1, 0.0) """*****************************************************************************************************""" def nointegration(*args, **kwargs): return (0.0, 0.0) # we jsut add the possibility to compute integrals for modelss class Model(LmfitModel): def __init__( self, func, independent_vars=None, param_names=None, nan_policy="raise", missing=None, prefix="", name=None, **kws ): self.intfunc = nointegration super(Model, self).__init__( func=func, independent_vars=independent_vars, param_names=param_names, nan_policy=nan_policy, missing=missing, prefix=prefix, name=name, **kws ) def __add__(self, other): """+""" return CompositeModel(self, other, operator.add) def __sub__(self, other): """-""" return CompositeModel(self, other, operator.sub) def __mul__(self, other): """*""" return CompositeModel(self, other, operator.mul) def __div__(self, other): """/""" return CompositeModel(self, other, operator.truediv) def __truediv__(self, other): """/""" return CompositeModel(self, other, operator.truediv) def eval_integral(self, params=None, **kwargs): # evaluate the integral of the peak, and associated scipy.integrate error (not the fit uncertainty) # needs to define self.intfunc first! return self.intfunc(**self.make_funcargs(params, kwargs)) def eval_component_integrals(self, params=None, **kwargs): """Evaluate the model integrals with the supplied parameters. Parameters ----------- params : Parameters, optional Parameters to use in Model. **kwargs : optional Additional keyword arguments to pass to model function. Returns ------- OrderedDict Keys are prefixes for component model, values are value of each component. """ key = self._prefix if len(key) < 1: key = self._name return { key: self.eval_integral(params=params, **kwargs) } # will be used to update the dictionnary of a composite model class CompositeModel(Model): """Combine two models (`left` and `right`) with a binary operator (`op`) into a CompositeModel. Normally, one does not have to explicitly create a `CompositeModel`, but can use normal Python operators `+`, '-', `*`, and `/` to combine components as in:: >>> mod = Model(fcn1) + Model(fcn2) * Model(fcn3) """ _names_collide = ( "\nTwo models have parameters named '{clash}'. " "Use distinct names." ) _bad_arg = "CompositeModel: argument {arg} is not a Model" _bad_op = "CompositeModel: operator {op} is not callable" _known_ops = { operator.add: "+", operator.sub: "-", operator.mul: "*", operator.truediv: "/", } def __init__(self, left, right, op, **kws): """ Parameters ---------- left : Model Left-hand model. right : Model Right-hand model. op : callable binary operator Operator to combine `left` and `right` models. **kws : optional Additional keywords are passed to `Model` when creating this new model. Notes ----- 1. The two models must use the same independent variable. """ if not isinstance(left, Model): raise ValueError(self._bad_arg.format(arg=left)) if not isinstance(right, Model): raise ValueError(self._bad_arg.format(arg=right)) if not callable(op): raise ValueError(self._bad_op.format(op=op)) self.left = left self.right = right self.op = op name_collisions = set(left.param_names) & set(right.param_names) if len(name_collisions) > 0: msg = "" for collision in name_collisions: msg += self._names_collide.format(clash=collision) raise NameError(msg) # we assume that all the sub-models have the same independent vars if "independent_vars" not in kws: kws["independent_vars"] = self.left.independent_vars if "nan_policy" not in kws: kws["nan_policy"] = self.left.nan_policy def _tmp(self, *args, **kws): pass Model.__init__(self, _tmp, **kws) for side in (left, right): prefix = side.prefix for basename, hint in side.param_hints.items(): self.param_hints["%s%s" % (prefix, basename)] = hint def _parse_params(self): self._func_haskeywords = ( self.left._func_haskeywords or self.right._func_haskeywords ) self._func_allargs = self.left._func_allargs + self.right._func_allargs self.def_vals = deepcopy(self.right.def_vals) self.def_vals.update(self.left.def_vals) self.opts = deepcopy(self.right.opts) self.opts.update(self.left.opts) def _reprstring(self, long=False): return "(%s %s %s)" % ( self.left._reprstring(long=long), self._known_ops.get(self.op, self.op), self.right._reprstring(long=long), ) def eval(self, params=None, **kwargs): """TODO: docstring in public method.""" return self.op( self.left.eval(params=params, **kwargs), self.right.eval(params=params, **kwargs), ) def eval_components(self, **kwargs): """Return OrderedDict of name, results for each component.""" out = OrderedDict(self.left.eval_components(**kwargs)) out.update(self.right.eval_components(**kwargs)) return out def eval_component_integrals(self, params=None, **kwargs): """Return OrderedDict of name, results for each component.""" out = OrderedDict(self.left.eval_component_integrals(params, **kwargs)) out.update(self.right.eval_component_integrals(params, **kwargs)) return out @property def param_names(self): """Return parameter names for composite model.""" return self.left.param_names + self.right.param_names @property def components(self): """Return components for composite model.""" return self.left.components + self.right.components def _get_state(self): return (self.left._get_state(), self.right._get_state(), self.op.__name__) def _set_state(self, state, funcdefs=None): return _buildmodel(state, funcdefs=funcdefs) def _make_all_args(self, params=None, **kwargs): """Generate **all** function arguments for all functions.""" out = self.right._make_all_args(params=params, **kwargs) out.update(self.left._make_all_args(params=params, **kwargs)) return out def _buildmodel(state, funcdefs=None): """Build model from saved state. Intended for internal use only. """ if len(state) != 3: raise ValueError("Cannot restore Model") known_funcs = {} for fname in lineshapes.functions: fcn = getattr(lineshapes, fname, None) if callable(fcn): known_funcs[fname] = fcn if funcdefs is not None: known_funcs.update(funcdefs) left, right, op = state if op is None and right is None: (fname, fcndef, name, prefix, ivars, pnames, phints, nan_policy, opts) = left if not callable(fcndef) and fname in known_funcs: fcndef = known_funcs[fname] if fcndef is None: raise ValueError("Cannot restore Model: model function not found") model = Model( fcndef, name=name, prefix=prefix, independent_vars=ivars, param_names=pnames, nan_policy=nan_policy, **opts ) for name, hint in phints.items(): model.set_param_hint(name, **hint) return model else: lmodel = _buildmodel(left, funcdefs=funcdefs) rmodel = _buildmodel(right, funcdefs=funcdefs) return CompositeModel(lmodel, rmodel, getattr(operator, op)) # Models derived from the lmfit Model class # Need to give param_names if we want to keep the correct order class LorenzianPower2DModel(Model): """Lorentzian model with variable power, with seven Parameters. Defined as: f(x; amp, loc0, loc1, width0, width1, th) = amp/(1.+((x0/width0)**2+(x1/width1)**2)**(power/2.))) """ EVALUATE = "peak" ICON = "lorenzpower.jpg" NAME = "Lorenzian with extra power" def __init__(self, independent_vars=["x"], prefix="", nan_policy="raise", **kwargs): kwargs.update( { "prefix": prefix, "nan_policy": nan_policy, "independent_vars": independent_vars, } ) super(LorenzianPower2DModel, self).__init__( lorenzianpower2D, param_names=["amp", "loc0", "loc1", "width0", "width1", "th", "power"], **kwargs ) self.set_param_hint("amp", value=1.0) self.set_param_hint("loc0", value=0.0) self.set_param_hint("loc1", value=0.0) self.set_param_hint("width0", value=1.0, min=0) self.set_param_hint("width1", value=1.0, min=0) self.set_param_hint("th", value=0.0, vary=False) self.set_param_hint("power", value=2.0, vary=False) self.intfunc = lorenzianpowerint def guess(self, data, x=None, **kwargs): """Estimate initial model parameter values from data.""" amp, loc0, loc1, width0, width1, th, power = 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 2.0 if x is not None: n = np.argmax(data) amp = data[n] loc0 = x[0][n] loc1 = x[1][n] width0 = (np.amax(x) - np.amin(x)) / 2.0 width1 = (np.amax(x) - np.amin(x)) / 2.0 if width0 == 0: width0 = 1.0 if width1 == 0: width1 = 1.0 pars = self.make_params( amp=amp, loc0=loc0, loc1=loc1, width0=width0, width1=width1, th=th, power=power, ) return update_param_vals(pars, self.prefix, **kwargs) __init__.__doc__ = COMMON_INIT_DOC guess.__doc__ = COMMON_GUESS_DOC class LorenzianPoly2DModel(Model): """Lorentzian model with polynomial, with seven Parameters. Defined as: f(x; amp, loc0, loc1, width0, width1, th) = amp/(1.+((x0/width0)**2+(x1/width1)**2)**(power/2.))) """ EVALUATE = "peak" ICON = "lorenzpoly.jpg" NAME = "Lorenzian with x**4 term" def __init__(self, independent_vars=["x"], prefix="", nan_policy="raise", **kwargs): kwargs.update( { "prefix": prefix, "nan_policy": nan_policy, "independent_vars": independent_vars, } ) super(LorenzianPoly2DModel, self).__init__( lorenzianpoly2D, param_names=[ "amp", "loc0", "loc1", "width0", "width1", "eta0", "eta1", "th", ], **kwargs ) self.set_param_hint("amp", value=1.0) self.set_param_hint("loc0", value=0.0) self.set_param_hint("loc1", value=0.0) self.set_param_hint("width0", value=1.0, min=0) self.set_param_hint("width1", value=1.0, min=0) self.set_param_hint("eta0", value=0.5, min=0, max=1) self.set_param_hint("eta1", value=0.5, min=0, max=1) self.set_param_hint("th", value=0.0, min=-np.pi, max=np.pi) self.intfunc = lorenzianpolyint def guess(self, data, x=None, **kwargs): """Estimate initial model parameter values from data.""" amp, loc0, loc1, width0, width1, th, power = 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 2.0 if x is not None: n = np.argmax(data) amp = data[n] loc0 = x[0][n] loc1 = x[1][n] width0 = (np.amax(x) - np.amin(x)) / 2.0 width1 = (np.amax(x) - np.amin(x)) / 2.0 if width0 == 0: width0 = 1.0 if width1 == 0: width1 = 1.0 pars = self.make_params( amp=amp, loc0=loc0, loc1=loc1, width0=width0, width1=width1, th=th, power=power, ) return update_param_vals(pars, self.prefix, **kwargs) def eval_integral(self, params=None, **kwargs): """Evaluate the integral of the model with supplied parameters and keyword arguments. Parameters ----------- params : Parameters, optional Parameters to use in Model. **kwargs : optional Additional keyword arguments to pass to model function. Returns ------- numpy.ndarray Value of model given the parameters and other arguments. Notes ----- 1. if `params` is None, the values for all parameters are expected to be provided as keyword arguments. If `params` is given, and a keyword argument for a parameter value is also given, the keyword argument will be used. 2. all non-parameter arguments for the model function, **including all the independent variables** will need to be passed in using keyword arguments. """ return self.intfunc(**self.make_funcargs(params, kwargs)) __init__.__doc__ = COMMON_INIT_DOC guess.__doc__ = COMMON_GUESS_DOC class LorenzianDoorModel(Model): """Lorentzian in one direction, door (with 2 erf) in the other direction, with seven Parameters. Defined as: f(x; amp, loc0, loc1, width0, width1, eta, th) = (amp/(1.+((x0/width0)**2)*(erf((y0-width0/2)/eta)-erf((y0+width0/2))) """ EVALUATE = "peak" ICON = "lorenzgauss.jpg" NAME = "Lorenzian/door" def __init__(self, independent_vars=["x"], prefix="", nan_policy="raise", **kwargs): kwargs.update( { "prefix": prefix, "nan_policy": nan_policy, "independent_vars": independent_vars, } ) super(LorenzianDoorModel, self).__init__( lorenziandoor2D, param_names=["amp", "loc0", "loc1", "width0", "width1", "eta", "th"], **kwargs ) self.set_param_hint("amp", value=1.0) self.set_param_hint("loc0", value=0.0) self.set_param_hint("loc1", value=0.0) self.set_param_hint("width0", value=1.0, min=0) self.set_param_hint("width1", value=1.0, min=0) self.set_param_hint("eta", value=0.2, min=0) self.set_param_hint("th", value=0.0, min=-np.pi, max=np.pi) self.intfunc = lorenziandoorint def guess(self, data, x=None, **kwargs): """Estimate initial model parameter values from data.""" amp, loc0, loc1, width0, width1, eta, th = 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0 if x is not None: n = np.argmax(data) amp = data[n] loc0 = x[0][n] loc1 = x[1][n] width0 = (np.amax(x) - np.amin(x)) / 2.0 width1 = (np.amax(x) - np.amin(x)) / 2.0 if width0 == 0: width0 = 1.0 if width1 == 0: width1 = 1.0 pars = self.make_params( amp=amp, loc0=loc0, loc1=loc1, width0=width0, width1=width1, eta=eta, th=th ) return update_param_vals(pars, self.prefix, **kwargs) __init__.__doc__ = COMMON_INIT_DOC guess.__doc__ = COMMON_GUESS_DOC class LorenzianGaussModel(Model): """Lorentzian in one direction, gaussian in the other direction, with six Parameters. Defined as: f(x; amp, loc0, loc1, width0, width1, th) = (amp/(1.+((x0/width0)**2)*np.exp(-(x1/width1)**2))) """ EVALUATE = "peak" ICON = "lorenzgauss.jpg" NAME = "Lorenzian/gaussian" def __init__(self, independent_vars=["x"], prefix="", nan_policy="raise", **kwargs): kwargs.update( { "prefix": prefix, "nan_policy": nan_policy, "independent_vars": independent_vars, } ) super(LorenzianGaussModel, self).__init__( lorenziangauss2D, param_names=["amp", "loc0", "loc1", "width0", "width1", "th"], **kwargs ) self.set_param_hint("amp", value=1.0) self.set_param_hint("loc0", value=0.0) self.set_param_hint("loc1", value=0.0) self.set_param_hint("width0", value=1.0, min=0) self.set_param_hint("width1", value=1.0, min=0) self.set_param_hint("th", value=0.0, min=-np.pi, max=np.pi) self.intfunc = lorenziangaussint def guess(self, data, x=None, **kwargs): """Estimate initial model parameter values from data.""" amp, loc0, loc1, width0, width1, th = 1.0, 0.0, 0.0, 1.0, 1.0, 0.0 if x is not None: n = np.argmax(data) amp = data[n] loc0 = x[0][n] loc1 = x[1][n] width0 = (np.amax(x) - np.amin(x)) / 2.0 width1 = (np.amax(x) - np.amin(x)) / 2.0 if width0 == 0: width0 = 1.0 if width1 == 0: width1 = 1.0 pars = self.make_params( amp=amp, loc0=loc0, loc1=loc1, width0=width0, width1=width1, th=th ) return update_param_vals(pars, self.prefix, **kwargs) __init__.__doc__ = COMMON_INIT_DOC guess.__doc__ = COMMON_GUESS_DOC class Linear2DModel(Model): """Lorentzian model, with six Parameters. Defined as: f(x; slope_x,slope_y,bg) = amp/(1.+(x0/width0)**2+(x1/width1)**2) """ EVALUATE = "points" ICON = "planar.jpg" NAME = "Linear background" def __init__(self, independent_vars=["x"], prefix="", nan_policy="raise", **kwargs): kwargs.update( { "prefix": prefix, "nan_policy": nan_policy, "independent_vars": independent_vars, } ) super(Linear2DModel, self).__init__( linear2D, param_names=["bg", "slope_x", "slope_y"], **kwargs ) self.set_param_hint("bg", value=1.0) self.set_param_hint("slope_x", value=0.0) self.set_param_hint("slope_y", value=0.0) def guess(self, data, x=None, **kwargs): """Estimate initial model parameter values from data.""" slope_x, slope_y, bg = 0.0, 0.0, 0.0 if x is not None: sx2 = np.sum(x[0] * x[0]) sy2 = np.sum(x[1] * x[1]) sxy = np.sum(x[0] * x[1]) sx = np.sum(x[0]) sy = np.sum(x[0]) szx = np.sum(x[0] * data) szy = np.sum(x[0] * data) sz = np.sum(data) npts = x[0].shape[0] a = np.array([[sx2, sxy, sx], [sx, sy2, sy], [sx, sy, npts]]) b = np.array([szx, szy, sz]) res = np.linalg.solve(a, b) slope_x = res[0] slope_y = res[1] bg = res[2] pars = self.make_params(slope_x=slope_x, slope_y=slope_y, bg=bg) return update_param_vals(pars, self.prefix, **kwargs) __init__.__doc__ = COMMON_INIT_DOC guess.__doc__ = COMMON_GUESS_DOC list_of_models = [] list_of_2D_models = [ Linear2DModel, LorenzianGaussModel, LorenzianPoly2DModel, LorenzianPower2DModel, LorenzianDoorModel, ] binoculars-0.0.10/grafit/signals.py000066400000000000000000000010221412510113200172000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Fri Feb 21 14:15:11 2020 @author: Prevot """ from guidata.qt.QtCore import SIGNAL # Emitted when a fit is performed SIG_FIT_DONE = SIGNAL("fit_done") # Emitted when a fit is performed SIG_FIT_SAVED = SIGNAL("fit_saved") # Emitted by parameter table when try is SIG_TRY_CHANGED = SIGNAL("try_changed") SIG_TRIGGERED = SIGNAL("triggered()") SIG_MODEL_ADDED = SIGNAL("model_added(int)") SIG_MODEL_REMOVED = SIGNAL("model_removed(int)") SIG_KEY_PRESSED_EVENT = SIGNAL("keyPressedEvent") binoculars-0.0.10/grafit/tools.py000066400000000000000000000366621412510113200167220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Fri Feb 21 14:13:29 2020 @author: Prevot """ import numpy as np from guidata.configtools import get_icon from guidata.qt.QtGui import QAction, QColor, QMenu from guidata.qt.QtCore import QPoint, Qt, QEvent from guidata.qthelpers import add_actions, get_std_icon from guiqwt.config import _ from guiqwt.events import ( QtDragHandler, RectangularSelectionHandler, setup_standard_tool_filter, EventMatch, KeyEventMatch, ) from guiqwt.signals import ( SIG_VALIDATE_TOOL, SIG_END_RECT, SIG_START_TRACKING, SIG_MOVE, SIG_STOP_NOT_MOVING, SIG_STOP_MOVING, ) from guiqwt.tools import ( CommandTool, DefaultToolbarID, RectangularActionTool, InteractiveTool, ToggleTool, ) from guiqwt.shapes import EllipseShape, PolygonShape from guiqwt.baseplot import canvas_to_axes from grafit.signals import SIG_TRIGGERED, SIG_MODEL_ADDED, SIG_KEY_PRESSED_EVENT SHAPE_Z_OFFSET = 1000 class KeyMatch(EventMatch): """ A callable returning True if it matches a key event keysequence: integer """ def __init__(self, keysequence): super(KeyMatch, self).__init__() assert isinstance(keysequence, int) self.keyseq = keysequence def get_event_types(self): return frozenset((QEvent.KeyPress,)) def __call__(self, event): return event.type() == QEvent.KeyPress and event.key() == self.keyseq class ShowWeightsTool(ToggleTool): def __init__( self, manager, title="show weights", icon="weight.png", tip="show weights associated to the data", toolbar_id=DefaultToolbarID, ): super(ShowWeightsTool, self).__init__( manager, title, icon, toolbar_id=toolbar_id ) def activate(self, checked=True): self.emit(SIG_VALIDATE_TOOL) class RunTool(CommandTool): def __init__( self, manager, title="Run", icon="apply.png", tip="run 2D fit", toolbar_id=DefaultToolbarID, ): super(RunTool, self).__init__(manager, title, icon, toolbar_id=toolbar_id) def setup_context_menu(self, menu, plot): pass def activate(self, checked=True): self.emit(SIG_VALIDATE_TOOL) class SaveFitTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): super(SaveFitTool, self).__init__( manager, _("Save fit result"), get_std_icon("DialogSaveButton", 16), toolbar_id=toolbar_id, ) def activate(self, checked=True): self.emit(SIG_VALIDATE_TOOL) class PrefTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("Run"), icon="settings.png", tip=_("Preferences"), toolbar_id=toolbar_id, ) self.manager = manager def create_action_menu(self, manager): # Create and return menu for the tool's action self.saveprefaction = QAction("Save as...", self) self.showtagsaction = QAction("Show tags", self) self.scaleprefaction = QAction("Autoscale", self) self.scaleprefaction.setCheckable(True) self.scaleprefaction.setChecked(True) self.restartaction = QAction("Restart from last parameters saved", self) self.restartaction.setCheckable(True) self.restartaction.setChecked(True) self.samescaleaction = QAction("Same scale for data and model images", self) self.samescaleaction.setCheckable(True) self.samescaleaction.setChecked(True) self.showdiffaction = QAction("Show difference profiles", self) self.showdiffaction.setCheckable(True) self.showdiffaction.setChecked(True) menu = QMenu() add_actions( menu, ( self.saveprefaction, self.showtagsaction, self.scaleprefaction, self.restartaction, self.samescaleaction, self.showdiffaction, ), ) self.action.setMenu(menu) return menu class EllipseSelectionHandlerCXY(RectangularSelectionHandler): # on utilise la classe heritee dont on surcharge la methode move def __init__(self, filter, btn, mods=Qt.NoModifier, start_state=0): super(EllipseSelectionHandlerCXY, self).__init__( filter=filter, btn=btn, mods=mods, start_state=start_state ) filter.add_event( start_state, KeyEventMatch((42, 43, 45, 47)), self.key_press, start_state ) def move(self, filter, event): """methode surchargee par la classe """ x1, y1 = canvas_to_axes(self.shape, event.pos()) x0, y0 = canvas_to_axes(self.shape, self.start) dx = x1 - x0 dy = y1 - y0 self.shape.points = np.array( [[x0 - dx, y0], [x0 + dx, y0], [x0, y0 - dy], [x0, y0 + dy]] ) self.move_action(filter, event) filter.plot.replot() def key_press(self, filter, event): self.emit(SIG_KEY_PRESSED_EVENT, filter, event.key()) def set_shape( self, shape, h0, h1, h2, h3, setup_shape_cb=None, avoid_null_shape=False ): self.shape = shape self.shape_h0 = h0 self.shape_h1 = h1 self.shape_h2 = h2 self.shape_h3 = h3 self.setup_shape_cb = setup_shape_cb self.avoid_null_shape = avoid_null_shape class RectangularSelectionHandlerCXY(RectangularSelectionHandler): # on utilise la classe heritee dont on surcharge la methode move def move(self, filter, event): """methode surchargee par la classe """ sympos = QPoint( 2 * self.start.x() - event.pos().x(), 2 * self.start.y() - event.pos().y() ) print(self.shape_h0, self.shape_h1) self.shape.move_local_point_to(self.shape_h0, sympos) self.shape.move_local_point_to(self.shape_h1, event.pos()) self.move_action(filter, event) filter.plot.replot() class RectangularActionToolCXY(RectangularActionTool): # outil de tracage de rectangles centre en x # on utilise la classe heritee dont on surcharge la methode setup_filter def setup_filter( self, baseplot ): # partie utilisee pendant le mouvement a la souris # utilise a l'initialisation de la toolbar # print "setup_filter" filter = baseplot.filter start_state = filter.new_state() handler = RectangularSelectionHandlerCXY( filter, Qt.LeftButton, start_state=start_state # gestionnaire du filtre ) shape, h0, h1 = self.get_shape() shape.pen.setColor(QColor("#00bfff")) handler.set_shape( shape, h0, h1, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE ) self.connect(handler, SIG_END_RECT, self.end_rect) # self.connect(handler, SIG_CLICK_EVENT, self.start) #a definir aussi dans RectangularSelectionHandler2 return setup_standard_tool_filter(filter, start_state) def activate(self): """Activate tool""" # print "commande active",self for baseplot, start_state in self.start_state.items(): baseplot.filter.set_state(start_state, None) self.action.setChecked(True) self.manager.set_active_tool(self) # plot = self.get_active_plot() # plot.newcurve=True def deactivate(self): """Deactivate tool""" # print "commande desactivee",self self.action.setChecked(False) class CircularActionToolCXY(RectangularActionToolCXY): TITLE = _("Circle") ICON = "circle.png" def __init__( self, manager, func1, func2, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, fix_orientation=False, switch_to_default_tool=None, ): self.key_func = func2 # function for keys super(CircularActionToolCXY, self).__init__( manager, func1, shape_style=shape_style, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, fix_orientation=fix_orientation, switch_to_default_tool=switch_to_default_tool, ) def setup_filter( self, baseplot ): # partie utilisee pendant le mouvement a la souris # utilise a l'initialisation de la toolbar # print "setup_filter" filter = baseplot.filter start_state = filter.new_state() handler = EllipseSelectionHandlerCXY( filter, Qt.LeftButton, start_state=start_state # gestionnaire du filtre ) shape, h0, h1, h2, h3 = self.get_shape() shape.pen.setColor(QColor("#00bfff")) handler.set_shape( shape, h0, h1, h2, h3, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE, ) self.connect(handler, SIG_END_RECT, self.end_rect) self.connect(handler, SIG_KEY_PRESSED_EVENT, self.key_pressed) return setup_standard_tool_filter(filter, start_state) def key_pressed(self, filter, key): plot = filter.plot self.key_func(plot, key) def get_shape(self): """Reimplemented RectangularActionTool method""" shape, h0, h1, h2, h3 = self.create_shape() self.setup_shape(shape) return shape, h0, h1, h2, h3 def create_shape(self): shape = EllipseShape(0, 0, 1, 1) self.set_shape_style(shape) shape.switch_to_ellipse() return shape, 0, 1, 2, 3 # give the values of h0,h1,h2,h3 use to move the shape class MultiPointTool(InteractiveTool): TITLE = _("Polyline") ICON = "polyline.png" CURSOR = Qt.ArrowCursor def __init__( self, manager, func, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(MultiPointTool, self).__init__( manager, toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None self.current_handle = None self.init_pos = None self.move_func = func if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def reset(self): self.shape = None self.current_handle = None def create_shape(self, filter, pt): self.shape = PolygonShape(closed=False) filter.plot.add_item_with_z_offset(self.shape, SHAPE_Z_OFFSET) self.shape.setVisible(True) self.shape.set_style(self.shape_style_sect, self.shape_style_key) self.shape.add_local_point(pt) return self.shape.add_local_point(pt) def setup_filter(self, baseplot): filter = baseplot.filter # Initialisation du filtre start_state = filter.new_state() # Bouton gauche : handler = QtDragHandler(filter, Qt.LeftButton, start_state=start_state) filter.add_event( start_state, KeyEventMatch((Qt.Key_Enter, Qt.Key_Return, Qt.Key_Space)), self.validate, start_state, ) filter.add_event( start_state, KeyEventMatch((Qt.Key_Backspace, Qt.Key_Escape,)), self.cancel_point, start_state, ) self.connect(handler, SIG_START_TRACKING, self.mouse_press) self.connect(handler, SIG_MOVE, self.move) self.connect(handler, SIG_STOP_NOT_MOVING, self.mouse_release) self.connect(handler, SIG_STOP_MOVING, self.mouse_release) return setup_standard_tool_filter(filter, start_state) def validate(self, filter, event): super(MultiPointTool, self).validate(filter, event) """ if self.handle_final_shape_cb is not None: self.handle_final_shape_cb(self.shape) """ self.shape.detach() filter.plot.replot() self.reset() def cancel_point(self, filter, event): if self.shape is None: return points = self.shape.get_points() if points is None: return elif len(points) <= 2: filter.plot.del_item(self.shape) self.reset() else: if self.current_handle: newh = self.shape.del_point(self.current_handle) else: newh = self.shape.del_point(-1) self.current_handle = newh filter.plot.replot() def mouse_press(self, filter, event): """We create a new shape if it's the first point otherwise we add a new point """ if self.shape is None: self.init_pos = event.pos() self.current_handle = self.create_shape(filter, event.pos()) filter.plot.replot() else: self.current_handle = self.shape.add_local_point(event.pos()) if self.current_handle > 2: self.shape.del_point(0) self.current_handle = 2 def move(self, filter, event): """moving while holding the button down lets the user position the last created point """ if self.shape is None or self.current_handle is None: # Error ?? return self.shape.move_local_point_to(self.current_handle, event.pos()) self.move_func(filter.plot, self.shape.points) filter.plot.replot() def mouse_release(self, filter, event): """Releasing the mouse button validate the last point position""" if self.current_handle is None: return if self.init_pos is not None and self.init_pos == event.pos(): self.shape.del_point(-1) else: self.shape.move_local_point_to(self.current_handle, event.pos()) self.init_pos = None self.current_handle = None self.move_func(filter.plot, self.shape.points) filter.plot.replot() def deactivate(self): """Deactivate tool""" if self.shape is not None: self.shape.detach() self.get_active_plot().replot() self.reset() self.action.setChecked(False) class AddModelTool(CommandTool): def __init__(self, manager, list_of_models, toolbar_id=DefaultToolbarID): self.list_of_models = list_of_models CommandTool.__init__( self, manager, _("Fit"), icon=get_icon("edit_add.png"), tip=_("Add curve for fit"), toolbar_id=toolbar_id, ) self.manager = manager def create_action_menu(self, manager): # Create and return menu for the tool's action menu = QMenu() for i, modelclass in enumerate(self.list_of_models): action = QAction(get_icon(modelclass.ICON), modelclass.NAME, self) add_actions(menu, (action,)) self.connect(action, SIG_TRIGGERED, lambda arg=i: self.add_model(arg)) self.action.setMenu(menu) return menu def add_model(self, k): print(k) i = k self.emit(SIG_MODEL_ADDED, i) binoculars-0.0.10/images/000077500000000000000000000000001412510113200151645ustar00rootroot00000000000000binoculars-0.0.10/images/circle_sponge.png000066400000000000000000000006741412510113200205150ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIME  CgzIIDATHՕN0<L]f/=[6 #4<@Ė%C&NDR-Yr]|$C΁H$-ZSOnPB kƅw$}-x\{-a<$=[}@n7I[+0?tXzVp_FkˢIJ1)h:(v,lK.C.qCQ|4o0o: 6L%e2`O]]Z|I:T345ܥ1(Vx|\f.iV 0{;8p4 p xp:UenIENDB`binoculars-0.0.10/images/mask_circle_grey.png000066400000000000000000000006331412510113200211760ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIME  27Iy1(IDATHՕ1N0EWP! HCV%E+5rNli-Il 4Rl=ߞpoX_xsߒ('[%# $5@8d$7u 5pHι%Pwfp K݁3'$G$x^f#0S"su(Ux Osly i~9o|lNgJ4pC[+<8YmY&,~j? & f3|%)Wi8~YUZoۿOn-[21IENDB`binoculars-0.0.10/images/mask_polygon_grey.png000066400000000000000000000006441412510113200214260ustar00rootroot00000000000000PNG  IHDRw=bKGDC pHYs  tIME  ;&1IDATHݕMJA G+Olx/BaV8#(Vq?Pn=31NJK%%46M'Fr[뛢3;MKkt'@g-bsup~ k0823;Ȓ.KT Byƞؿ(.4ZX(&l̩f_.3g SIvg7% /CF[lG@9 ؐ4k 0: x0, y0 = self.get_reference_point() xx0 = xMap.transform(x0) yy0 = yMap.transform(y0) try: # Optimized version in PyQt >= v4.5 t0 = QTransform.fromTranslate(xx0, yy0) except AttributeError: # Fallback for PyQt <= v4.4 t0 = QTransform().translate(xx0, yy0) tr = brush.transform() tr = tr * t0 brush = QBrush(brush) brush.setTransform(tr) return pen, brush, sym def draw(self, painter, xMap, yMap, canvasRect): pen, brush, symbol = self.get_pen_brush(xMap, yMap) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(pen) painter.setBrush(brush) points = self.transform_points(xMap, yMap) # connection des segments shape_lines = self.transform_connects(points) painter.drawLines(shape_lines) """ if self.ADDITIONNAL_POINTS: shape_points = points[:-self.ADDITIONNAL_POINTS] other_points = points[-self.ADDITIONNAL_POINTS:] else: shape_points = points other_points = [] if self.closed: painter.drawPolygon(shape_points) else: painter.drawPolyline(shape_points) """ if symbol != QwtSymbol.NoSymbol: for i in range(points.size()): symbol.draw(painter, points[i].toPoint()) """ if self.LINK_ADDITIONNAL_POINTS and other_points: pen2 = painter.pen() pen2.setStyle(Qt.DotLine) painter.setPen(pen2) painter.drawPolyline(other_points) """ def poly_hit_test(self, plot, ax, ay, pos): pos = QPointF(pos) dist = sys.maxint handle = -1 Cx, Cy = pos.x(), pos.y() poly = QPolygonF() pts = self.points for i in range(pts.shape[0]): # On calcule la distance dans le repère du canvas px = plot.transform(ax, pts[i, 0]) py = plot.transform(ay, pts[i, 1]) if i < pts.shape[0] - self.ADDITIONNAL_POINTS: poly.append(QPointF(px, py)) d = (Cx - px) ** 2 + (Cy - py) ** 2 if d < dist: dist = d handle = i inside = poly.containsPoint(QPointF(Cx, Cy), Qt.OddEvenFill) return sqrt(dist), handle, inside, None def hit_test(self, pos): """return (dist, handle, inside)""" if not self.plot(): return sys.maxint, 0, False, None return self.poly_hit_test(self.plot(), self.xAxis(), self.yAxis(), pos) def add_local_point(self, pos): pt = canvas_to_axes(self, pos) return self.add_point(pt) def add_point(self, pt): N, _ = self.points.shape self.points = np.resize(self.points, (N + 1, 2)) self.points[N, :] = pt return N def del_point(self, handle): self.points = np.delete(self.points, handle, 0) if handle < len(self.points): return handle else: return self.points.shape[0] - 1 def move_point_to(self, handle, pos, ctrl=None): # self.points[handle, :] = pos # ici il faut ecrire le comportement de la shape if handle == 0: pos0 = self.points[handle, :] dx = pos[0] - pos0[0] dy = pos[1] - pos0[1] for i in range(len(self.points)): self.points[i, 0] = self.points[i, 0] + dx self.points[i, 1] = self.points[i, 1] + dy else: # regarder uniquement s'il s'agit des axes x0 = self.points[0, 0] y0 = self.points[0, 1] xi = self.points[handle, 0] yi = self.points[handle, 1] r0 = np.sqrt((xi - x0) * (xi - x0) + (yi - y0) * (yi - y0)) xf = pos[0] yf = pos[1] rf = np.sqrt((xf - x0) * (xf - x0) + (yf - y0) * (yf - y0)) fact = rf / r0 for i in range(1, len(self.points)): self.points[i, 0] = (self.points[i, 0] - x0) * fact + x0 self.points[i, 1] = (self.points[i, 1] - y0) * fact + y0 def move_shape(self, old_pos, new_pos): dx = new_pos[0] - old_pos[0] dy = new_pos[1] - old_pos[1] self.points += np.array([[dx, dy]]) def get_item_parameters(self, itemparams): self.shapeparam.update_param(self) itemparams.add("ShapeParam", self, self.shapeparam) def set_item_parameters(self, itemparams): update_dataset(self.shapeparam, itemparams.get("ShapeParam"), visible_only=True) self.shapeparam.update_shape(self) assert_interfaces_valid(PointCloudShape) class GridShape(PointCloudShape, PolygonShape): # une classe particuliere de pointcloudshape def __init__( self, O=[0, 0], u=[100, 0], v=[0, 100], order=1, sg="p4", axesparam=None, shapeparam=None, gridshapeparam=None, ): super(GridShape, self).__init__(shapeparam=shapeparam) self.arrow_angle = 15 # degrees self.arrow_size = 10 # pixels self.x_pen = self.pen self.x_brush = self.brush self.y_pen = self.pen self.y_brush = self.brush self.set_movable(False) if axesparam is None: self.axesparam = AxesShapeParam(_("Axes"), icon="gtaxes.png") else: self.axesparam = axesparam self.axesparam.update_param(self) if shapeparam is None: self.shapeparam = ShapeParam(_("Shape"), icon="rectangle.png") else: self.shapeparam = shapeparam self.shapeparam.update_shape(self) if gridshapeparam is None: self.gridshapeparam = GridShapeParam(_("GridShape"), icon="rectangle.png") else: self.gridshapeparam = gridshapeparam self.gridshapeparam.update_shape(self) self.O = np.array(O, float) # centre du reseau self.u = np.array(u, float) # vecteur 1 self.v = np.array(v, float) # vecteur 2 self.uR, self.uT = xy_to_angle(self.u[0], self.u[1]) self.vR, self.vT = xy_to_angle(self.v[0], self.v[1]) self.order = order # nombre de mailles self.sg = sg # space group self.slaves = list() self.set_nodes() self.set_points() self.set_connects() self.define_points() def __reduce__(self): self.shapeparam.update_param(self) state = ( self.shapeparam, self.O, self.u, self.v, self.order, self.sg, self.slaves, self.z(), ) return (GridShape, (), state) def __getstate__(self): return ( self.shapeparam, self.O, self.u, self.v, self.order, self.sg, self.slaves, self.z(), ) def __setstate__(self, state): # quand on fait un pickle, on charge aussi les slaves param, O, u, v, order, sg, slaves, z = state self.O = O self.u = u self.v = v self.order = order self.slaves = slaves for slave in self.slaves: slave.set_master(self) self.sg = sg self.setZ(z) self.shapeparam = param self.shapeparam.update_shape(self) self.set_nodes() self.set_points() self.set_connects() self.define_points() def add_slave(self, slave): self.slaves.append(slave) def remove_slave(self, slave): self.slaves.remove(slave) def set_nodes(self): self.nodes = list() # les trois premiers noeuds servent a definir le reseau self.nodes.append([0, 0]) self.nodes.append([1, 0]) self.nodes.append([0, 1]) for i in range(-self.order, self.order + 1): for j in range(-self.order, self.order + 1): node = [i, j] if node not in self.nodes: self.nodes.append(node) self.npts = len(self.nodes) def set_points(self): self.points = np.array( list( [ self.O[0] + self.u[0] * node[0] + self.v[0] * node[1], self.O[1] + self.u[1] * node[0] + self.v[1] * node[1], ] for node in self.nodes ), float, ) for slave in self.slaves: slave.set_points() def set_connects(self): self.connects = np.array( list( [i, j] for i in range(self.npts) for j in range(self.npts) if self.dist(i, j) == 1 ) ) def define_points(self): # redefini les positions des points de la grille en fonction des vecteurs ux et uy for i in range(self.npts): node = self.nodes[i] self.points[i, 0] = self.O[0] + self.u[0] * node[0] + self.v[0] * node[1] self.points[i, 1] = self.O[1] + self.u[1] * node[0] + self.v[1] * node[1] def dist(self, i, j): ni = self.nodes[i] nj = self.nodes[j] return (ni[0] - nj[0]) ** 2 + (ni[1] - nj[1]) ** 2 def set_space_group_constraints(self, master="u", RT=False): if RT: self.u[0], self.u[1] = angle_to_xy(self.uR, self.uT) self.v[0], self.v[1] = angle_to_xy(self.vR, self.vT) if master == "u": if self.sg in ["pm", "p2mm", "pg", "p2mg", "p2gg"]: # les axes doivent etre perpendiculaires u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(v2 / u2) self.v[0] = -self.u[1] * ratio self.v[1] = self.u[0] * ratio elif self.sg in ["cm", "c2mm"]: # les axes doivent etre orthonormes u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(u2 / v2) self.v[0] = self.v[0] * ratio self.v[1] = self.v[1] * ratio elif self.sg in ["p4", "p4mm", "p4gm"]: # les axes doivent etre orthonormes self.v[0] = -self.u[1] self.v[1] = self.u[0] elif self.sg in ["p3", "p3m1", "p31m", "p6", "p6mm"]: # les axes doivent etre orthonormes self.v[0] = self.u[0] * cos_60 - self.u[1] * sin_60 self.v[1] = self.u[0] * sin_60 + self.u[1] * cos_60 else: if self.sg in ["pm", "p2mm", "pg", "p2mg", "p2gg"]: # les axes doivent etre perpendiculaires u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(u2 / v2) self.u[0] = self.v[1] * ratio self.u[1] = -self.v[0] * ratio if self.sg in ["cm", "c2mm"]: # les axes doivent etre orthonormes u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(v2 / u2) self.u[0] = self.u[0] * ratio self.u[1] = self.u[1] * ratio if self.sg in ["p4", "p4mm", "p4gm"]: # les axes doivent etre orthonormes self.u[0] = self.v[1] self.u[1] = -self.v[0] if self.sg in ["p3", "p3m1", "p31m", "p6", "p6mm"]: # les axes doivent etre orthonormes self.u[0] = self.v[0] * cos_60 + self.v[1] * sin_60 self.u[1] = -self.v[0] * sin_60 + self.v[1] * cos_60 self.uR, self.uT = xy_to_angle(self.u[0], self.u[1]) self.vR, self.vT = xy_to_angle(self.v[0], self.v[1]) def move_point_to(self, handle, pos, ctrl=None): # self.points[handle, :] = pos # ici il faut ecrire le comportement de la shape if handle == 0: self.O = np.array(pos, float) self.define_points() elif handle == 1: # il s'agit de l'axe des x x0 = self.points[0, 0] y0 = self.points[0, 1] x1 = pos[0] y1 = pos[1] self.u[0] = x1 - x0 self.u[1] = y1 - y0 # ici on ecrit le comportement pour assurer que le groupe d'espace est le bon self.set_space_group_constraints("u") self.define_points() elif handle == 2: # il s'agit de l'axe des x x0 = self.points[0, 0] y0 = self.points[0, 1] x2 = pos[0] y2 = pos[1] self.v[0] = x2 - x0 self.v[1] = y2 - y0 self.set_space_group_constraints("v") self.define_points() for slave in self.slaves: slave.set_points() """ def set_style(self, section, option): self.shapeparam.read_config(CONF, section, option) self.shapeparam.update_shape(self) self.axesparam.read_config(CONF, section, option) self.axesparam.update_axes(self) """ def draw(self, painter, xMap, yMap, canvasRect): PointCloudShape.draw(self, painter, xMap, yMap, canvasRect) painter.setBrush(painter.pen().color()) self.draw_arrow(painter, xMap, yMap, self.points[0], self.points[1]) self.draw_arrow(painter, xMap, yMap, self.points[0], self.points[2]) def draw_arrow(self, painter, xMap, yMap, p0, p1): sz = self.arrow_size angle = pi * self.arrow_angle / 180.0 ca, sa = cos(angle), sin(angle) d1x = xMap.transform(p1[0]) - xMap.transform(p0[0]) d1y = yMap.transform(p1[1]) - yMap.transform(p0[1]) norm = sqrt(d1x ** 2 + d1y ** 2) if abs(norm) < 1e-6: return d1x *= sz / norm d1y *= sz / norm n1x = -d1y n1y = d1x # arrow : a0 - a1 == p1 - a2 a1x = xMap.transform(p1[0]) a1y = yMap.transform(p1[1]) a0x = a1x - ca * d1x + sa * n1x a0y = a1y - ca * d1y + sa * n1y a2x = a1x - ca * d1x - sa * n1x a2y = a1y - ca * d1y - sa * n1y poly = QPolygonF() poly.append(QPointF(a0x, a0y)) poly.append(QPointF(a1x, a1y)) poly.append(QPointF(a2x, a2y)) painter.drawPolygon(poly) d0x = xMap.transform(p0[0]) d0y = yMap.transform(p0[1]) painter.drawLine(QPointF(d0x, d0y), QPointF(a1x, a1y)) def get_item_parameters(self, itemparams): self.shapeparam.update_param(self) itemparams.add("ShapeParam", self, self.shapeparam) self.gridshapeparam.update_param(self) itemparams.add("GridShapeParam", self, self.gridshapeparam) def set_item_parameters(self, itemparams): update_dataset(self.shapeparam, itemparams.get("ShapeParam"), visible_only=True) self.shapeparam.update_shape(self) # modification des parametres specifiques de la gridshape order0 = self.order self.gridshapeparam.update_shape(self) self.define_points() if order0 is not self.order: self.set_nodes() self.set_points() self.set_connects() """ def set_style(self, section, option): PolygonShape.set_style(self, section, option+"/border") self.gridshapeparam.read_config(CONF, section, option) self.gridshapeparam.update_axes(self) #problematique... """ class ReconstructionShape(PointCloudShape, PolygonShape): # une classe particuliere de pointcloudshape _can_move = False def __init__( self, A=[2.0, 0.0], B=[0.0, 2.0], master=None, order=2, axesparam=None, shapeparam=None, reconstructionshapeparam=None, ): super(ReconstructionShape, self).__init__(shapeparam=shapeparam) self.arrow_angle = 15 # degrees self.arrow_size = 10 # pixels self.x_pen = self.pen self.x_brush = self.brush self.y_pen = self.pen self.y_brush = self.brush if axesparam is None: self.axesparam = AxesShapeParam(_("Axes"), icon="gtaxes.png") else: self.axesparam = axesparam self.axesparam.update_param(self) if shapeparam is None: self.shapeparam = ShapeParam(_("Shape"), icon="rectangle.png") else: self.shapeparam = shapeparam self.shapeparam.update_shape(self) if reconstructionshapeparam is None: self.reconstructionshapeparam = ReconstructionShapeParam( _("ReconstructionShape"), icon="rectangle.png" ) else: self.reconstructionshapeparam = reconstructionshapeparam self.reconstructionshapeparam.update_shape(self) self.A = np.array( A, float ) # vecteur 1 de la matrice de la reconstruction dans l'espace direct self.B = np.array( B, float ) # vecteur 2 de la matrice de la reconstruction dans l'espace direct self.delta = float(A[0] * B[1] - A[1] * B[0]) self.order = order self.set_nodes() # print "master=",master if master: self.set_master(master) def __reduce__(self): self.shapeparam.update_param(self) state = (self.shapeparam, self.A, self.B, self.order, self.z()) return (ReconstructionShape, (), state) def __getstate__(self): return self.shapeparam, self.A, self.B, self.order, self.z() def __setstate__(self, state): param, A, B, order, z = state self.A = A self.B = B self.order = order self.set_nodes() self.setZ(z) self.shapeparam = param self.shapeparam.update_shape(self) def set_master(self, master): self.master = master self.O = self.master.O self.set_points() self.set_connects() self.define_points() def set_nodes(self): self.nodes = list() # les trois premiers noeuds servent a definir le reseau self.nodes.append([0, 0]) self.nodes.append([1, 0]) self.nodes.append([0, 1]) for i in range(-self.order, self.order + 1): for j in range(-self.order, self.order + 1): node = [i, j] if node not in self.nodes: self.nodes.append(node) self.npts = len(self.nodes) def set_points(self): # print self.A,self.B self.delta = float(self.A[0] * self.B[1] - self.A[1] * self.B[0]) # relation dans l'espace reciproque self.Ar = [self.B[1] / self.delta, -self.B[0] / self.delta] self.Br = [-self.A[1] / self.delta, self.A[0] / self.delta] # print self.Ar,self.Br self.u = [ self.master.u[0] * self.Ar[0] + self.master.v[0] * self.Ar[1], self.master.u[1] * self.Ar[0] + self.master.v[1] * self.Ar[1], ] self.v = [ self.master.u[0] * self.Br[0] + self.master.v[0] * self.Br[1], self.master.u[1] * self.Br[0] + self.master.v[1] * self.Br[1], ] self.O = self.master.O # print self.u,self.v,self.O self.points = np.array( list( [ self.O[0] + self.u[0] * node[0] + self.v[0] * node[1], self.O[1] + self.u[1] * node[0] + self.v[1] * node[1], ] for node in self.nodes ), float, ) def set_connects(self): self.connects = np.array( list( [i, j] for i in range(self.npts) for j in range(self.npts) if self.dist(i, j) == 1 ) ) def define_points(self): # redefini les positions des points de la grille en fonction des vecteurs ux et uy for i in range(self.npts): node = self.nodes[i] self.points[i, 0] = self.O[0] + self.u[0] * node[0] + self.v[0] * node[1] self.points[i, 1] = self.O[1] + self.u[1] * node[0] + self.v[1] * node[1] def dist(self, i, j): ni = self.nodes[i] nj = self.nodes[j] return (ni[0] - nj[0]) ** 2 + (ni[1] - nj[1]) ** 2 def set_space_group_constraints(self, master="u"): if master == "u": if self.sg in ["pm", "p2mm", "pg", "p2mg", "p2gg"]: # les axes doivent etre perpendiculaires u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(v2 / u2) self.v[0] = -self.u[1] * ratio self.v[1] = self.u[0] * ratio elif self.sg in ["cm", "c2mm"]: # les axes doivent etre orthonormes u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(u2 / v2) self.v[0] = self.v[0] * ratio self.v[1] = self.v[1] * ratio elif self.sg in ["p4", "p4mm", "p4gm"]: # les axes doivent etre orthonormes self.v[0] = -self.u[1] self.v[1] = self.u[0] elif self.sg in ["p3", "p3m1", "p31m", "p6", "p6mm"]: # les axes doivent etre orthonormes self.v[0] = self.u[0] * cos_60 - self.u[1] * sin_60 self.v[1] = self.u[0] * sin_60 + self.u[1] * cos_60 else: if self.sg in ["pm", "p2mm", "pg", "p2mg", "p2gg"]: # les axes doivent etre perpendiculaires u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(u2 / v2) self.u[0] = self.v[1] * ratio self.u[1] = -self.v[0] * ratio if self.sg in ["cm", "c2mm"]: # les axes doivent etre orthonormes u2 = self.u[0] * self.u[0] + self.u[1] * self.u[1] v2 = self.v[0] * self.v[0] + self.v[1] * self.v[1] ratio = np.sqrt(v2 / u2) self.u[0] = self.u[0] * ratio self.u[1] = self.u[1] * ratio if self.sg in ["p4", "p4mm", "p4gm"]: # les axes doivent etre orthonormes self.u[0] = self.v[1] self.u[1] = -self.v[0] if self.sg in ["p3", "p3m1", "p31m", "p6", "p6mm"]: # les axes doivent etre orthonormes self.u[0] = self.v[0] * cos_60 + self.v[1] * sin_60 self.u[1] = -self.v[0] * sin_60 + self.v[1] * cos_60 def move_point_to(self, handle, pos, ctrl=None): # self.points[handle, :] = pos # ici il faut ecrire le comportement de la shape return def move_shape(self, old_pos, new_pos): return """ def set_style(self, section, option): self.shapeparam.read_config(CONF, section, option) self.shapeparam.update_shape(self) self.axesparam.read_config(CONF, section, option) self.axesparam.update_axes(self) """ def draw(self, painter, xMap, yMap, canvasRect): PointCloudShape.draw(self, painter, xMap, yMap, canvasRect) painter.setBrush(painter.pen().color()) self.draw_arrow(painter, xMap, yMap, self.points[0], self.points[1]) self.draw_arrow(painter, xMap, yMap, self.points[0], self.points[2]) def draw_arrow(self, painter, xMap, yMap, p0, p1): sz = self.arrow_size angle = pi * self.arrow_angle / 180.0 ca, sa = cos(angle), sin(angle) d1x = xMap.transform(p1[0]) - xMap.transform(p0[0]) d1y = yMap.transform(p1[1]) - yMap.transform(p0[1]) norm = sqrt(d1x ** 2 + d1y ** 2) if abs(norm) < 1e-6: return d1x *= sz / norm d1y *= sz / norm n1x = -d1y n1y = d1x # arrow : a0 - a1 == p1 - a2 a1x = xMap.transform(p1[0]) a1y = yMap.transform(p1[1]) a0x = a1x - ca * d1x + sa * n1x a0y = a1y - ca * d1y + sa * n1y a2x = a1x - ca * d1x - sa * n1x a2y = a1y - ca * d1y - sa * n1y poly = QPolygonF() poly.append(QPointF(a0x, a0y)) poly.append(QPointF(a1x, a1y)) poly.append(QPointF(a2x, a2y)) painter.drawPolygon(poly) d0x = xMap.transform(p0[0]) d0y = yMap.transform(p0[1]) painter.drawLine(QPointF(d0x, d0y), QPointF(a1x, a1y)) def get_item_parameters(self, itemparams): self.shapeparam.update_param(self) itemparams.add("ShapeParam", self, self.shapeparam) self.reconstructionshapeparam.update_param(self) itemparams.add("ReconstructionShapeParam", self, self.reconstructionshapeparam) def set_item_parameters(self, itemparams): update_dataset(self.shapeparam, itemparams.get("ShapeParam"), visible_only=True) self.shapeparam.update_shape(self) # modification des parametres specifiques de la gridshape order0 = self.order self.reconstructionshapeparam.update_shape(self) self.set_points() if order0 is not self.order: self.set_nodes() self.set_points() self.set_connects() class GridShapeTool(RectangularShapeTool): # redefinition de EllipseTool de guiqwt TITLE = _("Grid") ICON = gridpath SHAPE_STYLE_KEY = "shape/gridshape" def activate(self): """Activate tool""" for baseplot, start_state in self.start_state.items(): baseplot.filter.set_state(start_state, None) self.action.setChecked(True) self.manager.set_active_tool(self) def create_shape(self): shape = GridShape() shape.select() self.set_shape_style(shape) return shape, 0, 1 def add_shape_to_plot(self, plot, p0, p1): """ Method called when shape's rectangular area has just been drawn on screen. Adding the final shape to plot and returning it. """ shape = self.get_final_shape(plot, p0, p1) shape.unselect() # on donne a la shape le dernier numero de la liste des EllipseStatShape items = list([item for item in plot.get_items() if isinstance(item, GridShape)]) N = len(items) shape.setTitle("Reseau %d" % N) shape.set_color(QColor("#ffff00")) # yellow plot.emit(SIG_ITEMS_CHANGED, plot) self.handle_final_shape(shape) plot.replot() def handle_final_shape(self, shape): super(GridShapeTool, self).handle_final_shape(shape) class PeakIdentificationParameters: def __init__(self, plot, itemname="", item=None, h=None, k=None, itemindice=0): self.plot = plot self.itemname = itemname self.item = item self.itemindice = itemindice self.h = h self.k = k self.griditems = list( [item for item in self.plot.get_items() if isinstance(item, GridShape)] ) self.reconstructionitems = list( [ item for item in self.plot.get_items() if isinstance(item, ReconstructionShape) ] ) def guess(self, xpic, ypic): # fonction qui va donner pour chacun des elements de griditems et reconstructionitems self.guesslist = list() self.distlist = list() # liste des distances a la tache for item in self.griditems: x = xpic - item.O[0] y = ypic - item.O[1] delta = item.u[0] * item.v[1] - item.u[1] * item.v[0] h = (x * item.v[1] - y * item.v[0]) / delta k = (y * item.u[0] - x * item.u[1]) / delta self.guesslist.append([item.title().text(), h, k, item]) xth = item.u[0] * round(h) + item.v[0] * round(k) yth = item.u[1] * round(h) + item.v[1] * round(k) dist = np.sqrt((xth - x) ** 2 + (yth - y) ** 2) self.distlist.append(dist) for item in self.reconstructionitems: x = xpic - item.O[0] y = ypic - item.O[1] delta = item.u[0] * item.v[1] - item.u[1] * item.v[0] h = (x * item.v[1] - y * item.v[0]) / delta k = (y * item.u[0] - x * item.u[1]) / delta self.guesslist.append([item.title().text(), h, k, item]) xth = item.u[0] * round(h) + item.v[0] * round(k) yth = item.u[1] * round(h) + item.v[1] * round(k) dist = np.sqrt((xth - x) ** 2 + (yth - y) ** 2) self.distlist.append(dist) if len(self.distlist) > 0: self.itemindice = np.argmin(self.distlist) self.item = self.guesslist[self.itemindice][3] self.itemname = self.guesslist[self.itemindice][0] self.h = round(self.guesslist[self.itemindice][1]) self.k = round(self.guesslist[self.itemindice][2]) class PeakIdentificationWindow(QDialog): # definit une fenetre pour rentrer la position de la tache reperee def __init__(self, pref): QDialog.__init__(self) self.pref = pref self.itemtexts = list([item[0] for item in self.pref.guesslist]) self.Gridentry = QComboBox(self) self.Gridentry.setGeometry(QRect(5, 5, 200, 25)) self.Gridentry.insertItems(0, self.itemtexts) self.hlabel = QLabel(self) self.hlabel.setGeometry(QRect(5, 35, 55, 25)) self.klabel = QLabel(self) self.klabel.setGeometry(QRect(65, 35, 55, 25)) self.hentry = QLineEdit(self) self.hentry.setGeometry(QRect(5, 65, 55, 25)) self.kentry = QLineEdit(self) self.kentry.setGeometry(QRect(65, 65, 55, 25)) self.changeitem(self.pref.itemindice) self.OK = QPushButton(self) self.OK.setGeometry(QRect(5, 185, 90, 25)) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setGeometry(QRect(100, 185, 90, 25)) self.Cancel.setText("Cancel") QObject.connect(self.Cancel, SIGNAL(_fromUtf8("clicked()")), self.closewin) QObject.connect(self.OK, SIGNAL(_fromUtf8("clicked()")), self.appl) QObject.connect( self.Gridentry, SIGNAL(_fromUtf8("currentIndexChanged (int)")), self.changeitem, ) def changeitem(self, i): # quand on change x, on change les valeurs min et max de x par defaut self.Gridentry.setCurrentIndex(i) print("change") self.hlabel.setText("%f" % self.pref.guesslist[i][1]) self.klabel.setText("%f" % self.pref.guesslist[i][2]) h = int(round(self.pref.guesslist[i][1])) k = int(round(self.pref.guesslist[i][2])) self.hentry.setText("%d" % h) self.kentry.setText("%d" % k) def appl(self): h = self.hentry.text() k = self.kentry.text() try: itemindex = self.Gridentry.currentIndex() self.pref.itemname = self.pref.guesslist[itemindex][0] self.pref.item = self.pref.guesslist[itemindex][3] self.close() self.setResult(1) self.pref.h = int(h) self.pref.k = int(k) except Exception: QMessageBox.about(self, "Error", "Input not valid") def closewin(self): self.setResult(0) self.close() class RectanglePeakShape(RectangleShape): def __init__(self, x1=0, y1=0, x2=0, y2=0, shapeparam=None): super(RectanglePeakShape, self).__init__(x1, y1, x2, y2, shapeparam=shapeparam) # self.hasmask=False """ def itemChanged(self): super(EllipseStatShape,self).itemChanged() print "item Changed!" """ class RectanglePeakTool(RectangularShapeTool): TITLE = None ICON = peakfindpath def __init__( self, manager, setup_shape_cb=None, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title="find a peak", icon=ICON, tip=None, switch_to_default_tool=None, ): super(RectangularShapeTool, self).__init__( manager, self.add_shape_to_plot, shape_style, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.setup_shape_cb = setup_shape_cb self.handle_final_shape_cb = handle_final_shape_cb def add_shape_to_plot(self, plot, p0, p1): """ Method called when shape's rectangular area has just been drawn on screen. Adding the final shape to plot and returning it. """ shape = self.get_final_shape(plot, p0, p1) plotpoints = shape.get_points() win = plot.window() data = ( win.image.data ) # on recupere les donnes associees a l'image dans l'application maitresse # conversion des unites du plot en pixel de l'image points = np.empty(plotpoints.shape) for i in range(4): points[i, 0], points[i, 1] = win.image.get_pixel_coordinates( plotpoints[i, 0], plotpoints[i, 1] ) # print points x1 = min(points[:, 0]) x2 = max(points[:, 0]) y1 = min(points[:, 1]) y2 = max(points[:, 1]) # on recupere les datas de l'image en cours de traitement dim = data.shape ix1 = max(0, round(x1)) ix2 = min(dim[1] - 1, round(x2)) if ix2 < ix1: ix2, ix1 = ix1, ix2 iy1 = max(0, round(y1)) iy2 = min(dim[0] - 1, round(y2)) if iy2 < iy1: iy2, iy1 = iy1, iy2 print(iy1, iy2, ix1, ix2) data2 = data[iy1:iy2, ix1:ix2] sx = ix2 - ix1 sy = iy2 - iy1 # Create x and y indices x = np.linspace(ix1, ix2 - 1, sx) y = np.linspace(iy1, iy2 - 1, sy) self.handle_final_shape(shape) try: # dans un premier temps on regarde les sommes 1D sumx = np.sum(data2, 0) fond = np.min(sumx) pic = np.max(sumx) - fond x0 = np.argmax(sumx) + ix1 sigma = min(1.0, sx / 10.0) initial_guess = (pic, x0, sigma, fond) popt, pcov = opt.curve_fit(Gaussian, x, sumx, p0=initial_guess) picx = popt[0] / sy x0 = popt[1] sigmax = popt[2] fondx = popt[3] / sy sumy = np.sum(data2, 1) fond = np.min(sumy) pic = np.max(sumy) - fond y0 = np.argmax(sumy) + iy1 sigma = min(1.0, sy / 10.0) initial_guess = (pic, y0, sigma, fond) popt, pcov = opt.curve_fit(Gaussian, y, sumy, p0=initial_guess) picy = popt[0] / sx y0 = popt[1] sigmay = popt[2] fondy = popt[3] / sx pic = (picx + picy) / 2.0 sigma = (sigmax + sigmay) / 2.0 fond = (fondx + fondy) / 2.0 initial_guess = (pic, x0, y0, sigma, fond) # print initial_guess x, y = np.meshgrid(x, y) popt, pcov = opt.curve_fit( twoD_iso_Gaussian, (x, y), data2.ravel(), p0=initial_guess ) initial_guess = (popt[0], popt[1], popt[2], popt[3], popt[3], 0.0, fond) # print popt popt, pcov = opt.curve_fit( twoD_Gaussian, (x, y), data2.ravel(), p0=initial_guess ) # print popt # conversion en unite de plot x0, y0 = win.image.get_plot_coordinates(popt[1], popt[2]) print("peak at", x0, y0) if (x1 < popt[1] < x2) and (y1 < popt[2] < y2): params = PeakIdentificationParameters(plot) params.guess(x0, y0) centre = SpotShape( x0, y0, gridname=params.itemname, grid=params.item, h=params.h, k=params.k, ) centre.set_style("plot", "shape/spot") plot.add_item(centre) self.handle_final_shape(centre) plot.replot() centre.setTitle(params.itemname + "(%f,%f)" % (x0, y0)) if params.h is not None: Ok = PeakIdentificationWindow(params).exec_() if Ok: centre.setTitle( params.itemname + "(%d,%d)" % (params.h, params.k) ) else: QMessageBox.about(plot, "Error", "Spot not found") except: QMessageBox.about(plot, "Error", "Spot not found") plot.del_item(shape) plot.replot() def setup_shape(self, shape): """To be reimplemented""" shape.setTitle(self.TITLE) if self.setup_shape_cb is not None: self.setup_shape_cb(shape) def handle_final_shape(self, shape): """To be reimplemented""" if self.handle_final_shape_cb is not None: self.handle_final_shape_cb(shape) class ReconstructionInitParameters: # classe pour les parametres de fit def __init__(self, plot, Ax=1, Ay=0, Bx=0, By=1, order=4): self.Ax = Ax self.Ay = Ay self.Bx = Bx self.By = By self.plot = plot self.master = None self.sym = False self.order = order class ReconstructionInitWindow(QDialog): # definit une fenetre pour rentrer les parametres d'affichage des preferences def __init__(self, pref): QDialog.__init__(self) self.pref = pref self.items = list( [item for item in pref.plot.get_items() if isinstance(item, GridShape)] ) self.itemtexts = list([item.title().text() for item in self.items]) self.setWindowTitle("Reconstruction Parameters") self.setFixedSize(QSize(330, 210)) self.lab1 = QLabel(self) self.lab1.setGeometry(QRect(5, 35, 55, 25)) self.lab1.setText("x") self.lab2 = QLabel(self) self.lab2.setGeometry(QRect(5, 65, 55, 25)) self.lab2.setText("y") self.lab1 = QLabel(self) self.lab1.setGeometry(QRect(65, 5, 55, 25)) self.lab1.setText("A") self.lab2 = QLabel(self) self.lab2.setGeometry(QRect(125, 5, 55, 25)) self.lab2.setText("B") self.Axentry = QLineEdit(self) self.Axentry.setGeometry(QRect(65, 35, 55, 25)) self.Axentry.setText("%d" % pref.Ax) self.Bxentry = QLineEdit(self) self.Bxentry.setGeometry(QRect(125, 35, 55, 25)) self.Bxentry.setText("%d" % pref.Ay) self.Ayentry = QLineEdit(self) self.Ayentry.setGeometry(QRect(65, 65, 55, 25)) self.Ayentry.setText("%d" % pref.Bx) self.Byentry = QLineEdit(self) self.Byentry.setGeometry(QRect(125, 65, 55, 25)) self.Byentry.setText("%d" % pref.By) self.lab5 = QLabel(self) self.lab5.setGeometry(QRect(5, 95, 55, 25)) self.lab5.setText("Master") self.Masterentry = QComboBox(self) self.Masterentry.setGeometry(QRect(65, 95, 120, 25)) self.Masterentry.insertItems(0, self.itemtexts) self.Symentry = QCheckBox(self) self.Symentry.setGeometry(QRect(5, 125, 180, 25)) self.Symentry.setText("Add symetrical domains") self.Symentry.setChecked(pref.sym) self.lab6 = QLabel(self) self.lab6.setGeometry(QRect(5, 155, 55, 25)) self.lab6.setText("Order") self.Orderentry = QLineEdit(self) self.Orderentry.setGeometry(QRect(65, 155, 55, 25)) self.Orderentry.setText("%d" % pref.order) self.OK = QPushButton(self) self.OK.setGeometry(QRect(5, 185, 90, 25)) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setGeometry(QRect(100, 185, 90, 25)) self.Cancel.setText("Cancel") QObject.connect(self.Cancel, SIGNAL(_fromUtf8("clicked()")), self.closewin) QObject.connect(self.OK, SIGNAL(_fromUtf8("clicked()")), self.appl) # self.exec_() def appl(self): Ax = self.Axentry.text() Ay = self.Ayentry.text() Bx = self.Bxentry.text() By = self.Byentry.text() order = self.Orderentry.text() try: Ax = float(Ax) Ay = float(Ay) Bx = float(Bx) By = float(By) order = int(order) self.pref.Ax = Ax self.pref.Ay = Ay self.pref.Bx = Bx self.pref.By = By self.pref.order = order self.pref.sym = self.Symentry.isChecked() itemindex = self.Masterentry.currentIndex() self.pref.master = self.items[itemindex] self.close() self.setResult(1) except Exception: QMessageBox.about(self, "Error", "Input can only be a float") def closewin(self): self.setResult(0) self.close() def check_different(A1, B1, A2, B2): # fonction qui regarde si la matrice définie par (A1,B1) et celle par (A2,B2) représente # le même reseau delta2 = A2[0] * B2[1] - A2[1] * B2[0] nA1 = (A1[0] * B2[1] - A1[1] * B2[0]) / delta2 pA1 = (A1[1] * A2[0] - A1[0] * A2[1]) / delta2 nB1 = (B1[0] * B2[1] - B1[1] * B2[0]) / delta2 pB1 = (B1[1] * A2[0] - B1[0] * A2[1]) / delta2 """ print A1,B1 print A2,B2 print delta2,A1[0]*B2[1]-A1[0]*B2[1] print nA1,pA1,nB1,pB1 """ if nA1 % 1 == 0 and pA1 % 1 == 0 and nB1 % 1 == 0 and pB1 % 1 == 0: return False else: return True class ReconstructionShapeTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("Reconstruction"), icon=reconstructionpath, tip=_("Add a recontruction"), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): # ouvre une fenetre pour les settings de reconstruction params = ReconstructionInitParameters(plot) Ok = ReconstructionInitWindow(params).exec_() if Ok: Ax = params.Ax Ay = params.Ay Bx = params.Bx By = params.By order = params.order master = params.master sym = params.sym self.addreconstruction(plot, Ax, Ay, Bx, By, order, master, sym) def addreconstruction(self, plot, Ax, Ay, Bx, By, order, master, sym): A1 = [Ax, Ay] B1 = [Bx, By] shape = ReconstructionShape(master=master, A=A1, B=B1, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#ff5500")) # orange self.items = list( [item for item in plot.get_items() if isinstance(item, ReconstructionShape)] ) n = len(self.items) + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) # print plot,shape if sym: # il faut regarder les reconstructions possibles if master.sg in ["pm", "pg", "cm", "pmm", "p2mm", "p2mg", "p2gg", "c2mm"]: A2 = [Ax, -Ay] B2 = [Bx, -By] if check_different(A1, B1, A2, B2): shape = ReconstructionShape(master=master, A=A2, B=B2, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#0000ff")) # blue n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) if master.sg in ["p4"]: # rotation 90° A2 = [Ay, Ax] B2 = [By, Bx] if check_different(A1, B1, A2, B2): shape = ReconstructionShape(master=master, A=A2, B=B2, order=order) shape.set_style("plot", "shape/reconstructionshape") n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_color(QColor("#0000ff")) # blue shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) if master.sg in ["p4mm", "p4gm"]: # rotation 90° A2 = [Ay, Ax] B2 = [By, Bx] if check_different(A1, B1, A2, B2): shape = ReconstructionShape(master=master, A=A2, B=B2, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#0000ff")) # blue n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) # miroir par rapport a l'axe x A3 = [Ax, -Ay] B3 = [Bx, -By] if check_different(A1, B1, A3, B3) and check_different(A2, B2, A3, B3): shape = ReconstructionShape(master=master, A=A3, B=B3, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#006400")) # dark green n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) # miroir par rapport a l'axe x+rotation A4 = [Ay, -Ax] B4 = [By, -Bx] if check_different(A1, B1, A4, B4) and check_different(A2, B2, A4, B4): shape = ReconstructionShape(master=master, A=A4, B=B4, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#ff0000")) # red n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) if master.sg in ["p3", "p6"]: # rotation 120° A2 = [-Ay, Ax - Ay] B2 = [-By, Bx - By] if check_different(A1, B1, A2, B2): shape = ReconstructionShape(master=master, A=A2, B=B2, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#0000ff")) # blue n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) # rotation -120° A3 = [-Ax + Ay, -Ax] B3 = [-Bx + By, -Bx] shape = ReconstructionShape(master=master, A=A3, B=B3, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#006400")) # dark green n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) if master.sg in ["p3m1", "p31m", "p6mm"]: A2 = [-Ay, Ax - Ay] B2 = [-By, Bx - By] if check_different(A1, B1, A2, B2): shape = ReconstructionShape(master=master, A=A2, B=B2, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#0000ff")) # blue n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) A3 = [-Ax + Ay, -Ax] B3 = [-Bx + By, -Bx] if check_different(A1, B1, A3, B3): shape = ReconstructionShape(master=master, A=A3, B=B3, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#006400")) # dark green n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) A4 = [Ax - Ay, -Ay] B4 = [Bx - By, -By] if ( check_different(A1, B1, A4, B4) and check_different(A2, B2, A4, B4) and check_different(A3, B3, A4, B4) ): shape = ReconstructionShape(master=master, A=A4, B=B4, order=order) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#ff0000")) # red n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) A5 = [Ay, Ax] B5 = [By, Bx] if check_different(A4, B4, A5, B5): shape = ReconstructionShape( master=master, A=A5, B=B5, order=order ) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#808080")) # grey n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) A6 = [-Ax, Ay - Ax] B6 = [-Bx, By - Bx] shape = ReconstructionShape( master=master, A=A6, B=B6, order=order ) shape.set_style("plot", "shape/reconstructionshape") shape.set_color(QColor("#000000")) # black n = n + 1 shape.setTitle("Reconstruction %d" % n) shape.set_movable("False") master.add_slave(shape) plot.add_item(shape) z1 = master.z() z2 = shape.z() shape.setZ(z1) master.setZ(z2) plot.replot() class DistorsionCorrectionTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("DistorsionCorrection"), icon=distorsionpath, tip=_("Distorsion Correction"), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): self.items = list( [item for item in plot.get_items() if isinstance(item, SpotShape)] ) xexp = list() yexp = list() xth = list() yth = list() # on recupere l'image pour transformer des coordonnees plot en coordonnees image win = plot.window() for item in self.items: x, y = win.image.get_pixel_coordinates(item.x0, item.y0) h = item.h k = item.k u = item.grid.u v = item.grid.v O = item.grid.O xexp.append(x) yexp.append(y) x1, y1 = win.image.get_pixel_coordinates( O[0] + h * u[0] + k * v[0], O[1] + h * u[1] + k * v[1] ) xth.append(x1) yth.append(y1) p3x, p3y = self.compute_distorsion(xexp, yexp, xth, yth) self.correct_distorsion(plot, p3x, p3y) def compute_distorsion(self, xexp, yexp, xth, yth): # calcule la transformation quadratique pour passer de nref = len(xexp) xexp = np.array(xexp) yexp = np.array(yexp) xth = np.array(xth) yth = np.array(yth) p1x = [0.0, 1.0, 0.0] p1y = [0.0, 0.0, 1.0] if nref > 2: p1x, success = opt.leastsq( erreur_lin, p1x, args=(xth, yth, xexp), maxfev=10000 ) p1y, success = opt.leastsq( erreur_lin, p1y, args=(xth, yth, yexp), maxfev=10000 ) p2x = [p1x[0], p1x[1], p1x[2], 0.0, 0.0, 0.0] p2y = [p1y[0], p1y[1], p1y[2], 0.0, 0.0, 0.0] if nref > 5: # on ajoute des termes quadratiques p2x, success = opt.leastsq( erreur_quad, p2x, args=(xth, yth, xexp), maxfev=10000 ) p2y, success = opt.leastsq( erreur_quad, p2y, args=(xth, yth, yexp), maxfev=10000 ) p3x = [ p2x[0], p2x[1], p2x[2], p2x[3], p2x[4], p2x[5], 0.0, 0.0, 0.0, 0.0, ] p3y = [ p2y[0], p2y[1], p2y[2], p2y[3], p2y[4], p2y[5], 0.0, 0.0, 0.0, 0.0, ] if nref > 9: # on ajoute des termes cubiques p3x, success = opt.leastsq( erreur_cub, p3x, args=(xth, yth, xexp), maxfev=10000 ) p3y, success = opt.leastsq( erreur_cub, p3y, args=(xth, yth, yexp), maxfev=10000 ) else: p3x = [ p2x[0], p2x[1], p2x[2], p2x[3], p2x[4], p2x[5], 0.0, 0.0, 0.0, 0.0, ] p3y = [ p2y[0], p2y[1], p2y[2], p2y[3], p2y[4], p2y[5], 0.0, 0.0, 0.0, 0.0, ] else: p3x = [p1x[0], p1x[1], p1x[2], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] p3y = [p1y[0], p1y[1], p1y[2], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] return p3x, p3y def correct_distorsion(self, plot, p3x, p3y): # corrige la distorsion pour cela on mappe l'image d'arrivee avec des rectangles qui correspondent # a des quadrilateres sur l'image non deformee. Ceux-ci sont obtenus par la transformation # xth->xexp, yth->yexp # dans un premier temps, on regarde la valeur de p3x[1] et p3xy[2] pour savoir sur quelle taille d'image # faire la correction win = plot.window() w, h = win.pilim.size meshdata = [] # dx=(x2-x1)/10. # dy=(y2-y1)/10. ninter = 10 nsteps = ninter + 1 xdest = np.linspace(0, w, nsteps) ydest = np.linspace(0, h, nsteps) xgrid, ygrid = np.meshgrid( xdest, ydest ) # ensemble des valeurs de x et y de destination xgrid = xgrid.ravel() ygrid = ygrid.ravel() xstart = corr_cub(p3x, xgrid, ygrid) # ensemble des valeurs de x et y de depart ystart = corr_cub(p3y, xgrid, ygrid) fic = open("sortie.txt", "w") for i in range(len(xgrid)): fic.write("%f %f %f %f" % (xgrid[i], ygrid[i], xstart[i], ystart[i])) fic.write("\n") fic.close() xstart = xstart.reshape(nsteps, nsteps) ystart = ystart.reshape(nsteps, nsteps) xdest = np.array(np.around(xdest), int) ydest = np.array(np.around(ydest), int) xstart = np.array(np.around(xstart), int) ystart = np.array(np.around(ystart), int) meshdata = [] for j in range(ninter): for i in range(ninter): xl = xdest[i] xr = xdest[i + 1] yu = ydest[j] yl = ydest[j + 1] xul = xstart[j, i] xur = xstart[j, i + 1] yul = ystart[j, i] yur = ystart[j, i + 1] xll = xstart[j + 1, i] xlr = xstart[j + 1, i + 1] yll = ystart[j + 1, i] ylr = ystart[j + 1, i + 1] meshdata.append( ((xl, yu, xr, yl), (xul, yul, xll, yll, xlr, ylr, xur, yur)) ) img = win.pilim.transform((w, h), Image.MESH, meshdata, Image.BICUBIC) data = np.array(img).reshape((w, h)) win2 = win.duplicate() win2.image.set_data(data) # on effectue la transformation sur les taches identifiees spotitems = list([item for item in plot.items if (isinstance(item, SpotShape))]) spotitems2 = list( [item for item in win2.plot.items if (isinstance(item, SpotShape))] ) for i in range(len(spotitems)): item = spotitems[i] item2 = spotitems2[i] # coordonnees pixels x0, y0 = win2.image.get_pixel_coordinates(item.x0, item.y0) # on recherche le polygone de depart jquad = -1 for j in range( len(meshdata) ): # on pourrait faire plus rapide en affinant a partir de la position supposee.... quad = mplPath(np.array(meshdata[j][1]).reshape(4, 2)) if quad.contains_point([x0, y0]): jquad = j break if jquad == -1: # le point n'est pas sur l'image d'arrivee, on le supprime win2.plot.del_item(item2) else: # on resoud le systeme d'equation lineaire pour obtenir les coefficients # de la transformation quad->rectangle xl, yu, xr, yl = meshdata[j][0] xul, yul, xll, yll, xlr, ylr, xur, yur = meshdata[j][1] A = [ [xul, yul, 1.0, 0.0, 0.0, 0.0, -xul * xl, -yul * xl], [xur, yur, 1.0, 0.0, 0.0, 0.0, -xur * xr, -yur * xr], [xll, yll, 1.0, 0.0, 0.0, 0.0, -xll * xl, -yll * xl], [xlr, ylr, 1.0, 0.0, 0.0, 0.0, -xlr * xr, -ylr * xr], [0.0, 0.0, 0.0, xul, yul, 1.0, -xul * yu, -yul * yu], [0.0, 0.0, 0.0, xll, yll, 1.0, -xll * yl, -yll * yl], [0.0, 0.0, 0.0, xur, yur, 1.0, -xur * yu, -yur * yu], [0.0, 0.0, 0.0, xlr, ylr, 1.0, -xlr * yl, -ylr * yl], ] B = [xl, xr, xl, xr, yu, yl, yu, yl] C = np.linalg.solve(A, B) # print B,np.dot(A,C) a, b, c, d, e, f, g, h = C # transformation quad->rectangle x1 = (a * x0 + b * y0 + c) / (g * x0 + h * y0 + 1.0) y1 = (d * x0 + e * y0 + f) / (g * x0 + h * y0 + 1.0) # coordonnees plot x2, y2 = win2.image.get_plot_coordinates(x1, y1) item2.set_pos(x2, y2) win2.show() class D2DDialog(ImageDialog): # une fenetre qui permet d'afficher les images D2D et gere les datas associees def __init__( self, edit=False, toolbar=True, options={"show_contrast": True}, parent=None, data=None, ): ImageDialog.__init__( self, edit=edit, toolbar=toolbar, wintitle="D2D window", options=options ) self.setGeometry(QRect(440, 470, 500, 500)) self.plot = self.get_plot() # self.plot.window()=self self.add_tool(GridShapeTool) self.ReconstructionShapeTool = self.add_tool(ReconstructionShapeTool) self.add_tool(RectanglePeakTool) self.add_tool(DistorsionCorrectionTool) self.data = data self.connect(self.plot, SIG_ITEM_REMOVED, self.refreshlist) self.show() def set_data( self, data, xdata=[None, None], ydata=[None, None], colormap="bone", transformable=False, ): # data est un tableau 2D qui represente les donnees # xdata sont les bornes en x, ydata les bornes en y self.data = np.array(data, np.float32) # on regarde s'il y a quelque chose de dessine listimage = self.plot.get_items(item_type=IColormapImageItemType) if len(listimage) > 0: self.defaultcolormap = listimage[0].get_color_map_name() else: self.defaultcolormap = "bone" if transformable: self.image = make.trimage(self.data, colormap=colormap) else: self.image = make.image( self.data, xdata=xdata, ydata=ydata, colormap=colormap ) self.pilim = Image.frombuffer( "F", (self.data.shape[1], self.data.shape[0]), self.data, "raw", "F", 0, 1 ) def show_image(self, remove=True): if remove: # on enleve tout ce qu'il y a self.plot.del_all_items() self.plot.add_item(self.image) self.plot.show_items() def refreshlist(self, item): # quand on efface un item if isinstance(item, GridShape): # on supprime referencement aux reconstructions if len(item.slaves) > 0: for slave in item.slave: slave.master = None if isinstance(item, ReconstructionShape): if item.master is not None: item.master.remove_slave(item) def duplicate(self): # retourne une copie de la fenetre win2 = D2DDialog() win2.set_data( self.data, xdata=self.image.get_xdata(), ydata=self.image.get_ydata() ) win2.show_image() plot = self.plot plot2 = win2.plot plot2.set_title(plot.get_title()) allitems = plot.items griditems = list([item for item in allitems if (isinstance(item, GridShape))]) spotitems = list([item for item in allitems if (isinstance(item, SpotShape))]) # on rajoute les griditems for item in griditems: item.shapeparam.update_param( item ) # certains parametres ne sont pas forcement actualises item2 = GridShape() # item.shapeparam.update_shape(item) param, O, u, v, order, sg, slaves, z = item.__getstate__() # print param slaves2 = list() # on oublie les slaves pour l'instant item2.__setstate__((param, O, u, v, order, sg, slaves2, z)) plot2.add_item(item2) # on rajoute les reconstructionitems avec les nouvelles relations master-slave # item2.slaves=list() #on repart de zero for slave in slaves: slave.shapeparam.update_param( slave ) # certains parametres ne sont pas forcement actualises slave2 = ReconstructionShape() params = slave.__getstate__() slave2.__setstate__(params) item2.add_slave(slave2) slave2.set_master(item2) # on ajoute la relation master-slave plot2.add_item(slave2) # on rajoute les spotitems for item in spotitems: item.shapeparam.update_param( item ) # certains parametres ne sont pas forcement actualises item2 = SpotShape() params = item.__getstate__() item2.__setstate__(params) plot2.add_item(item2) item2.set_grid_from_gridname(item2.gridname) return win2 def test(): """Test""" # -- Create QApplication # -- filename = osp.join(osp.dirname(__file__), "test.jpg") FNAME = "test.pickle" win = D2DDialog() if access(FNAME, R_OK): print("Restoring data...") iofile = open(FNAME, "rb") image = pickle.load(iofile) x0, x1 = image.get_xdata() y0, y1 = image.get_ydata() data = image.get_data(x0, y0, x1, y1)[2] image.data = np.array(data, float) print("taille de l'image", image.data.shape) win.set_data(data, xdata=[x0, x1], ydata=[y0, y1]) win.show_image(remove=True) ngriditem = pickle.load(iofile) plot = win.get_plot() # print ngriditem for i in range(ngriditem): griditem = pickle.load(iofile) plot.add_item(griditem) for slave in griditem.slaves: # slave deja charge # slave.set_master(griditem) plot.add_item(slave) nspotitem = pickle.load(iofile) # print nspotitem for i in range(nspotitem): spotitem = pickle.load(iofile) plot.add_item(spotitem) spotitem.set_grid_from_gridname(spotitem.gridname) if spotitem.grid is None: message = "Spot %d not indexed" % (i) QMessageBox.about(plot, "Error", message) # print i,spotitem.gridname,spotitem.grid iofile.close() print("OK") else: image = make.image( filename=filename, xdata=[-200.5, 200.5], ydata=[-200.5, 200.5], colormap="bone", ) x0, x1 = image.get_xdata() y0, y1 = image.get_ydata() data = image.get_data(x0, y0, x1, y1)[2] image.data = np.array(data, float) print("taille de l'image", image.data.shape) win.set_data(data, xdata=[x0, x1], ydata=[y0, y1]) win.show_image(remove=True) plot = win.get_plot() shape = GridShape(O=[00, 000], u=[185, 0], v=[92.5, 160], sg="p3m1", order=2) shape.set_style("plot", "shape/gridshape") shape.setTitle("Reseau 1") plot.add_item(shape) win.ReconstructionShapeTool.addreconstruction( plot, 4.0, 1.0, -1.0, 3.0, 4, shape, True ) win.exec_() iofile = open(FNAME, "wb") pickle.dump(image, iofile) allitems = plot.get_items() # print allitems griditems = list([item for item in allitems if (isinstance(item, GridShape))]) # reconstructionitems=list([item for item in allitems if (isinstance(item, ReconstructionShape))]) spotitems = list([item for item in allitems if (isinstance(item, SpotShape))]) pickle.dump(len(griditems), iofile) for item in griditems: # print item pickle.dump(item, iofile) pickle.dump(len(spotitems), iofile) for item in spotitems: # print item pickle.dump(item, iofile) if __name__ == "__main__": import guidata _app = guidata.qapplication() # test() win = D2DDialog() win.exec_() binoculars-0.0.10/scripts/binoculars000077500000000000000000000314411412510113200175000ustar00rootroot00000000000000#!/usr/bin/env python import sys import os import argparse import numpy def set_src(): import sys import os.path as osp dirpath = osp.join(osp.dirname(osp.abspath(__file__)), osp.pardir) sys.path.insert(0, osp.abspath(dirpath)) try: import binoculars.space import binoculars.util except ImportError: # try to use code from src distribution set_src() import binoculars.space import binoculars.util # INFO def command_info(args): parser = argparse.ArgumentParser(prog='binoculars info') parser.add_argument('infile', nargs='+', help='input files, must be .hdf5') parser.add_argument("--config", help="display config used to generate the hdf5 file", action='store_true') parser.add_argument("--extractconfig", help="save config used to generate the hdf5 file in a new text file", action='store', dest='output') args = parser.parse_args(args) if args.output: if len(args.infile) > 1: print('only one space file argument is support with extractconfig -> using the first') config = binoculars.util.ConfigFile.fromfile(args.infile[0]) config.totxtfile(args.output) else: for f in args.infile: try: axes = binoculars.space.Axes.fromfile(f) except Exception as e: print(('{0}: unable to load Space: {1!r}'.format(f, e))) else: print(('{0} \n{1!r}'.format(f, axes))) if args.config: try: config = binoculars.util.ConfigFile.fromfile(f) except Exception as e: print(('{0}: unable to load util.ConfigFile: {1!r}'.format(f, e))) else: print(('{!r}'.format(config))) # CONVERT def command_convert(args): parser = argparse.ArgumentParser(prog='binoculars convert') parser.add_argument('--wait', action='store_true', help='wait for input files to appear') binoculars.util.argparse_common_arguments(parser, 'project', 'slice', 'pslice', 'rebin', 'transform', 'subtract') parser.add_argument('--read-trusted-zpi', action='store_true', help='read legacy .zpi files, ONLY FROM A TRUSTED SOURCE!') parser.add_argument('infile', help='input file, must be a .hdf5') parser.add_argument('outfile', help='output file, can be .hdf5 or .edf or .txt') args = parser.parse_args(args) if args.wait: binoculars.util.statusnl('waiting for {0} to appear'.format(args.infile)) binoculars.util.wait_for_file(args.infile) binoculars.util.statusnl('processing...') if args.infile.endswith('.zpi'): if not args.read_trusted_zpi: print('error: .zpi files are unsafe, use --read-trusted-zpi to open') sys.exit(1) space = binoculars.util.zpi_load(args.infile) else: space = binoculars.space.Space.fromfile(args.infile) ext = os.path.splitext(args.outfile)[-1] if args.subtract: space -= binoculars.space.Space.fromfile(args.subtract) space, info = binoculars.util.handle_ordered_operations(space, args) if ext == '.edf': binoculars.util.space_to_edf(space, args.outfile) print('saved at {0}'.format(args.outfile)) elif ext == '.txt': binoculars.util.space_to_txt(space, args.outfile) print('saved at {0}'.format(args.outfile)) elif ext == '.hdf5': space.tofile(args.outfile) print('saved at {0}'.format(args.outfile)) else: sys.stderr.write('unknown extension {0}, unable to save!\n'.format(ext)) sys.exit(1) # PLOT def command_plot(args): import matplotlib.pyplot as pyplot import binoculars.fit import binoculars.plot parser = argparse.ArgumentParser(prog='binoculars plot') parser.add_argument('infile', nargs='+') binoculars.util.argparse_common_arguments(parser, 'savepdf', 'savefile', 'clip', 'nolog', 'project', 'slice', 'pslice', 'subtract', 'rebin', 'transform') parser.add_argument('--multi', default=None, choices=('grid', 'stack')) parser.add_argument('--fit', default=None) parser.add_argument('--guess', default=None) args = parser.parse_args(args) if args.subtract: subtrspace = binoculars.space.Space.fromfile(args.subtract) subtrspace, subtrinfo = binoculars.util.handle_ordered_operations(subtrspace, args, auto3to2=True) args.nolog = True guess = [] if args.guess is not None: for n in args.guess.split(','): guess.append(float(n.replace('m', '-'))) # PLOTTING AND SIMPLEFITTING pyplot.figure(figsize=(12, 9)) plotcount = len(args.infile) plotcolumns = int(numpy.ceil(numpy.sqrt(plotcount))) plotrows = int(numpy.ceil(float(plotcount) / plotcolumns)) for i, filename in enumerate(args.infile): space = binoculars.space.Space.fromfile(filename) space, info = binoculars.util.handle_ordered_operations(space, args, auto3to2=True) fitdata = None if args.fit: fit = binoculars.fit.get_class_by_name(args.fit)(space, guess) print(fit) if fit.success: fitdata = fit.fitdata if plotcount > 1: if space.dimension == 1 and args.multi is None: args.multi = 'stack' if space.dimension == 2 and args.multi != 'grid': if args.multi is not None: sys.stderr.write('warning: stack display not supported for multi-file-plotting, falling back to grid\n') args.multi = 'grid' # elif space.dimension == 3: # not reached, project_and_slice() guarantees that elif space.dimension > 3: sys.stderr.write('error: cannot display 4 or higher dimensional data, use --project or --slice to decrease dimensionality\n') sys.exit(1) if args.subtract: space -= subtrspace basename = os.path.splitext(os.path.basename(filename))[0] if args.multi == 'grid': pyplot.subplot(plotrows, plotcolumns, i+1) binoculars.plot.plot(space, pyplot.gcf(), pyplot.gca(), label=basename, log=not args.nolog, clipping=float(args.clip), fit=fitdata) if plotcount > 1 and args.multi == 'grid': pyplot.gca().set_title(basename) if plotcount == 1: label = basename else: label = '{0} files'.format(plotcount) if args.subtract: label = '{0} (subtracted {1})'.format(label, os.path.splitext(os.path.basename(args.subtract))[0]) if plotcount > 1 and args.multi == 'stack': pyplot.legend() pyplot.suptitle('{0}, {1}'.format(label, ' '.join(info))) if args.savepdf or args.savefile: if args.savefile: pyplot.savefig(args.savefile) else: filename = '{0}_plot.pdf'.format(os.path.splitext(args.infile[0])[0]) filename = binoculars.util.find_unused_filename(filename) pyplot.savefig(filename) else: pyplot.show() # FIT def command_fit(args): import matplotlib.pyplot as pyplot import binoculars.fit import binoculars.plot parser = argparse.ArgumentParser(prog='binoculars fit') parser.add_argument('infile') parser.add_argument('axis') parser.add_argument('resolution') parser.add_argument('func') parser.add_argument('--follow', action='store_true', help='use the result of the previous fit as guess for the next') binoculars.util.argparse_common_arguments(parser, 'savepdf', 'savefile', 'clip', 'nolog') args = parser.parse_args(args) axes = binoculars.space.Axes.fromfile(args.infile) axindex = axes.index(args.axis) ax = axes[axindex] axlabel = ax.label if float(args.resolution) < ax.res: raise ValueError('interval {0} to low, minimum interval is {1}'.format(args.resolution, ax.res)) mi, ma = ax.min, ax.max bins = numpy.linspace(mi, ma, numpy.ceil(1 / numpy.float(args.resolution) * (ma - mi)) + 1) parameters = [] variance = [] fitlabel = [] guess = None basename = os.path.splitext(os.path.basename(args.infile))[0] if args.savepdf or args.savefile: if args.savefile: filename = binoculars.util.filename_enumerator(args.savefile) else: filename = binoculars.util.filename_enumerator('{0}_fit.pdf'.format(basename)) fitclass = binoculars.fit.get_class_by_name(args.func) for start, stop in zip(bins[:-1], bins[1:]): info = [] key = [slice(None) for i in axes] key[axindex] = slice(start, stop) newspace = binoculars.space.Space.fromfile(args.infile, key) left, right = newspace.axes[axindex].min, newspace.axes[axindex].max if newspace.dimension == axes.dimension: newspace = newspace.project(axindex) fit = fitclass(newspace, guess) paramnames = fit.parameters print(fit) if fit.success: fitlabel.append(numpy.mean([start, stop])) parameters.append(fit.result) variance.append(fit.variance) if args.follow and not fit.variance[0] == float(0): guess = fit.result else: guess = None fit = fit.fitdata else: fit = None guess = None print(guess) if args.savepdf or args.savefile: if len(newspace.get_masked().compressed()): if newspace.dimension == 1: pyplot.figure(figsize=(12, 9)) pyplot.subplot(111) binoculars.plot.plot(newspace, pyplot.gcf(), pyplot.gca(), label=basename, log=not args.nolog, clipping=float(args.clip), fit=fit) elif newspace.dimension == 2: pyplot.figure(figsize=(12, 9)) pyplot.subplot(121) binoculars.plot.plot(newspace, pyplot.gcf(), pyplot.gca(), label=basename, log=not args.nolog, clipping=float(args.clip), fit=None) pyplot.subplot(122) binoculars.plot.plot(newspace, pyplot.gcf(), pyplot.gca(), label=basename, log=not args.nolog, clipping=float(args.clip), fit=fit) info.append('sliced in {0} from {1} to {2}'.format(axlabel, left, right)) pyplot.suptitle('{0}'.format(' '.join(info))) pyplot.savefig(next(filename)) pyplot.close() parameters = numpy.vstack(n for n in parameters).T variance = numpy.vstack(n for n in variance).T pyplot.figure(figsize=(9, 4 * parameters.shape[0] + 2)) for i in range(parameters.shape[0]): pyplot.subplot(parameters.shape[0], 1, i) pyplot.plot(fitlabel, parameters[i, :]) if paramnames[i] in ['I']: pyplot.semilogy() pyplot.xlabel(paramnames[i]) pyplot.suptitle('fit summary of {0}'.format(args.infile)) if args.savepdf or args.savefile: if args.savefile: root, ext = os.path.split(args.savefile) pyplot.savefig('{0}_summary{1}'.format(root, ext)) print('saved at {0}_summary{1}'.format(root, ext)) filename = '{0}_summary{1}'.format(root, '.txt') else: pyplot.savefig('{0}_summary.pdf'.format(os.path.splitext(args.infile)[0])) print('saved at {0}_summary.pdf'.format(os.path.splitext(args.infile)[0])) filename = '{0}_summary.txt'.format(os.path.splitext(args.infile)[0]) file = open(filename, 'w') file.write('L\t') file.write('\t'.join(paramnames)) file.write('\n') for n in range(parameters.shape[1]): file.write('{0}\t'.format(fitlabel[n])) file.write('\t'.join(numpy.array(parameters[:, n], dtype=numpy.str))) file.write('\n') file.close() # PROCESS def command_process(args): import binoculars.main binoculars.util.register_python_executable(__file__) binoculars.main.Main.from_args(args) # start of main thread # SUBCOMMAND ARGUMENT HANDLING def usage(msg=''): print("""usage: binoculars COMMAND ... {1} available commands: convert mathematical operations & file format conversions info basic information on Space in .hdf5 file fit crystal truncation rod fitting plot 1D & 2D plotting (parts of) Space and basic fitting process data crunching / binning run binoculars COMMAND --help more info on that command """.format(sys.argv[0], msg)) sys.exit(1) if __name__ == '__main__': binoculars.space.silence_numpy_errors() subcommands = {'info': command_info, 'convert': command_convert, 'plot': command_plot, 'fit': command_fit, 'process': command_process} if len(sys.argv) < 2: usage() subcommand = sys.argv[1] if subcommand in ('-h', '--help'): usage() if subcommand not in subcommands: usage("binoculars error: unknown command '{0}'\n".format(subcommand)) subcommands[sys.argv[1]](sys.argv[2:]) binoculars-0.0.10/scripts/binoculars-fitaid000077500000000000000000001744731412510113200207530ustar00rootroot00000000000000#!/usr/bin/python3 # TODO: export des fit en ascii should be versionned. from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union import sys import os.path import itertools import h5py import matplotlib.figure import matplotlib.image import numpy import binoculars.main import binoculars.plot import binoculars.util from matplotlib.backends.backend_qt5agg import ( FigureCanvasQTAgg, NavigationToolbar2QT) from matplotlib.pyplot import Rectangle from numpy import ndarray from numpy.ma import MaskedArray from PyQt5.Qt import (Qt) from PyQt5.QtCore import (pyqtSignal) from PyQt5.QtWidgets import ( QAction, QApplication, QSlider, QMenuBar, QTabWidget, QFileDialog, QStatusBar, QMessageBox, QRadioButton, QButtonGroup, QCheckBox, QPushButton, QHBoxLayout, QVBoxLayout, QSplitter, QTableWidgetItem, QTableWidget, QLabel, QLineEdit, QMainWindow, QWidget, QComboBox, QProgressDialog, QDoubleSpinBox) from scipy.interpolate import griddata from scipy.spatial.qhull import QhullError from binoculars.fit import PeakFitBase from binoculars.space import Axes, Axis, Space, get_axis_values, get_bins class FitData(object): def __init__(self, filename: str, parent: QWidget) -> None: self.filename = filename self.axdict = {} # type: Dict[str, Axes] with h5py.File(self.filename, 'a') as db: for rodkey in self.rods(): spacename = db[rodkey].attrs['filename'] if not os.path.exists(spacename): result = QMessageBox.question( parent, 'Warning', 'Cannot find space {0} at file {1}; locate proper space'.format(rodkey, spacename), # noqa QMessageBox.Open, QMessageBox.Ignore ) if result == QMessageBox.Open: spacename = str(QFileDialog.getOpenFileName( caption='Open space {0}'.format(rodkey), directory='.', filter='*.hdf5')) db[rodkey].attrs['filename'] = spacename else: raise IOError('Select proper input') self.axdict[rodkey] = Axes.fromfile(spacename) def create_rod(self, rodkey: str, spacename: str) -> None: with h5py.File(self.filename, 'a') as db: if rodkey not in list(db.keys()): db.create_group(rodkey) db[rodkey].attrs['filename'] = spacename self.axdict[rodkey] = Axes.fromfile(spacename) def delete_rod(self, rodkey: str) -> None: with h5py.File(self.filename, 'a') as db: del db[rodkey] def rods(self) -> List[str]: with h5py.File(self.filename, 'a') as db: rods = list(db.keys()) return rods def copy(self, oldkey: str, newkey: str) -> None: with h5py.File(self.filename, 'a') as db: if oldkey in list(db.keys()): db.copy(db[oldkey], db, name=newkey) @property def filelist(self) -> List[str]: filelist = [] with h5py.File(self.filename, 'a') as db: for key in db.keys(): filelist.append(db[key].attrs['filename']) return filelist def save_axis(self, rodkey: str, axis: str) -> None: return self.save(rodkey, 'axis', axis) def save_resolution(self, rodkey: str, resolution: float) -> None: return self.save(rodkey, 'resolution', resolution) def save(self, rodkey: str, key: str, value: ndarray) -> None: with h5py.File(self.filename, 'a') as db: db[rodkey].attrs[str(key)] = value def load(self, rodkey: str, key: str) -> Optional[ndarray]: with h5py.File(self.filename, 'a') as db: if rodkey in db: if key in db[rodkey].attrs: return db[rodkey].attrs[str(key)] return None class RodData(FitData): def __init__(self, filename: str, rodkey: Optional[str], axis: str, resolution: float, parent: QWidget) -> None: super(RodData, self).__init__(filename, parent) if rodkey is not None: self.rodkey = rodkey self.slicekey = '{0}_{1}'.format(axis, resolution) self.axis = axis self.resolution = resolution with h5py.File(self.filename, 'a') as db: if rodkey in db: if self.slicekey not in db[rodkey]: db[rodkey].create_group(self.slicekey) db[rodkey][self.slicekey].create_group('attrs') def paxes(self) -> List[Axis]: axes = self.axdict[self.rodkey] projected = list(axes) axindex = axes.index(self.axis) projected.pop(axindex) return projected def get_bins(self) -> Tuple[ndarray, Axis, int]: axes = self.axdict[self.rodkey] axindex = axes.index(self.axis) ax = axes[axindex] bins = get_bins(ax, self.resolution) return bins, ax, axindex def rodlength(self) -> int: bins, ax, axindex = self.get_bins() return numpy.alen(bins) - 1 def get_index_value(self, index: int) -> ndarray: values = get_axis_values(self.axdict[self.rodkey], self.axis, self.resolution) return values[index] def get_key(self, index: int) -> List[slice]: axes = self.axdict[self.rodkey] bins, ax, axindex = self.get_bins() assert len(bins) > index, (bins, index) start, stop = bins[index], bins[index + 1] k = [slice(None) for i in axes] k[axindex] = slice(start, stop) return k def space_from_index(self, index: int) -> Space: with h5py.File(self.filename, 'a') as db: filename = db[self.rodkey].attrs['filename'] space = Space.fromfile(filename, self.get_key(index)) return space.project(self.axis) def replace_dataset(self, grp: h5py.Group, dataset_name: str, arr: ndarray) -> None: if dataset_name in grp: del grp[dataset_name] dataset = grp.require_dataset(dataset_name, arr.shape, dtype=arr.dtype, exact=False, compression='gzip') dataset.write_direct(arr) def save_data(self, index: int, key: str, data: MaskedArray) -> None: with h5py.File(self.filename, 'a') as db: grp = db[self.rodkey][self.slicekey] self.replace_dataset(grp, f"{index}_{key}", data) self.replace_dataset(grp, f"{index}_{key}_mask", data.mask) def load_data(self, index: int, key: str) -> Optional[MaskedArray]: with h5py.File(self.filename, 'a') as db: try: grp = db[self.rodkey][self.slicekey] data = grp[f"{index}_{key}"] mask = grp[f"{index}_{key}_mask"] return numpy.ma.array(data, mask=mask) except KeyError: return None def save_sliceattr(self, index: int, key: str, value: ndarray) -> None: mkey = 'mask{0}'.format(key) with h5py.File(self.filename, 'a') as db: try: group = db[self.rodkey][self.slicekey]['attrs'] # else it breaks with the old fitaid except KeyError: db[self.rodkey][self.slicekey].create_group('attrs') group = db[self.rodkey][self.slicekey]['attrs'] if key not in group: dataset = group.create_dataset(key, (self.rodlength(),)) dataset = group.create_dataset(mkey, (self.rodlength(),), dtype=numpy.bool) dataset.write_direct( numpy.ones(self.rodlength(), dtype=numpy.bool) ) group[key][index] = value group[mkey][index] = 0 def load_sliceattr(self, index: int, key: str) -> Optional[MaskedArray]: mkey = 'mask{0}'.format(key) with h5py.File(self.filename, 'a') as db: try: group = db[self.rodkey][self.slicekey]['attrs'] except KeyError: db[self.rodkey][self.slicekey].create_group('attrs') group = db[self.rodkey][self.slicekey]['attrs'] if key in list(group.keys()): g = group[key] if g.shape == (0,): return None else: mg = group[mkey] if mg.shape == (0,): return None else: return numpy.ma.array(group[key][index], mask=group[mkey][index]) else: return None def all_attrkeys(self) -> List[str]: with h5py.File(self.filename, 'a') as db: group = db[self.rodkey][self.slicekey]['attrs'] return list(group.keys()) def all_from_key(self, key: str) -> Optional[Tuple[ndarray, MaskedArray]]: with h5py.File(self.filename, 'a') as db: mkey = 'mask{0}'.format(key) axes = self.axdict[self.rodkey] group = db[self.rodkey][self.slicekey]['attrs'] if key in list(group.keys()): return (get_axis_values(axes, self.axis, self.resolution), numpy.ma.array(group[key], mask=numpy.array(group[mkey]))) return None def load_loc(self, index: Optional[int]) -> Optional[List[Optional[MaskedArray]]]: if index is not None: loc = list() count = itertools.count() key = 'guessloc{0}'.format(next(count)) while self.load_sliceattr(index, key) is not None: loc.append(self.load_sliceattr(index, key)) key = 'guessloc{0}'.format(next(count)) if len(loc) > 0: return loc else: count = itertools.count() key = 'loc{0}'.format(next(count)) while self.load_sliceattr(index, key) is not None: loc.append(self.load_sliceattr(index, key)) key = 'loc{0}'.format(next(count)) if len(loc) > 0: return loc return None def save_loc(self, index: Optional[int], loc: Sequence[Optional[MaskedArray]]) -> None: if index is not None: for i, value in enumerate(loc): self.save_sliceattr(index, 'guessloc{0}'.format(i), value) def save_segments(self, segments: ndarray) -> None: with h5py.File(self.filename, 'a') as db: grp = db[self.rodkey][self.slicekey] self.replace_dataset(grp, 'segment', segments) def load_segments(self) -> Optional[ndarray]: with h5py.File(self.filename, 'a') as db: try: grp = db[self.rodkey][self.slicekey] return grp['segment'][()] except KeyError: return None def load_int(self, key: str) -> Optional[int]: v = self.load('', key) if v is not None: return int(v) return None def save_aroundroi(self, aroundroi: bool) -> None: return super(RodData, self).save(self.rodkey, 'aroundroi', aroundroi) def save_fromfit(self, fromfit: bool) -> None: return super(RodData, self).save(self.rodkey, 'fromfit', fromfit) def save_index(self, index: int) -> None: return super(RodData, self).save(self.rodkey, 'index', index) def save_roi(self, roi: List[float]) -> None: return super(RodData, self).save(self.rodkey, 'roi', roi) def currentindex(self) -> Optional[int]: index = self.load_int('index') if index is not None: if index < 0 or index >= self.rodlength(): # deal with no selection index = None return index def load(self, _rodkey: str, key: str) -> Optional[ndarray]: return super(RodData, self).load(self.rodkey, key) def __iter__(self) -> Generator[Space, None, None]: for index in range(self.rodlength()): yield self.space_from_index(index) class Window(QMainWindow): def __init__(self, parent: Optional[QWidget] = None): super(Window, self).__init__(parent) newproject = QAction("New project", self) newproject.triggered.connect(self.newproject) loadproject = QAction("Open project", self) loadproject.triggered.connect(self.loadproject) addspace = QAction("Import space", self) addspace.triggered.connect(self.add_to_project) menu_bar = QMenuBar() file = menu_bar.addMenu("&File") file.addAction(newproject) file.addAction(loadproject) file.addAction(addspace) self.setMenuBar(menu_bar) self.statusbar = QStatusBar() self.tab_widget = QTabWidget(self) self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.tab_widget.removeTab) self.setCentralWidget(self.tab_widget) self.setMenuBar(menu_bar) self.setStatusBar(self.statusbar) def newproject(self) -> None: dialog = QFileDialog(self, "project filename") dialog.setNameFilters(['binoculars fit file (*.fit)']) dialog.setDefaultSuffix('fit') dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: widget = TopWidget(str(fname), parent=self) self.tab_widget.addTab(widget, short_filename(str(fname))) self.tab_widget.setCurrentWidget(widget) except Exception as e: QMessageBox.critical( self, 'New project', 'Unable to save project to {}: {}'.format(fname, e) ) def loadproject(self, filename: Optional[str]=None) -> None: if not filename: dialog = QFileDialog(self, "Load project") dialog.setNameFilters(['binoculars fit file (*.fit)']) dialog.setFileMode(QFileDialog.ExistingFiles) dialog.setAcceptMode(QFileDialog.AcceptOpen) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: widget = TopWidget(str(fname), parent=self) self.tab_widget.addTab(widget, short_filename(str(fname))) self.tab_widget.setCurrentWidget(widget) except Exception as e: QMessageBox.critical( self, 'Load project', 'Unable to load project from {}: {}'.format(fname, e) ) else: widget = TopWidget(str(fname), parent=self) self.tab_widget.addTab(widget, 'fname') self.tab_widget.setCurrentWidget(widget) def add_to_project(self, filename: Optional[str]=None) -> None: if self.tab_widget.count() == 0: QMessageBox.warning( self, 'Warning', 'First select a file to store data') self.newproject() if not filename: dialog = QFileDialog(self, "Import spaces") dialog.setNameFilters(['binoculars space file (*.hdf5)']) dialog.setFileMode(QFileDialog.ExistingFiles) dialog.setAcceptMode(QFileDialog.AcceptOpen) if not dialog.exec_(): return fname = dialog.selectedFiles() if not fname: return for name in fname: try: widget = self.tab_widget.currentWidget() widget.addspace(str(name)) except Exception as e: QMessageBox.critical( self, 'Import spaces', 'Unable to import space {}: {}'.format(fname, e) ) else: widget = self.tab_widget.currentWidget() widget.addspace(filename) class TopWidget(QWidget): def __init__(self, filename: str, parent: Optional[QWidget]=None): super(TopWidget, self).__init__(parent) hbox = QHBoxLayout() vbox = QVBoxLayout() minihbox = QHBoxLayout() minihbox2 = QHBoxLayout() self.database = FitData(filename, self) self.table = TableWidget(self.database) self.nav = ButtonedSlider() self.nav.slice_index.connect(self.index_change) self.table.trigger.connect(self.active_change) self.table.check_changed.connect(self.refresh_plot) self.tab_widget = QTabWidget() self.fitwidget = FitWidget(None, self) self.integratewidget = IntegrateWidget(None, self, self) self.plotwidget = OverviewWidget(None, self) self.peakwidget = PeakWidget(None, self) self.tab_widget.addTab(self.fitwidget, 'Fit') self.tab_widget.addTab(self.integratewidget, 'Integrate') self.tab_widget.addTab(self.plotwidget, 'plot') self.tab_widget.addTab(self.peakwidget, 'Peaktracker') self.emptywidget = QWidget() self.emptywidget.setLayout(vbox) vbox.addWidget(self.table) vbox.addWidget(self.nav) self.functions = list() # type: List[Type[PeakFitBase]] self.function_box = QComboBox() for function in dir(binoculars.fit): cls = getattr(binoculars.fit, function) if isinstance(cls, type)\ and issubclass(cls, PeakFitBase): self.functions.append(cls) self.function_box.addItem(function) self.function_box.setCurrentIndex( self.function_box.findText('PolarLorentzian2D') ) vbox.addWidget(self.function_box) vbox.addLayout(minihbox) vbox.addLayout(minihbox2) self.all_button = QPushButton('fit all') self.rod_button = QPushButton('fit rod') self.slice_button = QPushButton('fit slice') self.all_button.clicked.connect(self.fit_all) self.rod_button.clicked.connect(self.fit_rod) self.slice_button.clicked.connect(self.fit_slice) minihbox.addWidget(self.all_button) minihbox.addWidget(self.rod_button) minihbox.addWidget(self.slice_button) self.allint_button = QPushButton('int all') self.rodint_button = QPushButton('int rod') self.sliceint_button = QPushButton('int slice') self.allint_button.clicked.connect(self.int_all) self.rodint_button.clicked.connect(self.int_rod) self.sliceint_button.clicked.connect(self.int_slice) minihbox2.addWidget(self.allint_button) minihbox2.addWidget(self.rodint_button) minihbox2.addWidget(self.sliceint_button) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.emptywidget) splitter.addWidget(self.tab_widget) self.tab_widget.currentChanged.connect(self.tab_change) hbox.addWidget(splitter) self.setLayout(hbox) def tab_change(self, index: int) -> None: if index == 2: self.refresh_plot() def addspace(self, filename: Optional[str]=None) -> None: self.table.addspace(filename or str(QFileDialog.getOpenFileName(self, 'Open Project', '.', '*.hdf5'))) # noqa def active_change(self) -> None: rodkey, axis, resolution = self.table.currentkey() newdatabase = RodData(self.database.filename, rodkey, axis, resolution, self) self.integratewidget.database = newdatabase self.peakwidget.database = newdatabase self.integratewidget.set_axis() self.peakwidget.set_axis() self.fitwidget.database = newdatabase self.nav.set_length(newdatabase.rodlength()) index = newdatabase.currentindex() if index is not None: self.nav.set_index(index) self.index_change(index) def index_change(self, index: int) -> None: # deal with no index if index != -1: if self.fitwidget.database is not None: self.fitwidget.database.save_index(index) self.fitwidget.plot(index) self.integratewidget.plot(index) def refresh_plot(self) -> None: self.plotwidget.refresh( [RodData(self.database.filename, rodkey, axis, resolution, self) for rodkey, axis, resolution in self.table.checked()] ) @property def fitclass(self) -> Type[PeakFitBase]: return self.functions[self.function_box.currentIndex()] def fit_slice(self) -> None: index = self.nav.index() if self.fitwidget.database is not None: space = self.fitwidget.database.space_from_index(index) self.fitwidget.fit(index, space, self.fitclass) self.fit_loc(self.fitwidget.database) self.fitwidget.plot(index) def fit_rod(self) -> None: def function(index: int, space: Space) -> None: self.fitwidget.fit(index, space, self.fitclass) if self.fitwidget.database is not None: self.progressbox(self.fitwidget.database.rodkey, function, enumerate( self.fitwidget.database), self.fitwidget.database.rodlength()) self.fit_loc(self.fitwidget.database) self.fitwidget.plot() def fit_all(self) -> None: def function(index: int, space: Space) -> None: self.fitwidget.fit(index, space, self.fitclass) for rodkey, axis, resolution in self.table.checked(): self.fitwidget.database = RodData( self.database.filename, rodkey, axis, resolution, self) self.progressbox( self.fitwidget.database.rodkey, function, enumerate(self.fitwidget.database), self.fitwidget.database.rodlength() ) self.fit_loc(self.fitwidget.database) self.fitwidget.plot() def int_slice(self) -> None: index = self.nav.index() if self.fitwidget.database is not None: space = self.fitwidget.database.space_from_index(index) self.integratewidget.integrate(index, space) self.integratewidget.plot(index) def int_rod(self) -> None: if self.integratewidget.database is not None: self.progressbox( self.integratewidget.database.rodkey, self.integratewidget.integrate, enumerate(self.integratewidget.database), self.integratewidget.database.rodlength() ) self.integratewidget.plot() def int_all(self) -> None: for rodkey, axis, resolution in self.table.checked(): self.integratewidget.database = RodData( self.database.filename, rodkey, axis, resolution, self) self.progressbox( self.integratewidget.database.rodkey, self.integratewidget.integrate, enumerate(self.integratewidget.database), self.integratewidget.database.rodlength() ) self.integratewidget.plot() def fit_loc(self, database: RodData) -> None: deg = 2 for param in database.all_attrkeys(): if param.startswith('loc'): res = database.all_from_key(param) if res is not None: x, y = res else: return res = database.all_from_key('var_{0}'.format(param)) if res is not None: x, yvar = res else: return cx = x[numpy.invert(y.mask)] y = y.compressed() yvar = yvar.compressed() w = numpy.log(1 / yvar) w[w == numpy.inf] = 0 w = numpy.nan_to_num(w) w[w < 0] = 0 w[w < numpy.median(w)] = 0 if len(x) > 0: c = numpy.polynomial.polynomial.polyfit(cx, y, deg, w=w) newy = numpy.polynomial.polynomial.polyval(x, c) for index, newval in enumerate(newy): database.save_sliceattr( index, 'guessloc{0}'.format(param.lstrip('loc')), newval ) def progressbox(self, rodkey: str, function: Any, iterator: Any, length: int) -> None: pd = QProgressDialog( 'Processing {0}'.format(rodkey), 'Cancel', 0, length) pd.setWindowModality(Qt.WindowModal) pd.show() def progress(index: int, item: Any) -> None: pd.setValue(index) if pd.wasCanceled(): raise KeyboardInterrupt QApplication.processEvents() function(*item) for index, item in enumerate(iterator): progress(index, item) pd.close() class TableWidget(QWidget): trigger = pyqtSignal() check_changed = pyqtSignal() def __init__(self, database: FitData, parent: Optional[QWidget]=None) -> None: super(TableWidget, self).__init__(parent) hbox = QHBoxLayout() self.database = database self.activeindex = 0 self.table = QTableWidget(0, 5) self.table.setHorizontalHeaderLabels( ['', 'rod', 'axis', 'res', 'remove']) self.table.cellClicked.connect(self.setlength) for index, width in enumerate([25, 150, 40, 50, 70]): self.table.setColumnWidth(index, width) for filename, rodkey in zip(database.filelist, database.rods()): self.addspace(filename, rodkey) hbox.addWidget(self.table) self.setLayout(hbox) def addspace(self, filename: str, rodkey: Optional[str]=None) -> None: def remove_callback(rodkey: str) -> Callable[[], None]: return lambda: self.remove(rodkey) def activechange_callback(index: int) -> Callable[[], None]: return lambda: self.setlength(index, 1) if rodkey is None: rodkey = short_filename(filename) if rodkey in self.database.rods(): newkey = find_unused_rodkey(rodkey, self.database.rods()) self.database.copy(rodkey, newkey) rodkey = newkey old_axis, old_resolution = (self.database.load(rodkey, 'axis'), self.database.load(rodkey, 'resolution')) self.database.create_rod(rodkey, filename) index = self.table.rowCount() self.table.insertRow(index) axes = Axes.fromfile(filename) checkboxwidget = QCheckBox() checkboxwidget.rodkey = rodkey checkboxwidget.setChecked(False) self.table.setCellWidget(index, 0, checkboxwidget) checkboxwidget.clicked.connect(self.check_changed) item = QTableWidgetItem(rodkey) self.table.setItem(index, 1, item) axis = QComboBox() for ax in axes: axis.addItem(ax.label) self.table.setCellWidget(index, 2, axis) if old_axis is not None: self.table.cellWidget( index, 2).setCurrentIndex(axes.index(old_axis)) elif index > 0: self.table.cellWidget(0, 2).setCurrentIndex( self.table.cellWidget(0, 2).currentIndex()) axis.currentIndexChanged.connect(self.trigger) resolution = QLineEdit() if old_resolution is not None: resolution.setText(str(old_resolution)) elif index > 0: resolution.setText(self.table.cellWidget(0, 3).text()) else: resolution.setText( str(axes[axes.index(str(axis.currentText()))].res)) resolution.editingFinished.connect(activechange_callback(index)) self.table.setCellWidget(index, 3, resolution) buttonwidget = QPushButton('remove') buttonwidget.clicked.connect(remove_callback(rodkey)) self.table.setCellWidget(index, 4, buttonwidget) def remove(self, rodkey: str) -> None: table_rodkeys = [self.table.cellWidget(index, 0).rodkey for index in range(self.table.rowCount())] for index, label in enumerate(table_rodkeys): if rodkey == label: self.table.removeRow(index) self.database.delete_rod(rodkey) print('removed: {0}'.format(rodkey)) def setlength(self, y: int, x: int=1) -> None: if self.database is not None: if x == 1: self.activeindex = y rodkey, axis, resolution = self.currentkey() self.database.save_axis(rodkey, axis) self.database.save_resolution(rodkey, resolution) self.trigger.emit() def currentkey(self) -> Tuple[Any, str, float]: rodkey = self.table.cellWidget(self.activeindex, 0).rodkey axis = str(self.table.cellWidget(self.activeindex, 2).currentText()) resolution = float(self.table.cellWidget(self.activeindex, 3).text()) return rodkey, axis, resolution def checked(self) -> List[Tuple[Any, str, float]]: selection = [] for index in range(self.table.rowCount()): checkbox = self.table.cellWidget(index, 0) if checkbox.isChecked(): rodkey = self.table.cellWidget(index, 0).rodkey axis = str(self.table.cellWidget(index, 2).currentText()) resolution = float(self.table.cellWidget(index, 3).text()) selection.append((rodkey, axis, resolution)) return selection def short_filename(filename: str) -> str: return filename.split('/')[-1].split('.')[0] class HiddenToolbar(NavigationToolbar2QT): def __init__(self, corner_callback: Any, canvas: Any) -> None: super(HiddenToolbar, self).__init__(canvas, None) self._corner_callback = corner_callback self.zoom() def _generate_key(self) -> List[List[float]]: limits = [] for a in self.canvas.figure.get_axes(): limits.append([a.get_xlim(), a.get_ylim()]) return limits def press_zoom(self, event: Any) -> None: self._corner_preclick = self._generate_key() def release_zoom(self, event: Any) -> None: if self._corner_preclick == self._generate_key(): self._corner_callback(event.xdata, event.ydata) self._corner_preclick = [] class FitWidget(QWidget): def __init__(self, database: Optional[RodData]=None, parent: Optional[QWidget]=None): super(FitWidget, self).__init__(parent) self.database = database vbox = QHBoxLayout() self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvasQTAgg(self.figure) self.toolbar = HiddenToolbar(self.loc_callback, self.canvas) vbox.addWidget(self.canvas) self.setLayout(vbox) def loc_callback(self, x: int, y: int) -> None: if self.database is not None and self.ax: self.database.save_loc(self.database.currentindex(), numpy.array([x, y])) def plot(self, index: Optional[int]=None) -> None: if self.database is not None: if index is None: index = self.database.currentindex() if index is not None: space = self.database.space_from_index(index) fitdata = self.database.load_data(index, 'fit') self.figure.clear() self.figure.space_axes = space.axes info = self.database.get_index_value(index) label = self.database.axis if fitdata is not None: if space.dimension == 1: self.ax = self.figure.add_subplot(111) binoculars.plot.plot( space, self.figure, self.ax, fit=fitdata) elif space.dimension == 2: self.ax = self.figure.add_subplot(121) binoculars.plot.plot(space, self.figure, self.ax, fit=None) self.ax = self.figure.add_subplot(122) binoculars.plot.plot( space, self.figure, self.ax, fit=fitdata) else: self.ax = self.figure.add_subplot(111) binoculars.plot.plot(space, self.figure, self.ax) self.figure.suptitle('{0}, res = {1}, {2} = {3}'.format( self.database.rodkey, self.database.resolution, label, info)) self.canvas.draw() def fit(self, index: int, space: Space, function: Any) -> None: if self.database is not None: if not len(space.get_masked().compressed()) == 0: loc = self.get_loc() fit = function(space, loc=loc) fit.fitdata.mask = space.get_masked().mask self.database.save_data(index, 'fit', fit.fitdata) params = list(line.split(':')[0] for line in fit.summary.split('\n')) print(fit.result, fit.variance) for key, value in zip(params, fit.result): self.database.save_sliceattr(index, key, value) for key, value in zip(params, fit.variance): self.database.save_sliceattr( index, 'var_{0}'.format(key), value) def get_loc(self) -> Optional[MaskedArray]: if self.database is not None: return self.database.load_loc(self.database.currentindex()) return None class IntegrateWidget(QWidget): def __init__(self, database: Optional[RodData], topwidget: TopWidget, parent: Optional[QWidget] = None): super(IntegrateWidget, self).__init__(parent) self.database = database self.topwidget = topwidget self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvasQTAgg(self.figure) self.toolbar = HiddenToolbar(self.loc_callback, self.canvas) hbox = QHBoxLayout() splitter = QSplitter(Qt.Vertical) self.make_controlwidget() splitter.addWidget(self.canvas) splitter.addWidget(self.control_widget) hbox.addWidget(splitter) self.setLayout(hbox) def make_controlwidget(self) -> None: self.control_widget = QWidget() integratebox = QVBoxLayout() intensitybox = QHBoxLayout() backgroundbox = QHBoxLayout() self.aroundroi = QCheckBox('background around roi') self.aroundroi.setChecked(True) self.aroundroi.clicked.connect(self.refresh_aroundroi) self.hsize = QDoubleSpinBox() self.vsize = QDoubleSpinBox() intensitybox.addWidget(QLabel('roi size:')) intensitybox.addWidget(self.hsize) intensitybox.addWidget(self.vsize) self.left = QDoubleSpinBox() self.right = QDoubleSpinBox() self.top = QDoubleSpinBox() self.bottom = QDoubleSpinBox() self.hsize.valueChanged.connect(self.send) self.vsize.valueChanged.connect(self.send) self.left.valueChanged.connect(self.send) self.right.valueChanged.connect(self.send) self.top.valueChanged.connect(self.send) self.bottom.valueChanged.connect(self.send) backgroundbox.addWidget(self.aroundroi) backgroundbox.addWidget(self.left) backgroundbox.addWidget(self.right) backgroundbox.addWidget(self.top) backgroundbox.addWidget(self.bottom) integratebox.addLayout(intensitybox) integratebox.addLayout(backgroundbox) self.fromfit = QRadioButton('peak from fit', self) self.fromfit.setChecked(True) self.fromfit.toggled.connect(self.plot_box) self.fromfit.toggled.connect(self.refresh_tracker) self.fromsegment = QRadioButton('peak from segment', self) self.fromsegment.setChecked(False) self.fromsegment.toggled.connect(self.plot_box) self.fromsegment.toggled.connect(self.refresh_tracker) self.trackergroup = QButtonGroup(self) self.trackergroup.addButton(self.fromfit) self.trackergroup.addButton(self.fromsegment) radiobox = QHBoxLayout() radiobox.addWidget(self.fromfit) radiobox.addWidget(self.fromsegment) integratebox.addLayout(radiobox) self.control_widget.setLayout(integratebox) # set default values # self.hsize.setValue(0.1) # self.vsize.setValue(0.1) def refresh_aroundroi(self) -> None: if self.database is not None: self.database.save_aroundroi(self.aroundroi.isChecked()) axes = self.database.paxes() if not self.aroundroi.isChecked(): self.left.setMinimum(axes[0].min) self.left.setMaximum(axes[0].max) self.right.setMinimum(axes[0].min) self.right.setMaximum(axes[0].max) self.top.setMinimum(axes[1].min) self.top.setMaximum(axes[1].max) self.bottom.setMinimum(axes[1].min) self.bottom.setMaximum(axes[1].max) else: self.left.setMinimum(0) self.left.setMaximum(axes[0].max - axes[0].min) self.right.setMinimum(0) self.right.setMaximum(axes[0].max - axes[0].min) self.top.setMinimum(0) self.top.setMaximum(axes[1].max - axes[1].min) self.bottom.setMinimum(0) self.bottom.setMaximum(axes[1].max - axes[1].min) def refresh_tracker(self) -> None: if self.database is not None: index = self.database.currentindex() if index is not None: self.database.save_fromfit(self.fromfit.isChecked()) self.plot_box() def set_axis(self) -> None: if self.database is not None: roi = self.database.load('', 'roi') aroundroi = self.database.load('', 'aroundroi') if aroundroi is not None: self.aroundroi.setChecked(aroundroi != 0) else: self.aroundroi.setChecked(True) self.refresh_aroundroi() axes = self.database.paxes() self.hsize.setSingleStep(axes[1].res) self.hsize.setDecimals(len(str(axes[1].res)) - 2) self.vsize.setSingleStep(axes[0].res) self.vsize.setDecimals(len(str(axes[0].res)) - 2) self.left.setSingleStep(axes[1].res) self.left.setDecimals(len(str(axes[1].res)) - 2) self.right.setSingleStep(axes[1].res) self.right.setDecimals(len(str(axes[1].res)) - 2) self.top.setSingleStep(axes[0].res) self.top.setDecimals(len(str(axes[0].res)) - 2) self.bottom.setSingleStep(axes[0].res) self.bottom.setDecimals(len(str(axes[0].res)) - 2) tracker = self.database.load('', 'fromfit') if tracker is not None: if tracker: self.fromfit.setChecked(True) else: self.fromsegment.setChecked(True) if roi is not None: boxes = [self.hsize, self.vsize, self.left, self.right, self.top, self.bottom] # noqa for box, value in zip(boxes, roi): box.setValue(value) def send(self) -> None: if self.database is not None: index = self.database.currentindex() if index is not None: roi = [self.hsize.value(), self.vsize.value(), self.left.value(), self.right.value(), self.top.value(), self.bottom.value()] self.database.save_roi(roi) self.plot_box() def integrate(self, index: int, space: Space) -> None: loc = self.get_loc() if loc is not None: axes = space.axes key = space.get_key(self.intkey(loc, axes)) if self.database is not None: fitdata = self.database.load_data(index, 'fit') if fitdata is not None: fitintensity = fitdata[key].data.flatten() fitbkg = numpy.hstack([fitdata[space.get_key(bkgkey)].data.flatten() # noqa for bkgkey in self.bkgkeys(loc, axes)]) if numpy.alen(fitbkg) == 0: fitstructurefactor = fitintensity.sum() elif numpy.alen(fitintensity) == 0: fitstructurefactor = numpy.nan else: fitstructurefactor = numpy.sqrt(fitintensity.sum() - numpy.alen(fitintensity) * 1.0 / numpy.alen(fitbkg) * fitbkg.sum()) # noqa self.database.save_sliceattr( index, 'fitsf', fitstructurefactor) niintensity = space[ self.intkey(loc, axes)].get_masked().compressed() try: intensity = interpolate( space[self.intkey(loc, axes)]).flatten() bkg = numpy.hstack([space[bkgkey].get_masked().compressed() for bkgkey in self.bkgkeys(loc, axes)]) interdata = space.get_masked() interdata[key] = intensity.reshape(interdata[key].shape) interdata[key].mask = numpy.zeros_like(interdata[key]) self.database.save_data(index, 'inter', interdata) except ValueError as e: print('Warning error interpolating silce {0}: {1}'.format(index, e)) # noqa intensity = numpy.array([]) bkg = numpy.array([]) except QhullError as e: print('Warning error interpolating silce {0}: {1}'.format(index, e)) # noqa intensity = numpy.array([]) bkg = numpy.array([]) if numpy.alen(intensity) == 0: structurefactor = numpy.nan nistructurefactor = numpy.nan elif numpy.alen(bkg) == 0: structurefactor = numpy.sqrt(intensity.sum()) nistructurefactor = numpy.sqrt(niintensity.sum()) else: structurefactor = numpy.sqrt(intensity.sum() - numpy.alen(intensity) * 1.0 / numpy.alen(bkg) * bkg.sum()) # noqa nistructurefactor = numpy.sqrt(niintensity.sum() - numpy.alen(niintensity) * 1.0 / numpy.alen(bkg) * bkg.sum()) # noqa self.database.save_sliceattr(index, 'sf', structurefactor) self.database.save_sliceattr(index, 'nisf', nistructurefactor) print('Structurefactor {0}: {1}'.format(index, structurefactor)) def intkey(self, coords: Optional[Union[List[Optional[MaskedArray]], ndarray]], axes: Axes) -> Tuple[slice, ...]: if coords is not None: vsize = self.vsize.value() / 2 hsize = self.hsize.value() / 2 return tuple(ax.restrict_slice(slice(coord - size, coord + size)) for ax, coord, size in zip(axes, coords, [vsize, hsize]) if coord is not None) return () def bkgkeys(self, coords: Optional[Union[List[Optional[MaskedArray]], ndarray]], axes: Axes) -> Union[Tuple[Tuple[slice, slice], Tuple[slice, slice], Tuple[slice, slice], Tuple[slice, slice]], List[Tuple[slice, slice]]]: if self.database is not None: aroundroi = self.database.load('', 'aroundroi') if aroundroi and coords is not None and coords[0] is not None and coords[1] is not None: key = self.intkey(coords, axes) vsize = self.vsize.value() / 2 hsize = self.hsize.value() / 2 leftkey_slice = slice(coords[1] - hsize - self.left.value(), coords[1] - hsize) leftkey = (key[0], axes[1].restrict_slice(leftkey_slice)) rightkey_slice = slice(coords[1] + hsize, coords[1] + hsize + self.right.value()) rightkey = (key[0], axes[1].restrict_slice(rightkey_slice)) topkey_slice = slice(coords[0] - vsize - self.top.value(), coords[0] - vsize) topkey = (axes[0].restrict_slice(topkey_slice), key[1]) bottomkey_slice = slice(coords[0] + vsize, coords[0] + vsize + self.bottom.value()) bottomkey = (axes[0].restrict_slice(bottomkey_slice), key[1]) return leftkey, rightkey, topkey, bottomkey else: slice0 = slice(self.left.value(), self.right.value()) slice1 = slice(self.top.value(), self.bottom.value()) return [(axes[0].restrict_slice(slice0), axes[1].restrict_slice(slice1))] return [] def get_loc(self) -> Optional[Union[List[Optional[MaskedArray]], ndarray]]: if self.database is not None: index = self.database.currentindex() if index is not None: if self.fromfit.isChecked(): return self.database.load_loc(index) else: indexvalue = self.database.get_index_value(index) return self.topwidget.peakwidget.get_coords(indexvalue) return None def loc_callback(self, x: int, y: int) -> None: if self.ax and self.database is not None: index = self.database.currentindex() if index is not None: if self.fromfit.isChecked(): self.database.save_loc(index, numpy.array([x, y])) else: indexvalue = self.database.get_index_value(index) self.topwidget.peakwidget.add_row(numpy.array([indexvalue, x, y])) self.plot_box() def plot(self, index: Optional[int]=None) -> None: if self.database is not None: if index is None: index = self.database.currentindex() if index is not None: space = self.database.space_from_index(index) interdata = self.database.load_data(index, 'inter') info = self.database.get_index_value(index) label = self.database.axis self.figure.clear() self.figure.space_axes = space.axes if interdata is not None: if space.dimension == 1: self.ax = self.figure.add_subplot(111) binoculars.plot.plot( space, self.figure, self.ax, fit=interdata) elif space.dimension == 2: self.ax = self.figure.add_subplot(121) binoculars.plot.plot(space, self.figure, self.ax, fit=None) self.ax = self.figure.add_subplot(122) binoculars.plot.plot( space, self.figure, self.ax, fit=interdata) else: self.ax = self.figure.add_subplot(111) binoculars.plot.plot(space, self.figure, self.ax) self.figure.suptitle('{0}, res = {1}, {2} = {3}'.format( self.database.rodkey, self.database.resolution, label, info)) self.plot_box() self.canvas.draw() def plot_box(self) -> None: if self.database is not None: index = self.database.currentindex() if index is not None: loc = self.get_loc() if len(self.figure.get_axes()) != 0 and loc is not None: ax = self.figure.get_axes()[0] axes = self.figure.space_axes key = self.intkey(loc, axes) bkgkey = self.bkgkeys(loc, axes) ax.patches = [] rect = Rectangle((key[0].start, key[1].start), key[0].stop - key[0].start, key[1].stop - key[1].start, alpha=0.2, color='k') ax.add_patch(rect) for k in bkgkey: bkg = Rectangle((k[0].start, k[1].start), k[0].stop - k[0].start, k[1].stop - k[1].start, alpha=0.2, color='r') ax.add_patch(bkg) self.canvas.draw() class ButtonedSlider(QWidget): slice_index = pyqtSignal(int) def __init__(self, parent: Optional[QWidget]=None): super(ButtonedSlider, self).__init__(parent) self.navigation_button_left_end = QPushButton('|<') self.navigation_button_left_one = QPushButton('<') self.navigation_slider = QSlider(Qt.Horizontal) self.navigation_slider.sliderReleased.connect(self.send) self.navigation_button_right_one = QPushButton('>') self.navigation_button_right_end = QPushButton('>|') self.navigation_button_left_end.setMaximumWidth(20) self.navigation_button_left_one.setMaximumWidth(20) self.navigation_button_right_end.setMaximumWidth(20) self.navigation_button_right_one.setMaximumWidth(20) self.navigation_button_left_end.clicked.connect( self.slider_change_left_end) self.navigation_button_left_one.clicked.connect( self.slider_change_left_one) self.navigation_button_right_end.clicked.connect( self.slider_change_right_end) self.navigation_button_right_one.clicked.connect( self.slider_change_right_one) box = QHBoxLayout() box.addWidget(self.navigation_button_left_end) box.addWidget(self.navigation_button_left_one) box.addWidget(self.navigation_slider) box.addWidget(self.navigation_button_right_one) box.addWidget(self.navigation_button_right_end) self.setDisabled(True) self.setLayout(box) def set_length(self, length: int) -> None: self.navigation_slider.setMinimum(0) self.navigation_slider.setMaximum(length - 1) self.navigation_slider.setTickPosition(QSlider.TicksBelow) self.navigation_slider.setValue(0) self.setEnabled(True) def send(self) -> None: self.slice_index.emit(self.navigation_slider.value()) def slider_change_left_one(self) -> None: self.navigation_slider.setValue( max(self.navigation_slider.value() - 1, 0)) self.send() def slider_change_left_end(self) -> None: self.navigation_slider.setValue(0) self.send() def slider_change_right_one(self) -> None: self.navigation_slider.setValue( min(self.navigation_slider.value() + 1, self.navigation_slider.maximum())) self.send() def slider_change_right_end(self) -> None: self.navigation_slider.setValue(self.navigation_slider.maximum()) self.send() def index(self) -> int: return self.navigation_slider.value() def set_index(self, index: int) -> None: self.navigation_slider.setValue(index) class HiddenToolbar2(NavigationToolbar2QT): def __init__(self, canvas: Any): super(HiddenToolbar2, self).__init__(canvas, None) self.zoom() class OverviewWidget(QWidget): def __init__(self, database: Optional[RodData], parent: Optional[QWidget]=None): super(OverviewWidget, self).__init__(parent) self.databaselist = [] # type: List[RodData] self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvasQTAgg(self.figure) self.toolbar = HiddenToolbar2(self.canvas) self.table = QTableWidget(0, 2) self.make_table() self.table.cellClicked.connect(self.plot) hbox = QHBoxLayout() splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.canvas) splitter.addWidget(self.control_widget) hbox.addWidget(splitter) self.setLayout(hbox) def select(self) -> List[str]: selection = [] for index in range(self.table.rowCount()): checkbox = self.table.cellWidget(index, 0) if checkbox.isChecked(): selection.append(str(self.table.cellWidget(index, 1).text())) return selection def make_table(self) -> None: self.control_widget = QWidget() vbox = QVBoxLayout() minibox = QHBoxLayout() vbox.addWidget(self.table) self.table.setHorizontalHeaderLabels(['', 'param']) for index, width in enumerate([25, 50]): self.table.setColumnWidth(index, width) self.log = QCheckBox('log') self.log.clicked.connect(self.plot) self.export_button = QPushButton('export curves') self.export_button.clicked.connect(self.export) minibox.addWidget(self.log) minibox.addWidget(self.export_button) vbox.addLayout(minibox) self.control_widget.setLayout(vbox) def export(self) -> None: folder = str(QFileDialog.getExistingDirectory( self, "Select directory to save curves")) params = self.select() for param in params: for database in self.databaselist: res = database.all_from_key(param) if res is not None: x, y = res args = numpy.argsort(x) filename = '{0}_{1}.txt'.format(param, database.rodkey) numpy.savetxt(os.path.join(folder, filename), numpy.vstack(arr[args] for arr in [x, y]).T) def refresh(self, databaselist: List[RodData]) -> None: self.databaselist = databaselist params = self.select() while self.table.rowCount() > 0: self.table.removeRow(0) allparams = [[param for param in database.all_attrkeys() if not param.startswith('mask')] for database in databaselist] allparams.extend([['locx_s', 'locy_s'] for database in databaselist if database.load_segments() is not None]) if len(allparams) > 0: uniqueparams = numpy.unique( numpy.hstack([params for params in allparams])) else: uniqueparams = [] for param in uniqueparams: index = self.table.rowCount() self.table.insertRow(index) checkboxwidget = QCheckBox() if param in params: checkboxwidget.setChecked(True) else: checkboxwidget.setChecked(False) self.table.setCellWidget(index, 0, checkboxwidget) checkboxwidget.clicked.connect(self.plot) item = QLabel(param) self.table.setCellWidget(index, 1, item) self.plot() def plot(self) -> None: params = self.select() self.figure.clear() self.ax = self.figure.add_subplot(111) for param in params: for database in self.databaselist: if param == 'locx_s': segments = database.load_segments() if segments is not None: x = numpy.hstack( [database.get_index_value(index) for index in range(database.rodlength())] ) y = numpy.vstack( [get_coords(xvalue, segments) for xvalue in x] ) self.ax.plot( x, y[:, 0], '+', label='{0} - {1}'.format('locx_s', database.rodkey) ) elif param == 'locy_s': segments = database.load_segments() if segments is not None: x = numpy.hstack( [database.get_index_value(index) for index in range(database.rodlength())] ) y = numpy.vstack( [get_coords(xvalue, segments) for xvalue in x] ) self.ax.plot( x, y[:, 1], '+', label='{0} - {1}'.format('locy_s', database.rodkey) ) else: res = database.all_from_key(param) if res is not None: x, y = res self.ax.plot( x, y, '+', label='{0} - {1}'.format(param, database.rodkey) ) self.ax.legend() if self.log.isChecked(): self.ax.semilogy() self.canvas.draw() class PeakWidget(QWidget): def __init__(self, database: Optional[RodData], parent: Optional[QWidget]=None): super(PeakWidget, self).__init__(parent) self.database = database # create a QTableWidget self.table = QTableWidget(0, 3, self) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.itemChanged.connect(self.save) self.btn_add_row = QPushButton('+', self) self.btn_add_row.clicked.connect(self.add_row) # TODO this cause an issu by passing a boool instead of a nunpy array. self.buttonRemove = QPushButton('-', self) self.buttonRemove.clicked.connect(self.remove) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.btn_add_row) hbox.addWidget(self.buttonRemove) vbox.addLayout(hbox) vbox.addWidget(self.table) self.setLayout(vbox) def set_axis(self) -> None: if self.database is not None: self.axes = self.database.paxes() while self.table.rowCount() > 0: self.table.removeRow(0) segments = self.database.load_segments() if segments is not None: for index in range(segments.shape[0]): self.add_row(segments[index, :]) self.table.setHorizontalHeaderLabels( ['{0}'.format(self.database.axis), '{0}'.format(self.axes[0].label), '{0}'.format(self.axes[1].label)] ) def add_row(self, row: Optional[ndarray]=None) -> None: rowindex = self.table.rowCount() self.table.insertRow(rowindex) if row is not None: for index in range(3): newitem = QTableWidgetItem(str(row[index])) self.table.setItem(rowindex, index, newitem) def remove(self) -> None: self.table.removeRow(self.table.currentRow()) self.save() def axis_coords(self) -> ndarray: a = numpy.zeros((self.table.rowCount(), self.table.columnCount())) for rowindex in range(a.shape[0]): for columnindex in range(a.shape[1]): item = self.table.item(rowindex, columnindex) if item is not None: a[rowindex, columnindex] = float(item.text()) return a def save(self) -> None: if self.database is not None: self.database.save_segments(self.axis_coords()) def get_coords(self, x: int) -> Optional[ndarray]: return get_coords(x, self.axis_coords()) def get_coords(x: int, coords: ndarray) -> Optional[ndarray]: if coords.shape[0] == 0: return None if coords.shape[0] == 1: return coords[0, 1:] args = numpy.argsort(coords[:, 0]) x0 = coords[args, 0] x1 = coords[args, 1] x2 = coords[args, 2] if x < x0.min(): first = 0 last = 1 elif x > x0.max(): first = -2 last = -1 else: first = numpy.searchsorted(x0, x) - 1 last = numpy.searchsorted(x0, x) a1 = (x1[last] - x1[first]) / (x0[last] - x0[first]) b1 = x1[first] - a1 * x0[first] a2 = (x2[last] - x2[first]) / (x0[last] - x0[first]) b2 = x2[first] - a2 * x0[first] return numpy.array([a1 * x + b1, a2 * x + b2]) def interpolate(space: Space) -> MaskedArray: data = space.get_masked() mask = data.mask grid = numpy.vstack([numpy.ma.array(g, mask=mask).compressed() for g in space.get_grid()]).T open = numpy.vstack( [numpy.ma.array(g, mask=numpy.invert(mask)).compressed() for g in space.get_grid()] ).T if open.shape[0] == 0: return data.compressed() elif grid.shape[0] == 0: return data.compressed() else: interpolated = griddata(grid, data.compressed(), open) values = data.data.copy() values[mask] = interpolated mask = numpy.isnan(values) if mask.sum() > 0: data = numpy.ma.array(values, mask=mask) grid = numpy.vstack([numpy.ma.array(g, mask=mask).compressed() for g in space.get_grid()]).T open = numpy.vstack( [numpy.ma.array(g, mask=numpy.invert(mask)).compressed() for g in space.get_grid()] ).T interpolated = griddata( grid, data.compressed(), open, method='nearest') values[mask] = interpolated return values def find_unused_rodkey(rodkey: str, rods: List[str]) -> str: if rodkey not in rods: newkey = rodkey else: for index in itertools.count(0): newkey = '{0}_{1}'.format(rodkey, index) if newkey not in rods: break return newkey if __name__ == "__main__": app = QApplication(sys.argv) main = Window() main.resize(1000, 600) main.show() sys.exit(app.exec_()) binoculars-0.0.10/scripts/binoculars-gui000077500000000000000000001361671412510113200202750ustar00rootroot00000000000000#!/usr/bin/python3 from __future__ import unicode_literals import sys import os import json import signal import subprocess import socket import threading import numpy import matplotlib.figure import matplotlib.image from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT from matplotlib.pyplot import rcParams rcParams['image.cmap'] = 'jet' # from mpl_toolkits.mplot3d import Axes3D from PyQt5.Qt import (Qt) # noqa from PyQt5.QtCore import (QThread, pyqtSignal) from PyQt5.QtGui import (QPainter) from PyQt5.QtWidgets import (QAction, QApplication, QStyle, QSlider, QMenuBar, QTabWidget, QFileDialog, QStatusBar, QMessageBox, QRadioButton, QButtonGroup, QCheckBox, QPushButton, QHBoxLayout, QVBoxLayout, QSplitter, QTableWidgetItem, QTableWidget, QLabel, QLineEdit, QStyleOptionSlider, QMainWindow, QWidget) #python3 support PY3 = sys.version_info > (3,) if PY3: import socketserver import queue else: import SocketServer as socketserver import Queue as queue def set_src(): import os.path as osp dirpath = osp.join(osp.dirname(osp.abspath(__file__)), osp.pardir) sys.path.insert(0, osp.abspath(dirpath)) try: import binoculars.main import binoculars.space import binoculars.plot import binoculars.util except ImportError: # try to use code from src distribution set_src() import binoculars.main import binoculars.space import binoculars.plot import binoculars.util #RangeSlider is taken from https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html class RangeSlider(QSlider): """ A slider for ranges. This class provides a dual-slider for ranges, where there is a defined maximum and minimum, as is a normal slider, but instead of having a single slider value, there are 2 slider values. This class emits the same signals as the QSlider base class, with the exception of valueChanged """ def __init__(self, *args): super(RangeSlider, self).__init__(*args) self._low = self.minimum() self._high = self.maximum() self.pressed_control = QStyle.SC_None self.hover_control = QStyle.SC_None self.click_offset = 0 # 0 for the low, 1 for the high, -1 for both self.active_slider = 0 def low(self): return self._low def setLow(self, low): self._low = low self.update() def high(self): return self._high def setHigh(self, high): self._high = high self.update() def paintEvent(self, event): # based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp painter = QPainter(self) style = QApplication.style() for i, value in enumerate([self._low, self._high]): opt = QStyleOptionSlider() self.initStyleOption(opt) # Only draw the groove for the first slider so it doesn't get drawn # on top of the existing ones every time if i == 0: opt.subControls = QStyle.SC_SliderHandle # QStyle.SC_SliderGroove | QStyle.SC_SliderHandle else: opt.subControls = QStyle.SC_SliderHandle if self.tickPosition() != self.NoTicks: opt.subControls |= QStyle.SC_SliderTickmarks if self.pressed_control: opt.activeSubControls = self.pressed_control opt.state |= QStyle.State_Sunken else: opt.activeSubControls = self.hover_control opt.sliderPosition = value opt.sliderValue = value style.drawComplexControl(QStyle.CC_Slider, opt, painter, self) def mousePressEvent(self, event): event.accept() style = QApplication.style() button = event.button() # In a normal slider control, when the user clicks on a point in the # slider's total range, but not on the slider part of the control the # control would jump the slider value to where the user clicked. # For this control, clicks which are not direct hits will slide both # slider parts if button: opt = QStyleOptionSlider() self.initStyleOption(opt) self.active_slider = -1 for i, value in enumerate([self._low, self._high]): opt.sliderPosition = value hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(), self) if hit == style.SC_SliderHandle: self.active_slider = i self.pressed_control = hit self.triggerAction(self.SliderMove) self.setRepeatAction(self.SliderNoAction) self.setSliderDown(True) break if self.active_slider < 0: self.pressed_control = QStyle.SC_SliderHandle self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos())) self.triggerAction(self.SliderMove) self.setRepeatAction(self.SliderNoAction) else: event.ignore() def mouseReleaseEvent(self, _event): self.sliderReleased.emit() def mouseMoveEvent(self, event): if self.pressed_control != QStyle.SC_SliderHandle: event.ignore() return event.accept() new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos())) opt = QStyleOptionSlider() self.initStyleOption(opt) if self.active_slider < 0: offset = new_pos - self.click_offset self._high += offset self._low += offset if self._low < self.minimum(): diff = self.minimum() - self._low self._low += diff self._high += diff if self._high > self.maximum(): diff = self.maximum() - self._high self._low += diff self._high += diff elif self.active_slider == 0: if new_pos >= self._high: new_pos = self._high - 1 self._low = new_pos else: if new_pos <= self._low: new_pos = self._low + 1 self._high = new_pos self.click_offset = new_pos self.update() self.sliderMoved.emit(new_pos) def __pick(self, pt): if self.orientation() == Qt.Horizontal: return pt.x() else: return pt.y() def __pixelPosToRangeValue(self, pos): opt = QStyleOptionSlider() self.initStyleOption(opt) style = QApplication.style() gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self) sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self) if self.orientation() == Qt.Horizontal: slider_length = sr.width() slider_min = gr.x() slider_max = gr.right() - slider_length + 1 else: slider_length = sr.height() slider_min = gr.y() slider_max = gr.bottom() - slider_length + 1 return style.sliderValueFromPosition(self.minimum(), self.maximum(), pos-slider_min, slider_max-slider_min, opt.upsideDown) class Window(QMainWindow): def __init__(self, parent=None): super(Window, self).__init__(parent) newproject = QAction("New project", self) newproject.triggered.connect(self.newproject) loadproject = QAction("Open project", self) loadproject.triggered.connect(self.loadproject) saveproject = QAction("Save project", self) saveproject.triggered.connect(self.saveproject) addspace = QAction("Import space", self) addspace.triggered.connect(self.add_to_project) savespace = QAction("Export space", self) savespace.triggered.connect(self.exportspace) menu_bar = QMenuBar() f = menu_bar.addMenu("&File") f.addAction(newproject) f.addAction(loadproject) f.addAction(saveproject) f.addAction(addspace) f.addAction(savespace) merge = QAction("Merge", self) merge.triggered.connect(self.merge) subtract = QAction("Subtract", self) subtract.triggered.connect(self.subtract) edit = menu_bar.addMenu("&Edit") edit.addAction(merge) edit.addAction(subtract) start_server = QAction("Start server queue", self) start_server.triggered.connect(lambda: self.open_server(startq=True)) stop_server = QAction("Stop server queue", self) stop_server.triggered.connect(self.kill_server) recieve = QAction("Open for spaces", self) recieve.triggered.connect(lambda: self.open_server(startq=False)) serve = menu_bar.addMenu("&Serve") serve.addAction(start_server) serve.addAction(stop_server) serve.addAction(recieve) self.tab_widget = QTabWidget(self) self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.tab_widget.removeTab) self.statusbar = QStatusBar() self.setCentralWidget(self.tab_widget) self.setMenuBar(menu_bar) self.setStatusBar(self.statusbar) self.threads = [] self.pro = None def closeEvent(self, event): self.kill_subprocess() super(Window, self).closeEvent(event) def newproject(self): widget = ProjectWidget([], parent=self) self.tab_widget.addTab(widget, 'New Project') self.tab_widget.setCurrentWidget(widget) def loadproject(self, filename=None): if not filename: dialog = QFileDialog(self, "Load project") dialog.setNameFilters(['binoculars project file (*.proj)']) dialog.setFileMode(QFileDialog.ExistingFiles) dialog.setAcceptMode(QFileDialog.AcceptOpen) if not dialog.exec_(): return fname = dialog.selectedFiles() if not fname: return for name in fname: try: widget = ProjectWidget.fromfile(str(name), parent=self) self.tab_widget.addTab(widget, short_filename(str(name))) self.tab_widget.setCurrentWidget(widget) except Exception as e: QMessageBox.critical(self, 'Load project', 'Unable to load project from {}: {}'.format(fname, e)) else: widget = ProjectWidget.fromfile(filename, parent=self) self.tab_widget.addTab(widget, short_filename(filename)) def saveproject(self): widget = self.tab_widget.currentWidget() dialog = QFileDialog(self, "Save project") dialog.setNameFilters(['binoculars project file (*.proj)']) dialog.setDefaultSuffix('proj') dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: index = self.tab_widget.currentIndex() self.tab_widget.setTabText(index, short_filename(fname)) widget.tofile(fname) except Exception as e: QMessageBox.critical(self, 'Save project', 'Unable to save project to {}: {}'.format(fname, e)) def add_to_project(self): if self.tab_widget.count() == 0: self.newproject() dialog = QFileDialog(self, "Import spaces") dialog.setNameFilters(['binoculars space file (*.hdf5)']) dialog.setFileMode(QFileDialog.ExistingFiles) dialog.setAcceptMode(QFileDialog.AcceptOpen) if not dialog.exec_(): return fname = dialog.selectedFiles() if not fname: return for name in fname: try: widget = self.tab_widget.currentWidget() widget.addspace(str(name), True) except Exception as _e: raise #QMessageBox.critical(self, 'Import spaces', 'Unable to import space {}: {}'.format(str(name), e)) def exportspace(self): widget = self.tab_widget.currentWidget() dialog = QFileDialog(self, "save mesh") dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: _index = self.tab_widget.currentIndex() widget.space_to_file(str(fname)) except Exception as e: QMessageBox.critical(self, 'export fitdata', 'Unable to save mesh to {}: {}'.format(fname, e)) def merge(self): widget = self.tab_widget.currentWidget() dialog = QFileDialog(self, "save mesh") dialog.setNameFilters(['binoculars space file (*.hdf5)']) dialog.setDefaultSuffix('hdf5') dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: _index = self.tab_widget.currentIndex() widget.merge(str(fname)) except Exception as e: QMessageBox.critical(self, 'merge', 'Unable to save mesh to {}: {}'.format(fname, e)) def subtract(self): dialog = QFileDialog(self, "subtract space") dialog.setNameFilters(['binoculars space file (*.hdf5)']) dialog.setFileMode(QFileDialog.ExistingFiles) dialog.setAcceptMode(QFileDialog.AcceptOpen) if not dialog.exec_(): return fname = dialog.selectedFiles() if not fname: return for name in fname: try: widget = self.tab_widget.currentWidget() widget.subtractspace(str(name)) except Exception as e: QMessageBox.critical(self, 'Import spaces', 'Unable to import space {}: {}'.format(fname, e)) def open_server(self, startq=True): if len(self.threads) != 0: print('Server already running') else: HOST, PORT = socket.gethostbyname(socket.gethostname()), 0 self.q = queue.Queue() server = ThreadedTCPServer((HOST, PORT), SpaceTCPHandler) server.q = self.q self.ip, self.port = server.server_address if startq: cmd = ['python', os.path.join(os.path.dirname(__file__), 'binoculars-server.py'), str(self.ip), str(self.port)] self.pro = subprocess.Popen(cmd, stdin=None, stdout=None, stderr=None, preexec_fn=os.setsid) server_thread = threading.Thread(target=server.serve_forever) server_thread.daemon = True server_thread.start() updater = UpdateThread() updater.data_found.connect(self.update) updater.q = self.q self.threads.append(updater) updater.start() if not startq: print(('GUI server started running at ip {0} and port {1}.'.format(self.ip, self.port))) def kill_server(self): if len(self.threads) == 0: print('No server running.') else: self.threads = [] self.kill_subprocess() self.pro = None def kill_subprocess(self): if not self.pro == None: os.killpg(self.pro.pid, signal.SIGTERM) def update(self): names = [] for tab in range(self.tab_widget.count()): names.append(self.tab_widget.tabText(tab)) if 'server' not in names: widget = ProjectWidget([], parent=self) self.tab_widget.addTab(widget, 'server') names.append('server') index = names.index('server') serverwidget = self.tab_widget.widget(index) while not self.threads[0].fq.empty(): command, space = self.threads[0].fq.get() serverwidget.table.addfromserver(command, space) serverwidget.table.select() if serverwidget.auto_update.isChecked(): serverwidget.limitwidget.refresh() class UpdateThread(QThread): fq = queue.Queue() data_found = pyqtSignal(object) def run(self): delay = binoculars.util.loop_delayer(1) jobs = [] labels = [] while 1: if not self.q.empty(): command, space = self.q.get() if command in labels: jobs[labels.index(command)].append(space) else: jobs.append([space]) labels.append(command) elif self.q.empty() and len(jobs) > 0: self.fq.put((labels.pop(), binoculars.space.sum(jobs.pop()))) self.data_found.emit('data found') else: next(delay) class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass class SpaceTCPHandler(socketserver.BaseRequestHandler): def handle(self): command, config, metadata, axes, photons, contributions = binoculars.util.socket_recieve(self) space = binoculars.space.Space(binoculars.space.Axes.fromarray(axes)) space.config = binoculars.util.ConfigFile.fromserial(config) space.config.command = command space.config.origin = 'server' space.metadata = binoculars.util.MetaData.fromserial(metadata) space.photons = photons space.contributions = contributions self.server.q.put((command, space)) class HiddenToolbar(NavigationToolbar2QT): def __init__(self, show_coords, update_sliders, canvas): NavigationToolbar2QT.__init__(self, canvas, None) self.show_coords = show_coords self.update_sliders = update_sliders self.zoom() self.threed = False def mouse_move(self, event): if not self.threed: self.show_coords(event) def press_zoom(self, event): super(HiddenToolbar, self).press_zoom(event) if not self.threed: self.inaxes = event.inaxes def release_zoom(self, event): super(HiddenToolbar, self).release_zoom(event) if not self.threed: self.update_sliders(self.inaxes) class ProjectWidget(QWidget): def __init__(self, filelist, key=None, projection=None, parent=None): super(ProjectWidget, self).__init__(parent) self.parent = parent self.figure = matplotlib.figure.Figure() self.canvas = FigureCanvasQTAgg(self.figure) self.toolbar = HiddenToolbar(self.show_coords, self.update_sliders, self.canvas) self.lin = QRadioButton('lin', self) self.lin.setChecked(False) self.lin.toggled.connect(self.plot) self.log = QRadioButton('log', self) self.log.setChecked(True) self.log.toggled.connect(self.plot) self.loglog = QRadioButton('loglog', self) self.loglog.setChecked(False) self.loglog.toggled.connect(self.plot) self.loggroup = QButtonGroup(self) self.loggroup.addButton(self.lin) self.loggroup.addButton(self.log) self.loggroup.addButton(self.loglog) self.swap_axes = QCheckBox('ax', self) self.swap_axes.setChecked(False) self.swap_axes.stateChanged.connect(self.plot) self.samerange = QCheckBox('same', self) self.samerange.setChecked(False) self.samerange.stateChanged.connect(self.update_colorbar) self.legend = QCheckBox('legend', self) self.legend.setChecked(True) self.legend.stateChanged.connect(self.plot) self.threed = QCheckBox('3d', self) self.threed.setChecked(False) self.threed.stateChanged.connect(self.plot) self.auto_update = QCheckBox('auto', self) self.auto_update.setChecked(True) self.datarange = RangeSlider(Qt.Horizontal) self.datarange.setMinimum(0) self.datarange.setMaximum(250) self.datarange.setLow(0) self.datarange.setHigh(self.datarange.maximum()) self.datarange.setTickPosition(QSlider.TicksBelow) self.datarange.sliderMoved.connect(self.update_colorbar) self.table = TableWidget(filelist) self.table.selectionError.connect(self.selectionerror) self.table.plotaxesChanged.connect(self.plotaxes_changed) self.key = key self.projection = projection self.button_save = QPushButton('save image') self.button_save.clicked.connect(self.save) self.button_refresh = QPushButton('refresh') self.button_refresh.clicked.connect(self.table.select) self.limitwidget = LimitWidget(self.table.plotaxes) self.limitwidget.keydict.connect(self.update_key) self.limitwidget.rangechange.connect(self.update_figure_range) self.initUI() self.table.select() def initUI(self): self.control_widget = QWidget(self) hbox = QHBoxLayout() left = QVBoxLayout() pushbox = QHBoxLayout() pushbox.addWidget(self.button_save) pushbox.addWidget(self.button_refresh) left.addLayout(pushbox) radiobox = QHBoxLayout() self.group = QButtonGroup(self) for label in ['stack', 'grid']: rb = QRadioButton(label, self.control_widget) rb.setChecked(True) self.group.addButton(rb) radiobox.addWidget(rb) radiobox.addWidget(self.lin) radiobox.addWidget(self.log) radiobox.addWidget(self.loglog) datarangebox = QHBoxLayout() datarangebox.addWidget(self.samerange) datarangebox.addWidget(self.legend) datarangebox.addWidget(self.threed) datarangebox.addWidget(self.swap_axes) datarangebox.addWidget(self.auto_update) left.addLayout(radiobox) left.addLayout(datarangebox) left.addWidget(self.datarange) left.addWidget(self.table) left.addWidget(self.limitwidget) self.control_widget.setLayout(left) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.control_widget) splitter.addWidget(self.canvas) hbox.addWidget(splitter) self.setLayout(hbox) def show_coords(self, event): plotaxes = event.inaxes if hasattr(plotaxes, 'space'): if plotaxes.space.dimension == 2: labels = numpy.array([plotaxes.get_xlabel(), plotaxes.get_ylabel()]) order = [plotaxes.space.axes.index(label) for label in labels] labels = labels[order] coords = numpy.array([event.xdata, event.ydata])[order] try: rounded_coords = [ax[ax.get_index(coord)] for ax, coord in zip(plotaxes.space.axes, coords)] intensity = '{0:.2e}'.format(plotaxes.space[list(coords)]) self.parent.statusbar.showMessage('{0} = {1}, {2} = {3}, Intensity = {4}'.format(labels[0], rounded_coords[0], labels[1], rounded_coords[1], intensity)) except ValueError: self.parent.statusbar.showMessage('out of range') elif plotaxes.space.dimension == 1: xaxis = plotaxes.space.axes[plotaxes.space.axes.index(plotaxes.get_xlabel())] if event.xdata in xaxis: xcoord = xaxis[xaxis.get_index(event.xdata)] intensity = '{0:.2e}'.format(event.ydata) self.parent.statusbar.showMessage('{0} = {1}, Intensity = {2}'.format(xaxis.label, xcoord, intensity)) def update_sliders(self, plotaxes): if not plotaxes == None: if hasattr(plotaxes, 'space'): space = plotaxes.space if space.dimension == 2: labels = numpy.array([plotaxes.get_xlabel(), plotaxes.get_ylabel()]) limits = list(lim for lim in [plotaxes.get_xlim(), plotaxes.get_ylim()]) elif space.dimension == 1: labels = [plotaxes.get_xlabel()] limits = [plotaxes.get_xlim()] keydict = dict() for key, value in zip(labels, limits): keydict[key] = value self.limitwidget.update_from_zoom(keydict) def selectionerror(self, message): self.limitwidget.setDisabled(True) self.errormessage(message) def plotaxes_changed(self, plotaxes): self.limitwidget.setEnabled(True) self.limitwidget.axes_update(plotaxes) def update_key(self, input): self.key = input['key'] self.projection = input['project'] if len(self.limitwidget.sliders) - len(self.projection) == 1: self.datarange.setDisabled(True) self.samerange.setDisabled(True) self.swap_axes.setDisabled(True) self.loglog.setEnabled(True) elif len(self.limitwidget.sliders) - len(self.projection) == 2: self.loglog.setDisabled(True) self.datarange.setEnabled(True) self.samerange.setEnabled(True) self.swap_axes.setEnabled(True) self.plot() def get_norm(self, mi, ma): log = self.log.isChecked() rangemin = self.datarange.low() * 1.0 / self.datarange.maximum() rangemax = self.datarange.high() * 1.0 / self.datarange.maximum() if log: power = 3 vmin = mi + (ma - mi) * rangemin ** power vmax = mi + (ma - mi) * rangemax ** power else: vmin = mi + (ma - mi) * rangemin vmax = mi + (ma - mi) * rangemax if log: return matplotlib.colors.LogNorm(vmin, vmax) else: return matplotlib.colors.Normalize(vmin, vmax) def get_normlist(self): _log = self.log.isChecked() same = self.samerange.checkState() if same: return [self.get_norm(min(self.datamin), max(self.datamax))] * len(self.datamin) else: norm = [] for i in range(len(self.datamin)): norm.append(self.get_norm(self.datamin[i], self.datamax[i])) return norm def plot(self): if len(self.table.plotaxes) == 0: return self.figure.clear() self.parent.statusbar.clearMessage() self.figure_images = [] log = self.log.isChecked() loglog = self.loglog.isChecked() plotcount = len(self.table.selection) plotcolumns = int(numpy.ceil(numpy.sqrt(plotcount))) plotrows = int(numpy.ceil(float(plotcount) / plotcolumns)) plotoption = None if self.group.checkedButton(): plotoption = self.group.checkedButton().text() spaces = [] for i, filename in enumerate(self.table.selection): axes = self.table.getax(filename) rkey = axes.restricted_key(self.key) if rkey == None: space = self.table.getspace(filename) else: try: space = self.table.getspace(filename, rkey) except KeyError: return projection = [ax for ax in self.projection if ax in space.axes] if projection: space = space.project(*projection) dimension = space.dimension if dimension == 0: self.errormessage('Choose suitable number of projections') if dimension == 3 and not self.threed.isChecked(): self.errormessage('Switch on 3D plotting, only works with small spaces') spaces.append(space) self.datamin = [] self.datamax = [] for space in spaces: data = space.get_masked().compressed() if log or loglog: data = data[data > 0] if len(data) > 0: self.datamin.append(data.min()) self.datamax.append(data.max()) else: # min, max when there is no data to plot self.datamin.append(numpy.pi) self.datamax.append(numpy.pi) norm = self.get_normlist() if dimension == 1 or dimension == 2: self.toolbar.threed = False else: self.toolbar.threed = True for i, space in enumerate(spaces): filename = self.table.selection[i] basename = os.path.splitext(os.path.basename(filename))[0] if plotcount > 1: if dimension == 1 and (plotoption == 'stack' or plotoption == None): self.ax = self.figure.add_subplot(111) if dimension == 2 and plotoption != 'grid': sys.stderr.write('warning: stack display not supported for multi-file-plotting, falling back to grid\n') plotoption = 'grid' elif dimension > 3: sys.stderr.write('error: cannot display 4 or higher dimensional data, use --project or --slice to decrease dimensionality\n') sys.exit(1) else: self.ax = self.figure.add_subplot(111) if plotoption == 'grid': if dimension == 1 or dimension == 2: self.ax = self.figure.add_subplot(plotrows, plotcolumns, i+1) elif self.threed.isChecked(): self.ax = self.figure.gca(projection='3d') self.ax.set_title(basename) if dimension == 2 and self.swap_axes.checkState(): space = space.reorder(list(ax.label for ax in space.axes)[::-1]) self.ax.space = space im = binoculars.plot.plot(space, self.figure, self.ax, log=log, loglog=loglog, label=basename, norm=norm[i]) self.figure_images.append(im) if dimension == 1 and self.legend.checkState(): self.ax.legend() self.update_figure_range(self.key_to_str(self.key)) self.canvas.draw() def merge(self, filename): try: spaces = tuple(self.table.getspace(selected_filename) for selected_filename in self.table.selection) newspace = binoculars.space.sum(binoculars.space.make_compatible(spaces)) newspace.tofile(filename) list(map(self.table.remove, self.table.selection)) self.table.addspace(filename, True) except Exception as e: QMessageBox.critical(self, 'Merge', 'Unable to merge the meshes. {}'.format(e)) def subtractspace(self, filename): try: subtractspace = binoculars.space.Space.fromfile(filename) spaces = tuple(self.table.getspace(selected_filename) for selected_filename in self.table.selection) newspaces = tuple(space - subtractspace for space in spaces) for space, selected_filename in zip(newspaces, self.table.selection): newfilename = binoculars.util.find_unused_filename(selected_filename) space.tofile(newfilename) self.table.remove(selected_filename) self.table.addspace(newfilename, True) except Exception as e: QMessageBox.critical(self, 'Subtract', 'Unable to subtract the meshes. {}'.format(e)) def errormessage(self, message): self.figure.clear() self.canvas.draw() self.parent.statusbar.showMessage(message) def update_figure_range(self, key): if len(key) == 0: return for ax in self.figure.axes: plotaxes = self.table.plotaxes xlabel, ylabel = ax.get_xlabel(), ax.get_ylabel() if xlabel in plotaxes: xindex = plotaxes.index(xlabel) ax.set_xlim(key[xindex][0], key[xindex][1]) if ylabel in plotaxes: yindex = plotaxes.index(ylabel) ax.set_ylim(key[yindex][0], key[yindex][1]) self.canvas.draw() def update_colorbar(self, value): normlist = self.get_normlist() for im, norm in zip(self.figure_images, normlist): im.set_norm(norm) self.canvas.draw() @staticmethod def key_to_str(key): return list([s.start, s.stop] for s in key) @staticmethod def str_to_key(s): return tuple(slice(float(key[0]), float(key[1])) for key in s) def tofile(self, filename=None): dict = {} dict['filelist'] = self.table.filelist dict['key'] = self.key_to_str(self.key) dict['projection'] = self.projection if filename == None: filename = str(QFileDialog.getSaveFileName(self, 'Save Project', '.')) with open(filename, 'w') as fp: json.dump(dict, fp) @classmethod def fromfile(cls, filename=None, parent=None): if filename == None: filename = str(QFileDialog.getOpenFileName(cls, 'Open Project', '.', '*.proj')) try: with open(filename, 'r') as fp: dict = json.load(fp) except IOError as e: raise cls.error.showMessage("unable to open '{0}' as project file (original error: {1!r})".format(filename, e)) newlist = [] for fn in dict['filelist']: if not os.path.exists(fn): warningbox = QMessageBox(2, 'Warning', 'Cannot find space at path {0}; locate proper space'.format(fn), buttons=QMessageBox.Open) warningbox.exec_() newname = str(QFileDialog.getOpenFileName(caption='Open space {0}'.format(fn), directory='.', filter='*.hdf5')) newlist.append(newname) else: newlist.append(fn) widget = cls(newlist, cls.str_to_key(dict['key']), dict['projection'], parent=parent) return widget def addspace(self, filename=None, add=False): if filename == None: filename = str(QFileDialog.getOpenFileName(self, 'Open Project', '.', '*.hdf5')) self.table.add_space(filename, add) def save(self): dialog = QFileDialog(self, "Save image") dialog.setNameFilters(['Portable Network Graphics (*.png)', 'Portable Document Format (*.pdf)']) dialog.setDefaultSuffix('png') dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) if not dialog.exec_(): return fname = dialog.selectedFiles()[0] if not fname: return try: self.figure.savefig(str(fname)) except Exception as e: QMessageBox.critical(self, 'Save image', 'Unable to save image to {}: {}'.format(fname, e)) def space_to_file(self, fname): ext = os.path.splitext(fname)[-1] for i, filename in enumerate(self.table.selection): axes = self.table.getax(filename) space = self.table.getspace(filename, key=axes.restricted_key(self.key)) projection = [ax for ax in self.projection if ax in space.axes] if projection: space = space.project(*projection) space.trim() outfile = binoculars.util.find_unused_filename(fname) if ext == '.edf': binoculars.util.space_to_edf(space, outfile) self.parent.statusbar.showMessage('saved at {0}'.format(outfile)) elif ext == '.txt': binoculars.util.space_to_txt(space, outfile) self.parent.statusbar.showMessage('saved at {0}'.format(outfile)) elif ext == '.hdf5': space.tofile(outfile) self.parent.statusbar.showMessage('saved at {0}'.format(outfile)) else: self.parent.statusbar.showMessage('unknown extension {0}, unable to save!\n'.format(ext)) def short_filename(filename): return filename.split('/')[-1].split('.')[0] class SpaceContainer(QTableWidgetItem): def __init__(self, label, space=None): super(SpaceContainer, self).__init__(short_filename(label)) self.label = label self.space = space def get_space(self, key=None): if self.space == None: return binoculars.space.Space.fromfile(self.label, key=key) else: if key == None: key = Ellipsis return self.space[key] def get_ax(self): if self.space == None: return binoculars.space.Axes.fromfile(self.label) else: return self.space.axes def add_to_space(self, space): if self.space is None: newspace = binoculars.space.Space.fromfile(self.label) + space newspace.tofile(self.label) else: self.space += space class TableWidget(QWidget): selectionError = pyqtSignal(str, name = 'Selection Error') plotaxesChanged = pyqtSignal(binoculars.space.Axes, name = 'plot axes changed') def __init__(self, filelist = [],parent=None): super(TableWidget, self).__init__(parent) hbox = QHBoxLayout() self.table = QTableWidget(0, 4) self.table.setHorizontalHeaderLabels(['', 'filename','labels', 'remove']) for index, width in enumerate([25,150,50,70]): self.table.setColumnWidth(index, width) for filename in filelist: self.add_space(filename) hbox.addWidget(self.table) self.setLayout(hbox) def add_space(self, filename, add = True, space = None): index = self.table.rowCount() self.table.insertRow(index) checkboxwidget = QCheckBox() checkboxwidget.setChecked(add) checkboxwidget.clicked.connect(self.select) self.table.setCellWidget(index,0, checkboxwidget) container = SpaceContainer(filename, space) self.table.setItem(index, 1, container) item = QTableWidgetItem(','.join(list(ax.label.lower() for ax in container.get_ax()))) self.table.setItem(index, 2, item) buttonwidget = QPushButton('remove') buttonwidget.clicked.connect(lambda: self.remove(filename)) self.table.setCellWidget(index,3, buttonwidget) if add: self.select() def addfromserver(self, command, space): if not command in self.filelist: self.add_space(command, add = False, space = space) else: container = self.table.item(self.filelist.index(command), 1) container.add_to_space(space) def remove(self, filename): self.table.removeRow(self.filelist.index(filename)) self.select() print(('removed: {0}'.format(filename))) def select(self): axes = self.plotaxes if len(axes) > 0: self.plotaxesChanged.emit(axes) else: self.selectionError.emit('no spaces selected or spaces with non identical labels selected') @property def selection(self): return list(container.label for checkbox, container in zip(self.itercheckbox(), self.itercontainer()) if checkbox.checkState()) @property def plotaxes(self): axes = tuple(container.get_ax() for checkbox, container in zip(self.itercheckbox(), self.itercontainer()) if checkbox.checkState()) if len(axes) > 0: try: return binoculars.space.Axes(binoculars.space.union_unequal_axes(ax) for ax in zip(*axes)) except ValueError: return () else: return () @property def filelist(self): return list(container.label for container in self.itercontainer()) def getax(self, filename): index = self.filelist.index(filename) return self.table.item(index, 1).get_ax() def getspace(self, filename, key = None): index = self.filelist.index(filename) return self.table.item(index, 1).get_space(key) def itercheckbox(self): return iter(self.table.cellWidget(index, 0) for index in range(self.table.rowCount())) def itercontainer(self): return iter(self.table.item(index, 1) for index in range(self.table.rowCount())) class LimitWidget(QWidget): keydict = pyqtSignal(dict, name = "keydict") rangechange = pyqtSignal(list, name = "rangechange") def __init__(self, axes, parent=None): super(LimitWidget, self).__init__(parent) self.initUI(axes) def initUI(self, axes): self.axes = axes self.sliders = list() self.qlabels = list() self.leftindicator = list() self.rightindicator = list() labels = list(ax.label for ax in axes) vbox = QVBoxLayout() hbox = QHBoxLayout() self.projectionlabel = QLabel(self) self.projectionlabel.setText('projection along axis') self.refreshbutton = QPushButton('all') self.refreshbutton.clicked.connect(self.refresh) vbox.addWidget(self.projectionlabel) self.checkbox = list() self.state = list() for label in labels: self.checkbox.append(QCheckBox(label, self)) for box in self.checkbox: self.state.append(box.checkState()) hbox.addWidget(box) box.stateChanged.connect(self.update_checkbox) self.state = numpy.array(self.state, dtype = numpy.bool) self.init_checkbox() vbox.addLayout(hbox) for label in labels: self.qlabels.append(QLabel(self)) self.leftindicator.append(QLineEdit(self)) self.rightindicator.append(QLineEdit(self)) self.sliders.append(RangeSlider(Qt.Horizontal)) for index, label in enumerate(labels): box = QHBoxLayout() box.addWidget(self.qlabels[index]) box.addWidget(self.leftindicator[index]) box.addWidget(self.sliders[index]) box.addWidget(self.rightindicator[index]) vbox.addLayout(box) for left in self.leftindicator: left.setMaximumWidth(50) for right in self.rightindicator: right.setMaximumWidth(50) for index, label in enumerate(labels): self.qlabels[index].setText(label) for index, ax in enumerate(axes): self.sliders[index].setMinimum(0) self.sliders[index].setMaximum(len(ax) - 1) self.sliders[index].setLow(0) self.sliders[index].setHigh(len(ax) - 1) self.sliders[index].setTickPosition(QSlider.TicksBelow) self.update_lines() for slider in self.sliders: slider.sliderMoved.connect(self.update_lines) for slider in self.sliders: slider.sliderReleased.connect(self.send_signal) for line in self.leftindicator: line.editingFinished.connect(self.update_sliders_left) line.editingFinished.connect(self.send_signal) for line in self.rightindicator: line.editingFinished.connect(self.update_sliders_right) line.editingFinished.connect(self.send_signal) vbox.addWidget(self.refreshbutton) if self.layout() == None: self.setLayout(vbox) def refresh(self): for slider in self.sliders: slider.setLow(slider.minimum()) slider.setHigh(slider.maximum()) self.update_lines() self.send_signal() def update_lines(self, value=0): for index, slider in enumerate(self.sliders): self.leftindicator[index].setText(str(self.axes[index][slider.low()])) self.rightindicator[index].setText(str(self.axes[index][slider.high()])) key = list((float(str(left.text())), float(str(right.text()))) for left, right in zip(self.leftindicator, self.rightindicator)) self.rangechange.emit(key) def send_signal(self): signal = {} key = ((float(str(left.text())), float(str(right.text()))) for left, right in zip(self.leftindicator, self.rightindicator)) key = [left if left == right else slice(left, right, None) for left, right in key] project = [] for ax, state in zip(self.axes, self.state): if state: project.append(ax.label) signal['project'] = project signal['key'] = key self.keydict.emit(signal) def update_sliders_left(self): for ax, left, right , slider in zip(self.axes, self.leftindicator, self.rightindicator, self.sliders): try: leftvalue = ax.get_index(float(str(left.text()))) rightvalue = ax.get_index(float(str(right.text()))) if leftvalue >= slider.minimum() and leftvalue < rightvalue: slider.setLow(leftvalue) else: slider.setLow(rightvalue - 1) except ValueError: slider.setLow(0) left.setText(str(ax[slider.low()])) def update_sliders_right(self): for ax, left, right, slider in zip(self.axes, self.leftindicator, self.rightindicator, self.sliders): leftvalue = ax.get_index(float(str(left.text()))) try: rightvalue = ax.get_index(float(str(right.text()))) if rightvalue <= slider.maximum() and rightvalue > leftvalue: slider.setHigh(rightvalue) else: slider.setHigh(leftvalue + 1) except ValueError: slider.setHigh(len(ax) - 1) right.setText(str(ax[slider.high()])) def update_checkbox(self): self.state = list() for box in self.checkbox: self.state.append(box.checkState()) self.send_signal() def init_checkbox(self): while numpy.alen(self.state) - self.state.sum() > 2: _index = numpy.where(self.state == False)[-1] self.state[-1] = True for box, state in zip(self.checkbox, self.state): box.setChecked(state) def axes_update(self, axes): if not set(ax.label for ax in self.axes) == set(ax.label for ax in axes): QWidget().setLayout(self.layout()) self.initUI(axes) self.send_signal() else: low = tuple(self.axes[index][slider.low()] for index, slider in enumerate(self.sliders)) high = tuple(self.axes[index][slider.high()] for index, slider in enumerate(self.sliders)) for index, ax in enumerate(axes): self.sliders[index].setMinimum(0) self.sliders[index].setMaximum(len(ax) - 1) self.axes = axes for index, slider in enumerate(self.sliders): self.leftindicator[index].setText(str(low[index])) self.rightindicator[index].setText(str(high[index])) self.update_sliders_left() self.update_sliders_right() self.send_signal() def update_from_zoom(self, keydict): for key in keydict: index = self.axes.index(key) self.leftindicator[index].setText(str(keydict[key][0])) self.rightindicator[index].setText(str(keydict[key][1])) self.update_sliders_left() self.update_sliders_right() self.send_signal() def is_empty(key): for k in key: if isinstance(k, slice): if k.start == k.stop: return True return False if __name__ == '__main__': app = QApplication(sys.argv) binoculars.space.silence_numpy_errors() main = Window() main.resize(1000, 600) main.newproject() main.show() sys.exit(app.exec_()) binoculars-0.0.10/scripts/binoculars-processgui000077500000000000000000000404501412510113200216610ustar00rootroot00000000000000#!/usr/bin/env python """ binoculars gui for data processing Created on 2015-06-04 author: Remy Nencib (remy.nencib@esrf.r) """ import sys import os import time from PyQt5.Qt import (Qt) # noqa from PyQt5.QtGui import (QColor, QPalette) from PyQt5.QtWidgets import (QAction, QApplication, QTabWidget, QFileDialog, QMessageBox, QPushButton, QHBoxLayout, QVBoxLayout, QSplitter, QTableWidgetItem, QTableWidget, QLabel, QLineEdit, QMainWindow, QWidget, QComboBox, QProgressDialog, QDockWidget) def set_src(): import os.path as osp dirpath = osp.join(osp.dirname(osp.abspath(__file__)), osp.pardir) sys.path.insert(0, osp.abspath(dirpath)) try: import binoculars.main import binoculars.util except ImportError: # try to use code from src distribution set_src() import binoculars.main import binoculars.util #--------------------------------------------CREATE MAIN WINDOW---------------------------------------- class Window(QMainWindow): def __init__(self): super(Window, self).__init__() self.initUI() self.tab_widget = QTabWidget(self) self.setCentralWidget(self.tab_widget) # add the close button for tabs self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.close_tab) #method for close tabs def close_tab(self, tab): self.tab_widget.removeTab(tab) def initUI(self): #we create the menu bar openfile = QAction('Open', self) openfile.setShortcut('Ctrl+O') openfile.setStatusTip('Open new File') openfile.triggered.connect(self.ShowFile) savefile = QAction('Save', self) savefile.setShortcut('Ctrl+S') savefile.setStatusTip('Save File') savefile.triggered.connect(self.Save) create = QAction('Create', self) create.setStatusTip('Create Configfile') create.triggered.connect(self.New_Config) menubar = self.menuBar() filemenu = menubar.addMenu('&File') filemenu.addAction(openfile) filemenu.addAction(savefile) filemenu = menubar.addMenu('&New Configfile') filemenu.addAction(create) #we configue the main windows palette = QPalette() palette.setColor(QPalette.Background, Qt.gray) self.setPalette(palette) self.setGeometry(50, 100, 700, 700) self.setWindowTitle('Binoculars processgui') self.show() self.ListCommand = QTableWidget(1, 2, self) self.ListCommand.verticalHeader().setVisible(True) self.ListCommand.horizontalHeader().setVisible(False) self.ListCommand.horizontalHeader().stretchSectionCount() self.ListCommand.setColumnWidth(0, 80) self.ListCommand.setColumnWidth(1, 80) self.ListCommand.setRowCount(0) self.buttonDelete = QPushButton('Delete', self) self.buttonDelete.clicked.connect(self.removeConf) self.process = QPushButton('run', self) self.process.setStyleSheet("background-color: darkred") self.process.clicked.connect(self.run) self.wid = QWidget() self.CommandLayout = QVBoxLayout() self.CommandLayout.addWidget(self.ListCommand) self.CommandLayout.addWidget(self.process) self.CommandLayout.addWidget(self.buttonDelete) self.wid.setLayout(self.CommandLayout) self.Dock = QDockWidget() self.Dock.setAllowedAreas(Qt.LeftDockWidgetArea) self.Dock.setFeatures(QDockWidget.NoDockWidgetFeatures) self.Dock.setWidget(self.wid) self.Dock.setMaximumWidth(200) self.Dock.setMinimumWidth(200) self.addDockWidget(Qt.DockWidgetArea(1), self.Dock) def removeConf(self): self.ListCommand.removeRow(self.ListCommand.currentRow()) def Add_To_Liste(self, xxx_todo_changeme): (command, cfg) = xxx_todo_changeme row = self.ListCommand.rowCount() index = self.tab_widget.currentIndex() filename = self.tab_widget.tabText(index) self.ListCommand.insertRow(self.ListCommand.rowCount()) dic = {filename: cfg} self.item1 = QTableWidgetItem(str(command)) self.item1.command = command self.item2 = QTableWidgetItem(str(filename)) self.item2.cfg = dic[filename] self.ListCommand.setItem(row, 0, self.item1) self.ListCommand.setItem(row, 1, self.item2) #We run the script and create a hdf5 file def run(self): maximum = self.ListCommand.rowCount() pd = QProgressDialog('running', 'Cancel', 0, maximum, self) pd.setWindowModality(Qt.WindowModal) pd.show() def progress(cfg, command): if pd.wasCanceled(): raise KeyboardInterrupt QApplication.processEvents() return binoculars.main.Main.from_object(cfg, command) try: for index in range(self.ListCommand.rowCount()): pd.setValue(index) cfg = self.ListCommand.item(index, 1).cfg command = self.ListCommand.item(index, 0).command print(cfg) progress(cfg, command) self.ListCommand.clear() self.ListCommand.setRowCount(0) except BaseException as e: #cfg = self.ListCommand.item(index,1).cfg #print cfg QMessageBox.about(self, "Error", "There was an error processing one of the scans: {0}".format(e)) finally: pd.close() #we call the load function def ShowFile(self): filename = QFileDialog.getOpenFileName(self, 'Open File', '') confwidget = Conf_Tab(self) confwidget.read_data(str(filename)) newIndex = self.tab_widget.addTab(confwidget, os.path.basename(str(filename))) confwidget.command.connect(self.Add_To_Liste) self.tab_widget.setCurrentIndex(newIndex) #we call the save function def Save(self): filename = QFileDialog().getSaveFileName(self, 'Save', '', '*.txt') widget = self.tab_widget.currentWidget() widget.save(filename) #we call the new tab conf def New_Config(self): widget = Conf_Tab(self) self.tab_widget.addTab(widget, 'New configfile') widget.command.connect(self.Add_To_Liste) #---------------------------------------------------------------------------------------------------- #-----------------------------------------CREATE TABLE----------------------------------------------- class Table(QWidget): def __init__(self, label, parent=None): super(Table, self).__init__() # create a QTableWidget self.table = QTableWidget(1, 2, self) self.table.setHorizontalHeaderLabels(['Parameter', 'Value', 'Comment']) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.setTextElideMode(Qt.ElideLeft) #create combobox self.combobox = QComboBox() #add items self.cell = QTableWidgetItem("type") self.table.setItem(0, 0, self.cell) self.table.setCellWidget(0, 1, self.combobox) #we create pushbuttons and we call the method when we clic on self.btn_add_row = QPushButton('+', self) self.btn_add_row.clicked.connect(self.add_row) self.buttonRemove = QPushButton('-', self) self.buttonRemove.clicked.connect(self.remove) #the dispositon of the table and the butttons vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.btn_add_row) hbox.addWidget(self.buttonRemove) vbox.addWidget(label) vbox.addLayout(hbox) vbox.addWidget(self.table) self.setLayout(vbox) def add_row(self): self.table.insertRow(self.table.rowCount()) def remove(self): self.table.removeRow(self.table.currentRow()) def get_keys(self): return list(str(self.table.item(index, 0).text()) for index in range(self.table.rowCount())) #Here we take all values from tables def getParam(self): for index in range(self.table.rowCount()): if not self.table.item == None: key = str(self.table.item(index, 0).text()) comment = str(self.table.item(index, 0).toolTip()) if index == 0: yield key, str(self.table.cellWidget(index, 1).currentText()), comment elif self.table.item(index, 1): if len(str(self.table.item(index, 1).text())) != 0 and self.table.item(index, 0).textColor() == QColor('black'): yield key, str(self.table.item(index, 1).text()), comment #Here we put all values in tables def addData(self, cfg): for item in cfg: if item == 'type': box = self.table.cellWidget(0, 1) value = cfg[item].split(':') if len(value) > 1: box.setCurrentIndex(box.findText(value[1], Qt.MatchFixedString)) else: box.setCurrentIndex(box.findText(cfg[item], Qt.MatchFixedString)) elif item not in self.get_keys(): self.add_row() row = self.table.rowCount() for col in range(self.table.columnCount()): if col == 0: newitem = QTableWidgetItem(item) self.table.setItem(row - 1, col, newitem) if col == 1: newitem2 = QTableWidgetItem(cfg[item]) self.table.setItem(row - 1, col, newitem2) else: index = self.get_keys().index(item) self.table.item(index, 1).setText(cfg[item]) def addDataConf(self, options): keys = self.get_keys() newconfigs = dict((option[0], '') for option in options if option[0] not in keys) self.addData(newconfigs) names = list(option[0] for option in options) for index, key in enumerate(self.get_keys()): if str(key) in names: self.table.item(index, 0).setTextColor(QColor('black')) self.table.item(index, 0).setToolTip(options[names.index(key)][1]) elif str(key) == 'type': self.table.item(index, 0).setTextColor(QColor('black')) else: self.table.item(index, 0).setTextColor(QColor('gray')) def add_to_combo(self, items): self.combobox.clear() self.combobox.addItems(items) #---------------------------------------------------------------------------------------------------- #-----------------------------------------CREATE CONFIG---------------------------------------------- class Conf_Tab(QWidget): def __init__(self, parent=None): super(Conf_Tab, self).__init__(parent) #we create 3 tables self.Dis = Table(QLabel('Dispatcher :')) self.Inp = Table(QLabel('Input :')) self.Pro = Table(QLabel('Projection :')) self.select = QComboBox() backends = list(backend.lower() for backend in binoculars.util.get_backends()) #we add the list of different backends on the select combobox self.select.addItems(backends) self.add = QPushButton('add') self.add.clicked.connect(self.AddCommand) self.scan = QLineEdit() self.scan.setToolTip('scan selection example: 820 824') vbox = QVBoxLayout() hbox = QHBoxLayout() splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.Dis) splitter.addWidget(self.Inp) splitter.addWidget(self.Pro) hbox.addWidget(splitter) commandbox = QHBoxLayout() commandbox.addWidget(self.add) commandbox.addWidget(self.scan) vbox.addWidget(self.select) vbox.addLayout(hbox) vbox.addLayout(commandbox) #the dispositon of all elements of the gui #Layout = QGridLayout() #Layout.addWidget(label1,1,1,1,2) #Layout.addWidget(label2,1,0,1,2) #Layout.addWidget(label3,1,2,1,2) #Layout.addWidget(self.select,0,0) #Layout.addWidget(self.Dis,2,1) #Layout.addWidget(self.Inp,2,0) #Layout.addWidget(self.Pro,2,2) #Layout.addWidget(self.add,3,0) #Layout.addWidget(self.scan,3,1) self.setLayout(vbox) #Here we call all methods for selected an ellement on differents combobox self.Dis.add_to_combo(binoculars.util.get_dispatchers()) self.select.activated['QString'].connect(self.DataCombo) self.Inp.combobox.activated.connect(self.DataTableInp) self.Pro.combobox.activated.connect(self.DataTableInpPro) self.Dis.combobox.activated.connect(self.DataTableInpDis) def DataCombo(self, text): self.Inp.add_to_combo(binoculars.util.get_inputs(str(text))) self.Pro.add_to_combo(binoculars.util.get_projections(str(text))) self.DataTableInp() self.DataTableInpPro() self.DataTableInpDis() def DataTableInp(self): backend = str(self.select.currentText()) inp = binoculars.util.get_input_configkeys(backend, str(self.Inp.combobox.currentText())) self.Inp.addDataConf(inp) def DataTableInpPro(self): backend = str(self.select.currentText()) proj = binoculars.util.get_projection_configkeys(backend, str(self.Pro.combobox.currentText())) self.Pro.addDataConf(proj) def DataTableInpDis(self): disp = binoculars.util.get_dispatcher_configkeys(str(self.Dis.combobox.currentText())) self.Dis.addDataConf(disp) #The save method we take all ellements on tables and we put them in this format {0} = {1} #{2} def save(self, filename): with open(filename, 'w') as fp: fp.write('[dispatcher]\n') # cycles over the iterator object for key, value, comment in self.Dis.getParam(): fp.write('{0} = {1} #{2}\n'.format(key, value, comment)) fp.write('[input]\n') for key, value, comment in self.Inp.getParam(): if key == 'type': value = '{0}:{1}'.format(self.select.currentText(), value) fp.write('{0} = {1} #{2}\n'.format(key, value, comment)) fp.write('[projection]\n') for key, value, comment in self.Pro.getParam(): if key == 'type': value = '{0}:{1}'.format(self.select.currentText(), value) fp.write('{0} = {1} #{2}\n'.format(key, value, comment)) #This method take the name of objects and values for run the script def get_configobj(self): inInp = {} inDis = {} inPro = {} inDis = dict((key, value) for key, value, comment in self.Dis.getParam()) for key, value, comment in self.Inp.getParam(): if key == 'type': value = '{0}:{1}'.format(str(self.select.currentText()).strip(), value) inInp[key] = value for key, value, comment in self.Pro.getParam(): if key == 'type': value = '{0}:{1}'.format(str(self.select.currentText()).strip(), value) inPro[key] = value cfg = binoculars.util.ConfigFile('processgui {0}'.format(time.strftime('%d %b %Y %H:%M:%S', time.localtime()))) setattr(cfg, 'input', inInp) setattr(cfg, 'dispatcher', inDis) setattr(cfg, 'projection', inPro) return cfg #This method take elements on a text file or the binocular script and put them on tables def read_data(self, filename): cfg = binoculars.util.ConfigFile.fromtxtfile(str(filename)) input_type = cfg.input['type'] backend, value = input_type.strip(' ').split(':') self.select.setCurrentIndex(self.select.findText(backend, Qt.MatchFixedString)) self.DataCombo(backend) self.Dis.addData(cfg.dispatcher) self.Inp.addData(cfg.input) self.Pro.addData(cfg.projection) #we add command on the DockWidget def AddCommand(self): scan = [str(self.scan.text())] cfg = self.get_configobj() commandconfig = (scan, cfg) self.command.emit(commandconfig) if __name__ == '__main__': app = QApplication(sys.argv) main = Window() main.show() sys.exit(app.exec_()) binoculars-0.0.10/scripts/binoculars-server000077500000000000000000000113501412510113200210010ustar00rootroot00000000000000#!/usr/bin/env python '''Serverqueue where jobs can be submitted. Jobs will be calculated on the spot or passed on to the OAR cluster if so specified in the configfile. Jobs can be submitted in a json dictionary. The keyword 'command' and 'configfilename' supply a string with the command and the path to the configfile. Everything else is assumed to be an override in the configfile. If an override cannot be parsed the job will start anyway without the override. The processingqueue cannot be interrupted. ''' import socket import threading import time import sys import traceback import json import os #python3 support PY3 = sys.version_info > (3,) if PY3: import socketserver import queue else: import SocketServer as socketserver import Queue as queue def set_src(): import sys import os.path as osp dirpath = osp.join(osp.dirname(osp.abspath(__file__)), osp.pardir) sys.path.insert(0, osp.abspath(dirpath)) try: import binoculars.main import binoculars.util except ImportError: # try to use code from src distribution set_src() import binoculars.main import binoculars.util class ProcessTCPHandler(socketserver.BaseRequestHandler): def handle(self): input = self.request.recv(1024) if input.startswith('test'): print('Recieved test request') self.request.sendall('Connection succesful') else: try: job = json.loads(input) parsed, result = parse_job(job) if parsed: print('Recieved command: {0}. Job is added to queue.\nNumber of jobs left in queue: {1}'.format(job['command'], self.server.q.qsize())) response = 'Job added to queue' self.server.q.put(job) else: response = result except: print('Could not parse the job: {0}'.format(input)) print(traceback.format_exc()) response = 'Error: Job could not be added to queue' finally: self.request.sendall(response) def parse_job(job): try: overrides = [] for key in list(job.keys()): if not key in ['command', 'configfilename']: section_key, value = job[key].split('=') section, key = section_key.split(':') overrides.append((section, key, value)) return True, overrides except: message = 'Error parsing the configuration options. {0}'.format(job) return False, message def process(run_event, ip, port, q): while run_event.is_set(): if q.empty(): time.sleep(1) else: job = q.get() # assume everything in the list is an override except for command and configfilename command = str(job['command']) configfilename = job['configfilename'] overrides = parse_job(job)[1] # [1] are the succesfully parsed jobs print('Start processing: {0}'.format(command)) try: configobj = binoculars.util.ConfigFile.fromtxtfile(configfilename, overrides=overrides) if binoculars.util.parse_bool(configobj.dispatcher['send_to_gui']): configobj.dispatcher['host'] = ip configobj.dispatcher['port'] = port binoculars.main.Main.from_object(configobj, [command]) print('Succesfully finished processing: {0}.'.format(command)) except Exception as exc: errorfilename = 'error_{0}.txt'.format(command) print('An error occured for scan {0}. For more information see {1}'.format(command, errorfilename)) with open(errorfilename, 'w') as fp: traceback.print_exc(file=fp) finally: print('Number of jobs left in queue: {0}'.format(q.qsize())) if __name__ == '__main__': if len(sys.argv) > 1: ip = sys.argv[1] port = sys.argv[2] else: ip = None port = None q = queue.Queue() binoculars.util.register_python_executable(os.path.join(os.path.dirname(__file__), 'binoculars.py')) HOST, PORT = socket.gethostbyname(socket.gethostname()), 0 run_event = threading.Event() run_event.set() process_thread = threading.Thread(target=process, args=(run_event, ip, port, q)) process_thread.start() server = socketserver.TCPServer((HOST, PORT), ProcessTCPHandler) server.q = q ip, port = server.server_address print('Process server started running at ip {0} and port {1}. Interrupt server with Ctrl-C'.format(ip, port)) try: server.serve_forever() except KeyboardInterrupt: run_event.clear() process_thread.join() binoculars-0.0.10/scripts/binoviewer.py000066400000000000000000005655751412510113200201600ustar00rootroot00000000000000""" Created on Wed Dec 07 11:10:28 2016 @author: Prevot """ # program for visualisation of the binoculars file the values # corresponds to the pixel center. ie, there is 11 points in the [0,1] # range with 0.1 resolution the data are loaded in the form of a numpy # array of shape (nx,ny,nz) # -*- coding: utf-8 -*- # # Copyright © 2009-2011, 2020 CEA # Pierre Raybaut # Licensed under the terms of the CECILL License # (see guiqwt/__init__.py for details) """Oblique averaged cross section test""" SHOW = True # Show test in GUI-based test launcher import os.path as osp import numpy as np import tables import guiqwt.cross_section # debug mode shows the ROI in the top-left corner of the image plot: guiqwt.cross_section.DEBUG = True from guidata.configtools import get_icon from guidata.dataset.datatypes import DataSet from guidata.dataset.dataitems import IntItem, FloatItem, BoolItem from guidata.qt.QtCore import SIGNAL, QSize, QObject, Qt, QPoint, QPointF from guidata.qt.QtGui import ( qApp, QCheckBox, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QLineEdit, QKeyEvent, QWidget, QSlider, QSpinBox, QDoubleSpinBox, QColor, QComboBox, QAction, QMenu, QMenuBar, QProgressBar, QPolygonF, QPushButton, QButtonGroup, QDialog, QMessageBox, QToolBar, QGroupBox, ) from guiqwt.baseplot import canvas_to_axes from guiqwt.builder import PlotItemBuilder as GuiPlotItemBuilder from guiqwt.config import _, make_title from guiqwt.curve import CurvePlot, CurveItem from guiqwt.events import DragHandler, setup_standard_tool_filter from guiqwt.histogram import lut_range_threshold from guiqwt.image import MaskedImageItem from guiqwt.interfaces import ICurveItemType, IPanel from guiqwt.plot import ImageDialog, PlotManager, CurveDialog from guiqwt.signals import ( SIG_ITEMS_CHANGED, SIG_STOP_NOT_MOVING, SIG_MOVE, SIG_STOP_MOVING, SIG_START_TRACKING, SIG_MARKER_CHANGED, SIG_RANGE_CHANGED, SIG_ITEM_SELECTION_CHANGED, SIG_VALIDATE_TOOL, SIG_ACTIVE_ITEM_CHANGED, ) from guiqwt.styles import ImageParam, MaskedImageParam, CurveParam from guiqwt.tools import ( OpenFileTool, CommandTool, DefaultToolbarID, InteractiveTool, RectangleTool, CircleTool, FreeFormTool, BasePlotMenuTool, AntiAliasingTool, SelectTool, ) from guiqwt.panels import PanelWidget from guiqwt.shapes import ( XRangeSelection, Marker, PointShape, RectangleShape, EllipseShape, PolygonShape, ) from guiqwt._scaler import _histogram from scipy.interpolate import griddata from scipy.ndimage import laplace, uniform_filter from scipy import signal from grafit import Fit2DWindow CURVE_COUNT = 0 SIG_STOP_TRACKING = SIGNAL("stop_tracking") SIG_DOUBLE_VALUE_CHANGED = SIGNAL("valueChanged(double)") SIG_INT_VALUE_CHANGED = SIGNAL("valueChanged(int)") SIG_SLIDER_PRESSED = SIGNAL("sliderPressed()") SIG_SLIDER_RELEASED = SIGNAL("sliderReleased()") SIG_STATE_CHANGED = SIGNAL("stateChanged(int)") SIG_CLICKED = SIGNAL("clicked()") ID_IMAGEMASKING = "image masking" class XRangeSelection2(XRangeSelection): def move_point_to(self, hnd, pos, ctrl=None): val, _ = pos if hnd == 0: if val <= self._max: self._min = val elif hnd == 1: if val >= self._min: self._max = val elif hnd == 2: move = val - (self._max + self._min) / 2 self._min += move self._max += move self.plot().emit(SIG_RANGE_CHANGED, self, self._min, self._max) def _nanmin(data): if data.dtype.name in ("float32", "float64", "float128"): return np.nanmin(data) else: return data.min() def _nanmax(data): if data.dtype.name in ("float32", "float64", "float128"): return np.nanmax(data) else: return data.max() def remove_masked_slices(line, length): # remove slice if length rms * threshold # values that are above threshold msign = diff1[1:] > 0 # lines below ldown = np.logical_and(mask, np.logical_not(msign)) # lines above lup = np.logical_and(mask, msign) temp = np.ma.zeros(mask.shape) temp.mask = ldown np.ma.apply_along_axis(remove_masked_slices, 1, temp, *(length,)) ldown = np.copy(temp.mask) temp.mask = lup np.ma.apply_along_axis(remove_masked_slices, 1, temp, *(length,)) mask = np.zeros_like(data, dtype=np.bool) mask[1:-1] = np.logical_or(ldown, temp.mask) return mask class Preferences(DataSet): eraser_size = IntItem("Eraser size", default=1, min=1) class LaplaceParam(DataSet): cutoff = IntItem("remove contributions smaller than", default=1, min=1) steps = IntItem("integration steps", default=10, min=0) increase = FloatItem("speed up coefficient", default=1.1, min=1.0) decrease = FloatItem( "speed down coefficient", default=0.5, nonzero=True, max=1.0 ).set_pos(col=1) fill = FloatItem("starting values", default=1.0, min=0.0) fillc = FloatItem("contributions", default=10, min=1.0).set_pos(col=1) doslice = BoolItem("apply along slice", default=False) class FilterParam(DataSet): cutoff = IntItem("remove contributions smaller than", default=1, min=1) class BgRangeSelection(XRangeSelection2): def __init__(self, _min, _max, shapeparam=None): super(BgRangeSelection, self).__init__(_min, _max, shapeparam=shapeparam) self.shapeparam.fill = "#000000" self.shapeparam.update_range(self) # creates all the above QObjects class BinoPlot(CurvePlot): def keyPressEvent(self, event): "on redefinit l'action liee a un evenement clavier quand la fenetre a le focus" # the keys have been inverted for the vertical displacement because the y axis is oriented in the increasing direction if type(event) == QKeyEvent: # here accept the event and do something touche = event.key() if touche == 16777236: self.emit(SIGNAL("Move(int)"), 1) elif touche == 16777234: self.emit(SIGNAL("Move(int)"), -1) class BinoCurve(CurveItem): def get_closest_x(self, xc, yc): # need to correct an error in guiqwt! # We assume X is sorted, otherwise we'd need : # argmin(abs(x-xc)) i = self._x.searchsorted(xc) n = len(self._x) if 0 < i < n: if np.fabs(self._x[i - 1] - xc) < np.fabs(self._x[i] - xc): return self._x[i - 1], self._y[i - 1] elif i == n: i = n - 1 return self._x[i], self._y[i] class AlphaMaskedArea(object): """Defines masked/alpha areas for a masked/alpha image item""" """geometry can be rectangular, elliptical or polygonal""" """mask can be applied inside or outside the shape""" """the shape can be used to mask or unmask""" """a gradient can be applied along a direction """ def __init__(self, geometry=None, pts=None, inside=None, mask=None, gradient=None): self.geometry = geometry self.pts = pts self.inside = inside self.mask = mask self.gradient = gradient def __eq__(self, other): return ( self.geometry == other.geometry and np.array_equal(self.pts, other.pts) and self.inside == other.inside and self.mask == self.mask and self.gradient == self.gradient ) def serialize(self, writer): """Serialize object to HDF5 writer""" for name in ("geometry", "inside", "mask", "gradient", "pts"): writer.write(getattr(self, name), name) def deserialize(self, reader): """Deserialize object from HDF5 reader""" self.geometry = reader.read("geometry") self.inside = reader.read("inside") self.mask = reader.read("mask") self.gradient = reader.read("gradient") self.pts = reader.read(group_name="pts", func=reader.read_array) class MaskedImageNan(MaskedImageItem): def get_histogram(self, nbins): """interface de IHistDataSource""" if self.data is None: return [0,], [0, 1] if self.histogram_cache is None or nbins != self.histogram_cache[0].shape[0]: # from guidata.utils import tic, toc if False: # tic("histo1") res = np.histogram(self.data[~np.isnan(self.data)], nbins) # toc("histo1") else: # TODO: _histogram is faster, but caching is buggy # in this version # tic("histo2") _min = _nanmin(self.data) _max = _nanmax(self.data) if self.data.dtype in (np.float64, np.float32): bins = np.unique( np.array( np.linspace(_min, _max, nbins + 1), dtype=self.data.dtype ) ) else: bins = np.arange(_min, _max + 2, dtype=self.data.dtype) res2 = np.zeros((bins.size + 1,), np.uint32) _histogram(self.data.flatten(), bins, res2) # toc("histo2") res = res2[1:-1], bins self.histogram_cache = res else: res = self.histogram_cache return res def set_masked_areas(self, areas): """Set masked areas (see set_mask_filename)""" self._masked_areas = areas def get_masked_areas(self): return self._masked_areas def add_masked_area(self, geometry, pts, inside, mask): area = AlphaMaskedArea( geometry=geometry, pts=pts, inside=inside, mask=mask, gradient=None ) for _area in self._masked_areas: if area == _area: return self._masked_areas.append(area) def mask_rectangular_area( self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True ): """ Mask rectangular area If inside is True (default), mask the inside of the area Otherwise, mask the outside """ ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) if inside: self.data[iy0:iy1, ix0:ix1] = np.ma.masked else: indexes = np.ones(self.data.shape, dtype=np.bool) indexes[iy0:iy1, ix0:ix1] = False self.data[indexes] = np.ma.masked if trace: self.add_masked_area( "rectangular", np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]), inside, mask=True, ) if do_signal: self._mask_changed() def mask_square_area(self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True): """ Mask a square area defined by the sponge If inside is True (default), mask the inside of the area Otherwise, mask the outside """ ix0, iy0 = self.get_nearest_indexes(x0, y0) ix1, iy1 = self.get_nearest_indexes(x1, y1) if inside: self.data[iy0:iy1, ix0:ix1] = np.ma.masked else: indexes = np.ones(self.data.shape, dtype=np.bool) indexes[iy0:iy1, ix0:ix1] = False self.data[indexes] = np.ma.masked if trace: self.add_masked_area( "square", np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]), inside, mask=True, ) if do_signal: self._mask_changed() def mask_circular_area( self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True ): """ Mask circular area, inside the rectangle (x0, y0, x1, y1), i.e. circle with a radius of .5*(x1-x0) If inside is True (default), mask the inside of the area Otherwise, mask the outside """ ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) xc, yc = 0.5 * (x0 + x1), 0.5 * (y0 + y1) radius = 0.5 * (x1 - x0) xdata, ydata = self.get_x_values(ix0, ix1), self.get_y_values(iy0, iy1) for ix in range(ix0, ix1): for iy in range(iy0, iy1): distance = np.sqrt( (xdata[ix - ix0] - xc) ** 2 + (ydata[iy - iy0] - yc) ** 2 ) if inside: if distance <= radius: self.data[iy, ix] = np.ma.masked elif distance > radius: self.data[iy, ix] = np.ma.masked if not inside: self.mask_rectangular_area(x0, y0, x1, y1, inside, trace=False) if trace: xc = (x0 + x1) / 2.0 yc = (y0 + y1) / 2.0 dx = abs(x1 - x0) / 2.0 dy = abs(y1 - y0) / 2.0 self.add_masked_area( "circular", np.array([[xc, yc + dy], [xc, yc - dy], [xc + dx, yc], [xc - dx, yc]]), inside, mask=True, ) if do_signal: self._mask_changed() def mask_polygonal_area(self, pts, inside=True, trace=True, do_signal=True): """ Mask polygonal area, inside the rectangle (x0, y0, x1, y1) #points is a np array of the polygon points """ x0, y0 = np.min(pts, axis=0) x1, y1 = np.max(pts, axis=0) if not self.plot(): return # we construct a QpolygonF to use the containsPoint function of PyQt poly = QPolygonF() for i in range(pts.shape[0]): poly.append(QPointF(pts[i, 0], pts[i, 1])) ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) xdata, ydata = ( self.get_x_values(ix0, ix1), self.get_y_values(iy0, iy1), ) # values in the axis referential for ix in range(ix0, ix1): for iy in range(iy0, iy1): inside_poly = poly.containsPoint( QPointF(xdata[ix - ix0], ydata[iy - iy0]), Qt.OddEvenFill ) if inside: if inside_poly: self.data[iy, ix] = np.ma.masked elif not inside_poly: self.data[iy, ix] = np.ma.masked if not inside: self.mask_rectangular_area(x0, y0, x1, y1, inside, trace=False) if trace: self.add_masked_area("polygonal", pts, inside, mask=True) if do_signal: self._mask_changed() def unmask_rectangular_area( self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True ): """ Unmask rectangular area If inside is True (default), unmask the inside of the area Otherwise, unmask the outside """ ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) if inside: self.data.mask[iy0:iy1, ix0:ix1] = False else: indexes = np.ones(self.data.shape, dtype=np.bool) indexes[iy0:iy1, ix0:ix1] = False self.data.mask[indexes] = False if trace: self.add_masked_area( "rectangular", np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]), inside, mask=False, ) if do_signal: self._mask_changed() def unmask_square_area( self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True ): """ Unmask square area If inside is True (default), unmask the inside of the area Otherwise, unmask the outside """ ix0, iy0 = self.get_nearest_indexes(x0, y0) ix1, iy1 = self.get_nearest_indexes(x1, y1) if inside: self.data.mask[iy0:iy1, ix0:ix1] = False else: indexes = np.ones(self.data.shape, dtype=np.bool) indexes[iy0:iy1, ix0:ix1] = False self.data.mask[indexes] = False if trace: self.add_masked_area( "square", np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]), inside, mask=False, ) if do_signal: self._mask_changed() def unmask_circular_area( self, x0, y0, x1, y1, inside=True, trace=True, do_signal=True ): """ Unmask circular area, inside the rectangle (x0, y0, x1, y1), i.e. circle with a radius of .5*(x1-x0) If inside is True (default), unmask the inside of the area Otherwise, unmask the outside """ ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) xc, yc = 0.5 * (x0 + x1), 0.5 * (y0 + y1) radius = 0.5 * (x1 - x0) xdata, ydata = self.get_x_values(ix0, ix1), self.get_y_values(iy0, iy1) for ix in range(ix0, ix1): for iy in range(iy0, iy1): distance = np.sqrt( (xdata[ix - ix0] - xc) ** 2 + (ydata[iy - iy0] - yc) ** 2 ) if inside: if distance <= radius: self.data.mask[iy, ix] = False elif distance > radius: self.data.mask[iy, ix] = False if not inside: self.unmask_rectangular_area(x0, y0, x1, y1, inside, trace=False) if trace: xc = (x0 + x1) / 2.0 yc = (y0 + y1) / 2.0 dx = abs(x1 - x0) / 2.0 dy = abs(y1 - y0) / 2.0 self.add_masked_area( "circular", np.array([[xc, yc + dy], [xc, yc - dy], [xc + dx, yc], [xc - dx, yc]]), inside, mask=False, ) if do_signal: self._mask_changed() def unmask_polygonal_area(self, pts, inside=True, trace=True, do_signal=True): """ Unmask polygonal area, inside the polygon points is a np array of the polygon points """ x0, y0 = np.min(pts, axis=0) x1, y1 = np.max(pts, axis=0) if not self.plot(): return # we construct a QpolygonF to use the containsPoint function of PyQt poly = QPolygonF() for i in range(pts.shape[0]): poly.append(QPointF(pts[i, 0], pts[i, 1])) ix0, iy0, ix1, iy1 = self.get_closest_index_rect(x0, y0, x1, y1) xdata, ydata = ( self.get_x_values(ix0, ix1), self.get_y_values(iy0, iy1), ) # values in the axis referential for ix in range(ix0, ix1): for iy in range(iy0, iy1): inside_poly = poly.containsPoint( QPointF(xdata[ix - ix0], ydata[iy - iy0]), Qt.OddEvenFill ) if inside: if inside_poly: self.data.mask[iy, ix] = False elif not inside_poly: self.data.mask[iy, ix] = False if not inside: self.unmask_rectangular_area(x0, y0, x1, y1, inside, trace=False) if trace: self.add_masked_area("polygonal", pts, inside, mask=False) if do_signal: self._mask_changed() def update_mask(self): if isinstance(self.data, np.ma.MaskedArray): self.data.set_fill_value(self.imageparam.filling_value) # self.mask_qcolor=self.imageparam.mask_color class PlotItemBuilder(GuiPlotItemBuilder): def __init__(self): super(PlotItemBuilder, self).__init__() def binocurve( self, x, y, title=u"", color=None, linestyle=None, linewidth=None, marker=None, markersize=None, markerfacecolor=None, markeredgecolor=None, shade=None, fitted=None, curvestyle=None, curvetype=None, baseline=None, xaxis="bottom", yaxis="left", ): """ Make a curve `plot item` from x, y, data (:py:class:`guiqwt.curve.CurveItem` object) * x: 1D NumPy array * y: 1D NumPy array * color: curve color name * linestyle: curve line style (MATLAB-like string or attribute name from the :py:class:`PyQt4.QtCore.Qt.PenStyle` enum (i.e. "SolidLine" "DashLine", "DotLine", "DashDotLine", "DashDotDotLine" or "NoPen") * linewidth: line width (pixels) * marker: marker shape (MATLAB-like string or attribute name from the :py:class:`PyQt4.Qwt5.QwtSymbol.Style` enum (i.e. "Cross", "Ellipse", "Star1", "XCross", "Rect", "Diamond", "UTriangle", "DTriangle", "RTriangle", "LTriangle", "Star2" or "NoSymbol") * markersize: marker size (pixels) * markerfacecolor: marker face color name * markeredgecolor: marker edge color name * shade: 0 <= float <= 1 (curve shade) * fitted: boolean (fit curve to data) * curvestyle: attribute name from the :py:class:`PyQt4.Qwt5.QwtPlotCurve.CurveStyle` enum (i.e. "Lines", "Sticks", "Steps", "Dots" or "NoCurve") * curvetype: attribute name from the :py:class:`PyQt4.Qwt5.QwtPlotCurve.CurveType` enum (i.e. "Yfx" or "Xfy") * baseline (float: default=0.0): the baseline is needed for filling the curve with a brush or the Sticks drawing style. The interpretation of the baseline depends on the curve type (horizontal line for "Yfx", vertical line for "Xfy") * xaxis, yaxis: X/Y axes bound to curve Examples: curve(x, y, marker='Ellipse', markerfacecolor='#ffffff') which is equivalent to (MATLAB-style support): curve(x, y, marker='o', markerfacecolor='w') """ basename = _("Curve") param = CurveParam(title=basename, icon="curve.png") if not title: global CURVE_COUNT CURVE_COUNT += 1 title = make_title(basename, CURVE_COUNT) self.__set_param( param, title, color, linestyle, linewidth, marker, markersize, markerfacecolor, markeredgecolor, shade, fitted, curvestyle, curvetype, baseline, ) curve = BinoCurve(param) curve.set_data(x, y) curve.update_params() self.__set_curve_axes(curve, xaxis, yaxis) return curve def imagenan( self, data=None, filename=None, title=None, alpha_mask=None, alpha=None, background_color=None, colormap=None, xdata=[None, None], ydata=[None, None], pixel_size=None, center_on=None, interpolation="linear", eliminate_outliers=None, xformat="%.1f", yformat="%.1f", zformat="%.1f", ): """ Make an image `plot item` from data (:py:class:`guiqwt.image.ImageItem` object or :py:class:`guiqwt.image.RGBImageItem` object if data has 3 dimensions) """ assert isinstance(xdata, (tuple, list)) and len(xdata) == 2 assert isinstance(ydata, (tuple, list)) and len(ydata) == 2 param = ImageParam(title="Image", icon="image.png") data, filename, title = self._get_image_data( data, filename, title, to_grayscale=True ) assert data.ndim == 2, "Data must have 2 dimensions" if pixel_size is None: assert center_on is None, ( "Ambiguous parameters: both `center_on`" " and `xdata`/`ydata` were specified" ) xmin, xmax = xdata ymin, ymax = ydata else: xmin, xmax, ymin, ymax = self.compute_bounds(data, pixel_size, center_on) self.__set_image_param( param, title, alpha_mask, alpha, interpolation, background=background_color, colormap=colormap, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, xformat=xformat, yformat=yformat, zformat=zformat, ) image = MaskedImageNan(data, None, param) image.set_filename(filename) if eliminate_outliers is not None: image.set_lut_range(lut_range_threshold(image, 256, eliminate_outliers)) return image def maskedimagenan( self, data=None, mask=None, filename=None, title=None, alpha_mask=False, alpha=1.0, xdata=[None, None], ydata=[None, None], pixel_size=None, center_on=None, background_color=None, colormap=None, show_mask=False, fill_value=None, interpolation="linear", eliminate_outliers=None, xformat="%.1f", yformat="%.1f", zformat="%.1f", ): """ Make a masked image `plot item` from data (:py:class:`guiqwt.image.MaskedImageItem` object) """ assert isinstance(xdata, (tuple, list)) and len(xdata) == 2 assert isinstance(ydata, (tuple, list)) and len(ydata) == 2 param = MaskedImageParam(title=_("Image"), icon="image.png") data, filename, title = self._get_image_data( data, filename, title, to_grayscale=True ) assert data.ndim == 2, "Data must have 2 dimensions" if pixel_size is None: assert center_on is None, ( "Ambiguous parameters: both `center_on`" " and `xdata`/`ydata` were specified" ) xmin, xmax = xdata ymin, ymax = ydata else: xmin, xmax, ymin, ymax = self.compute_bounds(data, pixel_size, center_on) self.__set_image_param( param, title, alpha_mask, alpha, interpolation, background=background_color, colormap=colormap, xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, show_mask=show_mask, fill_value=fill_value, xformat=xformat, yformat=yformat, zformat=zformat, ) image = MaskedImageNan(data, mask, param) image.set_filename(filename) if eliminate_outliers is not None: image.set_lut_range(lut_range_threshold(image, 256, eliminate_outliers)) return image make = PlotItemBuilder() class SlicePrefs: i0 = 0 i1 = 1 i2 = 2 i3 = 0 absmins = [0, 0, 0] # les valeurs min des points absmaxs = [1, 1, 1] # les valeurs max des points (borne exclue) mins = [0, 0, 0] # les valeurs min de la range pour les slices maxs = [0, 0, 0] # les valeurs max de la range pour les slices (borne exclue) steps = [1, 1, 1] labels = ["x", "y", "z"] ranges = [1.0, 1.0, 1.0] pos1 = [0.0, 0.0, 0.0] # position en bas de tige pos2 = [0.0, 0.0, 1.0] # position en haut de tige stepw = 1 # width of a slice stepn = 1 # number of slices do_it = False class MoveHandler(DragHandler): def __init__(self, filter, btn, mods=Qt.NoModifier, start_state=0): super(MoveHandler, self).__init__(filter, btn, mods, start_state) self.avoid_null_shape = False def set_shape(self, shape, setup_shape_cb=None, avoid_null_shape=False): # called at initialization of the tool self.shape = shape self.setup_shape_cb = setup_shape_cb self.avoid_null_shape = avoid_null_shape def start_tracking(self, filter, event): self.shape.attach(filter.plot) self.shape.setZ(filter.plot.get_max_z() + 1) if self.avoid_null_shape: self.start -= QPoint(1, 1) pt = event.pos() self.shape.set_local_center(pt) if self.setup_shape_cb is not None: self.setup_shape_cb(self.shape) self.shape.show() filter.plot.replot() self.emit(SIG_START_TRACKING, filter, event) def stop_tracking( self, filter, event ): # when the mouse press button is released before moving self.shape.detach() self.emit(SIG_STOP_TRACKING, filter, event) filter.plot.replot() def start_moving(self, filter, event): pass def move(self, filter, event): pt = event.pos() self.shape.set_local_center(pt) self.emit(SIG_MOVE, filter, event) filter.plot.replot() def stop_moving( self, filter, event ): # when the mouse press button is released after moving self.emit(SIG_STOP_MOVING, filter, event) self.shape.detach() filter.plot.replot() class RectangleCentredShape(RectangleShape): CLOSED = True def __init__(self, xc=0, yc=0, hsize=1, vsize=1, shapeparam=None): super(RectangleCentredShape, self).__init__(shapeparam=shapeparam) self.is_ellipse = False self.hsize = hsize self.vsize = vsize self.set_center(xc, yc) def set_size(self, hsize, vsize): self.hsize = hsize self.vsize = vsize x0, y0, x1, y1 = self.get_rect() xc = (x0 + x1) / 2.0 yc = (y0 + y1) / 2.0 self.set_center(xc, yc) def set_local_center(self, pos): # set center from position in canvas units xc, yc = canvas_to_axes(self, pos) self.set_center(xc, yc) def set_center(self, xc, yc): x0 = xc - self.hsize / 2.0 y0 = yc - self.vsize / 2.0 x1 = xc + self.hsize / 2.0 y1 = yc + self.vsize / 2.0 self.set_rect(x0, y0, x1, y1) class EllipseCentredShape(EllipseShape): CLOSED = True def __init__(self, xc=0, yc=0, hsize=1, vsize=1, shapeparam=None): super(EllipseCentredShape, self).__init__(shapeparam=shapeparam) self.is_ellipse = True self.hsize = hsize self.vsize = vsize self.set_center(xc, yc) def set_size(self, hsize, vsize): self.hsize = hsize self.vsize = vsize x0, y0, x1, y1 = self.get_rect() xc = (x0 + x1) / 2.0 yc = (y0 + y1) / 2.0 self.set_center(xc, yc) def set_local_center(self, pos): # set center from position in canvas units xc, yc = canvas_to_axes(self, pos) self.set_center(xc, yc) def set_center(self, xc, yc): x0 = xc - self.hsize / 2.0 y0 = yc - self.vsize / 2.0 x1 = xc + self.hsize / 2.0 y1 = yc + self.vsize / 2.0 self.set_rect(x0, y0, x1, y1) class SquareCentredShape(RectangleShape): CLOSED = True def __init__(self, xc=0, yc=0, size=1, shapeparam=None): super(SquareCentredShape, self).__init__(shapeparam=shapeparam) self.is_ellipse = False self.size = size self.set_center(xc, yc) def set_size(self, size): self.size = size x0, y0, x1, y1 = self.get_rect() xc = (x0 + x1) / 2.0 yc = (y0 + y1) / 2.0 self.set_center(xc, yc) def set_local_center(self, pos): # set center from position in canvas units xc, yc = canvas_to_axes(self, pos) self.set_center(xc, yc) def set_center(self, xc, yc): x0 = xc - self.size / 2.0 y0 = yc - self.size / 2.0 x1 = xc + self.size / 2.0 y1 = yc + self.size / 2.0 self.set_rect(x0, y0, x1, y1) class DrawingTool(InteractiveTool): TITLE = _("Drawing") ICON = "pencil.png" CURSOR = Qt.CrossCursor AVOID_NULL_SHAPE = False def __init__( self, manager, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(DrawingTool, self).__init__( manager, toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def reset(self): self.shape = None self.current_handle = None def setup_shape(self, shape): """To be reimplemented""" shape.setTitle(self.TITLE) def create_shape(self): shape = PointShape(0, 0) self.setup_shape(shape) return shape def set_shape_style(self, shape): shape.set_style(self.shape_style_sect, self.shape_style_key) def setup_filter(self, baseplot): filter = baseplot.filter start_state = filter.new_state() self.shape = self.get_shape() handler = MoveHandler(filter, Qt.LeftButton, start_state=start_state) handler.set_shape( self.shape, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE ) self.connect(handler, SIG_START_TRACKING, self.start) self.connect(handler, SIG_MOVE, self.move) self.connect(handler, SIG_STOP_NOT_MOVING, self.stop) self.connect(handler, SIG_STOP_MOVING, self.stop) self.connect(handler, SIG_STOP_TRACKING, self.stop) return setup_standard_tool_filter(filter, start_state) def get_shape(self): """Reimplemented RectangularActionTool method""" shape = self.create_shape() self.setup_shape(shape) return shape def validate(self, filter, event): super(DrawingTool, self).validate(filter, event) if self.handle_final_shape_cb is not None: self.handle_final_shape_cb(self.shape) self.reset() def start(self, filter, event): pass def move(self, filter, event): pass def stop(self, filter, event): pass class CircularDrawingTool(DrawingTool): TITLE = _("Drawing") ICON = "circlebrush.png" size = 10 def set_size(self, size): self.size = size if self.shape is not None: self.shape.set_size(self.size) def create_shape(self): shape = EllipseCentredShape(0, 0, self.size) self.setup_shape(shape) return shape class EllipseDrawingTool(DrawingTool): TITLE = _("Drawing") ICON = "brush.jpg" hpixelsize = 1 # size in image pixels vpixelsize = 1 hsize = 1 vsize = 1 def set_pixel_size(self, hpixelsize, vpixelsize=None): hsize = self.hsize / (self.hpixelsize - 0.5) vsize = self.vsize / (self.vpixelsize - 0.5) self.hpixelsize = hpixelsize if vpixelsize is None: self.vpixelsize = hpixelsize # square shape else: self.vpixelsize = vpixelsize self.set_size(hsize, vsize) def set_size(self, hsize, vsize): # size in image coordinates self.hsize = hsize * (self.hpixelsize - 0.5) self.vsize = vsize * (self.vpixelsize - 0.5) if self.shape is not None: self.shape.set_size(self.hsize, self.vsize) def create_shape(self): shape = EllipseCentredShape(0, 0, self.hsize, self.vsize) self.setup_shape(shape) return shape class SquareDrawingTool(DrawingTool): TITLE = _("Drawing") ICON = "brush.jpg" size = 10 def set_size(self, size): self.size = size if self.shape is not None: self.shape.set_size(self.size) def create_shape(self): shape = SquareCentredShape(0, 0, self.size) self.setup_shape(shape) return shape class RectangleDrawingTool(DrawingTool): TITLE = _("Drawing") ICON = "brush.jpg" hpixelsize = 1 # size in image pixels vpixelsize = 1 hsize = 1 vsize = 1 def set_pixel_size(self, hpixelsize, vpixelsize=None): hsize = self.hsize / (self.hpixelsize - 0.5) vsize = self.vsize / (self.vpixelsize - 0.5) self.hpixelsize = hpixelsize if vpixelsize is None: self.vpixelsize = hpixelsize # square shape else: self.vpixelsize = vpixelsize self.set_size(hsize, vsize) def set_size(self, hsize, vsize): # size in image coordinates self.hsize = hsize * (self.hpixelsize - 0.5) self.vsize = vsize * (self.vpixelsize - 0.5) if self.shape is not None: self.shape.set_size(self.hsize, self.vsize) def create_shape(self): shape = RectangleCentredShape(0, 0, self.hsize, self.vsize) self.setup_shape(shape) return shape class RectangleEraserTool(RectangleDrawingTool): TITLE = _("Rectangle eraser") ICON = "eraser.png" CURSOR = Qt.CrossCursor AVOID_NULL_SHAPE = False SHAPE_STYLE_SECT = "plot" SHAPE_STYLE_KEY = "shape/drag" def __init__( self, manager, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(RectangleEraserTool, self).__init__( manager, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None self.hsize = 10 self.vsize = 10 if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def start(self, filter, event): self.emit(SIGNAL("suppress_area()")) def stop(self, filter, event): self.emit(SIG_STOP_MOVING) def move(self, filter, event): """moving while holding the button down lets the user position the last created point """ self.emit(SIGNAL("suppress_area()")) class CircularMaskTool(CircularDrawingTool): TITLE = _("Circular masking brush") ICON = "circle_sponge.png" CURSOR = Qt.CrossCursor AVOID_NULL_SHAPE = False SHAPE_STYLE_SECT = "plot" SHAPE_STYLE_KEY = "shape/drag" def __init__( self, manager, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(CircularMaskTool, self).__init__( manager, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None self.masked_image = None # associated masked image item self.size = 10 self.mode = True if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def set_mode(self, mode): # set the action mask/unmask self.mode = mode def start(self, filter, event): self.masked_image = self.find_masked_image(filter.plot) if self.masked_image is not None: x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() def find_masked_image(self, plot): item = plot.get_active_item() if isinstance(item, MaskedImageItem): return item else: items = [ item for item in plot.get_items() if isinstance(item, MaskedImageItem) ] if items: return items[-1] def move(self, filter, event): """moving while holding the button down lets the user position the last created point """ if self.masked_image is not None: # mask = self.masked_image.get_mask() x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() """ x0, y0, x1, y1 = shape.get_rect() self.masked_image.mask_circular_area(x0, y0, x1, y1,inside=inside) """ class EllipseMaskTool(EllipseDrawingTool): TITLE = _("Ellipse masking brush") ICON = "circle_sponge.png" CURSOR = Qt.CrossCursor AVOID_NULL_SHAPE = False SHAPE_STYLE_SECT = "plot" SHAPE_STYLE_KEY = "shape/drag" def __init__( self, manager, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(EllipseMaskTool, self).__init__( manager, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None self.masked_image = None # associated masked image item self.mode = True if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def set_mode(self, mode): # set the action mask/unmask self.mode = mode def start(self, filter, event): self.masked_image = self.find_masked_image(filter.plot) if self.masked_image is not None: x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_circular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() def find_masked_image(self, plot): item = plot.get_active_item() if isinstance(item, MaskedImageItem): return item else: items = [ item for item in plot.get_items() if isinstance(item, MaskedImageItem) ] if items: return items[-1] def move(self, filter, event): """moving while holding the button down lets the user position the last created point """ if self.masked_image is not None: x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() class RectangleMaskTool(RectangleDrawingTool): TITLE = _("Square masking brush") ICON = "square_sponge.png" CURSOR = Qt.CrossCursor AVOID_NULL_SHAPE = False SHAPE_STYLE_SECT = "plot" SHAPE_STYLE_KEY = "shape/drag" def __init__( self, manager, handle_final_shape_cb=None, shape_style=None, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None, switch_to_default_tool=None, ): super(RectangleMaskTool, self).__init__( manager, toolbar_id=toolbar_id, title=title, icon=icon, tip=tip, switch_to_default_tool=switch_to_default_tool, ) self.handle_final_shape_cb = handle_final_shape_cb self.shape = None self.masked_image = None # associated masked image item self.mode = True if shape_style is not None: self.shape_style_sect = shape_style[0] self.shape_style_key = shape_style[1] else: self.shape_style_sect = "plot" self.shape_style_key = "shape/drag" def set_mode(self, mode): # set the action mask/unmask self.mode = mode def start(self, filter, event): self.masked_image = self.find_masked_image(filter.plot) if self.masked_image is not None: x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() def find_masked_image(self, plot): item = plot.get_active_item() if isinstance(item, MaskedImageItem): return item else: items = [ item for item in plot.get_items() if isinstance(item, MaskedImageItem) ] if items: return items[-1] def move(self, filter, event): """moving while holding the button down lets the user position the last created point """ if self.masked_image is not None: x0, y0, x1, y1 = self.shape.get_rect() if self.mode: self.masked_image.mask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) else: self.masked_image.unmask_rectangular_area( x0, y0, x1, y1, trace=False, inside=True ) self.masked_image.plot().replot() class SetSliceWindow(QDialog): # definit une fenetre pour rentrer les parametres de dialogue de construction de map 3D def __init__(self, prefs): self.prefs = prefs super( SetSliceWindow, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.setWindowTitle("Parameters for slice generation") self.setFixedSize(QSize(450, 160)) self.layout = QGridLayout() self.setLayout(self.layout) self.lab0 = QLabel("Direction", self) self.lab1 = QLabel("Slice", self) self.lab2 = QLabel("Scan", self) self.lab3 = QLabel("Raw sum", self) self.lab4 = QLabel("Min (incl.)", self) self.lab5 = QLabel("Max (excl.)", self) self.xlab = QLabel(prefs.labels[0], self) self.ylab = QLabel(prefs.labels[1], self) self.zlab = QLabel(prefs.labels[2], self) self.lab6 = QLabel("Steps", self) self.lab7 = QLabel("Width", self) self.xbox1 = QCheckBox(self) self.ybox1 = QCheckBox(self) self.zbox1 = QCheckBox(self) self.group1 = QButtonGroup(self) self.group1.addButton(self.xbox1, 1) self.group1.addButton(self.ybox1, 2) self.group1.addButton(self.zbox1, 3) self.group1.button(prefs.i1 + 1).setChecked(True) self.xbox2 = QCheckBox(self) self.ybox2 = QCheckBox(self) self.zbox2 = QCheckBox(self) self.group2 = QButtonGroup(self) self.group2.addButton(self.xbox2, 1) self.group2.addButton(self.ybox2, 2) self.group2.addButton(self.zbox2, 3) self.group2.button(prefs.i2 + 1).setChecked(True) self.xbox3 = QCheckBox(self) self.ybox3 = QCheckBox(self) self.zbox3 = QCheckBox(self) self.group3 = QButtonGroup(self) self.group3.addButton(self.xbox3, 1) self.group3.addButton(self.ybox3, 2) self.group3.addButton(self.zbox3, 3) self.group3.button(prefs.i3 + 1).setChecked(True) self.xmin = QDoubleSpinBox(self) self.xmin.setRange(prefs.absmins[0], prefs.absmaxs[0]) self.xmin.setSingleStep(prefs.steps[0]) self.xmin.setDecimals(max(2, int(1 - np.log10(prefs.steps[0])))) self.xmin.setValue(prefs.mins[0]) self.xmax = QDoubleSpinBox(self) self.xmax.setRange(prefs.absmins[0], prefs.absmaxs[0]) self.xmax.setSingleStep(prefs.steps[0]) self.xmax.setDecimals(max(2, int(1 - np.log10(prefs.steps[0])))) self.xmax.setValue(prefs.maxs[0]) self.ymin = QDoubleSpinBox(self) self.ymin.setRange(prefs.absmins[1], prefs.absmaxs[1]) self.ymin.setSingleStep(prefs.steps[1]) self.ymin.setDecimals(max(2, int(1 - np.log10(prefs.steps[1])))) self.ymin.setValue(prefs.mins[1]) self.ymax = QDoubleSpinBox(self) self.ymax.setRange(prefs.absmins[1], prefs.absmaxs[1]) self.ymax.setSingleStep(prefs.steps[1]) self.ymax.setDecimals(max(2, int(1 - np.log10(prefs.steps[1])))) self.ymax.setValue(prefs.maxs[1]) self.zmin = QDoubleSpinBox(self) self.zmin.setRange(prefs.absmins[2], prefs.absmaxs[2]) self.zmin.setSingleStep(prefs.steps[2]) self.zmin.setDecimals(max(2, int(1 - np.log10(prefs.steps[2])))) self.zmin.setValue(prefs.mins[2]) self.zmax = QDoubleSpinBox(self) self.zmax.setRange(prefs.absmins[2], prefs.absmaxs[2]) self.zmax.setSingleStep(prefs.steps[2]) self.zmax.setDecimals(max(2, int(1 - np.log10(prefs.steps[2])))) self.zmax.setValue(prefs.maxs[2]) stepn = int( (prefs.maxs[prefs.i1] - prefs.mins[prefs.i1]) / prefs.steps[prefs.i1] ) if stepn == 0: stepn = 1 stepw = (prefs.maxs[prefs.i1] - prefs.mins[prefs.i1]) / stepn self.stepn = QSpinBox(self) self.stepn.setMinimum(1) self.stepn.setMaximum(stepn) self.stepn.setValue(stepn) self.stepw = QLineEdit(self) self.stepw.setText("%f" % (stepw)) self.OK = QPushButton(self) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setText("Cancel") self.layout.addWidget(self.lab0, 0, 0) self.layout.addWidget(self.lab1, 0, 1) self.layout.addWidget(self.lab2, 0, 2) self.layout.addWidget(self.lab3, 0, 3) self.layout.addWidget(self.lab4, 0, 4) self.layout.addWidget(self.lab5, 0, 5) self.layout.addWidget(self.xlab, 1, 0) self.layout.addWidget(self.xbox1, 1, 1) self.layout.addWidget(self.xbox2, 1, 2) self.layout.addWidget(self.xbox3, 1, 3) self.layout.addWidget(self.xmin, 1, 4) self.layout.addWidget(self.xmax, 1, 5) self.layout.addWidget(self.ylab, 2, 0) self.layout.addWidget(self.ybox1, 2, 1) self.layout.addWidget(self.ybox2, 2, 2) self.layout.addWidget(self.ybox3, 2, 3) self.layout.addWidget(self.ymin, 2, 4) self.layout.addWidget(self.ymax, 2, 5) self.layout.addWidget(self.zlab, 3, 0) self.layout.addWidget(self.zbox1, 3, 1) self.layout.addWidget(self.zbox2, 3, 2) self.layout.addWidget(self.zbox3, 3, 3) self.layout.addWidget(self.zmin, 3, 4) self.layout.addWidget(self.zmax, 3, 5) self.layout.addWidget(self.lab6, 4, 0) self.layout.addWidget(self.stepn, 4, 1) self.layout.addWidget(self.lab7, 4, 2) self.layout.addWidget(self.stepw, 4, 3) self.layout.addWidget(self.OK, 4, 4) self.layout.addWidget(self.Cancel, 4, 5) for i in range(1, 4): self.layout.setColumnMinimumWidth(i, 80) for i in range(6): self.layout.setColumnStretch(i, 1) QObject.connect(self.Cancel, SIGNAL("clicked()"), self.closewin) QObject.connect(self.OK, SIGNAL("clicked()"), self.appl) for button in [ self.xbox1, self.ybox1, self.zbox1, self.xbox2, self.ybox2, self.zbox2, self.xbox3, self.ybox3, self.zbox3, ]: QObject.connect(button, SIGNAL("clicked()"), self.validate) for button in [ self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax, ]: QObject.connect(button, SIGNAL("valueChanged(double)"), self.round_values) QObject.connect( self.stepn, SIGNAL("valueChanged(int)"), self.compute_step_width ) QObject.connect( self.stepw, SIGNAL("textEdited(QString)"), self.compute_step_number ) # only when user change the text! QObject.connect(self.Cancel, SIGNAL("clicked()"), self.closewin) self.exec_() def round_values(self, x): i1 = round((self.xmin.value() - self.prefs.absmins[0]) / self.prefs.steps[0]) i2 = round((self.xmax.value() - self.prefs.absmins[0]) / self.prefs.steps[0]) self.xmin.setValue(self.prefs.absmins[0] + i1 * self.prefs.steps[0]) self.xmax.setValue(self.prefs.absmins[0] + i2 * self.prefs.steps[0]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 i1 = round((self.ymin.value() - self.prefs.absmins[1]) / self.prefs.steps[1]) i2 = round((self.ymax.value() - self.prefs.absmins[1]) / self.prefs.steps[1]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 self.ymin.setValue(self.prefs.absmins[1] + i1 * self.prefs.steps[1]) self.ymax.setValue(self.prefs.absmins[1] + i2 * self.prefs.steps[1]) i1 = round((self.zmin.value() - self.prefs.absmins[2]) / self.prefs.steps[2]) i2 = round((self.zmax.value() - self.prefs.absmins[2]) / self.prefs.steps[2]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 self.zmin.setValue(self.prefs.absmins[2] + i1 * self.prefs.steps[2]) self.zmax.setValue(self.prefs.absmins[2] + i2 * self.prefs.steps[2]) def compute_step_number(self, text): i1 = self.group1.checkedId() - 1 try: mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepw = abs(float(self.stepw.text())) stepn = int((maxs[i1] - mins[i1]) / stepw) stepw = (maxs[i1] - mins[i1]) / stepn self.stepn.setValue(stepn) self.stepw.setText("%f" % (stepw)) except Exception: print("problem in compute_step_number") pass def compute_step_width(self, ii): i1 = self.group1.checkedId() - 1 try: mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepn = self.stepn.value() stepw = (maxs[i1] - mins[i1]) / stepn self.stepw.setText("%f" % (stepw)) except Exception: QMessageBox.about(self, "Error", "Input can only be a number") return def validate(self): i1 = self.group1.checkedId() - 1 i2 = self.group2.checkedId() - 1 if i2 == i1: i2 = (i1 + 1) % 3 self.group2.button(i2 + 1).setChecked(True) i3 = 3 - (i1 + i2) self.group3.button(i3 + 1).setChecked(True) mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepn = int((maxs[i1] - mins[i1]) / self.prefs.steps[i1]) if stepn == 0: stepn = 1 stepw = (maxs[i1] - mins[i1]) / stepn self.stepn.setMaximum(stepn) self.stepn.setValue(stepn) self.stepw.setText("%f" % (stepw)) def appl(self): self.prefs.i1 = self.group1.checkedId() - 1 self.prefs.i2 = self.group2.checkedId() - 1 self.prefs.i3 = self.group3.checkedId() - 1 try: self.prefs.mins[0] = self.xmin.value() self.prefs.mins[1] = self.ymin.value() self.prefs.mins[2] = self.zmin.value() self.prefs.maxs[0] = self.xmax.value() self.prefs.maxs[1] = self.ymax.value() self.prefs.maxs[2] = self.zmax.value() self.prefs.stepn = self.stepn.value() self.prefs.stepw = ( self.prefs.maxs[self.prefs.i1] - self.prefs.mins[self.prefs.i1] ) / self.prefs.stepn except Exception: QMessageBox.about(self, "Error", "Input can only be a number") return if ( self.prefs.mins[0] >= self.prefs.maxs[0] or self.prefs.mins[1] >= self.prefs.maxs[1] or self.prefs.mins[2] >= self.prefs.maxs[2] ): QMessageBox.about( self, "Error", "Minimum values must be lower than maximum ones" ) return self.close() self.prefs.do_it = True def closewin(self): self.close() self.prefs.do_it = False class Set2DSliceWindow(QDialog): # definit une fenetre pour rentrer les parametres de dialogue de construction d'une serie de slices def __init__(self, prefs): self.prefs = prefs super( Set2DSliceWindow, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.setWindowTitle("Parameters for slice generation") self.setFixedSize(QSize(450, 160)) self.layout = QGridLayout() self.setLayout(self.layout) self.lab0 = QLabel("Direction", self) self.lab1 = QLabel("Slice", self) self.lab4 = QLabel("Min (incl.)", self) self.lab5 = QLabel("Max (excl.)", self) self.xlab = QLabel(prefs.labels[0], self) self.ylab = QLabel(prefs.labels[1], self) self.zlab = QLabel(prefs.labels[2], self) self.lab6 = QLabel("Steps", self) self.lab7 = QLabel("Width", self) self.xbox1 = QCheckBox(self) self.ybox1 = QCheckBox(self) self.zbox1 = QCheckBox(self) self.group1 = QButtonGroup(self) self.group1.addButton(self.xbox1, 1) self.group1.addButton(self.ybox1, 2) self.group1.addButton(self.zbox1, 3) self.group1.button(prefs.i1 + 1).setChecked(True) self.xmin = QDoubleSpinBox(self) self.xmin.setRange(prefs.absmins[0], prefs.absmaxs[0]) self.xmin.setSingleStep(prefs.steps[0]) self.xmin.setDecimals(max(2, int(1 - np.log10(prefs.steps[0])))) self.xmin.setValue(prefs.mins[0]) self.xmax = QDoubleSpinBox(self) self.xmax.setRange(prefs.absmins[0], prefs.absmaxs[0]) self.xmax.setSingleStep(prefs.steps[0]) self.xmax.setDecimals(max(2, int(1 - np.log10(prefs.steps[0])))) self.xmax.setValue(prefs.maxs[0]) self.ymin = QDoubleSpinBox(self) self.ymin.setRange(prefs.absmins[1], prefs.absmaxs[1]) self.ymin.setSingleStep(prefs.steps[1]) self.ymin.setDecimals(max(2, int(1 - np.log10(prefs.steps[1])))) self.ymin.setValue(prefs.mins[1]) self.ymax = QDoubleSpinBox(self) self.ymax.setRange(prefs.absmins[1], prefs.absmaxs[1]) self.ymax.setSingleStep(prefs.steps[1]) self.ymax.setDecimals(max(2, int(1 - np.log10(prefs.steps[1])))) self.ymax.setValue(prefs.maxs[1]) self.zmin = QDoubleSpinBox(self) self.zmin.setRange(prefs.absmins[2], prefs.absmaxs[2]) self.zmin.setSingleStep(prefs.steps[2]) self.zmin.setDecimals(max(2, int(1 - np.log10(prefs.steps[2])))) self.zmin.setValue(prefs.mins[2]) self.zmax = QDoubleSpinBox(self) self.zmax.setRange(prefs.absmins[2], prefs.absmaxs[2]) self.zmax.setSingleStep(prefs.steps[2]) self.zmax.setDecimals(max(2, int(1 - np.log10(prefs.steps[2])))) self.zmax.setValue(prefs.maxs[2]) stepn = int( (prefs.maxs[prefs.i1] - prefs.mins[prefs.i1]) / prefs.steps[prefs.i1] ) if stepn == 0: stepn = 1 stepw = (prefs.maxs[prefs.i1] - prefs.mins[prefs.i1]) / stepn self.stepn = QSpinBox(self) self.stepn.setMinimum(1) self.stepn.setMaximum(stepn) self.stepn.setValue(stepn) self.stepw = QLineEdit(self) self.stepw.setText("%f" % (stepw)) self.OK = QPushButton(self) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setText("Cancel") self.layout.addWidget(self.lab0, 0, 0) self.layout.addWidget(self.lab1, 0, 1) self.layout.addWidget(self.lab4, 0, 4) self.layout.addWidget(self.lab5, 0, 5) self.layout.addWidget(self.xlab, 1, 0) self.layout.addWidget(self.xbox1, 1, 1) self.layout.addWidget(self.xmin, 1, 4) self.layout.addWidget(self.xmax, 1, 5) self.layout.addWidget(self.ylab, 2, 0) self.layout.addWidget(self.ybox1, 2, 1) self.layout.addWidget(self.ymin, 2, 4) self.layout.addWidget(self.ymax, 2, 5) self.layout.addWidget(self.zlab, 3, 0) self.layout.addWidget(self.zbox1, 3, 1) self.layout.addWidget(self.zmin, 3, 4) self.layout.addWidget(self.zmax, 3, 5) self.layout.addWidget(self.lab6, 4, 0) self.layout.addWidget(self.stepn, 4, 1) self.layout.addWidget(self.lab7, 4, 2) self.layout.addWidget(self.stepw, 4, 3) self.layout.addWidget(self.OK, 4, 4) self.layout.addWidget(self.Cancel, 4, 5) for i in range(1, 4): self.layout.setColumnMinimumWidth(i, 80) for i in range(6): self.layout.setColumnStretch(i, 1) QObject.connect(self.Cancel, SIGNAL("clicked()"), self.closewin) QObject.connect(self.OK, SIGNAL("clicked()"), self.appl) for button in [self.xbox1, self.ybox1, self.zbox1]: QObject.connect(button, SIGNAL("clicked()"), self.validate) for button in [ self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax, ]: QObject.connect(button, SIGNAL("valueChanged(double)"), self.round_values) QObject.connect( self.stepn, SIGNAL("valueChanged(int)"), self.compute_step_width ) QObject.connect( self.stepw, SIGNAL("textEdited(QString)"), self.compute_step_number ) # only when user change the text! QObject.connect(self.Cancel, SIGNAL("clicked()"), self.closewin) self.exec_() def round_values(self, x): i1 = round((self.xmin.value() - self.prefs.absmins[0]) / self.prefs.steps[0]) i2 = round((self.xmax.value() - self.prefs.absmins[0]) / self.prefs.steps[0]) self.xmin.setValue(self.prefs.absmins[0] + i1 * self.prefs.steps[0]) self.xmax.setValue(self.prefs.absmins[0] + i2 * self.prefs.steps[0]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 i1 = round((self.ymin.value() - self.prefs.absmins[1]) / self.prefs.steps[1]) i2 = round((self.ymax.value() - self.prefs.absmins[1]) / self.prefs.steps[1]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 self.ymin.setValue(self.prefs.absmins[1] + i1 * self.prefs.steps[1]) self.ymax.setValue(self.prefs.absmins[1] + i2 * self.prefs.steps[1]) i1 = round((self.zmin.value() - self.prefs.absmins[2]) / self.prefs.steps[2]) i2 = round((self.zmax.value() - self.prefs.absmins[2]) / self.prefs.steps[2]) if i1 >= i2: if i2 == 0: i1 = 0 i2 = 1 else: i1 = i2 - 1 self.zmin.setValue(self.prefs.absmins[2] + i1 * self.prefs.steps[2]) self.zmax.setValue(self.prefs.absmins[2] + i2 * self.prefs.steps[2]) def compute_step_number(self, text): i1 = self.group1.checkedId() - 1 try: mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepw = abs(float(self.stepw.text())) stepn = int((maxs[i1] - mins[i1]) / stepw) stepw = (maxs[i1] - mins[i1]) / stepn self.stepn.setValue(stepn) self.stepw.setText("%f" % (stepw)) except Exception: print("problem in compute_step_number") pass def compute_step_width(self, ii): i1 = self.group1.checkedId() - 1 try: mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepn = self.stepn.value() stepw = (maxs[i1] - mins[i1]) / stepn self.stepw.setText("%f" % (stepw)) except Exception: QMessageBox.about(self, "Error", "Input can only be a number") return def validate(self): i1 = self.group1.checkedId() - 1 mins = [self.xmin.value(), self.ymin.value(), self.zmin.value()] maxs = [self.xmax.value(), self.ymax.value(), self.zmax.value()] stepn = int((maxs[i1] - mins[i1]) / self.prefs.steps[i1]) if stepn == 0: stepn = 1 stepw = (maxs[i1] - mins[i1]) / stepn self.stepn.setMaximum(stepn) self.stepn.setValue(stepn) self.stepw.setText("%f" % (stepw)) def appl(self): self.prefs.i1 = self.group1.checkedId() - 1 try: self.prefs.mins[0] = self.xmin.value() self.prefs.mins[1] = self.ymin.value() self.prefs.mins[2] = self.zmin.value() self.prefs.maxs[0] = self.xmax.value() self.prefs.maxs[1] = self.ymax.value() self.prefs.maxs[2] = self.zmax.value() self.prefs.stepn = self.stepn.value() self.prefs.stepw = ( self.prefs.maxs[self.prefs.i1] - self.prefs.mins[self.prefs.i1] ) / self.prefs.stepn except Exception: QMessageBox.about(self, "Error", "Input can only be a number") return if ( self.prefs.mins[0] >= self.prefs.maxs[0] or self.prefs.mins[1] >= self.prefs.maxs[1] or self.prefs.mins[2] >= self.prefs.maxs[2] ): QMessageBox.about( self, "Error", "Minimum values must be lower than maximum ones" ) return self.close() self.prefs.do_it = True def closewin(self): self.close() self.prefs.do_it = False class SetSliceWindow2(QDialog): # definit une fenetre pour rentrer les parametres de dialogue de construction de slice le long d'une rod def __init__(self, prefs): self.prefs = prefs super( SetSliceWindow2, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.setWindowTitle("Parameters for slice generation") # self.setFixedSize(QSize(450, 160)) self.layout = QGridLayout() self.setLayout(self.layout) self.lab01 = QLabel("Direction", self) self.lab02 = QLabel("Range/width", self) self.lab03 = QLabel("Start", self) self.lab04 = QLabel("Stop", self) self.lab05 = QLabel("Steps", self) self.lab10 = QLabel("Slice", self) self.lab20 = QLabel("Scan", self) self.lab30 = QLabel("Raw sum", self) self.lab41 = QLabel(prefs.labels[0], self) self.lab42 = QLabel(prefs.labels[1], self) self.lab43 = QLabel(prefs.labels[2], self) self.lab50 = QLabel("Pos. 1", self) self.lab60 = QLabel("Pos. 2", self) self.combo11 = QComboBox(self) self.combo21 = QComboBox(self) self.combo31 = QComboBox(self) for combo in [self.combo11, self.combo21, self.combo31]: combo.addItem(prefs.labels[0]) combo.addItem(prefs.labels[1]) combo.addItem(prefs.labels[2]) self.box12 = QLineEdit(self) self.box22 = QDoubleSpinBox(self) self.box32 = QDoubleSpinBox(self) self.box13 = QDoubleSpinBox(self) # start self.box14 = QDoubleSpinBox(self) # stop self.box15 = QSpinBox(self) # steps self.box15.setMinimum(1) self.entry51 = QLineEdit(self) self.entry52 = QLineEdit(self) self.entry53 = QLineEdit(self) self.entry61 = QLineEdit(self) self.entry62 = QLineEdit(self) self.entry63 = QLineEdit(self) self.OK = QPushButton(self) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setText("Cancel") self.layout.addWidget(self.lab01, 0, 1) self.layout.addWidget(self.lab02, 0, 2) self.layout.addWidget(self.lab03, 0, 3) self.layout.addWidget(self.lab04, 0, 4) self.layout.addWidget(self.lab05, 0, 5) self.layout.addWidget(self.lab10, 1, 0) self.layout.addWidget(self.lab20, 2, 0) self.layout.addWidget(self.lab30, 3, 0) self.layout.addWidget(self.lab41, 4, 1) self.layout.addWidget(self.lab42, 4, 2) self.layout.addWidget(self.lab43, 4, 3) self.layout.addWidget(self.lab50, 5, 0) self.layout.addWidget(self.lab60, 6, 0) self.layout.addWidget(self.combo11, 1, 1) self.layout.addWidget(self.combo21, 2, 1) self.layout.addWidget(self.combo31, 3, 1) self.layout.addWidget(self.box12, 1, 2) self.layout.addWidget(self.box22, 2, 2) self.layout.addWidget(self.box32, 3, 2) self.layout.addWidget(self.box13, 1, 3) self.layout.addWidget(self.box14, 1, 4) self.layout.addWidget(self.box15, 1, 5) self.layout.addWidget(self.entry51, 5, 1) self.layout.addWidget(self.entry52, 5, 2) self.layout.addWidget(self.entry53, 5, 3) self.layout.addWidget(self.entry61, 6, 1) self.layout.addWidget(self.entry62, 6, 2) self.layout.addWidget(self.entry63, 6, 3) self.layout.addWidget(self.OK, 7, 0) self.layout.addWidget(self.Cancel, 7, 1) self.combo11.setCurrentIndex(prefs.i2) self.combo21.setCurrentIndex(prefs.i1) self.combo31.setCurrentIndex(prefs.i0) self.set_slice_direction(prefs.i2) self.set_scan_direction(prefs.i1) self.set_raw_direction(prefs.i0) self.entry51.setText("%f" % ((self.prefs.mins[0] + self.prefs.maxs[0]) / 2.0)) self.entry61.setText("%f" % ((self.prefs.mins[0] + self.prefs.maxs[0]) / 2.0)) self.entry52.setText("%f" % ((self.prefs.mins[1] + self.prefs.maxs[1]) / 2.0)) self.entry62.setText("%f" % ((self.prefs.mins[1] + self.prefs.maxs[1]) / 2.0)) self.entry53.setText("%f" % (self.prefs.mins[2])) self.entry63.setText("%f" % (self.prefs.maxs[2])) for i in range(1, 4): self.layout.setColumnMinimumWidth(i, 80) for i in range(6): self.layout.setColumnStretch(i, 1) QObject.connect(self.Cancel, SIGNAL("clicked()"), self.closewin) QObject.connect(self.OK, SIGNAL("clicked()"), self.appl) QObject.connect( self.combo11, SIGNAL("activated(int)"), self.set_slice_direction ) QObject.connect(self.combo21, SIGNAL("activated(int)"), self.set_scan_direction) QObject.connect(self.combo31, SIGNAL("activated(int)"), self.set_raw_direction) for box in [self.box13, self.box14]: QObject.connect(box, SIGNAL("valueChanged(double)"), self.round_values) QObject.connect( self.box12, SIGNAL("textEdited(QString)"), self.set_steps ) # only when user change the text! QObject.connect(self.box15, SIGNAL("valueChanged(int)"), self.set_width) self.exec_() def set_steps(self, x): i2 = self.combo11.currentIndex() width = float(self.box12.text()) if width < self.prefs.steps[i2]: width = self.prefs.steps[i2] vmin = self.box13.value() vmax = self.box14.value() step = int((vmax - vmin) / width) if step < 0: step = 1 width = (vmax - vmin) / step self.box15.setValue(step) self.box12.setText("%f" % width) def set_width(self, x): i2 = self.combo11.currentIndex() vmin = self.box13.value() vmax = self.box14.value() step = self.box15.value() width = (vmax - vmin) / step if width < self.prefs.steps[i2]: step = int((vmax - vmin) / self.prefs.steps[i2]) width = (vmax - vmin) / step self.box15.setValue(step) self.box15.setValue(step) self.box12.setText("%f" % width) def round_values(self, x): i2 = self.combo11.currentIndex() x1 = (self.box13.value() - self.prefs.absmins[i2]) / self.prefs.steps[i2] x2 = (self.box14.value() - self.prefs.absmins[i2]) / self.prefs.steps[i2] j1 = round(x1) j2 = round(x2) if j1 >= j2: if j2 == 0: j1 = 0 j2 = 1 else: j1 = j2 - 1 vmin = self.prefs.absmins[i2] + j1 * self.prefs.steps[i2] vmax = self.prefs.absmins[i2] + j2 * self.prefs.steps[i2] self.box13.setValue(vmin) self.box14.setValue(vmax) width = (vmax - vmin) / self.box15.value() if width < self.prefs.steps[i2]: step = int((vmax - vmin) / self.prefs.steps[i2]) width = (vmax - vmin) / step self.box15.setValue(step) self.box12.setText("%f" % width) def set_slice_direction(self, i2): # called when a combobox has been changed stepmax = int( (self.prefs.absmaxs[i2] - self.prefs.absmins[i2]) / self.prefs.steps[i2] ) self.box15.setMaximum(stepmax) step = int( (self.prefs.absmaxs[i2] - self.prefs.absmins[i2]) / self.prefs.steps[i2] ) self.box15.setValue(step) self.box13.setRange(self.prefs.absmins[i2], self.prefs.absmaxs[i2]) self.box13.setValue(self.prefs.mins[i2]) self.box13.setSingleStep(self.prefs.steps[i2]) self.box13.setDecimals(max(2, int(1 - np.log10(self.prefs.steps[i2])))) self.box14.setRange(self.prefs.absmins[i2], self.prefs.absmaxs[i2]) self.box14.setValue(self.prefs.maxs[i2]) self.box14.setSingleStep(self.prefs.steps[i2]) self.box14.setDecimals(max(2, int(1 - np.log10(self.prefs.steps[i2])))) self.box12.setText("%f" % self.prefs.steps[i2]) def set_scan_direction(self, i1): self.box22.setRange(0, self.prefs.absmaxs[i1] - self.prefs.absmins[i1]) self.box22.setSingleStep(self.prefs.steps[i1]) self.box22.setDecimals(max(2, int(1 - np.log10(self.prefs.steps[i1])))) self.box22.setValue(self.prefs.maxs[i1] - self.prefs.mins[i1]) def set_raw_direction(self, i0): self.box32.setRange(0, self.prefs.absmaxs[i0] - self.prefs.absmins[i0]) self.box32.setSingleStep(self.prefs.steps[i0]) self.box32.setDecimals(max(2, int(1 - np.log10(self.prefs.steps[i0])))) self.box32.setValue(self.prefs.maxs[i0] - self.prefs.mins[i0]) def appl(self): self.prefs.i2 = self.combo11.currentIndex() # slice integration self.prefs.i1 = self.combo21.currentIndex() # scan direction self.prefs.i0 = self.combo31.currentIndex() # raw sum direction try: self.prefs.ranges[2] = float(self.box12.text()) self.prefs.ranges[1] = self.box22.value() self.prefs.ranges[0] = self.box32.value() self.prefs.stepn = self.box15.value() self.prefs.mins[2] = self.box13.value() self.prefs.maxs[2] = self.box14.value() self.prefs.pos1[0] = float(self.entry51.text()) self.prefs.pos1[1] = float(self.entry52.text()) self.prefs.pos1[2] = float(self.entry53.text()) self.prefs.pos2[0] = float(self.entry61.text()) self.prefs.pos2[1] = float(self.entry62.text()) self.prefs.pos2[2] = float(self.entry63.text()) except Exception: QMessageBox.about(self, "Error", "Input can only be a number") return if self.prefs.mins[2] >= self.prefs.maxs[2]: QMessageBox.about( self, "Error", "Minimum values must be lower than maximum ones" ) return self.close() print(self.prefs.stepn) self.prefs.do_it = True def closewin(self): self.close() self.prefs.do_it = False class FitTool(CommandTool): def __init__( self, manager, title=None, icon=None, tip=None, toolbar_id=DefaultToolbarID ): if title == None: title = "Fit curve" if icon == None: icon = get_icon("curve.png") super(FitTool, self).__init__(manager, title, icon, toolbar_id=toolbar_id) def activate_command(self, plot, checked): """Activate tool""" self.emit(SIG_VALIDATE_TOOL) class RunTool(CommandTool): def __init__( self, manager, title=None, icon=None, tip=None, toolbar_id=DefaultToolbarID ): if title == None: title = "Apply" if icon == None: icon = get_icon("apply.png") super(RunTool, self).__init__(manager, title, icon, toolbar_id=toolbar_id) def setup_context_menu(self, menu, plot): pass def activate_command(self, plot, checked): """Activate tool""" self.emit(SIG_VALIDATE_TOOL) # in this module we define a widget for showing the progression of calculations class ProgressBar(QWidget): def __init__(self, title): QWidget.__init__(self) self.setWindowTitle(title) self.setFixedSize(QSize(200, 80)) self.progressbar = QProgressBar(self) self.progressbar.setGeometry(10, 10, 180, 30) self.cancelbtn = QPushButton("Cancel", self) self.cancelbtn.setGeometry(10, 40, 100, 30) QObject.connect(self.cancelbtn, SIGNAL("clicked()"), self.cancelwin) self.stop = False def cancelwin(self): self.stop = True def update_progress(self, x): # print self.progressbar.value() self.progressbar.setValue(x * 100) qApp.processEvents() class IntSpinSliderBox(QWidget): # a convenient widget that combines a slider and an integer spinbox def __init__(self, parent=None): QWidget.__init__(self, parent=parent) self.label = QLabel(self) self.slider = QSlider(self) self.slider.setOrientation(0x1) self.spinbox = QSpinBox(self) self.setRange(0, 100) self.setSingleStep(1) hBox = QHBoxLayout(self) hBox.addWidget(self.label) hBox.addWidget(self.slider) hBox.addWidget(self.spinbox) self.connect(self.spinbox, SIG_INT_VALUE_CHANGED, self.update_from_spinbox) self.connect(self.slider, SIG_INT_VALUE_CHANGED, self.update_from_slider) def setText(self, text): self.label.setText(text) def setRange(self, _min, _max): self.spinbox.setRange(_min, _max) self.slider.setMinimum(_min) self.slider.setMaximum(_max) def setSingleStep(self, step): self.spinbox.setSingleStep(step) self.slider.setTickInterval(step) def update_from_spinbox(self, i): self.slider.blockSignals(True) self.slider.setValue(i) self.slider.blockSignals(False) self.emit(SIG_INT_VALUE_CHANGED, i) def update_from_slider(self, i): self.spinbox.blockSignals(True) self.spinbox.setValue(i) self.spinbox.blockSignals(False) self.emit(SIG_INT_VALUE_CHANGED, i) def setValue(self, i): self.slider.blockSignals(True) self.spinbox.blockSignals(True) self.spinbox.setValue(i) self.slider.setValue(i) self.slider.blockSignals(False) self.slider.blockSignals(False) if i != self.value(): self.emit(SIG_INT_VALUE_CHANGED, i) def value(self): return self.spinbox.value() class DoubleSpinSliderBox(QWidget): # a convenient widget that combines a slider and a double spinbox def __init__(self, parent=None): QWidget.__init__(self, parent=parent) self.label = QLabel(self) self.slider = QSlider(self) self.slider.setOrientation(0x1) self.slider.setMinimum(0) self.slider.setMaximum(100) self.spinbox = QDoubleSpinBox(self) self.setRange(0.0, 100.0) self.setSingleStep(1.0) self.setSingleStep(1.0) hBox = QHBoxLayout(self) hBox.addWidget(self.label) hBox.addWidget(self.slider) hBox.addWidget(self.spinbox) self.connect(self.spinbox, SIG_DOUBLE_VALUE_CHANGED, self.update_from_spinbox) self.connect(self.slider, SIG_INT_VALUE_CHANGED, self.update_from_slider) self.connect( self.slider, SIG_SLIDER_PRESSED, lambda: self.emit(SIG_SLIDER_PRESSED) ) self.connect( self.slider, SIG_SLIDER_RELEASED, lambda: self.emit(SIG_SLIDER_RELEASED) ) def setText(self, text): self.label.setText(text) def setRange(self, _min, _max): if _max > _min: self.spinbox.setRange(_min, _max) self._min = _min self._max = _max self.scale = 100.0 / (self._max - self._min) def setSingleStep(self, step): self.spinbox.setSingleStep(step) def setDecimals(self, i): self.spinbox.setDecimals(i) def update_from_spinbox(self, x): self.slider.blockSignals(True) i = int((x - self._min) * self.scale) self.slider.setValue(i) self.slider.blockSignals(False) self.emit(SIG_SLIDER_PRESSED) self.emit(SIG_DOUBLE_VALUE_CHANGED, x) self.emit(SIG_SLIDER_RELEASED) def update_from_slider(self, i): self.spinbox.blockSignals(True) x = float(i) / self.scale + self._min self.spinbox.setValue(x) self.spinbox.blockSignals(False) self.emit(SIG_DOUBLE_VALUE_CHANGED, x) def setValue(self, x): self.spinbox.blockSignals(True) self.slider.blockSignals(True) self.spinbox.setValue(x) i = int((x - self._min) * self.scale) self.slider.setValue(i) self.slider.blockSignals(False) self.spinbox.blockSignals(False) if x != self.value(): self.emit(SIG_SLIDER_PRESSED) self.emit(SIG_DOUBLE_VALUE_CHANGED, x) self.emit(SIG_SLIDER_RELEASED) def value(self): return self.spinbox.value() def couple_doublespinsliders(spsl1, spsl2): QObject.connect( spsl1, SIG_DOUBLE_VALUE_CHANGED, lambda x: spsl2.setValue(max(x, spsl2.value())) ) QObject.connect( spsl2, SIG_DOUBLE_VALUE_CHANGED, lambda x: spsl1.setValue(min(x, spsl1.value())) ) class ImageMaskingWidget(PanelWidget): __implements__ = (IPanel,) PANEL_ID = ID_IMAGEMASKING PANEL_TITLE = "image masking" PANEL_ICON = None # string def __init__(self, parent=None): self._mask_shapes = {} self._mask_already_restored = {} super(ImageMaskingWidget, self).__init__(parent) self.setMinimumSize(QSize(250, 500)) self.masked_image = None # associated masked image item self.manager = None # manager for the associated image plot self.toolbar = toolbar = QToolBar(self) toolbar.setOrientation(Qt.Horizontal) self.vBox = QVBoxLayout(self) # central widget gbox1 = QGroupBox(self) gbox1.setTitle(_("Mask image with shape tools")) self.vBox1 = QVBoxLayout(gbox1) self.insidebutton = QCheckBox(self) self.insidebutton.setText(_("Inside")) self.outsidebutton = QCheckBox(self) self.outsidebutton.setText(_("Outside")) self.group1 = QButtonGroup(self) self.group1.addButton(self.insidebutton) self.group1.addButton(self.outsidebutton) self.insidebutton.setChecked(True) hBox1 = QHBoxLayout() hBox1.addWidget(self.insidebutton) hBox1.addWidget(self.outsidebutton) self.maskbutton = QCheckBox(self) self.maskbutton.setText(_("Mask")) self.unmaskbutton = QCheckBox(self) self.unmaskbutton.setText(_("Unmask")) self.group2 = QButtonGroup(self) self.group2.addButton(self.maskbutton) self.group2.addButton(self.unmaskbutton) self.maskbutton.setChecked(True) hBox2 = QHBoxLayout() hBox2.addWidget(self.maskbutton) hBox2.addWidget(self.unmaskbutton) self.brushsizelabel = QLabel(self) self.brushsizelabel.setText(_("Brush size")) self.hbrushsize = QSpinBox(self) self.hbrushsize.setRange(1, 100) self.vbrushsize = QSpinBox(self) self.vbrushsize.setRange(1, 100) hBox3 = QHBoxLayout() hBox3.addWidget(self.brushsizelabel) hBox3.addWidget(self.hbrushsize) hBox3.addWidget(self.vbrushsize) self.singleshapebutton = QCheckBox(self) self.singleshapebutton.setText(_("Single Shape")) self.singleshapebutton.setChecked(False) self.autoupdatebutton = QCheckBox(self) self.autoupdatebutton.setText(_("Auto update")) self.autoupdatebutton.setChecked(True) self.applymaskbutton = QPushButton(self) self.applymaskbutton.setText(_("Apply mask")) hBox4 = QHBoxLayout() hBox4.addWidget(self.autoupdatebutton) hBox4.addWidget(self.applymaskbutton) self.showshapesbutton = QCheckBox(self) self.showshapesbutton.setText(_("Show shapes")) self.removeshapesbutton = QPushButton(self) self.removeshapesbutton.setText(_("Remove shapes")) hBox5 = QHBoxLayout() hBox5.addWidget(self.showshapesbutton) hBox5.addWidget(self.removeshapesbutton) self.vBox1.addWidget(toolbar) self.vBox1.addLayout(hBox1) self.vBox1.addLayout(hBox2) self.vBox1.addLayout(hBox3) self.vBox1.addWidget(self.singleshapebutton) self.vBox1.addLayout(hBox4) self.vBox1.addLayout(hBox5) gbox2 = QGroupBox(self) gbox2.setTitle(_("Mask operations")) self.gr0 = QGridLayout(gbox2) self.showmaskbutton = QCheckBox(self) self.showmaskbutton.setText(_("Show mask")) self.clearmaskbutton = QPushButton(self) self.clearmaskbutton.setText(_("Clear mask")) self.invertmaskbutton = QPushButton(self) self.invertmaskbutton.setText(_("Invert mask")) self.dilatationbutton = QPushButton(self) self.dilatationbutton.setText(_("Dilatation")) self.erosionbutton = QPushButton(self) self.erosionbutton.setText(_("Erosion")) self.sizebox = QSpinBox(self) self.sizebox.setRange(1, 100) self.gr0.addWidget(self.invertmaskbutton, 0, 0) self.gr0.addWidget(self.clearmaskbutton, 1, 0) self.gr0.addWidget(self.showmaskbutton, 2, 0) self.gr0.addWidget(self.dilatationbutton, 0, 1) self.gr0.addWidget(self.erosionbutton, 1, 1) self.gr0.addWidget(self.sizebox, 2, 1) gbox3 = QGroupBox(self) gbox3.setTitle(_("Mask image from threshold")) self.vBox3 = QVBoxLayout(gbox3) # set VBox to central widget self.minspinslider = DoubleSpinSliderBox(self) self.minspinslider.setText("Min") self.minspinslider.setRange(0.0, 100.0) self.minspinslider.setDecimals(1) self.minspinslider.setSingleStep(0.1) self.minspinslider.setValue(0.0) self.maxspinslider = DoubleSpinSliderBox(self) self.maxspinslider.setText("Max") self.maxspinslider.setRange(0.0, 100.0) self.maxspinslider.setDecimals(1) self.maxspinslider.setSingleStep(0.1) self.maxspinslider.setValue(100.0) couple_doublespinsliders(self.minspinslider, self.maxspinslider) # self.vBox2.addLayout(hBox7) self.vBox3.addWidget(self.minspinslider) self.vBox3.addWidget(self.maxspinslider) self.vBox.addWidget(gbox1) self.vBox.addWidget(gbox2) self.vBox.addWidget(gbox3) def register_plot(self, baseplot): self._mask_shapes.setdefault(baseplot, []) self.connect(baseplot, SIG_ITEMS_CHANGED, self.items_changed) self.connect(baseplot, SIG_ITEM_SELECTION_CHANGED, self.item_selection_changed) def register_panel(self, manager): """Register panel to plot manager""" self.manager = manager default_toolbar = self.manager.get_default_toolbar() self.manager.add_toolbar(self.toolbar, "masking shapes") self.manager.set_default_toolbar(default_toolbar) for plot in manager.get_plots(): self.register_plot(plot) def configure_panel(self): """Configure panel""" self.ellipse_mask_tool = self.manager.add_tool( EllipseMaskTool, toolbar_id="masking shapes" ) self.rect_mask_tool = self.manager.add_tool( RectangleMaskTool, toolbar_id="masking shapes" ) self.rect_tool = self.manager.add_tool( RectangleTool, toolbar_id="masking shapes", handle_final_shape_cb=lambda shape: self.handle_shape(shape), title=_("Mask rectangular area"), icon="mask_rectangle_grey.png", ) self.ellipse_tool = self.manager.add_tool( CircleTool, toolbar_id="masking shapes", handle_final_shape_cb=lambda shape: self.handle_shape(shape), title=_("Mask circular area"), icon="mask_circle_grey.png", ) self.polygon_tool = self.manager.add_tool( FreeFormTool, toolbar_id="masking shapes", handle_final_shape_cb=lambda shape: self.handle_shape(shape), title=_("Mask polygonal area"), icon="mask_polygon_grey.png", ) self.run_tool = self.manager.add_tool( RunTool, title=_("Remove masked area"), toolbar_id="masking shapes" ) self.setup_actions() self.hbrushsize.setValue(1) self.vbrushsize.setValue(1) self.set_brush_size(1) self.showmaskbutton.setChecked(True) self.showshapesbutton.setChecked(True) def setup_actions(self): # QObject.connect(self.maskbutton, SIG_STATE_CHANGED, self.set_mode) QObject.connect(self.hbrushsize, SIG_INT_VALUE_CHANGED, self.set_brush_size) QObject.connect(self.vbrushsize, SIG_INT_VALUE_CHANGED, self.set_brush_size) QObject.connect(self.applymaskbutton, SIG_CLICKED, self.apply_mask) QObject.connect(self.showshapesbutton, SIG_STATE_CHANGED, self.show_shapes) QObject.connect(self.removeshapesbutton, SIG_CLICKED, self.remove_all_shapes) QObject.connect(self.clearmaskbutton, SIG_CLICKED, self.clear_mask) QObject.connect(self.showmaskbutton, SIG_STATE_CHANGED, self.show_mask) QObject.connect(self.invertmaskbutton, SIG_CLICKED, self.invert_mask) QObject.connect(self.dilatationbutton, SIG_CLICKED, self.mask_dilatation) QObject.connect(self.erosionbutton, SIG_CLICKED, self.mask_erosion) QObject.connect( self.minspinslider, SIG_DOUBLE_VALUE_CHANGED, self.update_threshold ) QObject.connect( self.maxspinslider, SIG_DOUBLE_VALUE_CHANGED, self.update_threshold ) def set_mode(self, i): self.ellipse_mask_tool.set_mode(self.maskbutton.isChecked()) self.rect_mask_tool.set_mode(self.maskbutton.isChecked()) def set_brush_size(self, i=1): # self.cmt.set_size(i) i = self.hbrushsize.value() j = self.vbrushsize.value() self.ellipse_mask_tool.set_pixel_size(i, j) self.rect_mask_tool.set_pixel_size(i, j) def update_threshold(self, x): plot = self.get_active_plot() if self.masked_image is None: return pmin = self.minspinslider.value() pmax = self.maxspinslider.value() vmin = np.percentile( self.masked_image.data[np.isfinite(self.masked_image.data)], pmin ) vmax = np.percentile( self.masked_image.data[np.isfinite(self.masked_image.data)], pmax ) self.masked_image.data.mask = np.ma.nomask np.ma.masked_less(self.masked_image.data, vmin, copy=False) np.ma.masked_greater(self.masked_image.data, vmax, copy=False) self.show_mask(True) plot.replot() def mask_dilatation(self): plot = self.get_active_plot() if self.masked_image is None: return radius = self.sizebox.value() L = np.arange(-radius, radius + 1) X, Y = np.meshgrid(L, L) struct = np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=np.bool) self.masked_image.data.mask = ( signal.fftconvolve(self.masked_image.data.mask, struct, "same") > 0.5 ) # much better than ndimage.binary_dilatation self.show_mask(True) plot.replot() def mask_erosion(self): plot = self.get_active_plot() if self.masked_image is None: return radius = self.sizebox.value() L = np.arange(-radius, radius + 1) X, Y = np.meshgrid(L, L) struct = np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=np.bool) self.masked_image.data.mask = ( signal.fftconvolve( np.logical_not(self.masked_image.data.mask), struct, "same" ) <= 0.5 ) # much better than ndimage.binary_dilatation self.show_mask(True) plot.replot() def get_active_plot(self): return self.manager.get_active_plot() def handle_shape(self, shape): shape.set_style("plot", "shape/mask") shape.shapeparam.label = "mask shape" shape.set_private(True) plot = self.manager.get_active_plot() plot.set_active_item(shape) if self.singleshapebutton.isChecked(): self.remove_shapes() if self.masked_image is not None: self.masked_image.unmask_all() self._mask_shapes[plot] += [ (shape, self.insidebutton.isChecked(), self.maskbutton.isChecked()) ] if self.autoupdatebutton.isChecked(): self.apply_shape_mask( shape, self.insidebutton.isChecked(), self.maskbutton.isChecked() ) def show_mask(self, state): if self.masked_image is not None: self.masked_image.set_mask_visible(state) def invert_mask(self): if self.masked_image is not None: self.masked_image.set_mask(np.logical_not(self.masked_image.get_mask())) self.masked_image.plot().replot() def apply_shape_mask(self, shape, inside, mask): if self.masked_image is None: return if isinstance(shape, RectangleShape): self.masked_image.align_rectangular_shape(shape) x0, y0, x1, y1 = shape.get_rect() if mask: self.masked_image.mask_rectangular_area(x0, y0, x1, y1, inside=inside) else: self.masked_image.unmask_rectangular_area(x0, y0, x1, y1, inside=inside) elif isinstance(shape, EllipseShape): x0, y0, x1, y1 = shape.get_rect() if mask: self.masked_image.mask_circular_area(x0, y0, x1, y1, inside=inside) else: self.masked_image.unmask_circular_area(x0, y0, x1, y1, inside=inside) elif isinstance(shape, PolygonShape): if mask: self.masked_image.mask_polygonal_area(shape.points, inside=inside) text = ( "adding masked polygonal area to" + self.masked_image.imageparam.label ) for pt in shape.points: text += "(%f,%f)," % (pt[0], pt[1]) text += " inside=" + str(inside) else: self.masked_image.unmask_polygonal_area(shape.points, inside=inside) text = ( "adding unmasked polygonal area to" + self.masked_image.imageparam.label ) for pt in shape.points: text += "(%f,%f)," % (pt[0], pt[1]) text += " inside=" + str(inside) def apply_mask(self): plot = self.get_active_plot() for shape, inside, mask in self._mask_shapes[plot]: self.apply_shape_mask(shape, inside, mask) self.show_mask(True) # self.masked_image.set_mask(mask) plot.replot() # self.emit(SIG_APPLIED_MASK_TOOL) def remove_all_shapes(self): message = _("Do you really want to remove all masking shapes?") plot = self.get_active_plot() answer = QMessageBox.warning( plot, _("Remove all masking shapes"), message, QMessageBox.Yes | QMessageBox.No, ) if answer == QMessageBox.Yes: self.remove_shapes() def remove_shapes(self): plot = self.get_active_plot() plot.del_items( [shape for shape, _inside, mask in self._mask_shapes[plot]] ) # remove shapes self._mask_shapes[plot] = [] plot.replot() def show_shapes(self, state): plot = self.get_active_plot() if plot is not None: for shape, _inside, mask in self._mask_shapes[plot]: shape.setVisible(state) plot.replot() def create_shapes_from_masked_areas(self): plot = self.get_active_plot() self._mask_shapes[plot] = [] for area in self.masked_image.get_masked_areas(): if area.geometry == "rectangular": shape = RectangleShape() shape.set_points(area.pts) self.masked_image.align_rectangular_shape(shape) elif area.geometry == "circular": shape = EllipseShape() shape.set_points(area.pts) elif area.geometry == "polygonal": shape = PolygonShape() shape.set_points(area.pts) shape.set_style("plot", "shape/custom_mask") shape.set_private(True) self._mask_shapes[plot] += [(shape, area.inside, area.mask)] plot.blockSignals(True) plot.add_item(shape) plot.blockSignals(False) def find_masked_image(self, plot): item = plot.get_active_item() if isinstance(item, MaskedImageNan): return item else: items = [ item for item in plot.get_items() if isinstance(item, MaskedImageNan) ] if items: return items[-1] def set_masked_image(self, plot): self.masked_image = self.find_masked_image(plot) if self.masked_image is not None and not self._mask_already_restored: self.create_shapes_from_masked_areas() self._mask_already_restored = True def items_changed(self, plot): self.set_masked_image(plot) self._mask_shapes[plot] = [ (shape, inside, mask) for shape, inside, mask in self._mask_shapes[plot] if shape.plot() is plot ] self.update_status(plot) def item_selection_changed(self, plot): self.set_masked_image(plot) self.update_status(plot) def reset_mask(self, plot): # remove shapes prior opening a masked image with masked areas self._mask_shapes[plot] = [] self._mask_already_restored = False def clear_mask(self): if self.masked_image is None: return message = _("Do you really want to clear the mask?") plot = self.get_active_plot() answer = QMessageBox.warning( plot, _("Clear mask"), message, QMessageBox.Yes | QMessageBox.No ) if answer == QMessageBox.Yes: self.masked_image.unmask_all() plot.replot() def update_status(self, plot): # self.action.setEnabled(self.masked_image is not None) pass def activate_command(self, plot, checked): """Activate tool""" pass class ProjectionWidget(PanelWidget): PANEL_ID = 999 def __init__(self, parent=None): super(ProjectionWidget, self).__init__(parent) self.manager = None # manager for the associated image plot self.local_manager = PlotManager(self) # local manager for the histogram plot self.setMinimumWidth(180) self.dockwidget = None VBox = QVBoxLayout() self.setLayout(VBox) self.zlog = QCheckBox("log(I)", self) self.integral = QCheckBox("integral", self) self.plane = QCheckBox( "plane selection", self ) # select a specific plane instead of a range self.bg = QCheckBox("subtract background", self) self.swap = QCheckBox("swap", self) self.autoscale = QCheckBox("autoscale", self) self.autoscale.setChecked(True) self.show_contributions = QCheckBox("show contributions", self) self.xplot = BinoPlot() self.yplot = BinoPlot() self.zplot = BinoPlot() self.local_manager.add_plot(self.xplot) self.local_manager.add_plot(self.yplot) self.local_manager.add_plot(self.zplot) self.xcurve = make.binocurve([0, 1], [0, 0]) self.xcurve.xlabel = "X" self.ycurve = make.binocurve([0, 1], [0, 0]) self.ycurve.xlabel = "Y" self.zcurve = make.binocurve([0, 1], [0, 0]) self.zcurve.xlabel = "Z" self.xrange = XRangeSelection2(0, 1) self.yrange = XRangeSelection2(0, 1) self.zrange = XRangeSelection2(0, 1) self.xbgrange1 = BgRangeSelection( 0, 1 ) # range selection for background subtraction self.ybgrange1 = BgRangeSelection(0, 1) self.zbgrange1 = BgRangeSelection(0, 1) self.xbgrange2 = BgRangeSelection( 0, 1 ) # range selection for background subtraction self.ybgrange2 = BgRangeSelection(0, 1) self.zbgrange2 = BgRangeSelection(0, 1) self.xbgrange1.setVisible(False) self.ybgrange1.setVisible(False) self.zbgrange1.setVisible(False) self.xbgrange2.setVisible(False) self.ybgrange2.setVisible(False) self.zbgrange2.setVisible(False) self.xmarker = Marker( constraint_cb=lambda x, y: self.xcurve.get_closest_x(x, y) ) self.xmarker.set_markerstyle("|") self.ymarker = Marker( constraint_cb=lambda x, y: self.ycurve.get_closest_x(x, y) ) self.ymarker.set_markerstyle("|") self.zmarker = Marker( constraint_cb=lambda x, y: self.zcurve.get_closest_x(x, y) ) self.zmarker.set_markerstyle("|") self.xmarker.setVisible(False) # default projection along h self.ymarker.setVisible(False) self.zmarker.setVisible(False) self.xplot.add_item(self.xbgrange1) self.xplot.add_item(self.xbgrange2) self.xplot.add_item(self.xrange) self.xplot.add_item(self.xcurve) self.xplot.add_item(self.xmarker) self.xplot.replot() self.yplot.add_item(self.ybgrange1) self.yplot.add_item(self.ybgrange2) self.yplot.add_item(self.yrange) self.yplot.add_item(self.ycurve) self.yplot.add_item(self.ymarker) self.yplot.replot() self.zplot.add_item(self.zbgrange1) self.zplot.add_item(self.zbgrange2) self.zplot.add_item(self.zrange) self.zplot.add_item(self.zcurve) self.zplot.add_item(self.zmarker) self.zplot.replot() self.xplot.set_active_item(self.xrange) self.yplot.set_active_item(self.yrange) self.zplot.set_active_item(self.zrange) HBox0a = QHBoxLayout() HBox0b = QHBoxLayout() HBox0c = QHBoxLayout() HBox1 = QHBoxLayout() HBox2 = QHBoxLayout() HBox3 = QHBoxLayout() self.projx = QCheckBox(self) self.projx.setText("X") self.projy = QCheckBox(self) self.projy.setText("Y") self.projz = QCheckBox(self) self.projz.setText("Z") self.projx.setChecked(True) self.axis = 0 self.projgroup = QButtonGroup(VBox) self.projgroup.setExclusive(True) self.projgroup.addButton(self.projx) self.projgroup.addButton(self.projy) self.projgroup.addButton(self.projz) HBox1.addWidget(self.projx) HBox2.addWidget(self.projy) HBox3.addWidget(self.projz) self.xmin = QLineEdit(self) self.ymin = QLineEdit(self) self.zmin = QLineEdit(self) HBox1.addWidget(self.xmin) HBox2.addWidget(self.ymin) HBox3.addWidget(self.zmin) self.xmax = QLineEdit(self) self.ymax = QLineEdit(self) self.zmax = QLineEdit(self) HBox1.addWidget(self.xmax) HBox2.addWidget(self.ymax) HBox3.addWidget(self.zmax) HBox0a.addWidget(self.zlog) HBox0a.addWidget(self.integral) HBox0a.addWidget(self.plane) HBox0b.addWidget(self.bg) HBox0c.addWidget(self.swap) HBox0c.addWidget(self.autoscale) HBox0c.addWidget(self.show_contributions) VBox.addLayout(HBox0a) VBox.addLayout(HBox0b) VBox.addLayout(HBox0c) VBox.addLayout(HBox1) VBox.addWidget(self.xplot) VBox.addLayout(HBox2) VBox.addWidget(self.yplot) VBox.addLayout(HBox3) VBox.addWidget(self.zplot) lman = self.local_manager lman.add_tool(SelectTool) lman.add_tool(BasePlotMenuTool, "item") lman.add_tool(BasePlotMenuTool, "axes") lman.add_tool(BasePlotMenuTool, "grid") lman.add_tool(AntiAliasingTool) lman.get_default_tool().activate() self.setup_connect() self.active_plot = self.xplot self.prefs = SlicePrefs() def keyPressEvent(self, event): if type(event) == QKeyEvent: # here accept the event and do something # print event.key() # print self.active_plot event.accept() else: event.ignore() def get_plot(self): return self.manager.get_active_plot() def register_panel(self, manager): self.manager = manager def configure_panel(self): pass def setup_connect(self): QObject.connect(self.xplot, SIG_RANGE_CHANGED, self.selection_changed) QObject.connect(self.yplot, SIG_RANGE_CHANGED, self.selection_changed) QObject.connect(self.zplot, SIG_RANGE_CHANGED, self.selection_changed) QObject.connect(self.xplot, SIG_MARKER_CHANGED, self.selection_changed) QObject.connect(self.yplot, SIG_MARKER_CHANGED, self.selection_changed) QObject.connect(self.zplot, SIG_MARKER_CHANGED, self.selection_changed) QObject.connect(self.xplot, SIG_ITEM_SELECTION_CHANGED, self.set_active_plot) QObject.connect(self.yplot, SIG_ITEM_SELECTION_CHANGED, self.set_active_plot) QObject.connect(self.zplot, SIG_ITEM_SELECTION_CHANGED, self.set_active_plot) QObject.connect(self.xplot, SIGNAL("Move(int)"), self.move_xcursor) QObject.connect(self.yplot, SIGNAL("Move(int)"), self.move_ycursor) QObject.connect(self.zplot, SIGNAL("Move(int)"), self.move_zcursor) QObject.connect(self.xmin, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.xmax, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.ymin, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.ymax, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.zmin, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.zmax, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.xmin, SIGNAL("returnPressed()"), self.value_changed) QObject.connect(self.zlog, SIGNAL("clicked()"), self.log_changed) QObject.connect(self.swap, SIGNAL("clicked()"), self.other_changed) QObject.connect( self.show_contributions, SIGNAL("clicked()"), self.other_changed ) QObject.connect(self.plane, SIGNAL("clicked()"), self.plane_changed) QObject.connect(self.bg, SIGNAL("clicked()"), self.bg_changed) QObject.connect(self.projx, SIGNAL("clicked()"), lambda: self.axis_changed(0)) QObject.connect(self.projy, SIGNAL("clicked()"), lambda: self.axis_changed(1)) QObject.connect(self.projz, SIGNAL("clicked()"), lambda: self.axis_changed(2)) def log_changed(self): if self.zlog.isChecked(): self.xplot.set_axis_scale("left", "log") self.yplot.set_axis_scale("left", "log") self.zplot.set_axis_scale("left", "log") else: self.xplot.set_axis_scale("left", "lin") self.yplot.set_axis_scale("left", "lin") self.zplot.set_axis_scale("left", "lin") self.xplot.replot() self.yplot.replot() self.zplot.replot() self.emit(SIGNAL("projection_changed()")) def update_bg_range(self): # we show background for the projected curve # we update position of the bg to be in adequation with the range selected self.xbgrange1.setVisible(False) self.ybgrange1.setVisible(False) self.zbgrange1.setVisible(False) self.xbgrange2.setVisible(False) self.ybgrange2.setVisible(False) self.zbgrange2.setVisible(False) dxs2 = self.spacings[self.axis] / 2.0 if self.axis == 0: bgrange1 = self.xbgrange1 bgrange2 = self.xbgrange2 if self.plane.isChecked(): xmarker = self.xmarker.xValue() xrmin, xrmax = xmarker - dxs2, xmarker + dxs2 else: xrmin, xrmax = self.xrange.get_range() xdata = self.xcurve.get_data()[0] elif self.axis == 1: bgrange1 = self.ybgrange1 bgrange2 = self.ybgrange2 if self.plane.isChecked(): xmarker = self.ymarker.xValue() xrmin, xrmax = xmarker - dxs2, xmarker + dxs2 else: xrmin, xrmax = self.yrange.get_range() xdata = self.ycurve.get_data()[0] else: bgrange1 = self.zbgrange1 bgrange2 = self.zbgrange2 if self.plane.isChecked(): xmarker = self.zmarker.xValue() xrmin, xrmax = xmarker - dxs2, xmarker + dxs2 else: xrmin, xrmax = self.zrange.get_range() xdata = self.zcurve.get_data()[0] bgrange1.setVisible(True) bgrange2.setVisible(True) if len(xdata) > 0: xmin = min(xdata) xmax = max(xdata) if ( xmin > xrmin ): # instead of starting from first point of the curve, we start from a lowest value xmin = xrmin if xmax < xrmax: xmax = xrmax bgrange1.set_range(xmin - dxs2, xrmin, dosignal=False) bgrange2.set_range(xmax + dxs2, xrmax, dosignal=False) else: # no data, we choose arbitrary 10% tail bgrange1.set_range(xrmin - 0.1 * (xrmax - xrmin), xrmin, dosignal=False) bgrange2.set_range(xrmax, xrmax + 0.1 * (xrmax - xrmin), dosignal=False) def bg_changed(self): if self.bg.isChecked(): # we show background for the projected curve self.update_bg_range() else: self.xbgrange1.setVisible(False) self.ybgrange1.setVisible(False) self.zbgrange1.setVisible(False) self.xbgrange2.setVisible(False) self.ybgrange2.setVisible(False) self.zbgrange2.setVisible(False) self.emit(SIGNAL("projection_changed()")) def other_changed(self, i=0): self.emit(SIGNAL("projection_changed()")) def set_active_plot(self, plot): self.active_plot = plot self.emit(SIGNAL("active_plot_changed()")) def selection_changed(self, plot): # a range selection has been modified xmin, xmax = self.xrange.get_range() ymin, ymax = self.yrange.get_range() zmin, zmax = self.zrange.get_range() self.selection_ranges = np.array([[xmin, xmax], [ymin, ymax], [zmin, zmax]]) self.set_values(self.selection_ranges) self.emit(SIGNAL("projection_changed()")) pass def value_changed(self): # a range value has been entered xmin = float(self.xmin.text()) xmax = float(self.xmax.text()) ymin = float(self.ymin.text()) ymax = float(self.ymax.text()) zmin = float(self.zmin.text()) zmax = float(self.zmax.text()) self.selection_ranges = np.array([[xmin, xmax], [ymin, ymax], [zmin, zmax]]) self.set_ranges(self.selection_ranges) self.emit(SIGNAL("projection_changed()")) pass def plane_changed(self): # change between range and plane selection modes if self.bg.isChecked(): self.update_bg_range() if self.plane.isChecked(): self.xrange.setVisible(False) self.yrange.setVisible(False) self.zrange.setVisible(False) self.set_marker_position() else: self.xmarker.setVisible(False) self.ymarker.setVisible(False) self.zmarker.setVisible(False) self.xrange.setVisible(True) self.yrange.setVisible(True) self.zrange.setVisible(True) self.xplot.replot() self.yplot.replot() self.zplot.replot() self.emit(SIGNAL("projection_changed()")) def move_xcursor(self, i): if self.plane.isChecked(): if self.axis == 0: # we move the cursor xdata, ydata = self.xcurve.get_data() if len(xdata) > 0: # put marker at the middle of the curve x, y = self.xmarker.get_pos() x, y = self.xcurve.get_closest_x(x + i * self.spacings[0], y) self.xmarker.setValue(x, y) self.emit(SIGNAL("projection_changed()")) else: # we move the range _min, _max = self.xrange.get_range() self.xrange.set_range( _min + i * self.spacings[0], _max + i * self.spacings[0] ) def move_ycursor(self, i): if self.plane.isChecked(): if self.axis == 1: # we move the cursor xdata, ydata = self.ycurve.get_data() if len(xdata) > 0: # put marker at the middle of the curve x, y = self.ymarker.get_pos() x, y = self.ycurve.get_closest_x(x + i * self.spacings[1], y) self.ymarker.setValue(x, y) self.emit(SIGNAL("projection_changed()")) else: # we move the range _min, _max = self.yrange.get_range() self.yrange.set_range( _min + i * self.spacings[1], _max + i * self.spacings[1] ) def move_zcursor(self, i): if self.plane.isChecked(): if self.axis == 2: # we move the cursor xdata, ydata = self.zcurve.get_data() if len(xdata) > 0: # put marker at the middle of the curve x, y = self.zmarker.get_pos() x, y = self.zcurve.get_closest_x(x + i * self.spacings[2], y) self.zmarker.setValue(x, y) self.emit(SIGNAL("projection_changed()")) else: # we move the range _min, _max = self.zrange.get_range() self.zrange.set_range( _min + i * self.spacings[2], _max + i * self.spacings[2] ) def set_marker_position(self): if self.axis == 0: self.xmarker.setVisible(True) self.ymarker.setVisible(False) self.zmarker.setVisible(False) self.xplot.select_some_items([self.xmarker]) xdata, ydata = self.xcurve.get_data() if len(xdata) > 0: # put marker at the middle of the curve x, y = self.xmarker.get_pos() x, y = self.xcurve.get_closest_x(x, y) self.xmarker.setValue(x, y) elif self.axis == 1: self.xmarker.setVisible(False) self.ymarker.setVisible(True) self.zmarker.setVisible(False) self.yplot.select_some_items([self.ymarker]) xdata, ydata = self.ycurve.get_data() if len(xdata) > 0: x, y = self.ymarker.get_pos() x, y = self.ycurve.get_closest_x(x, y) self.ymarker.setValue(x, y) else: self.xmarker.setVisible(False) self.ymarker.setVisible(False) self.zmarker.setVisible(True) self.zplot.select_some_items([self.zmarker]) xdata, ydata = self.zcurve.get_data() if len(xdata) > 0: x, y = self.zmarker.get_pos() x, y = self.zcurve.get_closest_x(x, y) self.zmarker.setValue(x, y) def axis_changed(self, axis): # a projection axis has been modified self.axis = axis if self.bg.isChecked(): self.update_bg_range() if self.plane.isChecked(): self.xmarker.setVisible(axis == 0) self.ymarker.setVisible(axis == 1) self.zmarker.setVisible(axis == 2) self.set_marker_position() self.emit(SIGNAL("projection_changed()")) self.set_marker_position() # needed to ajust y value if axis == 0: self.xplot.replot() elif axis == 1: self.yplot.replot() else: self.zplot.replot() else: self.emit(SIGNAL("projection_changed()")) def set_ranges_and_values(self, ranges, labels=None, spacings=[1, 1, 1]): # adjust ranges and values with new data if labels is not None: self.projx.setText(labels[0]) self.projy.setText(labels[1]) self.projz.setText(labels[2]) self.xcurve.xlabel = labels[0] self.ycurve.xlabel = labels[1] self.zcurve.xlabel = labels[2] self.selection_ranges = np.array(ranges) self.ranges = np.array(ranges) self.set_ranges(ranges, spacings) self.set_values(ranges) def set_ranges(self, ranges, spacings=None, dosignal=False): if spacings is not None: self.spacings = spacings # intervals between points along x,y,z # selection self.xrange.set_range( ranges[0][0] - self.spacings[0] / 2.0, ranges[0][1] + self.spacings[0] / 2.0, dosignal=dosignal, ) self.yrange.set_range( ranges[1][0] - self.spacings[1] / 2.0, ranges[1][1] + self.spacings[1] / 2.0, dosignal=dosignal, ) self.zrange.set_range( ranges[2][0] - self.spacings[2] / 2.0, ranges[2][1] + self.spacings[2] / 2.0, dosignal=dosignal, ) # background left self.xbgrange1.set_range( ranges[0][0] - 3.0 * self.spacings[0] / 2.0, ranges[0][0] - self.spacings[0] / 2.0, dosignal=dosignal, ) self.ybgrange1.set_range( ranges[1][0] - 3.0 * self.spacings[1] / 2.0, ranges[1][0] - self.spacings[1] / 2.0, dosignal=dosignal, ) self.zbgrange1.set_range( ranges[2][0] - 3.0 * self.spacings[2] / 2.0, ranges[2][0] - self.spacings[2] / 2.0, dosignal=dosignal, ) # background right self.xbgrange2.set_range( ranges[0][1] + self.spacings[0] / 2.0, ranges[0][1] + 3.0 * self.spacings[0] / 2.0, dosignal=dosignal, ) self.ybgrange2.set_range( ranges[1][1] + self.spacings[1] / 2.0, ranges[1][1] + 3.0 * self.spacings[1] / 2.0, dosignal=dosignal, ) self.zbgrange2.set_range( ranges[2][1] + self.spacings[2] / 2.0, ranges[2][1] + 3.0 * self.spacings[2] / 2.0, dosignal=dosignal, ) self.xplot.replot() self.yplot.replot() self.zplot.replot() def set_values(self, ranges): self.xmin.setText("%f" % ranges[0][0]) self.xmax.setText("%f" % ranges[0][1]) self.ymin.setText("%f" % ranges[1][0]) self.ymax.setText("%f" % ranges[1][1]) self.zmin.setText("%f" % ranges[2][0]) self.zmax.setText("%f" % ranges[2][1]) class Image3DDialog(ImageDialog): def __init__( self, parent=None, ): defaultoptions = { "show_contrast": True, "show_xsection": False, "show_ysection": False, "lock_aspect_ratio": False, } ImageDialog.__init__(self, edit=False, toolbar=True, options=defaultoptions) self.fittool = self.add_tool(FitTool) self.fittool.connect(self.fittool, SIG_VALIDATE_TOOL, self.set_fit) self.rectangleerasertool = self.add_tool(RectangleEraserTool) self.rectangleerasertool.connect( self.rectangleerasertool, SIGNAL("suppress_area()"), self.suppress_rectangular_area, ) self.image = None self.xcurve = None self.ycurve = None self.zcurve = None self.xvalues = [] self.yvalues = [] self.zvalues = [] self.data = [] self.isafit = 0 self.sliceprefs = SlicePrefs() self.laplaceparam = LaplaceParam() self.filterparam = FilterParam() self.create_menubar() self.preferences = Preferences() self.make_default_image() QObject.connect(self.p_panel, SIGNAL("projection_changed()"), self.update_image) QObject.connect( self.imagemasking.run_tool, SIG_VALIDATE_TOOL, self.delete_masked_values ) def register_image_tools(self): ImageDialog.register_image_tools(self) self.openfiletool = self.add_tool(OpenFileTool) self.openfiletool.connect( self.openfiletool, SIGNAL("openfile(QString*)"), self.open_file ) def create_menubar(self): self.menubar = QMenuBar(self) self.layout().setMenuBar(self.menubar) """***************File Menu*******************************""" self.menuFile = QMenu("File", self.menubar) self.actionOpen = QAction("Open", self.menuFile) self.actionOpen.setShortcut("Ctrl+o") self.actionQuit = QAction("Quit", self.menuFile) self.actionQuit.setShortcut("Ctrl+Q") self.menuFile.addActions((self.actionOpen, self.actionQuit)) """***************Operations Menu************************""" self.menuOperations = QMenu("Operations", self.menubar) self.menuSlices = QMenu("Slices", self.menuOperations) self.action2D = QAction("Slice rod", self.menuSlices) self.actionStraight = QAction("Scan straight rod", self.menuSlices) self.actionTilted = QAction("Scan tilted rod", self.menuSlices) self.menuSlices.addActions( (self.action2D, self.actionStraight, self.actionTilted) ) self.menuInterpolation = QMenu("Interpolation", self.menuOperations) self.actionGriddata = QAction("Scipy griddata", self.menuInterpolation) self.actionLaplace = QAction("Laplace equation", self.menuInterpolation) self.actionFilling = QAction("Filling", self.menuInterpolation) self.menuInterpolation.addActions( (self.actionGriddata, self.actionLaplace, self.actionFilling) ) self.actionFilter = QAction("Filter data", self.menuOperations) self.menuOperations.addMenu(self.menuSlices) self.menuOperations.addMenu(self.menuInterpolation) self.menuOperations.addAction(self.actionFilter) """***************Settings Menu************************""" self.menuSettings = QMenu("Setting", self.menubar) self.actionPreferences = QAction("Preferences", self.menuSettings) self.menuSettings.addAction(self.actionPreferences) """**********************************************""" self.menubar.addMenu(self.menuFile) self.menubar.addMenu(self.menuOperations) self.menubar.addMenu(self.menuSettings) QObject.connect(self.actionOpen, SIGNAL("triggered()"), self.get_open_filename) QObject.connect(self.actionQuit, SIGNAL("triggered()"), self.close) QObject.connect(self.action2D, SIGNAL("triggered()"), self.make_2D_slices) QObject.connect( self.actionStraight, SIGNAL("triggered()"), self.make_straight_slices ) QObject.connect( self.actionTilted, SIGNAL("triggered()"), self.make_tilted_slices ) QObject.connect( self.actionGriddata, SIGNAL("triggered()"), self.griddata_interpolation ) QObject.connect( self.actionLaplace, SIGNAL("triggered()"), self.laplace_interpolation ) QObject.connect(self.actionFilling, SIGNAL("triggered()"), self.filling) QObject.connect(self.actionFilter, SIGNAL("triggered()"), self.do_filter) QObject.connect( self.actionPreferences, SIGNAL("triggered()"), self.set_preferences ) def create_plot(self, options, row=0, column=0, rowspan=1, columnspan=1): ImageDialog.create_plot(self, options, row, column, rowspan, columnspan) # ra_panel = ObliqueCrossSection(self) # splitter = self.plot_widget.xcsw_splitter # splitter.addWidget(ra_panel) # splitter.setStretchFactor(splitter.count()-1, 1) # splitter.setSizes(list(splitter.sizes())+[2]) # self.add_panel(ra_panel) self.p_panel = ProjectionWidget(self) self.imagemasking = ImageMaskingWidget(self) splitter = self.plot_widget.ycsw_splitter splitter.addWidget(self.p_panel) splitter.addWidget(self.imagemasking) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 0) splitter.setStretchFactor(2, 0) splitter.setSizes(list(splitter.sizes()) + [2]) self.add_panel(self.p_panel) self.add_panel(self.imagemasking) def make_default_image(self): self.image = make.maskedimagenan( np.zeros((1, 1)), interpolation="nearest", xdata=[0, 0], ydata=[0, 0] ) self.imagemasking.masked_image = self.image self.image.set_mask_visible(self.imagemasking.showmaskbutton.isChecked()) self.get_plot().add_item(self.image) def update_image(self): axis = self.p_panel.axis integral = self.p_panel.integral.isChecked() bg = self.p_panel.bg.isChecked() if self.data is not None: if self.p_panel.plane.isChecked(): self.select_plane(axis, bg=bg) # show intensity for selected plane else: self.make_projection( axis, integral=integral, bg=bg ) # show integrated intensity for selected range # pixel size calculation xmin, xmax = self.image.get_xdata() dx = (xmax - xmin) / float(self.image.data.shape[1]) ymin, ymax = self.image.get_ydata() dy = (ymax - ymin) / float(self.image.data.shape[0]) self.rectangleerasertool.set_size(dx, dy) self.imagemasking.rect_mask_tool.set_size(dx, dy) self.imagemasking.ellipse_mask_tool.set_size(dx, dy) def get_open_filename(self): self.openfiletool.activate() def open_file(self, filename): print(self.openfiletool.directory) filename = str(filename) try: hfile = tables.openFile(filename) except: QMessageBox.about(self, "Error", "unable to open file") return print(hfile.root.binoculars.counts.shape) try: self.data = np.array(hfile.root.binoculars.counts.read(), np.float32) self.contributions = np.array( hfile.root.binoculars.contributions.read(), np.float32 ) except tables.exceptions.NoSuchNodeError: QMessageBox.about(self, "Error", "file is empty") return except MemoryError: try: self.data = np.array(hfile.root.binoculars.counts.read(), np.float16) self.contributions = np.array( hfile.root.binoculars.contributions.read(), np.float16 ) except MemoryError: QMessageBox.about(self, "Error", "file is too big") axes = hfile.root.binoculars.axes self.labels = [] # print self.axes._v_children,labels for leaf in axes._f_listNodes(): self.labels.append(leaf.name) # print self.axes._f_listNodes() self.x_label, self.y_label, self.z_label = self.labels self.x_values = axes._v_children[self.x_label].read() self.y_values = axes._v_children[self.y_label].read() self.z_values = axes._v_children[self.z_label].read() self.values = np.array([self.x_values, self.y_values, self.z_values]) ranges = self.values[:, 1:3] spacings = self.values[:, 3] self.p_panel.set_ranges_and_values(ranges, self.labels, spacings) if self.image.plot() is None: self.make_default_image() self.update_image() self.setWindowTitle(filename.split("/")[-1]) mins = [ round(self.x_values[1], 12), round(self.y_values[1], 12), round(self.z_values[1], 12), ] maxs = [ round(self.x_values[2], 12), round(self.y_values[2], 12), round(self.z_values[2], 12), ] steps = [ round(self.x_values[3], 12), round(self.y_values[3], 12), round(self.z_values[3], 12), ] maxs = maxs + steps # borne exclue self.update_sliceprefs( absranges=[mins, maxs], ranges=[mins, maxs], steps=steps, labels=self.labels ) # self.slicetool2.update_pref(absranges=[mins,maxs],steps=steps,labels=self.labels) # interpolation de self.data def set_preferences(self): doit = self.preferences.edit() if doit: self.rectangleerasertool.set_pixel_size(self.preferences.eraser_size) def suppress_rectangular_area(self): # we first suppress pixels on the drawn image, then apply the result to the data if self.image is not None: x0, y0, x1, y1 = self.rectangleerasertool.shape.get_rect() self.image.suppress_rectangular_area(x0, y0, x1, y1) plot = self.get_plot() plot.replot() def delete_masked_values(self): axis = self.p_panel.axis ix0, ix1 = self.showndatalimits[0] iy0, iy1 = self.showndatalimits[1] iz0, iz1 = self.showndatalimits[2] mask = self.image.data.mask if self.p_panel.swap.isChecked(): mask = mask.transpose() if axis == 0: for ix in range(ix0, ix1): self.data[ix, iy0:iy1, iz0:iz1] = np.where( mask, 0.0, self.data[ix, iy0:iy1, iz0:iz1] ) self.contributions[ix, iy0:iy1, iz0:iz1] = np.where( mask, 0.0, self.contributions[ix, iy0:iy1, iz0:iz1] ) elif axis == 1: for iy in range(iy0, iy1): self.data[ix0:ix1, iy, iz0:iz1] = np.where( mask, 0.0, self.data[ix0:ix1, iy, iz0:iz1] ) self.contributions[ix0:ix1, iy, iz0:iz1] = np.where( mask, 0.0, self.contributions[ix0:ix1, iy, iz0:iz1] ) else: for iz in range(iz0, iz1): self.data[ix0:ix1, iy0:iy1, iz] = np.where( mask, 0.0, self.data[ix0:ix1, iy0:iy1, iz] ) self.contributions[ix0:ix1, iy0:iy1, iz] = np.where( mask, 0.0, self.contributions[ix0:ix1, iy0:iy1, iz] ) self.update_image() def laplace_interpolation(self): # first we select point to interpolate doit = self.laplaceparam.edit() if not doit: return if self.laplaceparam.doslice: i0 = self.laplaceparam.cutoff progressbar = ProgressBar("progression...") progressbar.show() sld = np.array(self.data) # copy to save slc = np.array(self.contributions) # copy to save kmax = self.data.shape[2] for k in range(kmax): progressbar.update_progress(k / float(kmax)) cont = self.contributions[:, :, k] if np.sum(cont) > 0: # we interpolate independantly each plane mpoints = np.nonzero(cont < i0) # values to interpolate dt = 0.5 sle = np.nan_to_num( self.data[:, :, k] / cont ) # normalized values, 0 if no contributions # first we fill with default a = sle.shape # a=float(a[0]*a[1]*a[2]) a = float(a[0] * a[1]) # sle[mpoints]=self.laplaceparam.fill #starting point # self.contributions[mpoints]=self.laplaceparam.fillc #starting point mpoints = np.nonzero(cont == 0) # values to interpolate mpointsc = np.nonzero(cont == 0) # copy x = len(mpoints[0]) while x > 0: z = uniform_filter(cont)[mpoints] y = uniform_filter(sle * cont)[mpoints] sle[mpoints] = y / z # replace with mean values sle[mpoints] = np.nan_to_num(sle[mpoints]) cont[mpoints] = np.where( sle[mpoints] > 0, self.laplaceparam.fillc, 0 ) # replace with default value fillc mpoints = np.nonzero(cont == 0) # values to interpolate x = len(mpoints[0]) if progressbar.stop: x = 0 mpoints = mpointsc delta = laplace(sle, mode="nearest") max0 = np.max(np.abs(delta[mpoints])) sle[mpoints] += delta[mpoints] * dt for i in range(self.laplaceparam.steps): delta = laplace(sle, mode="nearest") max1 = np.max(np.abs(delta[mpoints])) if max1 < max0: dt = dt * self.laplaceparam.increase else: dt = dt * self.laplaceparam.decrease sle[mpoints] += delta[mpoints] * dt self.data[:, :, k] = sle * self.contributions[:, :, k] if progressbar.stop: break progressbar.close() self.update_image() i = QMessageBox.question( self, "what should I do", "what should I do", "apply", "continue", "cancel", ) if i == 2: # do not change and stop iterating self.data = sld self.contributions = slc self.update_image() else: i0 = self.laplaceparam.cutoff mpoints = np.nonzero(self.contributions < i0) # values to interpolate dt = 0.5 sld = np.array(self.data) # copy to save slc = np.array(self.contributions) sle = np.nan_to_num( self.data / slc ) # normalized values, 0 if no contributions # first we fill with default a = sle.shape # a=float(a[0]*a[1]*a[2]) a = float(a[0] * a[1] * a[2]) # sle[mpoints]=self.laplaceparam.fill #starting point # self.contributions[mpoints]=self.laplaceparam.fillc #starting point progressbar = ProgressBar("filling first...") progressbar.show() mpoints = np.nonzero(slc == 0) # values to interpolate mpointsc = np.nonzero(slc == 0) # copy x = len(mpoints[0]) while x > 0: progressbar.update_progress(1.0 - x / a) z = uniform_filter(self.contributions)[mpoints] y = uniform_filter(sle * self.contributions)[mpoints] sle[mpoints] = y / z # replace with mean values sle[mpoints] = np.nan_to_num(sle[mpoints]) self.contributions[mpoints] = np.where( sle[mpoints] > 0, self.laplaceparam.fillc, 0 ) # replace with default value fillc mpoints = np.nonzero(self.contributions == 0) # values to interpolate x = len(mpoints[0]) if progressbar.stop: x = 0 progressbar.close() mpoints = mpointsc delta = laplace(sle, mode="nearest") max0 = np.max(np.abs(delta[mpoints])) sle[mpoints] += delta[mpoints] * dt while doit: progressbar = ProgressBar("laplace transformation...") progressbar.show() for i in range(self.laplaceparam.steps): delta = laplace(sle, mode="nearest") max1 = np.max(np.abs(delta[mpoints])) if max1 < max0: dt = dt * self.laplaceparam.increase else: dt = dt * self.laplaceparam.decrease sle[mpoints] += delta[mpoints] * dt x = i / float(self.laplaceparam.steps) progressbar.update_progress(x) if progressbar.stop: break self.data = sle * self.contributions self.update_image() txt = "Maximum laplacian value=%f" % max1 i = QMessageBox.question( self, "what should I do", txt, "apply", "continue", "cancel" ) if i == 2: # do not change and stop iterating self.data = sld self.contributions = slc self.update_image() doit = False elif i == 0: doit = False # stop iterating def filling(self): # fill pixels with no contributions with pixels where # select one plane slc = np.array(self.contributions) sle = np.nan_to_num(self.data / slc) a = sle.shape # a=float(a[0]*a[1]*a[2]) a = float(a[0] * a[1] * a[2]) progressbar = ProgressBar("filling...") progressbar.show() mpoints = np.nonzero(slc == 0) # values to interpolate x = len(mpoints[0]) while x > 0: progressbar.update_progress(1.0 - x / a) z = uniform_filter(slc)[mpoints] y = uniform_filter(sle * slc)[mpoints] sle[mpoints] = y / z # replace with mean values sle[mpoints] = np.nan_to_num(sle[mpoints]) slc[mpoints] = 10 * z / z # replace with default value=10 slc[mpoints] = np.nan_to_num(slc[mpoints]) mpoints = np.nonzero(slc == 0) # values to interpolate x = len(mpoints[0]) if progressbar.stop: x = 0 self.contributions = slc self.data = sle * slc self.update_image() def griddata_interpolation(self): """old version on slices x,y = np.indices(self.data.shape[0:2]) nz=self.data.shape[2] print "interpolation" for iz in range(nz): print iz+1,' / ',nz sld=self.data[:,:,iz] slc=self.contributions[:,:,iz] if np.sum(slc)>0: sle=sld/slc #intensite normalisees #interpolate contributions slc[np.isnan(sle)]=griddata((x[~np.isnan(sle)], y[~np.isnan(sle)]), # points we know slc[~np.isnan(sle)], # values we know (x[np.isnan(sle)], y[np.isnan(sle)]), method='linear',fill_value=0.) #fill with 0 for points outside of the convex hull of the input points #interpolate normalized intensities and multiply by contributions sld[np.isnan(sle)]=slc[np.isnan(sle)]*griddata((x[~np.isnan(sle)], y[~np.isnan(sle)]), # points we know sle[~np.isnan(sle)], # values we know (x[np.isnan(sle)], y[np.isnan(sle)]), method='linear',fill_value=0.) self.data[:,:,iz]=sld self.contributions[:,:,iz]=slc """ """new version: 3D interpolation""" x, y, z = np.indices(self.data.shape) sle = self.data / self.contributions # intensite normalisees hascont = self.contributions > 0 # interpolate contributions print("interpolate contributions") self.contributions[~hascont] = griddata( (x[hascont], y[hascont], z[hascont]), # points we know self.contributions[hascont], # values we know (x[~hascont], y[~hascont], z[~hascont]), method="nearest", fill_value=0.0, ) # fill with 0 for points outside of the convex hull of the input points # nterpolate normalized intensities and multiply by # contributions print("interpolate counts") self.data[~hascont] = self.contributions[~hascont] * griddata( (x[hascont], y[hascont], z[hascont]), # points we know sle[hascont], # values we know (x[~hascont], y[~hascont], z[~hascont]), method="nearest", fill_value=0.0, ) self.update_image() def do_filter(self): doit = self.filterparam.edit() if not doit: return mpoints = np.nonzero( self.contributions < self.filterparam.cutoff ) # values to remove self.contributions[mpoints] = 0 self.data[mpoints] = 0 self.update_image() def select_plane(self, axis, bg=False): # show a given plane (with orientation given axis and position given by corresponding marker) with np.errstate(invalid="ignore"): xmin = self.x_values[1] - self.x_values[3] / 2.0 # raw table limits xmax = self.x_values[2] + self.x_values[3] / 2.0 ymin = self.y_values[1] - self.y_values[3] / 2.0 ymax = self.y_values[2] + self.y_values[3] / 2.0 zmin = self.z_values[1] - self.z_values[3] / 2.0 zmax = self.z_values[2] + self.z_values[3] / 2.0 if axis == 0: x0 = self.p_panel.xmarker.xValue() ix0 = int(round((x0 - self.x_values[1]) / self.x_values[3])) if ix0 < 0: ix0 = 0 if ix0 > self.data.shape[0]: ix0 = self.data.shape[0] if self.p_panel.show_contributions.isChecked(): proj = self.contributions[ix0, :, :] projx = np.sum(self.contributions, axis=(1, 2)) projy = np.sum(self.contributions[ix0, :, :], axis=1) projz = np.sum(self.contributions[ix0, :, :], axis=0) else: proj = self.data[ix0, :, :] / self.contributions[ix0, :, :] projx = np.sum(self.data, axis=(1, 2)) / np.sum( self.contributions, axis=(1, 2) ) projy = np.sum(self.data[ix0, :, :], axis=1) / np.sum( self.contributions[ix0, :, :], axis=1 ) projz = np.sum(self.data[ix0, :, :], axis=0) / np.sum( self.contributions[ix0, :, :], axis=0 ) self.showndatalimits = [ [ix0, ix0 + 1], [0, self.data.shape[1]], [0, self.data.shape[2]], ] # limits shown elif axis == 1: y0 = self.p_panel.ymarker.xValue() iy0 = int(round((y0 - self.y_values[1]) / self.y_values[3])) if iy0 < 0: iy0 = 0 if iy0 > self.data.shape[1]: iy0 = self.data.shape[1] if self.p_panel.show_contributions.isChecked(): proj = self.contributions[:, iy0, :] projx = np.sum(self.contributions[:, iy0, :], axis=1) projy = np.sum(self.contributions, axis=(0, 2)) projz = np.sum(self.contributions[:, iy0, :], axis=0) else: proj = self.data[:, iy0, :] / self.contributions[:, iy0, :] projx = np.sum(self.data[:, iy0, :], axis=1) / np.sum( self.contributions[:, iy0, :], axis=1 ) projy = np.sum(self.data, axis=(0, 2)) / np.sum( self.contributions, axis=(0, 2) ) projz = np.sum(self.data[:, iy0, :], axis=0) / np.sum( self.contributions[:, iy0, :], axis=0 ) self.showndatalimits = [ [0, self.data.shape[0]], [iy0, iy0 + 1], [0, self.data.shape[2]], ] # limits shown else: z0 = self.p_panel.zmarker.xValue() iz0 = int(round((z0 - self.z_values[1]) / self.z_values[3])) if iz0 < 0: iz0 = 0 if iz0 > self.data.shape[2]: iz0 = self.data.shape[2] if self.p_panel.show_contributions.isChecked(): proj = self.contributions[:, :, iz0] projx = np.sum(self.contributions[:, :, iz0], axis=1) projy = np.sum(self.contributions[:, :, iz0], axis=0) projz = np.sum(self.contributions, axis=(0, 1)) else: proj = self.data[:, :, iz0] / self.contributions[:, :, iz0] projx = np.sum(self.data[:, :, iz0], axis=1) / np.sum( self.contributions[:, :, iz0], axis=1 ) projy = np.sum(self.data[:, :, iz0], axis=0) / np.sum( self.contributions[:, :, iz0], axis=0 ) projz = np.sum(self.data, axis=(0, 1)) / np.sum( self.contributions, axis=(0, 1) ) self.showndatalimits = [ [0, self.data.shape[0]], [0, self.data.shape[1]], [iz0, iz0 + 1], ] # limits shown if bg: # i3,i4,i5,i6 indices for bg subraction if axis == 0: vv = self.x_values x3, x4 = self.p_panel.xbgrange1.get_range() x5, x6 = self.p_panel.xbgrange2.get_range() elif axis == 1: vv = self.y_values x3, x4 = self.p_panel.ybgrange1.get_range() x5, x6 = self.p_panel.ybgrange2.get_range() else: vv = self.z_values x3, x4 = self.p_panel.zbgrange1.get_range() x5, x6 = self.p_panel.zbgrange2.get_range() ibgs = [] for x in [x3, x4, x5, x6]: ibg = int(np.floor((x - vv[1]) / vv[3]) + 1) if ibg < 0: ibg = 0 if ibg > self.data.shape[axis]: ibg = self.data.shape[axis] ibgs.append(ibg) i3, i4, i5, i6 = ibgs if i3 > i4: i3, i4 = i4, i3 if i5 > i6: i5, i6 = i6, i5 # we subtract background: if axis == 0: bg_value = ( np.sum(self.data[i3:i4, :, :], axis=axis) + np.sum(self.data[i5:i6, :, :], axis=axis) ) / ( np.sum(self.contributions[i3:i4, :, :], axis=axis) + np.sum(self.contributions[i5:i6, :, :], axis=axis) ) elif axis == 1: bg_value = ( np.sum(self.data[:, i3:i4, :], axis=axis) + np.sum(self.data[:, i5:i6, :], axis=axis) ) / ( np.sum(self.contributions[:, i3:i4, :], axis=axis) + np.sum(self.contributions[:, i5:i6, :], axis=axis) ) else: bg_value = ( np.sum(self.data[:, :, i3:i4], axis=axis) + np.sum(self.data[:, :, i5:i6], axis=axis) ) / ( np.sum(self.contributions[:, :, i3:i4], axis=axis) + np.sum(self.contributions[:, :, i5:i6], axis=axis) ) bg_value = np.nan_to_num(bg_value) # replace nan numbers with 0. proj = proj - bg_value ai = self.x_values[1] af = ( self.x_values[2] + self.x_values[3] / 2.0 ) # pour eviter les problemes d'arrondi. arange s'arrete tout seul da = self.x_values[3] xpts = np.arange(ai, af, da) ai = self.y_values[1] af = self.y_values[2] + self.y_values[3] / 2.0 da = self.y_values[3] ypts = np.arange(ai, af, da) ai = self.z_values[1] af = self.z_values[2] + self.z_values[3] / 2.0 da = self.z_values[3] zpts = np.arange(ai, af, da) if axis == 0: xdata, ydata = (zmin, zmax), (ymin, ymax) xlabel, ylabel = self.z_label, self.y_label elif axis == 1: xdata, ydata = (zmin, zmax), (xmin, xmax) xlabel, ylabel = self.z_label, self.x_label else: xdata, ydata = (ymin, ymax), (xmin, xmax) xlabel, ylabel = self.y_label, self.x_label plot = self.get_plot() if self.p_panel.zlog.isChecked(): v = np.nanmin( proj[np.array(proj, bool)] ) # minimum of the nonzero values of the array proj = np.log(proj + v) lutrange = self.image.get_lut_range() if self.p_panel.swap.isChecked(): plot.set_axis_title("bottom", ylabel) plot.set_axis_title("left", xlabel) self.image.set_data(np.transpose(proj)) self.image.set_xdata(ydata[0], ydata[1]) self.image.set_ydata(xdata[0], xdata[1]) else: plot.set_axis_title("bottom", xlabel) plot.set_axis_title("left", ylabel) self.image.set_data(proj) self.image.set_xdata(xdata[0], xdata[1]) self.image.set_ydata(ydata[0], ydata[1]) self.image.imageparam.update_param(self.image) self.image.imageparam.update_image(self.image) if self.p_panel.autoscale.isChecked(): plot.do_autoscale() else: self.image.set_lut_range(lutrange) plot.replot() plot.emit(SIG_ITEM_SELECTION_CHANGED, plot) self.p_panel.xcurve.set_data( xpts[np.isfinite(projx)], projx[np.isfinite(projx)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.xplot.do_autoscale() else: self.p_panel.xplot.replot() self.p_panel.ycurve.set_data( ypts[np.isfinite(projy)], projy[np.isfinite(projy)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.yplot.do_autoscale() else: self.p_panel.yplot.replot() self.p_panel.zcurve.set_data( zpts[np.isfinite(projz)], projz[np.isfinite(projz)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.zplot.do_autoscale() else: self.p_panel.zplot.replot() def make_projection(self, axis, integral=False, bg=False): # axis: axis along which the projection will be made # integral: use raw sum instead of normalized counts # bg: subtract background, with bgts from each side with np.errstate(invalid="ignore"): xmin = self.p_panel.selection_ranges[0][0] xmax = self.p_panel.selection_ranges[0][1] ymin = self.p_panel.selection_ranges[1][0] ymax = self.p_panel.selection_ranges[1][1] zmin = self.p_panel.selection_ranges[2][0] zmax = self.p_panel.selection_ranges[2][1] ix1 = int(np.floor((xmin - self.x_values[1]) / self.x_values[3]) + 1) ix2 = int(np.floor((xmax - self.x_values[1]) / self.x_values[3]) + 1) iy1 = int(np.floor((ymin - self.y_values[1]) / self.y_values[3]) + 1) iy2 = int(np.floor((ymax - self.y_values[1]) / self.y_values[3]) + 1) iz1 = int(np.floor((zmin - self.z_values[1]) / self.z_values[3]) + 1) iz2 = int(np.floor((zmax - self.z_values[1]) / self.z_values[3]) + 1) if ix1 > ix2: ix2, ix1 = ix1, ix2 if iy1 > iy2: iy2, iy1 = iy1, iy2 if iz1 > iz2: iz2, iz1 = iz1, iz2 if ix1 < 0: ix1 = 0 if ix2 < 0: ix2 = 0 if iy1 < 0: iy1 = 0 if iy2 < 0: iy2 = 0 if iz1 < 0: iz1 = 0 if iz2 < 0: iz2 = 0 if ix1 > self.data.shape[0]: ix1 = self.data.shape[0] if ix2 > self.data.shape[0]: ix2 = self.data.shape[0] if iy1 > self.data.shape[1]: iy1 = self.data.shape[1] if iy2 > self.data.shape[1]: iy2 = self.data.shape[1] if iz1 > self.data.shape[2]: iz1 = self.data.shape[2] if iz2 > self.data.shape[2]: iz2 = self.data.shape[2] if ix1 == ix2: return if iy1 == iy2: return if iz1 == iz2: return if self.p_panel.show_contributions.isChecked(): proj = np.sum(self.contributions[ix1:ix2, iy1:iy2, iz1:iz2], axis=axis) projx = np.sum(self.contributions[:, iy1:iy2, iz1:iz2], axis=(1, 2)) projy = np.sum(self.contributions[ix1:ix2, :, iz1:iz2], axis=(0, 2)) projz = np.sum(self.contributions[ix1:ix2, iy1:iy2, :], axis=(0, 1)) else: proj = np.sum(self.data[ix1:ix2, iy1:iy2, iz1:iz2], axis=axis) / np.sum( self.contributions[ix1:ix2, iy1:iy2, iz1:iz2], axis=axis ) projx = np.sum(self.data[:, iy1:iy2, iz1:iz2], axis=(1, 2)) / np.sum( self.contributions[:, iy1:iy2, iz1:iz2], axis=(1, 2) ) projy = np.sum(self.data[ix1:ix2, :, iz1:iz2], axis=(0, 2)) / np.sum( self.contributions[ix1:ix2, :, iz1:iz2], axis=(0, 2) ) projz = np.sum(self.data[ix1:ix2, iy1:iy2, :], axis=(0, 1)) / np.sum( self.contributions[ix1:ix2, iy1:iy2, :], axis=(0, 1) ) if bg: # we subtract background: if axis == 0: vv = self.x_values x3, x4 = self.p_panel.xbgrange1.get_range() x5, x6 = self.p_panel.xbgrange2.get_range() elif axis == 1: vv = self.y_values x3, x4 = self.p_panel.ybgrange1.get_range() x5, x6 = self.p_panel.ybgrange2.get_range() else: vv = self.z_values x3, x4 = self.p_panel.zbgrange1.get_range() x5, x6 = self.p_panel.zbgrange2.get_range() ibgs = [] for x in [x3, x4, x5, x6]: ibg = int(np.floor((x - vv[1]) / vv[3]) + 1) if ibg < 0: ibg = 0 if ibg > self.data.shape[axis]: ibg = self.data.shape[axis] ibgs.append(ibg) i3, i4, i5, i6 = ibgs if i3 > i4: i3, i4 = i4, i3 if i5 > i6: i5, i6 = i6, i5 if axis == 0: bg_value = ( np.sum(self.data[i3:i4, iy1:iy2, iz1:iz2], axis=axis) + np.sum(self.data[i5:i6, iy1:iy2, iz1:iz2], axis=axis) ) / ( np.sum( self.contributions[i3:i4, iy1:iy2, iz1:iz2], axis=axis ) + np.sum( self.contributions[i5:i6, iy1:iy2, iz1:iz2], axis=axis ) ) elif axis == 1: bg_value = ( np.sum(self.data[ix1:ix2, i3:i4, iz1:iz2], axis=axis) + np.sum(self.data[ix1:ix2, i5:i6, iz1:iz2], axis=axis) ) / ( np.sum( self.contributions[ix1:ix2, i3:i4, iz1:iz2], axis=axis ) + np.sum( self.contributions[ix1:ix2, i5:i6, iz1:iz2], axis=axis ) ) else: bg_value = ( np.sum(self.data[ix1:ix2, iy1:iy2, i3:i4], axis=axis) + np.sum(self.data[ix1:ix2, iy1:iy2, i5:i6], axis=axis) ) / ( np.sum( self.contributions[ix1:ix2, iy1:iy2, i3:i4], axis=axis ) + np.sum( self.contributions[ix1:ix2, iy1:iy2, i5:i6], axis=axis ) ) bg_value = np.nan_to_num(bg_value) # replace nan numbers with 0. proj = proj - bg_value if integral: # we replace averaged value by integrated value projx = projx * (ymax - ymin) * (zmax - zmin) projy = projy * (xmax - xmin) * (zmax - zmin) projz = projz * (xmax - xmin) * (ymax - ymin) xmin = ( self.x_values[1] + (ix1 - 0.5) * self.x_values[3] ) # x_values are at the center of the pixels xmax = self.x_values[1] + (ix2 - 0.5) * self.x_values[3] ymin = self.y_values[1] + (iy1 - 0.5) * self.y_values[3] ymax = self.y_values[1] + (iy2 - 0.5) * self.y_values[3] zmin = self.z_values[1] + (iz1 - 0.5) * self.z_values[3] zmax = self.z_values[1] + (iz2 - 0.5) * self.z_values[3] self.showndatalimits = [[ix1, ix2], [iy1, iy2], [iz1, iz2]] # limits shown if axis == 0: xdata, ydata = (zmin, zmax), (ymin, ymax) xlabel, ylabel = self.z_label, self.y_label elif axis == 1: xdata, ydata = (zmin, zmax), (xmin, xmax) xlabel, ylabel = self.z_label, self.x_label else: xdata, ydata = (ymin, ymax), (xmin, xmax) xlabel, ylabel = self.y_label, self.x_label xpts = self.x_values[1] + np.arange(self.data.shape[0]) * self.x_values[3] ypts = self.y_values[1] + np.arange(self.data.shape[1]) * self.y_values[3] zpts = self.z_values[1] + np.arange(self.data.shape[2]) * self.z_values[3] plot = self.get_plot() if self.p_panel.zlog.isChecked(): v = np.nanmin( proj[np.array(proj, bool)] ) # minimum of the nonzero values of the array proj = np.log(proj + v) lutrange = self.image.get_lut_range() if self.p_panel.swap.isChecked(): plot.set_axis_title("bottom", ylabel) plot.set_axis_title("left", xlabel) self.image.set_data(np.transpose(proj)) self.image.set_xdata(ydata[0], ydata[1]) self.image.set_ydata(xdata[0], xdata[1]) else: plot.set_axis_title("bottom", xlabel) plot.set_axis_title("left", ylabel) self.image.set_data(proj) self.image.set_xdata(xdata[0], xdata[1]) self.image.set_ydata(ydata[0], ydata[1]) self.image.imageparam.update_param(self.image) self.image.imageparam.update_image(self.image) if self.p_panel.autoscale.isChecked(): plot.do_autoscale() else: self.image.set_lut_range(lutrange) plot.replot() plot.emit(SIG_ITEM_SELECTION_CHANGED, plot) self.p_panel.xcurve.set_data( xpts[np.isfinite(projx)], projx[np.isfinite(projx)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.xplot.do_autoscale() else: self.p_panel.xplot.replot() self.p_panel.ycurve.set_data( ypts[np.isfinite(projy)], projy[np.isfinite(projy)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.yplot.do_autoscale() else: self.p_panel.yplot.replot() self.p_panel.zcurve.set_data( zpts[np.isfinite(projz)], projz[np.isfinite(projz)] ) if self.p_panel.autoscale.isChecked(): self.p_panel.zplot.do_autoscale() else: self.p_panel.zplot.replot() def update_sliceprefs(self, absranges=None, ranges=None, steps=None, labels=None): if absranges is not None: # fixe les bornes des spinbox self.sliceprefs.absmins = absranges[0] self.sliceprefs.absmaxs = absranges[1] if ranges is not None: # determine les valeurs des spinbox self.sliceprefs.mins = ranges[0] self.sliceprefs.maxs = ranges[1] if steps is not None: self.sliceprefs.steps = steps if labels is not None: self.sliceprefs.labels = labels print(self.sliceprefs.mins, self.sliceprefs.maxs, self.sliceprefs.steps) def make_2D_slices(self): xmin = self.p_panel.selection_ranges[0][0] xmax = self.p_panel.selection_ranges[0][1] ymin = self.p_panel.selection_ranges[1][0] ymax = self.p_panel.selection_ranges[1][1] zmin = self.p_panel.selection_ranges[2][0] zmax = self.p_panel.selection_ranges[2][1] mins = [round(xmin, 12), round(ymin, 12), round(zmin, 12)] maxs = [round(xmax, 12), round(ymax, 12), round(zmax, 12)] self.update_sliceprefs(ranges=[mins, maxs]) prefs = self.sliceprefs Set2DSliceWindow(self.sliceprefs) # ouvre une fenetre de dialogue if not prefs.do_it: return # print prefs.mins # print prefs.maxs # print prefs.stepn,prefs.stepw # print prefs.i1,prefs.i2,prefs.i3 i1 = prefs.i1 # slice direction tag1 = self.labels[i1] # axis name ix1 = int( round((prefs.mins[0] - self.x_values[1]) / self.x_values[3]) ) # borne inclue[ ix2 = int( round((prefs.maxs[0] - self.x_values[1]) / self.x_values[3]) ) # borne exclue[ iy1 = int(round((prefs.mins[1] - self.y_values[1]) / self.y_values[3])) iy2 = int(round((prefs.maxs[1] - self.y_values[1]) / self.y_values[3])) iz1 = int(round((prefs.mins[2] - self.z_values[1]) / self.z_values[3])) iz2 = int(round((prefs.maxs[2] - self.z_values[1]) / self.z_values[3])) if ix1 >= ix2 or iy1 >= iy2 or iz1 >= iz2: return if ix1 < 0: ix1 = 0 if iy1 < 0: iy1 = 0 if iz1 < 0: iz1 = 0 if ix2 > self.data.shape[0]: print("ix2 trop grand") ix2 = self.data.shape[0] if iy2 > self.data.shape[1]: print("iy2 trop grand") iy2 = self.data.shape[1] if iz2 > self.data.shape[2]: print("iz2 trop grand") iz2 = self.data.shape[2] mins = [self.x_values[1], self.y_values[1], self.z_values[1]] maxs = [self.x_values[2], self.y_values[2], self.z_values[2]] steps = [self.x_values[3], self.y_values[3], self.z_values[3]] if i1 != 2: QMessageBox.about(self, "not implemented yet", "change direction") print("not implemented yet") return print("mins,maxs") print(mins) print(maxs) print("-------------") x_range = [mins[0] + steps[0] * ix1, mins[0] + steps[0] * ix2] y_range = [mins[1] + steps[1] * iy1, mins[1] + steps[1] * iy2] vmin = prefs.mins[2] v1 = vmin j1 = int(round((v1 - mins[2]) / steps[2])) # borne [ fitwindow = Fit2DWindow() fitwindow.cwd = self.openfiletool.directory for i in range(prefs.stepn): v2 = vmin + (i + 1) * prefs.stepw j2 = int(round((v2 - mins[i1]) / steps[i1])) # borne [ proj = np.sum( self.data[ix1:ix2, iy1:iy2, j1:j2], axis=2 ) # we do not multiply by stepw in order to have the density along the rod, as usual projcont = np.sum(self.contributions[ix1:ix2, iy1:iy2, j1:j2], axis=2) data = proj / projcont weights = projcont / np.sqrt(proj + 1) # 1/errors # in that case the error bar is 1 count Q = mins[2] + float(j1 + j2 - 1) * steps[2] / 2.0 title = tag1 + "=%f" % (Q) # we transpose data to have y as first direction (line number) if self.p_panel.swap.isChecked(): # x along fitwindow.add_data( np.transpose(data), x_range, y_range, weights, title=title, xlabel=self.x_label, ylabel=self.y_label, tags=[(tag1, Q)], ) else: fitwindow.add_data( data, y_range, x_range, weights, title=title, xlabel=self.y_label, ylabel=self.x_label, tags=[(tag1, Q)], ) j1 = j2 fitwindow.setWindowTitle(self.windowTitle()) fitwindow.show() def make_straight_slices(self): xmin = self.p_panel.selection_ranges[0][0] xmax = self.p_panel.selection_ranges[0][1] ymin = self.p_panel.selection_ranges[1][0] ymax = self.p_panel.selection_ranges[1][1] zmin = self.p_panel.selection_ranges[2][0] zmax = self.p_panel.selection_ranges[2][1] mins = [round(xmin, 12), round(ymin, 12), round(zmin, 12)] maxs = [round(xmax, 12), round(ymax, 12), round(zmax, 12)] self.update_sliceprefs(ranges=[mins, maxs]) prefs = self.sliceprefs SetSliceWindow(self.sliceprefs) # ouvre une fenetre de dialogue if not prefs.do_it: return # print prefs.mins # print prefs.maxs # print prefs.stepn,prefs.stepw # print prefs.i1,prefs.i2,prefs.i3 i1, i2, i3 = prefs.i1, prefs.i2, prefs.i3 ix1 = int( round((prefs.mins[0] - self.x_values[1]) / self.x_values[3]) ) # borne inclue[ ix2 = int( round((prefs.maxs[0] - self.x_values[1]) / self.x_values[3]) ) # borne exclue[ iy1 = int(round((prefs.mins[1] - self.y_values[1]) / self.y_values[3])) iy2 = int(round((prefs.maxs[1] - self.y_values[1]) / self.y_values[3])) iz1 = int(round((prefs.mins[2] - self.z_values[1]) / self.z_values[3])) iz2 = int(round((prefs.maxs[2] - self.z_values[1]) / self.z_values[3])) if ix1 >= ix2 or iy1 >= iy2 or iz1 >= iz2: return if ix1 < 0: ix1 = 0 if iy1 < 0: iy1 = 0 if iz1 < 0: iz1 = 0 if ix2 > self.data.shape[0]: print("ix2 trop grand") ix2 = self.data.shape[0] if iy2 > self.data.shape[1]: print("iy2 trop grand") iy2 = self.data.shape[1] if iz2 > self.data.shape[2]: print("iz2 trop grand") iz2 = self.data.shape[2] imins = [ix1, iy1, iz1] imaxs = [ix2, iy2, iz2] mins = [self.x_values[1], self.y_values[1], self.z_values[1]] maxs = [self.x_values[2], self.y_values[2], self.z_values[2]] steps = [self.x_values[3], self.y_values[3], self.z_values[3]] print("mins,maxs") print(mins) print(maxs) print("-------------") ni3 = imaxs[i3] - imins[i3] # We do raw integration along direction i3, keeping a 3D array proj3 = ( np.sum(self.data[ix1:ix2, iy1:iy2, iz1:iz2], axis=i3) * prefs.steps[i3] * ni3 ) # comme on va diviser par le nombre de contributions, il faut multiplier par la taille de la fenetre avant # qui est de prefs.steps[i3]*ni3 # we have to take into account the number of time the pixels have been counted cont3 = np.sum(self.contributions[ix1:ix2, iy1:iy2, iz1:iz2], axis=i3) axes = [0, 1, 2] axes.remove(i3) # np.sum(self.contributions[ix1:ix2,iy1:iy2,iz1:iz2],axis=(prefs.i3),keepdims=True) # print 'ix1,ix2:',ix1,ix2 # print 'iy1,iy2:',iy1,iy2 # print 'iz1,iz2:',iz1,iz2 # print "proj3",proj3.shape ii1 = axes.index(i1) # index of the axis for 2D array to make sum # better would be to transpose at the beginning... if ii1 == 1: proj3 = proj3.transpose() cont3 = cont3.transpose() # we make slices along i1, now first direction of the 2D array vmin = prefs.mins[i1] # vmax=prefs.maxs[i1] v1 = vmin j1 = int(round((v1 - mins[i1]) / steps[i1])) # borne [ title = str(self.windowTitle()) title = ( title + "\nslices along " + self.labels[i1] + "\nraw sum along " + self.labels[i3] + " in the range [%g,%g[" % (prefs.mins[i3], prefs.maxs[i3]) ) self.slicewin = CurveDialog( edit=False, toolbar=True, wintitle="CurveDialog test", options=dict(title=title, xlabel=self.labels[i2], ylabel="intensity"), ) plot = self.slicewin.get_plot() QObject.connect(plot, SIG_ACTIVE_ITEM_CHANGED, self.update_fit) self.init_fit() # scan abscisse wstep = steps[i2] wmin = mins[i2] + imins[i2] * wstep wmax = mins[i2] + imaxs[i2] * wstep pts = np.arange( wmin, wmax - wstep / 2.0, wstep ) # we put wmax-wstep to be sure not to have wmax included # print 'pts',wmin,wmax,pts.shape tag1 = self.labels[i1] tag2 = self.labels[i3] + "_min" tag3 = self.labels[i3] + "_max" zrgb = 255.0 / float(prefs.stepn - 1) Qs = [] integrals = [] # intensity integrated along the window chosen errors = [] for i in range(prefs.stepn): red = int(round(i * zrgb)) green = 0 blue = int(round((prefs.stepn - 1 - i) * zrgb)) v2 = vmin + (i + 1) * prefs.stepw j2 = int(round((v2 - mins[i1]) / steps[i1])) # borne [ proj1 = np.sum( proj3[j1:j2], axis=0 ) # we do not multiply by stepw in order to have the density along the rod, as usual # print 'proj1',proj1.shape cont1 = np.sum(cont3[j1:j2], axis=0) ydata = np.ma.masked_where(cont1 == 0, proj1) ydata = ( ydata / cont1 ) # on divise par les contributions donc on a une densite xdata = np.ma.masked_where(cont1 == 0, pts) Q = mins[i1] + float(j1 + j2 - 1) * steps[i1] / 2.0 title = self.labels[i1] + "=%f" % (Q) item = make.curve( xdata.compressed(), ydata.compressed(), title=title, color=QColor(red, green, blue), ) item.xlabel = self.labels[i2] item.tags = [[tag1, Q], [tag2, prefs.mins[i3]], [tag3, prefs.maxs[i3]]] Qs.append(Q) ncoupstot = np.sum(proj1) / ( prefs.steps[i3] * ni3 ) # nombre de coups detectes print(Q, ncoupstot) error = np.sqrt(ncoupstot) integral = np.sum(ydata) * wstep integrals.append(integral) errors.append(integral / error) plot.add_item(item) j1 = j2 v1 = v2 self.figfit.setsavedir(self.openfiletool.directory) self.slicewin.show() self.intwin = CurveDialog( edit=False, toolbar=True, wintitle="CurveDialog test", options=dict(title=title, xlabel=self.labels[i1], ylabel="intensity"), ) item = make.error(Qs, integrals, None, errors) plot = self.intwin.get_plot() plot.add_item(item) self.intwin.show() self.slicewin.exec_() def make_tilted_slices(self): prefs = self.sliceprefs SetSliceWindow2(prefs) # ouvre une fenetre de dialogue if not prefs.do_it: return print(prefs.i0, prefs.i1, prefs.i2) # indices of directions print(prefs.ranges) # range for raw_sum/range for scan/width og a slice print(prefs.stepn) # number of slices print(prefs.mins[2], prefs.maxs[2]) # limit of slices print(prefs.pos1) # 1 position in the rod print(prefs.pos2) # 2 position in the rod # print prefs.mins # print prefs.maxs # print prefs.stepn,prefs.stepw # print prefs.i1,prefs.i2,prefs.i3 i0, i1, i2 = ( prefs.i0, prefs.i1, prefs.i2, ) # i0 slice direction,i1 scan direction, i2 raw sum direction if i1 == i0 or i1 == i2 or i2 == i0: print("two indices equal!!!") return pos1 = np.array(prefs.pos1) pos2 = np.array(prefs.pos2) if pos1[i2] == pos2[i2]: print("pos1[i2]==pos2[i2]=%f" % pos1[i2]) return # we make a slice # mins=[self.x_values[1],self.y_values[1],self.z_values[1]] # maxs=[self.x_values[2],self.y_values[2],self.z_values[2]] # steps=[self.x_values[3],self.y_values[3],self.z_values[3]] # i2_min=int(round((prefs.mins[i2]-prefs.absmins[i2])/prefs.steps[i2])) # i2_max=int(round((prefs.maxs[i2]-prefs.absmins[i2])/prefs.steps[i2])) # we swap the array to have i0,i1,i2 coprresponding to axes 0,1,2 data = self.data cont = self.contributions if i2 != 2: data = np.swapaxes(data, i2, 2) cont = np.swapaxes(cont, i2, 2) # pos1[i0],pos1[2]=pos1[2],pos1[i0] # pos2[i0],pos2[2]=pos2[2],pos2[i0] if i1 != 1: data = np.swapaxes(data, i1, 1) cont = np.swapaxes(cont, i1, 1) # pos1[i1],pos1[1]=pos1[1],pos1[i1] # pos2[i1],pos2[1]=pos2[1],pos2[i1] # that should be OK now! we have slice=last axes,raw_sum=first axe # number of integration points: kx = int(prefs.ranges[0] / prefs.steps[i0]) ky = int(prefs.ranges[1] / prefs.steps[i1]) print("number of points along raw_sum and scan directions", kx, ky) zmin = prefs.mins[2] zmax = prefs.maxs[2] z1 = zmin k1 = int(round((z1 - prefs.absmins[i2]) / prefs.steps[i2])) # borne inf [ title = str(self.windowTitle()) title = ( title + "\nslices along " + self.labels[i2] + "\nraw sum along " + self.labels[i0] ) self.slicewin = CurveDialog( edit=False, toolbar=True, wintitle="CurveDialog test", options=dict(title=title, xlabel=self.labels[i0], ylabel="intensity"), ) plot = self.slicewin.get_plot() QObject.connect(plot, SIG_ACTIVE_ITEM_CHANGED, self.update_fit) self.init_fit() if prefs.stepn > 1: zrgb = 255.0 / float(prefs.stepn - 1) else: zrgb = 0.0 for istep in range(prefs.stepn): z2 = zmin + (istep + 1) * (zmax - zmin) / prefs.stepn k2 = int(round((z2 - prefs.absmins[i2]) / prefs.steps[i2])) # borne sup [ scan_values = np.zeros(ky) # a scan scan_conts = np.zeros(ky) # number of contributing original pixels red = int(round(istep * zrgb)) green = 0 blue = int(round((prefs.stepn - 1 - istep) * zrgb)) print("slice between", k1, k2) for k in range(k1, k2): # center of the rod data_sli = data[:, :, k] cont_sli = cont[:, :, k] z = prefs.absmins[i2] + k * prefs.steps[i2] # center of the rod x = pos1[i0] + (pos2[i0] - pos1[i0]) * (z - pos1[i2]) / ( pos2[i2] - pos1[i2] ) y = pos1[i1] + (pos2[i1] - pos1[i1]) * (z - pos1[i2]) / ( pos2[i2] - pos1[i2] ) x1 = x - prefs.ranges[0] / 2 x2 = x + prefs.ranges[0] / 2 y1 = y - prefs.ranges[1] / 2 y2 = y + prefs.ranges[1] / 2 ix1 = int(round((x1 - prefs.absmins[i0]) / prefs.steps[i0])) ix2 = int(round((x2 - prefs.absmins[i0]) / prefs.steps[i0])) iy1 = int(round((y1 - prefs.absmins[i1]) / prefs.steps[i1])) iy2 = int(round((y2 - prefs.absmins[i1]) / prefs.steps[i1])) print("k=", k, "z=", z, "ix1,ix2,iy1,iy2=", ix1, ix2, iy1, iy2) if k == k1: ix1deb = ix1 ix2deb = ix2 iy1deb = iy1 iy2deb = iy2 zdeb = z if k == k2 - 1: ix1fin = ix1 ix2fin = ix2 iy1fin = iy1 iy2fin = iy2 zfin = z if ix1 < 0: ix1 = 0 # the slice will be smaller if ix2 > data_sli.shape[0]: ix2 = data_sli.shape[0] # the slice will be smaller if iy1 >= 0 and iy2 <= data_sli.shape[1]: scan_values = ( scan_values + np.sum(data_sli[ix1:ix2, iy1:iy2], axis=0) * (ix2 - ix1) * prefs.ranges[0] ) scan_conts = ( scan_conts + np.sum(cont_sli[ix1:ix2, iy1:iy2], axis=0) * (ix2 - ix1) * prefs.ranges[0] ) else: # we have to ajust the position of the slice if iy1 < 0: idec1 = -iy1 iy1 = 0 else: idec1 = 0 if iy2 > data_sli.shape[1]: idec2 = ky - (iy2 - data_sli.shape[1]) iy2 = data_sli.shape[1] else: idec2 = ky scan_values[idec1:idec2] = ( scan_values[idec1:idec2] + np.sum(data_sli[ix1:ix2, iy1:iy2], axis=0) * (ix2 - ix1) * prefs.ranges[0] ) scan_conts[idec1:idec2] = scan_conts[idec1:idec2] + np.sum( cont_sli[ix1:ix2, iy1:iy2], axis=0 ) # xvalues x1deb = prefs.absmins[i0] + ix1deb * prefs.steps[i0] x1fin = prefs.absmins[i0] + ix1fin * prefs.steps[i0] x2deb = prefs.absmins[i0] + (ix2deb - 1) * prefs.steps[i0] x2fin = prefs.absmins[i0] + (ix2fin - 1) * prefs.steps[i0] x1 = (x1deb + x1fin) / 2.0 x2 = (x2deb + x2fin) / 2.0 xcen = (x1 + x2) / 2.0 # yvalues y1deb = prefs.absmins[i1] + iy1deb * prefs.steps[i1] y1fin = prefs.absmins[i1] + iy1fin * prefs.steps[i1] y2deb = prefs.absmins[i1] + (iy2deb - 1) * prefs.steps[i1] y2fin = prefs.absmins[i1] + (iy2fin - 1) * prefs.steps[i1] y1 = (y1deb + y1fin) / 2.0 y2 = (y2deb + y2fin) / 2.0 ycen = (y1 + y2) / 2.0 zcen = (zdeb + zfin) / 2.0 abscissae = np.arange(ky) * prefs.steps[i1] + y1 title = "scan_" + self.labels[i2] + "=%f" % zcen tag1 = self.labels[i0] + "_center" tag2 = self.labels[i1] + "_center" tag3 = self.labels[i2] + "_center" tag4 = self.labels[i0] + "_range" ydata = np.ma.masked_where(scan_conts == 0, scan_values) ydata = ydata / scan_conts xdata = np.ma.masked_where(scan_conts == 0, abscissae) item = make.curve( xdata.compressed(), ydata.compressed(), title=title, color=QColor(red, green, blue), ) item.xlabel = self.labels[i1] item.tags = [ [tag1, xcen], [tag2, ycen], [tag3, zcen], [tag4, prefs.ranges[0]], ] plot.add_item(item) k1 = k2 self.figfit.setsavedir(self.openfiletool.directory) self.slicewin.show() self.slicewin.exec_() def init_fit(self): # ici on inclue une figure de fit provenant de grafit if self.isafit == 0: from grafit2.grafit2 import Ui_FitWindow # import grafit2 as grafit self.figfit = Ui_FitWindow() # self.figfit=grafit.Ui_FitWindow() self.figfit.setupUi() # tags est une liste de couples [non,valeur] self.figfit.settags([], saveint=True) self.isafit = 1 # ici on redirige le signal de fermeture de la fenetre de fit self.figfit.closeEvent = self.close_fit self.figfit.move(10, 500) self.figfit.show() # self.update_fit() def set_fit(self): self.init_fit() QObject.connect( self.p_panel, SIGNAL("active_plot_changed()"), self.update_curve_fit ) def update_curve_fit(self): plot = self.p_panel.active_plot curve = plot.get_items(item_type=ICurveItemType)[0] if self.isafit: self.show_curve_fit(curve) def show_curve_fit(self, item): if item is not None: # try: x, y = item.get_data() ylabel = "intensity" title = str(self.windowTitle()) self.figfit.setvalexp(x, y, xlabel=item.xlabel, ylabel=ylabel, title=title) self.figfit.show() # except: # print "error in show_curve_fit, unable to display curve to be fitted" # pass def show_fit(self, item): if item is not None: try: x, y = item.get_data() ylabel = "intensity" self.figfit.setvalexp( x, y, xlabel=item.xlabel, ylabel=ylabel, tags=item.tags, title=item.curveparam.label, ) self.figfit.show() except: print("error in show_fit, unable to display curve to be fitted") pass def update_fit(self, plot): itemselected = plot.get_active_item(force=False) if self.isafit: self.show_fit(itemselected) def close_fit(self, closeevent): self.isafit = 0 def test(): """Test""" # -- Create QApplication import guidata _app = guidata.qapplication() # -- win = Image3DDialog() # win.get_plot().manager.get_tool(AspectRatioTool).lock_action.setChecked(False) win.get_plot().set_aspect_ratio(lock=False) win.resize(1200, 800) # win.open_file('sample2a_295-295.hdf5') # win.openfiletool.directory='G:/Documents Geoffroy PREVOT/Ag-Si/SIXS-SiAg(110)-Juillet2019/binoculars V2' # win.open_file('G:/Documents Geoffroy PREVOT/Ag-Si/SIXS-SiAg(110)-Juillet2019/binoculars V2/sample2a_924-942_[m5.00-5.00,m1.55-m1.45,0.00-2.00].hdf5') win.openfiletool.directory = "G:/Documents Geoffroy PREVOT/ANR-Germanene-2017/Manip SOLEIL nov 2019/binoculars depot1 corrige/hdf5" win.open_file( "G:/Documents Geoffroy PREVOT/ANR-Germanene-2017/Manip SOLEIL nov 2019/binoculars depot1 corrige/hdf5/hdf5 in plan 0.1 et 2.5/0.1/Al111_depot1_258-258.hdf5" ) win.setGeometry(10, 35, 1250, 900) # win.open_file('Au111_noir_na=2_nb=1_188-281.hdf5') # win.p_panel.set_ranges([[4.145,4.306],[-0.057,0.060],[0.05,3.90]],dosignal=True) # win.update_image() # win.open_file('test_2186-2201_180.hdf5') win.show() # SetSliceWindow2(win.slicetool.prefs) _app.exec_() if __name__ == "__main__": from guidata.configtools import add_image_path abspath = osp.abspath(__file__) dirpath = osp.dirname(abspath) add_image_path(dirpath) # test() binoculars-0.0.10/scripts/grafit2.py000066400000000000000000002436051412510113200173300ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Fri Dec 06 12:15:46 2013 @author: prevot Objectif: remplacer les courbes par des instances de classe FitCurve.nom : nom de la courbe FitCurve.function : fonction associee FitCurve.parameters : parametres standards de la courbe FitCurve.letter : lettre symbolique de la fonction FitCurve.getIntegral() : calcule l'integrale pour les fonctions le permettant FitCurve.RectangularActionTool : le type d'outil de tracage FitCurve.action : le comportement de la souris au traçage """ import copy import numpy as np from os import path, getcwd from guiqwt.plot import CurveWidget, PlotManager from guiqwt.builder import make from guidata.qt.QtCore import Qt, SIGNAL, QRegExp, QRect, QSize, QPoint, QString from guidata.qt.QtGui import ( QSyntaxHighlighter, QTextCharFormat, QListWidget, QListWidgetItem, QTextEdit, QTableWidget, QTableWidgetItem, QLabel, QMenu, QAction, QCursor, QColor, QIcon, QMessageBox, QInputDialog, QFileDialog, QDialog, QMainWindow, QSplitter, QApplication, QPushButton, QButtonGroup, QSpinBox, QRadioButton, QCheckBox, ) from guidata.qthelpers import add_actions, get_std_icon import sys, weakref from guiqwt.events import RectangularSelectionHandler, setup_standard_tool_filter from guiqwt.tools import ( OpenFileTool, CommandTool, DefaultToolbarID, RectangularActionTool, BaseCursorTool, ) from guiqwt.shapes import XRangeSelection from guiqwt.signals import SIG_END_RECT, SIG_RANGE_CHANGED, SIG_ITEM_REMOVED from guiqwt.config import _ from guiqwt.interfaces import ICurveItemType, IShapeItemType from lmfit import minimize, Parameters, Parameter from scipy.special import erf from scipy.integrate import simps SHAPE_Z_OFFSET = 1000 try: _fromUtf8 = QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s abspath = path.abspath(__file__) dirpath = path.dirname(abspath) abspath = getcwd() runpath = path.join(dirpath, "Run.png") gausspath = path.join(dirpath, "Gauss.png") eraserpath = path.join(dirpath, "Eraser.png") steppath = path.join(dirpath, "Step.png") doorpath = path.join(dirpath, "Door.png") gaussderpath = path.join(dirpath, "GaussDer.png") gausslorpath = path.join(dirpath, "Skew.png") skewpath = path.join(dirpath, "Skew.png") yfullrangepath = path.join(dirpath, "y_full_range.png") class Highlighter(QSyntaxHighlighter): PATTERNS = [" ", "\t", ","] def __init__(self, parent, patternid=0): super(Highlighter, self).__init__(parent) self.highlightFormat = QTextCharFormat() self.highlightFormat.setForeground(Qt.blue) self.highlightFormat.setBackground(Qt.green) self.setpattern(patternid) def setpattern(self, patternid): self.expression = QRegExp(self.PATTERNS[patternid]) def highlightBlock(self, text): index = self.expression.indexIn(text) while index >= 0: length = self.expression.matchedLength() self.setFormat(index, length, self.highlightFormat) index = text.indexOf(self.expression, index + length) # fonctions de fit def lorentz(x, p): # p[0] max d'intensite, p[1] centre, p[2],FWHM return p[0] / (1.0 + ((x - p[1]) / 0.5 / p[2]) ** 2) def lorentzder(x, p): # p[0] max d'intensite, p[1] centre, p[2],FWHM return p[0] * (p[1] - x) / p[2] / (1.0 + ((x - p[1]) / 0.5 / p[2]) ** 2) ** 2 def gauss(x, p): w = 0.600561204393225 return p[0] * np.exp(-(((x - p[1]) / w / p[2]) ** 2)) def gausslor(x, p): # gaussian switching to lorentzian for x-p[1]=p[2]*p[3] if p[3] > 0: w = 0.600561204393225 p32 = p[3] * p[2] pp1 = p32 + p[1] - ((w * p[2]) ** 2) / p32 pp0 = p[0] * np.exp(-(((p32) / w / p[2]) ** 2)) * (p[1] + p32 - pp1) ** 2 b = np.greater(x, p[1] + p32) return (p[0] * np.exp(-(((x - p[1]) / w / p[2]) ** 2))) * (1 - b) + ( pp0 / (x - pp1) ** 2 ) * b elif p[3] < 0: w = 0.600561204393225 p32 = p[3] * p[2] pp1 = p32 + p[1] - ((w * p[2]) ** 2) / p32 pp0 = p[0] * np.exp(-(((p32) / w / p[2]) ** 2)) * (p[1] + p32 - pp1) ** 2 b = np.less(x, p[1] + p32) return (p[0] * np.exp(-(((x - p[1]) / w / p[2]) ** 2))) * (1 - b) + ( pp0 / (x - pp1) ** 2 ) * b else: return gauss(x, p) def gaussder(x, p): # derivee d'une gaussienne w = 0.600561204393225 return p[0] * (p[1] - x) / p[2] * np.exp(-(((x - p[1]) / w / p[2]) ** 2)) def skew(x, p): w = 0.600561204393225 return ( p[0] * np.exp(-(((x - p[1]) / w / p[2]) ** 2)) * (1.0 + erf((x - p[1]) * p[3] / w / p[2])) ) def voigt(x, p): w = 0.600561204393225 return p[0] * ( (1.0 - p[3]) * np.exp(-(((x - p[1]) / w / p[2]) ** 2)) + p[3] / (1.0 + ((x - p[1]) / 0.5 / p[2]) ** 2) ) def step(x, p): w = 0.600561204393225 return 0.5 * p[0] * (1.0 - erf((x - p[1]) / w / p[2])) def door(x, p): return ( p[0] / 2.0 * (erf((x - p[1] + p[2] / 2.0) / p[3]) - erf((x - p[1] - p[2] / 2.0) / p[3])) ) # integrales des fonctions # integrales des fonctions def Ilorentz(p): return p[0] * p[2] * np.pi / 2.0 def Igauss(p): wpi = 1.064467019431226 return p[0] * p[2] * wpi def Ivoigt(p): wpi = 1.064467019431226 return p[0] * p[2] * ((1.0 - p[3]) * wpi + p[3] * np.pi / 2.0) def Igausslor(p): w = 0.600561204393225 wpis2 = 0.532233509715613 p32 = np.abs(p[3] * p[2]) pp = ((w * p[2]) ** 2) / p32 pp1 = p32 + p[1] - pp pp0 = p[0] * np.exp(-(((p32) / w / p[2]) ** 2)) * (p[1] + p32 - pp1) ** 2 return p[0] * p[2] * wpis2 * (1.0 + erf(p[3] / p[2])) + pp0 / pp # dictionnaire reliant la fonction au nom curvetypes = dict( gaussian=gauss, lorentzian=lorentz, voigt=voigt, door=door, step=step, gaussian_derivative=gaussder, lorentz_derivative=gaussder, gaussian_lorentzian=gausslor, skew=skew, ) # dictionnaire reliant l'integrale au nom inttypes = dict( gaussian=Igauss, lorentzian=Ilorentz, voigt=Ivoigt, door=None, step=None, gaussian_derivative=None, lorentz_derivative=None, gaussian_lorentzian=Igausslor, skew=Igauss, ) curvenames = list( ( "gaussian", "lorentzian", "voigt", "door", "step", "gaussian_derivative", "lorentz_derivative", "gaussian_lorentzian", "skew", ) ) curveparams = dict( gaussian=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ), lorentzian=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ), voigt=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0, None), ("eta", 0.5, 0.0, 1.0), ), door=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0, None), ("steep", 0.25, 0.0, None), ), step=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("steep", 1.0, 0.0, None), ), gaussian_derivative=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ), lorentzian_derivative=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ), gaussian_lorentzian=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ("eps", 0.1, None, None), ), skew=( ("int", 1.0, None, None), ("pos", 0.0, None, None), ("fwhm", 1.0, 0.0, None), ("alpha", 0.0, None, None), ), ) # parametres de reference communs aux differentes courbes int0 = Parameter("int", 1.0, min=0.0) pos0 = Parameter("pos", 0.0) fwhm0 = Parameter("fwhm", 1.0, min=0.0) eta0 = Parameter("eta", 0.5, min=0.0, max=1.0) steep0 = Parameter("steep", 0.25, min=0.0) alpha0 = Parameter("alpha", 0.0) eps0 = Parameter("alpha", 0.0) class FitCurve: # definit une classe pour les fonction de fit def __init__(self, name, curvetype, list_of_parameters, integral=None): self.name = name self.curvetype = curvetype self.Parameters = Parameters() for parameter in list_of_parameters: self.Parameters.__setitem__(parameter.name, parameter) gaussparams = FitCurve("gaussian", gauss, (int0, pos0, fwhm0), Igauss) lorentzparams = FitCurve("lorentzian", lorentz, (int0, pos0, fwhm0), Ilorentz) voigtparams = FitCurve("voigt", voigt, (int0, pos0, fwhm0, eta0), Ivoigt) doorparams = FitCurve("door", door, (int0, pos0, fwhm0, steep0)) stepparams = FitCurve("step", step, (int0, pos0, steep0)) gaussderparams = FitCurve("gaussian_derivative", gaussder, (int0, pos0, fwhm0)) lorentzderparams = FitCurve("lorentzian_derivative", gaussder, (int0, pos0, fwhm0)) gausslorparams = FitCurve("gaussian_lorentzian", gausslor, (int0, pos0, fwhm0, eps0)) skewparams = FitCurve("skew", skew, (int0, pos0, fwhm0, alpha0)) def model(params, x, fixparams): # fixparams : parametres fixes # nombre de courbes utilisees degpol = fixparams[0] curveslist = fixparams[1] ncurv = len(curveslist) # degre du polynome : degpol p = np.empty(degpol + 1) for i in range(degpol + 1): name = "pol%d" % (i) p[i] = params[name].value yth = poly(x, p, degpol) for i in range(ncurv): i1 = i + 1 kstr = "%d" % (i1) curvename = curveslist[i] p = list() for cp in curveparams[curvename]: entry = cp[0] + kstr p.append(params[entry].value) yth = yth + curvetypes[curvename](x, p) return yth def integral(params, curveslist): # retourne la liste des valeurs des integrales # le premier point est l'integrale totale ncurv = len(curveslist) intlist = list() inttot = 0.0 for i in range(ncurv): i1 = i + 1 kstr = "%d" % (i1) curvename = curveslist[i] p = list() for cp in curveparams[curvename]: ent = cp[0] entry = ent + kstr p.append(params[entry].value) intfunct = inttypes[curvename] if intfunct is not None: intpart = intfunct(p) # on recupere la fonction qui calcule l'integrale intlist.append(intpart) inttot = inttot + intpart else: intlist.append(None) intlist.insert(0, inttot) return intlist def residual(params, x, data, fixparams): # fparam : parametres fixes return model(params, x, fixparams) - data ## Parametric function: 'p' is the parameter vector, 'x' the independent varibles """ def poly(x,p,degpol): y=p[0] xp=1 for i in range(degpol): xp=xp*x y=y+xp*p[i+1] return y """ def poly(x, p, degpol): y = 0 # p[0] xp = 1 for i in range(degpol + 1): y = y + xp * p[i] xp = xp * x return y class TagsWindow(QDialog): # definit une fenetre pour rentrer l'affichage des tags def __init__(self, tags): QDialog.__init__(self) self.setWindowTitle(_fromUtf8("List of tags")) self.list = QListWidget(self) self.list = QListWidget(self) self.list.setGeometry(QRect(5, 5, 200, 300)) for tag in tags: tagname = tag[0] tagvalue = ": %f" % tag[1] text = tagname + tagvalue item = QListWidgetItem(self.list) item.setText(text) self.OK = QPushButton(self) self.OK.setGeometry(QRect(5, 310, 100, 25)) self.OK.setText("OK") self.setGeometry(QRect(5, 30, 210, 350)) self.connect(self.OK, SIGNAL(_fromUtf8("clicked()")), self.appl) self.exec_() def appl(self): self.close() class readfileparams: # definit une classe pour la lecture des parametres d'un fichier a lire def __init__(self): self.heading = 0 # taille de l'entete self.title = False # ligne de titre self.delimiter = 0 # separateur de nombre self.x = 1 # indice colonne x self.y = 2 # indice colonne y class readfilewindow(QDialog): SIG_FIT_DONE = Signal("fit_done") def __init__(self, params, ficlines): self.params = params super( readfilewindow, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.setWindowTitle("Parameters for reading file") self.setFixedSize(QSize(410, 320)) self.ficlines = ficlines self.display = QTextEdit(self) self.display.setReadOnly(True) self.display.setGeometry(QRect(5, 5, 400, 100)) self.display.setLineWrapMode(0) self.highlighter = Highlighter(self.display.document(), self.params.delimiter) self.lab1 = QLabel(self) self.lab1.setGeometry(QRect(5, 110, 50, 25)) self.lab1.setText("heading") self.lab2 = QLabel(self) self.lab2.setGeometry(QRect(5, 140, 50, 25)) self.lab2.setText("title") self.lab3 = QLabel(self) self.lab3.setGeometry(QRect(5, 170, 50, 25)) self.lab3.setText("delimiter") self.lab4 = QLabel(self) self.lab4.setGeometry(QRect(5, 200, 50, 25)) self.lab4.setText("x column") self.lab4 = QLabel(self) self.lab4.setGeometry(QRect(5, 230, 50, 25)) self.lab4.setText("y column") self.heading = QSpinBox(self) self.heading.setGeometry(QRect(60, 110, 45, 25)) self.heading.setValue(params.heading) self.heading.setMaximum(len(ficlines)) self.title = QRadioButton(self) self.title.setGeometry(QRect(60, 140, 45, 25)) self.title.setChecked(params.title) self.delimiter0 = QCheckBox(self) self.delimiter0.setGeometry(QRect(60, 170, 65, 25)) self.delimiter0.setText("space") self.delimiter1 = QCheckBox(self) self.delimiter1.setGeometry(QRect(130, 170, 65, 25)) self.delimiter1.setText("tab") self.delimiter2 = QCheckBox(self) self.delimiter2.setGeometry(QRect(200, 170, 45, 25)) self.delimiter2.setText("comma") self.delimiters = QButtonGroup(self) self.delimiters.addButton(self.delimiter0, 0) self.delimiters.addButton(self.delimiter1, 1) self.delimiters.addButton(self.delimiter2, 2) self.delimiters.button(params.delimiter).setChecked(True) self.x = QSpinBox(self) self.x.setGeometry(QRect(60, 200, 45, 25)) self.x.setValue(params.x) self.y = QSpinBox(self) self.y.setGeometry(QRect(60, 230, 45, 25)) self.y.setValue(params.y) self.OK = QPushButton(self) self.OK.setGeometry(QRect(5, 260, 90, 25)) self.OK.setText("OK") self.Cancel = QPushButton(self) self.Cancel.setGeometry(QRect(100, 260, 90, 25)) self.Cancel.setText("Cancel") self.connect( self.heading, SIGNAL(_fromUtf8("valueChanged(int)")), self.changeheading ) self.QObject.connect(self.Cancel, SIGNAL(_fromUtf8("clicked()")), self.closewin) self.QObject.connect(self.OK, SIGNAL(_fromUtf8("clicked()")), self.appl) self.QObject.connect( self.delimiters, SIGNAL(_fromUtf8("buttonClicked(int)")), self.setpattern ) self.changeheading(self.params.heading) self.exec_() def setpattern(self, patternid): self.highlighter.setpattern(patternid) self.display.setText(self.text) def changeheading(self, value): self.params.heading = value # first line n1 = min(value, len(self.ficlines) - 1) n2 = min(value + 20, len(self.ficlines)) self.text = "" for i in range(n1, n2): self.text = self.text + self.ficlines[i] self.display.setText(self.text) def appl(self): # try: self.params.heading = int(self.heading.text()) self.params.title = self.title.isChecked() self.params.x = int(self.x.text()) self.params.y = int(self.y.text()) self.params.delimiter = self.delimiters.checkedId() # except Exception: # QMessageBox.about(self, 'Error','Input can only be a number') # return self.close() def closewin(self): self.close() class FitTable(QTableWidget): # objet qui gere les entree de parametres, l'affichage des courbes, le lancement des fits, l'enregistrement du resultat def __init__(self, parent=None, extendedparams=None): QTableWidget.__init__(self, parent=parent) self.setColumnCount(8) self.setObjectName(_fromUtf8("tableValeurs")) self.setHorizontalHeaderLabels( ( "Parameters", "Estimation", "Fit result", "Sigma", "Restrains", "Min", "Max", "Expression", ) ) # on remet le tableau a zero : deux rangees pour un polynome du 1er degre self.reset(extendedparams) # on definit les differents menus contextuels self.initcontexts() # au cas ou self.int1 = 0 # utilise pour contourner le signal de cellChanged self.trackchange = True # On connecte les signaux self.connect(self, SIGNAL(_fromUtf8("cellChanged(int,int)")), self.cellChanged) self.connect(self, SIGNAL(_fromUtf8("cellPressed(int,int)")), self.cellPressed) self.connect( self.horizontalHeader(), SIGNAL(_fromUtf8("sectionPressed(int)")), self.sectionClicked, ) # pas de fit sauve self.isfitted = False self.issaved = False self.tags = list() self.saveint = False self.ints = [] self.updatestart = ( False # si coche, on active setwholeastry apres enregistrement ) self.cwd = abspath def reset(self, extendedparams=None): if extendedparams is None: # on met deux items vide extendedparams = ExtendedParams() extendedparams.addbackground() extendedparams.addbackground() # on remplit le tableau a l'aide du dictionnaire extendedparams self.initparams(extendedparams, settry=True) self.savename = None self.isfitted = False def inititems(self, listrow): # met pour les lignes i de listrow un item vide non editable pour colonnes 0,2,3,4 for i in listrow: for j in range(8): # self.setItem(i,j,QTableWidgetItem(_fromUtf8(""))) self.setItem(i, j, QTableWidgetItem(0)) self.item(i, 0).setFlags(Qt.ItemFlags(33)) self.item(i, 2).setFlags(Qt.ItemFlags(33)) self.item(i, 3).setFlags(Qt.ItemFlags(33)) self.item(i, 4).setFlags(Qt.ItemFlags(33)) def initparams(self, extendedparams=None, settry=False, setopt=False): self.trackchange = False # on redessine le tableau a l'aide du dictionnaire extendedparams # si settry, on remplit aussi les valeurs d'essai, les bornes, les expressions if extendedparams is not None: # on utilise les valeurs donnees en input, sinon on prend celles de self.extendedparams self.extendedparams = extendedparams n = 0 oldrc = self.rowCount() newrc = len(self.extendedparams.partry) - len( self.extendedparams.tags ) # on n'affiche pas les tags self.setRowCount(newrc) if newrc > oldrc: # on rajoute des rangees vides self.inititems(range(oldrc, newrc)) for k in range(self.extendedparams.degpol + 1): kstr = "%d" % (k) entry = "pol" + kstr self.setrow(entry, n, settry, setopt) n = n + 1 for k in range(self.extendedparams.ncurves): curvename = self.extendedparams.curveslist[k] for cp in curveparams[curvename]: ent = cp[0] entry = ent + "%d" % (k + 1) self.setrow(entry, n, settry, setopt) n = n + 1 self.trackchange = True def setrow(self, nom, n, settry=True, setopt=False): # appele uniquement dans initparams. donc on ne change pas self.trackchange # on ne change pas expr car on veut pouvoir la garder en memoire meme si on ne l'utilise pas ptry = self.extendedparams.partry popt = self.extendedparams.paropt self.item(n, 0).setText(_fromUtf8(nom)) """ self.setItem(n,0,QTableWidgetItem(_fromUtf8(nom))) self.setItem(n,2,QTableWidgetItem(_fromUtf8(""))) self.setItem(n,3,QTableWidgetItem(_fromUtf8(""))) self.setItem(n,4,QTableWidgetItem(_fromUtf8(""))) self.item(n,0).setFlags(Qt.ItemFlags(33)) self.item(n,2).setFlags(Qt.ItemFlags(33)) self.item(n,3).setFlags(Qt.ItemFlags(33)) self.item(n,4).setFlags(Qt.ItemFlags(33)) """ if settry: vtry = ptry[nom].value mintry = ptry[nom].min maxtry = ptry[nom].max print(nom, mintry, maxtry) varytry = ptry[nom].vary exprtry = ptry[nom].expr self.item(n, 1).setText(_fromUtf8("%f" % (vtry))) # self.setItem(n,1,QTableWidgetItem(_fromUtf8("%f" % (vtry)))) if exprtry is not None: # self.setItem(n,4,QTableWidgetItem(_fromUtf8(exprtry))) self.item(n, 4).setText(_fromUtf8("Expr")) elif varytry is True: # self.setItem(n,4,QTableWidgetItem(_fromUtf8("Free"))) self.item(n, 4).setText(_fromUtf8("Free")) else: # self.setItem(n,4,QTableWidgetItem(_fromUtf8("Fixed"))) self.item(n, 4).setText(_fromUtf8("Fixed")) if mintry is not None: # self.setItem(n,5,QTableWidgetItem(_fromUtf8("%f" % (mintry)))) self.item(n, 5).setText(_fromUtf8("%f" % (mintry))) if maxtry is not None: # self.setItem(n,6,QTableWidgetItem(_fromUtf8("%f" % (maxtry)))) self.item(n, 6).setText(_fromUtf8("%f" % (maxtry))) # here we rewrite the expression even if it is not used self.item(n, 7).setText(_fromUtf8(ptry[nom].express)) if setopt and self.isfitted: vopt = popt[nom].value # self.setItem(n,2,QTableWidgetItem(_fromUtf8("%f" % (vopt)))) self.item(n, 2).setText(_fromUtf8("%f" % (vopt))) verr = popt[nom].stderr if verr is not None: self.item(n, 3).setText(_fromUtf8("%f" % (verr))) else: self.item(n, 2).setText(_fromUtf8("")) self.item(n, 3).setText(_fromUtf8("")) n = n + 1 """ exemple: self["degpol"]=1 self["ncurves"]=2 self["curveslist"]=({courbe:gaussienne,parametre:(int,pos,fwhm)},{courbe:voigt,parametre:(int,pos,fwhm,eta)}) self["partry"]=orderer dict """ def setactivecurve(self, val): # defini la courbe active qui va etre modifie a la souris self.activecurve = val def initcontexts(self): # initialise une liste de menus contextuels pour les cases, associes a chaque colonne self.contexts = list(None for i in range(self.columnCount())) # initialise une liste de menus contextuels pour les sectionheaders, associes a chaque colonne self.Contexts = list(None for i in range(self.columnCount())) # menu pour la colonne 0 contexta = QMenu(self) self.addbg = QAction("Increase background order", self) contexta.addAction(self.addbg) self.connect(self.addbg, SIGNAL(_fromUtf8("triggered()")), self.add_bg) self.rmbg = QAction("Decrease background order", self) contexta.addAction(self.rmbg) self.connect(self.rmbg, SIGNAL(_fromUtf8("triggered()")), self.rm_bg) self.rmbg = QAction("Decrease background order", self) contexta.addAction(self.rmbg) self.connect(self.rmbg, SIGNAL(_fromUtf8("triggered()")), self.rm_bg) self.freezebg = QAction("Fixed background", self) contexta.addAction(self.freezebg) self.connect(self.freezebg, SIGNAL(_fromUtf8("triggered()")), self.freeze_bg) self.releasebg = QAction("Free background", self) contexta.addAction(self.releasebg) self.connect(self.releasebg, SIGNAL(_fromUtf8("triggered()")), self.release_bg) contextb = QMenu(self) self.addcv = QAction("Add a curve", self) contextb.addAction(self.addcv) self.connect(self.addcv, SIGNAL(_fromUtf8("triggered()")), self.set_cv) self.rmcv = QAction("Remove this curve", self) contextb.addAction(self.rmcv) self.connect(self.rmcv, SIGNAL(_fromUtf8("triggered()")), self.rm_cv) self.chcv = QAction("Reconfigure curve", self) contextb.addAction(self.chcv) self.connect(self.chcv, SIGNAL(_fromUtf8("triggered()")), self.ch_cv) self.freezecv = QAction("Fixed curve", self) contextb.addAction(self.freezecv) self.connect(self.freezecv, SIGNAL(_fromUtf8("triggered()")), self.freeze_cv) self.releasecv = QAction("Free curve", self) contextb.addAction(self.releasecv) self.connect(self.releasecv, SIGNAL(_fromUtf8("triggered()")), self.release_cv) self.contexts[0] = (contexta, contextb) # menu pour la colonne 2 context = QMenu(self) self.setastry = QAction("Set as try", self) context.addAction(self.setastry) self.connect(self.setastry, SIGNAL(_fromUtf8("triggered()")), self.set_as_try) self.showfit = QAction("Display Fit", self) context.addAction(self.showfit) self.connect(self.showfit, SIGNAL(_fromUtf8("triggered()")), self.showfitpart) self.showtry = QAction("Display Try", self) context.addAction(self.showtry) self.connect(self.showtry, SIGNAL(_fromUtf8("triggered()")), self.drawtrypart) self.contexts[2] = context # menu pour la colonne 4 context = QMenu(self) self.setfixed = QAction("Fixed", self) context.addAction(self.setfixed) self.connect(self.setfixed, SIGNAL(_fromUtf8("triggered()")), self.set_fixed) self.setfree = QAction("Free", self) context.addAction(self.setfree) self.connect(self.setfree, SIGNAL(_fromUtf8("triggered()")), self.set_free) self.useexpr = QAction("Expr", self) context.addAction(self.useexpr) self.connect(self.useexpr, SIGNAL(_fromUtf8("triggered()")), self.use_expr) self.contexts[4] = context # menu pour le header 2 context = QMenu(self) self.setwholeastry = QAction("Set whole as try", self) context.addAction(self.setwholeastry) self.connect( self.setwholeastry, SIGNAL(_fromUtf8("triggered()")), self.set_wholeastry ) self.Contexts[2] = context # menu pour le header 4 context = QMenu(self) self.inverserestrains = QAction("Inverse restrains", self) context.addAction(self.inverserestrains) self.connect( self.inverserestrains, SIGNAL(_fromUtf8("triggered()")), self.inverse_restrains, ) self.fixall = QAction("All fixed", self) context.addAction(self.fixall) self.connect(self.fixall, SIGNAL(_fromUtf8("triggered()")), self.fix_all) self.releaseall = QAction("All free", self) context.addAction(self.releaseall) self.connect( self.releaseall, SIGNAL(_fromUtf8("triggered()")), self.release_all ) self.Contexts[4] = context def cellPressed(self, int1, int2): self.int1 = int1 context = self.contexts[int2] if context is not None: if int2 == 0: if int1 <= self.extendedparams.degpol: context[0].popup(QCursor.pos()) else: context[1].popup(QCursor.pos()) else: context.popup(QCursor.pos()) # self.extendedparams.printparams() def cellChanged(self, int1, int2): if self.trackchange: # print "Changed at",int1,int2 if int2 == 1: # on change la valeur du parametre txt = str(self.item(int1, 1).text()) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].value = float(txt) self.drawtry() if int2 == 5: # on change la borne inferieure txt = str(self.item(int1, 5).text()) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].min = float(txt) if int2 == 6: # on change la borne superieure txt = str(self.item(int1, 6).text()) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].max = float(txt) if int2 == 7: # on change l'expression txt = str(self.item(int1, 7).text()) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].express = txt # a copy of expression if len(txt) == 0: self.extendedparams.partry[entry].expr = None self.extendedparams.partry[entry].vary = True else: self.extendedparams.partry[entry].expr = txt self.trackchange = False self.item(int1, 4).setText(_fromUtf8("Expr")) self.trackchange = True def sectionClicked(self, int2): # quand une colonne entiere est selectionnee valable pour les colonnes 2 et 4 context = self.Contexts[int2] if context is not None: context.popup(QCursor.pos()) # self.extendedparams.printparams() def add_bg(self): # on ajoute un ordre au polynome de fond self.extendedparams.addbackground() self.isfitted = False self.initparams(settry=True) self.drawtry() def rm_bg(self): # on enleve un ordre au polynome de fond self.extendedparams.removebackground() self.isfitted = False self.initparams(settry=True) self.drawtry() def freeze_bg(self): # on met fixe tous les parametres du fond self.trackchange = False for int1 in range(self.extendedparams.degpol + 1): item = self.item(int1, 4) item.setText(_fromUtf8("Fixed")) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = False self.trackchange = True def release_bg(self): # on met fixe tous les parametres du fond self.trackchange = False for int1 in range(self.extendedparams.degpol + 1): item = self.item(int1, 4) item.setText(_fromUtf8("Free")) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = True self.trackchange = True def freeze_cv(self): # on met fixe tous les parametres d'une courbe self.trackchange = False entry = str(self.item(self.int1, 0).text()) icurve = self.extendedparams.partry[entry].number self.extendedparams.freezecurve(icurve) self.initparams(settry=True, setopt=True) self.trackchange = True def release_cv(self): # on met fixe tous les parametres d'une courbe self.trackchange = False entry = str(self.item(self.int1, 0).text()) icurve = self.extendedparams.partry[entry].number self.extendedparams.releasecurve(icurve) self.initparams(settry=True, setopt=True) self.trackchange = True def release_all(self): # on met fixe tous les parametres du fond self.trackchange = False for int1 in range(self.rowCount()): item = self.item(int1, 4) item.setText(_fromUtf8("Free")) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = True self.trackchange = True def fix_all(self): # on met fixe tous les parametres du fond self.trackchange = False for int1 in range(self.rowCount()): item = self.item(int1, 4) item.setText(_fromUtf8("Fixed")) entry = str(self.item(int1, 0).text()) self.extendedparams.partry[entry].vary = False self.trackchange = True def set_cv(self, curvetype=None, params=None, bg=0.0): # icurve numero de la courbe a changer le cas echeant -1 c'est la derniere print("bg", bg) if curvetype is None: # on ouvre une fenetre pour demander le choix de courbe self.isfitted = False cvtyp, ok = QInputDialog.getItem( self, "curve type", "curve choice", curvenames, editable=False ) if ok: self.extendedparams.addcurve(str(cvtyp)) self.initparams(settry=True) self.drawtry() else: # on prend les donnees en parametre if self.activecurve == "new": self.isfitted = False # plot.lastcurve=make.curve(xtry, ytry, color="k",title='gaussienne') # plot.add_item(plot.lastcurve) self.extendedparams.addcurve(curvetype, params) # si la courbe est la premiere courbe, on met le fond a la valeur bg icurve = len(self.extendedparams.curveslist) - 1 if icurve == 0: self.extendedparams.partry["pol0"].value = bg self.initparams(settry=True) self.plot.newcurve = False self.activecurve = -1 else: # plot.lastcurve.set_data(xtry, ytry) self.extendedparams.changecurve( paramvalues=params, icurve=self.activecurve ) print(self.activecurve) # si la courbe est la premiere courbe, on met le fond a la valeur bg if self.activecurve == 0: self.extendedparams.partry["pol0"].value = bg elif self.activecurve == -1: icurve = len(self.extendedparams.curveslist) - 1 if icurve == 0: self.extendedparams.partry["pol0"].value = bg self.initparams(settry=True) self.drawtry() def rm_cv(self): # on supprime la courbe selectionnee entry = str(self.item(self.int1, 0).text()) icurve = self.extendedparams.partry[entry].number self.isfitted = False if icurve > 0: # normalement c'est toujours le cas self.extendedparams.removecurve(icurve) self.initparams(settry=True) self.drawtry() self.parent().parent().manager.activate_default_tool() def ch_cv(self): # a partir de la souris, on retrace la courbe selectionnee entry = str(self.item(self.int1, 0).text()) i = self.extendedparams.partry[entry].number if i > 0: # normalement c'est toujours le cas curvename = self.extendedparams.curveslist[i - 1] manager = self.parent().parent().manager self.activecurve = i - 1 fittool = manager.get_tool(FitTool) if curvename == "gaussian": fittool.gausstool.activate() elif curvename == "lorentzian": fittool.lorentztool.activate() elif curvename == "voigt": fittool.voigttool.activate() elif curvename == "step": fittool.steptool.activate() elif curvename == "door": fittool.doortool.activate() elif curvename == "gaussian_derivative": fittool.gaussdertool.activate() elif curvename == "lorentzian_derivative": fittool.lorentzdertool.activate() elif curvename == "gaussian_lorentzian": fittool.gausslortool.activate() elif curvename == "skew": fittool.skewtool.activate() self.initparams(settry=True) self.drawtry() # self.gausscurve.action,self.lorentzcurve.action,self.voigtcurve.action,self.stepcurve.action,self.doorcurve.action def set_fixed(self): self.trackchange = False item = self.item(self.int1, 4) item.setText(_fromUtf8("Fixed")) entry = str(self.item(self.int1, 0).text()) self.extendedparams.partry[entry].vary = False self.extendedparams.partry[entry].expr = None # nothing to do with self.extendedparams.partry[entry].express, we keep it in memory self.trackchange = True def set_free(self): self.trackchange = False item = self.item(self.int1, 4) item.setText(_fromUtf8("Free")) entry = str(self.item(self.int1, 0).text()) self.extendedparams.partry[entry].vary = True self.extendedparams.partry[entry].expr = None # nothing to do with self.extendedparams.partry[entry].express, we keep it in memory self.trackchange = True def use_expr(self): self.trackchange = False item = self.item(self.int1, 4) item.setText(_fromUtf8("Expr")) entry = str(self.item(self.int1, 0).text()) txt = str(self.item(self.int1, 7).text()) self.extendedparams.partry[ entry ].express = txt # in principle, should have been already set if len(txt) != 0: # on met l'expression indiquee self.extendedparams.partry[entry].expr = txt self.extendedparams.partry[entry].vary = False self.trackchange = True def inverse_restrains(self): # inverse les contraintes: les parametres libres deviennent fixes et vice-versa self.trackchange = False for int1 in range(self.rowCount()): item = self.item(int1, 4) entry = str(self.item(int1, 0).text()) vary = self.extendedparams.partry[entry].vary if vary: self.extendedparams.partry[entry].vary = False item.setText(_fromUtf8("Fixed")) else: self.extendedparams.partry[entry].vary = True item.setText(_fromUtf8("Free")) self.trackchange = True def set_wholeastry(self): self.trackchange = False self.extendedparams.setwholefitastry() self.initparams(settry=True, setopt=True) self.trackchange = True def set_as_try(self): self.trackchange = False item = self.item(self.int1, 1) entry = str(self.item(self.int1, 0).text()) if entry in self.extendedparams.paropt: self.extendedparams.partry[entry].value = self.extendedparams.paropt[ entry ].value item.setText("%f" % (self.extendedparams.paropt[entry].value)) self.drawtry() self.trackchange = True def drawtrypart(self): pass def showfitpart(self): pass def startfit(self): self.extendedparams.startfit() self.isfitted = True self.issaved = False self.drawopt() self.initparams(settry=False, setopt=True) self.emit(self.SIG_FIT_DONE) def drawtry(self): xmin, xmax = self.plot.get_axis_limits("bottom") xtry = np.linspace(xmin, xmax, num=10000) ytry = model( self.extendedparams.partry, xtry, (self.extendedparams.degpol, self.extendedparams.curveslist), ) self.plot.curvetry.set_data(xtry, ytry) self.plot.show_items((self.plot.curveexp, self.plot.curvetry)) self.plot.hide_items((self.plot.curveopt,)) def drawopt(self): xmin, xmax = self.plot.get_axis_limits("bottom") xopt = np.linspace(xmin, xmax, num=10000) yopt = model( self.extendedparams.paropt, xopt, (self.extendedparams.degpol, self.extendedparams.curveslist), ) self.plot.curveopt.set_data(xopt, yopt) self.plot.show_items((self.plot.curveexp, self.plot.curveopt)) self.plot.hide_items((self.plot.curvetry,)) def save_fit(self): # sauvegarde des donnees if self.savename is None: self.setfilename() # definit le nom de fichier if self.savename is not None: # dans le cas contraire, c'est qu'on a annule a l'etape precedente self.fic = open(self.savename, "a") # on ecrit le nom des variables lig = "Scan " # on ecrit le nom des variables passes en tags for kk in self.tags: lig = lig + kk[0] + " " # on ecrit la valeur des integrales brutes if self.saveint: lig = lig + "int_tot " for i in range(len(self.extendedparams.curveslist)): lig = lig + "int_%d " % (i + 1) # on ecrit le nom des variables de fit for kk in self.extendedparams.partry: lig = lig + kk + " " lig = lig + " type \n" # print lig self.fic.write(lig) # on ecrit la valeur des variables title = self.scantitle lig = title + " " # on ecrit la valeur des variables passes en tags for kk in self.tags: lig = lig + kk[1] + " " # on ecrit la valeur des integrales brutes if self.saveint: self.ints = integral( self.extendedparams.paropt, self.extendedparams.curveslist ) lig = lig + "%g " % (self.ints[0]) for i in range(len(self.extendedparams.curveslist)): if self.ints[i + 1] is None: lig = lig + "None " else: lig = lig + "%g " % (self.ints[i + 1]) for kk in self.extendedparams.partry: lig = lig + "%g " % (self.extendedparams.paropt[kk].value) typ = "" for func in self.extendedparams.curveslist: typ = typ + func[0] # on garde en abreviation le 1er caractere " lig = lig + typ + "\n" # print lig self.fic.write(lig) # on ecrit une ligne avec les erreurs lig = "sigma " if self.saveint: # ici on n'a pas integre l'evaluation des integrale car elle necessite # la diagonalisation de la matrice des covariances. lig = lig + "0. " for i in range(len(self.extendedparams.curveslist)): lig = lig + "0. " for kk in self.extendedparams.partry: if self.extendedparams.paropt[kk].stderr is None: lig = lig + "None " else: lig = lig + "%g " % (self.extendedparams.paropt[kk].stderr) lig = lig + typ + "\n" self.fic.write(lig) self.fic.close() # on sauve la figure de fit ific = 1 figshortname = self.scantitle + "_%.3d.png" % (ific) figname = path.join(self.cwd, figshortname) while path.isfile(figname): ific = ific + 1 figshortname = self.scantitle + "_%.3d.png" % (ific) figname = path.join(self.cwd, figshortname) print(figname, " sauve") self.plot.save_widget(figname) self.issaved = True if self.updatestart: self.set_wholeastry() self.emit(self.SIG_FIT_DONE) def setfilename(self): self.savename = str( QFileDialog.getSaveFileName( None, "Save fit parameters", self.cwd, filter="*.txt" ) ) if len(self.savename) == 0: self.savename = None else: self.cwd = path.dirname(path.realpath(self.savename)) def rangemove(self, shape): # on recalcule le masque items = self.plot.get_items(z_sorted=False, item_type=IShapeItemType) ranges = list() for item in items: ranges.append(item.get_range()) if shape not in items: ranges.append(shape.get_range()) # alors il s'agit d'une nouvelle XRangeSelection self.extendedparams.mask(ranges) def rangeremove(self, shape): self.setrange() def setrange(self): # on recalcule le masque items = self.plot.get_items(z_sorted=False, item_type=IShapeItemType) ranges = list() for item in items: ranges.append(item.get_range()) self.extendedparams.mask(ranges) def shiftguess(self, shift): # on shifte toutes les positions guess des courbes # attention, si les positions sont fixes, ca decale les courbes quand meme # si on ne veut pas decaler, alors il faut mettre la valeur dans Expression for int1 in range(self.rowCount()): txt = str(self.item(int1, 1).text()) entry = str(self.item(int1, 0).text()) if "pos" in entry[:3]: # alors c'est un parametre de position a shifter val = float(txt) + shift self.item(int1, 1).setText("%f" % (val)) class ExtendedParams: # dictionnaire de la liste des courbes pour le fit # utilise pour comprendre les Parameters de lmfit # ne pilote jamais le tracage des courbes et l'ecriture dans le tableau def __init__(self): self.degpol = -1 # degre du polynome de fond self.ncurves = 0 # nombre de courbes self.curveslist = ( list() ) # liste d'un dictionnaire comprenant type de courbe et nom des parametres self.partry = Parameters() # parametres d'essai self.paropt = Parameters() # parametres optimises self.xexp = np.zeros((1,)) self.yexp = np.zeros((1,)) self.tags = [] def printparams(self): print("printparams") for key in self.partry.keys(): print(key, self.partry[key].value, self.partry[key].vary) def addbackground(self): k = self.degpol + 1 kstr = "%d" % (k) entry = "pol" + kstr self.degpol = k self.partry.add(entry, value=0.00) self.partry[ entry ].number = ( 0 # on rajoute une reference au nombre de la courbe (0 pour le polynome) ) self.partry[ entry ].express = "" # on ajoute a la classe une variable 'express' qui est la valeur de l'expression pas forcement appliquee (pour la garder en memoire) def removebackground(self): k = self.degpol kstr = "%d" % (k) entry = "pol" + kstr del self.partry[entry] self.degpol = k - 1 def addcurve(self, curvename="gausian", paramvalues=None): # ajoute une courbe dans la liste parametres self.curveslist.append(curvename) k = self.ncurves + 1 if paramvalues is None: # on prend les valeurs par defaut for ent, val, vmin, vmax in curveparams[curvename]: entry = ent + "%d" % (k) self.partry.add(entry, value=val) self.partry[entry].express = "" self.partry[entry].number = k if vmin is not None: self.partry[entry].min = vmin if vmax is not None: self.partry[entry].max = vmax else: # on prend les valeurs de paramvalues for cp, val in zip(curveparams[curvename], paramvalues): entry = cp[0] + "%d" % (k) self.partry.add(entry, value=val) self.partry[entry].express = "" self.partry[entry].number = k vmin = cp[2] vmax = cp[3] if vmin is not None: self.partry[entry].min = vmin if vmax is not None: self.partry[entry].max = vmax self.ncurves = k def freezecurve(self, k): # freeze tout les parametres associes a la courbe curvename = self.curveslist[k - 1] print(curvename) for cp in curveparams[curvename]: ent = cp[0] entry = ent + "%d" % (k) self.partry[entry].vary = False print(entry, "Fixed") def releasecurve(self, k): # freeze tout les parametres associes a la courbe curvename = self.curveslist[k - 1] print(curvename) for cp in curveparams[curvename]: ent = cp[0] entry = ent + "%d" % (k) self.partry[entry].vary = True print(entry, "Free") def removecurve(self, k): ncurve = len(self.curveslist) curvename = self.curveslist.pop(k - 1) for cp in curveparams[curvename]: ent = cp[0] entry = ent + "%d" % (k) del self.partry[entry] # reorganisation des courbes for k2 in range(k, ncurve): curvename = self.curveslist[k2 - 1] for cp in curveparams[curvename]: ent = cp[0] entry = ent + "%d" % (k2 + 1) entry2 = ent + "%d" % (k2) # on redescend les courbes d'une unite self.partry.add( entry2, value=self.partry[entry].value, vary=self.partry[entry].vary, min=self.partry[entry].min, max=self.partry[entry].max, expr=self.partry[entry].expr, ) self.partry[entry2].number = k2 self.partry[entry2].express = "" del self.partry[entry] self.ncurves = self.ncurves - 1 def changecurve(self, paramvalues=None, icurve=-1): # icurve est le numero de la liste des courbes, a partir de 0 curvename = self.curveslist[icurve] k = icurve + 1 if icurve == -1: k = len(self.curveslist) if paramvalues is None: # on prend les valeurs par defaut for cp in curveparams[curvename]: ent = cp[0] val = cp[1] entry = ent + "%d" % (k) self.partry[entry].value = val # print entry,self.partry[entry] else: # on prend les valeurs de paramvalues for cp, val in zip(curveparams[curvename], paramvalues): entry = cp[0] + "%d" % (k) self.partry[entry].value = val def setvalexp(self, xexp, yexp): self.xexp = xexp self.yexp = yexp self.xexp0 = xexp self.yexp0 = yexp def mask(self, ranges): xexp = np.ma.array(self.xexp0) for rang in ranges: xexp = np.ma.masked_inside(xexp, rang[0], rang[1]) mask = np.ma.getmaskarray(xexp) yexp = np.ma.array(self.yexp0, mask=mask) self.xexp = xexp.compressed() self.yexp = yexp.compressed() def startfit(self): # self.paropt=copy.deepcopy(self.partry) """ print "self.paropt:",self.paropt print "self.degpol:",self.degpol print "self.curveslist:",self.curveslist print "self.xexp",self.xexp print "self.yexp",self.yexp """ # modification of lmfit result = minimize( residual, self.partry, args=(self.xexp, self.yexp, (self.degpol, self.curveslist)), ) self.paropt = result.params result.params.pretty_print() """ for kk in self.partry: pkt=self.partry[kk].value pkmi=self.partry[kk].min pkma=self.partry[kk].max pko=self.paropt[kk].value pkv=self.paropt[kk].vary pke=self.paropt[kk].expr print kk,pkt,pko,pkv,pkmi,pkma,pke """ self.integrate() print(integral(self.paropt, self.curveslist)) def integrate(self): # calcule l'integrale brute en enlevant le fond self.int1 = 0.0 self.int2 = 0.0 ncurv = len(self.curveslist) if ncurv > 0: integraltot = simps(self.yexp, self.xexp) # integraltot=np.sum(self.yexp) ybg = model(self.paropt, self.xexp, (self.degpol, list())) # self.int1=integraltot-np.sum(ybg) self.int1 = integraltot - simps(ybg, self.xexp) print("integrale brute fond soustrait:", self.int1) # calcule l'integrale brute en enlevant toutes les courbes sauf la premiere self.parpart = copy.deepcopy(self.paropt) self.parpart["int1"].value = 0.0 ybg = model(self.parpart, self.xexp, (self.degpol, self.curveslist)) # self.int2=integraltot-np.sum(ybg) self.int2 = integraltot - simps(ybg, self.xexp) print("integrale brute fond + courbes >1 soustraits:", self.int2) yth = model(self.paropt, self.xexp, (-1, self.curveslist)) self.int3 = simps(yth, self.xexp) print("integrale numerique courbes simulees:", self.int3) def setwholefitastry(self): for kk in self.partry: if kk in self.paropt: self.partry[kk].value = self.paropt[kk].value def updatetags(self, tags): # on supprime les entrees precedentes: for tag in self.tags: if tag[0] in tags: # le tag existe encore, on update sa valeur self.partry[tag[0]].value = tag[1] else: del self.partry[tag[0]] # le tag a disparu, on le supprime for tag in tags: if tag[0] not in tags: # le tag n'existe pas encore, on l'ajoute self.partry.add(tag[0], value=tag[1], vary=False) self.tags = tags class MaskTool(BaseCursorTool): TITLE = _("Mask data") ICON = "xrange.png" SWITCH_TO_DEFAULT_TOOL = True def __init__( self, manager, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None ): super(MaskTool, self).__init__( manager, toolbar_id, title=title, icon=icon, tip=tip ) self._last_item = None def get_last_item(self): if self._last_item is not None: return self._last_item() def create_shape(self): return XRangeSelection(0, 0) def move(self, filter, event): super(MaskTool, self).move(filter, event) def end_move(self, filter, event): super(MaskTool, self).end_move(filter, event) def get_associated_item(self, plot): items = plot.get_selected_items(item_type=ICurveItemType) if len(items) == 1: self._last_item = weakref.ref(items[0]) return self.get_last_item() def update_status(self, plot): pass # item = self.get_associated_item(plot) # self.action.setEnabled(item is not None) class FitTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("Fit"), icon=gausspath, tip=_("Add curve for fit"), toolbar_id=toolbar_id, ) self.manager = manager def create_action_menu(self, manager): # Create and return menu for the tool's action """ self.gausscurve = manager.add_tool(RectangularActionToolCX,actiongauss, toolbar_id=None, icon="rectangle.png",title = "Gauss") self.lorentzcurve = manager.add_tool(RectangularActionToolCX,actionlorentz, toolbar_id=None, icon="rectangle.png",title = "Lorentz") self.voigtcurve = manager.add_tool(RectangularActionToolCX,actionvoigt, toolbar_id=None, icon="rectangle.png",title = "Voigt") self.stepcurve = manager.add_tool(RectangularActionToolCXY ,actionstep, toolbar_id=None, icon="rectangle.png",title = "Step") self.doorcurve = manager.add_tool(RectangularActionToolCX,actiondoor, toolbar_id=None, icon="rectangle.png",title = "Door") """ gaussaction = QAction(QIcon(gausspath), "Gaussian", self) self.connect(gaussaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewgauss) lorentzaction = QAction(QIcon(gausspath), "Lorentzian", self) self.connect( lorentzaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewlorentz ) voigtaction = QAction(QIcon(gausspath), "Voigt", self) self.connect(voigtaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewvoigt) stepaction = QAction(QIcon(steppath), "Step", self) self.connect(stepaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewstep) dooraction = QAction(QIcon(doorpath), "Door", self) self.connect(dooraction, SIGNAL(_fromUtf8("triggered()")), self.drawnewdoor) gaussderaction = QAction(QIcon(gaussderpath), "Gaussian_derivative", self) self.connect( gaussderaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewgaussder ) lorentzderaction = QAction(QIcon(gaussderpath), "Lorentz_derivative", self) self.connect( lorentzderaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewlorentzder ) gaussloraction = QAction(QIcon(gausslorpath), "Gaussian_lorentzian", self) self.connect( gaussloraction, SIGNAL(_fromUtf8("triggered()")), self.drawnewgausslor ) skewaction = QAction(QIcon(skewpath), "Skew", self) self.connect(skewaction, SIGNAL(_fromUtf8("triggered()")), self.drawnewskew) resetaction = QAction(QIcon(eraserpath), "Reset", self) self.connect(resetaction, SIGNAL(_fromUtf8("triggered()")), self.reset) menu = QMenu() # add_actions(menu, (self.gausscurve.action,self.lorentzcurve.action,self.voigtcurve.action,self.stepcurve.action,self.doorcurve.action,resetaction)) add_actions( menu, ( gaussaction, lorentzaction, voigtaction, stepaction, dooraction, gaussderaction, lorentzderaction, gaussloraction, skewaction, resetaction, ), ) self.gausstool = self.manager.add_tool( RectangularActionToolCX, actiongauss, toolbar_id=None, icon="rectangle.png", title="Gauss", ) self.lorentztool = self.manager.add_tool( RectangularActionToolCX, actionlorentz, toolbar_id=None, icon="rectangle.png", title="Lorentz", ) self.voigttool = self.manager.add_tool( RectangularActionToolCX, actionvoigt, toolbar_id=None, icon="rectangle.png", title="Voigt", ) self.steptool = self.manager.add_tool( RectangularActionToolCXY, actionstep, toolbar_id=None, icon="rectangle.png", title="Step", ) self.doortool = self.manager.add_tool( RectangularActionToolCX, actiondoor, toolbar_id=None, icon="rectangle.png", title="Door", ) self.gaussdertool = self.manager.add_tool( RectangularActionToolCX, actiongaussder, toolbar_id=None, icon="rectangle.png", title="GaussDer", ) self.lorentzdertool = self.manager.add_tool( RectangularActionToolCX, actionlorentzder, toolbar_id=None, icon="rectangle.png", title="LorentzDer", ) self.gausslortool = self.manager.add_tool( RectangularActionToolCX, actiongausslor, toolbar_id=None, icon="rectangle.png", title="GaussLor", ) self.skewtool = self.manager.add_tool( RectangularActionToolCX, actionskew, toolbar_id=None, icon="rectangle.png", title="Skew", ) # print "g",self.gausstool # print "l",self.lorentztool # print "v",self.voigttool # print "s",self.steptool # print "d",self.doortool self.action.setMenu(menu) return menu def reset(self): plot = self.get_active_plot() self.manager.activate_default_tool() plot.tabval.reset() plot.tabval.extendedparams.setvalexp(plot.xexp, plot.yexp) plot.tabval.drawtry() def drawnewgauss(self): # print "drawnewgauss" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.gausstool.activate() def drawnewlorentz(self): # print "drawnewlorentz" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.lorentztool.activate() def drawnewvoigt(self): # print "drawnewvoigt" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.voigttool.activate() def drawnewstep(self): # print "drawnewstep" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.steptool.activate() def drawnewdoor(self): # print "drawnewdoor" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.doortool.activate() def drawnewgaussder(self): # print "drawnewgauss" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.gaussdertool.activate() def drawnewlorentzder(self): # print "drawnewgauss" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.lorentzdertool.activate() def drawnewgausslor(self): # print "drawnewgauss" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.gausslortool.activate() def drawnewskew(self): # print "drawnewgauss" plot = self.get_active_plot() plot.tabval.setactivecurve("new") self.skewtool.activate() def handle_shape(self, shape, inside): shape.set_style("plot", "shape/mask") shape.set_private(True) plot = self.get_active_plot() plot.set_active_item(shape) self._mask_shapes[plot] += [(shape, inside)] def deactivate(self): """Deactivate tools""" print("deactivate") self.gausscurve.deactivate() self.lorentzcurve.deactivate() self.voigtcurve.deactivate() self.stepcurve.deactivate() self.doorcurve.deactivate() self.gaussdercurve.deactivate() self.lorentzdercurve.deactivate() print("deactivate done") class RunTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): """ CommandTool.__init__(self, manager, _("Run"),icon=QIcon("Run.png"), tip=_("Perform fit"), toolbar_id=toolbar_id) """ CommandTool.__init__( self, manager, _("Run"), icon=runpath, tip=_("Perform fit"), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): plot = self.get_active_plot() plot.tabval.startfit() # Activate tool pass class YFullRangeTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("Full Range"), icon=yfullrangepath, tip=_("Set Y Full Range"), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): plot = self.get_active_plot() vmin = np.amin(plot.tabval.extendedparams.yexp) vmax = np.amax(plot.tabval.extendedparams.yexp) plot.set_axis_limits("left", vmin, vmax) plot.replot() class PrefTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): CommandTool.__init__( self, manager, _("Run"), icon="settings.png", tip=_("Preferences"), toolbar_id=toolbar_id, ) self.manager = manager def create_action_menu(self, manager): # Create and return menu for the tool's action self.saveprefaction = QAction("Save as...", self) self.connect( self.saveprefaction, SIGNAL(_fromUtf8("triggered()")), self.savepref ) self.updatestartaction = manager.create_action( _("Start with last fit"), toggled=self.updatestart ) self.updatestartaction.setChecked(False) self.showtagsaction = QAction("Show tags", self) self.connect( self.showtagsaction, SIGNAL(_fromUtf8("triggered()")), self.showtags ) self.scaleprefaction = QAction("Autoscale", self) self.scaleprefaction.setCheckable(True) self.scaleprefaction.setChecked(True) menu = QMenu() # add_actions(menu, (self.gausscurve.action,self.lorentzcurve.action,self.voigtcurve.action,self.stepcurve.action,self.doorcurve.action,resetaction)) add_actions( menu, ( self.saveprefaction, self.updatestartaction, self.showtagsaction, self.scaleprefaction, ), ) self.action.setMenu(menu) return menu def savepref(self): # print "drawnewlorentz" plot = self.get_active_plot() plot.tabval.setfilename() def showtags(self): # print "drawnewlorentz" plot = self.get_active_plot() tags = plot.tabval.extendedparams.tags # print 'tags' TagsWindow(tags) def updatestart(self): # print "drawnewlorentz" plot = self.get_active_plot() if self.updatestartaction.isChecked(): plot.tabval.updatestart = True else: plot.tabval.updatestart = False def setupdatestart(self, value): plot = self.get_active_plot() self.updatestartaction.setChecked(value) plot.tabval.updatestart = value class SaveFitTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): super(SaveFitTool, self).__init__( manager, _("Save fit result"), get_std_icon("DialogSaveButton", 16), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): if plot.tabval.isfitted: plot.tabval.save_fit() class RectangularSelectionHandlerCX(RectangularSelectionHandler): # on utilise la classe heritee dont on surcharge la methode move def move(self, filter, event): """methode surchargee par la classe """ sympos = QPoint(2 * self.start.x() - event.pos().x(), self.start.y()) self.shape.move_local_point_to(self.shape_h0, sympos) self.shape.move_local_point_to(self.shape_h1, event.pos()) self.move_action(filter, event) filter.plot.replot() class RectangularActionToolCX(RectangularActionTool): # outil de tracage de rectangles centre en x # on utilise la classe heritee dont on surcharge la methode setup_filter def setup_filter( self, baseplot ): # partie utilisee pendant le mouvement a la souris # utilise a l'initialisation de la toolbar # print "setup_filter" filter = baseplot.filter start_state = filter.new_state() handler = RectangularSelectionHandlerCX( filter, Qt.LeftButton, start_state=start_state # gestionnaire du filtre ) shape, h0, h1 = self.get_shape() shape.pen.setColor(QColor("#00bfff")) handler.set_shape( shape, h0, h1, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE ) self.connect(handler, SIG_END_RECT, self.end_rect) # self.connect(handler, SIG_CLICK_EVENT, self.start) #a definir aussi dans RectangularSelectionHandler2 return setup_standard_tool_filter(filter, start_state) def activate(self): """Activate tool""" # print "commande active",self for baseplot, start_state in self.start_state.items(): baseplot.filter.set_state(start_state, None) self.action.setChecked(True) self.manager.set_active_tool(self) # plot = self.get_active_plot() # plot.newcurve=True def deactivate(self): """Deactivate tool""" # print "commande desactivee",self self.action.setChecked(False) class RectangularSelectionHandlerCXY(RectangularSelectionHandler): # on utilise la classe heritee dont on surcharge la methode move def move(self, filter, event): """methode surchargee par la classe """ sympos = QPoint( 2 * self.start.x() - event.pos().x(), 2 * self.start.y() - event.pos().y() ) self.shape.move_local_point_to(self.shape_h0, sympos) self.shape.move_local_point_to(self.shape_h1, event.pos()) self.move_action(filter, event) filter.plot.replot() class RectangularActionToolCXY(RectangularActionTool): # outil de tracage de rectangles centre en x # on utilise la classe heritee dont on surcharge la methode setup_filter def setup_filter( self, baseplot ): # partie utilisee pendant le mouvement a la souris # utilise a l'initialisation de la toolbar # print "setup_filter" filter = baseplot.filter start_state = filter.new_state() handler = RectangularSelectionHandlerCXY( filter, Qt.LeftButton, start_state=start_state # gestionnaire du filtre ) shape, h0, h1 = self.get_shape() shape.pen.setColor(QColor("#00bfff")) handler.set_shape( shape, h0, h1, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE ) self.connect(handler, SIG_END_RECT, self.end_rect) # self.connect(handler, SIG_CLICK_EVENT, self.start) #a definir aussi dans RectangularSelectionHandler2 return setup_standard_tool_filter(filter, start_state) def activate(self): """Activate tool""" # print "commande active",self for baseplot, start_state in self.start_state.items(): baseplot.filter.set_state(start_state, None) self.action.setChecked(True) self.manager.set_active_tool(self) # plot = self.get_active_plot() # plot.newcurve=True def deactivate(self): """Deactivate tool""" # print "commande desactivee",self self.action.setChecked(False) def get_xiwb_cx(plot, p0, p1): # return position, intensity,fwhm and background from the rectangular tool cx ax, ay = plot.get_axis_id("bottom"), plot.get_axis_id("left") x1, y1 = plot.invTransform(ax, p0.x()), plot.invTransform(ay, p0.y()) x2, y2 = plot.invTransform(ax, p1.x()), plot.invTransform(ay, p1.y()) return x1, y1 - y2, 2.0 * (x2 - x1), y2 def get_xiwb_cxy(plot, p0, p1): # return position, intensity,width and background from the rectangular tool cxy ax, ay = plot.get_axis_id("bottom"), plot.get_axis_id("left") x1, y1 = plot.invTransform(ax, p0.x()), plot.invTransform(ay, p0.y()) x2, y2 = plot.invTransform(ax, p1.x()), plot.invTransform(ay, p1.y()) return x1, 2 * (y1 - y2), 2.0 * (x2 - x1), y2 def actiongauss(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("gaussian", (itry, x0try, wtry), bg=btry) # lim=plot.get_axis_limits("bottom") # xtry=np.linspace(lim[0],lim[1], num=100) # ytry=gauss(xtry,[itry,x0try,wtry])+btry """ #pour rajouter un polygone points=np.concatenate((xtry,ytry)) points=points.reshape((2,100)) points=np.transpose(points) testpolygon=PolygonShape(points=points) testpolygon.set_selectable(False) plot.add_item(testpolygon) """ def actionlorentz(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("lorentzian", (itry, x0try, wtry), bg=btry) def actionvoigt(plot, p0, p1): # trace une pseudo-voigt avec poids egal sur les pics lorentz et gauss x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("voigt", (itry, x0try, wtry, 0.5), bg=btry) def actionstep(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cxy(plot, p0, p1) plot.tabval.set_cv("step", (itry, x0try, wtry), bg=btry) def actiondoor(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("door", (itry, x0try, wtry, wtry / 4.0), bg=btry) def actiongaussder(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("gaussian_derivative", (itry, x0try, wtry), bg=btry + itry / 2.0) def actiongausslor(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("gaussian_lorentzian", (itry, x0try, wtry, 0.1), bg=btry) def actionlorentzder(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("gaussian_derivative", (itry, x0try, wtry), bg=btry + itry / 2.0) def actionskew(plot, p0, p1): x0try, itry, wtry, btry = get_xiwb_cx(plot, p0, p1) plot.tabval.set_cv("skew", (itry, x0try, wtry, 0.0), bg=btry) class Ui_FitWindow(QMainWindow): def setupUi(self): self.setObjectName(_fromUtf8("MainWindow")) self.setWindowTitle("Fit window") self.savename = None # on utilise un splitter pour separer la fenetre en deux, ce sera le central widget self.splitter = QSplitter(Qt.Vertical) # widget pour afficher les courbes self.cv = CurveWidget(parent=self, show_itemlist=True) # widget pour afficher les donnees du fit , gerer les entrees de parametres self.tabval = FitTable(parent=self) self.splitter.addWidget(self.cv) self.splitter.addWidget(self.tabval) self.setCentralWidget(self.splitter) # on utilise le plot manager de guiqwt self.manager = PlotManager(self) self.plot = self.cv.get_plot() # on associe les references croisees au tableau des donnees et plot self.plot.tabval = self.tabval self.tabval.plot = self.plot # attribut pour dire qu'on a demande une nouvelle courbe qui n'est pas encore ajustee a la souris self.plot.newcurve = False # default empty curves z = np.empty((0,)) self.empty = True self.plot.curveexp = make.curve( z, z, marker="Diamond", markerfacecolor="r", markeredgecolor="r", markersize=4, linestyle="NoPen", title="experiment", ) self.plot.curvetry = make.curve(z, z, color="g", title="estimate") self.plot.curveopt = make.curve(z, z, color="k", title="fit result") self.plot.curveexp.set_readonly(True) self.plot.curvetry.set_readonly(True) self.plot.curveopt.set_readonly(True) self.plot.add_item(self.plot.curveexp) self.plot.add_item(self.plot.curvetry) self.plot.add_item(self.plot.curveopt) self.plot.hide_items( (self.plot.curveexp, self.plot.curvetry, self.plot.curveopt) ) self.manager.add_plot(self.plot) self.connect(self.plot, SIG_RANGE_CHANGED, self.tabval.rangemove) self.connect(self.plot, SIG_ITEM_REMOVED, self.tabval.rangeremove) # self.connect(self.tabval, SIG_FIT_DONE, self.coucou) # ---Add toolbar and register manager tools toolbar = self.addToolBar("tools") self.manager.add_toolbar(toolbar, id(toolbar)) # self.manager.register_all_curve_tools() self.manager.register_standard_tools() self.manager.register_other_tools() self.oft = self.manager.add_tool(OpenFileTool) self.oft.formats = " *.txt *.dat" self.title = "Open data file" self.oft.connect(self.oft, SIGNAL("openfile(QString*)"), self.open_file) self.FitTool = self.manager.add_tool(FitTool) # ok self.MaskTool = self.manager.add_tool(MaskTool) self.manager.add_tool(RunTool) # ok self.manager.add_tool(SaveFitTool) self.manager.add_tool(YFullRangeTool) self.Pref_Tool = self.manager.add_tool(PrefTool) self.Pref_Tool.setupdatestart(self.tabval.updatestart) # redimensionnement de la fenetre principale, les widgets suivent # sinon, prennent leur SizeHint (300,400) pour cv par exemple self.resize(820, 600) self.readfileparams = readfileparams() # pour savoir si un fit a ete fait et sauve def coucou(self): print("coucou") def reset(self): self.FitTool.reset() # testpolygon=PolygonShape(points=[[1,0],[1.1,0.1],[1.2,0.5],[1.4,2]]) # self.plot.add_item(testpolygon) def open_file(self, QString): fname = str(QString) shortname = path.split(fname)[1] fic = open(fname) ficlines = fic.readlines() readfilewindow(self.readfileparams, ficlines) seps = [" ", "\t", ","] sep = seps[self.readfileparams.delimiter] nf = len(ficlines) ix = self.readfileparams.x - 1 iy = self.readfileparams.y - 1 if self.readfileparams.title: try: ni = self.readfileparams.heading + 1 title = ficlines[self.readfileparams.heading].split(sep) print(title) xlabel = title[ix] ylabel = title[iy] except: ni = self.readfileparams.heading xlabel = "x" ylabel = "y" else: ni = self.readfileparams.heading xlabel = "x" ylabel = "y" x = [] y = [] for i in range(ni, nf): try: ll = ficlines[i].split(sep) xx = float(ll[ix]) yy = float(ll[iy]) if np.isfinite(xx) and np.isfinite(yy): x.append(xx) y.append(yy) except: pass self.tabval.cwd = path.dirname(path.realpath(fname)) self.setvalexp(x, y, xlabel=xlabel, ylabel=ylabel, title=shortname) def settags(self, tags, saveint=None): # tags est une liste de couples de valeurs transmis par l'application qui sollicite grafit pour # etre enregistre dans le fichier de sauvegarde self.tabval.extendedparams.updatetags(tags) # soit saveint n'est pas modifie et rien ne change, soit saveint l'est et on change if saveint is not None: self.saveint = saveint self.tabval.saveint = saveint if self.saveint: print("on sauve les integrales") else: print("on ne sauve pas les integrales") def setupdatestart(self, value): self.tabval.updatestart = value self.Pref_Tool.setupdatestart(self.tabval.updatestart) def getfitresult(self): return [ self.tabval.extendedparams.paropt, integral( self.tabval.extendedparams.paropt, self.tabval.extendedparams.curveslist ), ] def setsaveint(self, value=True): self.tabval.saveint = value def setsavedir(self, cwd): self.tabval.cwd = cwd def setvalexp( self, xexp, yexp, xlabel="x", ylabel="y", title="scan", xlog=False, ylog=False, tags=[], followscans=False, ): # title est le titre du scan qui est fitte. xlog et ylog determinent si l'echelle est log ou pas # infos est une liste de deux strings qui sont enregistrees en plus des parametres du fit # lors de la sauvegarde des donnes. Gere par le programme qui appelle grafit. i = 1 if self.tabval.isfitted and self.tabval.issaved is False: # avant d'ecraser le fit precedent on demande s'il faut le sauver # pose probleme dans le cas ou setvalexp est genere par le deplacement d'une shape i = QMessageBox.question( self, "save", "do you want to save fit?", "Yes", "No", "Cancel" ) if i == 0: self.tabval.save_fit() # s'il existe deja un fit, et que followscans=True, alors on decale les valeurs de fit # de façon a ce que la position de guess des pics se retrouve au meme endroit par # rapport au centre du scan if followscans and not self.empty: cen0 = (max(self.plot.xexp) + min(self.plot.xexp)) / 2.0 cen1 = (max(xexp) + min(xexp)) / 2.0 shift = cen1 - cen0 print("on shifte de ", shift) self.tabval.shiftguess(shift) if i < 2: # si i=2, on ne fait rien if self.Pref_Tool.scaleprefaction.isChecked(): if xlog: self.plot.set_axis_scale("bottom", "log", autoscale=True) else: self.plot.set_axis_scale("bottom", "lin", autoscale=True) if ylog: self.plot.set_axis_scale("left", "log", autoscale=True) else: self.plot.set_axis_scale("left", "lin", autoscale=True) self.plot.xexp = xexp self.plot.yexp = yexp self.tabval.scantitle = title self.tabval.extendedparams.setvalexp(xexp, yexp) self.settags(tags) self.plot.set_titles(title=title, xlabel=xlabel, ylabel=ylabel) self.plot.curveexp.set_data(xexp, yexp) self.plot.show_items((self.plot.curveexp,)) self.plot.hide_items((self.plot.curvetry, self.plot.curveopt)) self.tabval.isfitted = False # on n'a pas fait de fit sur ce set de donnees self.tabval.setrange() self.plot.do_autoscale() self.empty = False # print xexp.shape,yexp.shape if __name__ == "__main__": app = QApplication(sys.argv) ui = Ui_FitWindow() ui.setupUi() ui.xlabel = "x" ui.ylabel = "y" x = np.arange(0, 3.14, 0.001) y = np.sin(x) ui.setvalexp(x, y) ui.show() sys.exit(app.exec_()) binoculars-0.0.10/scripts/nxsViewer.py000066400000000000000000002006271412510113200177610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Created on Tue Oct 08 10:39:12 2013 @author: Prevot """ import numpy as np from scipy.interpolate import griddata from guidata.qt import QtCore, QtGui import os import tables # from guidata.qt.QtGui import QFont # from guidata.qt.QtCore import Qt from guiqwt.plot import CurveDialog from guiqwt.builder import make from guiqwt.signals import ( SIG_ACTIVE_ITEM_CHANGED, SIG_START_TRACKING, SIG_MOVE, SIG_STOP_NOT_MOVING, SIG_STOP_MOVING, ) from guiqwt.events import QtDragHandler, setup_standard_tool_filter from guiqwt.tools import InteractiveTool, DefaultToolbarID, CommandTool from guiqwt.interfaces import ICurveItemType from guiqwt.config import _ from guiqwt.shapes import Marker from guiqwt.interfaces import ITrackableItemType # from guidata.qthelpers import get_std_icon from guidata.configtools import get_icon from reciprocal2D import D2Dview try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s # import guidata # _app = guidata.qapplication() degtorad = np.pi / 180.0 """ paramsdict = {a[0]:a[1] for a in params} paramslist = list(a[0] for a in params) """ class MeshParameters: # classe pour les parametres de construction d'une map 3D def __init__( self, Nx=1, Ny=1, xmin=0.0, ymin=0.0, xmax=0.0, ymax=0.0, ix=0, iy=0, iz=0, ilog=0, io=0, angle=0.0, ): self.Nx = Nx self.Ny = Ny self.xmin = xmin self.ymin = ymin self.xmax = xmax self.ymax = ymax self.ix = ix self.iy = iy self.iz = iz self.ilog = ilog # intensite en echelle log self.io = io # utiliser la matrice d'orientation self.angle = angle # rajouter cet angle self.bc = False # correction de fond sur chaque scan self.b1 = 0.0 # percentile min pour estimation du background self.b2 = 10.0 # percentile max pour estimation du background class DataFilter: # classe pour les parametres de filtre def __init__(self, value=0.0, role="none"): self.ifil = -1 self.value = value self.role = role self.roles = list(("none", "high-pass", "low-pass", "cut")) self.irole = self.roles.index(self.role) class PrefParameters: # classe pour les parametres de preference # -> affichage du nom des moteurs # -> mise a jour automatique du fit def __init__(self): self.namedisplays = list( ( "my suggestion", "short name", "short and family name", "full name", "nxs name", ) ) self.inamedisplay = 0 self.autoupdate = True self.followscans = True class DataSet: # definit une classe pour rentrer les donnees associees a un fichier nxs def __init__(self, longname, shortname, datafilter=None, pref=None): if pref is None: pref = PrefParameters() self.shortname = shortname self.longname = longname try: fichier = tables.openFile(longname) self.nodedatasizes = list() # liste des longueurs des tableaux de donnees # print shortname,fichier.listNodes('/')[0].start_time[0],fichier.listNodes('/')[0].end_time[0] # for leaf in fichier.__iter__(): # # if leaf._v_name=="temperature": # print leaf._v_name,leaf.read()[0] for leaf in fichier.listNodes("/")[0].scan_data: self.nodedatasizes.append(leaf.shape[0]) self.npts = max(self.nodedatasizes) # on ne selectionne que les noeuds qui ont la meme longueur, il peut y avoir des donnes avec des tableaux de taille plus petite par exemple 1, on laisse tomber self.nodenames = list() # nom des noeuds (ex data_01) self.nodelongnames = list() # nom comprehensible des noeuds (complet) self.nodenicknames = list() # diminutif pour affichage self.data = np.empty(0) # creation d'un tableau vide de depart # print pref.inamedisplay for leaf in fichier.listNodes("/")[0].scan_data: if leaf.shape[0] == self.npts: self.nodenames.append(leaf.name) try: nodelongname = leaf.attrs.long_name except: nodelongname = "" # nodelongname=leaf.attrs.long_name[leaf.attrs.long_name.rfind('/')+1:] if len(nodelongname) == 0: nodelongname = ( leaf.name ) # si pas de nom long on garde le nom nxs self.nodelongnames.append(nodelongname) self.data = np.concatenate( (self.data, leaf.read()[1:]) ) # on ajoute les donnees au tableau np, on enleve le premier point if pref.inamedisplay <= 1: nodenickname = nodelongname.split("/")[ -1 ] # on prend le dernier self.nodenicknames.append(nodenickname) elif pref.inamedisplay == 2: try: namesplit = nodelongname.split("/") nodenickname = ( namesplit[-2] + "/" + namesplit[-1] ) # on prend les deux derniers si possible self.nodenicknames.append(nodenickname) except: self.nodenicknames.append(nodelongname) elif pref.inamedisplay == 3: self.nodenicknames.append( nodelongname ) # on prend le nom long complet elif pref.inamedisplay == 4: self.nodenicknames.append(leaf.name) # on prend le nom nxs except ValueError: print("probleme le fichier ", longname, "est corrompu") self.npts = 0 self.nmotors = 0 self.mins = np.empty(0) self.maxs = np.empty(0) self.data = np.empty(0) fichier.close() return except tables.exceptions.NoSuchNodeError: print("probleme le fichier ", longname, "est corrompu") self.npts = 0 self.nmotors = 0 self.mins = np.empty(0) self.maxs = np.empty(0) self.data = np.empty(0) fichier.close() return else: fichier.close() self.npts = self.npts - 1 # on a enleve le premier point self.nmotors = len(self.nodenames) # nombre de colonnes retenues # si les preferences de display sont "my suggestion" on va regarder si un nom figure plusieurs fois # dans ce cas, on choisit de prendre le nom plus long if pref.inamedisplay == 0: for i in range(self.nmotors - 1): # pas la peine de faire le dernier point! nickname = self.nodenicknames[i] if nickname in self.nodenicknames[i + 1 :]: # alors item en double nodelongname = self.nodelongnames[i] namesplit = nodelongname.split("/") try: nodenickname = ( namesplit[-2] + "/" + namesplit[-1] ) # on prend les deux derniers except: nodenickname = nodelongname # on prend les deux derniers self.nodenicknames[i] = nodenickname j = i try: while 1: j = self.nodenicknames.index(j + 1) self.nodenicknames[ j ] = nodenickname # attention, on ne garantit pas que nodenickname!=nickname except ValueError: pass self.data = self.data.reshape((self.nmotors, self.npts)) # print self.data[0] test = np.any( self.data != 0, axis=0 ) # si une valeur non nulle, la condition est verifiee # print test self.data = np.compress(test, self.data, axis=1) if datafilter is not None: # on filtre les valeurs en regardant la condition sur le filtre # print datafilter.role,datafilter.ifil if datafilter.role != "none" and datafilter.ifil > -1: # print "irole=",datafilter.irole if datafilter.irole == 1: # print "on laisse si col",datafilter.ifil,">",datafilter.value # print self.data[datafilter.ifil]>datafilter.value self.data = np.compress( self.data[datafilter.ifil] > datafilter.value, self.data, axis=1 ) elif datafilter.irole == 2: # print "on laisse si col",datafilter.ifil,"<",datafilter.value self.data = np.compress( self.data[datafilter.ifil] < datafilter.value, self.data, axis=1 ) elif datafilter.irole == 3: # print "on laisse si col",datafilter.ifil,"!=",datafilter.value self.data = np.compress( self.data[datafilter.ifil] != datafilter.value, self.data, axis=1, ) self.npts = self.data.shape[1] # nombre de points non totalement nuls if self.npts == 0: # filtrage tel qu'il n'y a plus de points QtGui.QMessageBox.about(None, "Error", "All points are filtered") self.mins = np.zeros(self.nmotors) self.maxs = np.ones(self.nmotors) else: self.mins = np.amin(self.data, axis=1) # bornes pour chaque parametre self.maxs = np.amax(self.data, axis=1) class Set3DparametersWindow(QtGui.QDialog): # definit une fenetre pour rentrer les parametres de dialogue de construction de map 3D def __init__(self, dataset, params, NX=1, NY=1, mins=None, maxs=None): self.params = params xdef = params.ix ydef = params.iy zdef = params.iz ilog = params.ilog io = params.io angle = params.angle bc = params.bc b1 = params.b1 b2 = params.b2 self.dataset = dataset self.NY = NY # a priori le nombre de scans selectionne super( Set3DparametersWindow, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.setWindowTitle("Parameters for 3D mesh computation") self.setFixedSize(QtCore.QSize(330, 230)) self.mins = mins self.maxs = maxs """ if mins==None: self.mins=np.zeros(dataset.nmotors) else: self.mins=mins if maxs==None: self.maxs=np.zeros(dataset.nmotors) else: self.maxs=maxs """ # print self.mins,self.maxs self.lab1 = QtGui.QLabel(self) self.lab1.setGeometry(QtCore.QRect(35, 5, 100, 25)) self.lab1.setText("component") self.lab2 = QtGui.QLabel(self) self.lab2.setGeometry(QtCore.QRect(140, 5, 55, 25)) self.lab2.setText("bins") self.lab3 = QtGui.QLabel(self) self.lab3.setGeometry(QtCore.QRect(200, 5, 55, 25)) self.lab3.setText("min") self.lab4 = QtGui.QLabel(self) self.lab4.setGeometry(QtCore.QRect(260, 5, 55, 25)) self.lab4.setText("max") self.labx = QtGui.QLabel(self) self.labx.setGeometry(QtCore.QRect(5, 35, 25, 25)) self.labx.setText("x") self.laby = QtGui.QLabel(self) self.laby.setGeometry(QtCore.QRect(5, 65, 25, 25)) self.laby.setText("y") self.labz = QtGui.QLabel(self) self.labz.setGeometry(QtCore.QRect(5, 95, 25, 25)) self.labz.setText("z") self.paramx = QtGui.QComboBox(self) self.paramx.setGeometry(QtCore.QRect(35, 35, 100, 25)) self.paramx.addItems(dataset.nodenicknames) self.paramx.setCurrentIndex(xdef) self.paramy = QtGui.QComboBox(self) self.paramy.setGeometry(QtCore.QRect(35, 65, 100, 25)) self.paramy.addItems(dataset.nodenicknames) self.paramy.addItem("None") self.paramy.setCurrentIndex(ydef) self.paramz = QtGui.QComboBox(self) self.paramz.setGeometry(QtCore.QRect(35, 95, 100, 25)) self.paramz.addItems(dataset.nodenicknames) self.paramz.setCurrentIndex(zdef) self.binx = QtGui.QLineEdit(self) self.binx.setGeometry(QtCore.QRect(140, 35, 45, 25)) self.binx.setText("%d" % (NX)) self.biny = QtGui.QLineEdit(self) self.biny.setGeometry(QtCore.QRect(140, 65, 45, 25)) self.biny.setText("%d" % (NY)) self.zscale = QtGui.QComboBox(self) self.zscale.setGeometry(QtCore.QRect(140, 95, 100, 25)) self.zscale.addItems(("z linear", "z log")) self.zscale.setCurrentIndex(ilog) self.xmin = QtGui.QLineEdit(self) self.xmin.setGeometry(QtCore.QRect(190, 35, 65, 25)) self.ymin = QtGui.QLineEdit(self) self.ymin.setGeometry(QtCore.QRect(190, 65, 65, 25)) self.xmax = QtGui.QLineEdit(self) self.xmax.setGeometry(QtCore.QRect(260, 35, 65, 25)) self.ymax = QtGui.QLineEdit(self) self.ymax.setGeometry(QtCore.QRect(260, 65, 65, 25)) self.changex(xdef) self.changey(ydef) self.io = QtGui.QRadioButton(self) self.io.setGeometry(QtCore.QRect(35, 125, 155, 25)) self.io.setText("use orientation matrix") self.io.setChecked(io) self.angle = QtGui.QLineEdit(self) self.angle.setGeometry(QtCore.QRect(200, 125, 65, 25)) self.angle.setText("%f" % (angle)) self.bc = QtGui.QRadioButton(self) self.bc.setGeometry(QtCore.QRect(35, 155, 155, 25)) self.bc.setText("correct background") self.bc.setChecked(bc) self.b1 = QtGui.QLineEdit(self) self.b1.setGeometry(QtCore.QRect(200, 155, 45, 25)) self.b1.setText("%d" % (b1)) self.b2 = QtGui.QLineEdit(self) self.b2.setGeometry(QtCore.QRect(250, 155, 45, 25)) self.b2.setText("%d" % (b2)) self.OK = QtGui.QPushButton(self) self.OK.setGeometry(QtCore.QRect(5, 185, 90, 25)) self.OK.setText("OK") self.Cancel = QtGui.QPushButton(self) self.Cancel.setGeometry(QtCore.QRect(100, 185, 90, 25)) self.Cancel.setText("Cancel") self.bgroup = QtGui.QButtonGroup(self) self.bgroup.setExclusive(False) self.bgroup.addButton(self.io) self.bgroup.addButton(self.bc) QtCore.QObject.connect( self.Cancel, QtCore.SIGNAL(_fromUtf8("clicked()")), self.closewin ) QtCore.QObject.connect( self.OK, QtCore.SIGNAL(_fromUtf8("clicked()")), self.appl ) QtCore.QObject.connect( self.paramx, QtCore.SIGNAL(_fromUtf8("currentIndexChanged (int)")), self.changex, ) QtCore.QObject.connect( self.paramy, QtCore.SIGNAL(_fromUtf8("currentIndexChanged (int)")), self.changey, ) self.exec_() def changex(self, i): # quand on change x, on change les valeurs min et max de x par defaut self.xmin.setText("%f" % (self.mins[i])) self.xmax.setText("%f" % (self.maxs[i])) def changey(self, i): # quand on change x, on change les valeurs min et max de x par defaut # sauf quand on est sur le dernier de la liste if i < len(self.mins): self.ymin.setText("%f" % (self.mins[i])) self.ymax.setText("%f" % (self.maxs[i])) else: self.ymin.setText("%d" % (1)) self.ymax.setText("%d" % (self.NY)) def appl(self): if self.parent: try: self.params.NX = int(self.binx.text()) self.params.NY = int(self.biny.text()) self.params.xmin = float(self.xmin.text()) self.params.xmax = float(self.xmax.text()) self.params.ymin = float(self.ymin.text()) self.params.ymax = float(self.ymax.text()) self.params.angle = float(self.angle.text()) self.params.b1 = float(self.b1.text()) self.params.b2 = float(self.b2.text()) except Exception: QtGui.QMessageBox.about(self, "Error", "Input can only be a number") return ix = self.paramx.currentIndex() iy = self.paramy.currentIndex() iz = self.paramz.currentIndex() if ix == iy or iy == iz or ix == iy: QtGui.QMessageBox.about(self, "Error", "Select 3 different parameters") return self.params.ix = ix self.params.iy = iy self.params.iz = iz self.params.ilog = self.zscale.currentIndex() self.params.io = self.io.isChecked() self.params.bc = self.bc.isChecked() self.close() def closewin(self): self.close() class SetFilterWindow(QtGui.QDialog): # definit une fenetre pour rentrer les parametres de dialogue de construction de map 3D def __init__(self, datafilter): super( SetFilterWindow, self ).__init__() # permet l'initialisation de la fenetre sans perdre les fonctions associees self.datafilter = datafilter self.setWindowTitle("Filter Policy") self.setFixedSize(QtCore.QSize(200, 100)) # print self.mins,self.maxs self.lab1 = QtGui.QLabel(self) self.lab1.setGeometry(QtCore.QRect(5, 5, 100, 25)) self.lab1.setText("Threshold value") self.lab2 = QtGui.QLabel(self) self.lab2.setGeometry(QtCore.QRect(5, 35, 100, 25)) self.lab2.setText("Filter policy") self.value = QtGui.QLineEdit(self) self.value.setGeometry(QtCore.QRect(105, 5, 90, 25)) self.value.setText("%f" % (datafilter.value)) self.role = QtGui.QComboBox(self) self.role.setGeometry(QtCore.QRect(105, 35, 90, 25)) self.role.addItems(datafilter.roles) self.role.setCurrentIndex(datafilter.irole) self.OK = QtGui.QPushButton(self) self.OK.setGeometry(QtCore.QRect(5, 65, 90, 25)) self.OK.setText("OK") self.Cancel = QtGui.QPushButton(self) self.Cancel.setGeometry(QtCore.QRect(100, 65, 90, 25)) self.Cancel.setText("Cancel") QtCore.QObject.connect( self.Cancel, QtCore.SIGNAL(_fromUtf8("clicked()")), self.closewin ) QtCore.QObject.connect( self.OK, QtCore.SIGNAL(_fromUtf8("clicked()")), self.appl ) self.exec_() def appl(self): self.datafilter.irole = self.role.currentIndex() self.datafilter.role = self.datafilter.roles[self.datafilter.irole] try: self.datafilter.value = float(self.value.text()) except Exception: QtGui.QMessageBox.about(self, "Error", "Input can only be a number") return self.close() def closewin(self): self.close() class SetPrefWindow(QtGui.QDialog): # definit une fenetre pour rentrer les parametres d'affichage des preferences def __init__(self, pref): QtGui.QDialog.__init__(self) self.pref = pref self.setWindowTitle("Preferences") self.setFixedSize(QtCore.QSize(250, 160)) self.lab1 = QtGui.QLabel(self) self.lab1.setGeometry(QtCore.QRect(5, 5, 90, 25)) self.lab1.setText("Name display") self.nameDisplay = QtGui.QComboBox(self) self.nameDisplay.setGeometry(QtCore.QRect(95, 5, 150, 25)) self.nameDisplay.addItems(pref.namedisplays) self.nameDisplay.setCurrentIndex(pref.inamedisplay) self.autoupdate = QtGui.QCheckBox(self) self.autoupdate.setGeometry(QtCore.QRect(5, 35, 150, 25)) self.autoupdate.setText("auto-update fit") self.autoupdate.setChecked(pref.autoupdate) self.followscans = QtGui.QCheckBox(self) self.followscans.setGeometry(QtCore.QRect(5, 65, 150, 25)) self.followscans.setText("follow fitted scans") self.followscans.setChecked(pref.followscans) self.OK = QtGui.QPushButton(self) self.OK.setGeometry(QtCore.QRect(5, 125, 90, 25)) self.OK.setText("OK") self.Cancel = QtGui.QPushButton(self) self.Cancel.setGeometry(QtCore.QRect(100, 125, 90, 25)) self.Cancel.setText("Cancel") QtCore.QObject.connect( self.Cancel, QtCore.SIGNAL(_fromUtf8("clicked()")), self.closewin ) QtCore.QObject.connect( self.OK, QtCore.SIGNAL(_fromUtf8("clicked()")), self.appl ) self.exec_() def appl(self): self.pref.inamedisplay = self.nameDisplay.currentIndex() self.pref.autoupdate = self.autoupdate.isChecked() self.pref.followscans = self.followscans.isChecked() self.close() def closewin(self): self.close() class SelectMultiPointTool(InteractiveTool): TITLE = _("Point selection") ICON = "point_selection.png" MARKER_STYLE_SECT = "plot" MARKER_STYLE_KEY = "marker/curve" CURSOR = QtCore.Qt.PointingHandCursor def __init__( self, manager, mode="reuse", on_active_item=False, title=None, icon=None, tip=None, end_callback=None, toolbar_id=DefaultToolbarID, marker_style=None, switch_to_default_tool=None, ): super(SelectMultiPointTool, self).__init__( manager, toolbar_id, title=title, icon=icon, tip=tip ) # switch_to_default_tool=switch_to_default_tool) assert mode in ("reuse", "create") self.mode = mode self.end_callback = end_callback self.marker = None self.last_pos = None self.on_active_item = on_active_item if marker_style is not None: self.marker_style_sect = marker_style[0] self.marker_style_key = marker_style[1] else: self.marker_style_sect = self.MARKER_STYLE_SECT self.marker_style_key = self.MARKER_STYLE_KEY self.impact = 0 def set_marker_style(self, marker): marker.set_style(self.marker_style_sect, self.marker_style_key) def setup_filter(self, baseplot): filter = baseplot.filter # Initialisation du filtre start_state = filter.new_state() # Bouton gauche : handler = QtDragHandler(filter, QtCore.Qt.LeftButton, start_state=start_state) self.connect(handler, SIG_START_TRACKING, self.start) self.connect(handler, SIG_MOVE, self.move) self.connect(handler, SIG_STOP_NOT_MOVING, self.stop) self.connect(handler, SIG_STOP_MOVING, self.stop) return setup_standard_tool_filter(filter, start_state) def start(self, filter, event): if self.marker is None: title = "" if self.TITLE: title = "%s
" % self.TITLE if self.on_active_item: # constraint_cb = filter.plot.on_active_curve # constraint_cb = self.on_active_curve constraint_cb = lambda x, y: self.on_active_curve(x, y, filter.plot) label_cb = lambda x, y: self.on_active_curve_label(x, y, filter.plot) """ label_cb = lambda x, y: title + \ filter.plot.get_coordinates_str(x, y) """ else: label_cb = lambda x, y: "%sx = %g
y = %g" % (title, x, y) self.marker = Marker(label_cb=label_cb, constraint_cb=constraint_cb) # print self.marker.xValue() : 0 self.set_marker_style(self.marker) self.marker.attach(filter.plot) self.marker.setZ(filter.plot.get_max_z() + 1) self.marker.setVisible(True) def stop(self, filter, event): self.move(filter, event) if self.mode != "reuse": self.marker.detach() self.marker = None if self.end_callback: self.end_callback(self) def move(self, filter, event): if self.marker is None: return # something is wrong ... self.marker.move_local_point_to(0, event.pos()) filter.plot.replot() self.last_pos = self.marker.xValue(), self.marker.yValue() def get_coordinates(self): return self.last_pos def on_active_curve(self, x, y, plot): curve = plot.get_last_active_item(ITrackableItemType) if curve: # x, y = get_closest_coordinates(x, y,curve) ax = curve.xAxis() ay = curve.yAxis() xc = plot.transform(ax, x) yc = plot.transform(ay, y) _distance, i, _inside, _other = curve.hit_test(QtCore.QPoint(xc, yc)) x = curve.x(i) y = curve.y(i) self.impact = i return x, y def on_active_curve_label(self, x, y, plot): curve = plot.get_last_active_item(ITrackableItemType) if curve: label = "valeurs des parametres\n" for i in range(curve.dataset.nmotors): label = ( label + curve.dataset.nodenicknames[i] + ":%f\n" % (curve.dataset.data[i, self.impact]) ) return label class Draw3DTool(CommandTool): def __init__(self, manager, toolbar_id=DefaultToolbarID): super(Draw3DTool, self).__init__( manager, _("Draw a 3D map"), get_icon("histogram2D.png"), toolbar_id=toolbar_id, ) def activate_command(self, plot, checked): """Activate tool""" itemselected = plot.get_selected_items() itemselection = list( item for item in itemselected if ICurveItemType in item.types() ) # on ne prend que les courbes trueselection = list(item.dataset for item in itemselection) if len(trueselection) == 0: print("aucun curveitem selectionne") return Nitem = len(trueselection) item0 = itemselection[0] dataset0 = item0.dataset Nmotors = dataset0.nmotors mins = np.amin( np.concatenate(list(item.mins for item in trueselection)).reshape( Nitem, Nmotors ), axis=0, ) maxs = np.amax( np.concatenate(list(item.maxs for item in trueselection)).reshape( Nitem, Nmotors ), axis=0, ) xlabel = item0.xlabel zlabel = item0.ylabel names = dataset0.nodenicknames ix = names.index(xlabel) # on propose par defaut ce qui est trace iz = names.index(zlabel) meshpar = MeshParameters(ix=ix, iy=0, iz=iz) Set3DparametersWindow( dataset0, meshpar, NX=item0.dataset.npts, NY=Nitem, mins=mins, maxs=maxs ) # ouvre une fenetre de dialogue prepare3Dmesh(trueselection, meshpar) class ImageFileList(QtGui.QListWidget): """ A specialized QListWidget that displays the list of all nxs files in a given directory and its subdirectories. """ def __init__(self, parent=None): QtGui.QListWidget.__init__(self, parent) self.setSelectionMode(3) # print "init" # item = QtGui.QListWidgetItem(self) # item.setText("vide") # self.setVisible(1) def setDirpath(self, dirpath): """ Set the current image directory and refresh the list. """ self.dirpath = dirpath self.populate() def getnxs(self): """ Return a list of filenames of all supported images in self._dirpath. """ self.nxs = [ (name, os.path.join(root, name)) for root, dirs, files in os.walk(self.dirpath) for name in files if name.endswith((".nxs")) ] self.nxs.sort() """ self.nxs = [os.path.join(root, name) for root, dirs, files in os.walk(self._dirpath) for name in files if name.endswith((".nxs"))] """ print(len(self.nxs), " files found") self.nxsdict = {a[0]: a[1] for a in self.nxs} # print self.nxsdict def populate(self): """ Fill the list with images from the current directory in self._dirpath. """ # In case we're repopulating, clear the list self.clear() self.getnxs() # Create a list item for each image file, # setting the text and icon appropriately for names in self.nxs: shortname = names[0] item = QtGui.QListWidgetItem(self) item.setText(shortname) # item.setIcon(QtGui.QIcon(image)) def prepare3Dmesh(trueselection, meshpar): # prepare la mesh en fonction de la liste des scans et des parametres coches dataset = trueselection[0] xx = np.concatenate(list(item.data[meshpar.ix] for item in trueselection)) xtitle = dataset.nodenicknames[meshpar.ix] if meshpar.iy < len(dataset.data): # on prend la valeur y yy = np.concatenate(list(item.data[meshpar.iy] for item in trueselection)) ytitle = dataset.nodenicknames[meshpar.iy] else: # on prend le numero dans la liste, le premier scan commence a 1 yy = np.zeros_like(dataset.data[meshpar.ix]) + 1 # print len(yy) for i in range(1, len(trueselection)): ypp = np.zeros_like(trueselection[i].data[meshpar.ix]) + i + 1 yy = np.concatenate((yy, ypp)) # print len(yy) ytitle = "numero" if meshpar.bc: # sur chaque scan en z, on fait une correction d'intensite. b1 = int(meshpar.b1) b2 = int(meshpar.b1) if b1 > b2: b1, b2 = b2, b1 b1 = max(0, b1) b2 = max(1, b2) b1 = min(b1, 99) b2 = min(b2, 100) fic = open("corr.txt", "w") for i in range(len(dataset.nodenicknames)): fic.write(dataset.nodenicknames[i]) fic.write(" ") fic.write("background") fic.write("\n") vm = dict() for item in trueselection: z1 = np.percentile(item.data[meshpar.iz], b1) z2 = np.percentile(item.data[meshpar.iz], b2) vm[item] = np.ma.mean(np.ma.masked_outside(item.data[meshpar.iz], z1, z2)) for i in range(len(dataset.nodenicknames)): fic.write("%f " % (item.data[i, 0])) fic.write("%f " % (vm[item])) fic.write("\n") vm[item] = 623 * np.arctan((item.data[15, 0] - 11.73) / 1.24) # print vm[item],item.data[:,0] zz = np.concatenate( list((item.data[meshpar.iz] - vm[item]) for item in trueselection) ) else: zz = np.concatenate(list(item.data[meshpar.iz] for item in trueselection)) bins = (meshpar.NY, meshpar.NX) bornes = [[meshpar.ymin, meshpar.ymax], [meshpar.xmin, meshpar.xmax]] scanlist = list(item.shortname for item in trueselection) scanlist.sort() # on met en ordre la liste des scans et on prend le premier et le dernier ztitle = scanlist[0] + "->" + scanlist[-1] ztitle = ztitle + ":" + dataset.nodenicknames[meshpar.iz] if meshpar.io: # on utilise la matrice d'orientation pour tracer la map x2 = xx y2 = yy try: fichier = tables.openFile(dataset.longname) for node in fichier.listNodes("/")[0].SIXS: if "alpha_star" in node: A_star = node.A_star.read()[0] B_star = node.B_star.read()[0] C_star = node.C_star.read()[0] alpha_star = node.alpha_star.read()[0] beta_star = node.beta_star.read()[0] gamma_star = node.gamma_star.read()[0] # print A_star,B_star,C_star,alpha_star,beta_star,gamma_star except Exception: print("la matrice d'orientation n'est pas definie") xtitle2 = xtitle ytitle2 = ytitle angle = 90.0 # valeurs par defaut unorm = 1.0 vnorm = 1.0 if xtitle == "h": if ytitle == "k": angle = gamma_star unorm = A_star vnorm = B_star xtitle2 = "kx" ytitle2 = "ky" elif ytitle == "l": angle = beta_star unorm = A_star vnorm = C_star xtitle2 = "kx" ytitle2 = "kz" elif xtitle == "k": if ytitle == "h": angle = -gamma_star unorm = B_star vnorm = A_star xtitle2 = "ky" ytitle2 = "kx" elif ytitle == "l": angle = alpha_star unorm = B_star vnorm = C_star xtitle2 = "ky" ytitle2 = "kz" elif xtitle == "l": if ytitle == "h": angle = -beta_star unorm = C_star vnorm = A_star xtitle2 = "kz" ytitle2 = "kx" elif ytitle == "k": angle = -alpha_star unorm = C_star vnorm = B_star xtitle2 = "kz" ytitle2 = "ky" angle = angle u = unorm * np.array([1, 0]) v = vnorm * np.array([np.cos(angle * degtorad), np.sin(angle * degtorad)]) x1 = xx * u[0] + yy * v[0] y1 = yy * u[1] + yy * v[1] angle2 = meshpar.angle cc = np.cos(angle2 * degtorad) ss = np.sin(angle2 * degtorad) x2 = x1 * cc - y1 * ss y2 = y1 * cc + x1 * ss u1 = [u[0] * cc - u[1] * ss, u[1] * cc + u[1] * ss] v1 = [v[0] * cc - v[1] * ss, v[1] * cc + v[1] * ss] bornes = [[np.amin(y2), np.amax(y2)], [np.amin(x2), np.amax(x2)]] # print bornes win = make3Dmesh( x2, y2, zz, zlog=meshpar.ilog, bins=bins, bornes=bornes, xtitle=xtitle2, ytitle=ytitle2, ztitle=ztitle, ) plot = win.get_plot() eps = 1.0e-10 if unorm == vnorm: if abs(angle - 90.0) < eps: sg = "p4" elif abs(angle - 60.0) < eps: sg = "p3" else: sg = "cm" else: if abs(angle - 90.0) < eps: sg = "p2" else: sg = "p1" shape = D2Dview.GridShape(O=[00, 000], u=u1, v=v1, sg=sg, order=2) shape.set_style("plot", "shape/gridshape") shape.setTitle("Reseau 1") shape.set_selectable(False) plot.add_item(shape) else: make3Dmesh( xx, yy, zz, zlog=meshpar.ilog, bins=bins, bornes=bornes, xtitle=xtitle, ytitle=ytitle, ztitle=ztitle, ) def make3Dmesh( x, y, z, zlog=0, bins=None, bornes=None, xtitle="x", ytitle="y", ztitle="3D mesh" ): # retourne un tableau 2D # print x.shape # print y.shape # print z.shape ones = np.ones(z.shape) if bins[0] > 1: # s'il y a plus d'une ligne en y on fait une map # on change les bornes pour faire l'histogramme correctement: # par exemple si on a pour y 4 valeurs en 1 2 3 4 il faut prendre bornes=(0.5,4.5) bornesy = bornes[0] deltay = (bornesy[1] - bornesy[0]) / (bins[0] - 1) bornesy[0] = bornesy[0] - deltay / 2.0 bornesy[1] = bornesy[1] + deltay / 2.0 if zlog: H, yedges, xedges = np.histogram2d( y, x, bins=bins, range=bornes, weights=np.log(z + 1) ) else: H, yedges, xedges = np.histogram2d(y, x, bins=bins, range=bornes, weights=z) C, yedges, xedges = np.histogram2d(y, x, bins=bins, range=bornes, weights=ones) # fic=open('hist.txt',"w") # for i in range(bins[0]): # for j in range (bins[1]): # fic.write("%f "%(H[i,j])) # fic.write("\n") # Hc=np.where(C=00,0,H) # inutile a priori si C=0 H=0 ## Find indecies of zeros values # index = np.where(C==0) Cc = np.where(C == 0, 1, C) # on met 1 pour eviter division par 0 hist = H / Cc mask0 = np.isfinite(hist) hist[C == 0] = np.nan # histvalues=H[::-1] / Cc[::-1] #image inversee en hauteur # hist=make.image(histvalues,xdata=[xedges[0],xedges[-1]],ydata=[yedges[0],yedges[-1]]) ## Create Boolean array of missing values mask = np.isfinite(hist) # interpolation des donnees manquantes values = hist[mask].flatten() ## Find indecies of finite values index = np.where(mask == True) """ x0,y0 = index[0],index[1] print "valeurs grille non masque" print x print y """ grid = np.where(mask0 == True) ## Grid irregular points to regular grid using delaunay triangulation values = griddata(index, values, grid, method="linear").reshape(hist.shape) # a la fin, il reste des NAN, si on veut pouvoir ajuster le contraste avec guiqwt, il # faut changer True en False dans le module guiqwt.image, get_histogram ligne 672 win = D2Dview.D2DDialog() win.setWindowTitle("3D mesh") win.set_data( values, xdata=[xedges[0], xedges[-1]], ydata=[yedges[0], yedges[-1]], colormap="jet", ) win.show_image() plot = win.get_plot() plot.set_axis_title("left", ytitle) plot.set_axis_title("bottom", xtitle) plot.set_title(ztitle) plot.set_axis_direction("left", reverse=False) else: # on trace un scan moyen if zlog: H, xedges = np.histogram( x, bins=bins[1], range=bornes[1], weights=np.log(z + 1) ) else: H, xedges = np.histogram(x, bins=bins[1], range=bornes[1], weights=z) C, xedges = np.histogram(x, bins=bins[1], range=bornes[1], weights=ones) win = CurveDialog(wintitle="Scan moyen") plot = win.get_plot() plot.set_axis_title("left", ztitle) plot.set_axis_title("bottom", xtitle) Cc = np.where(C == 0, 1, C) # on met 1 pour eviter division par 0 hist = H / Cc hist[C == 0] = np.nan # on met nan la ou il n'y a pas de valeur xedges = xedges - (xedges[1] - xedges[0]) / 2.0 # on decale d'un demi pas xedges = xedges[1:] plot.add_item(make.curve(xedges, hist)) win.show() """ win.image=hist win.setGeometry(QtCore.QRect(420,50,800, 600)) plot=win.get_plot() plot.add_item(hist) plot.set_axis_direction("left", reverse=False) plot.set_axis_title("left",ytitle) plot.set_axis_title("bottom",xtitle) plot.font_title.setPointSize (9) plot.set_title(ztitle) plot.set_aspect_ratio(lock=False) win.show() """ """ extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]] #pylab.imshow(H[::-1] / C[::-1], extent=extent, interpolation='nearest', aspect=aspect) pylab.imshow(H[::-1] / Cc[::-1], extent=extent, interpolation='nearest') pylab.xlim(xedges[0], xedges[-1]) pylab.ylim(yedges[0], yedges[-1]) pylab.clim() pylab.show() """ return win class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.setGeometry(QtCore.QRect(10, 50, 420, 600)) MainWindow.setWindowTitle( QtGui.QApplication.translate( "MainWindow", "nxsViewer", None, QtGui.QApplication.UnicodeUTF8 ) ) self.centralwidget = QtGui.QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.maxpar = 30 # probleme de maxpar a gerer... self.initlist() defaultfont = QtGui.QFont() defaultfont.setPointSize(10) QtGui.QApplication.setFont(defaultfont) self.listfiles = ImageFileList(parent=self.centralwidget) self.listfiles.setGeometry(QtCore.QRect(10, 10, 250, 550)) self.listfiles.setObjectName(_fromUtf8("listfiles")) self.autoadd = QtGui.QRadioButton(self.centralwidget) self.autoadd.setGeometry(QtCore.QRect(270, 10, 50, 30)) self.autoadd.setText("add") self.autoadd.setFont(defaultfont) self.autoreplace = QtGui.QRadioButton(self.centralwidget) self.autoreplace.setGeometry(QtCore.QRect(320, 10, 80, 30)) self.autoreplace.setText("replace") self.Xboxlabel = QtGui.QLabel(self.centralwidget) self.Xboxlabel.setGeometry(QtCore.QRect(270, 30, 30, 30)) self.Xboxlabel.setObjectName(_fromUtf8("Xboxlabel")) self.Xboxlabel.setText("X") self.Yboxlabel = QtGui.QLabel(self.centralwidget) self.Yboxlabel.setGeometry(QtCore.QRect(290, 30, 30, 30)) self.Yboxlabel.setObjectName(_fromUtf8("Xboxlabel")) self.Yboxlabel.setText("Y") self.Filterboxlabel = QtGui.QLabel(self.centralwidget) self.Filterboxlabel.setGeometry(QtCore.QRect(310, 30, 30, 30)) self.Filterboxlabel.setObjectName(_fromUtf8("Filterboxlabel")) self.Filterboxlabel.setText("Filter") """ self.Zboxlabel = QtGui.QLabel(self.centralwidget) self.Zboxlabel.setGeometry(QtCore.QRect(330, 10, 30, 30)) self.Zboxlabel.setObjectName(_fromUtf8("Xboxlabel")) self.Zboxlabel.setText("Z") """ self.XGroup = QtGui.QButtonGroup(self.centralwidget) self.XCheckBoxes = list() self.YCheckBoxes = list() self.FilterGroup = QtGui.QButtonGroup(self.centralwidget) self.FilterCheckBoxes = list() v = 60 for param in range(self.maxpar): cbox = QtGui.QCheckBox(self.centralwidget) cbox.hide() cbox.setGeometry(QtCore.QRect(270, v, 20, 20)) self.XCheckBoxes.append(cbox) self.XGroup.addButton(cbox) QtCore.QObject.connect( cbox, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), self.boxselection_changed, ) cbox = QtGui.QCheckBox(self.centralwidget) cbox.hide() cbox.setGeometry(QtCore.QRect(290, v, 20, 20)) self.YCheckBoxes.append(cbox) QtCore.QObject.connect( cbox, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), self.boxselection_changed, ) cbox = QtGui.QCheckBox(self.centralwidget) cbox.hide() cbox.setGeometry(QtCore.QRect(310, v, 300, 20)) self.FilterCheckBoxes.append(cbox) self.FilterGroup.addButton(cbox) QtCore.QObject.connect( cbox, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), self.boxselection_changed, ) v = v + 20 self.nCheckBoxes = 0 # au debut, pas de cases affichees """ self.XCheckBoxes[self.maxpar-1].setChecked(1) self.XCheckBoxes[7].setChecked(1) self.YCheckBoxes[0].setChecked(1) """ MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtGui.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 400, 20)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QtGui.QMenu(self.menubar) self.menuFile.setTitle( QtGui.QApplication.translate( "MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8 ) ) self.menuFile.setObjectName(_fromUtf8("menuFile")) self.menuEdit = QtGui.QMenu(self.menubar) self.menuEdit.setTitle( QtGui.QApplication.translate( "MainWindow", "Edit", None, QtGui.QApplication.UnicodeUTF8 ) ) self.menuEdit.setObjectName(_fromUtf8("menuEdit")) self.menuData = QtGui.QMenu(self.menubar) self.menuData.setTitle( QtGui.QApplication.translate( "MainWindow", "Data", None, QtGui.QApplication.UnicodeUTF8 ) ) self.menuData.setObjectName(_fromUtf8("menuData")) self.menuGraph = QtGui.QMenu(self.menubar) self.menuGraph.setTitle( QtGui.QApplication.translate( "MainWindow", "Graph", None, QtGui.QApplication.UnicodeUTF8 ) ) self.menuGraph.setObjectName(_fromUtf8("menuEdit")) MainWindow.setMenuBar(self.menubar) self.statusbar = QtGui.QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.actionOpen = QtGui.QAction(MainWindow) self.actionOpen.setText( QtGui.QApplication.translate( "MainWindow", "Open", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionOpen.setObjectName(_fromUtf8("actionImage")) self.actionSave = QtGui.QAction(MainWindow) self.actionSave.setText( QtGui.QApplication.translate( "MainWindow", "Save", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionSave.setObjectName(_fromUtf8("actionImage")) self.actionSave.setDisabled(1) self.actionQuit = QtGui.QAction(MainWindow) self.actionQuit.setText( QtGui.QApplication.translate( "MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionQuit.setObjectName(_fromUtf8("actionQuit")) self.menuFile.addAction(self.actionOpen) self.menuFile.addAction(self.actionSave) self.menuFile.addAction(self.actionQuit) self.actionCopy = QtGui.QAction(MainWindow) self.actionCopy.setText( QtGui.QApplication.translate( "MainWindow", "Copy", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionCopy.setObjectName(_fromUtf8("actionCopy")) self.actionClear = QtGui.QAction(MainWindow) self.actionClear.setText( QtGui.QApplication.translate( "MainWindow", "Clear", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionClear.setObjectName(_fromUtf8("actionClear")) self.menuEdit.addAction(self.actionCopy) self.menuEdit.addAction(self.actionClear) self.actionNewWindow = QtGui.QAction(MainWindow) self.actionNewWindow.setText( QtGui.QApplication.translate( "MainWindow", "New window", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionNewWindow.setObjectName(_fromUtf8("NewWindow ")) self.actionXLog = QtGui.QAction(MainWindow) self.actionXLog.setText( QtGui.QApplication.translate( "MainWindow", "X log", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionXLog.setObjectName(_fromUtf8("XLog")) self.actionXLog.setCheckable(1) self.actionYLog = QtGui.QAction(MainWindow) self.actionYLog.setText( QtGui.QApplication.translate( "MainWindow", "Y log", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionYLog.setObjectName(_fromUtf8("YLog")) self.actionYLog.setCheckable(1) self.actionAutoscale = QtGui.QAction(MainWindow) self.actionAutoscale.setText( QtGui.QApplication.translate( "MainWindow", "Autoscale", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionAutoscale.setObjectName(_fromUtf8("Autoscale")) self.actionAutoscale.setCheckable(1) self.menuGraph.addAction(self.actionNewWindow) self.menuGraph.addAction(self.actionXLog) self.menuGraph.addAction(self.actionYLog) self.menuGraph.addAction(self.actionAutoscale) self.actionFilter = QtGui.QAction(MainWindow) self.actionFilter.setText( QtGui.QApplication.translate( "MainWindow", "Filter policy", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionFilter.setObjectName(_fromUtf8("InitFilter ")) self.actionInitFit = QtGui.QAction(MainWindow) self.actionInitFit.setText( QtGui.QApplication.translate( "MainWindow", "Fit", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionInitFit.setObjectName(_fromUtf8("InitFit ")) self.action3DMesh = QtGui.QAction(MainWindow) self.action3DMesh.setText( QtGui.QApplication.translate( "MainWindow", "3DMesh", None, QtGui.QApplication.UnicodeUTF8 ) ) self.action3DMesh.setObjectName(_fromUtf8("Draw a 3D Mesh ")) self.actionPreferences = QtGui.QAction(MainWindow) self.actionPreferences.setText( QtGui.QApplication.translate( "MainWindow", "Preferences", None, QtGui.QApplication.UnicodeUTF8 ) ) self.actionPreferences.setObjectName(_fromUtf8("Preferences ")) self.menuData.addAction(self.actionFilter) self.menuData.addAction(self.actionInitFit) self.menuData.addAction(self.action3DMesh) self.menuData.addAction(self.actionPreferences) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuEdit.menuAction()) self.menubar.addAction(self.menuData.menuAction()) self.menubar.addAction(self.menuGraph.menuAction()) self.makewindow() self.retranslateUi(MainWindow) QtCore.QObject.connect( self.actionOpen, QtCore.SIGNAL(_fromUtf8("triggered()")), self.ouvrirFichiers, ) QtCore.QObject.connect( self.actionSave, QtCore.SIGNAL(_fromUtf8("triggered()")), self.enregistrer ) QtCore.QObject.connect( self.actionQuit, QtCore.SIGNAL(_fromUtf8("triggered()")), self.fermer ) QtCore.QObject.connect( self.actionXLog, QtCore.SIGNAL(_fromUtf8("triggered()")), self.setXLog ) QtCore.QObject.connect( self.actionYLog, QtCore.SIGNAL(_fromUtf8("triggered()")), self.setYLog ) QtCore.QObject.connect( self.actionNewWindow, QtCore.SIGNAL(_fromUtf8("triggered()")), self.makewindow, ) QtCore.QObject.connect( self.actionFilter, QtCore.SIGNAL(_fromUtf8("triggered()")), self.setfilter ) QtCore.QObject.connect( self.actionInitFit, QtCore.SIGNAL(_fromUtf8("triggered()")), self.initfit ) QtCore.QObject.connect( self.action3DMesh, QtCore.SIGNAL(_fromUtf8("triggered()")), self.draw3Dmesh ) QtCore.QObject.connect( self.actionPreferences, QtCore.SIGNAL(_fromUtf8("triggered()")), self.setpreferences, ) QtCore.QObject.connect( self.listfiles, QtCore.SIGNAL(_fromUtf8("itemSelectionChanged ()")), self.item_selectionchanged, ) QtCore.QObject.connect( self.listfiles, QtCore.SIGNAL(_fromUtf8("itemClicked (QListWidgetItem *)")), self.item_clicked, ) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.clipboard = QtGui.QApplication.clipboard() # self.Image.setFocus() self.csuite = ( "#00bfff", "#ff6a6a", "#98fb98", "#ffd700", "#ee82e2", "#e9967a", "#4169e1", "#ff0000", "#00ff00", "#ffa500", ) self.icolor = 0 self.listtitles = list() direct = "E:\Geoffroy-Boulot\Au-Cu\Sixs-decembre2013" self.listfiles.setDirpath(direct) self.autoreplace.setChecked(1) self.isafit = 0 self.lastitem = None self.nXBoxesChecked = 0 self.nYBoxesChecked = 0 self.datafilter = DataFilter() self.pref = PrefParameters() self.meshpar = MeshParameters() def refreshboxes(self, dataset): # on met à jour la liste des cases a cocher en fonction du dataset selectionne if dataset.nmotors > self.nCheckBoxes: # on rajoute des cases for i in range(self.nCheckBoxes, dataset.nmotors): self.XCheckBoxes[i].show() self.YCheckBoxes[i].show() self.FilterCheckBoxes[i].show() elif dataset.nmotors < self.nCheckBoxes: # on rajoute des cases for i in range(dataset.nmotors, self.nCheckBoxes): self.XCheckBoxes[i].hide() self.YCheckBoxes[i].hide() self.FilterCheckBoxes[i].hide() if self.XCheckBoxes[i].isChecked(): self.XCheckBoxes[i].setChecked(0) if self.FilterCheckBoxes[i].isChecked(): self.FilterCheckBoxes[i].setChecked(0) """ a priori pas necessaire en Y, comme ca on garde en memoire les cases cochees if self.YCheckBoxes[i].isChecked(): self.YCheckBoxes[i].setChecked(0) """ self.nXBoxesChecked = 0 self.nYBoxesChecked = 0 for i in range(dataset.nmotors): self.FilterCheckBoxes[i].setText(dataset.nodenicknames[i]) if self.XCheckBoxes[i].isChecked(): self.nXBoxesChecked = self.nXBoxesChecked + 1 if self.YCheckBoxes[i].isChecked(): self.nYBoxesChecked = self.nYBoxesChecked + 1 self.nCheckBoxes = dataset.nmotors def initfit(self): # ici on inclue une figure de fit provenant de grafit if self.isafit == 0: from grafit2.grafit2 import Ui_FitWindow # import grafit2 as grafit self.figfit = Ui_FitWindow() # self.figfit=grafit.Ui_FitWindow() self.figfit.setupUi() self.figfit.setupdatestart(self.pref.autoupdate) # tags est une liste de couples [non,valeur] self.figfit.settags([], saveint=True) self.isafit = 1 # ici on redirige le signal de fermeture de la fenetre de fit self.figfit.closeEvent = self.closefit self.figfit.move(10, 500) self.figfit.show() self.updatefit() def closefit(self, closeevent): self.isafit = 0 def showfit(self, itemselected): x, y = itemselected.get_data() xlabel = itemselected.xlabel ylabel = itemselected.ylabel xmin, xmax = self.plot.get_axis_limits("bottom") y2 = np.compress((x >= xmin) & (x <= xmax), y) x2 = np.compress((x >= xmin) & (x <= xmax), x) # on recupere les donnees du scan (angles, H,K,L) pour les envoyer a grafit try: fichier = tables.openFile(itemselected.dataset.longname) group = fichier.listNodes("/")[0] leafnames = [ "I14-C-CX2__EX__DIFF-UHV-H__#1/raw_value", "I14-C-CX2__EX__DIFF-UHV-K__#1/raw_value", "I14-C-CX2__EX__DIFF-UHV-L__#1/raw_value", "SIXS/I14-C-CX2__EX__mu-uhv__#1/raw_value", "SIXS/I14-C-CX2__EX__omega-uhv__#1/raw_value", "SIXS/I14-C-CX2__EX__delta-uhv__#1/raw_value", "SIXS/I14-C-CX2__EX__gamma-uhv__#1/raw_value", ] leafshortnames = ["H", "K", "L", "mu", "omega", "delta", "gamma"] leafs = [group._f_getChild(leafname) for leafname in leafnames] leafvalues = list(leaf.read()[0] for leaf in leafs) tags = zip(leafshortnames, leafvalues) except Exception: tags = [] self.figfit.setvalexp( x2, y2, xlabel=xlabel, ylabel=ylabel, title=itemselected.curveparam.label, xlog=self.actionXLog.isChecked(), ylog=self.actionYLog.isChecked(), tags=tags, followscans=self.pref.followscans, ) self.figfit.show() def updatefit(self): # print "update fit" itemselected = self.plot.get_active_item(force=False) if itemselected == None: itemselected = self.lastitem if itemselected == None: pass elif ICurveItemType in itemselected.types(): # on a selectionne une curve # print "curve" if self.isafit: self.showfit(itemselected) def initlist(self): self.paramlist = [ "y", "ybrut", "filters", "mu", " ", "delta", "gamma", "h", "k", "l", "q", ] def fermer(self): if self.isafit: self.figfit.close() self.win.close() MainWindow.close() def enregistrer(self): pass def setXLog(self): if self.actionXLog.isChecked(): self.plot.set_axis_scale("bottom", "log") else: self.plot.set_axis_scale("bottom", "lin") self.plot.show_items() def setYLog(self): if self.actionYLog.isChecked(): self.plot.set_axis_scale("left", "log") else: self.plot.set_axis_scale("left", "lin") self.plot.show_items() def changeZ(self): pass def retranslateUi(self, MainWindow): pass def drawcurve(self, dataset): kid = self.XGroup.checkedId() # a priori il y a toujours un X de selectionne if kid == -1: print("no X selected") else: ix = -kid - 2 absi = dataset.data[ix] for iy in range(dataset.nmotors): if self.YCheckBoxes[iy].isChecked(): # print i,params[i] xlabel = dataset.nodenicknames[ix] ylabel = dataset.nodenicknames[iy] title = dataset.shortname + "_" + ylabel + "(" + xlabel + ")" if ( title not in self.listtitles ): # on ne retrace pas des courbes deja faites self.lastitem = self.drawgraph( absi, dataset.data[iy], title=title, xlabel=xlabel, ylabel=ylabel, ) self.lastitem.dataset = dataset self.plot.set_titles(xlabel=xlabel, ylabel=ylabel) self.addplot(self.lastitem) self.icolor = (self.icolor + 1) % 10 self.listtitles.append(title) def draw3Dmesh(self): trueselection = list() scanlist = list() for item in self.listfiles.selectedItems(): shortname = str(item.text()) longname = self.listfiles.nxsdict[shortname] dataset = DataSet( longname, shortname, datafilter=self.datafilter, pref=self.pref ) trueselection.append(dataset) scanlist.append(shortname) if len(trueselection) == 0: print("aucun item selectionne") return Nitem = len(trueselection) Nmotors = dataset.nmotors mins = np.amin( np.concatenate(list(dataset.mins for dataset in trueselection)).reshape( Nitem, Nmotors ), axis=0, ) maxs = np.amax( np.concatenate(list(dataset.maxs for dataset in trueselection)).reshape( Nitem, Nmotors ), axis=0, ) Set3DparametersWindow( dataset, self.meshpar, NX=dataset.npts, NY=Nitem, mins=mins, maxs=maxs ) # ouvre une fenetre de dialogue prepare3Dmesh(trueselection, self.meshpar) def makewindow(self): self.win = CurveDialog( edit=False, toolbar=True, wintitle="Scan window", options=dict(xlabel="xlabel", ylabel="ylabel"), ) self.win.setGeometry(QtCore.QRect(440, 50, 800, 600)) self.plot = self.win.get_plot() self.win.get_itemlist_panel().show() self.plot.set_items_readonly(False) self.win.add_tool( SelectMultiPointTool, title=None, on_active_item=True, mode="create" ) self.win.add_tool(Draw3DTool) self.win.show() self.addplot(make.legend()) self.plot.connect(self.plot, SIG_ACTIVE_ITEM_CHANGED, self.updatefit) # self.win.exec_() def addplot(self, *items): for item in items: self.plot.add_item(item) # self.plot.activateWindow() met la fenêtre self.win au premier plan if self.actionAutoscale.isChecked(): self.plot.do_autoscale(replot=True) self.plot.show_items() def drawgraph(self, absi, ordo, title=None, xlabel=None, ylabel=None): """i #inutile on a mis des filtres if self.actionYLog.isChecked(): ymin=min(ordo) #print ymin if ymin<=0.0001: ymax=max(ordo)+1 ycorr=np.where(ordo <= 0.0001, ymax+1, ordo) ymin=min(ycorr) ordo=np.where(ycorr > ymax, ymin, ycorr) """ item = make.curve(absi, ordo, title=title, color=self.csuite[self.icolor]) item.xlabel = xlabel item.ylabel = ylabel return item def ouvrirFichiers(self): self.dirfiles = QtGui.QFileDialog.getExistingDirectory(None, "Browse Directory") print(str(self.dirfiles)) self.listfiles.setDirpath(str(self.dirfiles)) def item_selectionchanged(self): # self.item_clicked(self.listfiles.selectedItems()[0]) if self.autoreplace.isChecked(): # on efface self.icolor = 0 self.listtitles = list() self.plot.del_all_items() self.addplot(make.legend()) if ( self.nXBoxesChecked > 0 and self.nYBoxesChecked > 0 ): # pas la peine de perdre du temps si rien n'est coche for item in self.listfiles.selectedItems(): # on ajoute les items self.init_item(item) if self.pref.autoupdate: self.updatefit() # on update le fit avec le dernier item si self.pref.autoupdate est True def item_clicked(self, item): # le fichier a lire est obtenu par concatenation du directory et du filename self.shortname = str(item.text()) longname = self.listfiles.nxsdict[self.shortname] self.currentdataset = DataSet( longname, self.shortname, datafilter=self.datafilter, pref=self.pref ) self.refreshboxes(self.currentdataset) def init_item(self, item): self.shortname = str(item.text()) longname = self.listfiles.nxsdict[self.shortname] self.currentdataset = DataSet( longname, self.shortname, datafilter=self.datafilter, pref=self.pref ) self.refreshboxes(self.currentdataset) self.isacurve = 1 self.drawcurve(self.currentdataset) def boxselection_changed(self, state): self.nXBoxesChecked = 0 self.nYBoxesChecked = 0 kid = self.FilterGroup.checkedId() self.datafilter.ifil = -kid - 2 for i in range(self.nCheckBoxes): if self.XCheckBoxes[i].isChecked(): self.nXBoxesChecked = self.nXBoxesChecked + 1 if self.YCheckBoxes[i].isChecked(): self.nYBoxesChecked = self.nYBoxesChecked + 1 self.item_selectionchanged() def setfilter(self): # ouvre une fenetre pour le reglage des filtres SetFilterWindow(self.datafilter) def setpreferences(self): # ouvre une fenetre pour le reglage des filtres SetPrefWindow(self.pref) if self.isafit: self.figfit.setupdatestart(self.pref.autoupdate) if __name__ == "__main__": import sys app = QtGui.QApplication(sys.argv) MainWindow = QtGui.QMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) ui.listfiles.setDirpath(os.getcwd()) MainWindow.show() sys.exit(app.exec_()) binoculars-0.0.10/setup.py000066400000000000000000000037241412510113200154370ustar00rootroot00000000000000import os from setuptools import setup, find_packages description = ( "Data reduction and analysis software for two-dimensional " "detectors in surface X-ray diffraction" ) long_description = """ BINoculars is a tool for data reduction and analysis of large sets of surface diffraction data that have been acquired with a two-dimensional X-ray detector. The intensity of each pixel of a two-dimensional detector is projected onto a three-dimensional grid in reciprocal-lattice coordinates using a binning algorithm. This allows for fast acquisition and processing of high-resolution data sets and results in a significant reduction of the size of the data set. The subsequent analysis then proceeds in reciprocal space. It has evolved from the specific needs of the ID03 beamline at the ESRF, but it has a modular design and can be easily adjusted and extended to work with data from other beamlines or from other measurement techniques.""" scripts = [ os.path.join("scripts", d) for d in [ "binoculars-fitaid", "binoculars-gui", "binoculars-processgui", "binoculars", ] ] install_requires = ["h5py", "numpy", "matplotlib", "pyFAI", "PyGObject", "PyMca5", "PyQt5", "xrayutilities"] # for now there is not egg info for vtk # 'vtk' setup( name="binoculars", version="0.0.10-dev", description=description, long_description=long_description, packages=find_packages(exclude=["*.test", "*.test.*", "test.*", "test", "grafit"]), install_requires=install_requires, scripts=scripts, author="Willem Onderwaater, Sander Roobol, Frédéric-Emmanuel Picca", author_email="onderwaa@esrf.fr, picca@synchrotron-soleil.fr", url="FIXME", license="GPL-3", classifiers=[ "Topic :: Scientific/Engineering", "Development Status :: 3 - Alpha", "Operating System :: POSIX", "Operating System :: Unix", "Programming Language :: Python :: 3.7", ], ) binoculars-0.0.10/tests/000077500000000000000000000000001412510113200150615ustar00rootroot00000000000000binoculars-0.0.10/tests/__init__.py000066400000000000000000000000001412510113200171600ustar00rootroot00000000000000binoculars-0.0.10/tests/test_cfg.py000066400000000000000000000012441412510113200172320ustar00rootroot00000000000000import binoculars.util import os import unittest class TestCase(unittest.TestCase): def setUp(self): fn = "examples/configs/example_config_id03" self.cfg = binoculars.util.ConfigFile.fromtxtfile(fn) def test_IO(self): self.cfg.totxtfile("test.txt") self.cfg.tofile("test.hdf5") print(binoculars.util.ConfigFile.fromfile("test.hdf5")) self.assertRaises(IOError, binoculars.util.ConfigFile.fromtxtfile, "") self.assertRaises(IOError, binoculars.util.ConfigFile.fromfile, "") def tearDown(self): os.remove("test.txt") os.remove("test.hdf5") if __name__ == "__main__": unittest.main() binoculars-0.0.10/tests/test_id03.py000066400000000000000000000037721412510113200172420ustar00rootroot00000000000000from binoculars.backends import id03 import binoculars.util import binoculars.space import os import numpy import unittest class TestCase(unittest.TestCase): def setUp(self): cfg_unparsed = {} specfile = os.path.join( os.path.split(os.getcwd())[0], "binoculars-binaries/examples/dataset/sixc_tutorial.spec", ) cfg_unparsed["specfile"] = specfile cfg_unparsed["sdd"] = "1000" cfg_unparsed["pixelsize"] = "0.055, 0.055" cfg_unparsed["imagefolder"] = specfile.replace("sixc_tutorial.spec", "images") cfg_unparsed["centralpixel"] = "50 ,50" numpy.save("mask.npy", numpy.identity(516)) cfg_unparsed["maskmatrix"] = "mask.npy" self.id03input = id03.EH2(cfg_unparsed) self.projection = id03.HKLProjection( {"resolution": "0.01", "limits": "[0:, :-1, 0:0.2]"} ) @unittest.expectedFailure def test_IO(self): jobs = list(self.id03input.generate_jobs(["820"])) destination_opts = self.id03input.get_destination_options(["820"]) imagedata = self.id03input.process_job(jobs[0]) intensity, weights, coords = imagedata.next() projected = self.projection.project(*coords) limits = self.projection.config.limits space1 = binoculars.space.Space.from_image( self.projection.config.resolution, self.projection.get_axis_labels(), projected, intensity, weights, limits=limits[0], ) print(space1) intensity, weights, coords = imagedata.next() projected = self.projection.project(*coords) space2 = binoculars.space.Space.from_image( self.projection.config.resolution, self.projection.get_axis_labels(), projected, intensity, weights, ) print(space1 + space2) def tearDown(self): os.remove("mask.npy") if __name__ == "__main__": unittest.main() binoculars-0.0.10/tests/test_metadata.py000066400000000000000000000032621412510113200202550ustar00rootroot00000000000000import binoculars.util import binoculars.space import os import numpy import unittest class MetaDataTestCase(unittest.TestCase): def setUp(self): fn = "examples/configs/example_config_id03" self.cfg = binoculars.util.ConfigFile.fromtxtfile(fn) @unittest.expectedFailure def test_IO(self): test = { "string": "string", "numpy.array": numpy.arange(10), "list": range(10), "tuple": tuple(range(10)), } metasection = binoculars.util.MetaBase() metasection.add_section("first", test) print(metasection) metadata = binoculars.util.MetaData() metadata.add_dataset(metasection) metadata.add_dataset(self.cfg) metadata.tofile("test.hdf5") metadata += binoculars.util.MetaData.fromfile("test.hdf5") axis = tuple( binoculars.space.Axis(0, 10, 1, label) for label in ["h", "k", "l"] ) axes = binoculars.space.Axes(axis) space = binoculars.space.Space(axes) spacedict = dict(z for z in zip("abcde", range(5))) dataset = binoculars.util.MetaBase("fromspace", spacedict) space.metadata.add_dataset(dataset) space.tofile("test2.hdf5") testspace = binoculars.space.Space.fromfile("test2.hdf5") print(space + testspace).metadata print("--------------------------------------------------------") print(metadata) print(metadata.serialize()) print(binoculars.util.MetaData.fromserial(metadata.serialize())) def tearDown(self): os.remove("test.hdf5") os.remove("test2.hdf5") if __name__ == "__main__": unittest.main()