Example #1
0
File: mask.py Project: daihui/pypad
    def __init__(self, raw_image, mask=None, filename='my_mask', fmt='pypad'):
        """
        Instantiate an interactive masking session.
        
        Parameters
        ----------
        raw_image : np.ndarray
            A shape (4, 8, 185, 388) array containing a reference image that
            the user will use to guide their masking.
            
        mask : padmask.PadMask
            A PadMask object to modify. If `None` (default), generate a new 
            mask.
            
        filename : str
            The name of the file to generate at the end of the session.
            
        fmt : str
            The file format of `filename` to write.
        """

        self.print_gui_help()

        self.filename = filename
        self.file_fmt = fmt

        if not raw_image.shape == (4, 16, 185, 194):
            raise ValueError("`raw_image` must have shape: (4, 16, 185, 194)")

        if mask == None:
            self.mask = PadMask()
        elif isinstance(mask, PadMask):
            self.mask = mask

        else:
            raise TypeError(
                '`mask` argument must be a pypad.mask.PadMask object')

        # inject a new mask type into our PadMask obj
        m = self.mask._blank_mask()

        if not 'manual' in self.mask._masks.keys():
            self.mask._inject_mask('manual', m)

        # deal with negative values
        if not 'negatives' in self.mask._masks.keys():
            self.mask._inject_mask('negatives', m.copy())
            self.mask._masks['negatives'][raw_image <= 0.0] = 0
            print "Masked: %d negative pixels" % np.sum(
                np.logical_not(self.mask._masks['negatives']))

        # we're going to plot the log of the image, so do that up front
        self.raw_image_4d = raw_image
        self.raw_image = utils.flatten_2x1s(raw_image)

        self.log_image = self.raw_image.copy()
        self.log_image[self.log_image < 0.0] = 0.0
        self.log_image = np.log10(self.log_image + 1.0)

        # populate an array containing the indices of all pixels in the image
        mg = np.meshgrid(np.arange(self.raw_image.shape[0]),
                         np.arange(self.raw_image.shape[1]))
        self.points = np.vstack((mg[0].flatten(), mg[1].flatten())).T

        # create a colormap with masked pixels clearly highlighted
        self.palette = plt.cm.PuOr_r  # reversed purple-orange -- base cm
        self.palette.set_under(color='green')

        # draw the main GUI, which is an image that can be interactively masked
        plt.figure(figsize=(9, 6))
        self.ax = plt.subplot(111)

        self.im = self.ax.imshow(
            (self.log_image * self.mask.mask2d) - 1e-10,
            cmap=self.palette,
            origin='lower',
            interpolation='nearest',
            vmin=1e-10,
            aspect=1,
            extent=[0, self.log_image.shape[0], 0, self.log_image.shape[1]])

        self.lc, = self.ax.plot((0, 0), (0, 0),
                                '-+m',
                                linewidth=1,
                                markersize=8,
                                markeredgewidth=1)
        self.lm, = self.ax.plot((0, 0), (0, 0),
                                '-+m',
                                linewidth=1,
                                markersize=8,
                                markeredgewidth=1)

        self.line_corner = (0, 0)
        self.xy = None
        self.lines_xy = None
        self.single_px = None  # for masking single pixels

        self.colorbar = plt.colorbar(self.im, pad=0.01)
        self.colorbar.set_label(r'$\log_{10}$ Intensity')

        cidb = plt.connect('button_press_event', self.on_click)
        cidk = plt.connect('key_press_event', self.on_keypress)
        cidm = plt.connect('motion_notify_event', self.on_move)

        plt.xlim([0, self.log_image.shape[0]])
        plt.ylim([0, self.log_image.shape[1]])

        self.ax.get_xaxis().set_ticks([])
        self.ax.get_yaxis().set_ticks([])

        # add toggle buttons that allow the user to turn on and off std masks
        # I used to have this in its own nice function, but MPL didn't like
        # that for some reason... there is probably a better way, I just dont
        # know the innerds of MPL enough --TJL

        axcolor = 'lightgoldenrodyellow'

        ax1 = plt.axes([0.04, 0.7, 0.12, 0.08])
        self.b1 = ToggleButton(ax1,
                               'nonbonded',
                               color=axcolor,
                               hovercolor='0.975')
        self.b1.on_turned_on(self.mask.mask_nonbonded)
        self.b1.on_turned_off(self.mask.remove_mask, 'nonbonded')
        self.b1.on_turned_on(self.update_image)
        self.b1.on_turned_off(self.update_image)

        ax2 = plt.axes([0.04, 0.6, 0.12, 0.08])
        self.b2 = ToggleButton(ax2,
                               'row 13',
                               color=axcolor,
                               hovercolor='0.975')
        self.b2.on_turned_on(self.mask.mask_row13)
        self.b2.on_turned_off(self.mask.remove_mask, 'row13')
        self.b2.on_turned_on(self.update_image)
        self.b2.on_turned_off(self.update_image)

        ax3 = plt.axes([0.04, 0.5, 0.12, 0.08])
        self.b3 = ToggleButton(ax3,
                               'borders',
                               color=axcolor,
                               hovercolor='0.975')
        self.b3.on_turned_on(self._set_borderwidth)
        self.mask_border_cid = self.b3.on_turned_on(self.mask.mask_borders)
        self.b3.on_turned_off(self.mask.remove_mask, 'border')
        self.b3.on_turned_on(self.update_image)
        self.b3.on_turned_off(self.update_image)

        ax4 = plt.axes([0.04, 0.4, 0.12, 0.08])
        self.b4 = ToggleButton(ax4,
                               'threshold',
                               color=axcolor,
                               hovercolor='0.975')
        self.b4.on_turned_on(self._set_threshold)
        self.mask_threshold_cid = self.b4.on_turned_on(
            self.mask.mask_threshold, self.raw_image_4d, None, None)
        self.b4.on_turned_off(self.mask.remove_mask, 'threshold')
        self.b4.on_turned_on(self.update_image)
        self.b4.on_turned_off(self.update_image)

        plt.show()

        return
