Beispiel #1
0
class LoadImage(object):
    '''
    provide methods to filter files and load images 
    '''
    # define configuration properties that are forwarded to self.config
    xdimension = _configPropertyR('xdimension')
    ydimension = _configPropertyR('ydimension')
    opendirectory = _configPropertyR('opendirectory')
    filenames = _configPropertyR('filenames')
    includepattern = _configPropertyR('includepattern')
    excludepattern = _configPropertyR('excludepattern')
    fliphorizontal = _configPropertyR('fliphorizontal')
    flipvertical = _configPropertyR('flipvertical')

    def __init__(self, p):
        self.config = p
        return

    def flipImage(self, pic):
        '''
        flip image if configured in config
        
        :param pic: 2d array, image array
        
        :return: 2d array, flipped image array
        '''
        if self.fliphorizontal:
            pic = np.array(pic[:, ::-1])
        if self.flipvertical:
            pic = np.array(pic[::-1, :])
        return pic

    def loadImage(self, filename):
        '''
        load image file, if failed (for example loading an incomplete file),
        then it will keep trying loading file for 5s
        
        :param filename: str, image file name
        
        :return: 2d ndarray, 2d image array (flipped)
        '''
        if os.path.exists(filename):
            filenamefull = filename
        else:
            filenamefull = os.path.join(self.opendirectory, filename)
        image = np.zeros(10000).reshape(100, 100)
        if os.path.exists(filenamefull):
            i = 0
            while i < 10:
                try:
                    image = openImage(filenamefull)
                    i = 10
                except:
                    i = i + 1
                    time.sleep(0.5)
            image = self.flipImage(image)
            image[image < 0] = 0
        return image

    def genFileList(self, filenames=None, opendir=None, includepattern=None, excludepattern=None, fullpath=False,
                   slicemethod=False,basefile=None, start=None, stop=None, step=None, zeros=None):
        '''
        generate the list of file in opendir according to include/exclude pattern
        
        :param filenames: list of str, list of file name patterns, all files match ANY pattern in this list will be included
        :param opendir: str, the directory to get files
        :param includepattern: list of str, list of wildcard of files that will be loaded, 
            all files match ALL patterns in this list will be included  
        :param excludepattern: list of str, list of wildcard of files that will be blocked,
            any files match ANY patterns in this list will be blocked
        :param fullpath: bool, if true, return the full path of each file
        
        :return: list of str, a list of filenames
        '''
        
        fileset = self.genFileSet(filenames, opendir, includepattern, excludepattern, fullpath, 
                                slicemethod, basefile, start, stop, step, zeros)
        return sorted(list(fileset))

    def genFileSet(self, filenames=None, opendir=None, includepattern=None, excludepattern=None, fullpath=False,
                   slicemethod=False,basefile=None, start=None, stop=None, step=None, zeros=None):
        '''
        generate the list of file in opendir according to include/exclude pattern
        or by specifying a start, stop and slice
        
        :param filenames: list of str, list of file name patterns, all files match ANY pattern in this list will be included
        :param opendir: str, the directory to get files
        :param includepattern: list of str, list of wildcard of files that will be loaded, 
            all files match ALL patterns in this list will be included  
        :param excludepattern: list of str, list of wildcard of files that will be blocked,
            any files match ANY patterns in this list will be blocked
        :param fullpath: bool, if true, return the full path of each file
        :param slicemethod: bool, if true use slice method
            The slicemethod is designed to have similar outcomes to Fit2d's 'File Series' methods.
            This produces a set of files which are, within the start stop bounds, and an integer
            mulitple of step from one another. This assumes that the defining number is at the end of the file.
            Finally, this only handles integer numbers, no more complex numbering schemes.
        :param basefile: str, the filename which is used to build the list of files
        :param start int, number of the first file to load
        :param stop int, number of the last file to load
        :param step int, distance between files in list
        :param zeros int, leading zeros in filenames
        
        :return: set of str, a list of filenames
        '''
        filenames = self.filenames if filenames == None else filenames
        opendir = self.opendirectory if opendir == None else opendir
        includepattern = self.includepattern if includepattern == None else includepattern
        excludepattern = self.excludepattern if excludepattern == None else excludepattern
        # filter the filenames according to include and exclude pattern
        filelist = os.listdir(opendir)
        fileset = set()
        for includep in includepattern:
            fileset |= set(fnmatch.filter(filelist, includep))
        for excludep in excludepattern:
            fileset -= set(fnmatch.filter(filelist, excludep))
        # filter by slicemethod
        if slicemethod:
            intrange=range(start, stop+step, step)
            strrange=[str(x).zfill(5 if zeros is None else zeros) for x in intrange]
            filelist2=[basefile+x+'.tif' for x in strrange]
            filelist3=[]
            for x in filelist2:
                if os.path.isfile(os.path.join(opendir,x)):
                    filelist3.append(x)
            fileset = set(filelist3)
        else:
            # filter the filenames according to filenames
            if len(filenames) > 0:
                fileset1 = set()
                for filename in filenames:
                    fileset1 |= set(fnmatch.filter(fileset, filename))
                fileset = fileset1
            if fullpath:
                filelist = map(lambda x: os.path.abspath(os.path.join(opendir, x)), fileset)
                fileset = set(filelist)
        return fileset
