Exemple #1
0
def q_integrate(
    integrator: AzimuthalIntegrator,
    data: np.ndarray,
    npt: int = 1000,
    polz_factor: float = 0,
    unit: Union[str, units.Unit] = "q_A^-1",
    radial_range: Tuple[float, float] = None,
    azimuth_range: Tuple[float, float] = None,
    mask: np.ndarray = None,
    dark: np.ndarray = None,
    flat: np.ndarray = None,
    method: str = 'splitbbox',
    normalization_factor: float = 1,
) -> Tuple[np.ndarray, np.ndarray]:
    q, I = integrator.integrate1d(data=data,
                                  npt=npt,
                                  radial_range=radial_range,
                                  azimuth_range=azimuth_range,
                                  mask=mask,
                                  polarization_factor=polz_factor,
                                  dark=dark,
                                  flat=flat,
                                  method=method,
                                  unit=unit,
                                  normalization_factor=normalization_factor)
    return q, I
Exemple #2
0
def get_soq(Isaxs, mask, setup, Vsaxs=None, nbins=1000):
    ai = AzimuthalIntegrator(dist=setup['distance'],
                             pixel1=setup['pix_size'][0] * 1e-6,
                             pixel2=setup['pix_size'][1] * 1e-6)
    ai.setFit2D(setup['distance'] * 1000, setup['ctr'][0], setup['ctr'][1])
    ai.wavelength = setup['lambda'] * 1e-10
    if Vsaxs is None:
        q, ii, e = ai.integrate1d(Isaxs,
                                  nbins,
                                  mask=~(mask.astype(bool)),
                                  unit='q_nm^-1',
                                  error_model='poisson')
    else:
        q, ii, e = ai.integrate1d(Isaxs,
                                  nbins,
                                  mask=~(mask.astype(bool)),
                                  unit='q_nm^-1',
                                  variance=Vsaxs)
    return q, ii, e
Exemple #3
0
def naive_sdd(data: np.ndarray,
              azimuthal_integrator: AzimuthalIntegrator,
              calibrant: calibrants = calibrant.get_calibrant("AgBh"),
              mask: np.ndarray=None,
              wavelength_override: float = None,
              npts: int = 2000) -> AzimuthalIntegrator:
    kwargs = {}
    if mask is not None:
        kwargs['mask'] = mask

    # TODO: add a type that is a special parameter-tree item allowing enable/disable the parameter in the gui
    if wavelength_override:
        azimuthal_integrator.set_wavelength(wavelength_override)

    # slice into the first index as long as there's higher dimensionality
    while len(data.shape) > 2:
        data = data[0]

    # Un-calibrated azimuthal integration
    r, radialprofile = azimuthal_integrator.integrate1d(np.asarray(data), npts, unit='r_mm', **kwargs)

    # find peaks
    peaks = np.array(find_peaks(np.arange(len(radialprofile)), radialprofile)).T

    # get best peak
    bestpeak = None
    for peak in peaks:
        if peak[0] > 15 and not np.isinf(peak[1]):  ####This thresholds the minimum sdd which is acceptable
            bestpeak = peak[0]
            # print peak
            break

    # identify order of selected peak
    best_order = (0, 0)

    for i in range(1, 6):
        peak_ratios = ((peaks[:, 0] / (np.arange(len(peaks)))) / (bestpeak / (i + 1)))
        order = np.sum(np.logical_and(peak_ratios < 1.1, 0.9 < peak_ratios))
        if order > best_order[0]:
            best_order = (order, i)

    calibrant1stpeak = calibrant.dSpacing[best_order[1]]

    # Calculate sample to detector distance for lowest q peak

    tth = 2 * np.arcsin(0.5 * azimuthal_integrator.wavelength / calibrant1stpeak / 1.e-10)
    tantth = np.tan(tth)
    sdd = r[int(round(bestpeak))] / tantth

    # set sdd back on azimuthal integrator
    fit2dcal = azimuthal_integrator.getFit2D()
    fit2dcal['directDist'] = sdd
    azimuthal_integrator.setFit2D(**fit2dcal)

    return azimuthal_integrator