Example #2
0
    def __init__(self, raw_image, mask=None, filename='my_mask', fmt='pypad'):
        """
        Instantiate an interactive masking session.
        
        Parameters
        ----------
        raw_image : np.ndarray
            A shape (4, 8, 185, 388) array containing a reference image that
            the user will use to guide their masking.
            
        mask : padmask.PadMask
            A PadMask object to modify. If `None` (default), generate a new 
            mask.
            
        filename : str
            The name of the file to generate at the end of the session.
            
        fmt : str
            The file format of `filename` to write.
        """
        
        
        self.print_gui_help()
        
        self.filename = filename
        self.file_fmt = fmt
        
        if not raw_image.shape == (4, 16, 185, 194):
            raise ValueError("`raw_image` must have shape: (4, 16, 185, 194)")
            
        if mask == None:
            self.mask = PadMask()            
        elif isinstance(mask, PadMask):
            self.mask = mask
            
        else:
            raise TypeError('`mask` argument must be a pypad.mask.PadMask object')
        
        
        # inject a new mask type into our PadMask obj
        m = self.mask._blank_mask()
        
        if not 'manual' in self.mask._masks.keys():
            self.mask._inject_mask('manual', m)

        # deal with negative values
        if not 'negatives' in self.mask._masks.keys():
            self.mask._inject_mask('negatives', m.copy())
            self.mask._masks['negatives'][raw_image <= 0.0] = 0
            print "Masked: %d negative pixels" % np.sum(np.logical_not(self.mask._masks['negatives']))
        
        
        # we're going to plot the log of the image, so do that up front
        self.raw_image_4d = raw_image
        self.raw_image = utils.flatten_2x1s(raw_image)
        
        self.log_image = self.raw_image.copy()
        self.log_image[self.log_image < 0.0] = 0.0
        self.log_image = np.log10(self.log_image + 1.0)
        
        
        # populate an array containing the indices of all pixels in the image
        mg = np.meshgrid( np.arange(self.raw_image.shape[0]),
                          np.arange(self.raw_image.shape[1]) )
        self.points = np.vstack((mg[0].flatten(), mg[1].flatten())).T
        
        
        # create a colormap with masked pixels clearly highlighted
        self.palette = plt.cm.PuOr_r # reversed purple-orange -- base cm
        self.palette.set_under(color='green')
        
                
        # draw the main GUI, which is an image that can be interactively masked
        plt.figure(figsize=(9,6))
        self.ax = plt.subplot(111)
        
        self.im = self.ax.imshow( (self.log_image * self.mask.mask2d) - 1e-10, cmap=self.palette,
                                  origin='lower', interpolation='nearest', vmin=1e-10, aspect=1,
                                  extent=[0, self.log_image.shape[0], 0, self.log_image.shape[1]] )
        
        self.lc, = self.ax.plot((0,0),(0,0),'-+m', linewidth=1, markersize=8, markeredgewidth=1)
        self.lm, = self.ax.plot((0,0),(0,0),'-+m', linewidth=1, markersize=8, markeredgewidth=1)
        
        self.line_corner = (0,0)
        self.xy = None
        self.lines_xy = None
        self.single_px = None # for masking single pixels
        
        self.colorbar = plt.colorbar(self.im, pad=0.01)
        self.colorbar.set_label(r'$\log_{10}$ Intensity')

        cidb = plt.connect('button_press_event',  self.on_click)
        cidk = plt.connect('key_press_event',     self.on_keypress)
        cidm = plt.connect('motion_notify_event', self.on_move)
        
        plt.xlim([0, self.log_image.shape[0]])
        plt.ylim([0, self.log_image.shape[1]])
        
        self.ax.get_xaxis().set_ticks([])
        self.ax.get_yaxis().set_ticks([])

        
        # add toggle buttons that allow the user to turn on and off std masks
        # I used to have this in its own nice function, but MPL didn't like 
        # that for some reason... there is probably a better way, I just dont
        # know the innerds of MPL enough --TJL
        
        
        axcolor = 'lightgoldenrodyellow'

        ax1 = plt.axes([0.04, 0.7, 0.12, 0.08])                       
        self.b1 = ToggleButton(ax1, 'nonbonded', color=axcolor, hovercolor='0.975')
        self.b1.on_turned_on(self.mask.mask_nonbonded)
        self.b1.on_turned_off(self.mask.remove_mask, 'nonbonded')
        self.b1.on_turned_on(self.update_image)
        self.b1.on_turned_off(self.update_image)
        
        ax2 = plt.axes([0.04, 0.6, 0.12, 0.08])                       
        self.b2 = ToggleButton(ax2, 'row 13', color=axcolor, hovercolor='0.975')
        self.b2.on_turned_on(self.mask.mask_row13)
        self.b2.on_turned_off(self.mask.remove_mask, 'row13')
        self.b2.on_turned_on(self.update_image)
        self.b2.on_turned_off(self.update_image)
        
        ax3 = plt.axes([0.04, 0.5, 0.12, 0.08])                       
        self.b3 = ToggleButton(ax3, 'borders', color=axcolor, hovercolor='0.975')
        self.b3.on_turned_on(self._set_borderwidth)
        self.mask_border_cid = self.b3.on_turned_on(self.mask.mask_borders)
        self.b3.on_turned_off(self.mask.remove_mask, 'border')
        self.b3.on_turned_on(self.update_image)
        self.b3.on_turned_off(self.update_image)
        
        ax4 = plt.axes([0.04, 0.4, 0.12, 0.08])                       
        self.b4 = ToggleButton(ax4, 'threshold', color=axcolor, hovercolor='0.975')
        self.b4.on_turned_on(self._set_threshold)
        self.mask_threshold_cid = self.b4.on_turned_on(self.mask.mask_threshold, self.raw_image_4d, None, None)
        self.b4.on_turned_off(self.mask.remove_mask, 'threshold')
        self.b4.on_turned_on(self.update_image)
        self.b4.on_turned_off(self.update_image)
                           
        plt.show()
        
        return