Beispiel #2
0
class Calculate(object):
    '''
    provide methods for integration, variance calculation and distance/Q matrix calculation etc.
    '''
    # define configuration properties that are forwarded to self.config
    xdimension = _configPropertyR('xdimension')
    ydimension = _configPropertyR('ydimension')
    xpixelsize = _configPropertyR('xpixelsize')
    ypixelsize = _configPropertyR('ypixelsize')
    xbeamcenter = _configPropertyR('xbeamcenter')
    ybeamcenter = _configPropertyR('ybeamcenter')
    rotation = _configPropertyR('rotation')
    tilt = _configPropertyR('tilt')
    distance = _configPropertyR('distance')
    wavelength = _configPropertyR('wavelength')
    integrationspace = _configPropertyR('integrationspace')
    qmax = _configPropertyR('qmax')
    qstep = _configPropertyR('qstep')
    tthmax = _configPropertyR('tthmax')
    tthstep = _configPropertyR('tthstep')
    tthmaxd = _configPropertyR('tthmaxd')
    tthstepd = _configPropertyR('tthstepd')
    tthorqstep = _configPropertyR('tthorqstep')
    tthorqmax = _configPropertyR('tthorqmax')
    uncertaintyenable = _configPropertyR('uncertaintyenable')
    sacorrectionenable = _configPropertyR('sacorrectionenable')
    polcorrectionenable = _configPropertyR('polcorrectionenable')
    polcorrectf = _configPropertyR('polcorrectf')

    def __init__(self, p):
        # create parameter proxy, so that parameters can be accessed by self.parametername in read-only mode
        self.config = p
        self.prepareCalculation()
        return

    def prepareCalculation(self):
        '''
        prepare data for calculation
        '''
        self.xydimension = self.xdimension * self.ydimension
        self.xr = (np.arange(self.xdimension, dtype=float) - self.xbeamcenter +
                   0.5) * self.xpixelsize
        self.yr = (np.arange(self.ydimension, dtype=float) - self.ybeamcenter +
                   0.5) * self.ypixelsize
        self.dmatrix = self.genDistanceMatrix()
        self.azimuthmatrix = np.arctan2(self.yr.reshape(self.ydimension, 1),
                                        self.xr.reshape(1, self.xdimension))
        self.genTTHorQMatrix()
        return

    def genTTHorQMatrix(self):
        '''
        generate a twotheta matrix or q matrix which stores the tth or q value
        or each pixel
        '''
        # set tth or q grid
        if self.integrationspace == 'twotheta':
            self.bin_edges = np.r_[0,
                                   np.arange(self.tthstep /
                                             2, self.tthmax, self.tthstep)]
            self.xgrid = np.degrees(self.bin_edges[1:] - self.tthstep / 2)
            self.tthorqmatrix = self.genTTHMatrix()
        elif self.integrationspace == 'qspace':
            self.bin_edges = np.r_[0,
                                   np.arange(self.qstep /
                                             2, self.qmax, self.qstep)]
            self.xgrid = self.bin_edges[1:] - self.qstep / 2
            self.tthorqmatrix = self.genQMatrix()
        return

    def genIntegrationInds(self, mask=None):
        '''
        generate self.bin_number used in integration (number of pixels in on bin)
        
        :param mask: 2D array, mask of image, should have same dimension, 1 for masked pixel
        
        :return: self.bin_number
        '''
        self.maskedmatrix = np.array(self.tthorqmatrix)
        if mask == None:
            mask = np.zeros((self.ydimension, self.xdimension), dtype=bool)
        self.maskedmatrix[mask] = 1000.0

        self.bin_number = np.array(np.histogram(self.maskedmatrix,
                                                self.bin_edges)[0],
                                   dtype=float)
        self.bin_number[self.bin_number <= 0] = 1
        return self.bin_number

    def intensity(self, pic):
        '''
        2D to 1D image integration, intensity of pixels are binned and then take average,
                
        :param pic: 2D array, array of raw counts, corrections hould be already applied
        
        :retrun: 2d array, [tthorq, intensity, unceratinty] or [tthorq, intensity]
        '''

        intensity = self.calculateIntensity(pic)
        if self.uncertaintyenable:
            std = np.sqrt(self.calculateVariance(pic))
            rv = np.vstack([self.xgrid, intensity, std])
        else:
            rv = np.vstack([self.xgrid, intensity])
        return rv

    def calculateIntensity(self, pic):
        '''
        calculate the 1D intensity
         
        :param pic: 2D array, array of raw counts, raw counts should be corrected
        
        :retrun: 1d array, 1D integrated intensity
        '''

        intensity = np.histogram(self.maskedmatrix,
                                 self.bin_edges,
                                 weights=pic)[0]
        return intensity / self.bin_number

    def calculateVariance(self, pic):
        '''
        calculate the 1D intensity
         
        :param pic: 2D array, array of raw counts, corrections hould be already applied
        
        :retrun: 1d array, variance of integrated intensity
        '''
        picvar = self.calculateVarianceLocal(pic)
        variance = np.histogram(self.maskedmatrix,
                                self.bin_edges,
                                weights=picvar)[0]
        return variance / self.bin_number

    def calculateVarianceLocal(self, pic):
        '''
        calculate the variance of raw counts of each pixel are calculated according to their 
        loacl variance.
        
        :param pic: 2d array, 2d image array, corrections hould be already applied
        
        :return: 2d array, variance of each pixel
        '''

        picavg = snf.uniform_filter(pic, 5, mode='wrap')
        pics2 = (pic - picavg)**2
        pvar = snf.uniform_filter(pics2, 5, mode='wrap')

        gain = pvar / pic
        gain[np.isnan(gain)] = 0
        gain[np.isinf(gain)] = 0
        gainmedian = np.median(gain)
        var = pic * gainmedian
        return var

    def genDistanceMatrix(self):
        '''
        Calculate the distance matrix
        
        :return: 2d array, distance between source and each pixel
        '''
        sinr = np.sin(-self.rotation)
        cosr = np.cos(-self.rotation)
        sint = np.sin(self.tilt)
        cost = np.cos(self.tilt)
        sourcexr = -self.distance * sint * cosr
        sourceyr = self.distance * sint * sinr
        sourcezr = self.distance * cost

        dmatrix = np.zeros((self.ydimension, self.xdimension), dtype=float)
        dmatrix += ((self.xr - sourcexr)**2).reshape(1, self.xdimension)
        dmatrix += ((self.yr - sourceyr)**2).reshape(self.ydimension, 1)
        dmatrix += sourcezr**2
        self.dmatrix = np.sqrt(dmatrix)
        return self.dmatrix

    def genTTHMatrix(self):
        '''
        Calculate the diffraction angle matrix 
        
        :return: 2d array, two theta angle (in radians) of each pixel's center
        '''

        sinr = np.sin(-self.rotation)
        cosr = np.cos(-self.rotation)
        sint = np.sin(self.tilt)
        cost = np.cos(self.tilt)
        sourcexr = -self.distance * sint * cosr
        sourceyr = self.distance * sint * sinr
        sourcezr = self.distance * cost

        tthmatrix1 = np.zeros((self.ydimension, self.xdimension), dtype=float)
        tthmatrix1 += ((-self.xr + sourcexr) * sourcexr).reshape(
            1, self.xdimension)
        tthmatrix1 += ((-self.yr + sourceyr) * sourceyr).reshape(
            self.ydimension, 1)
        tthmatrix1 += sourcezr * sourcezr
        tthmatrix = np.arccos(tthmatrix1 / self.dmatrix / self.distance)
        return tthmatrix

    def genQMatrix(self):
        '''
        Calculate the q matrix 
        
        :return: 2d array, q value of each pixel's center
        '''
        sinr = np.sin(-self.rotation)
        cosr = np.cos(-self.rotation)
        sint = np.sin(self.tilt)
        cost = np.cos(self.tilt)
        sourcexr = -self.distance * sint * cosr
        sourceyr = self.distance * sint * sinr
        sourcezr = self.distance * cost

        tthmatrix1 = np.zeros((self.ydimension, self.xdimension), dtype=float)
        tthmatrix1 += ((-self.xr + sourcexr) * sourcexr).reshape(
            1, self.xdimension)
        tthmatrix1 += ((-self.yr + sourceyr) * sourceyr).reshape(
            self.ydimension, 1)
        tthmatrix1 += sourcezr * sourcezr
        tthmatrix = np.arccos(tthmatrix1 / self.dmatrix / self.distance)
        Q = 4 * np.pi * np.sin(tthmatrix / 2.0) / self.wavelength
        return Q

    def genCorrectionMatrix(self):
        '''
        generate correction matrix. multiple the 2D raw counts array by this correction matrix
        to get corrected raw counts. It will calculate solid angle correction or polarization correction.
        
        :return: 2d array, correction matrix to apply on the image
        '''
        rv = self._solidAngleCorrection() * self._polarizationCorrection()
        return rv

    def _solidAngleCorrection(self):
        '''
        generate correction matrix of soild angle correction for 2D flat detector.
         
        :return: 2d array, correction matrix to apply on the image
        '''
        if self.sacorrectionenable:
            sourcezr = self.distance * np.cos(self.tilt)
            correction = (self.dmatrix / sourcezr)
        else:
            correction = np.ones((self.ydimension, self.xdimension))
        return correction

    def _polarizationCorrection(self):
        '''
        generate correction matrix of polarization correction for powder diffraction for 2D flat detector.
        require the self.polcorrectf factor in configuration.

        :return: 2d array, correction matrix to apply on the image
        '''
        if self.polcorrectionenable:
            tthmatrix = self.tthorqmatrix if self.integrationspace == 'twotheta' else self.genTTHMatrix(
            )
            azimuthmatrix = self.azimuthmatrix
            p = 0.5 * (1 + (np.cos(tthmatrix))**2)
            p1 = 0.5 * self.polcorrectf * np.cos(
                2 * azimuthmatrix) * (np.sin(tthmatrix))**2
            p = 1.0 / (p - p1)
        else:
            p = np.ones((self.ydimension, self.xdimension))
        return p