Exemple #4
0
    def run(self):
        ai = AzimuthalIntegrator(dist=self.__distance,
                                 poni1=self.__poni1,
                                 poni2=self.__poni2,
                                 rot1=self.__rotation1,
                                 rot2=self.__rotation2,
                                 rot3=self.__rotation3,
                                 detector=self.__detector,
                                 wavelength=self.__wavelength)

        # FIXME error model, method

        self.__result1d = ai.integrate1d(
            data=self.__image,
            npt=self.__nPointsRadial,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        self.__result2d = ai.integrate2d(
            data=self.__image,
            npt_rad=self.__nPointsRadial,
            npt_azim=self.__nPointsAzimuthal,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        try:
            self.__directDist = ai.getFit2D()["directDist"]
        except Exception:
            # The geometry could not fit this param
            _logger.debug("Backtrace", exc_info=True)
            self.__directDist = None

        if self.__calibrant:

            rings = self.__calibrant.get_2th()
            try:
                rings = utils.from2ThRad(rings, self.__radialUnit,
                                         self.__wavelength, self.__directDist)
            except ValueError:
                message = "Convertion to unit %s not supported. Ring marks ignored"
                _logger.warning(message, self.__radialUnit)
                rings = []
            # Filter the rings which are not part of the result
            rings = filter(
                lambda x: self.__result1d.radial[0] <= x <= self.__result1d.
                radial[-1], rings)
            rings = list(rings)
        else:
            rings = []
        self.__ringAngles = rings

        self.__ai = ai
Exemple #5
0
def azimuthal_integrate(
    z,
    origin,
    detector_distance,
    detector,
    wavelength,
    size_1d,
    unit,
    kwargs_for_integrator,
    kwargs_for_integrate1d,
):
    """Calculate the azimuthal integral of z around a determined origin.

    This method is used for signals where the origin is iterated, compared to
    azimuthal_integrate_fast which is used when the origin in the data is
    constant.

    Parameters
    ----------
    z : np.array()
        Two-dimensional data array containing the signal.
    origin : np.array()
        A size 2 numpy array containing the position of the origin.
    detector_distance : float
        Detector distance in meters passed to pyFAI AzimuthalIntegrator.
    detector : pyFAI.detectors.Detector object
        A pyFAI detector used for the AzimuthalIntegrator.
    wavelength : float
        The electron wavelength in meters. Used by pyFAI AzimuthalIntegrator.
    size_1d : int
        The size of the returned 1D signal. (i.e. number of pixels in the 1D
        azimuthal integral.)
    unit : str
        The unit for for PyFAI integrate1d.
    *args :
        Arguments to be passed to AzimuthalIntegrator.
    **kwargs :
        Keyword arguments to be passed to AzimuthalIntegrator.
    Returns
    -------
    tth : np.array()
        One-dimensional scattering vector axis of z.
    I : np.array()
        One-dimensional azimuthal integral of z.
    """
    p1, p2 = origin[0] * detector.pixel1, origin[1] * detector.pixel2
    ai = AzimuthalIntegrator(dist=detector_distance,
                             poni1=p1,
                             poni2=p2,
                             detector=detector,
                             wavelength=wavelength,
                             **kwargs_for_integrator)
    tth, I = ai.integrate1d(z, size_1d, unit=unit, **kwargs_for_integrate1d)
    return tth, I
Exemple #6
0
def process(
    *,
    raw_img: np.ndarray,
    ai: AzimuthalIntegrator,
    dk_img: np.ndarray = None,
    dk_sub_bg_img: np.ndarray = None,
    integ_setting: dict = None,
    mask_setting: dict = None,
    pdfgetx_setting: dict = None,
) -> dict:
    """The function to process the data from event."""
    data = dict()
    # dark subtraction
    if dk_img is None:
        dk_img = np.zeros_like(raw_img)
    dk_sub_img = np.subtract(raw_img, dk_img)
    data.update({"dk_sub_image": dk_sub_img})
    # background subtraction
    if dk_sub_bg_img is None:
        dk_sub_bg_img = np.zeros_like(dk_sub_img)
    bg_sub_img = np.subtract(dk_sub_img, dk_sub_bg_img)
    data.update({"bg_sub_image": bg_sub_img})
    # auto masking
    mask, _ = integ.auto_mask(bg_sub_img, ai, mask_setting=mask_setting)
    data.update({"mask": mask})
    # integration
    x, y = ai.integrate1d(bg_sub_img, mask=mask, **integ_setting)
    chi_max_ind = np.argmax(y)
    data.update({
        "chi_Q": x,
        "chi_I": y,
        "chi_max": y[chi_max_ind],
        "chi_argmax": x[chi_max_ind]
    })
    # transformation
    pdfconfig = PDFConfig(dataformat="QA", **pdfgetx_setting)
    pdfgetter = PDFGetter(pdfconfig)
    pdfgetter(x, y)
    iq, sq, fq, gr = pdfgetter.iq, pdfgetter.sq, pdfgetter.fq, pdfgetter.gr
    gr_max_ind = np.argmax(gr[1])
    data.update({
        "iq_Q": iq[0],
        "iq_I": iq[1],
        "sq_Q": sq[0],
        "sq_S": sq[1],
        "fq_Q": fq[0],
        "fq_F": fq[1],
        "gr_r": gr[0],
        "gr_G": gr[1],
        "gr_max": gr[1][gr_max_ind],
        "gr_argmax": gr[0][gr_max_ind]
    })
    return data
Exemple #7
0
def test_AzimuthalIntegrator_pickle():
    import dill
    import numpy as np
    from pyFAI.azimuthalIntegrator import AzimuthalIntegrator

    det = pyFAI.detectors.detector_factory('pilatus2m')
    ai = AzimuthalIntegrator(detector=det)
    ai.set_wavelength(.1)
    spectra = ai.integrate1d(np.ones(det.shape), 1000)  # force lut generation
    dump = dumps(ai)
    newai = loads(dump)
    assert np.array_equal(newai.integrate1d(np.ones(det.shape), 1000), spectra)
    assert newai.detector.shape == (1679, 1475)
Exemple #8
0
def naive_sdd(data: np.ndarray,
              mask: np.ndarray,
              calibrant: calibrant.Calibrant,
              azimuthal_integrator: AzimuthalIntegrator,
              npts: int = 2000) -> AzimuthalIntegrator:
    # Un-calibrated azimuthal integration
    r, radialprofile = azimuthal_integrator.integrate1d(data,
                                                        npts,
                                                        unit='r_mm',
                                                        mask=mask)

    # find peaks
    peaks = np.array(find_peaks(np.arange(len(radialprofile)),
                                radialprofile)).T

    # get best peak
    bestpeak = None
    for peak in peaks:
        if peak[0] > 15 and not np.isinf(
                peak[1]
        ):  ####This thresholds the minimum sdd which is acceptable
            bestpeak = peak[0]
            # print peak
            break

    # identify order of selected peak
    best_order = (0, 0)

    for i in range(1, 6):
        peak_ratios = ((peaks[:, 0] / (np.arange(len(peaks)))) / (bestpeak /
                                                                  (i + 1)))
        order = np.sum(np.logical_and(peak_ratios < 1.1, 0.9 < peak_ratios))
        if order > best_order[0]:
            best_order = (order, i)

    calibrant1stpeak = calibrant.dSpacing[best_order[1]]

    # Calculate sample to detector distance for lowest q peak

    tth = 2 * np.arcsin(
        0.5 * azimuthal_integrator.wavelength / calibrant1stpeak / 1.e-10)
    tantth = np.tan(tth)
    sdd = r[int(round(bestpeak))] / tantth

    # set sdd back on azimuthal integrator
    fit2dcal = azimuthal_integrator.getFit2D()
    fit2dcal['directDist'] = sdd
    azimuthal_integrator.setFit2D(**fit2dcal)

    return azimuthal_integrator
Exemple #9
0
def get_soq(Isaxs, mask, setup, Vsaxs=None, nbins=1000):
    ai = AzimuthalIntegrator(
        dist=setup["distance"],
        pixel1=setup["pix_size"][0] * 1e-6,
        pixel2=setup["pix_size"][1] * 1e-6,
    )
    ai.setFit2D(setup["distance"] * 1000, setup["ctr"][0], setup["ctr"][1])
    ai.wavelength = setup["lambda"] * 1e-10
    if Vsaxs is None:
        q, ii, e = ai.integrate1d(
            Isaxs,
            nbins,
            mask=~(mask.astype(bool)),
            unit="q_nm^-1",
            error_model="poisson",
        )
    else:
        q, ii, e = ai.integrate1d(Isaxs,
                                  nbins,
                                  mask=~(mask.astype(bool)),
                                  unit="q_nm^-1",
                                  variance=Vsaxs)
    return q, ii, e
Exemple #10
0
    def run(self):
        ai = AzimuthalIntegrator(
            dist=self.__distance,
            poni1=self.__poni1,
            poni2=self.__poni2,
            rot1=self.__rotation1,
            rot2=self.__rotation2,
            rot3=self.__rotation3,
            detector=self.__detector,
            wavelength=self.__wavelength)

        numberPoint1D = 1024
        numberPointRadial = 400
        numberPointAzimuthal = 360

        # FIXME error model, method

        self.__result1d = ai.integrate1d(
            data=self.__image,
            npt=numberPoint1D,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        self.__result2d = ai.integrate2d(
            data=self.__image,
            npt_rad=numberPointRadial,
            npt_azim=numberPointAzimuthal,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        if self.__calibrant:

            rings = self.__calibrant.get_2th()
            rings = filter(lambda x: x <= self.__result1d.radial[-1], rings)
            rings = list(rings)
            try:
                rings = utils.from2ThRad(rings, self.__radialUnit, self.__wavelength, ai)
            except ValueError:
                message = "Convertion to unit %s not supported. Ring marks ignored"
                _logger.warning(message, self.__radialUnit)
                rings = []
        else:
            rings = []
        self.__ringAngles = rings
Exemple #11
0
    def run(self):
        ai = AzimuthalIntegrator(dist=self.__distance,
                                 poni1=self.__poni1,
                                 poni2=self.__poni2,
                                 rot1=self.__rotation1,
                                 rot2=self.__rotation2,
                                 rot3=self.__rotation3,
                                 detector=self.__detector,
                                 wavelength=self.__wavelength)

        numberPoint1D = 1024
        numberPointRadial = 400
        numberPointAzimuthal = 360

        # FIXME error model, method

        self.__result1d = ai.integrate1d(
            data=self.__image,
            npt=numberPoint1D,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        self.__result2d = ai.integrate2d(
            data=self.__image,
            npt_rad=numberPointRadial,
            npt_azim=numberPointAzimuthal,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        if self.__calibrant:

            rings = self.__calibrant.get_2th()
            rings = filter(lambda x: x <= self.__result1d.radial[-1], rings)
            rings = list(rings)
            try:
                rings = utils.from2ThRad(rings, self.__radialUnit,
                                         self.__wavelength, ai)
            except ValueError:
                message = "Convertion to unit %s not supported. Ring marks ignored"
                _logger.warning(message, self.__radialUnit)
                rings = []
        else:
            rings = []
        self.__ringAngles = rings
Exemple #12
0
def integrate(img: ndarray,
              ai: AzimuthalIntegrator,
              mask: ndarray = None,
              integ_setting: dict = None) -> Tuple[ndarray, dict]:
    """Use AzimuthalIntegrator to integrate the image.

    Parameters
    ----------
    img : ndarray
        The 2D diffraction image array.

    ai : AzimuthalIntegrator
        The AzimuthalIntegrator instance.

    mask : ndarray
        The mask as a 0 and 1 array. 0 pixels are good pixels, 1 pixels are masked out.

    integ_setting : dict
        The user's modification to integration settings.

    Returns
    -------
    chi : ndarray
        The chi data. The first row is bin centers and the second row is the average intensity in bins.

    _integ_setting: dict
        The whole integration setting.
    """
    # merge integrate setting
    _integ_setting = INTEG_SETTING.copy()
    if integ_setting is not None:
        _integ_setting.update(integ_setting)
    # integrate
    xy = ai.integrate1d(img, mask=mask, **_integ_setting)
    chi = np.stack(xy)
    return chi, _integ_setting
Exemple #13
0
def run(args):
    FILE = args.INPUT
    if np.shape(FILE) == (1, ):
        FILE = np.sort(glob.glob(str(FILE[0])))

    SAVE_PATH = os.getcwd()
    if args.OUTPUT:
        SAVE_PATH = args.OUTPUT
    else:
        print(
            '!!! Warning files will be saved in the current folder because no output was defined.'
        )

    ### Read json file ###
    jsonParam = readJson(args.JSON)

    ### Integration of FILE ###
    azimutalIntegrator = AzimuthalIntegrator(
        dist=jsonParam['dist'],
        poni1=jsonParam['poni1'],
        poni2=jsonParam['poni2'],
        rot1=jsonParam['rot1'],
        rot2=jsonParam['rot2'],
        rot3=jsonParam['rot3'],
        pixel1=jsonParam['detector_config']['pixel1'],
        pixel2=jsonParam['detector_config']['pixel2'],
        detector=jsonParam['detector'],
        wavelength=jsonParam['wavelength'])
    #dark = np.array(fabio.open(jsonParam['dark_current']).data)
    #flat = np.array(fabio.open(jsonParam['flat_field']).data)
    mask = np.array(fabio.open(jsonParam['mask_file']).data)
    for i in range(len(FILE)):
        dataFile = np.array(fabio.open(FILE[i]).data)
        dataBragg, dataPowder = azimutalIntegrator.separate(
            dataFile,
            npt_rad=1024,
            npt_azim=512,
            unit=jsonParam['unit'],
            method='splitpixel',
            percentile=Threshold,
            mask=None,
            restore_mask=True)
        dataXP, dataYP = azimutalIntegrator.integrate1d(
            dataPowder,
            int(jsonParam['nbpt_rad']),
            filename=None,
            correctSolidAngle=jsonParam['do_solid_angle'],
            variance=None,
            error_model=None,
            radial_range=(float(jsonParam['radial_range_min']),
                          float(jsonParam['radial_range_max'])),
            azimuth_range=None,
            mask=mask,
            dummy=jsonParam['do_dummy'],
            delta_dummy=jsonParam['delta_dummy'],
            polarization_factor=None,
            method=jsonParam['method'],
            unit=jsonParam['unit'],
            safe=True,
            profile=False,
            all=False,
            metadata=None)
        dataXB, dataYB = azimutalIntegrator.integrate1d(
            dataBragg,
            int(jsonParam['nbpt_rad']),
            filename=None,
            correctSolidAngle=jsonParam['do_solid_angle'],
            variance=None,
            error_model=None,
            radial_range=(float(jsonParam['radial_range_min']),
                          float(jsonParam['radial_range_max'])),
            azimuth_range=None,
            mask=mask,
            dummy=jsonParam['do_dummy'],
            delta_dummy=jsonParam['delta_dummy'],
            polarization_factor=None,
            method=jsonParam['method'],
            unit=jsonParam['unit'],
            safe=True,
            profile=False,
            all=False,
            metadata=None)
        dataXO, dataYO = azimutalIntegrator.integrate1d(
            dataFile,
            int(jsonParam['nbpt_rad']),
            filename=None,
            correctSolidAngle=jsonParam['do_solid_angle'],
            variance=None,
            error_model=None,
            radial_range=(float(jsonParam['radial_range_min']),
                          float(jsonParam['radial_range_max'])),
            azimuth_range=None,
            mask=mask,
            dummy=jsonParam['do_dummy'],
            delta_dummy=jsonParam['delta_dummy'],
            polarization_factor=None,
            method=jsonParam['method'],
            unit=jsonParam['unit'],
            safe=True,
            profile=False,
            all=False,
            metadata=None)

        # Display #
        currentFILE = FILE[i]
        if args.DISPLAY:
            ax1 = plt.subplot(2, 3, 1)
            ax1.imshow(dataFile, interpolation=None)
            ax1.set_title('Full data')
            ax2 = plt.subplot(2, 3, 2, sharex=ax1, sharey=ax1)
            ax2.imshow(dataBragg, interpolation=None)
            ax2.set_title('Bragg contribution')
            ax3 = plt.subplot(2, 3, 3, sharex=ax1, sharey=ax1)
            ax3.imshow(dataPowder, interpolation=None)
            ax3.set_title('Powder contribution')
            ax4 = plt.subplot(2, 1, 2)
            ax4.plot(dataXP, dataYP, label='Powder contribution')
            ax4.plot(dataXB, dataYB, label='Bragg contribution')
            ax4.plot(dataXO, dataYO, label='Full')
            ax4.set_title('Integrated data')
            ax4.set_xlabel(r'$Q (\AA^{-1})$', fontsize=11)
            ax4.set_ylabel(r'$I(A.U.)$', fontsize=11)
            plt.legend()
            plt.show()

        # Save Tif File #

        if args.TIFSAVE:
            Image.fromarray(dataBragg).save(currentFILE[:-4] + '_THR' +
                                            str(Threshold) + '_bragg.tif')
            Image.fromarray(dataPowder).save(currentFILE[:-4] + '_THR' +
                                             str(Threshold) + '_powder.tif')

        # Save integrated data #

        f_out_dataPowder = open(
            currentFILE[:-4] + '_THR' + str(Threshold) + '_powder.dat', 'w')
        for j in range(0, np.size(dataXP, 0)):
            f_out_dataPowder.writelines('%07f' % dataXP[j] + '    ' +
                                        '%04f' % dataYP[j] + '\n')
        f_out_dataPowder.close()
        f_out_dataBragg = open(
            currentFILE[:-4] + '_THR' + str(Threshold) + '_bragg.dat', 'w')
        for j in range(0, np.size(dataXB, 0)):
            f_out_dataBragg.writelines('%07f' % dataXB[j] + '    ' +
                                       '%04f' % dataYB[j] + '\n')
        f_out_dataBragg.close()
        f_out_dataFull = open(
            currentFILE[:-4] + '_THR' + str(Threshold) + '_full.dat', 'w')
        for j in range(0, np.size(dataXO, 0)):
            f_out_dataFull.writelines('%07f' % dataXO[j] + '    ' +
                                      '%04f' % dataYO[j] + '\n')
        f_out_dataFull.close()
Exemple #14
0
class CalibrationModel(QtCore.QObject):
    def __init__(self, img_model=None):
        super(CalibrationModel, self).__init__()
        """
        :param img_model:
        :type img_model: ImgModel
        """
        self.img_model = img_model
        self.points = []
        self.points_index = []
        self.pattern_geometry = AzimuthalIntegrator()
        self.pattern_geometry_img_shape = None
        self.cake_geometry = None
        self.cake_geometry_img_shape = None
        self.calibrant = Calibrant()
        self.pattern_geometry.wavelength = 0.3344e-10
        self.start_values = {'dist': 200e-3,
                             'wavelength': 0.3344e-10,
                             'pixel_width': 79e-6,
                             'pixel_height': 79e-6,
                             'polarization_factor': 0.99}
        self.orig_pixel1 = 79e-6
        self.orig_pixel2 = 79e-6
        self.fit_wavelength = False
        self.fit_distance = True
        self.fit_poni1 = True
        self.fit_poni2 = True
        self.fit_rot1 = True
        self.fit_rot2 = True
        self.fit_rot3 = True
        self.is_calibrated = False
        self.use_mask = False
        self.filename = ''
        self.calibration_name = 'None'
        self.polarization_factor = 0.99
        self.supersampling_factor = 1
        self.correct_solid_angle = True
        self._calibrants_working_dir = calibrants_path

        self.distortion_spline_filename = None

        self.tth = np.linspace(0, 25)
        self.int = np.sin(self.tth)
        self.num_points = len(self.int)

        self.cake_img = np.zeros((2048, 2048))
        self.cake_tth = None
        self.cake_azi = None

        self.peak_search_algorithm = None

    def find_peaks_automatic(self, x, y, peak_ind):
        """
        Searches peaks by using the Massif algorithm
        :param float x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param float y:
            y-coordinate in pixel - should be from original image (not supersampled y-coordinate)
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            array of points found
        """
        massif = Massif(self.img_model._img_data)
        cur_peak_points = massif.find_peaks((int(np.round(x)), int(np.round(y))), stdout=DummyStdOut())
        if len(cur_peak_points):
            self.points.append(np.array(cur_peak_points))
            self.points_index.append(peak_ind)
        return np.array(cur_peak_points)

    def find_peak(self, x, y, search_size, peak_ind):
        """
        Searches a peak around the x,y position. It just searches for the maximum value in a specific search size.
        :param int x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param int y:
            y-coordinate in pixel - should be form original image (not supersampled y-coordinate)
        :param search_size:
            the length of the search rectangle in pixels in all direction in which the algorithm searches for
            the maximum peak
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            point found (as array)
        """
        left_ind = int(np.round(x - search_size * 0.5))
        if left_ind < 0:
            left_ind = 0
        top_ind = int(np.round(y - search_size * 0.5))
        if top_ind < 0:
            top_ind = 0
        search_array = self.img_model.img_data[left_ind:(left_ind + search_size), top_ind:(top_ind + search_size)]
        x_ind, y_ind = np.where(search_array == search_array.max())
        x_ind = x_ind[0] + left_ind
        y_ind = y_ind[0] + top_ind
        self.points.append(np.array([x_ind, y_ind]))
        self.points_index.append(peak_ind)
        return np.array([np.array((x_ind, y_ind))])

    def clear_peaks(self):
        self.points = []
        self.points_index = []

    def remove_last_peak(self):
        if self.points:
            num_points = int(self.points[-1].size/2)  # each peak is x, y so length is twice as number of peaks
            self.points.pop(-1)
            self.points_index.pop(-1)
            return num_points

    def create_cake_geometry(self):
        self.cake_geometry = AzimuthalIntegrator(splineFile=self.distortion_spline_filename)

        pyFAI_parameter = self.pattern_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        pyFAI_parameter['wavelength'] = self.pattern_geometry.wavelength

        self.cake_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                    poni1=pyFAI_parameter['poni1'],
                                    poni2=pyFAI_parameter['poni2'],
                                    rot1=pyFAI_parameter['rot1'],
                                    rot2=pyFAI_parameter['rot2'],
                                    rot3=pyFAI_parameter['rot3'],
                                    pixel1=pyFAI_parameter['pixel1'],
                                    pixel2=pyFAI_parameter['pixel2'])

        self.cake_geometry.wavelength = pyFAI_parameter['wavelength']

    def setup_peak_search_algorithm(self, algorithm, mask=None):
        """
        Initializes the peak search algorithm on the current image
        :param algorithm:
            peak search algorithm used. Possible algorithms are 'Massif' and 'Blob'
        :param mask:
            if a mask is used during the process this is provided here as a 2d array for the image.
        """

        if algorithm == 'Massif':
            self.peak_search_algorithm = Massif(self.img_model.raw_img_data)
        elif algorithm == 'Blob':
            if mask is not None:
                self.peak_search_algorithm = BlobDetection(self.img_model.raw_img_data * mask)
            else:
                self.peak_search_algorithm = BlobDetection(self.img_model.raw_img_data)
            self.peak_search_algorithm.process()
        else:
            return

    def search_peaks_on_ring(self, ring_index, delta_tth=0.1, min_mean_factor=1,
                             upper_limit=55000, mask=None):
        """
        This function is searching for peaks on an expected ring. It needs an initial calibration
        before. Then it will search for the ring within some delta_tth and other parameters to get
        peaks from the calibrant.

        :param ring_index: the index of the ring for the search
        :param delta_tth: search space around the expected position in two theta
        :param min_mean_factor: a factor determining the minimum peak intensity to be picked up. it is based
                                on the mean value of the search area defined by delta_tth. Pick a large value
                                for larger minimum value and lower for lower minimum value. Therefore, a smaller
                                number is more prone to picking up noise. typical values like between 1 and 3.
        :param upper_limit: maximum intensity for the peaks to be picked
        :param mask: in case the image has to be masked from certain areas, it need to be given here. Default is None.
                     The mask should be given as an 2d array with the same dimensions as the image, where 1 denotes a
                     masked pixel and all others should be 0.
        """
        self.reset_supersampling()
        if not self.is_calibrated:
            return

        # transform delta from degree into radians
        delta_tth = delta_tth / 180.0 * np.pi

        # get appropriate two theta value for the ring number
        tth_calibrant_list = self.calibrant.get_2th()
        if ring_index >= len(tth_calibrant_list):
            raise NotEnoughSpacingsInCalibrant()
        tth_calibrant = np.float(tth_calibrant_list[ring_index])

        # get the calculated two theta values for the whole image
        tth_array = self.pattern_geometry.twoThetaArray(self.img_model._img_data.shape)

        # create mask based on two_theta position
        ring_mask = abs(tth_array - tth_calibrant) <= delta_tth

        if mask is not None:
            mask = np.logical_and(ring_mask, np.logical_not(mask))
        else:
            mask = ring_mask

        # calculate the mean and standard deviation of this area
        sub_data = np.array(self.img_model._img_data.ravel()[np.where(mask.ravel())], dtype=np.float64)
        sub_data[np.where(sub_data > upper_limit)] = np.NaN
        mean = np.nanmean(sub_data)
        std = np.nanstd(sub_data)

        # set the threshold into the mask (don't detect very low intensity peaks)
        threshold = min_mean_factor * mean + std
        mask2 = np.logical_and(self.img_model._img_data > threshold, mask)
        mask2[np.where(self.img_model._img_data > upper_limit)] = False
        size2 = mask2.sum(dtype=int)

        keep = int(np.ceil(np.sqrt(size2)))
        try:
            sys.stdout = DummyStdOut
            res = self.peak_search_algorithm.peaks_from_area(mask2, Imin=mean - std, keep=keep)
            sys.stdout = sys.__stdout__
        except IndexError:
            res = []

        # Store the result
        if len(res):
            self.points.append(np.array(res))
            self.points_index.append(ring_index)

        self.set_supersampling()
        self.pattern_geometry.reset()

    def set_calibrant(self, filename):
        self.calibrant = Calibrant()
        self.calibrant.load_file(filename)
        self.pattern_geometry.calibrant = self.calibrant

    def set_start_values(self, start_values):
        self.start_values = start_values
        self.polarization_factor = start_values['polarization_factor']

    def calibrate(self):
        self.pattern_geometry = GeometryRefinement(self.create_point_array(self.points, self.points_index),
                                                   dist=self.start_values['dist'],
                                                   wavelength=self.start_values['wavelength'],
                                                   pixel1=self.start_values['pixel_width'],
                                                   pixel2=self.start_values['pixel_height'],
                                                   calibrant=self.calibrant,
                                                   splineFile=self.distortion_spline_filename)
        self.orig_pixel1 = self.start_values['pixel_width']
        self.orig_pixel2 = self.start_values['pixel_height']

        self.refine()
        self.create_cake_geometry()
        self.is_calibrated = True

        self.calibration_name = 'current'
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.pattern_geometry.reset()

    def refine(self):
        self.reset_supersampling()
        self.pattern_geometry.data = self.create_point_array(self.points, self.points_index)

        fix = ['wavelength']
        if self.fit_wavelength:
            fix = []
        if not self.fit_distance:
            fix.append('dist')
        if not self.fit_poni1:
            fix.append('poni1')
        if not self.fit_poni2:
            fix.append('poni2')
        if not self.fit_rot1:
            fix.append('rot1')
        if not self.fit_rot2:
            fix.append('rot2')
        if not self.fit_rot3:
            fix.append('rot3')
        if self.fit_wavelength:
            self.pattern_geometry.refine2()
        self.pattern_geometry.refine2_wavelength(fix=fix)

        self.create_cake_geometry()
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.pattern_geometry.reset()

    def integrate_1d(self, num_points=None, mask=None, polarization_factor=None, filename=None,
                     unit='2th_deg', method='csr'):
        if np.sum(mask) == self.img_model.img_data.shape[0] * self.img_model.img_data.shape[1]:
            # do not perform integration if the image is completely masked...
            return self.tth, self.int

        if self.pattern_geometry_img_shape != self.img_model.img_data.shape:
            # if cake geometry was used on differently shaped image before the azimuthal integrator needs to be reset
            self.pattern_geometry.reset()
            self.pattern_geometry_img_shape = self.img_model.img_data.shape

        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if num_points is None:
            num_points = self.calculate_number_of_pattern_points(2)
        self.num_points = num_points

        t1 = time.time()

        if unit is 'd_A':
            try:
                self.tth, self.int = self.pattern_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                       method=method,
                                                                       unit='2th_deg',
                                                                       mask=mask,
                                                                       polarization_factor=polarization_factor,
                                                                       correctSolidAngle=self.correct_solid_angle,
                                                                       filename=filename)
            except NameError:
                self.tth, self.int = self.pattern_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                       method='csr',
                                                                       unit='2th_deg',
                                                                       mask=mask,
                                                                       polarization_factor=polarization_factor,
                                                                       correctSolidAngle=self.correct_solid_angle,
                                                                       filename=filename)
            self.tth = self.pattern_geometry.wavelength / (2 * np.sin(self.tth / 360 * np.pi)) * 1e10
            self.int = self.int
        else:
            try:
                self.tth, self.int = self.pattern_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                       method=method,
                                                                       unit=unit,
                                                                       mask=mask,
                                                                       polarization_factor=polarization_factor,
                                                                       correctSolidAngle=self.correct_solid_angle,
                                                                       filename=filename)
            except NameError:
                self.tth, self.int = self.pattern_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                       method='csr',
                                                                       unit=unit,
                                                                       mask=mask,
                                                                       polarization_factor=polarization_factor,
                                                                       correctSolidAngle=self.correct_solid_angle,
                                                                       filename=filename)
        logger.info('1d integration of {0}: {1}s.'.format(os.path.basename(self.img_model.filename), time.time() - t1))

        ind = np.where((self.int > 0) & (~np.isnan(self.int)))
        self.tth = self.tth[ind]
        self.int = self.int[ind]
        return self.tth, self.int

    def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', method='csr',
                     rad_points=None, azimuth_points=360,
                     azimuth_range=None):
        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if self.cake_geometry_img_shape != self.img_model.img_data.shape:
            # if cake geometry was used on differently shaped image before the azimuthal integrator needs to be reset
            self.cake_geometry.reset()
            self.cake_geometry_img_shape = self.img_model.img_data.shape

        if rad_points is None:
            rad_points = self.calculate_number_of_pattern_points(2)
        self.num_points = rad_points

        t1 = time.time()

        res = self.cake_geometry.integrate2d(self.img_model.img_data, rad_points, azimuth_points,
                                             azimuth_range=azimuth_range,
                                             method=method,
                                             mask=mask,
                                             unit=unit,
                                             polarization_factor=polarization_factor,
                                             correctSolidAngle=self.correct_solid_angle)
        logger.info('2d integration of {0}: {1}s.'.format(os.path.basename(self.img_model.filename), time.time() - t1))
        self.cake_img = res[0]
        self.cake_tth = res[1]
        self.cake_azi = res[2]
        return self.cake_img

    def create_point_array(self, points, points_ind):
        res = []
        for i, point_list in enumerate(points):
            if point_list.shape == (2,):
                res.append([point_list[0], point_list[1], points_ind[i]])
            else:
                for point in point_list:
                    res.append([point[0], point[1], points_ind[i]])
        return np.array(res)

    def get_point_array(self):
        return self.create_point_array(self.points, self.points_index)

    def get_calibration_parameter(self):
        pyFAI_parameter = self.pattern_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        try:
            fit2d_parameter = self.pattern_geometry.getFit2D()
            fit2d_parameter['polarization_factor'] = self.polarization_factor
        except TypeError:
            fit2d_parameter = None

        pyFAI_parameter['wavelength'] = self.pattern_geometry.wavelength
        if fit2d_parameter:
            fit2d_parameter['wavelength'] = self.pattern_geometry.wavelength

        return pyFAI_parameter, fit2d_parameter

    def calculate_number_of_pattern_points(self, max_dist_factor=1.5):
        # calculates the number of points for an integrated pattern, based on the distance of the beam center to the the
        # image corners. Maximum value is determined by the shape of the image.
        fit2d_parameter = self.pattern_geometry.getFit2D()
        center_x = fit2d_parameter['centerX']
        center_y = fit2d_parameter['centerY']
        width, height = self.img_model.img_data.shape

        if width > center_x > 0:
            side1 = np.max([abs(width - center_x), center_x])
        else:
            side1 = width

        if center_y < height and center_y > 0:
            side2 = np.max([abs(height - center_y), center_y])
        else:
            side2 = height
        max_dist = np.sqrt(side1 ** 2 + side2 ** 2)
        return int(max_dist * max_dist_factor)

    def load(self, filename):
        """
        Loads a calibration file and and sets all the calibration parameter.
        :param filename: filename for a *.poni calibration file
        """
        self.pattern_geometry = AzimuthalIntegrator()
        self.pattern_geometry.load(filename)
        self.orig_pixel1 = self.pattern_geometry.pixel1
        self.orig_pixel2 = self.pattern_geometry.pixel2
        self.calibration_name = get_base_name(filename)
        self.filename = filename
        self.is_calibrated = True
        self.create_cake_geometry()
        self.set_supersampling()

    def save(self, filename):
        """
        Saves the current calibration parameters into a a text file. Default extension is
        *.poni
        """
        self.cake_geometry.save(filename)
        self.calibration_name = get_base_name(filename)
        self.filename = filename

    def create_file_header(self):
        try:
            # pyFAI version 0.12.0
            return self.pattern_geometry.makeHeaders(polarization_factor=self.polarization_factor)
        except AttributeError:
            # pyFAI after version 0.12.0
            from pyFAI.io import DefaultAiWriter
            return DefaultAiWriter(None, self.pattern_geometry).make_headers()

    def set_fit2d(self, fit2d_parameter):
        """
        Reads in a dictionary with fit2d parameters where the fields of the dictionary are:
        'directDist', 'centerX', 'centerY', 'tilt', 'tiltPlanRotation', 'pixelX', pixelY',
        'polarization_factor', 'wavelength'
        """
        self.pattern_geometry.setFit2D(directDist=fit2d_parameter['directDist'],
                                       centerX=fit2d_parameter['centerX'],
                                       centerY=fit2d_parameter['centerY'],
                                       tilt=fit2d_parameter['tilt'],
                                       tiltPlanRotation=fit2d_parameter['tiltPlanRotation'],
                                       pixelX=fit2d_parameter['pixelX'],
                                       pixelY=fit2d_parameter['pixelY'])
        self.pattern_geometry.wavelength = fit2d_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = fit2d_parameter['polarization_factor']
        self.orig_pixel1 = fit2d_parameter['pixelX'] * 1e-6
        self.orig_pixel2 = fit2d_parameter['pixelY'] * 1e-6
        self.is_calibrated = True
        self.set_supersampling()

    def set_pyFAI(self, pyFAI_parameter):
        """
        Reads in a dictionary with pyFAI parameters where the fields of dictionary are:
        'dist', 'poni1', 'poni2', 'rot1', 'rot2', 'rot3', 'pixel1', 'pixel2', 'wavelength',
        'polarization_factor'
        """
        self.pattern_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                       poni1=pyFAI_parameter['poni1'],
                                       poni2=pyFAI_parameter['poni2'],
                                       rot1=pyFAI_parameter['rot1'],
                                       rot2=pyFAI_parameter['rot2'],
                                       rot3=pyFAI_parameter['rot3'],
                                       pixel1=pyFAI_parameter['pixel1'],
                                       pixel2=pyFAI_parameter['pixel2'])
        self.pattern_geometry.wavelength = pyFAI_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = pyFAI_parameter['polarization_factor']
        self.orig_pixel1 = pyFAI_parameter['pixel1']
        self.orig_pixel2 = pyFAI_parameter['pixel2']
        self.is_calibrated = True
        self.set_supersampling()

    def load_distortion(self, spline_filename):
        self.distortion_spline_filename = spline_filename
        self.pattern_geometry.set_splineFile(spline_filename)
        if self.cake_geometry:
            self.cake_geometry.set_splineFile(spline_filename)

    def reset_distortion_correction(self):
        self.distortion_spline_filename = None
        self.pattern_geometry.set_splineFile(None)
        if self.cake_geometry:
            self.cake_geometry.set_splineFile(None)

    def set_supersampling(self, factor=None):
        """
        Sets the supersampling to a specific factor. Whereby the factor determines in how many artificial pixel the
        original pixel is split. (factor^2)

        factor  n_pixel
        1       1
        2       4
        3       9
        4       16
        """
        if factor is None:
            factor = self.supersampling_factor
        self.pattern_geometry.pixel1 = self.orig_pixel1 / float(factor)
        self.pattern_geometry.pixel2 = self.orig_pixel2 / float(factor)

        if factor != self.supersampling_factor:
            self.pattern_geometry.reset()
            self.supersampling_factor = factor

    def reset_supersampling(self):
        self.pattern_geometry.pixel1 = self.orig_pixel1
        self.pattern_geometry.pixel2 = self.orig_pixel2

    def get_two_theta_img(self, x, y):
        """
        Gives the two_theta value for the x,y coordinates on the image. Be aware that this function will be incorrect
        for pixel indices, since it does not correct for center of the pixel.
        :param  x: x-coordinate in pixel on the image
        :type   x: ndarray
        :param  y: y-coordinate in pixel on the image
        :type   y: ndarray

        :return  : two theta in radians
        """
        x *= self.supersampling_factor
        y *= self.supersampling_factor

        return self.pattern_geometry.tth(x - 0.5, y - 0.5)[0]  # deletes 0.5 because tth function uses pixel indices

    def get_azi_img(self, x, y):
        """
        Gives chi for position on image.
        :param  x: x-coordinate in pixel on the image
        :type   x: ndarray
        :param  y: y-coordinate in pixel on the image
        :type   y: ndarray

        :return  : azimuth in radians
        """
        x *= self.supersampling_factor
        y *= self.supersampling_factor
        return self.pattern_geometry.chi(x - 0.5, y - 0.5)[0]

    def get_two_theta_array(self):
        return self.pattern_geometry.twoThetaArray(self.img_model.img_data.shape)[::self.supersampling_factor,
               ::self.supersampling_factor]

    def get_pixel_ind(self, tth, azi):
        """
        Calculates pixel index for a specfic two theta and azimutal value.
        :param tth:
            two theta in radians
        :param azi:
            azimuth in radians
        :return:
            tuple of index 1 and 2
        """

        tth_ind = find_contours(self.pattern_geometry.ttha, tth)
        if len(tth_ind) == 0:
            return []
        tth_ind = np.vstack(tth_ind)
        azi_values = self.pattern_geometry.chi(tth_ind[:, 0], tth_ind[:, 1])
        min_index = np.argmin(np.abs(azi_values - azi))
        return tth_ind[min_index, 0], tth_ind[min_index, 1]

    @property
    def wavelength(self):
        return self.pattern_geometry.wavelength
class TestMultiGeometry(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self)
        self.data = fabio.open(UtilsTest.getimage("1788/moke.tif")).data
        self.lst_data = [self.data[:250, :300], self.data[250:, :300], self.data[:250, 300:], self.data[250:, 300:]]
        self.det = Detector(1e-4, 1e-4)
        self.det.max_shape = (500, 600)
        self.sub_det = Detector(1e-4, 1e-4)
        self.sub_det.max_shape = (250, 300)
        self.ai = AzimuthalIntegrator(0.1, 0.03, 0.03, detector=self.det)
        self.range = (0, 23)
        self.ais = [AzimuthalIntegrator(0.1, 0.030, 0.03, detector=self.sub_det),
                    AzimuthalIntegrator(0.1, 0.005, 0.03, detector=self.sub_det),
                    AzimuthalIntegrator(0.1, 0.030, 0.00, detector=self.sub_det),
                    AzimuthalIntegrator(0.1, 0.005, 0.00, detector=self.sub_det),
                    ]
        self.mg = MultiGeometry(self.ais, radial_range=self.range, unit="2th_deg")
        self.N = 390

    def tearDown(self):
        unittest.TestCase.tearDown(self)
        self.data = None
        self.lst_data = None
        self.det = None
        self.sub_det = None
        self.ai = None
        self.ais = None
        self.mg = None

    def test_integrate1d(self):
        tth_ref, I_ref = self.ai.integrate1d(self.data, radial_range=self.range, npt=self.N, unit="2th_deg", method="splitpixel")
        obt = self.mg.integrate1d(self.lst_data, self.N)
        tth_obt, I_obt = obt
        self.assertEqual(abs(tth_ref - tth_obt).max(), 0, "Bin position is the same")
        # intensity need to be scaled by solid angle 1e-4*1e-4/0.1**2 = 1e-6
        delta = (abs(I_obt * 1e6 - I_ref).max())
        self.assertLessEqual(delta, 5e-5, "Intensity is the same delta=%s" % delta)

    def test_integrate2d(self):
        ref = self.ai.integrate2d(self.data, self.N, 360, radial_range=self.range, azimuth_range=(-180, 180), unit="2th_deg", method="splitpixel", all=True)
        obt = self.mg.integrate2d(self.lst_data, self.N, 360, all=True)
        self.assertEqual(abs(ref["radial"] - obt["radial"]).max(), 0, "Bin position is the same")
        self.assertEqual(abs(ref["azimuthal"] - obt["azimuthal"]).max(), 0, "Bin position is the same")
        # intensity need to be scaled by solid angle 1e-4*1e-4/0.1**2 = 1e-6
        delta = abs(obt["I"] * 1e6 - ref["I"])[obt["count"] >= 1]  # restict on valid pixel
        delta_cnt = abs(obt["count"] - ref["count"])
        delta_sum = abs(obt["sum"] * 1e6 - ref["sum"])
        if delta.max() > 1:
            logger.warning("TestMultiGeometry.test_integrate2d gave intensity difference of %s" % delta.max())
            if logger.level <= logging.DEBUG:
                from matplotlib import pyplot as plt
                f = plt.figure()
                a1 = f.add_subplot(2, 2, 1)
                a1.imshow(ref["sum"])
                a2 = f.add_subplot(2, 2, 2)
                a2.imshow(obt["sum"])
                a3 = f.add_subplot(2, 2, 3)
                a3.imshow(delta_sum)
                a4 = f.add_subplot(2, 2, 4)
                a4.plot(delta_sum.sum(axis=0))
                f.show()
                raw_input()

        self.assertLess(delta_cnt.max(), 0.001, "pixel count is the same delta=%s" % delta_cnt.max())
        self.assertLess(delta_sum.max(), 0.03, "pixel sum is the same delta=%s" % delta_sum.max())
        self.assertLess(delta.max(), 0.004, "pixel intensity is the same (for populated pixels) delta=%s" % delta.max())