Example #3
0
File: mask.py Project: daihui/pypad
class MaskGUI(object):
    def __init__(self, raw_image, mask=None, filename='my_mask', fmt='pypad'):
        """
        Instantiate an interactive masking session.
        
        Parameters
        ----------
        raw_image : np.ndarray
            A shape (4, 8, 185, 388) array containing a reference image that
            the user will use to guide their masking.
            
        mask : padmask.PadMask
            A PadMask object to modify. If `None` (default), generate a new 
            mask.
            
        filename : str
            The name of the file to generate at the end of the session.
            
        fmt : str
            The file format of `filename` to write.
        """

        self.print_gui_help()

        self.filename = filename
        self.file_fmt = fmt

        if not raw_image.shape == (4, 16, 185, 194):
            raise ValueError("`raw_image` must have shape: (4, 16, 185, 194)")

        if mask == None:
            self.mask = PadMask()
        elif isinstance(mask, PadMask):
            self.mask = mask

        else:
            raise TypeError(
                '`mask` argument must be a pypad.mask.PadMask object')

        # inject a new mask type into our PadMask obj
        m = self.mask._blank_mask()

        if not 'manual' in self.mask._masks.keys():
            self.mask._inject_mask('manual', m)

        # deal with negative values
        if not 'negatives' in self.mask._masks.keys():
            self.mask._inject_mask('negatives', m.copy())
            self.mask._masks['negatives'][raw_image <= 0.0] = 0
            print "Masked: %d negative pixels" % np.sum(
                np.logical_not(self.mask._masks['negatives']))

        # we're going to plot the log of the image, so do that up front
        self.raw_image_4d = raw_image
        self.raw_image = utils.flatten_2x1s(raw_image)

        self.log_image = self.raw_image.copy()
        self.log_image[self.log_image < 0.0] = 0.0
        self.log_image = np.log10(self.log_image + 1.0)

        # populate an array containing the indices of all pixels in the image
        mg = np.meshgrid(np.arange(self.raw_image.shape[0]),
                         np.arange(self.raw_image.shape[1]))
        self.points = np.vstack((mg[0].flatten(), mg[1].flatten())).T

        # create a colormap with masked pixels clearly highlighted
        self.palette = plt.cm.PuOr_r  # reversed purple-orange -- base cm
        self.palette.set_under(color='green')

        # draw the main GUI, which is an image that can be interactively masked
        plt.figure(figsize=(9, 6))
        self.ax = plt.subplot(111)

        self.im = self.ax.imshow(
            (self.log_image * self.mask.mask2d) - 1e-10,
            cmap=self.palette,
            origin='lower',
            interpolation='nearest',
            vmin=1e-10,
            aspect=1,
            extent=[0, self.log_image.shape[0], 0, self.log_image.shape[1]])

        self.lc, = self.ax.plot((0, 0), (0, 0),
                                '-+m',
                                linewidth=1,
                                markersize=8,
                                markeredgewidth=1)
        self.lm, = self.ax.plot((0, 0), (0, 0),
                                '-+m',
                                linewidth=1,
                                markersize=8,
                                markeredgewidth=1)

        self.line_corner = (0, 0)
        self.xy = None
        self.lines_xy = None
        self.single_px = None  # for masking single pixels

        self.colorbar = plt.colorbar(self.im, pad=0.01)
        self.colorbar.set_label(r'$\log_{10}$ Intensity')

        cidb = plt.connect('button_press_event', self.on_click)
        cidk = plt.connect('key_press_event', self.on_keypress)
        cidm = plt.connect('motion_notify_event', self.on_move)

        plt.xlim([0, self.log_image.shape[0]])
        plt.ylim([0, self.log_image.shape[1]])

        self.ax.get_xaxis().set_ticks([])
        self.ax.get_yaxis().set_ticks([])

        # add toggle buttons that allow the user to turn on and off std masks
        # I used to have this in its own nice function, but MPL didn't like
        # that for some reason... there is probably a better way, I just dont
        # know the innerds of MPL enough --TJL

        axcolor = 'lightgoldenrodyellow'

        ax1 = plt.axes([0.04, 0.7, 0.12, 0.08])
        self.b1 = ToggleButton(ax1,
                               'nonbonded',
                               color=axcolor,
                               hovercolor='0.975')
        self.b1.on_turned_on(self.mask.mask_nonbonded)
        self.b1.on_turned_off(self.mask.remove_mask, 'nonbonded')
        self.b1.on_turned_on(self.update_image)
        self.b1.on_turned_off(self.update_image)

        ax2 = plt.axes([0.04, 0.6, 0.12, 0.08])
        self.b2 = ToggleButton(ax2,
                               'row 13',
                               color=axcolor,
                               hovercolor='0.975')
        self.b2.on_turned_on(self.mask.mask_row13)
        self.b2.on_turned_off(self.mask.remove_mask, 'row13')
        self.b2.on_turned_on(self.update_image)
        self.b2.on_turned_off(self.update_image)

        ax3 = plt.axes([0.04, 0.5, 0.12, 0.08])
        self.b3 = ToggleButton(ax3,
                               'borders',
                               color=axcolor,
                               hovercolor='0.975')
        self.b3.on_turned_on(self._set_borderwidth)
        self.mask_border_cid = self.b3.on_turned_on(self.mask.mask_borders)
        self.b3.on_turned_off(self.mask.remove_mask, 'border')
        self.b3.on_turned_on(self.update_image)
        self.b3.on_turned_off(self.update_image)

        ax4 = plt.axes([0.04, 0.4, 0.12, 0.08])
        self.b4 = ToggleButton(ax4,
                               'threshold',
                               color=axcolor,
                               hovercolor='0.975')
        self.b4.on_turned_on(self._set_threshold)
        self.mask_threshold_cid = self.b4.on_turned_on(
            self.mask.mask_threshold, self.raw_image_4d, None, None)
        self.b4.on_turned_off(self.mask.remove_mask, 'threshold')
        self.b4.on_turned_on(self.update_image)
        self.b4.on_turned_off(self.update_image)

        plt.show()

        return

    def _set_threshold(self):
        print "\n --- Enter threshold values --- "
        self.lower_thld = float(raw_input('Enter lower threshold: '))
        self.upper_thld = float(raw_input('Enter upper threshold: '))
        self.b4.onstate_exargs[self.mask_threshold_cid] = (self.raw_image_4d,
                                                           self.upper_thld,
                                                           self.lower_thld)
        return

    def _set_borderwidth(self):
        print "\n --- Enter the desired border width --- "
        raw_in = raw_input('Size of border (in pixels) [1]: ')
        if raw_in == '':
            self.borderwidth = 1
        else:
            self.borderwidth = int(raw_in)
        self.b3.onstate_exargs[self.mask_border_cid] = (self.borderwidth, )
        return

    def update_image(self):
        self.im.set_data((self.log_image * self.mask.mask2d) - 1e-10)
        return

    def on_click(self, event):

        # for WHATEVER reason, the imshow drawing is stretched incorrectly
        # such that pixel positions for x/y are off by the ratio used below
        # ... this is likely due to me not understanding MPL, hence this hack
        # -- TJL
        ratio = float(self.log_image.shape[0]) / float(self.log_image.shape[1])
        x_coord = event.xdata / ratio
        y_coord = event.ydata * ratio

        # if a button that is *not* the left click is pressed
        if event.inaxes and (event.button is not 1):

            # save the points for masking in pixel coordinates
            if self.xy != None:
                self.xy = np.vstack(
                    (self.xy, np.array([int(x_coord),
                                        int(y_coord)])))
            else:
                self.xy = np.array([int(x_coord), int(y_coord)])

            # save the points for drawing the lines in MPL coordinates
            if self.lines_xy != None:
                self.lines_xy = np.vstack(
                    (self.lines_xy,
                     np.array([int(event.xdata),
                               int(event.ydata)])))
            else:
                self.lines_xy = np.array([int(event.xdata), int(event.ydata)])

            self.lc.set_data(self.lines_xy.T)  # draws lines
            self.line_corner = (int(event.xdata), int(event.ydata))

        # if the left button is pressed
        elif event.inaxes and (event.button is 1):
            self.single_px = (int(x_coord), int(y_coord))
            print "Selected: (%s, %s)" % self.single_px

        return

    def on_keypress(self, event):

        # mask or unmask
        if event.key in ['m', 'u']:

            if self.xy == None:
                print "No area selected, mask not changed."
            else:

                # print "Masking region inside:"
                # print self.xy

                # wrap around to close polygon
                self.xy = np.vstack((self.xy, self.xy[0, :]))
                path = Path(self.xy)
                in_area = path.contains_points(self.points + 0.5)
                inds = self.points[in_area]

                #print self.xy
                #print inds

                # if we're going to mask, mask
                if event.key == 'm':
                    print 'Masking convex area...'
                    x = self._conv_2dinds_to_4d(inds)
                    self.mask._masks['manual'][x[:, 0], x[:, 1], x[:, 2],
                                               x[:, 3]] = 0

                # if we're unmasking, unmask
                elif event.key == 'u':
                    print 'Unmasking convex area...'
                    x = self._conv_2dinds_to_4d(inds)
                    self.mask._masks['manual'][x[:, 0], x[:, 1], x[:, 2],
                                               x[:, 3]] = 1

                # draw and reset
                self.update_image()
                self._reset()
                plt.draw()

        # reset all masks
        elif event.key == 'r':
            print 'Unmasking all'
            self.mask._masks['manual'] = self.mask._blank_mask()
            self.update_image()
            self._reset()
            #self.im.autoscale()
            plt.draw()

        # toggle selection
        elif event.key == 't':

            if self.single_px != None:
                x = self._conv_2dinds_to_4d(np.array(self.single_px)[None, :])
                x = x.flatten()
                if self.mask.mask[x[0], x[1], x[2], x[3]] == 0:
                    print "Unmasking single pixel:", self.single_px
                    self.mask._masks['manual'][x[0], x[1], x[2], x[3]] = 1
                else:
                    print "Masking single pixel:", self.single_px
                    self.mask._masks['manual'][x[0], x[1], x[2], x[3]] = 0

            else:
                print "No single pixel selected to toggle. Click a pixel and"
                print "    press `t` to toggle the mask on that pixel."

            self.update_image()
            self._reset()
            #self.im.autoscale()
            plt.draw()

        # clear mouse selection
        elif event.key == 'x':
            print "Reset selections"
            self._reset()

        # save and exit
        elif event.key == 'w':
            self.mask.save(self.filename, fmt=self.file_fmt)
            plt.close()
            return

        # exit w/o saving
        elif event.key == 'q':
            print 'Exiting without saving...'
            plt.close()
            return

        # else:
        #     print "Could not understand key: %s" % event.key
        #     print "Valid options: {m, u, r, k, q}"

        return

    def on_move(self, event):
        if not event.inaxes: return
        xm, ym = int(event.xdata), int(event.ydata)

        # update the line positions
        if self.line_corner != (0, 0):
            self.lm.set_data((self.line_corner[0], xm),
                             (self.line_corner[1], ym))
            plt.draw()

        return

    def _reset(self):
        self.single_pixel = None
        self.xy = None
        self.lines_xy = None
        self.lc.set_data([], [])
        self.lm.set_data([], [])
        self.line_corner = (0, 0)
        return

    def _conv_2dinds_to_4d(self, inds):
        """
        Convert indices in a 2d Cheetah array to (4,16,185,194).
        
        Parameters
        ----------
        inds : np.ndarray, int
            An N x 2 array, where the first column indexes x on the 2d image,
            and the second column indexes y.
            
        Returns
        -------
        inds_4d : np.ndarray, int
            An N x 4 array, with each column indexing quads/asics/y/x,
        """

        inds_4d = np.zeros((inds.shape[0], 4), dtype=np.int32)

        # index each asic, in the correct order
        of64 = (inds[:, 1] / 185) * 2 + (inds[:, 0] / 388) * 16 + (inds[:, 0] /
                                                                   194) % 2
        assert np.all(of64 < 64)

        # quads / asics
        # print 'masking in ASICs:', inds, of64
        inds_4d[:, 0] = of64 / 16
        inds_4d[:, 1] = of64 % 16

        # x / y : note the image is displayed transposed
        inds_4d[:, 2] = (inds[:, 1] % 185)
        inds_4d[:, 3] = (inds[:, 0] % 194)

        return inds_4d

    def print_gui_help(self):

        print
        print
        print "   --- WELCOME TO PYPAD's INTERACTIVE MASKING ENVIRONMENT --- "
        print
        print " Green pixels are masked."
        print
        print " Keystrokes"
        print " ----------"
        print " m : mask               u : unmask            r : reset "
        print " x : clear selection    w : save & exit       t : toggle pixel"
        print " q : exit w/o saving"
        print
        print " Mouse"
        print " -----"
        print " Right click on three or more points to draw a polygon around a"
        print " set of pixels. Then press `m` or `u` to mask or unmask that area."
        print
        print " You can also mask/unmask single pixels by clicking on them with"
        print " the mouse and pressing `t` to toggle the mask state."
        print
        print " Toggle Buttons (left)"
        print " ---------------------"
        print " nonbonded : Mask nonbonded pixels, and their nearest neighbours."
        print "             These pixels aren't even connected to the detector."
        print
        print " row 13    : In some old experiments, row 13 on one ASIC was "
        print "             busted -- mask that (will be clear if this is needed)"
        print
        print " threshold : You will be prompted for an upper and lower limit --"
        print "             pixels outside that range are masked. Units are ADUs"
        print "             and you can set only a lower/upper limit by passing"
        print "             'None' for one option."
        print
        print " borders   : Mask the borders of each ASIC. These often give"
        print "             anomoulous responses. Recommended to mask one pixel"
        print "             borders at least."
        print ""
        print "                          ----- // -----"
        print