Beispiel #3
0
class LoadImage(object):
    '''
    provide methods to filter files and load images 
    '''
    # define configuration properties that are forwarded to self.config
    xdimension = _configPropertyR('xdimension')
    ydimension = _configPropertyR('ydimension')
    opendirectory = _configPropertyR('opendirectory')
    filenames = _configPropertyR('filenames')
    includepattern = _configPropertyR('includepattern')
    excludepattern = _configPropertyR('excludepattern')
    fliphorizontal = _configPropertyR('fliphorizontal')
    flipvertical = _configPropertyR('flipvertical')

    def __init__(self, p):
        self.config = p
        return

    def flipImage(self, pic):
        '''
        flip image if configured in config

        :param pic: 2d array, image array

        :return: 2d array, flipped image array
        '''
        if self.fliphorizontal:
            pic = np.array(pic[:, ::-1])
        if self.flipvertical:
            pic = np.array(pic[::-1, :])
        return pic

    def loadImage(self, filename):
        '''
        load image file, if failed (for example loading an incomplete file),
        then it will keep trying loading file for 5s

        :param filename: str, image file name

        :return: 2d ndarray, 2d image array (flipped)
        '''
        if os.path.exists(filename):
            filenamefull = filename
        else:
            filenamefull = os.path.join(self.opendirectory, filename)
        image = np.zeros(10000).reshape(100, 100)
        if os.path.exists(filenamefull):
            i = 0
            while i < 10:
                try:
                    if os.path.splitext(filenamefull)[-1] == '.npy':
                        image = np.load(filenamefull)
                    else:
                        image = openImage(filenamefull)
                    i = 10
                except:
                    i = i + 1
                    time.sleep(0.5)
            #image = self.flipImage(image)
            # default flip as gui plot rendering is different from np
            image = np.flipud(image)
            image[image < 0] = 0
        return image

    def genFileList(self,
                    filenames=None,
                    opendir=None,
                    includepattern=None,
                    excludepattern=None,
                    fullpath=False):
        '''
        generate the list of file in opendir according to include/exclude pattern

        :param filenames: list of str, list of file name patterns, all files match ANY pattern in this list will be included
        :param opendir: str, the directory to get files
        :param includepattern: list of str, list of wildcard of files that will be loaded, 
            all files match ALL patterns in this list will be included  
        :param excludepattern: list of str, list of wildcard of files that will be blocked,
            any files match ANY patterns in this list will be blocked
        :param fullpath: bool, if true, return the full path of each file

        :return: list of str, a list of filenames
        '''

        fileset = self.genFileSet(filenames, opendir, includepattern,
                                  excludepattern, fullpath)
        return sorted(list(fileset))

    def genFileSet(self,
                   filenames=None,
                   opendir=None,
                   includepattern=None,
                   excludepattern=None,
                   fullpath=False):
        '''
        generate the list of file in opendir according to include/exclude pattern

        :param filenames: list of str, list of file name patterns, all files match ANY pattern in this list will be included
        :param opendir: str, the directory to get files
        :param includepattern: list of str, list of wildcard of files that will be loaded, 
            all files match ALL patterns in this list will be included  
        :param excludepattern: list of str, list of wildcard of files that will be blocked,
            any files match ANY patterns in this list will be blocked
        :param fullpath: bool, if true, return the full path of each file

        :return: set of str, a list of filenames
        '''
        filenames = self.filenames if filenames == None else filenames
        opendir = self.opendirectory if opendir == None else opendir
        includepattern = self.includepattern if includepattern == None else includepattern
        excludepattern = self.excludepattern if excludepattern == None else excludepattern
        # filter the filenames according to include and exclude pattern
        filelist = os.listdir(opendir)
        fileset = set()
        for includep in includepattern:
            fileset |= set(fnmatch.filter(filelist, includep))
        for excludep in excludepattern:
            fileset -= set(fnmatch.filter(filelist, excludep))
        # filter the filenames according to filenames
        if len(filenames) > 0:
            fileset1 = set()
            for filename in filenames:
                fileset1 |= set(fnmatch.filter(fileset, filename))
            fileset = fileset1
        if fullpath:
            filelist = map(lambda x: os.path.abspath(os.path.join(opendir, x)),
                           fileset)
            fileset = set(filelist)
        return fileset