Exemple #16
0
    def run(self):
        ai = AzimuthalIntegrator(
            dist=self.__distance,
            poni1=self.__poni1,
            poni2=self.__poni2,
            rot1=self.__rotation1,
            rot2=self.__rotation2,
            rot3=self.__rotation3,
            detector=self.__detector,
            wavelength=self.__wavelength)

        # FIXME Add error model

        method1d = method_registry.Method(1, self.__method.split, self.__method.algo, self.__method.impl, None)
        methods = method_registry.IntegrationMethod.select_method(method=method1d)
        if len(methods) == 0:
            method1d = method_registry.Method(1, method1d.split, "*", "*", None)
            _logger.warning("Downgrade 1D integration method to %s", method1d)
        else:
            method1d = methods[0].method

        method2d = method_registry.Method(2, self.__method.split, self.__method.algo, self.__method.impl, None)
        methods = method_registry.IntegrationMethod.select_method(method=method2d)
        if len(methods) == 0:
            method2d = method_registry.Method(2, method2d.split, "*", "*", None)
            _logger.warning("Downgrade 2D integration method to %s", method2d)
        else:
            method2d = methods[0].method

        self.__result1d = ai.integrate1d(
            method=method1d,
            data=self.__image,
            npt=self.__nPointsRadial,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        self.__result2d = ai.integrate2d(
            method=method2d,
            data=self.__image,
            npt_rad=self.__nPointsRadial,
            npt_azim=self.__nPointsAzimuthal,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        # Create an image masked where data exists
        self.__resultMask2d = None
        if self.__mask is not None:
            if self.__mask.shape == self.__image.shape:
                maskData = numpy.ones(shape=self.__image.shape, dtype=numpy.float32)
                maskData[self.__mask == 0] = float("NaN")

                if self.__displayMask:
                    self.__resultMask2d = ai.integrate2d(
                        method=method2d,
                        data=maskData,
                        npt_rad=self.__nPointsRadial,
                        npt_azim=self.__nPointsAzimuthal,
                        unit=self.__radialUnit,
                        polarization_factor=self.__polarizationFactor)
            else:
                _logger.warning("Inconsistency between image and mask sizes. %s != %s", self.__image.shape, self.__mask.shape)

        try:
            self.__directDist = ai.getFit2D()["directDist"]
        except Exception:
            # The geometry could not fit this param
            _logger.debug("Backtrace", exc_info=True)
            self.__directDist = None

        if self.__calibrant:

            rings = self.__calibrant.get_2th()
            try:
                rings = unitutils.from2ThRad(rings, self.__radialUnit, self.__wavelength, self.__directDist)
            except ValueError:
                message = "Convertion to unit %s not supported. Ring marks ignored"
                _logger.warning(message, self.__radialUnit)
                rings = []
            # Filter the rings which are not part of the result
            rings = filter(lambda x: self.__result1d.radial[0] <= x <= self.__result1d.radial[-1], rings)
            rings = list(rings)
        else:
            rings = []
        self.__ringAngles = rings

        self.__ai = ai
Exemple #17
0
class Data_Reducer(QWidget):
    """
    This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data
    """
    def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, transmission_corr=True, azimuthRange=(-180.0,180.0), parent=None):
        """
        poniFile is the calibration file obtained after Q-calibration
        """
        QWidget.__init__(self,parent)
        self.layout=QGridLayout(self)
        self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r'))
        if poniFile is not None:
            self.poniFile=poniFile
        else:
            self.poniFile=self.setup_dict['poniFile']
        if maskFile is not None:
            self.maskFile=maskFile
        else:
            self.maskFile=self.setup_dict['maskFile']
        self.dataFile=dataFile
        if darkFile is None:
            self.dark_corrected=False
            self.darkFile=''
        else:
            self.darkFile=darkFile
            self.dark_corrected=True
       
        self.curDir=os.getcwd()
        
        self.extractedFolder=extractedFolder
        self.npt=npt
        self.set_externally=False
        self.transmission_corr=transmission_corr        
        #ai=AIWidget()
        #self.layout.addWidget(ai)
        self.azimuthRange=azimuthRange
        self.create_UI()
        if os.path.exists(self.poniFile):
            self.openPoniFile(file=self.poniFile)
        if os.path.exists(self.maskFile):
            self.openMaskFile(file=self.maskFile)
        
        
        
        
    def create_UI(self):
        """
        Creates the widget user interface
        """
        row=0
        col=0
        dataFileLabel=QLabel('Data file')
        self.layout.addWidget(dataFileLabel,row,col)
        col+=1
        self.dataFileLineEdit=QLineEdit(self.dataFile)
        self.layout.addWidget(self.dataFileLineEdit,row,col,1,2)
        col+=2
        self.openDataPushButton=QPushButton('Select')
        self.openDataPushButton.clicked.connect(self.openDataFiles)
        self.layout.addWidget(self.openDataPushButton,row,col)
        col+=1
        self.reducePushButton=QPushButton('Reduce data')
        self.reducePushButton.clicked.connect(self.reduce_multiple)
        self.layout.addWidget(self.reducePushButton,row,col,1,1)
        
        row+=1
        col=0
        darkFileLabel=QLabel('Dark file')
        self.layout.addWidget(darkFileLabel,row,col)
        col+=1
        self.darkFileLineEdit=QLineEdit(self.darkFile)
        self.layout.addWidget(self.darkFileLineEdit,row,col,1,2)
        col+=2
        self.openDarkPushButton=QPushButton('Select')
        self.openDarkPushButton.clicked.connect(self.openDarkFile)
        self.layout.addWidget(self.openDarkPushButton,row,col)
        col+=1
        self.darkCheckBox=QCheckBox('Dark Correction')
        self.layout.addWidget(self.darkCheckBox,row,col)
        
        row+=1
        col=0
        poniFileLabel=QLabel('Calibration file')
        self.layout.addWidget(poniFileLabel,row,col)
        col+=1
        self.poniFileLineEdit=QLineEdit(self.poniFile)
        self.layout.addWidget(self.poniFileLineEdit,row,col,1,2)
        col+=2
        self.openPoniPushButton=QPushButton('Select')
        self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None))
        self.layout.addWidget(self.openPoniPushButton,row,col)
        col+=1
        self.calibratePushButton=QPushButton('Calibrate')
        self.calibratePushButton.clicked.connect(self.calibrate)
        self.layout.addWidget(self.calibratePushButton,row,col)
        
        row+=1
        col=0
        maskFileLabel=QLabel('Mask file')
        self.layout.addWidget(maskFileLabel,row,col)
        col+=1
        self.maskFileLineEdit=QLineEdit(self.maskFile)
        self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged)
        self.layout.addWidget(self.maskFileLineEdit,row,col,1,2)
        col+=2
        self.openMaskPushButton=QPushButton('Select')
        self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None))
        self.layout.addWidget(self.openMaskPushButton,row,col)
        col+=1
        self.createMaskPushButton=QPushButton('Create mask')
        self.createMaskPushButton.clicked.connect(self.createMask)
        self.layout.addWidget(self.createMaskPushButton,row,col)
        
        
        row+=1
        col=0
        extractedFolderLabel=QLabel('Extracted folder')
        self.layout.addWidget(extractedFolderLabel,row,col)
        col+=1
        self.extractedFolderLineEdit=QLineEdit()
        self.layout.addWidget(self.extractedFolderLineEdit,row,col,1,2)
        col+=2
        self.extractedFolderPushButton=QPushButton('Select')
        self.extractedFolderPushButton.clicked.connect(self.openFolder)
        self.layout.addWidget(self.extractedFolderPushButton,row,col)
        
        row+=1
        col=0
        radialPointsLabel=QLabel('Radial Points')
        self.layout.addWidget(radialPointsLabel,row,col)
        col+=1
        self.radialPointsLineEdit=QLineEdit(str(self.npt))
        self.radialPointsLineEdit.returnPressed.connect(self.nptChanged)
        self.layout.addWidget(self.radialPointsLineEdit,row,col)
        col+=1
        azimuthRangeLabel=QLabel('Azimuthal Range (min:max)')
        self.layout.addWidget(azimuthRangeLabel,row,col)
        col+=1
        self.azimuthRangeLineEdit=QLineEdit('%.2f:%.2f'%(self.azimuthRange[0],self.azimuthRange[1]))
        self.azimuthRangeLineEdit.returnPressed.connect(self.azimuthRangeChanged)
        self.layout.addWidget(self.azimuthRangeLineEdit,row,col)
        col+=1
        self.transCorrCheckBox=QCheckBox('Transmission Correction')
        self.transCorrCheckBox.setTristate(False)
        self.transCorrCheckBox.setChecked(self.transmission_corr)
        self.layout.addWidget(self.transCorrCheckBox)
        
        
        row+=1
        col=0
        progressLabel=QLabel('Status')
        self.layout.addWidget(progressLabel,row,col,1,1)
        col+=1
        self.progressBar=QProgressBar()
        self.layout.addWidget(self.progressBar,row,col,1,1)
        col+=1
        self.statusLabel=QLabel('Idle')
        self.layout.addWidget(self.statusLabel,row,col,1,1)
        col+=1
        normLabel=QLabel('Normalized by')
        self.normComboBox=QComboBox()
        self.normComboBox.addItems(['BSDiode','Image sum','Monitor'])
        self.layout.addWidget(normLabel,row,col,1,1)
        col+=1
        self.layout.addWidget(self.normComboBox,row,col,1,1)
        
        row+=1
        col=0
        self.tabWidget=QTabWidget(self)
        self.layout.addWidget(self.tabWidget,row,col,20,5)
        self.imageWidget=Image_Widget(zeros((100,100)))
        imgNumberLabel=QLabel('Image number')
        self.imgNumberSpinBox=QSpinBox()
        self.imgNumberSpinBox.setSingleStep(1)
        self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1)
        self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2)
        self.imageView=self.imageWidget.imageView.getView()
        self.plotWidget=PlotWidget()
        self.plotWidget.setXLabel('Q, &#8491;<sup>-1</sup>',fontsize=5)
        self.plotWidget.setYLabel('Intensity',fontsize=5)
        self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data')
        self.tabWidget.addTab(self.imageWidget,'Masked 2D-data')
       
        
        
    def createMask(self):
        """
        Opens a mask-widget to create mask file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0])
        if fname is not None or fname!='':
            img=fb.open(fname).data
            self.maskWidget=MaskWidget(img)
            self.maskWidget.saveMaskPushButton.clicked.disconnect()
            self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask)
            self.maskWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok)
            
    def maskFileChanged(self):
        """
        Changes the mask file
        """
        maskFile=str(self.maskFileLineEdit.text())
        if str(maskFile)=='':
            self.maskFile=None
        elif os.path.exists(maskFile):
            self.maskFile=maskFile
        else:
            self.maskFile=None
            
    def save_mask(self):
        """
        Saves the entire mask combining all the shape ROIs
        """
        fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0])
        name,extn=os.path.splitext(fname)
        if extn=='':
            fname=name+'.msk'
        elif extn!='.msk':
            QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!')
            return
        else:
            tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None)
            tmpfile.save(fname)
            self.maskFile=fname
            self.maskFileLineEdit.setText(self.maskFile)
            
    def calibrate(self):
        """
        Opens a calibartion widget to create calibration file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0])
        if fname is not None:
            img=fb.open(fname).data
            pixel1=79.0
            pixel2=79.0
            self.calWidget=CalibrationWidget(img,pixel1,pixel2)
            self.calWidget.saveCalibrationPushButton.clicked.disconnect()
            self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration)
            self.calWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok)
            
    def save_calibration(self):
        fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0])
        tfname=os.path.splitext(fname)[0]+'.poni'
        self.calWidget.applyPyFAI()
        self.calWidget.geo.save(tfname)      
        self.poniFile=tfname
        self.poniFileLineEdit.setText(self.poniFile)
        
        
    def openPoniFile(self,file=None):
        """
        Select and imports the calibration file
        """
        if file is None:
            self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0]
            self.poniFileLineEdit.setText(self.poniFile)
        else:
            self.poniFile=file
        if os.path.exists(self.poniFile):
            self.setup_dict['poniFile']=self.poniFile
            json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            fh=open(self.poniFile,'r')
            lines=fh.readlines()
            self.calib_data={}
            for line in lines:
                if line[0]!='#':
                    key,val=line.split(': ')
                    self.calib_data[key]=float(val)
            self.dist=self.calib_data['Distance']
            self.pixel1=self.calib_data['PixelSize1']
            self.pixel2=self.calib_data['PixelSize2']
            self.poni1=self.calib_data['Poni1']
            self.poni2=self.calib_data['Poni2']
            self.rot1=self.calib_data['Rot1']
            self.rot2=self.calib_data['Rot2']
            self.rot3=self.calib_data['Rot3']
            self.wavelength=self.calib_data['Wavelength']
            self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength)
            #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1]
            #self.roi=cake(pos,movable=False)
            #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged)
            
            #self.imageView.addItem(self.roi)
        else:
            QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok)                
        
    def endAngleChanged(self,evt):
        print(evt.pos())
        
        
    def nptChanged(self):
        """
        Changes the number of radial points
        """
        try:
            self.npt=int(self.radialPointsLineEdit.text())
        except:
            QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok)
            
    def azimuthRangeChanged(self):
        """
        Changes the azimuth angular range
        """
        try:
            self.azimuthRange=tuple(map(float, self.azimuthRangeLineEdit.text().split(':')))
        except:
            QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok)
        
    def openDataFile(self):
        """
        Select and imports one data file
        """
        dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0]
        if dataFile!='':
            self.dataFile=dataFile
            self.curDir=os.path.dirname(self.dataFile)
            self.dataFileLineEdit.setText(self.dataFile)
            self.data2d=fb.open(self.dataFile).data
            if self.darkFile is not None:
                self.applyDark()
            if self.maskFile is not None:
                self.applyMask()    
            self.imageWidget.setImage(self.data2d,transpose=True)
            self.tabWidget.setCurrentWidget(self.imageWidget)
            if not self.set_externally:
                self.extractedFolder=os.path.join(self.curDir,'extracted_pyFAI')
                if not os.path.exists(self.extractedFolder):
                    os.makedirs(self.extractedFolder)
                    
    def openDataFiles(self):
        """
        Selects and imports multiple data files
        """
        self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0]
        if len(self.dataFiles)!=0:
            self.imgNumberSpinBox.valueChanged.connect(self.imageChanged)
            self.imgNumberSpinBox.setMinimum(0)
            self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1)
            self.dataFileLineEdit.setText(str(self.dataFiles))
            self.curDir=os.path.dirname(self.dataFiles[0])
            self.extractedFolder=os.path.join(self.curDir,'extracted_pyFAI')
            if not os.path.exists(self.extractedFolder):
                os.makedirs(self.extractedFolder)
            self.extractedFolderLineEdit.setText(self.extractedFolder)
            self.imgNumberSpinBox.setValue(0)
            self.imageChanged()
            
    def imageChanged(self):
        self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data
        if self.darkFile is not None:
            self.applyDark()
        if self.maskFile is not None:
            self.applyMask()    
        self.imageWidget.setImage(self.data2d,transpose=True)
            

                  
            
                
    def applyDark(self):
        if not self.dark_corrected and self.darkFile!='':
            self.dark2d=fb.open(self.darkFile).data
            self.data2d=self.data2d-self.dark2d
            self.dark_corrected=True
                
    def applyMask(self):
        self.mask2d=fb.open(self.maskFile).data
        self.data2d=self.data2d*(1+self.mask2d)/2.0
        self.mask_applied=True

    def openDarkFile(self):
        """
        Select and imports the dark file
        """
        self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0]
        if self.darkFile!='':
            self.dark_corrected=False
            self.darkFileLineEdit.setText(self.darkFile)
            if self.dataFile is not None:
                self.data2d=fb.open(self.dataFile).data
                self.applyDark()
        
    
    def openMaskFile(self,file=None):
        """
        Select and imports the Mask file
        """
        if file is None:
            self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0]
        else:
            self.maskFile=file
        if self.maskFile!='':
            self.mask_applied=False
            if os.path.exists(self.maskFile):
                self.curDir=os.path.dirname(self.maskFile)
                self.maskFileLineEdit.setText(self.maskFile)
                self.setup_dict['maskFile']=self.maskFile
                self.setup_dict['poniFile']=self.poniFile
                json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            else:
                self.openMaskFile(file=None)
            if self.dataFile is not None:
                self.applyMask()
        else:
            self.maskFile=None
            self.maskFileLineEdit.clear()
            
            
        
    def openFolder(self):
        """
        Select the folder to save the reduce data
        """
        self.extractedFolder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir)
        if self.extractedFolder!='':
            self.extractedFolderLineEdit.setText(self.extractedFolder)
            self.set_externally=True
        
        
    def reduceData(self):
        """
        Reduces the 2d data to 1d data
        """
        print('Iloveu', self.darkFile)
        if (self.dataFile is not None) and (os.path.exists(self.dataFile)):
            if (self.poniFile is not None) and (os.path.exists(self.poniFile)):
#                self.statusLabel.setText('Busy')
#                self.progressBar.setRange(0, 0)
                imageData=fb.open(self.dataFile)
                #self.data2d=imageData.data
                #if self.maskFile is not None:
                #    self.applyMask()    
                #self.imageWidget.setImage(self.data2d,transpose=True)
                #self.tabWidget.setCurrentWidget(self.imageWidget)
                
                self.header=imageData.header
                try:
                    self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10)
                except:
                    self.ai.set_wavelength(self.wavelength)
                print('Iloveu',self.darkFile)
                if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked():
                    self.darkFile=self.dataFile.split('.')[0]+'_dark.edf'
                    dark=fb.open(self.darkFile)
                    self.darkFileLineEdit.setText(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File read from existing dark files")                    
                elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked():
                    dark=fb.open(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File from memory subtracted")                
                else:
                    imageDark=None
                    try:
                        self.header['BSDiode_corr']=float(imageData.header['BSDiode'])
                        self.header['Monitor_corr']=float(imageData.header['Monitor'])
                    except:
                        self.transCorrCheckBox.setCheckState(Qt.Unchecked)
                    print("No dark correction done")
                if self.transCorrCheckBox.isChecked():
                    if str(self.normComboBox.currentText())=='BSDiode':
                        norm_factor=self.header['BSDiode_corr']#/float(self.header['count_time'])
                    elif str(self.normComboBox.currentText())=='Monitor':
                        norm_factor=self.header['Monitor_corr']
                    else:
                        norm_factor=sum(imageData.data)
                else:
                    norm_factor=1.0
                    
                if self.maskFile is not None:
                    imageMask=fb.open(self.maskFile).data
                else:
                    imageMask=None
                print(self.maskFile)
#                QApplication.processEvents()
                #print(self.azimuthRange)
                self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthRange)
                self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data')
                self.plotWidget.setTitle(self.dataFile,fontsize=2)
#                self.progressBar.setRange(0,100)
#                self.progressBar.setValue(100)
#                self.statusLabel.setText('Idle')
#                QApplication.processEvents()
                self.saveData()
                self.tabWidget.setCurrentWidget(self.plotWidget)
            else:
                QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok)
                
        else:
            QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok)
            
    def reduce_multiple(self):
        """
        Reduce multiple files
        """
        #try:
        i=0
        self.progressBar.setRange(0,len(self.dataFiles))
        self.progressBar.setValue(i)
        self.statusLabel.setText('Busy')
        for file in self.dataFiles:
            self.dataFile=file
            QApplication.processEvents()
            self.reduceData()
            i=i+1
            self.progressBar.setValue(i)
            QApplication.processEvents()
        self.statusLabel.setText('Idle')
        #except:
        #    QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok)
        
    def saveData(self):
        """
        saves the extracted data into a file
        """
        if not os.path.exists(self.extractedFolder):
            os.makedirs(self.extractedFolder)
        filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt')
        headers='File extracted on '+time.asctime()+'\n'
        headers='Files used for extraction are:\n'
        headers+='Data file: '+self.dataFile+'\n'
        if self.darkFile is not None:
            headers+='Dark file: '+self.darkFile+'\n'
        else:
            headers+='Dark file: None\n'
        headers+='Poni file: '+self.poniFile+'\n'
        if self.maskFile is not None:
            headers+='mask file: '+self.maskFile+'\n'
        else:
            headers+='mask file: None\n'
        for key in self.header.keys():
            headers+=key+'='+str(self.header[key])+'\n'
        headers+='Q (A^-1)\t\tIntensity\t\tIntensity_err'
        data=vstack((self.q,self.I,self.Ierr)).T
        savetxt(filename,data,header=headers,comments='#')