Example #4
0
class MaskGUI(object):

    def __init__(self, raw_image, mask=None, filename='my_mask', fmt='pypad'):
        """
        Instantiate an interactive masking session.
        
        Parameters
        ----------
        raw_image : np.ndarray
            A shape (4, 8, 185, 388) array containing a reference image that
            the user will use to guide their masking.
            
        mask : padmask.PadMask
            A PadMask object to modify. If `None` (default), generate a new 
            mask.
            
        filename : str
            The name of the file to generate at the end of the session.
            
        fmt : str
            The file format of `filename` to write.
        """
        
        
        self.print_gui_help()
        
        self.filename = filename
        self.file_fmt = fmt
        
        if not raw_image.shape == (4, 16, 185, 194):
            raise ValueError("`raw_image` must have shape: (4, 16, 185, 194)")
            
        if mask == None:
            self.mask = PadMask()            
        elif isinstance(mask, PadMask):
            self.mask = mask
            
        else:
            raise TypeError('`mask` argument must be a pypad.mask.PadMask object')
        
        
        # inject a new mask type into our PadMask obj
        m = self.mask._blank_mask()
        
        if not 'manual' in self.mask._masks.keys():
            self.mask._inject_mask('manual', m)

        # deal with negative values
        if not 'negatives' in self.mask._masks.keys():
            self.mask._inject_mask('negatives', m.copy())
            self.mask._masks['negatives'][raw_image <= 0.0] = 0
            print "Masked: %d negative pixels" % np.sum(np.logical_not(self.mask._masks['negatives']))
        
        
        # we're going to plot the log of the image, so do that up front
        self.raw_image_4d = raw_image
        self.raw_image = utils.flatten_2x1s(raw_image)
        
        self.log_image = self.raw_image.copy()
        self.log_image[self.log_image < 0.0] = 0.0
        self.log_image = np.log10(self.log_image + 1.0)
        
        
        # populate an array containing the indices of all pixels in the image
        mg = np.meshgrid( np.arange(self.raw_image.shape[0]),
                          np.arange(self.raw_image.shape[1]) )
        self.points = np.vstack((mg[0].flatten(), mg[1].flatten())).T
        
        
        # create a colormap with masked pixels clearly highlighted
        self.palette = plt.cm.PuOr_r # reversed purple-orange -- base cm
        self.palette.set_under(color='green')
        
                
        # draw the main GUI, which is an image that can be interactively masked
        plt.figure(figsize=(9,6))
        self.ax = plt.subplot(111)
        
        self.im = self.ax.imshow( (self.log_image * self.mask.mask2d) - 1e-10, cmap=self.palette,
                                  origin='lower', interpolation='nearest', vmin=1e-10, aspect=1,
                                  extent=[0, self.log_image.shape[0], 0, self.log_image.shape[1]] )
        
        self.lc, = self.ax.plot((0,0),(0,0),'-+m', linewidth=1, markersize=8, markeredgewidth=1)
        self.lm, = self.ax.plot((0,0),(0,0),'-+m', linewidth=1, markersize=8, markeredgewidth=1)
        
        self.line_corner = (0,0)
        self.xy = None
        self.lines_xy = None
        self.single_px = None # for masking single pixels
        
        self.colorbar = plt.colorbar(self.im, pad=0.01)
        self.colorbar.set_label(r'$\log_{10}$ Intensity')

        cidb = plt.connect('button_press_event',  self.on_click)
        cidk = plt.connect('key_press_event',     self.on_keypress)
        cidm = plt.connect('motion_notify_event', self.on_move)
        
        plt.xlim([0, self.log_image.shape[0]])
        plt.ylim([0, self.log_image.shape[1]])
        
        self.ax.get_xaxis().set_ticks([])
        self.ax.get_yaxis().set_ticks([])

        
        # add toggle buttons that allow the user to turn on and off std masks
        # I used to have this in its own nice function, but MPL didn't like 
        # that for some reason... there is probably a better way, I just dont
        # know the innerds of MPL enough --TJL
        
        
        axcolor = 'lightgoldenrodyellow'

        ax1 = plt.axes([0.04, 0.7, 0.12, 0.08])                       
        self.b1 = ToggleButton(ax1, 'nonbonded', color=axcolor, hovercolor='0.975')
        self.b1.on_turned_on(self.mask.mask_nonbonded)
        self.b1.on_turned_off(self.mask.remove_mask, 'nonbonded')
        self.b1.on_turned_on(self.update_image)
        self.b1.on_turned_off(self.update_image)
        
        ax2 = plt.axes([0.04, 0.6, 0.12, 0.08])                       
        self.b2 = ToggleButton(ax2, 'row 13', color=axcolor, hovercolor='0.975')
        self.b2.on_turned_on(self.mask.mask_row13)
        self.b2.on_turned_off(self.mask.remove_mask, 'row13')
        self.b2.on_turned_on(self.update_image)
        self.b2.on_turned_off(self.update_image)
        
        ax3 = plt.axes([0.04, 0.5, 0.12, 0.08])                       
        self.b3 = ToggleButton(ax3, 'borders', color=axcolor, hovercolor='0.975')
        self.b3.on_turned_on(self._set_borderwidth)
        self.mask_border_cid = self.b3.on_turned_on(self.mask.mask_borders)
        self.b3.on_turned_off(self.mask.remove_mask, 'border')
        self.b3.on_turned_on(self.update_image)
        self.b3.on_turned_off(self.update_image)
        
        ax4 = plt.axes([0.04, 0.4, 0.12, 0.08])                       
        self.b4 = ToggleButton(ax4, 'threshold', color=axcolor, hovercolor='0.975')
        self.b4.on_turned_on(self._set_threshold)
        self.mask_threshold_cid = self.b4.on_turned_on(self.mask.mask_threshold, self.raw_image_4d, None, None)
        self.b4.on_turned_off(self.mask.remove_mask, 'threshold')
        self.b4.on_turned_on(self.update_image)
        self.b4.on_turned_off(self.update_image)
                           
        plt.show()
        
        return
        
        
    def _set_threshold(self):
        print "\n --- Enter threshold values --- "
        self.lower_thld = float( raw_input('Enter lower threshold: ') )
        self.upper_thld = float( raw_input('Enter upper threshold: ') )
        self.b4.onstate_exargs[ self.mask_threshold_cid ] = (self.raw_image_4d, self.upper_thld, self.lower_thld)
        return
    

    def _set_borderwidth(self):
        print "\n --- Enter the desired border width --- "
        raw_in = raw_input('Size of border (in pixels) [1]: ')
        if raw_in == '':
            self.borderwidth = 1
        else:
            self.borderwidth = int( raw_in )
        self.b3.onstate_exargs[ self.mask_border_cid ] = (self.borderwidth,)
        return
    
    
    def update_image(self):
        self.im.set_data( (self.log_image * self.mask.mask2d) - 1e-10 )
        return

    
    def on_click(self, event):
        
        # for WHATEVER reason, the imshow drawing is stretched incorrectly
        # such that pixel positions for x/y are off by the ratio used below
        # ... this is likely due to me not understanding MPL, hence this hack
        # -- TJL
        ratio = float(self.log_image.shape[0]) / float(self.log_image.shape[1])
        x_coord = event.xdata / ratio
        y_coord = event.ydata * ratio
         
        # if a button that is *not* the left click is pressed
        if event.inaxes and (event.button is not 1):

            # save the points for masking in pixel coordinates
            if self.xy != None:
                self.xy = np.vstack(( self.xy, np.array([int(x_coord), 
                                                         int(y_coord)]) ))
            else:
                self.xy = np.array([int(x_coord), int(y_coord)])

            # save the points for drawing the lines in MPL coordinates
            if self.lines_xy != None:
                self.lines_xy = np.vstack(( self.lines_xy, np.array([int(event.xdata), 
                                                                     int(event.ydata)]) ))
            else:
                self.lines_xy = np.array([int(event.xdata), int(event.ydata)])
            
            self.lc.set_data(self.lines_xy.T) # draws lines
            self.line_corner = (int(event.xdata), int(event.ydata))
            
        # if the left button is pressed
        elif event.inaxes and (event.button is 1):
            self.single_px = (int(x_coord), int(y_coord))
            print "Selected: (%s, %s)" % self.single_px        
        
        return


    def on_keypress(self, event):

        # mask or unmask
        if event.key in ['m', 'u']:
            
            if self.xy == None:
                print "No area selected, mask not changed."
            else:
            
                # print "Masking region inside:"
                # print self.xy
           
                # wrap around to close polygon
                self.xy = np.vstack(( self.xy, self.xy[0,:] ))
                path = Path(self.xy)
                in_area = path.contains_points(self.points+0.5)
                inds = self.points[in_area]
            
                #print self.xy
                #print inds

                # if we're going to mask, mask
                if event.key == 'm':
                    print 'Masking convex area...'
                    x = self._conv_2dinds_to_4d(inds)
                    self.mask._masks['manual'][x[:,0],x[:,1],x[:,2],x[:,3]] = 0
                
                # if we're unmasking, unmask
                elif event.key == 'u':
                    print 'Unmasking convex area...'
                    x = self._conv_2dinds_to_4d(inds)
                    self.mask._masks['manual'][x[:,0],x[:,1],x[:,2],x[:,3]] = 1
            
                # draw and reset
                self.update_image()
                self._reset()
                plt.draw()
        

        # reset all masks
        elif event.key == 'r':
            print 'Unmasking all'
            self.mask._masks['manual'] = self.mask._blank_mask()
            self.update_image()
            self._reset()
            #self.im.autoscale()
            plt.draw()
        
        # toggle selection    
        elif event.key == 't':
            
            if self.single_px != None:            
                x = self._conv_2dinds_to_4d( np.array(self.single_px)[None,:] )
                x = x.flatten()
                if self.mask.mask[x[0],x[1],x[2],x[3]] == 0:
                    print "Unmasking single pixel:", self.single_px
                    self.mask._masks['manual'][x[0],x[1],x[2],x[3]] = 1
                else:
                    print "Masking single pixel:", self.single_px
                    self.mask._masks['manual'][x[0],x[1],x[2],x[3]] = 0
                    
            else:
                print "No single pixel selected to toggle. Click a pixel and"
                print "    press `t` to toggle the mask on that pixel."
            
            self.update_image()
            self._reset()
            #self.im.autoscale()
            plt.draw()
            
        # clear mouse selection
        elif event.key == 'x':
            print "Reset selections"
            self._reset()
            
           
        # save and exit
        elif event.key == 'w':
            self.mask.save(self.filename, fmt=self.file_fmt)
            plt.close()
            return
          
        # exit w/o saving
        elif event.key == 'q':
            print 'Exiting without saving...'
            plt.close()
            return
          
        # else:
        #     print "Could not understand key: %s" % event.key
        #     print "Valid options: {m, u, r, k, q}"
            
        return
    

    def on_move(self, event):
        if not event.inaxes: return
        xm, ym = int(event.xdata), int(event.ydata)
        
        # update the line positions
        if self.line_corner != (0,0):
            self.lm.set_data((self.line_corner[0],xm), (self.line_corner[1],ym))
            plt.draw()
            
        return
    
        
    def _reset(self):
        self.single_pixel = None
        self.xy = None
        self.lines_xy = None
        self.lc.set_data([], [])
        self.lm.set_data([], [])
        self.line_corner = (0, 0)
        return
    
        
    def _conv_2dinds_to_4d(self, inds):
        """
        Convert indices in a 2d Cheetah array to (4,16,185,194).
        
        Parameters
        ----------
        inds : np.ndarray, int
            An N x 2 array, where the first column indexes x on the 2d image,
            and the second column indexes y.
            
        Returns
        -------
        inds_4d : np.ndarray, int
            An N x 4 array, with each column indexing quads/asics/y/x,
        """
        
        inds_4d = np.zeros((inds.shape[0], 4), dtype=np.int32)
        
        # index each asic, in the correct order
        of64 = (inds[:,1] / 185) * 2 + (inds[:,0] / 388) * 16 + (inds[:,0] / 194) % 2
        assert np.all(of64 < 64)
        
        # quads / asics
        # print 'masking in ASICs:', inds, of64
        inds_4d[:,0] = of64 / 16
        inds_4d[:,1] = of64 % 16
        
        # x / y : note the image is displayed transposed
        inds_4d[:,2] = (inds[:,1] % 185)
        inds_4d[:,3] = (inds[:,0] % 194)
        
        return inds_4d
    
        
    def print_gui_help(self):
        
        print
        print
        print "   --- WELCOME TO PYPAD's INTERACTIVE MASKING ENVIRONMENT --- "
        print
        print " Green pixels are masked."
        print
        print " Keystrokes"
        print " ----------"
        print " m : mask               u : unmask            r : reset "
        print " x : clear selection    w : save & exit       t : toggle pixel"
        print " q : exit w/o saving"
        print
        print " Mouse"
        print " -----"
        print " Right click on three or more points to draw a polygon around a"
        print " set of pixels. Then press `m` or `u` to mask or unmask that area."
        print
        print " You can also mask/unmask single pixels by clicking on them with"
        print " the mouse and pressing `t` to toggle the mask state."
        print
        print " Toggle Buttons (left)"
        print " ---------------------"
        print " nonbonded : Mask nonbonded pixels, and their nearest neighbours."
        print "             These pixels aren't even connected to the detector."
        print 
        print " row 13    : In some old experiments, row 13 on one ASIC was "
        print "             busted -- mask that (will be clear if this is needed)"
        print 
        print " threshold : You will be prompted for an upper and lower limit --"
        print "             pixels outside that range are masked. Units are ADUs"
        print "             and you can set only a lower/upper limit by passing"
        print "             'None' for one option."
        print 
        print " borders   : Mask the borders of each ASIC. These often give"
        print "             anomoulous responses. Recommended to mask one pixel"
        print "             borders at least."
        print ""
        print "                          ----- // -----"
        print