Beispiel #4
0
class Mask(object):
    '''
    provide methods for mask generation, including:
    
    static mask: tif mask, npy mask
    dymanic mask: masking dark pixels, bright pixels
    
    '''

    xdimension = _configPropertyR('xdimension')
    ydimension = _configPropertyR('ydimension')
    fliphorizontal = _configPropertyR('fliphorizontal')
    flipvertical = _configPropertyR('flipvertical')
    wavelength = _configPropertyR('wavelength')
    maskfile = _configPropertyR('maskfile')
    brightpixelmask = _configPropertyR('brightpixelmask')
    darkpixelmask = _configPropertyR('darkpixelmask')
    cropedges = _configPropertyR('cropedges')
    avgmask = _configPropertyR('avgmask')

    def __init__(self, p, calculate):
        self.config = p
        self.staticmask = np.zeros((self.ydimension, self.xdimension))
        self.dynamicmask = None
        self.calculate = calculate
        return

    def staticMask(self, maskfile=None):
        '''
        create a static mask according existing mask file. This mask remain unchanged for different images
        
        :param maskfile: string, file name of mask, 
            mask file supported: .npy, .tif file, ATTN: mask in .npy form should be already flipped, 
            and 1 (or larger) stands for masked pixels, 0(<0) stands for unmasked pixels
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        maskfile = self.maskfile if maskfile == None else maskfile

        if os.path.exists(maskfile):
            if maskfile.endswith('.npy'):
                rv = np.load(maskfile)
            elif maskfile.endswith('.tif'):
                immask = openImage(maskfile)
                rv = self.flipImage(immask)
        else:
            rv = np.zeros((self.ydimension, self.xdimension))

        self.staticmask = (rv > 0)
        return self.staticmask

    def dynamicMask(self,
                    pic,
                    dymask=None,
                    brightpixelmask=None,
                    darkpixelmask=None,
                    avgmask=None):
        '''
        create a dynamic mask according to image array. This mask changes for different images
        
        :param pic: 2d array, image array to be processed
        :parma dymask: 2d array, mask array used in average mask calculation
        :param brightpixelmask: pixels with much lower intensity compare to adjacent pixels will be masked
        :param darkpixelmask: pixels with much higher intensity compare to adjacent pixels will be masked
        :param avgmask: Mask the pixels too bright or too dark compared to the average intensity at the similar diffraction angle
             
        :return: 2d array of boolean, 1 stands for masked pixel
        '''

        brightpixelmask = self.brightpixelmask if brightpixelmask == None else brightpixelmask
        darkpixelmask = self.darkpixelmask if darkpixelmask == None else darkpixelmask
        avgmask = self.avgmask if avgmask == None else avgmask

        if darkpixelmask or brightpixelmask or avgmask:
            rv = np.zeros((self.ydimension, self.xdimension))
            if darkpixelmask:
                rv += self.darkPixelMask(pic)
            if brightpixelmask:
                rv += self.brightPixelMask(pic)
            if avgmask:
                rv += self.avgMask(pic, dymask=dymask)
            self.dynamicmask = (rv > 0)
        else:
            self.dynamicmask = None
        return self.dynamicmask

    def edgeMask(self, cropedges=None):
        '''
        generate edge mask
        
        :param cropedges: crop the image, maske pixels around the image edge (left, right, 
            top, bottom), must larger than 0, if None, use self.corpedges
        '''
        ce = self.cropedges if cropedges == None else cropedges
        mask = np.ones((self.ydimension, self.xdimension), dtype=bool)
        mask[ce[2]:-ce[3], ce[0]:-ce[1]] = 0
        return mask

    def avgMask(self, image, high=None, low=None, dymask=None, cropedges=None):
        '''
        generate a mask that automatically mask the pixels, whose intensities are 
        too high or too low compare to the pixels which have similar twotheta value
        
        :param image: 2d array, image file (array)
        :param high: float (default: 2.0), int > avgint * high will be masked
        :param low: float (default: 0.5), int < avgint * low will be masked
        :param dymask: 2d bool array, mask array used in calculation, True for masked pixel, 
            if None, then use self.staticmask
        :param cropedges: crop the image, maske pixels around the image edge (left, right, 
            top, bottom), must larger than 0, if None, use self.config.corpedges
        
        :return 2d bool array, True for masked pixel, edgemake included, dymask not included
        '''
        if dymask == None:
            dymask = self.staticmask
        high = self.config.avgmaskhigh if high == None else high
        low = self.config.avgmasklow if low == None else low

        self.calculate.genIntegrationInds(dymask)
        chi = self.calculate.intensity(image)
        index = np.rint(self.calculate.tthorqmatrix /
                        self.config.tthorqstep).astype(int)
        index[index >= len(chi[1]) - 1] = len(chi[1]) - 1
        avgimage = chi[1][index.ravel()].reshape(index.shape)
        mask = np.ones((self.ydimension, self.xdimension), dtype=bool)
        ce = self.cropedges if cropedges == None else cropedges
        mask[ce[2]:-ce[3], ce[0]:-ce[1]] = np.logical_or(
            image[ce[2]:-ce[3], ce[0]:-ce[1]] < avgimage * low,
            image[ce[2]:-ce[3], ce[0]:-ce[1]] > avgimage * high)
        return mask

    def darkPixelMask(self, pic, r=None):
        '''
        pixels with much lower intensity compare to adjacent pixels will be masked
        
        :param pic: 2d array, image array to be processed
        :param r: float, a threshold for masked pixels
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        r = self.config.darkpixelr if r == None else r  # 0.1

        avgpic = np.average(pic)
        ks = np.ones((5, 5))
        ks1 = np.ones((7, 7))
        picb = snf.percentile_filter(pic, 5, 3) < avgpic * r
        picb = snm.binary_dilation(picb, structure=ks)
        picb = snm.binary_erosion(picb, structure=ks1)
        return picb

    def brightPixelMask(self, pic, size=None, r=None):
        '''
        pixels with much higher intensity compare to adjacent pixels will be masked,
        this mask is used when there are some bright spots/pixels whose intensity is higher 
        than its neighbors but not too high. Only use this on a very good powder averaged 
        data. Otherwise it may mask wrong pixels. 
        
        This mask has similar functions as 'selfcorr' function. However, this mask will only 
        consider pixels' local neighbors pixels and tend to mask more pixels. While 'selfcorr' 
        function compare one pixel to other pixels in same bin.
        
        :param pic: 2d array, image array to be processed
        :param size: int, size of local testing area
        :param r: float, a threshold for masked pixels   
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        size = self.config.brightpixelsize if size == None else size  # 5
        r = self.config.brightpixelr if r == None else r  # 1.2

        rank = snf.rank_filter(pic, -size, size)
        ind = snm.binary_dilation(pic > rank * r, np.ones((3, 3)))
        return ind

    def undersample(self, undersamplerate):
        '''
        a special mask used for undesampling image. It will create a mask that
        discard (total number*(1-undersamplerate)) pixels
        :param undersamplerate: float, 0~1, ratio of pixels to keep
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        mask = np.random.rand(self.ydimension,
                              self.xdimension) < undersamplerate
        return mask

    def flipImage(self, pic):
        '''
        flip image if configured in config
        
        :param pic: 2d array, image array
        
        :return: 2d array, flipped image array
        '''
        if self.fliphorizontal:
            pic = pic[:, ::-1]
        if self.flipvertical:
            pic = pic[::-1, :]
        return pic

    def saveMask(self, filename, pic=None, addmask=None):
        '''
        generate a mask according to the addmask and pic. save it to .npy. 1 stands for masked pixel
        the mask has same order as the pic, which means if the pic is flipped, the mask is fliped
        (when pic is loaded though loadimage, it is flipped)
        
        :param filename: str, filename of mask file to be save
        :param pic: 2d array, image array
        :param addmask: list of str, control which mask to generate
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        if not hasattr(self, 'mask'):
            self.normalMask(addmask)
        if (not hasattr(self, 'dynamicmask')) and (pic != None):
            self.dynamicMask(pic, addmask=addmask)
        tmask = self.mask
        if hasattr(self, 'dynamicmask'):
            if self.dynamicmask != None:
                tmask = np.logical_or(
                    self.mask, self.dynamicmask) if pic != None else self.mask
        np.save(filename, tmask)
        return tmask
class SaveResults(object):
    '''
    save results into files 
    '''
    integrationspace = _configPropertyR('integrationspace')
    savedirectory = _configPropertyR('savedirectory')
    gsasoutput = _configPropertyR('gsasoutput')
    filenameplus = _configPropertyR('filenameplus')

    def __init__(self, p):
        self.config = p
        self.prepareCalculation()
        return

    def prepareCalculation(self):
        if not os.path.exists(self.savedirectory):
            os.makedirs(self.savedirectory)
        return

    def getFilePathWithoutExt(self, filename):
        '''
        get the normalized full path of filename with out extension
        
        :param filename: string, could be full path or file name only and with/without ext, only the base part of filename is used.
        
        :return: string, full normalized path of file without extension
        '''
        filebase = os.path.splitext(os.path.split(filename)[1])[0]
        if self.filenameplus != '' and self.filenameplus != None:
            filenamep = '_'.join(
                [filebase, self.filenameplus, self.integrationspace])
        else:
            filenamep = '_'.join([filebase, self.integrationspace])
        filepathwithoutext = os.path.join(self.savedirectory, filenamep)
        return filepathwithoutext

    def save(self, rv):
        '''
        save diffraction intensity in .chi and gsas format(optional)
        
        :param rv: dict, result include integrated diffration intensity
            the rv['chi'] should be a 2d array with shape (2,len of intensity) or (3, len of intensity)
            file name is generated according to orginal file name and savedirectory
        '''
        rv = self.saveChi(rv['chi'], rv['filename'])
        if self.gsasoutput:
            if self.gsasoutput in set(['std', 'esd', 'fxye']):
                rv = [rv, self.saveGSAS(rv['chi'], rv['filename'])]
        return rv

    def saveChi(self, xrd, filename):
        '''
        save diffraction intensity in .chi
        
        :param xrd: 2d array with shape (2,len of intensity) or (3, len of intensity), [tthorq, intensity, (unceratinty)]
        :param filename: str, base file name 
        '''
        filepath = self.getFilePathWithoutExt(filename) + '.chi'
        f = open(filepath, 'wb')
        f.write('#### start data\n')
        f.write(self.config.getHeader(mode='short'))
        np.savetxt(f, xrd.transpose(), fmt='%g')
        f.close()
        return filepath

    def saveGSAS(self, xrd, filename):
        '''
        save diffraction intensity in gsas format
        
        :param xrd: 2d array with shape (2,len of intensity) or (3, len of intensity), [tthorq, intensity, (unceratinty)]
        :param filename: str, base file name
        '''
        filepath = self.getFilePathWithoutExt(filename) + '.gsas'
        f = open(filepath, 'wb')
        f.write(self.config.getHeader(mode='short'))
        f.write('#### start data\n')
        if xrd.shape[0] == 3:
            s = writeGSASStr(
                os.path.splitext(path)[0], self.gsasoutput, xrd[0], xrd[1],
                xrd[2])
        elif xrd.shape[0] == 2:
            s = writeGSASStr(
                os.path.splitext(path)[0], self.gsasoutput, xrd[0], xrd[1])
        f.write(s)
        f.close()
        return filepath
Beispiel #6
0
class Mask(object):
    '''
    provide methods for mask generation, including:
    
    static mask: fit2d (.msk) mask, tif mask, npy mask, masking edge pixels, 
    dymanic mask: masking dead pixels, bright pixels
    
    *fit2d mask if supported through Fabio
    '''

    xdimension = _configPropertyR('xdimension')
    ydimension = _configPropertyR('ydimension')
    fliphorizontal = _configPropertyR('fliphorizontal')
    flipvertical = _configPropertyR('flipvertical')
    maskedges = _configPropertyR('maskedges')
    wavelength = _configPropertyR('wavelength')
    addmask = _configPropertyR('addmask')

    def __init__(self, p):
        self.config = p
        return

    def staticMask(self, addmask=None):
        '''
        create a static mask according existing mask file. This mask remain unchanged for different images
        
        :param addmask: list of string, file name of mask and/or 'edge', 
            mask file supported: .msk, .npy, .tif file, ATTN: mask array should be already flipped, 
            and 1 (or larger) stands for masked pixels, 0(<0) stands for unmasked pixels
            if 'edge' is specified here. it will create a mask that mask the pixel near the edge of detector,
            require self.maskedges 
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        addmask = self.addmask if addmask == None else addmask

        rv = np.zeros((self.ydimension, self.xdimension))
        # fit2d mask
        maskfit2ds = filter(lambda msk: msk.endswith('.msk'), addmask)
        if len(maskfit2ds) > 0:
            for maskfit2d in maskfit2ds:
                if os.path.exists(maskfit2d):
                    # rv += self.flipImage(immask.data)
                    rv += openImage(maskfit2d)
        # .npy mask
        npymasks = filter(lambda msk: msk.endswith('.npy'), addmask)
        if len(npymasks) > 0:
            for npymask in npymasks:
                if os.path.exists(npymask):
                    rv += np.load(npymask)
        # .tif mask
        tifmasks = filter(lambda msk: msk.endswith('.tif'), addmask)
        if len(tifmasks) > 0:
            for tifmask in tifmasks:
                if os.path.exists(tifmask):
                    immask = openImage(tifmask)
                    rv += self.flipImage(immask.data)
        # edge mask
        edgemask = filter(lambda msk: msk.startswith('edge'), addmask)
        if len(edgemask) > 0:
            if np.sum(self.maskedges) != 0:
                rv += self.edgeMask(self.maskedges)

        self.staticmask = (rv > 0)
        return self.staticmask

    def dynamicMask(self, pic, addmask=None):
        '''
        create a dynamic mask according to image array. This mask changes for different images
        
        :param pic: 2d array, image array to be processed
        :param addmask: list of string, ['deadpixel', 'brightpixel']
            deadpixel: pixels with much lower intensity compare to adjacent pixels will be masked
            brightpixel: pixels with much higher intensity compare to adjacent pixels will be masked 
             
        :return: 2d array of boolean, 1 stands for masked pixel
        '''

        addmask = self.addmask if addmask == None else addmask
        rv = np.zeros((self.ydimension, self.xdimension))
        flag = False
        # deadpixel mask
        dpmask = filter(lambda msk: msk.startswith('dead'), addmask)
        if len(dpmask) > 0:
            rv += self.deadPixelMask(pic)
            flag = True
        # bright pixel mask
        bpmask = filter(lambda msk: msk.startswith('bright'), addmask)
        if len(bpmask) > 0:
            rv += self.brightPixelMask(pic)
            flag = True
        # return None if none mask applied
        if flag:
            self.dynamicmask = (rv > 0)
        else:
            self.dynamicmask = None
        return self.dynamicmask

    def deadPixelMask(self, pic):
        '''
        pixels with much lower intensity compare to adjacent pixels will be masked
        
        :param pic: 2d array, image array to be processed
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        avgpic = np.average(pic)
        ks = np.ones((5, 5))
        ks1 = np.ones((7, 7))
        picb = snf.percentile_filter(pic, 5, 3) < avgpic / 10
        picb = snm.binary_dilation(picb, structure=ks)
        picb = snm.binary_erosion(picb, structure=ks1)
        return picb

    def brightPixelMask(self, pic, size=5, r=1.2):
        '''
        pixels with much higher intensity compare to adjacent pixels will be masked,
        this mask is used when there are some bright spots/pixels whose intensity is higher 
        than its neighbors but not too high. Only use this on a very good powder averaged 
        data. Otherwise it may mask wrong pixels. 
        
        This mask has similar functions as 'selfcorr' function. However, this mask will only 
        consider pixels' local neighbors pixels and tend to mask more pixels. While 'selfcorr' 
        function compare one pixel to other pixels in same bin.
        
        :param pic: 2d array, image array to be processed
        :param size: int, size of local area to test if a pixel is a bright pixel
        :param r: float, a threshold for masked pixels   
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        rank = snf.rank_filter(pic, -size, size)
        ind = snm.binary_dilation(pic > rank * r, np.ones((3, 3)))
        return ind

    def edgeMask(self, edges=None):
        '''
        mask the pixels near edge and around corner
        
        :param edges: list of int (length of 5), first 4 are numbers of pixels masked at each edge
            in (left, right, top, bottom), last one is the radius of round cut at the corner
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        edges = self.maskedges if edges == None else edges
        rv = np.zeros((self.ydimension, self.xdimension))
        if edges[0] != 0:
            rv[:, :edges[0]] = 1
        if edges[1] != 0:
            rv[:, -edges[1]:] = 1
        if edges[2] != 0:
            rv[:edges[2], :] = 1
        if edges[3] != 0:
            rv[-edges[3]::, :] = 1

        ra = edges[4]
        ball = np.zeros((ra * 2, ra * 2))
        radi = (np.arange(ra * 2) - ra).reshape((1, ra * 2)) ** 2 + \
                (np.arange(ra * 2) - ra).reshape((ra * 2, 1)) ** 2
        radi = np.sqrt(radi)
        ind = radi > ra
        rv[-edges[3] - ra:-edges[3], edges[0]:edges[0] + ra] = ind[-ra:, :ra]
        rv[-edges[3] - ra:-edges[3], -edges[1] - ra:-edges[1]] = ind[-ra:, -ra:]
        rv[edges[2]: edges[2] + ra, edges[0]:edges[0] + ra] = ind[:ra, :ra]
        rv[edges[2]: edges[2] + ra, -edges[1] - ra:-edges[1]] = ind[:ra, -ra:]
        return rv

    def undersample(self, undersamplerate):
        '''
        a special mask used for undesampling image. It will create a mask that
        discard (total number*(1-undersamplerate)) pixels
        :param undersamplerate: float, 0~1, ratio of pixels to keep
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        mask = np.random.rand(self.ydimension, self.xdimension) < undersamplerate
        return mask

    def flipImage(self, pic):
        '''
        flip image if configured in config
        
        :param pic: 2d array, image array
        
        :return: 2d array, flipped image array
        '''
        if self.fliphorizontal:
            pic = pic[:, ::-1]
        if self.flipvertical:
            pic = pic[::-1, :]
        return pic

    def saveMask(self, filename, pic=None, addmask=None):
        '''
        generate a mask according to the addmask and pic. save it to .npy. 1 stands for masked pixel
        the mask has same order as the pic, which means if the pic is flipped, the mask is fliped
        (when pic is loaded though loadimage, it is flipped)
        
        :param filename: str, filename of mask file to be save
        :param pic: 2d array, image array
        :param addmask: list of str, control which mask to generate
        
        :return: 2d array of boolean, 1 stands for masked pixel
        '''
        if not hasattr(self, 'mask'):
            self.normalMask(addmask)
        if (not hasattr(self, 'dynamicmask')) and (pic != None):
            self.dynamicMask(pic, addmask=addmask)
        tmask = self.mask
        if hasattr(self, 'dynamicmask'):
            if self.dynamicmask != None:
                tmask = np.logical_or(self.mask, self.dynamicmask) if pic != None else self.mask
        np.save(filename, tmask)
        return tmask