Exemple #18
0
class EwaldArch(PawsPlugin):
    """Class for storing area detector data collected in
    X-ray diffraction experiments.

    Attributes:
        idx: integer name of arch
        map_raw: numpy 2d array of the unprocessed image data
        poni: poni data for integration
        mask: map of pixels to be masked out of integration
        scan_info: information from any relevant motors and sensors
        ai_args: arguments passed to AzimuthalIntegrator
        file_lock: lock to ensure only one writer to data file
        integrator: AzimuthalIntegrator object from pyFAI
        arch_lock: threading lock used to ensure only one process can
            access data at a time
        map_norm: normalized image data
        map_q: reciprocal space coordinates for data
        int_1d: int_1d_data object from containers
        int_2d: int_2d_data object from containers

    Methods:
        integrate_1d: integrate the image data to create I, 2theta, q,
            and normalization arrays
        integrate_2d: not implemented
        set_integrator: set new integrator
        set_map_raw: replace raw data
        set_poni: replace poni object
        set_mask: replace mask data
        set_scan_info: replace scan_info
        save_to_h5: save data to hdf5 file
        load_from_h5: load data from hdf5 file
        copy: create copy of arch
    """

    # pylint: disable=too-many-instance-attributes

    def __init__(self,
                 idx=None,
                 map_raw=None,
                 poni=PONI(),
                 mask=None,
                 scan_info={},
                 ai_args={},
                 file_lock=Condition()):
        # pylint: disable=too-many-arguments
        super(EwaldArch, self).__init__()
        self.idx = idx
        self.map_raw = map_raw
        self.poni = poni
        if mask is None and map_raw is not None:
            self.mask = np.where(map_raw < 0, 1, 0)
        else:
            self.mask = mask
        self.scan_info = scan_info
        self.ai_args = ai_args
        self.file_lock = file_lock
        self.integrator = AzimuthalIntegrator(dist=self.poni.dist,
                                              poni1=self.poni.poni1,
                                              poni2=self.poni.poni2,
                                              rot1=self.poni.rot1,
                                              rot2=self.poni.rot2,
                                              rot3=self.poni.rot3,
                                              wavelength=self.poni.wavelength,
                                              detector=self.poni.detector,
                                              **ai_args)
        self.arch_lock = Condition()
        self.map_norm = 0
        self.map_q = 0
        self.int_1d = int_1d_data()
        self.int_2d = int_2d_data()
        self.xyz = None  # TODO: implement rotations to generate pixel coords
        self.tcr = None
        self.qchi = None

    def integrate_1d(self,
                     numpoints=10000,
                     radial_range=[0, 180],
                     monitor=None,
                     unit=units.TTH_DEG,
                     **kwargs):
        """Wrapper for integrate1d method of AzimuthalIntegrator from pyFAI.
        Sets 1d integration variables for object instance.

        args:
            numpoints: int, number of points in final array
            radial_range: tuple or list, lower and upper end of integration
            monitor: str, keyword for normalization counter in scan_info
            unit: pyFAI unit for integration, units.TTH_DEG or units.Q_A
            kwargs: other keywords to be passed to integrate1d, see pyFAI docs.

        returns:
            result: integrate1d result from pyFAI.
        """
        with self.arch_lock:
            if monitor is not None:
                self.map_norm = self.map_raw / self.scan_info[monitor]
            else:
                self.map_norm = self.map_raw
            if self.mask is None:
                self.mask = np.where(self.map_raw < 0, 1, 0)

            result = self.integrator.integrate1d(self.map_norm,
                                                 numpoints,
                                                 unit=unit,
                                                 radial_range=radial_range,
                                                 mask=self.mask,
                                                 **kwargs)

            self.int_1d.ttheta, self.int_1d.q = parse_unit(
                result, self.poni.wavelength)

            self.int_1d.pcount = result._count
            self.int_1d.raw = result._sum_signal
            self.int_1d.norm = pawstools.div0(self.int_1d.raw,
                                              self.int_1d.pcount)
        return result

    def integrate_2d(self):
        """Not implemented.
        """
        with self.arch_lock:
            pass

    def set_integrator(self, **args):
        """Sets AzimuthalIntegrator with new arguments and instances poni
        attribute.

        args:
            args: see pyFAI for acceptable arguments for the integrator
                constructor.

        returns:
            None
        """

        with self.arch_lock:
            self.ai_args = args
            self.integrator = AzimuthalIntegrator(
                dist=self.poni.dist,
                poni1=self.poni.poni1,
                poni2=self.poni.poni2,
                rot1=self.poni.rot1,
                rot2=self.poni.rot2,
                rot3=self.poni.rot3,
                wavelength=self.poni.wavelength,
                detector=self.poni.detector,
                **args)

    def set_map_raw(self, new_data):
        with self.arch_lock:
            self.map_raw = new_data
            if self.mask is None:
                self.mask = np.where(self.map_raw < 0, 1, 0)

    def set_poni(self, new_data):
        with self.arch_lock:
            self.poni = new_data

    def set_mask(self, new_data):
        with self.arch_lock:
            self.mask = new_data

    def set_scan_info(self, new_data):
        with self.arch_lock:
            self.scan_info = new_data

    def save_to_h5(self, file):
        """Saves data to hdf5 file using h5py as backend.

        args:
            file: h5py group or file object.

        returns:
            None
        """
        with self.file_lock:
            if str(self.idx) in file:
                del (file[str(self.idx)])
            grp = file.create_group(str(self.idx))
            lst_attr = [
                "map_raw", "mask", "map_norm", "map_q", "xyz", "tcr", "qchi",
                "scan_info", "ai_args"
            ]
            pawstools.attributes_to_h5(self, grp, lst_attr)
            grp.create_group('int_1d')
            pawstools.attributes_to_h5(self.int_1d, grp['int_1d'])
            grp.create_group('int_2d')
            pawstools.attributes_to_h5(self.int_2d, grp['int_2d'])
            grp.create_group('poni')
            pawstools.dict_to_h5(self.poni.to_dict(), grp['poni'])

    def load_from_h5(self, file):
        """Loads data from hdf5 file and sets attributes.

        args:
            file: h5py file or group object

        returns:
            None
        """
        with self.file_lock:
            with self.arch_lock:
                if str(self.idx) not in file:
                    print("No data can be found")
                grp = file[str(self.idx)]
                lst_attr = [
                    "map_raw", "mask", "map_norm", "map_q", "xyz", "tcr",
                    "qchi", "scan_info", "ai_args"
                ]
                pawstools.h5_to_attributes(self, grp, lst_attr)
                pawstools.h5_to_attributes(self.int_1d, grp['int_1d'])
                pawstools.h5_to_attributes(self.int_2d, grp['int_2d'])
                self.poni = PONI.from_yamdict(pawstools.h5_to_dict(
                    grp['poni']))
                self.integrator = AzimuthalIntegrator(
                    dist=self.poni.dist,
                    poni1=self.poni.poni1,
                    poni2=self.poni.poni2,
                    rot1=self.poni.rot1,
                    rot2=self.poni.rot2,
                    rot3=self.poni.rot3,
                    wavelength=self.poni.wavelength,
                    detector=self.poni.detector,
                    **self.ai_args)

    def copy(self):
        arch_copy = EwaldArch(copy.deepcopy(self.idx),
                              copy.deepcopy(self.map_raw),
                              copy.deepcopy(self.poni),
                              copy.deepcopy(self.mask),
                              copy.deepcopy(self.scan_info),
                              copy.deepcopy(self.ai_args), self.file_lock)
        arch_copy.integrator = copy.deepcopy(self.integrator)
        arch_copy.arch_lock = Condition()
        arch_copy.map_norm = copy.deepcopy(self.map_norm)
        arch_copy.map_q = copy.deepcopy(self.map_q)
        arch_copy.int_1d = copy.deepcopy(self.int_1d)
        arch_copy.int_2d = copy.deepcopy(self.int_2d)
        arch_copy.xyz = copy.deepcopy(self.xyz)
        arch_copy.tcr = copy.deepcopy(self.tcr)
        arch_copy.qchi = copy.deepcopy(self.qchi)

        return arch_copy
Exemple #19
0
def integrate_them(o):
    """ 
    Process a series of files
    o = options object 
    o.parfile gives name of parameter file (fit2d or poni format)
    o.dark overrides to supply dark filename
    o.flood overrides to supply flood filename
    o.mask  overrides to supply mask filename
    o.backcalc asks for the back computation of the image
    o.npts gives number of output points
    """
    # pyFAI.load( ponifile )
    integrator = AzimuthalIntegrator()
    #integrator.tth = integrator.newtth
    # integrator.setChiDiscAtZero()
    ptype = determineparfile(o.parfile)
    if ptype == "pyfai":
        integrator.load(o.parfile)
        if o.dark is not None:
            print("Using dark from command line", o.dark)
        if o.flood is not None:
            print("Using dark from command line", o.flood)
    elif ptype == "fit2d":
        f2d = fit2dcakepars(o.parfile)
        if f2d["SPATIAL DIS."][0] not in ["Y", "y"]:
            # Set to None. Spatial is from parfile
            f2d["SD FILE"] = None
        integrator.setFit2D(float(f2d["DISTANCE"]),
                            float(f2d["X-BEAM CENTRE"]),
                            float(f2d["Y-BEAM CENTRE"]),
                            tilt=float(f2d["ANGLE OF TILT"]),
                            tiltPlanRotation=float(f2d["TILT ROTATION"]),
                            pixelX=float(f2d["X-PIXEL SIZE"]),
                            pixelY=float(f2d["Y-BEAM CENTRE"]),
                            splineFile=f2d["SD FILE"])
        integrator.rot3 = 0
        integrator.reset()
        print(integrator.param, integrator.detector.pixel1)
        # First choice is command line. Then from pars if supplied
        if o.dark is None:
            if f2d["DARK CURRENT"][0] in ["Y", "y"]:
                o.dark = f2d["DC FILE"]
                print("Using dark from fit2d parameter file", o.dark)
        else:
            print("Using dark from command line", o.dark)
        if o.flood is None:
            if f2d["FLAT-FIELD"][0] in ["Y", "y"]:
                o.flood = f2d["FF FILE"]
                print("Using flood from fit2d parameter file", o.flood)
        else:
            print("Using flood from command line", o.flood)
    # Should be in fabio utilities
    df = darkflood(o.dark, o.flood)
    # Should be in fabio
    fs = edffilenameseries(o.stem, o.first, o.last, o.glob, o.extn)
    #    integrator.polarization( factor = 1, shape=(2048,2048) )
    # Command line is first priority for make
    if o.mask is not None:
        mask = fabio.open(o.mask).data
        print("Using mask", o.mask)
        # assume poni file deals with this independently?
    elif ptype == "fit2d":  # try in fit2d parfile
        if f2d["USE MASK"][0] in ['y', 'Y']:
            mask = fabio.open(f2d["MASK FILE"]).data
            print("Using mask", f2d["MASK FILE"])
        else:
            mask = None
    if mask is not None:
        print("mask mean:", mask.mean())
    integrator.write(os.path.splitext(o.parfile)[0] + ".poni")
    for f in fs:
        print("Processing", f, end=' ')
        try:
            fo = df.correct(fabio.open(f))
        except:

            continue
        if ptype == "fit2d":
            outFile = f.replace(f2d["input_extn"], f2d["output_extn"])
        else:
            outFile = f.replace(o.extn, ".dat")
        global SOLID_ANGLE
        if 0:
            from matplotlib.pylab import imshow, figure, show, log, plot
            #imshow(log(fo.data))
            #figure()
            if mask is not None:
                imshow(log(fo.data * (1 - mask)),
                       vmin=log(10),
                       vmax=log(30000))
            else:
                imshow(log(fo.data), vmin=log(100), vmax=log(3000))
            #        show()
        if o.npts is None:
            npts = min(fo.data.shape)
        else:
            npts = int(o.npts)
        tth, I = integrator.integrate1d(
            fo.data,
            nbPt=npts,
            filename=outFile,
            correctSolidAngle=SOLID_ANGLE,
            mask=mask,  # 1 for valid
            unit="q_A^-1",
            #dummy=dummy, # mask pixels == dummy
            #delta_dummy=delta_dummy # precision of dummy
        )

        print("wrote", outFile)
        if o.backcalc:
            calcimage = calcfrom1d(integrator, tth, I,
                                   fo.data.shape) * integrator._polarization
            err = (calcimage - fo.data) * (1 - mask) / (calcimage + mask)
            e = fabio.edfimage.edfimage(data=err.astype(numpy.float32))
            e.write(outFile + ".edf")
            fitcen(fo.data, calcimage, (1 - mask))


#            from matplotlib.pylab import imshow, show
#            imshow( integrator._polarization )
#            show()

    if o.display:
        if mask is not None:
            display(tth, I,
                    (calcimage - fo.data) * (1 - mask) / (calcimage + 1))
        else:
            display(tth, I, (calcimage - fo.data) / (calcimage + 1))
Exemple #20
0
def run(args):
    FILE = args.INPUT
    if np.shape(FILE) == (1, ):
        FILE = np.sort(glob.glob(str(FILE[0])))
    sample_name = str(input("Enter sample name for saving: "))
    SAVE_PATH = os.getcwd()
    if args.OUTPUT:
        SAVE_PATH = args.OUTPUT
    else:
        print(
            '!!! Warning files will be saved in the current folder because no output was defined.'
        )
    currentFile = np.zeros((len(FILE), 2))

    ### Read json file ###
    jsonParam = readJson(args.JSON)

    ### Grabbing first file to check matrix size ###
    for i in range(0, len(FILE)):
        currentFile[i] = re.findall(r'\d{3,7}', FILE[i])
        progression("Cheking files, matrix size definition ..... ", len(FILE),
                    i)
    pattern = np.zeros(
        (int(np.max(currentFile[:, 0])), int(np.max(currentFile[:, 1]) + 1),
         int(jsonParam['nbpt_rad'])))

    ### Normalisation matrix ###
    pico = np.zeros((len(FILE), 1))
    for i in range(len(FILE)):
        image = fabio.open(FILE[i])
        counter = image.header["counter_pos"].split(" ")
        pico[i] = float(counter[7]) * 0.0000001
        progression("Importing counter ..... ", len(FILE), i)
    picoCorrected = median_filter(pico.flat, 9, mode='constant')

    ### Integration of FILE ###
    azimutalIntegrator = AzimuthalIntegrator(
        dist=jsonParam['dist'],
        poni1=jsonParam['poni1'],
        poni2=jsonParam['poni2'],
        rot1=jsonParam['rot1'],
        rot2=jsonParam['rot2'],
        rot3=jsonParam['rot3'],
        pixel1=jsonParam['pixel1'],
        pixel2=jsonParam['pixel2'],
        splineFile=jsonParam['splineFile'],
        detector=jsonParam['detector'],
        wavelength=jsonParam['wavelength'])
    dark = np.array(fabio.open(jsonParam['dark_current']).data)
    flat = np.array(fabio.open(jsonParam['flat_field']).data)
    mask = np.array(fabio.open(jsonParam['mask_file']).data)
    offset_rot = int(np.min(currentFile[:, 0]))
    offset_trans = int(np.min(currentFile[:, 1]))
    for i in range(len(FILE)):
        dataFile = np.array(fabio.open(FILE[i]).data)
        if args.SEPARATE:
            bragg, amorphous = azimutalIntegrator.separate(
                dataFile,
                npt_rad=1024,
                npt_azim=512,
                unit=jsonParam['unit'],
                method='splitpixel',
                percentile=50,
                mask=None,
                restore_mask=True)
            if args.SEPARATE == 'Amorphous':
                dataFile = amorphous
            elif args.SEPARATE == 'Bragg':
                dataFile = bragg
        dataX, dataY = azimutalIntegrator.integrate1d(
            dataFile,
            int(jsonParam['nbpt_rad']),
            filename=None,
            correctSolidAngle=jsonParam['do_solid_angle'],
            variance=None,
            error_model=None,
            radial_range=(float(jsonParam['radial_range_min']),
                          float(jsonParam['radial_range_max'])),
            azimuth_range=None,
            mask=mask,
            dummy=jsonParam['do_dummy'],
            delta_dummy=jsonParam['delta_dummy'],
            polarization_factor=None,
            method='csr',
            dark=dark,
            flat=flat,
            unit=jsonParam['unit'],
            safe=True,
            normalization_factor=picoCorrected[i],
            profile=False,
            all=False,
            metadata=None)
        currentFile[i] = re.findall(r'\d{3,7}', FILE[i])
        pattern[int(currentFile[i, 0]) - offset_rot,
                int(currentFile[i, 1]) - offset_trans, :] = dataY
        progression("Integrating data............. ", len(FILE), i)
    print()

    ### Storing special 2-theta ###
    theta = np.linspace(int(np.min(currentFile[:, 0])),
                        int(np.max(currentFile[:, 0])),
                        int(np.max(currentFile[:, 0])))
    if args.THETA:
        special_theta = np.fromstring(args.THETA, dtype=int, sep=',')
        for i in range(0, np.size(theta, 0)):
            theta[i] = i * (int(special_theta[0])) % int(special_theta[1])

    ### Multiplier ###
    if args.MULTIPLIER:
        pattern = pattern * int(args.MULTIPLIER)

    ### Saving ###
    if args.OUTPUT:
        f = h5py.File(args.OUTPUT + sample_name + '_sinogram.xrdct', 'w')
    else:
        f = h5py.File(sample_name + '_sinogram.xrdct', 'w')
    grp = f.create_group("data")

    dset = f.create_dataset('data/data', np.shape(pattern), dtype='f')
    dset[:, :, :] = pattern[:, :, :]

    dset_theta = f.create_dataset('data/theta', np.shape(theta), dtype='f')
    dset_theta[:] = theta[:]

    dset_dataX = f.create_dataset('data/dataX', np.shape(dataX), dtype='f')
    dset_dataX[:] = dataX[:]
Exemple #21
0
class CalibrationModel(object):
    def __init__(self, img_model=None):
        """
        :param img_model:
        :type img_model: ImgModel
        """
        self.img_model = img_model
        self.points = []
        self.points_index = []
        self.spectrum_geometry = AzimuthalIntegrator()
        self.cake_geometry = None
        self.calibrant = Calibrant()
        self.start_values = {'dist': 200e-3,
                             'wavelength': 0.3344e-10,
                             'pixel_width': 79e-6,
                             'pixel_height': 79e-6,
                             'polarization_factor': 0.99}
        self.orig_pixel1 = 79e-6
        self.orig_pixel2 = 79e-6
        self.fit_wavelength = False
        self.fit_distance = True
        self.is_calibrated = False
        self.use_mask = False
        self.filename = ''
        self.calibration_name = 'None'
        self.polarization_factor = 0.99
        self.supersampling_factor = 1
        self._calibrants_working_dir = os.path.dirname(calibrants.__file__)

        self.cake_img = np.zeros((2048, 2048))
        self.tth = np.linspace(0, 25)
        self.int = np.sin(self.tth)
        self.num_points = len(self.int)

        self.peak_search_algorithm = None

    def find_peaks_automatic(self, x, y, peak_ind):
        """
        Searches peaks by using the Massif algorithm
        :param x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param y:
            y-coordinate in pixel - should be from original image (not supersampled y-coordinate)
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            array of points found
        """
        massif = Massif(self.img_model._img_data)
        cur_peak_points = massif.find_peaks([x, y], stdout=DummyStdOut())
        if len(cur_peak_points):
            self.points.append(np.array(cur_peak_points))
            self.points_index.append(peak_ind)
        return np.array(cur_peak_points)

    def find_peak(self, x, y, search_size, peak_ind):
        """
        Searches a peak around the x,y position. It just searches for the maximum value in a specific search size.
        :param x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param y:
            y-coordinate in pixel - should be form original image (not supersampled y-coordinate)
        :param search_size:
            the length of the search rectangle in pixels in all direction in which the algorithm searches for
            the maximum peak
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            point found (as array)
        """
        left_ind = np.round(x - search_size * 0.5)
        if left_ind < 0:
            left_ind = 0
        top_ind = np.round(y - search_size * 0.5)
        if top_ind < 0:
            top_ind = 0
        search_array = self.img_model.img_data[left_ind:(left_ind + search_size),
                       top_ind:(top_ind + search_size)]
        x_ind, y_ind = np.where(search_array == search_array.max())
        x_ind = x_ind[0] + left_ind
        y_ind = y_ind[0] + top_ind
        self.points.append(np.array([x_ind, y_ind]))
        self.points_index.append(peak_ind)
        return np.array([np.array((x_ind, y_ind))])

    def clear_peaks(self):
        self.points = []
        self.points_index = []

    def create_cake_geometry(self):
        self.cake_geometry = AzimuthalIntegrator()

        pyFAI_parameter = self.spectrum_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        pyFAI_parameter['wavelength'] = self.spectrum_geometry.wavelength

        self.cake_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                    poni1=pyFAI_parameter['poni1'],
                                    poni2=pyFAI_parameter['poni2'],
                                    rot1=pyFAI_parameter['rot1'],
                                    rot2=pyFAI_parameter['rot2'],
                                    rot3=pyFAI_parameter['rot3'],
                                    pixel1=pyFAI_parameter['pixel1'],
                                    pixel2=pyFAI_parameter['pixel2'])

        self.cake_geometry.wavelength = pyFAI_parameter['wavelength']

    def setup_peak_search_algorithm(self, algorithm, mask=None):
        """
        Initializes the peak search algorithm on the current image
        :param algorithm:
            peak search algorithm used. Possible algorithms are 'Massif' and 'Blob'
        :param mask:
            if a mask is used during the process this is provided here as a 2d array for the image.
        """

        if algorithm == 'Massif':
            self.peak_search_algorithm = Massif(self.img_model.get_raw_img_data())
        elif algorithm == 'Blob':
            if mask is not None:
                self.peak_search_algorithm = BlobDetection(self.img_model.get_raw_img_data() * mask)
            else:
                self.peak_search_algorithm = BlobDetection(self.img_model.get_raw_img_data())
            self.peak_search_algorithm.process()
        else:
            return

    def search_peaks_on_ring(self, ring_index, delta_tth=0.1, min_mean_factor=1,
                             upper_limit=55000, mask=None):
        """
        This function is searching for peaks on an expected ring. It needs an initial calibration
        before. Then it will search for the ring within some delta_tth and other parameters to get
        peaks from the calibrant.

        :param ring_index: the index of the ring for the search
        :param delta_tth: search space around the expected position in two theta
        :param min_mean_factor: a factor determining the minimum peak intensity to be picked up. it is based
                                on the mean value of the search area defined by delta_tth. Pick a large value
                                for larger minimum value and lower for lower minimum value. Therefore, a smaller
                                number is more prone to picking up noise. typical values like between 1 and 3.
        :param upper_limit: maximum intensity for the peaks to be picked
        :param mask: in case the image has to be masked from certain areas, it need to be given here. Default is None.
                     The mask should be given as an 2d array with the same dimensions as the image, where 1 denotes a
                     masked pixel and all others should be 0.
        """
        self.reset_supersampling()
        if not self.is_calibrated:
            return

        # transform delta from degree into radians
        delta_tth = delta_tth / 180.0 * np.pi

        # get appropriate two theta value for the ring number
        tth_calibrant_list = self.calibrant.get_2th()
        tth_calibrant = np.float(tth_calibrant_list[ring_index])

        # get the calculated two theta values for the whole image
        if self.spectrum_geometry._ttha is None:
            tth_array = self.spectrum_geometry.twoThetaArray(self.img_model._img_data.shape)
        else:
            tth_array = self.spectrum_geometry._ttha

        # create mask based on two_theta position
        ring_mask = abs(tth_array - tth_calibrant) <= delta_tth

        if mask is not None:
            mask = np.logical_and(ring_mask, np.logical_not(mask))
        else:
            mask = ring_mask

        # calculate the mean and standard deviation of this area
        sub_data = np.array(self.img_model._img_data.ravel()[np.where(mask.ravel())], dtype=np.float64)
        sub_data[np.where(sub_data > upper_limit)] = np.NaN
        mean = np.nanmean(sub_data)
        std = np.nanstd(sub_data)

        # set the threshold into the mask (don't detect very low intensity peaks)
        threshold = min_mean_factor * mean + std
        mask2 = np.logical_and(self.img_model._img_data > threshold, mask)
        mask2[np.where(self.img_model._img_data > upper_limit)] = False
        size2 = mask2.sum(dtype=int)

        keep = int(np.ceil(np.sqrt(size2)))
        try:
            sys.stdout = DummyStdOut
            res = self.peak_search_algorithm.peaks_from_area(mask2, Imin=mean - std, keep=keep)
            sys.stdout = sys.__stdout__
        except IndexError:
            res = []

        # Store the result
        if len(res):
            self.points.append(np.array(res))
            self.points_index.append(ring_index)

        self.set_supersampling()
        self.spectrum_geometry.reset()

    def set_calibrant(self, filename):
        self.calibrant = Calibrant()
        self.calibrant.load_file(filename)
        self.spectrum_geometry.calibrant = self.calibrant

    def set_start_values(self, start_values):
        self.start_values = start_values
        self.polarization_factor = start_values['polarization_factor']

    def calibrate(self):
        self.spectrum_geometry = GeometryRefinement(self.create_point_array(self.points, self.points_index),
                                                    dist=self.start_values['dist'],
                                                    wavelength=self.start_values['wavelength'],
                                                    pixel1=self.start_values['pixel_width'],
                                                    pixel2=self.start_values['pixel_height'],
                                                    calibrant=self.calibrant)
        self.orig_pixel1 = self.start_values['pixel_width']
        self.orig_pixel2 = self.start_values['pixel_height']

        self.refine()
        self.create_cake_geometry()
        self.is_calibrated = True

        self.calibration_name = 'current'
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.spectrum_geometry.reset()

    def refine(self):
        self.reset_supersampling()
        self.spectrum_geometry.data = self.create_point_array(self.points, self.points_index)

        fix = ['wavelength']
        if self.fit_wavelength:
            fix = []
        if not self.fit_distance:
            fix.append('dist')
        if self.fit_wavelength:
            self.spectrum_geometry.refine2()
        self.spectrum_geometry.refine2_wavelength(fix=fix)

        self.create_cake_geometry()
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.spectrum_geometry.reset()

    def integrate_1d(self, num_points=None, mask=None, polarization_factor=None, filename=None,
                     unit='2th_deg', method='csr'):
        if np.sum(mask) == self.img_model.img_data.shape[0] * self.img_model.img_data.shape[1]:
            # do not perform integration if the image is completely masked...
            return self.tth, self.int

        if self.spectrum_geometry._polarization is not None:
            if self.img_model.img_data.shape != self.spectrum_geometry._polarization.shape:
                # resetting the integrator if the polarization correction matrix has not the correct shape
                self.spectrum_geometry.reset()

        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if num_points is None:
            num_points = self.calculate_number_of_spectrum_points(2)
        self.num_points = num_points

        t1 = time.time()

        if unit is 'd_A':
            try:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                        method=method,
                                                                        unit='2th_deg',
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            except NameError:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                        method='csr',
                                                                        unit='2th_deg',
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            self.tth = self.spectrum_geometry.wavelength / (2 * np.sin(self.tth / 360 * np.pi)) * 1e10
            self.int = self.int
        else:
            try:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                        method=method,
                                                                        unit=unit,
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            except NameError:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_model.img_data, num_points,
                                                                        method='csr',
                                                                        unit=unit,
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
        logger.info('1d integration of {0}: {1}s.'.format(os.path.basename(self.img_model.filename), time.time() - t1))

        ind = np.where((self.int > 0) & (~np.isnan(self.int)))
        self.tth = self.tth[ind]
        self.int = self.int[ind]
        return self.tth, self.int

    def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', method='csr', dimensions=(2048, 2048)):
        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if self.cake_geometry._polarization is not None:
            if self.img_model.img_data.shape != self.cake_geometry._polarization.shape:
                # resetting the integrator if the polarization correction matrix has not the same shape as the image
                self.cake_geometry.reset()

        t1 = time.time()

        res = self.cake_geometry.integrate2d(self.img_model._img_data, dimensions[0], dimensions[1], method=method,
                                             mask=mask,
                                             unit=unit, polarization_factor=polarization_factor)
        logger.info('2d integration of {0}: {1}s.'.format(os.path.basename(self.img_model.filename), time.time() - t1))
        self.cake_img = res[0]
        self.cake_tth = res[1]
        self.cake_azi = res[2]
        return self.cake_img

    def create_point_array(self, points, points_ind):
        res = []
        for i, point_list in enumerate(points):
            if point_list.shape == (2,):
                res.append([point_list[0], point_list[1], points_ind[i]])
            else:
                for point in point_list:
                    res.append([point[0], point[1], points_ind[i]])
        return np.array(res)

    def get_point_array(self):
        return self.create_point_array(self.points, self.points_index)

    def get_calibration_parameter(self):
        pyFAI_parameter = self.cake_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        try:
            fit2d_parameter = self.cake_geometry.getFit2D()
            fit2d_parameter['polarization_factor'] = self.polarization_factor
        except TypeError:
            fit2d_parameter = None
        try:
            pyFAI_parameter['wavelength'] = self.spectrum_geometry.wavelength
            fit2d_parameter['wavelength'] = self.spectrum_geometry.wavelength
        except RuntimeWarning:
            pyFAI_parameter['wavelength'] = 0

        return pyFAI_parameter, fit2d_parameter

    def calculate_number_of_spectrum_points(self, max_dist_factor=1.5):
        # calculates the number of points for an integrated spectrum, based on the distance of the beam center to the the
        # image corners. Maximum value is determined by the shape of the image.
        fit2d_parameter = self.spectrum_geometry.getFit2D()
        center_x = fit2d_parameter['centerX']
        center_y = fit2d_parameter['centerY']
        width, height = self.img_model.img_data.shape

        if center_x < width and center_x > 0:
            side1 = np.max([abs(width - center_x), center_x])
        else:
            side1 = width

        if center_y < height and center_y > 0:
            side2 = np.max([abs(height - center_y), center_y])
        else:
            side2 = height
        max_dist = np.sqrt(side1 ** 2 + side2 ** 2)
        return int(max_dist * max_dist_factor)

    def load(self, filename):
        """
        Loads a calibration file and and sets all the calibration parameter.
        :param filename: filename for a *.poni calibration file
        """
        self.spectrum_geometry = AzimuthalIntegrator()
        self.spectrum_geometry.load(filename)
        self.orig_pixel1 = self.spectrum_geometry.pixel1
        self.orig_pixel2 = self.spectrum_geometry.pixel2
        self.calibration_name = get_base_name(filename)
        self.filename = filename
        self.is_calibrated = True
        self.create_cake_geometry()
        self.set_supersampling()

    def save(self, filename):
        """
        Saves the current calibration parameters into a a text file. Default extension is
        *.poni
        """
        self.cake_geometry.save(filename)
        self.calibration_name = get_base_name(filename)
        self.filename = filename

    def create_file_header(self):
        return self.cake_geometry.makeHeaders(polarization_factor=self.polarization_factor)

    def set_fit2d(self, fit2d_parameter):
        """
        Reads in a dictionary with fit2d parameters where the fields of the dictionary are:
        'directDist', 'centerX', 'centerY', 'tilt', 'tiltPlanRotation', 'pixelX', pixelY',
        'polarization_factor', 'wavelength'
        """
        self.spectrum_geometry.setFit2D(directDist=fit2d_parameter['directDist'],
                                        centerX=fit2d_parameter['centerX'],
                                        centerY=fit2d_parameter['centerY'],
                                        tilt=fit2d_parameter['tilt'],
                                        tiltPlanRotation=fit2d_parameter['tiltPlanRotation'],
                                        pixelX=fit2d_parameter['pixelX'],
                                        pixelY=fit2d_parameter['pixelY'])
        self.spectrum_geometry.wavelength = fit2d_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = fit2d_parameter['polarization_factor']
        self.orig_pixel1 = fit2d_parameter['pixelX'] * 1e-6
        self.orig_pixel2 = fit2d_parameter['pixelY'] * 1e-6
        self.is_calibrated = True
        self.set_supersampling()

    def set_pyFAI(self, pyFAI_parameter):
        """
        Reads in a dictionary with pyFAI parameters where the fields of dictionary are:
        'dist', 'poni1', 'poni2', 'rot1', 'rot2', 'rot3', 'pixel1', 'pixel2', 'wavelength',
        'polarization_factor'
        """
        self.spectrum_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                        poni1=pyFAI_parameter['poni1'],
                                        poni2=pyFAI_parameter['poni2'],
                                        rot1=pyFAI_parameter['rot1'],
                                        rot2=pyFAI_parameter['rot2'],
                                        rot3=pyFAI_parameter['rot3'],
                                        pixel1=pyFAI_parameter['pixel1'],
                                        pixel2=pyFAI_parameter['pixel2'])
        self.spectrum_geometry.wavelength = pyFAI_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = pyFAI_parameter['polarization_factor']
        self.orig_pixel1 = pyFAI_parameter['pixel1']
        self.orig_pixel2 = pyFAI_parameter['pixel2']
        self.is_calibrated = True
        self.set_supersampling()

    def set_supersampling(self, factor=None):
        """
        Sets the supersampling to a specific factor. Whereby the factor determines in how many artificial pixel the
        original pixel is split. (factor^2)

        factor  n_pixel
        1       1
        2       4
        3       9
        4       16
        """
        if factor is None:
            factor = self.supersampling_factor
        self.spectrum_geometry.pixel1 = self.orig_pixel1 / float(factor)
        self.spectrum_geometry.pixel2 = self.orig_pixel2 / float(factor)

        if factor != self.supersampling_factor:
            self.spectrum_geometry.reset()
            self.supersampling_factor = factor

    def reset_supersampling(self):
        self.spectrum_geometry.pixel1 = self.orig_pixel1
        self.spectrum_geometry.pixel2 = self.orig_pixel2

    def get_two_theta_img(self, x, y):
        """
        Gives the two_theta value for the x,y coordinates on the image. Be aware that this function will be incorrect
        for pixel indices, since it does not correct for center of the pixel.
        :return:
            two theta in radians
        """
        x = np.array([x]) * self.supersampling_factor
        y = np.array([y]) * self.supersampling_factor

        return self.spectrum_geometry.tth(x - 0.5, y - 0.5)[0]  # deletes 0.5 because tth function uses pixel indices

    def get_azi_img(self, x, y):
        """
        Gives chi for position on image.
        :param x:
            x-coordinate in pixel
        :param y:
            y-coordinate in pixel
        :return:
            azimuth in radians
        """
        x *= self.supersampling_factor
        y *= self.supersampling_factor
        return self.spectrum_geometry.chi(x, y)[0]

    def get_two_theta_cake(self, x):
        """
        Gives the two_theta value for the x coordinate in the cake
        :param x:
            x-coordinate on image
        :return:
            two theta in degree
        """
        x -= 0.5
        cake_step = self.cake_tth[1] - self.cake_tth[0]
        tth = self.cake_tth[int(np.floor(x))] + (x  - np.floor(x)) * cake_step
        return tth

    def get_azi_cake(self, x):
        """
        Gives the azimuth value for a cake.
        :param x:
            x-coordinate in pixel
        :return:
            azimuth in degree
        """
        x -= 0.5
        azi_step = self.cake_azi[1] - self.cake_azi[0]
        azi = self.cake_azi[int(np.floor(x))] + (x - np.floor(x)) * azi_step
        return azi

    def get_two_theta_array(self):
        return self.spectrum_geometry._ttha[::self.supersampling_factor, ::self.supersampling_factor]

    def get_pixel_ind(self, tth, azi):
        """
        Calculates pixel index for a specfic two theta and azimutal value.
        :param tth:
            two theta in radians
        :param azi:
            azimuth in radians
        :return:
            tuple of index 1 and 2
        """
        tth_ind = find_contours(self.spectrum_geometry.ttha, tth)
        tth_ind = np.vstack(tth_ind)
        azi_values = self.spectrum_geometry.chi(tth_ind[:, 0], tth_ind[:, 1])
        min_index = np.argmin(np.abs(azi_values - azi))
        return tth_ind[min_index, 0], tth_ind[min_index, 1]

    @property
    def wavelength(self):
        return self.spectrum_geometry.wavelength
Exemple #22
0
    def run(self):
        ai = AzimuthalIntegrator(dist=self.__distance,
                                 poni1=self.__poni1,
                                 poni2=self.__poni2,
                                 rot1=self.__rotation1,
                                 rot2=self.__rotation2,
                                 rot3=self.__rotation3,
                                 detector=self.__detector,
                                 wavelength=self.__wavelength)

        # FIXME Add error model

        method1d = method_registry.Method(1, self.__method.split,
                                          self.__method.algo,
                                          self.__method.impl, None)
        methods = method_registry.IntegrationMethod.select_method(
            method=method1d)
        if len(methods) == 0:
            method1d = method_registry.Method(1, method1d.split, "*", "*",
                                              None)
            _logger.warning("Downgrade 1D integration method to %s", method1d)
        else:
            method1d = methods[0].method

        method2d = method_registry.Method(2, self.__method.split,
                                          self.__method.algo,
                                          self.__method.impl, None)
        methods = method_registry.IntegrationMethod.select_method(
            method=method2d)
        if len(methods) == 0:
            method2d = method_registry.Method(2, method2d.split, "*", "*",
                                              None)
            _logger.warning("Downgrade 2D integration method to %s", method2d)
        else:
            method2d = methods[0].method

        self.__result1d = ai.integrate1d(
            method=method1d,
            data=self.__image,
            npt=self.__nPointsRadial,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        self.__result2d = ai.integrate2d(
            method=method2d,
            data=self.__image,
            npt_rad=self.__nPointsRadial,
            npt_azim=self.__nPointsAzimuthal,
            unit=self.__radialUnit,
            mask=self.__mask,
            polarization_factor=self.__polarizationFactor)

        # Create an image masked where data exists
        self.__resultMask2d = None
        if self.__mask is not None:
            if self.__mask.shape == self.__image.shape:
                maskData = numpy.ones(shape=self.__image.shape,
                                      dtype=numpy.float32)
                maskData[self.__mask == 0] = float("NaN")

                if self.__displayMask:
                    self.__resultMask2d = ai.integrate2d(
                        method=method2d,
                        data=maskData,
                        npt_rad=self.__nPointsRadial,
                        npt_azim=self.__nPointsAzimuthal,
                        unit=self.__radialUnit,
                        polarization_factor=self.__polarizationFactor)
            else:
                _logger.warning(
                    "Inconsistency between image and mask sizes. %s != %s",
                    self.__image.shape, self.__mask.shape)

        try:
            self.__directDist = ai.getFit2D()["directDist"]
        except Exception:
            # The geometry could not fit this param
            _logger.debug("Backtrace", exc_info=True)
            self.__directDist = None

        if self.__calibrant:

            rings = self.__calibrant.get_2th()
            try:
                rings = unitutils.from2ThRad(rings, self.__radialUnit,
                                             self.__wavelength,
                                             self.__directDist)
            except ValueError:
                message = "Convertion to unit %s not supported. Ring marks ignored"
                _logger.warning(message, self.__radialUnit)
                rings = []
            # Filter the rings which are not part of the result
            rings = filter(
                lambda x: self.__result1d.radial[0] <= x <= self.__result1d.
                radial[-1], rings)
            rings = list(rings)
        else:
            rings = []
        self.__ringAngles = rings

        self.__ai = ai
Exemple #23
0
class EigerFrame(wx.Frame):
    """AreaDetector Display """

    img_attrs = ('ArrayData', 'UniqueId_RBV')
    cam_attrs = ('Acquire', 'DetectorState_RBV', 'ArrayCounter',
                 'ArrayCounter_RBV', 'ThresholdEnergy', 'ThresholdEnergy_RBV',
                 'PhotonEnergy', 'PhotonEnergy_RBV', 'NumImages',
                 'NumImages_RBV', 'AcquireTime', 'AcquireTime_RBV',
                 'AcquirePeriod', 'AcquirePeriod_RBV', 'TriggerMode',
                 'TriggerMode_RBV')

    # plugins to enable
    enabled_plugins = ('image1', 'Over1', 'ROI1', 'JPEG1', 'TIFF1')

    def __init__(self, prefix=None, url=None, scale=1.0):
        self.ad_img = None
        self.ad_cam = None
        if prefix is None:
            dlg = SavedParameterDialog(label='Detector Prefix',
                                       title='Connect to Eiger Detector',
                                       configfile='.ad_eigerdisplay.dat')
            res = dlg.GetResponse()
            dlg.Destroy()
            if res.ok:
                prefix = res.value

        self.prefix = prefix
        self.fname = 'Eiger.tif'
        self.esimplon = None
        if url is not None and HAS_SIMPLON:
            self.esimplon = EigerSimplon(url, prefix=prefix + 'cam1:')

        self.lineplotter = None
        self.calib = {}
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.scandb = None
        wx.Frame.__init__(self,
                          None,
                          -1,
                          "Eiger500K Area Detector Display",
                          style=wx.DEFAULT_FRAME_STYLE)

        self.buildMenus()
        self.buildFrame()

        wx.CallAfter(self.connect_escandb)

    def connect_escandb(self):
        if HAS_ESCAN and os.environ.get('ESCAN_CREDENTIALS', None) is not None:
            self.scandb = ScanDB()
            calib_loc = self.scandb.get_info('eiger_calibration')
            cal = self.scandb.get_detectorconfig(calib_loc)
            self.setup_calibration(json.loads(cal.text))

    def buildFrame(self):
        sbar = self.CreateStatusBar(3, wx.CAPTION)  # |wx.THICK_FRAME)
        self.SetStatusWidths([-1, -1, -1])
        sfont = sbar.GetFont()
        sfont.SetWeight(wx.BOLD)
        sfont.SetPointSize(10)
        sbar.SetFont(sfont)

        self.SetStatusText('', 0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)

        pvpanel = PVConfigPanel(panel, self.prefix, display_pvs)

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start', size=wsize)
        stop_btn = wx.Button(panel, label='Stop', size=wsize)
        free_btn = wx.Button(panel, label='Free Run', size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='stop'))
        free_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='free'))

        self.cmap_choice = wx.Choice(panel, size=(80, -1), choices=colormaps)
        self.cmap_choice.SetSelection(0)
        self.cmap_choice.Bind(wx.EVT_CHOICE, self.onColorMap)

        self.cmap_reverse = wx.CheckBox(panel, label='Reverse', size=(60, -1))
        self.cmap_reverse.Bind(wx.EVT_CHECKBOX, self.onColorMap)

        self.show1d_btn = wx.Button(panel,
                                    label='Show 1D Integration',
                                    size=(200, -1))
        self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
        self.show1d_btn.Disable()

        self.imagesize = wx.StaticText(panel,
                                       label='? x ?',
                                       size=(250, 30),
                                       style=txtstyle)

        self.contrast = ContrastChoice(panel, callback=self.set_contrast_level)

        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        irow = 0
        sizer.Add(pvpanel, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn, (irow, 1), (1, 1), labstyle)
        sizer.Add(free_btn, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(lin(300), (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(wx.StaticText(panel, label='Color Map: '), (irow, 0), (1, 1),
                  labstyle)

        sizer.Add(self.cmap_choice, (irow, 1), (1, 1), labstyle)
        sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label, (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(self,
                                      prefix=self.prefix,
                                      rot90=DEFAULT_ROTATION,
                                      size=(400, 750),
                                      writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT | wx.GROW | wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER | wx.GROW | wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)

        try:
            self.SetIcon(wx.Icon(ICONFILE, wx.BITMAP_TYPE_ICO))
        except:
            pass

        wx.CallAfter(self.connect_pvs)

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None,
                            message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            if self.scandb is not None:
                CalibrationDialog(self, ppath).Show()
            else:
                self.setup_calibration(read_poni(ppath))

    def setup_calibration(self, calib):
        """set up calibration from calibration dict"""
        if self.image.rot90 in (1, 3):
            calib['rot3'] = np.pi / 2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()

        h, w = self.image.GetImageSize()
        img.shape = (w, h)
        img = img[3:-3, 1:-1][::-1, :]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img,
                                            2048,
                                            unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q,
                                xi,
                                xlabel=r'$Q (\rm\AA^{-1})$',
                                marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff" % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None,
                            message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())

        root, fname = os.path.split(path)
        epics.caput("%sTIFF1:FileName" % self.prefix, fname)
        epics.caput("%sTIFF1:FileWriteMode" % self.prefix, 0)
        time.sleep(0.05)
        epics.caput("%sTIFF1:WriteFile" % self.prefix, 1)
        time.sleep(0.05)

        print(
            "Saved TIFF File ",
            epics.caget("%sTIFF1:FullFileName_RBV" % self.prefix,
                        as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg = """Eiger Image Display version 0.1
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About Epics Image Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q", "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu, "&Rotate CCW\tCtrl+R",
                 "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu, "Flip Up/Down\tCtrl+T", "Flip Up/Down",
                 self.onFlipV)
        MenuItem(self, omenu, "Flip Left/Right\tCtrl+F", "Flip Left/Right",
                 self.onFlipH)
        MenuItem(self, omenu, "Reset Rotations and Flips", "Reset",
                 self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About Epics AreadDetector Display",
                 self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = DEFAULT_ROTATION
        self.image.flipv = self.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv = not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0 - contrast_level]

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime = 0.25
            self.ad_cam.AcquirePeriod = 0.25
            self.ad_cam.NumImages = 345600
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        if self.prefix.endswith(':'):
            self.prefix = self.prefix[:-1]
        if self.prefix.endswith(':image1'):
            self.prefix = self.prefix[:-7]
        if self.prefix.endswith(':cam1'):
            self.prefix = self.prefix[:-5]

        self.write('Connecting to AD %s' % self.prefix)
        self.ad_img = epics.Device(self.prefix + ':image1:',
                                   delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + ':cam1:',
                                   delim='',
                                   attrs=self.cam_attrs)

        epics.caput("%s:TIFF1:EnableCallbacks" % self.prefix, 1)
        epics.caput("%s:TIFF1:AutoSave" % self.prefix, 0)
        epics.caput("%s:TIFF1:AutoIncrement" % self.prefix, 0)
        epics.caput("%s:TIFF1:FileWriteMode" % self.prefix, 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning:  Camera seems to not be connected!')
                return
        if verbose:
            self.write('Connected to AD %s' % self.prefix)

        self.SetTitle("Epics Image Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel % (sizex, sizey)
        except:
            sizelabel = sizelabel % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV', self.onDetState)
        self.contrast.set_level_str('0.05')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=1)
Exemple #24
0
class ADFrame(wx.Frame):
    """
    AreaDetector Display Frame
    """
    def __init__(self, configfile=None):
        wx.Frame.__init__(self,
                          None,
                          -1,
                          'AreaDetector Viewer',
                          style=wx.DEFAULT_FRAME_STYLE)

        if configfile is None:
            wcard = 'Detector Config Files (*.yaml)|*.yaml|All files (*.*)|*.*'
            configfile = FileOpen(self,
                                  "Read Detector Configuration File",
                                  default_file='det.yaml',
                                  wildcard=wcard)
        if configfile is None:
            sys.exit()

        self.config = read_adconfig(configfile)
        self.prefix = self.config['general']['prefix']
        self.fname = self.config['general']['name']
        self.colormode = self.config['general']['colormode'].lower()
        self.cam_attrs = self.config['cam_attributes']
        self.img_attrs = self.config['img_attributes']
        self.fsaver = self.config['general']['filesaver']

        self.SetTitle(self.config['general']['title'])
        self.scandb = None
        if ScanDB is not None:
            self.scandb = ScanDB()
            if self.scandb.engine is None:  # not connected to running scandb server
                self.scandb = None

        self.calib = None
        self.ad_img = None
        self.ad_cam = None
        self.lineplotter = None
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.thumbnail = None

        self.buildMenus()
        self.buildFrame()

    def buildFrame(self):
        self.SetFont(Font(11))

        sbar = self.CreateStatusBar(3, wx.CAPTION)
        self.SetStatusWidths([-1, -1, -1])
        self.SetStatusText('', 0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)
        pvpanel = PVConfigPanel(panel, self.prefix, self.config['controls'])

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start', size=wsize)
        stop_btn = wx.Button(panel, label='Stop', size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='stop'))

        self.contrast = ContrastControl(panel,
                                        callback=self.set_contrast_level)
        self.imagesize = wx.StaticText(panel,
                                       label='? x ?',
                                       size=(150, 30),
                                       style=txtstyle)

        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        irow = 0
        sizer.Add(pvpanel, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn, (irow, 1), (1, 1), labstyle)

        if self.config['general'].get('show_free_run', False):
            free_btn = wx.Button(panel, label='Free Run', size=wsize)
            free_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='free'))
            irow += 1
            sizer.Add(free_btn, (irow, 0), (1, 2), labstyle)

        irow += 1
        sizer.Add(lin(200, wid=4), (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        if self.colormode.startswith('mono'):
            self.cmap_choice = wx.Choice(panel,
                                         size=(80, -1),
                                         choices=self.config['colormaps'])
            self.cmap_choice.SetSelection(0)
            self.cmap_choice.Bind(wx.EVT_CHOICE, self.onColorMap)
            self.cmap_reverse = wx.CheckBox(panel,
                                            label='Reverse',
                                            size=(60, -1))
            self.cmap_reverse.Bind(wx.EVT_CHECKBOX, self.onColorMap)

            irow += 1
            sizer.Add(wx.StaticText(panel, label='Color Map: '), (irow, 0),
                      (1, 1), labstyle)

            sizer.Add(self.cmap_choice, (irow, 1), (1, 1), labstyle)
            sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label, (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        if self.config['general']['show_1dintegration']:
            self.show1d_btn = wx.Button(panel,
                                        label='Show 1D Integration',
                                        size=(200, -1))
            self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
            self.show1d_btn.Disable()
            irow += 1
            sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        if self.config['general']['show_thumbnail']:
            t_size = self.config['general'].get('thumbnail_size', 100)

            self.thumbnail = ThumbNailImagePanel(panel,
                                                 imgsize=t_size,
                                                 size=(350, 350),
                                                 motion_writer=partial(
                                                     self.write, panel=0))

            label = wx.StaticText(panel,
                                  label='Thumbnail size (pixels): ',
                                  size=(200, -1),
                                  style=txtstyle)

            self.thumbsize = FloatSpin(panel,
                                       value=100,
                                       min_val=10,
                                       increment=5,
                                       action=self.onThumbSize,
                                       size=(150, -1),
                                       style=txtstyle)

            irow += 1
            sizer.Add(label, (irow, 0), (1, 1), labstyle)
            sizer.Add(self.thumbsize, (irow, 1), (1, 1), labstyle)
            irow += 1
            sizer.Add(self.thumbnail, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(
            self,
            prefix=self.prefix,
            rot90=self.config['general']['default_rotation'],
            size=(750, 750),
            writer=partial(self.write, panel=1),
            thumbnail=self.thumbnail,
            motion_writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT | wx.GROW | wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER | wx.GROW | wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)
        iconfile = self.config['general'].get('iconfile', None)
        if iconfile is None or not os.path.exists(iconfile):
            iconfile = DEFAULT_ICONFILE
        try:
            self.SetIcon(wx.Icon(iconfile, wx.BITMAP_TYPE_ICO))
        except:
            pass
        self.connect_pvs()

    def onThumbSize(self, event=None):
        self.thumbnail.imgsize = int(self.thumbsize.GetValue())

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None,
                            message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            self.setup_calibration(ppath)

    def setup_calibration(self, ponifile):
        """set up calibration from PONI file"""
        calib = read_poni(ponifile)
        # if self.image.rot90 in (1, 3):
        #     calib['rot3'] = np.pi/2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()
        else:
            self.write('Warning: PyFAI is not installed')

        if self.scandb is not None:
            _, calname = os.path.split(ponifile)
            self.scandb.set_detectorconfig(calname, json.dumps(calib))
            self.scandb.set_info('xrd_calibration', calname)

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()

        h, w = self.image.GetImageSize()
        img.shape = (w, h)

        # may need to trim outer pixels (int1d_trimx/int1d_trimy in config)
        xstride = 1
        if self.config['general'].get('int1d_flipx', False):
            xstride = -1

        xslice = slice(None, None, xstride)
        trimx = int(self.config['general'].get('int1d_trimx', 0))
        if trimx != 0:
            xslice = slice(trimx * xstride, -trimx * xstride, xstride)

        ystride = 1
        if self.config['general'].get('int1d_flipy', True):
            ystride = -1

        yslice = slice(None, None, ystride)
        trimy = int(self.config['general'].get('int1d_trimy', 0))
        if trimy > 0:
            yslice = slice(trimy * ystride, -trimy * ystride, ystride)

        img = img[yslice, xslice]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img,
                                            2048,
                                            unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q,
                                xi,
                                xlabel=r'$Q (\rm\AA^{-1})$',
                                marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff" % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None,
                            message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())

        root, fname = os.path.split(path)
        epics.caput("%s%sFileName" % self.prefix, self.fsaver, fname)
        epics.caput("%s%sFileWriteMode" % self.prefix, self.fsaver, 0)
        time.sleep(0.05)
        epics.caput("%s%sWriteFile" % self.prefix, self.fsaver, 1)
        time.sleep(0.05)

        file_pv = "%s%sFullFileName_RBV" % (self.prefix, self.prefix)
        print("Saved image File ", epics.caget(file_pv, as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg = """areaDetector Display version 0.2
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About areaDetector Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q", "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu, "&Rotate CCW\tCtrl+R",
                 "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu, "Flip Up/Down\tCtrl+T", "Flip Up/Down",
                 self.onFlipV)
        MenuItem(self, omenu, "Flip Left/Right\tCtrl+F", "Flip Left/Right",
                 self.onFlipH)
        MenuItem(self, omenu, "Reset Rotations and Flips", "Reset",
                 self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About areaDetector Display",
                 self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = 0
        self.image.flipv = self.image.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv = not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0 - contrast_level]
        self.image.Refresh()

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            ftime = self.config['general']['free_run_time']
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime = ftime
            self.ad_cam.AcquirePeriod = ftime
            self.ad_cam.NumImages = int((3 * 86400.) / ftime)
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return
        self.write('Connecting to areaDetector %s' % self.prefix)

        self.ad_img = epics.Device(self.prefix + 'image1:',
                                   delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + 'cam1:',
                                   delim='',
                                   attrs=self.cam_attrs)

        if self.config['general']['use_filesaver']:
            epics.caput("%s%sEnableCallbacks" % (self.prefix, self.fsaver), 1)
            epics.caput("%s%sAutoSave" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sAutoIncrement" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sFileWriteMode" % (self.prefix, self.fsaver), 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning: detector seems to not be connected!')
                return
        if verbose:
            self.write('Connected to detector %s' % self.prefix)

        self.SetTitle("Epics areaDetector Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel % (sizex, sizey)
        except:
            sizelabel = sizelabel % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV', self.onDetState)
        self.contrast.set_level_str('0.01')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=0)
Exemple #25
0
from pyFAI.azimuthalIntegrator import AzimuthalIntegrator
import pyFAI.opencl.peak_finder
shape = 2048, 2048
npeaks = 100
nbins = 512
numpy.random.seed(0)

img = numpy.ones(shape, dtype="float32")
variance = img.copy()
peaks = numpy.random.randint(0, shape[0] * shape[1], size=npeaks)
img.ravel()[peaks] = 4e9
print(img.shape, img.mean(), img.std())
# or a in zip(peaks//shape[1], peaks%shape[1]): print(a)

JF4 = Detector(pixel1=75e-6, pixel2=75e-6, max_shape=shape)
ai = AzimuthalIntegrator(detector=JF4)
ai.setFit2D(100, shape[1] // 2, shape[0] // 2)
csr = ai.setup_CSR(None, nbins, unit="r_m", split="no").lut

r2 = ai.array_from_unit(unit="r_m")
res = ai.integrate1d(img, nbins, unit="r_m")
pf = pyFAI.opencl.peak_finder.OCL_PeakFinder(csr,
                                             img.size,
                                             bin_centers=res[0],
                                             radius=r2,
                                             profile=True)
print(pf.count(img, error_model="azimuthal", cutoff_clip=6), npeaks)
# res = pf(img, variance=variance)
# for a in zip(res[0] // shape[1], res[0] % shape[1], res[1]): print(a)
pf.log_profile(stats=True)
Exemple #26
0
class ADFrame(wx.Frame):
    """
    AreaDetector Display Frame
    """
    def __init__(self, configfile=None):
        wx.Frame.__init__(self, None, -1, 'AreaDetector Viewer',
                          style=wx.DEFAULT_FRAME_STYLE)

        if configfile is None:
            wcard = 'Detector Config Files (*.yaml)|*.yaml|All files (*.*)|*.*'
            configfile = FileOpen(self, "Read Detector Configuration File",
                                  default_file='det.yaml',
                                  wildcard=wcard)
        if configfile is None:
            sys.exit()

        self.config = read_adconfig(configfile)
        self.prefix = self.config['general']['prefix']
        self.fname = self.config['general']['name']
        self.colormode = self.config['general']['colormode'].lower()
        self.cam_attrs = self.config['cam_attributes']
        self.img_attrs = self.config['img_attributes']
        self.fsaver = self.config['general']['filesaver']

        self.SetTitle(self.config['general']['title'])
        self.scandb = None
        if ScanDB is not None:
            self.scandb = ScanDB()
            if self.scandb.engine is None: # not connected to running scandb server
                self.scandb = None

        self.calib = None
        self.ad_img = None
        self.ad_cam = None
        self.lineplotter = None
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.thumbnail = None

        self.buildMenus()
        self.buildFrame()

    def buildFrame(self):
        self.SetFont(Font(11))

        sbar = self.CreateStatusBar(3, wx.CAPTION)
        self.SetStatusWidths([-1, -1, -1])
        self.SetStatusText('',0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)
        pvpanel = PVConfigPanel(panel, self.prefix, self.config['controls'])

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start',    size=wsize)
        stop_btn  = wx.Button(panel, label='Stop',     size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON,  partial(self.onButton, key='stop'))

        self.contrast = ContrastControl(panel, callback=self.set_contrast_level)
        self.imagesize = wx.StaticText(panel, label='? x ?',
                                       size=(150, 30), style=txtstyle)


        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        irow = 0
        sizer.Add(pvpanel,  (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn,  (irow, 1), (1, 1), labstyle)

        if self.config['general'].get('show_free_run', False):
            free_btn  = wx.Button(panel, label='Free Run', size=wsize)
            free_btn.Bind(wx.EVT_BUTTON,  partial(self.onButton, key='free'))
            irow += 1
            sizer.Add(free_btn,  (irow, 0), (1, 2), labstyle)

        irow += 1
        sizer.Add(lin(200, wid=4),  (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        if self.colormode.startswith('mono'):
            self.cmap_choice = wx.Choice(panel, size=(80, -1),
                                         choices=self.config['colormaps'])
            self.cmap_choice.SetSelection(0)
            self.cmap_choice.Bind(wx.EVT_CHOICE,  self.onColorMap)
            self.cmap_reverse = wx.CheckBox(panel, label='Reverse', size=(60, -1))
            self.cmap_reverse.Bind(wx.EVT_CHECKBOX,  self.onColorMap)

            irow += 1
            sizer.Add(wx.StaticText(panel, label='Color Map: '),
                      (irow, 0), (1, 1), labstyle)

            sizer.Add(self.cmap_choice,  (irow, 1), (1, 1), labstyle)
            sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label,  (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        if self.config['general']['show_1dintegration']:
            self.show1d_btn = wx.Button(panel, label='Show 1D Integration',
                                         size=(200, -1))
            self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
            self.show1d_btn.Disable()
            irow += 1
            sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        if self.config['general']['show_thumbnail']:
            t_size=self.config['general'].get('thumbnail_size', 100)

            self.thumbnail = ThumbNailImagePanel(panel, imgsize=t_size,
                                                 size=(350, 350),
                                                 motion_writer=partial(self.write, panel=0))

            label = wx.StaticText(panel, label='Thumbnail size (pixels): ',
                                       size=(200, -1), style=txtstyle)

            self.thumbsize = FloatSpin(panel, value=100, min_val=10, increment=5,
                                       action=self.onThumbSize,
                                       size=(150, -1), style=txtstyle)

            irow += 1
            sizer.Add(label,          (irow, 0), (1, 1), labstyle)
            sizer.Add(self.thumbsize, (irow, 1), (1, 1), labstyle)
            irow += 1
            sizer.Add(self.thumbnail, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(self, prefix=self.prefix,
                                      rot90=self.config['general']['default_rotation'],
                                      size=(750, 750),
                                      writer=partial(self.write, panel=1),
                                      thumbnail=self.thumbnail,
                                      motion_writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT|wx.GROW|wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER|wx.GROW|wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)
        iconfile = self.config['general'].get('iconfile', None)
        if iconfile is None or not os.path.exists(iconfile):
            iconfile = DEFAULT_ICONFILE
        try:
            self.SetIcon(wx.Icon(iconfile, wx.BITMAP_TYPE_ICO))
        except:
            pass
        self.connect_pvs()


    def onThumbSize(self, event=None):
        self.thumbnail.imgsize = int(self.thumbsize.GetValue())

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None, message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            self.setup_calibration(ppath)

    def setup_calibration(self, ponifile):
        """set up calibration from PONI file"""
        calib = read_poni(ponifile)
        # if self.image.rot90 in (1, 3):
        #     calib['rot3'] = np.pi/2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()
        else:
            self.write('Warning: PyFAI is not installed')

        if self.scandb is not None:
            _, calname  = os.path.split(ponifile)
            self.scandb.set_detectorconfig(calname, json.dumps(calib))
            self.scandb.set_info('xrd_calibration', calname)

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()

        h, w = self.image.GetImageSize()
        img.shape = (w, h)

        # may need to trim outer pixels (int1d_trimx/int1d_trimy in config)
        xstride = 1
        if self.config['general'].get('int1d_flipx', False):
            xstride = -1

        xslice = slice(None, None, xstride)
        trimx = int(self.config['general'].get('int1d_trimx', 0))
        if trimx != 0:
            xslice = slice(trimx*xstride, -trimx*xstride, xstride)

        ystride = 1
        if self.config['general'].get('int1d_flipy', True):
            ystride = -1

        yslice = slice(None, None, ystride)
        trimy = int(self.config['general'].get('int1d_trimy', 0))
        if trimy > 0:
            yslice = slice(trimy*ystride, -trimy*ystride, ystride)

        img = img[yslice, xslice]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img, 2048, unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q, xi, xlabel=r'$Q (\rm\AA^{-1})$', marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff"  % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None, message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())


        root, fname = os.path.split(path)
        epics.caput("%s%sFileName" % self.prefix, self.fsaver, fname)
        epics.caput("%s%sFileWriteMode" % self.prefix, self.fsaver, 0)
        time.sleep(0.05)
        epics.caput("%s%sWriteFile" % self.prefix, self.fsaver, 1)
        time.sleep(0.05)

        file_pv = "%s%sFullFileName_RBV" % (self.prefix, self.prefix)
        print("Saved image File ", epics.caget(file_pv,  as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg =  """areaDetector Display version 0.2
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About areaDetector Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q",  "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu,  "&Rotate CCW\tCtrl+R", "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu,  "Flip Up/Down\tCtrl+T", "Flip Up/Down", self.onFlipV)
        MenuItem(self, omenu,  "Flip Left/Right\tCtrl+F", "Flip Left/Right", self.onFlipH)
        MenuItem(self, omenu,  "Reset Rotations and Flips", "Reset", self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About areaDetector Display", self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = 0
        self.image.flipv = self.image.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv= not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0-contrast_level]
        self.image.Refresh()

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            ftime = self.config['general']['free_run_time']
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime   = ftime
            self.ad_cam.AcquirePeriod = ftime
            self.ad_cam.NumImages = int((3*86400.)/ftime)
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return
        self.write('Connecting to areaDetector %s' % self.prefix)

        self.ad_img = epics.Device(self.prefix + 'image1:', delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + 'cam1:', delim='',
                                   attrs=self.cam_attrs)

        if self.config['general']['use_filesaver']:
            epics.caput("%s%sEnableCallbacks" % (self.prefix, self.fsaver), 1)
            epics.caput("%s%sAutoSave" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sAutoIncrement" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sFileWriteMode" % (self.prefix, self.fsaver), 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning: detector seems to not be connected!')
                return
        if verbose:
            self.write('Connected to detector %s' % self.prefix)

        self.SetTitle("Epics areaDetector Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel  % (sizex, sizey)
        except:
            sizelabel = sizelabel  % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV',  self.onDetState)
        self.contrast.set_level_str('0.01')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=0)
class TestMultiGeometry(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self)
        self.data = fabio.open(UtilsTest.getimage("1788/moke.tif")).data
        self.lst_data = [
            self.data[:250, :300], self.data[250:, :300],
            self.data[:250, 300:], self.data[250:, 300:]
        ]
        self.det = Detector(1e-4, 1e-4)
        self.det.max_shape = (500, 600)
        self.sub_det = Detector(1e-4, 1e-4)
        self.sub_det.max_shape = (250, 300)
        self.ai = AzimuthalIntegrator(0.1, 0.03, 0.03, detector=self.det)
        self.range = (0, 23)
        self.ais = [
            AzimuthalIntegrator(0.1, 0.030, 0.03, detector=self.sub_det),
            AzimuthalIntegrator(0.1, 0.005, 0.03, detector=self.sub_det),
            AzimuthalIntegrator(0.1, 0.030, 0.00, detector=self.sub_det),
            AzimuthalIntegrator(0.1, 0.005, 0.00, detector=self.sub_det),
        ]
        self.mg = MultiGeometry(self.ais,
                                radial_range=self.range,
                                unit="2th_deg")
        self.N = 390

    def tearDown(self):
        unittest.TestCase.tearDown(self)
        self.data = None
        self.lst_data = None
        self.det = None
        self.sub_det = None
        self.ai = None
        self.ais = None
        self.mg = None

    def test_integrate1d(self):
        tth_ref, I_ref = self.ai.integrate1d(self.data,
                                             radial_range=self.range,
                                             npt=self.N,
                                             unit="2th_deg",
                                             method="splitpixel")
        obt = self.mg.integrate1d(self.lst_data, self.N)
        tth_obt, I_obt = obt
        self.assertEqual(
            abs(tth_ref - tth_obt).max(), 0, "Bin position is the same")
        # intensity need to be scaled by solid angle 1e-4*1e-4/0.1**2 = 1e-6
        delta = (abs(I_obt * 1e6 - I_ref).max())
        self.assert_(delta < 5e-5, "Intensity is the same delta=%s" % delta)

    def test_integrate2d(self):
        ref = self.ai.integrate2d(self.data,
                                  self.N,
                                  360,
                                  radial_range=self.range,
                                  azimuth_range=(-180, 180),
                                  unit="2th_deg",
                                  method="splitpixel",
                                  all=True)
        obt = self.mg.integrate2d(self.lst_data, self.N, 360, all=True)
        self.assertEqual(
            abs(ref["radial"] - obt["radial"]).max(), 0,
            "Bin position is the same")
        self.assertEqual(
            abs(ref["azimuthal"] - obt["azimuthal"]).max(), 0,
            "Bin position is the same")
        # intensity need to be scaled by solid angle 1e-4*1e-4/0.1**2 = 1e-6
        delta = abs(obt["I"] * 1e6 -
                    ref["I"])[obt["count"] >= 1e-6]  # restrict on valid pixel
        delta_cnt = abs(obt["count"] - ref["count"])
        delta_sum = abs(obt["sum"] * 1e6 - ref["sum"])
        if delta.max() > 0:
            logger.warning(
                "TestMultiGeometry.test_integrate2d gave intensity difference of %s"
                % delta.max())
            if logger.level <= logging.DEBUG:
                from matplotlib import pyplot as plt
                f = plt.figure()
                a1 = f.add_subplot(2, 2, 1)
                a1.imshow(ref["sum"])
                a2 = f.add_subplot(2, 2, 2)
                a2.imshow(obt["sum"])
                a3 = f.add_subplot(2, 2, 3)
                a3.imshow(delta_sum)
                a4 = f.add_subplot(2, 2, 4)
                a4.plot(delta_sum.sum(axis=0))
                f.show()
                raw_input()

        self.assert_(delta_cnt.max() < 0.001,
                     "pixel count is the same delta=%s" % delta_cnt.max())
        self.assert_(delta_sum.max() < 0.03,
                     "pixel sum is the same delta=%s" % delta_sum.max())
        self.assert_(
            delta.max() < 0.004,
            "pixel intensity is the same (for populated pixels) delta=%s" %
            delta.max())
Exemple #28
0
class CalibrationData(object):
    def __init__(self, img_data=None):
        self.img_data = img_data
        self.points = []
        self.points_index = []
        self.spectrum_geometry = AzimuthalIntegrator()
        self.calibrant = Calibrant()
        self.start_values = {'dist': 200e-3,
                             'wavelength': 0.3344e-10,
                             'pixel_width': 79e-6,
                             'pixel_height': 79e-6,
                             'polarization_factor': 0.99}
        self.orig_pixel1 = 79e-6
        self.orig_pixel2 = 79e-6
        self.fit_wavelength = False
        self.fit_distance = True
        self.is_calibrated = False
        self.use_mask = False
        self.filename = ''
        self.calibration_name = 'None'
        self.polarization_factor = 0.99
        self.supersampling_factor = 1
        self._calibrants_working_dir = os.path.dirname(Calibrants.__file__)

        self.cake_img = np.zeros((2048, 2048))
        self.tth = np.linspace(0, 25)
        self.int = np.sin(self.tth)

    def find_peaks_automatic(self, x, y, peak_ind):
        """
        Searches peaks by using the Massif algorithm
        :param x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param y:
            y-coordinate in pixel - should be from original image (not supersampled y-coordinate)
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            array of points found
        """
        massif = Massif(self.img_data._img_data)
        cur_peak_points = massif.find_peaks([x, y])
        if len(cur_peak_points):
            self.points.append(np.array(cur_peak_points))
            self.points_index.append(peak_ind)
        return np.array(cur_peak_points)

    def find_peak(self, x, y, search_size, peak_ind):
        """
        Searches a peak around the x,y position. It just searches for the maximum value in a specific search size.
        :param x:
            x-coordinate in pixel - should be from original image (not supersampled x-coordinate)
        :param y:
            y-coordinate in pixel - should be form original image (not supersampled y-coordinate)
        :param search_size:
            the amount of pixels in all direction in which the algorithm searches for the maximum peak
        :param peak_ind:
            peak/ring index to which the found points will be added
        :return:
            point found (as array)
        """
        left_ind = np.round(x - search_size * 0.5)
        top_ind = np.round(y - search_size * 0.5)
        x_ind, y_ind = np.where(self.img_data._img_data[left_ind:(left_ind + search_size),
                                top_ind:(top_ind + search_size)] == \
                                self.img_data._img_data[left_ind:(left_ind + search_size),
                                top_ind:(top_ind + search_size)].max())
        x_ind = x_ind[0] + left_ind
        y_ind = y_ind[0] + top_ind
        self.points.append(np.array([x_ind, y_ind]))
        self.points_index.append(peak_ind)
        return np.array([np.array((x_ind, y_ind))])

    def clear_peaks(self):
        self.points = []
        self.points_index = []

    def create_cake_geometry(self):
        self.cake_geometry = AzimuthalIntegrator()

        pyFAI_parameter = self.spectrum_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        pyFAI_parameter['wavelength'] = self.spectrum_geometry.wavelength

        self.cake_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                    poni1=pyFAI_parameter['poni1'],
                                    poni2=pyFAI_parameter['poni2'],
                                    rot1=pyFAI_parameter['rot1'],
                                    rot2=pyFAI_parameter['rot2'],
                                    rot3=pyFAI_parameter['rot3'],
                                    pixel1=pyFAI_parameter['pixel1'],
                                    pixel2=pyFAI_parameter['pixel2'])

        self.cake_geometry.wavelength = pyFAI_parameter['wavelength']

    def setup_peak_search_algorithm(self, algorithm, mask=None):
        """
        Initializes the peak search algorithm on the current image
        :param algorithm:
            peak search algorithm used. Possible algorithms are 'Massif' and 'Blob'
        :param mask:
            if a mask is used during the process this is provided here as a 2d array for the image.
        """

        if algorithm == 'Massif':
            self.peak_search_algorithm = Massif(self.img_data._img_data)
        elif algorithm == 'Blob':
            if mask is not None:
                self.peak_search_algorithm = BlobDetection(self.img_data._img_data * mask)
            else:
                self.peak_search_algorithm = BlobDetection(self.img_data._img_data)
            self.peak_search_algorithm.process()
        else:
            return


    def search_peaks_on_ring(self, peak_index, delta_tth=0.1, min_mean_factor=1,
                             upper_limit=55000, mask=None):
        self.reset_supersampling()
        if not self.is_calibrated:
            return

        # transform delta from degree into radians
        delta_tth = delta_tth / 180.0 * np.pi

        # get appropriate two theta value for the ring number
        tth_calibrant_list = self.calibrant.get_2th()
        tth_calibrant = np.float(tth_calibrant_list[peak_index])

        # get the calculated two theta values for the whole image
        if self.spectrum_geometry._ttha is None:
            tth_array = self.spectrum_geometry.twoThetaArray(self.img_data._img_data.shape)
        else:
            tth_array = self.spectrum_geometry._ttha

        # create mask based on two_theta position
        ring_mask = abs(tth_array - tth_calibrant) <= delta_tth

        if mask is not None:
            mask = np.logical_and(ring_mask, np.logical_not(mask))
        else:
            mask = ring_mask

        # calculate the mean and standard deviation of this area
        sub_data = np.array(self.img_data._img_data.ravel()[np.where(mask.ravel())], dtype=np.float64)
        sub_data[np.where(sub_data > upper_limit)] = np.NaN
        mean = np.nanmean(sub_data)
        std = np.nanstd(sub_data)

        # set the threshold into the mask (don't detect very low intensity peaks)
        threshold = min_mean_factor * mean + std
        mask2 = np.logical_and(self.img_data._img_data > threshold, mask)
        mask2[np.where(self.img_data._img_data > upper_limit)] = False
        size2 = mask2.sum(dtype=int)

        keep = int(np.ceil(np.sqrt(size2)))
        try:
            res = self.peak_search_algorithm.peaks_from_area(mask2, Imin=mean - std, keep=keep)
        except IndexError:
            res = []

        # Store the result
        if len(res):
            self.points.append(np.array(res))
            self.points_index.append(peak_index)

        self.set_supersampling()
        self.spectrum_geometry.reset()

    def set_calibrant(self, filename):
        self.calibrant = Calibrant()
        self.calibrant.load_file(filename)
        self.spectrum_geometry.calibrant = self.calibrant

    def set_start_values(self, start_values):
        self.start_values = start_values
        self.polarization_factor = start_values['polarization_factor']

    def calibrate(self):
        self.spectrum_geometry = GeometryRefinement(self.create_point_array(self.points, self.points_index),
                                                    dist=self.start_values['dist'],
                                                    wavelength=self.start_values['wavelength'],
                                                    pixel1=self.start_values['pixel_width'],
                                                    pixel2=self.start_values['pixel_height'],
                                                    calibrant=self.calibrant)
        self.orig_pixel1 = self.start_values['pixel_width']
        self.orig_pixel2 = self.start_values['pixel_height']

        self.refine()
        self.create_cake_geometry()
        self.is_calibrated = True

        self.calibration_name = 'current'
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.spectrum_geometry.reset()

    def refine(self):
        self.reset_supersampling()
        self.spectrum_geometry.data = self.create_point_array(self.points, self.points_index)

        fix = ['wavelength']
        if self.fit_wavelength:
            fix = []
        if not self.fit_distance:
            fix.append('dist')
        if self.fit_wavelength:
            self.spectrum_geometry.refine2()
        self.spectrum_geometry.refine2_wavelength(fix=fix)

        self.create_cake_geometry()
        self.set_supersampling()
        # reset the integrator (not the geometric parameters)
        self.spectrum_geometry.reset()

    def integrate_1d(self, num_points=None, mask=None, polarization_factor=None, filename=None,
                     unit='2th_deg', method='csr'):
        if np.sum(mask) == self.img_data.img_data.shape[0] * self.img_data.img_data.shape[1]:
            # do not perform integration if the image is completely masked...
            return self.tth, self.int

        if self.spectrum_geometry._polarization is not None:
            if self.img_data.img_data.shape != self.spectrum_geometry._polarization.shape:
                # resetting the integrator if the polarization correction matrix has not the correct shape
                self.spectrum_geometry.reset()

        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if num_points is None:
            num_points = self.calculate_number_of_spectrum_points(2)
        self.num_points = num_points

        t1 = time.time()

        if unit is 'd_A':
            try:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_data.img_data, num_points,
                                                                        method=method,
                                                                        unit='2th_deg',
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            except NameError:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_data.img_data, num_points,
                                                                        method=method,
                                                                        unit='2th_deg',
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            self.tth = self.spectrum_geometry.wavelength / (2 * np.sin(self.tth / 360 * np.pi)) * 1e10
            self.int = self.int
        else:
            try:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_data.img_data, num_points,
                                                                        method=method,
                                                                        unit=unit,
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
            except NameError:
                self.tth, self.int = self.spectrum_geometry.integrate1d(self.img_data.img_data, num_points,
                                                                        method='lut',
                                                                        unit=unit,
                                                                        mask=mask,
                                                                        polarization_factor=polarization_factor,
                                                                        filename=filename)
        logger.info('1d integration of {}: {}s.'.format(os.path.basename(self.img_data.filename), time.time() - t1))

        ind = np.where((self.int > 0) & (~np.isnan(self.int)))
        self.tth = self.tth[ind]
        self.int = self.int[ind]
        return self.tth, self.int

    def integrate_2d(self, mask=None, polarization_factor=None, unit='2th_deg', method='csr', dimensions=(2048, 2048)):
        if polarization_factor is None:
            polarization_factor = self.polarization_factor

        if self.cake_geometry._polarization is not None:
            if self.img_data.img_data.shape != self.cake_geometry._polarization.shape:
                # resetting the integrator if the polarization correction matrix has not the same shape as the image
                self.cake_geometry.reset()

        t1 = time.time()

        res = self.cake_geometry.integrate2d(self.img_data._img_data, dimensions[0], dimensions[1], method=method,
                                             mask=mask,
                                             unit=unit, polarization_factor=polarization_factor)
        logger.info('2d integration of {}: {}s.'.format(os.path.basename(self.img_data.filename), time.time() - t1))
        self.cake_img = res[0]
        self.cake_tth = res[1]
        self.cake_azi = res[2]
        return self.cake_img

    def create_point_array(self, points, points_ind):
        res = []
        for i, point_list in enumerate(points):
            if point_list.shape == (2,):
                res.append([point_list[0], point_list[1], points_ind[i]])
            else:
                for point in point_list:
                    res.append([point[0], point[1], points_ind[i]])
        return np.array(res)

    def get_point_array(self):
        return self.create_point_array(self.points, self.points_index)

    def get_calibration_parameter(self):
        pyFAI_parameter = self.cake_geometry.getPyFAI()
        pyFAI_parameter['polarization_factor'] = self.polarization_factor
        try:
            fit2d_parameter = self.cake_geometry.getFit2D()
            fit2d_parameter['polarization_factor'] = self.polarization_factor
        except TypeError:
            fit2d_parameter = None
        try:
            pyFAI_parameter['wavelength'] = self.spectrum_geometry.wavelength
            fit2d_parameter['wavelength'] = self.spectrum_geometry.wavelength
        except RuntimeWarning:
            pyFAI_parameter['wavelength'] = 0

        return pyFAI_parameter, fit2d_parameter

    def calculate_number_of_spectrum_points(self, max_dist_factor=1.5):
        # calculates the number of points for an integrated spectrum, based on the distance of the beam center to the the
        #image corners. Maximum value is determined by the shape of the image.
        fit2d_parameter = self.spectrum_geometry.getFit2D()
        center_x = fit2d_parameter['centerX']
        center_y = fit2d_parameter['centerY']
        width, height = self.img_data.img_data.shape

        if center_x < width and center_x > 0:
            side1 = np.max([abs(width - center_x), center_x])
        else:
            side1 = width

        if center_y < height and center_y > 0:
            side2 = np.max([abs(height - center_y), center_y])
        else:
            side2 = height
        max_dist = np.sqrt(side1 ** 2 + side2 ** 2)
        return int(max_dist * max_dist_factor)

    def load(self, filename):
        self.spectrum_geometry = AzimuthalIntegrator()
        self.spectrum_geometry.load(filename)
        self.orig_pixel1 = self.spectrum_geometry.pixel1
        self.orig_pixel2 = self.spectrum_geometry.pixel2
        self.calibration_name = get_base_name(filename)
        self.filename = filename
        self.is_calibrated = True
        self.create_cake_geometry()
        self.set_supersampling()

    def save(self, filename):
        self.cake_geometry.save(filename)
        self.calibration_name = get_base_name(filename)
        self.filename = filename

    def create_file_header(self):
        return self.cake_geometry.makeHeaders(polarization_factor=self.polarization_factor)

    def set_fit2d(self, fit2d_parameter):
        print fit2d_parameter
        self.spectrum_geometry.setFit2D(directDist=fit2d_parameter['directDist'],
                                        centerX=fit2d_parameter['centerX'],
                                        centerY=fit2d_parameter['centerY'],
                                        tilt=fit2d_parameter['tilt'],
                                        tiltPlanRotation=fit2d_parameter['tiltPlanRotation'],
                                        pixelX=fit2d_parameter['pixelX'],
                                        pixelY=fit2d_parameter['pixelY'])
        self.spectrum_geometry.wavelength = fit2d_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = fit2d_parameter['polarization_factor']
        self.orig_pixel1 = fit2d_parameter['pixelX'] * 1e-6
        self.orig_pixel2 = fit2d_parameter['pixelY'] * 1e-6
        self.is_calibrated = True
        self.set_supersampling()

    def set_pyFAI(self, pyFAI_parameter):
        self.spectrum_geometry.setPyFAI(dist=pyFAI_parameter['dist'],
                                        poni1=pyFAI_parameter['poni1'],
                                        poni2=pyFAI_parameter['poni2'],
                                        rot1=pyFAI_parameter['rot1'],
                                        rot2=pyFAI_parameter['rot2'],
                                        rot3=pyFAI_parameter['rot3'],
                                        pixel1=pyFAI_parameter['pixel1'],
                                        pixel2=pyFAI_parameter['pixel2'])
        self.spectrum_geometry.wavelength = pyFAI_parameter['wavelength']
        self.create_cake_geometry()
        self.polarization_factor = pyFAI_parameter['polarization_factor']
        self.orig_pixel1 = pyFAI_parameter['pixel1']
        self.orig_pixel2 = pyFAI_parameter['pixel2']
        self.is_calibrated = True
        self.set_supersampling()

    def set_supersampling(self, factor=None):
        if factor is None:
            factor = self.supersampling_factor
        self.spectrum_geometry.pixel1 = self.orig_pixel1 / float(factor)
        self.spectrum_geometry.pixel2 = self.orig_pixel2 / float(factor)

        if factor != self.supersampling_factor:
            self.spectrum_geometry.reset()
            self.supersampling_factor = factor

    def reset_supersampling(self):
        self.spectrum_geometry.pixel1 = self.orig_pixel1
        self.spectrum_geometry.pixel2 = self.orig_pixel2

    def get_two_theta_img(self, x, y):
        """
        Gives the two_theta value for the x,y coordinates on the image
        :return:
            two theta in radians
        """
        x = np.array([x]) * self.supersampling_factor
        y = np.array([y]) * self.supersampling_factor

        return self.spectrum_geometry.tth(x, y)[0]

    def get_azi_img(self, x, y):
        """
        Gives chi for position on image.
        :param x:
            x-coordinate in pixel
        :param y:
            y-coordinate in pixel
        :return:
            azimuth in radians
        """
        x *= self.supersampling_factor
        y *= self.supersampling_factor
        return self.spectrum_geometry.chi(x, y)[0]

    def get_two_theta_cake(self, y):
        """
        Gives the two_theta value for the x coordinate in the cake
        :param x:
            y-coordinate on image
        :return:
            two theta in degree
        """
        return self.cake_tth[np.round(y[0])]

    def get_azi_cake(self, x):
        """
        Gives the azimuth value for a cake.
        :param x:
            x-coordinate in pixel
        :return:
            azimuth in degree
        """
        return self.cake_azi[np.round(x[0])]

    def get_two_theta_array(self):
        return self.spectrum_geometry._ttha[::self.supersampling_factor, ::self.supersampling_factor]

    @property
    def wavelength(self):
        return self.spectrum_geometry.wavelength
Exemple #29
0
class XAnoS_Reducer(QWidget):
    """
    This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data
    """
    def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, azimuthalRange=(-180.0,180.0), parent=None):
        """
        poniFile is the calibration file obtained after Q-calibration
        """
        QWidget.__init__(self,parent)
        self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r'))
        if poniFile is not None:
            self.poniFile=poniFile
        else:
            self.poniFile=self.setup_dict['poniFile']
        if maskFile is not None:
            self.maskFile=maskFile
        else:
            self.maskFile=self.setup_dict['maskFile']
        self.dataFile=dataFile
        if darkFile is None:
            self.dark_corrected=False
            self.darkFile=''
        else:
            self.darkFile=darkFile
            self.dark_corrected=True
       
        self.curDir=os.getcwd()
        
        self.extractedBaseFolder=extractedFolder
        self.npt=npt
        self.set_externally=False
        #ai=AIWidget()
        #self.layout.addWidget(ai)
        self.azimuthalRange=azimuthalRange
        self.create_UI()
        if os.path.exists(self.poniFile):
            self.openPoniFile(file=self.poniFile)
        if os.path.exists(self.maskFile):
            self.openMaskFile(file=self.maskFile)   
        self.clientRunning=False     
        
        
    def create_UI(self):
        """
        Creates the widget user interface
        """
        loadUi('UI_Forms/Data_Reduction_Client.ui',self)
        self.poniFileLineEdit.setText(str(self.poniFile))
        self.maskFileLineEdit.setText(str(self.maskFile))
        self.darkFileLineEdit.setText(str(self.darkFile))
        self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
        self.radialPointsLineEdit.setText(str(self.npt))
        self.openDataPushButton.clicked.connect(self.openDataFiles)
        self.reducePushButton.clicked.connect(self.reduce_multiple)
        self.openDarkPushButton.clicked.connect(self.openDarkFile)
        self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None))
        self.calibratePushButton.clicked.connect(self.calibrate)
        self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged)
        self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None))
        self.createMaskPushButton.clicked.connect(self.createMask)
        self.extractedFolderPushButton.clicked.connect(self.openFolder)
        self.extractedFolderLineEdit.textChanged.connect(self.extractedFolderChanged)
        self.polCorrComboBox.currentIndexChanged.connect(self.polarizationChanged)
        self.polarizationChanged()
        self.radialPointsLineEdit.returnPressed.connect(self.nptChanged)
        self.azimuthalRangeLineEdit.returnPressed.connect(self.azimuthalRangeChanged)
        self.azimuthalRangeChanged()
        #self.statusLabel.setStyleSheet("color:rgba(0,1,0,0)")
        self.imageWidget=Image_Widget(zeros((100,100)))
        self.cakedImageWidget=Image_Widget(zeros((100,100)))
        imgNumberLabel=QLabel('Image number')
        self.imgNumberSpinBox=QSpinBox()
        self.imgNumberSpinBox.setSingleStep(1)
        self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1)
        self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2)
        self.imageView=self.imageWidget.imageView.getView()
        self.plotWidget=PlotWidget()
        self.plotWidget.setXLabel('Q, &#8491;<sup>-1</sup>',fontsize=5)
        self.plotWidget.setYLabel('Intensity',fontsize=5)
        self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data')
        self.tabWidget.addTab(self.imageWidget,'Masked 2D-data')
        self.tabWidget.addTab(self.cakedImageWidget,'Reduced Caked Data')
        
        self.serverAddress=self.serverAddressLineEdit.text()
        self.startClientPushButton.clicked.connect(self.startClient)
        self.stopClientPushButton.clicked.connect(self.stopClient)
        self.serverAddressLineEdit.returnPressed.connect(self.serverAddressChanged)
        
        self.startServerPushButton.clicked.connect(self.startServer)
        self.stopServerPushButton.clicked.connect(self.stopServer)
        
    def startServer(self):
        serverAddr=self.serverAddressLineEdit.text()
        dataDir=QFileDialog.getExistingDirectory(self,'Select data folder',options=QFileDialog.ShowDirsOnly)
        self.serverStatusLabel.setText('<font color="Red">Transmitting</font>')
        QApplication.processEvents()
        self.serverThread=QThread()
        self.zeromq_server=ZeroMQ_Server(serverAddr,dataDir)
        self.zeromq_server.moveToThread(self.serverThread)
        self.serverThread.started.connect(self.zeromq_server.loop)
        self.zeromq_server.messageEmitted.connect(self.updateServerMessage)
        self.zeromq_server.folderFinished.connect(self.serverDone)
        QTimer.singleShot(0,self.serverThread.start)

    
    def updateServerMessage(self,mesg):
        #self.serverStatusLabel.setText('<font color="Red">Transmitting</font>')
        self.serverMessageLabel.setText('Server sends: %s'%mesg)
        QApplication.processEvents()
        
    def serverDone(self):
        self.serverStatusLabel.setText('<font color="Green">Idle</font>')
        self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint)
        self.serverThread.quit()
        self.serverThread.wait()
        self.serverThread.deleteLater()
        self.zeromq_server.deleteLater()
        
    def stopServer(self):
        try:
            self.zeromq_server.running=False
            self.serverStatusLabel.setText('<font color="Green">Idle</font>')
            self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint)
            self.serverThread.quit()
            self.serverThread.wait()
            self.serverThread.deleteLater()
            self.zeromq_server.deleteLater()
        except:
            QMessageBox.warning(self,'Server Error','Start the server before stopping it')
        
    def enableClient(self,enable=True):
        self.startClientPushButton.setEnabled(enable)
        self.stopClientPushButton.setEnabled(enable)
        
    def enableServer(self,enable=True):
        self.startServerPushButton.setEnabled(enable)
        self.stopServerPushButton.setEnabled(enable)
        
        
    def startClient(self):
        if self.clientRunning:
            self.stopClient()
        else:
            self.clientFree=True
            self.clientRunning=True
            self.files=[]
            self.listenerThread = QThread()
            addr=self.clientAddressLineEdit.text()
            self.zeromq_listener = ZeroMQ_Listener(addr)
            self.zeromq_listener.moveToThread(self.listenerThread)
            self.listenerThread.started.connect(self.zeromq_listener.loop)
            self.zeromq_listener.messageReceived.connect(self.signal_received)
            QTimer.singleShot(0, self.listenerThread.start)
            QTimer.singleShot(0,self.clientReduce)
            self.clientStatusLabel.setText('<font color="red">Connected</font>')
            
    def stopClient(self):
        try:
            self.clientRunning=False
            self.clientFree=False
            self.zeromq_listener.messageReceived.disconnect()
            self.zeromq_listener.running=False
            self.listenerThread.quit()
            self.listenerThread.wait()
            self.listenerThread.deleteLater()
            self.zeromq_listener.deleteLater()
            self.clientStatusLabel.setText('<font color="green">Idle</font>')
        except:
            QMessageBox.warning(self,'Client Error', 'Please start the client first before closing.',QMessageBox.Ok)
        
        
    def serverAddressChanged(self):
        if self.clientRunning:
            self.startClient()
        
        
    def signal_received(self, message):
        self.clientMessageLabel.setText('Client receives: %s'%message)
        if 'dark.edf' not in message:
            self.files.append(message)
            
            
    def clientReduce(self):
        while self.clientFree:
            QApplication.processEvents()
            if len(self.files)>0:
                message=self.files[0]
                self.dataFiles=[message]
                self.dataFileLineEdit.setText(str(self.dataFiles))
                self.extractedBaseFolder=os.path.dirname(message)
                self.extractedFolder=os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text())
                if not os.path.exists(self.extractedFolder):
                    os.makedirs(self.extractedFolder)
                self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
                self.set_externally=True
                self.reduce_multiple()
                self.set_externally=False
                self.files.pop(0)
        
            
    def closeEvent(self, event):
        if self.clientRunning:
            self.stopClient()
        event.accept()
       
    def polarizationChanged(self):
        if self.polCorrComboBox.currentText()=='Horizontal':
            self.polarization_factor=1
        elif self.polCorrComboBox.currentText()=='Vertical':
            self.polarization_factor=-1
        elif self.polCorrComboBox.currentText()=='Circular':
            self.polarization_factor=0
        else:
            self.polarization_factor=None
            
    def createMask(self):
        """
        Opens a mask-widget to create mask file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0])
        if fname is not None or fname!='':
            img=fb.open(fname).data
            self.maskWidget=MaskWidget(img)
            self.maskWidget.saveMaskPushButton.clicked.disconnect()
            self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask)
            self.maskWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok)
            
    def maskFileChanged(self):
        """
        Changes the mask file
        """
        maskFile=str(self.maskFileLineEdit.text())
        if str(maskFile)=='':
            self.maskFile=None
        elif os.path.exists(maskFile):
            self.maskFile=maskFile
        else:
            self.maskFile=None
            
    def save_mask(self):
        """
        Saves the entire mask combining all the shape ROIs
        """
        fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0])
        name,extn=os.path.splitext(fname)
        if extn=='':
            fname=name+'.msk'
        elif extn!='.msk':
            QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!')
            return
        else:
            tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None)
            tmpfile.save(fname)
            self.maskFile=fname
            self.maskFileLineEdit.setText(self.maskFile)
            
    def calibrate(self):
        """
        Opens a calibartion widget to create calibration file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0])
        if fname is not None:
            img=fb.open(fname).data
            if self.maskFile is not None:
                try:
                    mask=fb.open(self.maskFile).data
                except:
                    QMessageBox.warning(self,'Mask File Error','Cannot open %s.\n No masking will be done.'%self.maskFile)
                    mask=None
            else:
                mask=None
            pixel1=79.0
            pixel2=79.0
            self.calWidget=CalibrationWidget(img,pixel1,pixel2,mask=mask)
            self.calWidget.saveCalibrationPushButton.clicked.disconnect()
            self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration)
            self.calWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok)
            
    def save_calibration(self):
        fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0])
        tfname=os.path.splitext(fname)[0]+'.poni'
        self.calWidget.applyPyFAI()
        self.calWidget.geo.save(tfname)      
        self.poniFile=tfname
        self.poniFileLineEdit.setText(self.poniFile)
        self.openPoniFile(file=self.poniFile)
        
    def openPoniFile(self,file=None):
        """
        Select and imports the calibration file
        """
        if file is None:
            self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0]
            self.poniFileLineEdit.setText(self.poniFile)
        else:
            self.poniFile=file
        if os.path.exists(self.poniFile):
            self.setup_dict['poniFile']=self.poniFile
            json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            fh=open(self.poniFile,'r')
            lines=fh.readlines()
            self.calib_data={}
            for line in lines:
                if line[0]!='#':
                    key,val=line.split(': ')
                    self.calib_data[key]=float(val)
            self.dist=self.calib_data['Distance']
            self.pixel1=self.calib_data['PixelSize1']
            self.pixel2=self.calib_data['PixelSize2']
            self.poni1=self.calib_data['Poni1']
            self.poni2=self.calib_data['Poni2']
            self.rot1=self.calib_data['Rot1']
            self.rot2=self.calib_data['Rot2']
            self.rot3=self.calib_data['Rot3']
            self.wavelength=self.calib_data['Wavelength']
            self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength)
            #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1]
            #self.roi=cake(pos,movable=False)
            #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged)
            
            #self.imageView.addItem(self.roi)
        else:
            QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok)                
        
    def endAngleChanged(self,evt):
        print(evt.pos())
        
        
    def nptChanged(self):
        """
        Changes the number of radial points
        """
        try:
            self.npt=int(self.radialPointsLineEdit.text())
        except:
            QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok)
            
    def azimuthalRangeChanged(self):
        """
        Changes the azimuth angular range
        """
        try:
            self.azimuthalRange=tuple(map(float, self.azimuthalRangeLineEdit.text().split(':')))
        except:
            QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok)
        
    def openDataFile(self):
        """
        Select and imports one data file
        """
        dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0]
        if dataFile!='':
            self.dataFile=dataFile
            self.curDir=os.path.dirname(self.dataFile)
            self.dataFileLineEdit.setText(self.dataFile)
            self.data2d=fb.open(self.dataFile).data
            if self.darkFile is not None:
                self.applyDark()
            if self.maskFile is not None:
                self.applyMask()    
            self.imageWidget.setImage(self.data2d,transpose=True)
            self.tabWidget.setCurrentWidget(self.imageWidget)
            if not self.set_externally:
                self.extractedFolder=os.path.join(self.curDir,self.extractedFolderLineEdit.text())
                if not os.path.exists(self.extractedFolder):
                    os.makedirs(self.extractedFolder)
                    
    def openDataFiles(self):
        """
        Selects and imports multiple data files
        """
        self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0]
        if len(self.dataFiles)!=0:
            self.imgNumberSpinBox.valueChanged.connect(self.imageChanged)
            self.imgNumberSpinBox.setMinimum(0)
            self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1)
            self.dataFileLineEdit.setText(str(self.dataFiles))
            self.curDir=os.path.dirname(self.dataFiles[0])
            self.extractedBaseFolder=self.curDir
            self.extractedFolder=os.path.abspath(os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text()))
            if not os.path.exists(self.extractedFolder):
                os.makedirs(self.extractedFolder)
            self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
            self.imgNumberSpinBox.setValue(0)
            self.imageChanged()
            
    def imageChanged(self):
        self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data
        if self.darkFile is not None:
            self.applyDark()
        if self.maskFile is not None:
            self.applyMask()    
        self.imageWidget.setImage(self.data2d,transpose=True)
            

                  
            
                
    def applyDark(self):
        if not self.dark_corrected and self.darkFile!='':
            self.dark2d=fb.open(self.darkFile).data
            self.data2d=self.data2d-self.dark2d
            self.dark_corrected=True
                
    def applyMask(self):
        self.mask2d=fb.open(self.maskFile).data
        self.data2d=self.data2d*(1+self.mask2d)/2.0
        self.mask_applied=True

    def openDarkFile(self):
        """
        Select and imports the dark file
        """
        self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0]
        if self.darkFile!='':
            self.dark_corrected=False
            self.darkFileLineEdit.setText(self.darkFile)
            if self.dataFile is not None:
                self.data2d=fb.open(self.dataFile).data
                self.applyDark()
        
    
    def openMaskFile(self,file=None):
        """
        Select and imports the Mask file
        """
        if file is None:
            self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0]
        else:
            self.maskFile=file
        if self.maskFile!='':
            self.mask_applied=False
            if os.path.exists(self.maskFile):
                self.curDir=os.path.dirname(self.maskFile)
                self.maskFileLineEdit.setText(self.maskFile)
                self.setup_dict['maskFile']=self.maskFile
                self.setup_dict['poniFile']=self.poniFile
                json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            else:
                self.openMaskFile(file=None)
            if self.dataFile is not None:
                self.applyMask()
        else:
            self.maskFile=None
            self.maskFileLineEdit.clear()
            
            
        
    def openFolder(self):
        """
        Select the folder to save the reduce data
        """
        oldfolder=self.extractedBaseFolder.text()
        folder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir)
        if folder!='':
            self.extractedBaseFolder=folder
            self.extractedBaseFolderLineEdit.setText(folder)
            self.extractedFolder=os.path.join(folder,self.extractedFolderLineEdit.text())
            self.set_externally=True
        else:
            self.extractedBaseFolder=oldfolder
            self.extractedBaseFolderLineEdit.setText(oldfolder)
            self.extractedFolder = os.path.join(oldfolder, self.extractedFolderLineEdit.text())
            self.set_externally = True


    def extractedFolderChanged(self,txt):
        self.extractedFolder=os.path.join(self.extractedBaseFolder,txt)
        self.set_externally=True

        
        
    def reduceData(self):
        """
        Reduces the 2d data to 1d data
        """
        if (self.dataFile is not None) and (os.path.exists(self.dataFile)):
            if (self.poniFile is not None) and (os.path.exists(self.poniFile)):
#                self.statusLabel.setText('Busy')
#                self.progressBar.setRange(0, 0)
                imageData=fb.open(self.dataFile)
                #self.data2d=imageData.data
                #if self.maskFile is not None:
                #    self.applyMask()    
                #self.imageWidget.setImage(self.data2d,transpose=True)
                #self.tabWidget.setCurrentWidget(self.imageWidget)
                
                self.header=imageData.header
                try:
                    self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10)
                except:
                    self.ai.set_wavelength(self.wavelength)
                #print(self.darkFile)
                if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked():
                    self.darkFile=self.dataFile.split('.')[0]+'_dark.edf'
                    dark=fb.open(self.darkFile)
                    self.darkFileLineEdit.setText(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File read from existing dark files")                    
                elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked():
                    dark=fb.open(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File from memory subtracted")                
                else:
                    imageDark=None
                    try:
                        self.header['BSDiode_corr']=float(imageData.header['BSDiode'])
                        self.header['Monitor_corr']=float(imageData.header['Monitor'])
                        self.header['Transmission'] = float(imageData.header['Transmission'])
                    except:
                        self.normComboBox.setCurrentText('None')
                    print("No dark correction done")
                if str(self.normComboBox.currentText())=='BSDiode':
                    norm_factor=self.header['BSDiode_corr']#/self.header['Monitor_corr']#float(self.header[
                    # 'count_time'])
                elif str(self.normComboBox.currentText())=='TransDiode':
                    norm_factor=self.header['Transmission']*self.header['Monitor_corr']
                elif str(self.normComboBox.currentText())=='Monitor':
                    norm_factor=self.header['Monitor_corr']
                elif str(self.normComboBox.currentText())=='Image Sum':
                    norm_factor=sum(imageData.data)
                else:
                    norm_factor=1.0
                    
                if self.maskFile is not None:
                    imageMask=fb.open(self.maskFile).data
                else:
                    imageMask=None
#                QApplication.processEvents()
                #print(self.azimuthalRange)
                self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthalRange,polarization_factor=self.polarization_factor)
                self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data')
                if not self.set_externally:
                    cakedI,qr,phir=self.ai.integrate2d(imageData.data,self.npt,mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,polarization_factor=self.polarization_factor)
                    self.cakedImageWidget.setImage(cakedI,xmin=qr[0],xmax=qr[-1],ymin=phir[0],ymax=phir[-1],transpose=True,xlabel='Q ', ylabel='phi ',unit=['&#8491;<sup>-1</sup>','degree'])
                    self.cakedImageWidget.imageView.view.setAspectLocked(False)
                    try:
                        self.azimuthalRegion.setRegion(self.azimuthalRange)
                    except:
                        self.azimuthalRegion=pg.LinearRegionItem(values=self.azimuthalRange,orientation=pg.LinearRegionItem.Horizontal,movable=True,bounds=[-180,180])
                        self.cakedImageWidget.imageView.getView().addItem(self.azimuthalRegion)
                        self.azimuthalRegion.sigRegionChanged.connect(self.azimuthalRegionChanged)
                self.plotWidget.setTitle(self.dataFile,fontsize=3)
#                self.progressBar.setRange(0,100)
#                self.progressBar.setValue(100)
#                self.statusLabel.setText('Idle')
#                QApplication.processEvents()
                self.saveData()
                #self.tabWidget.setCurrentWidget(self.plotWidget)
            else:
                QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok)
                
        else:
            QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok)
            
    def azimuthalRegionChanged(self):
        minp,maxp=self.azimuthalRegion.getRegion()
        self.azimuthalRangeLineEdit.setText('%.1f:%.1f'%(minp,maxp))
        self.azimuthalRange=[minp,maxp]
        self.set_externally=True
        
        
            
    def reduce_multiple(self):
        """
        Reduce multiple files
        """
        try:
            i=0
            self.progressBar.setRange(0,len(self.dataFiles))
            self.progressBar.setValue(i)
            self.statusLabel.setText('<font color="red">Busy</font>')
            for file in self.dataFiles:
                self.dataFile=file
                QApplication.processEvents()
                self.reduceData()
                i=i+1
                self.progressBar.setValue(i)
                QApplication.processEvents()
            self.statusLabel.setText('<font color="green">Idle</font>')
            self.progressBar.setValue(0)
        except:
            QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok)
        
    def saveData(self):
        """
        saves the extracted data into a file
        """
        if not os.path.exists(self.extractedFolder):
            os.makedirs(self.extractedFolder)
        filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt')
        headers='File extracted on '+time.asctime()+'\n'
        headers='Files used for extraction are:\n'
        headers+='Data file: '+self.dataFile+'\n'
        if self.darkFile is not None:
            headers+='Dark file: '+self.darkFile+'\n'
        else:
            headers+='Dark file: None\n'
        headers+='Poni file: '+self.poniFile+'\n'
        if self.maskFile is not None:
            headers+='mask file: '+self.maskFile+'\n'
        else:
            headers+='mask file: None\n'
        for key in self.header.keys():
            headers+=key+'='+str(self.header[key])+'\n'
        headers+="col_names=['Q (inv Angs)','Int','Int_err']\n"
        headers+='Q (inv Angs)\tInt\tInt_err'
        data=vstack((self.q,self.I,self.Ierr)).T
        savetxt(filename,data,header=headers,comments='#')
Exemple #30
0
class EigerFrame(wx.Frame):
    """AreaDetector Display """

    img_attrs = ('ArrayData', 'UniqueId_RBV')
    cam_attrs = ('Acquire', 'DetectorState_RBV',
                 'ArrayCounter', 'ArrayCounter_RBV',
                 'ThresholdEnergy', 'ThresholdEnergy_RBV',
                 'PhotonEnergy', 'PhotonEnergy_RBV',
                 'NumImages', 'NumImages_RBV',
                 'AcquireTime', 'AcquireTime_RBV',
                 'AcquirePeriod', 'AcquirePeriod_RBV',
                 'TriggerMode', 'TriggerMode_RBV')

    # plugins to enable
    enabled_plugins = ('image1', 'Over1', 'ROI1', 'JPEG1', 'TIFF1')

    def __init__(self, prefix=None, url=None, scale=1.0):
        self.ad_img = None
        self.ad_cam = None
        if prefix is None:
            dlg = SavedParameterDialog(label='Detector Prefix',
                                       title='Connect to Eiger Detector',
                                       configfile='.ad_eigerdisplay.dat')
            res = dlg.GetResponse()
            dlg.Destroy()
            if res.ok:
                prefix = res.value

        self.prefix = prefix
        self.fname = 'Eiger.tif'
        self.esimplon = None
        if url is not None and HAS_SIMPLON:
            self.esimplon = EigerSimplon(url, prefix=prefix+'cam1:')

        self.lineplotter = None
        self.calib = {}
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.scandb = None
        wx.Frame.__init__(self, None, -1, "Eiger500K Area Detector Display",
                          style=wx.DEFAULT_FRAME_STYLE)

        self.buildMenus()
        self.buildFrame()

        wx.CallAfter(self.connect_escandb)

    def connect_escandb(self):
        if HAS_ESCAN and os.environ.get('ESCAN_CREDENTIALS', None) is not None:
            self.scandb = ScanDB()
            calib_loc = self.scandb.get_info('eiger_calibration')
            cal = self.scandb.get_detectorconfig(calib_loc)
            self.setup_calibration(json.loads(cal.text))

    def buildFrame(self):
        sbar = self.CreateStatusBar(3, wx.CAPTION) # |wx.THICK_FRAME)
        self.SetStatusWidths([-1, -1, -1])
        sfont = sbar.GetFont()
        sfont.SetWeight(wx.BOLD)
        sfont.SetPointSize(10)
        sbar.SetFont(sfont)

        self.SetStatusText('',0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)

        pvpanel = PVConfigPanel(panel, self.prefix, display_pvs)

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start',    size=wsize)
        stop_btn  = wx.Button(panel, label='Stop',     size=wsize)
        free_btn  = wx.Button(panel, label='Free Run', size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON,  partial(self.onButton, key='stop'))
        free_btn.Bind(wx.EVT_BUTTON,  partial(self.onButton, key='free'))

        self.cmap_choice = wx.Choice(panel, size=(80, -1),
                                             choices=colormaps)
        self.cmap_choice.SetSelection(0)
        self.cmap_choice.Bind(wx.EVT_CHOICE,  self.onColorMap)

        self.cmap_reverse = wx.CheckBox(panel, label='Reverse', size=(60, -1))
        self.cmap_reverse.Bind(wx.EVT_CHECKBOX,  self.onColorMap)

        self.show1d_btn =  wx.Button(panel, label='Show 1D Integration', size=(200, -1))
        self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
        self.show1d_btn.Disable()

        self.imagesize = wx.StaticText(panel, label='? x ?',
                                       size=(250, 30), style=txtstyle)

        self.contrast = ContrastChoice(panel, callback=self.set_contrast_level)

        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)


        irow = 0
        sizer.Add(pvpanel,  (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn,  (irow, 1), (1, 1), labstyle)
        sizer.Add(free_btn,  (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(lin(300),  (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(wx.StaticText(panel, label='Color Map: '),
                  (irow, 0), (1, 1), labstyle)

        sizer.Add(self.cmap_choice,  (irow, 1), (1, 1), labstyle)
        sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label,  (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(self, prefix=self.prefix,
                                      rot90=DEFAULT_ROTATION,
                                      size=(400, 750),
                                      writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT|wx.GROW|wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER|wx.GROW|wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)

        try:
            self.SetIcon(wx.Icon(ICONFILE, wx.BITMAP_TYPE_ICO))
        except:
            pass

        wx.CallAfter(self.connect_pvs )

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None, message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            if self.scandb is not None:
                CalibrationDialog(self, ppath).Show()
            else:
                self.setup_calibration(read_poni(ppath))

    def setup_calibration(self, calib):
        """set up calibration from calibration dict"""
        if self.image.rot90 in (1, 3):
            calib['rot3'] = np.pi/2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()


        h, w = self.image.GetImageSize()
        img.shape = (w, h)
        img = img[3:-3, 1:-1][::-1, :]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img, 2048, unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q, xi, xlabel=r'$Q (\rm\AA^{-1})$', marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff"  % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None, message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())


        root, fname = os.path.split(path)
        epics.caput("%sTIFF1:FileName" % self.prefix, fname)
        epics.caput("%sTIFF1:FileWriteMode" % self.prefix, 0)
        time.sleep(0.05)
        epics.caput("%sTIFF1:WriteFile" % self.prefix, 1)
        time.sleep(0.05)

        print("Saved TIFF File ",
              epics.caget("%sTIFF1:FullFileName_RBV" % self.prefix, as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg =  """Eiger Image Display version 0.1
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About Epics Image Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q",  "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu,  "&Rotate CCW\tCtrl+R", "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu,  "Flip Up/Down\tCtrl+T", "Flip Up/Down", self.onFlipV)
        MenuItem(self, omenu,  "Flip Left/Right\tCtrl+F", "Flip Left/Right", self.onFlipH)
        MenuItem(self, omenu,  "Reset Rotations and Flips", "Reset", self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About Epics AreadDetector Display", self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = DEFAULT_ROTATION
        self.image.flipv = self.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv= not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0-contrast_level]

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime = 0.25
            self.ad_cam.AcquirePeriod = 0.25
            self.ad_cam.NumImages = 345600
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        if self.prefix.endswith(':'):
            self.prefix = self.prefix[:-1]
        if self.prefix.endswith(':image1'):
            self.prefix = self.prefix[:-7]
        if self.prefix.endswith(':cam1'):
            self.prefix = self.prefix[:-5]

        self.write('Connecting to AD %s' % self.prefix)
        self.ad_img = epics.Device(self.prefix + ':image1:', delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + ':cam1:', delim='',
                                   attrs=self.cam_attrs)

        epics.caput("%s:TIFF1:EnableCallbacks" % self.prefix, 1)
        epics.caput("%s:TIFF1:AutoSave" % self.prefix, 0)
        epics.caput("%s:TIFF1:AutoIncrement" % self.prefix, 0)
        epics.caput("%s:TIFF1:FileWriteMode" % self.prefix, 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning:  Camera seems to not be connected!')
                return
        if verbose:
            self.write('Connected to AD %s' % self.prefix)

        self.SetTitle("Epics Image Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel  % (sizex, sizey)
        except:
            sizelabel = sizelabel  % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV',  self.onDetState)
        self.contrast.set_level_str('0.05')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=1)