Exemple #1
0
class PolygonSelection(HasStrictTraits):
    """Plots, and lets the user interact with, a 2D selection.
    
    Attributes
    ----------
    polygon : Instance(numpy.ndarray)
        The polygon vertices
        
    view : Instance(IView)
        the IView that this view is wrapping.  I suggest that if it's another
        ISelectionView, that its `interactive` property remain False. >.>
        
    interactive : Bool
        is this view interactive?  Ie, can the user set the polygon verticies
        with mouse clicks?
    """
    
    id = "edu.mit.synbio.cytoflow.views.polygon"
    friendly_id = "Polygon Selection"
    
    view = Instance(IView, transient = True)
    interactive = Bool(False, transient = True)
    
    vertices = List((Float, Float))
    
    # internal state.
    _cursor = Instance(Cursor, transient = True)
    _path = Instance(mpl.path.Path, transient = True)
    _patch = Instance(mpl.patches.PathPatch, transient = True)
    _line = Instance(mpl.lines.Line2D, transient = True)
    _drawing = Bool(transient = True)
        
    def plot(self, experiment, **kwargs):
        """Plot self.view, and then plot the selection on top of it."""
        self.view.plot(experiment, **kwargs)
        self._draw_poly()

    def is_valid(self, experiment):
        """If the decorated view is valid, we are too."""
        return self.view.is_valid(experiment)
    
    @on_trait_change('vertices')
    def _draw_poly(self):
        ca = plt.gca()
         
        if self._patch and self._patch in ca.patches:
            self._patch.remove()
            
        if self._drawing or not self.vertices or len(self.vertices) < 3 \
           or any([len(x) != 2 for x in self.vertices]):
            return
             

        patch_vert = np.concatenate((np.array(self.vertices), 
                                    np.array((0,0), ndmin = 2)))
                                    
        self._patch = \
            mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True),
                                  edgecolor="black",
                                  linewidth = 1.5,
                                  fill = False)
            
        ca.add_patch(self._patch)
        plt.gcf().canvas.draw()
    
    @on_trait_change('interactive')
    def _interactive(self):
        if self.interactive:
            ax = plt.gca()
            self._cursor = Cursor(ax, horizOn = False, vertOn = False)            
            self._cursor.connect_event('button_press_event', self._onclick)
            self._cursor.connect_event('motion_notify_event', self._onmove)
        else:
            self._cursor.disconnect_events()
            self._cursor = None       
    
    def _onclick(self, event): 
        """Update selection traits"""      
        if(self._cursor.ignore(event)):
            return
        
        if event.dblclick:
            self._drawing = False
            self.vertices = map(tuple, self._path.vertices)
            self._path = None
            return
                
        ca = plt.gca()
                
        self._drawing = True
        if self._patch and self._patch in ca.patches:
            self._patch.remove()
            
        if self._path:
            vertices = np.concatenate((self._path.vertices,
                                      np.array((event.xdata, event.ydata), ndmin = 2)))
        else:
            vertices = np.array((event.xdata, event.ydata), ndmin = 2)
            
        self._path = mpl.path.Path(vertices, closed = False)
        self._patch = mpl.patches.PathPatch(self._path, 
                                            edgecolor = "black",
                                            fill = False)

        ca.add_patch(self._patch)
        plt.gcf().canvas.draw()
        
    def _onmove(self, event):       
         
        if(self._cursor.ignore(event) 
           or not self._drawing
           or not self._path
           or self._path.vertices.shape[0] == 0
           or not event.xdata
           or not event.ydata):
            return
        
        ca = plt.gca()
         
        if not ca:
            return
         
        if self._line and self._line in ca.lines:
            self._line.remove()
            
        xdata = [self._path.vertices[-1, 0], event.xdata]
        ydata = [self._path.vertices[-1, 1], event.ydata]
        self._line = mpl.lines.Line2D(xdata, ydata, linewidth = 1, color = "black")
        
        ca.add_line(self._line)
        plt.gcf().canvas.draw()
Exemple #2
0
class QuadSelection(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a quadrant gate.
    
    Attributes
    ----------
    op : Instance(Range2DOp)
        The instance of Range2DOp that we're viewing / editing
        
    huefacet : Str
        The conditioning variable to plot multiple colors
        
    subset : Str
        The string passed to `Experiment.query()` to subset the data before
        plotting
        
    interactive : Bool
        is this view interactive?  Ie, can the user set the threshold with a 
        mouse click?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but
    they must both be unset!
        
    Examples
    --------
    
    In an IPython notebook with `%matplotlib notebook`
    
    >>> q = flow.QuadOp(name = "Quad",
    ...                 xchannel = "V2-A",
    ...                 ychannel = "Y2-A"))
    >>> qv = q.default_view()
    >>> qv.interactive = True
    >>> qv.plot(ex2) 
    """
    
    id = Constant('edu.mit.synbio.cytoflow.views.quad')
    friendly_id = Constant("Quadrant Selection")
    
    op = Instance(IOperation)
    name = DelegatesTo('op')
    xchannel = DelegatesTo('op')
    ychannel = DelegatesTo('op')
    interactive = Bool(False, transient = True)
    
    # internal state.
    _ax = Any(transient = True)
    _hline = Instance(Line2D, transient = True)
    _vline = Instance(Line2D, transient = True)
    _cursor = Instance(Cursor, transient = True)
        
    def plot(self, experiment, **kwargs):
        """Plot the underlying scatterplot and then plot the selection on top of it."""
        
        if not experiment:
            raise util.CytoflowOpError("No experiment specified")
        
        if not experiment:
            raise util.CytoflowViewError("No experiment specified")
        
        if self.xfacet:
            raise util.CytoflowViewError("RangeSelection.xfacet must be empty or `Undefined`")
        
        if self.yfacet:
            raise util.CytoflowViewError("RangeSelection.yfacet must be empty or `Undefined`")
        
        super(QuadSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_lines()
        self._interactive()

    @on_trait_change('op.xthreshold, op.ythreshold', post_init = True)
    def _draw_lines(self):
        if not self._ax:
            return
        
        if self._hline and self._hline in self._ax.lines:
            self._hline.remove()
            
        if self._vline and self._vline in self._ax.lines:
            self._vline.remove()
            
        if self.op.xthreshold and self.op.ythreshold:
            self._hline = plt.axhline(self.op.ythreshold, 
                                      linewidth = 3, 
                                      color = 'blue')
            self._vline = plt.axvline(self.op.xthreshold,
                                      linewidth = 3,
                                      color = 'blue')

            plt.draw_if_interactive()

    @on_trait_change('interactive', post_init = True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax,
                                  horizOn = True,
                                  vertOn = True,
                                  color = 'blue') 
            self._cursor.connect_event('button_press_event', self._onclick)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None
            
    def _onclick(self, event):
        """Update the threshold location"""
        self.op.xthreshold = event.xdata
        self.op.ythreshold = event.ydata    
class ECE4012:
    def __init__(self, figure, filename, toolbar):
        self.dfCal = None
        self.fig = figure
        self.toolbar = toolbar
        self.filename = filename
        self.rect = None

    # Function used to setup graph after class is created or after new data is loaded
    def initializer(self, type):
        self.fig.clear()
        print("clear figure")
        self.fig.subplots_adjust(hspace=1.0)
        self.annoteText = "annotated"
        self.isAnnotate = False
        self.fig.canvas.mpl_connect('button_release_event', self.onrelease)
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)
        self.fig.canvas.mpl_connect('key_press_event', self.onpress)
        self.fig.canvas.mpl_connect('pick_event', self.onpick)

        if type == "DB":
            self.searchOpenCalibration(self.filename)
            self.conn = self.getConnection(self.filename)
            t = time.time()
            df = pd.read_sql_query("select * from  acc;", self.conn)
            elapsed = time.time() - t
            print("time taken to read data was {:f}".format(elapsed))
        elif type == "CSV":
            print("CSV getting Calibration")
            self.searchOpenCalibration(self.filename)
            print("CSV opening file")
            df = pd.read_csv(self.filename)
            print("CSV opened file")

        print("Starting")
        # Apply calibration if calibration data is loaded
        if self.dfCal is not None:
            df["valuex"] = np.subtract(df["valuex"], self.dfCal['Values'][
                self.XOffset]) / self.dfCal['Values'][self.XGain]
            df["valuey"] = np.subtract(df["valuey"], self.dfCal['Values'][
                self.YOffset]) / self.dfCal['Values'][self.YGain]
            df["valuez"] = np.subtract(df["valuez"], self.dfCal['Values'][
                self.ZOffset]) / self.dfCal['Values'][self.ZGain]

        self.fig.subplots_adjust(bottom=0.3, left=0.2, hspace=1.0)
        # setup some constants to be used in Scale Selection panel
        self.labels = ['30 sec', '5 min', '30 min', '1 hr', '2 hr']
        sec30 = pd.Timedelta(30, unit='s')
        min5 = pd.Timedelta(5, unit='m')
        min30 = pd.Timedelta(30, unit='m')
        hr1 = pd.Timedelta(1, unit='h')
        hr2 = pd.Timedelta(2, unit='h')

        # Scale Selection panel made from Radio Buttons
        self.scaleDeltaArray = [sec30, min5, min30, hr1, hr2]
        self.rax = self.fig.add_axes([0.02, 0.7, 0.11, 0.15])
        self.rax.set_title('Scale')
        self.check = RadioButtons(self.rax, self.labels)
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)
        self.check.on_clicked(self.changeScale)

        # Data Set Selection Check Box
        self.rcbax = self.fig.add_axes([0.02, 0.25, 0.11, 0.15])
        self.rcbax.set_title('DataSet')
        self.checkBox = CheckButtons(self.rcbax,
                                     ('valuex', 'valuey', 'valuez', 'mag'),
                                     (True, True, True, True))
        self.checkBox.on_clicked(self.oncheck)
        xs = np.linspace(15, 21, 200)
        horiz_line_dataz = np.array([0.41 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_dataz, 'C3-')
        horiz_line_datam = np.array([0.26 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datam, 'C0-')
        horiz_line_datay = np.array([0.58 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datay, 'C2-')
        horiz_line_datax = np.array([0.73 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datax, 'C1-')

        # Create magnitude value of acceleration data in a dataframe and substract 1g to eliminate gravity
        df["mag"] = np.sqrt(np.square(df["valuex"]) + np.square(df['valuey']) + \
                            np.square(df["valuez"]))-1.0
        print("Normalize")
        ya_min = df.loc[:, "mag"].min() - 0.1
        ya_max = df.loc[:, "mag"].max() + 0.1
        print("y limit (max={:f},min={:f})".format(ya_max, ya_min))

        print("Convert Date Time")
        print(df["epoch"].iloc[0])
        print(df["epoch"].iloc[1])
        # Convert epoch to datetime
        if type == "DB":
            print("epoch type", df.dtypes[0])
            df['epoch'] = pd.to_datetime(df['epoch'], unit='ms')
        elif type == "CSV":
            print("epoch type", df.dtypes[0])
            if 'annotated' not in df.columns:
                df['epoch'] = pd.to_datetime(df['epoch'], unit='ms')
            else:
                df['epoch'] = pd.to_datetime(df['epoch'])
        print("epoch type after", df.dtypes[0])

        # Adjusting epoch time to be in Eastern Time Zone
        df['epoch'] = df['epoch'].dt.tz_localize('UTC').dt.tz_convert(
            'America/New_York')
        # Create external numpy array to manage timestamp data to be sure that data will be datetime
        vepoch = df['epoch']
        self.datevalues = vepoch.dt.to_pydatetime()

        # Create annotated and colorHex columns in the dataframe for capturing annotation
        if 'annotated' not in df.columns:
            charArr = np.chararray((len(df["mag"], )), unicode=True)
            charArr[:] = ' '
            df["annotated"] = pd.DataFrame(charArr)
            df["colorHex"] = pd.DataFrame(charArr)
        # Define format for graph ticks
        self.hoursFmt = mdates.DateFormatter('%a-%m-%d %H:%M:%S')
        self.HMSFmt = mdates.DateFormatter('%H:%M:%S')
        # Get total time of the data in hours
        startDate = df["epoch"].iloc[0]
        self.startDate = startDate
        endDate = df["epoch"].iloc[-1]
        totaltime = int(endDate.timestamp() * 1000) - \
                    int(startDate.timestamp() * 1000)
        total_hours = totaltime / 1000 / 3600  #totaltime in hours
        total_minutes = total_hours * 60
        print("total hours {:f}".format(total_hours))

        # Create variable to track drag time
        self.timeTrack = 0
        # Create variable to track initial click xpos
        self.xpos = 0
        # Create class attribute with the data to be used in the rest of class functions
        self.df = df

        print("Creating Top Graph")
        # First axis  representing Top Graph
        self.ax = self.fig.add_subplot(2, 1, 1)
        # Second axis representing Bottom Graph
        print("Creating Bottom Graph")
        self.ax2 = self.fig.add_subplot(2, 1, 2)
        # Adjust distance between toolbar and graph to give more space
        self.fig.subplots_adjust(bottom=0.145)

        # Adjust distance between toolbar and graph if data is 24 hours or more
        if total_hours > 24:
            self.fig.subplots_adjust(hspace=1.3)
        print("Start Plot Setting")

        # Set axis titles to graphs
        self.ax.set_xlabel("Time")
        self.ax2.set_xlabel("Time")
        self.ax.set_ylabel("Acceleration (g)")
        self.ax2.set_ylabel("Acceleration (g)")
        # Adjust y-axis range to maximum and minimum of magnitude
        self.ax.set_ylim(ya_min, ya_max)
        self.ax2.set_ylim(ya_min, ya_max)
        # Set title with word Calibrated if calibration was applied
        bottomtitle = 'Magnitude Graph for Start Date ' + "{:%Y-%m-%d}".format(
            startDate)
        if self.dfCal is not None:
            bottomtitle = 'Calibrated ' + bottomtitle
        self.ax.set_title(bottomtitle)

        toptitle = 'Zoomed-In Graph for Start Date ' + "{:%Y-%m-%d}".format(
            startDate)
        if self.dfCal is not None:
            toptitle = 'Calibrated ' + toptitle
        self.ax2.set_title(toptitle)

        # Prepare x-axis range to display initially in bottom graph
        start_time = df["epoch"].iloc[0]
        print("start time={v}".format(v=start_time))
        # If data loaded is longer than 1 hour, GUI will show the first hour on bottom graph
        if total_hours > 1:
            end_time = start_time + hr1
        elif total_minutes > 30:
            end_time = start_time + min30
        elif total_minutes > 5:
            end_time = start_time + min5
        else:
            end_time = start_time + sec30
        print("end time={v}".format(v=end_time))
        self.startX = start_time
        self.endX = end_time

        # Set x-axis limit with previous selection
        self.ax2.set_xlim(start_time, end_time)
        print("Set Limit")
        # Create data mask with informaton being displayed
        mask = (df["epoch"] >= start_time) & (df["epoch"] <= end_time)
        print("Plotting")

        # Plot top graph
        t = time.time()
        # To increase speed to graph, only plot first 4 points and then
        # substitute the data with function set_data, this saves several seconds of load time
        self.plotTop, = self.ax.plot(df["epoch"].iloc[0:3],
                                     df["mag"].iloc[0:3],
                                     visible=True)
        self.plotTop.set_data(self.datevalues, df["mag"])
        self.ax.relim()
        self.ax.autoscale_view(True, True, True)
        self.fig.canvas.draw()
        elapsed = time.time() - t
        print("time taken to top graph magnitude was {:f}".format(elapsed))

        print(len(df.loc[mask, "epoch"]))
        # Plot bottom graph
        t = time.time()
        # Initially graphs only magnitude
        # To increase speed to graph, only plot first 4 points and then
        # substitute the data with function set_data, this saves several seconds of load time
        self.lm, = self.ax2.plot(df["epoch"].iloc[0:3],
                                 df["mag"].iloc[0:3],
                                 visible=True)
        self.lm.set_data(self.datevalues, df["mag"])
        self.ax2.relim()
        self.ax2.autoscale_view(True, True, True)
        self.fig.canvas.draw()
        elapsed = time.time() - t
        print("time taken to bottom graph magnitude was {:f}".format(elapsed))
        t = time.time()
        # Create thread to plot x value in the bottom graph in background mode
        # to avoid startup delay
        try:
            thread1 = myThread(1, "valuex", self)
            thread1.start()
        except:
            print("Error: unable to start thread")
        elapsed = time.time() - t
        print("time taken to bottom graph valuex was {:f}".format(elapsed))
        t = time.time()
        # Create thread to plot y value in the bottom graph in background mode
        # to avoid startup delay
        try:
            thread2 = myThread(2, "valuey", self)
            thread2.start()
        except:
            print("Error: unable to start thread")

        elapsed = time.time() - t
        print("time taken to bottom graph valuey was {:f}".format(elapsed))
        t = time.time()
        # Create thread to plot z value in the bottom graph in background mode
        # to avoid startup delay
        try:
            thread3 = myThread(3, "valuez", self)
            thread3.start()
        except:
            print("Error: unable to start thread")

        elapsed = time.time() - t
        print("time taken to bottom graph valuez was {:f}".format(elapsed))

        # x in the range
        tX = df.loc[mask, "epoch"]
        self.ax2.set_xlim(tX.iloc[0], tX.iloc[len(tX) - 1])
        # Set tick format and number on top graph
        self.ax.xaxis.set_major_formatter(self.HMSFmt)
        self.ax.get_xaxis().set_major_locator(LinearLocator(numticks=12))
        # Setup format of ticks to Weekday month/day if data is 24 hours or more
        if total_hours > 24:
            self.ax.xaxis.set_major_formatter(self.hoursFmt)

        # Rotate labels on x-axis 45 degrees and set font size to 8
        self.ax.tick_params(axis='x', labelsize=8, rotation=45)
        # Select initial random color for random annotation
        self.color = self.colorChoose(random.randint(0, 255),
                                      random.randint(0, 255),
                                      random.randint(0, 255))
        self.removedObj = None
        # Refresh the graph
        self.fig.canvas.draw()
        # Prepare graph to display red cursor on top graph
        self.fig.canvas.mpl_connect('motion_notify_event', self.onmouseover)
        self.cursor = Cursor(self.ax,
                             useblit=True,
                             horizOn=False,
                             color='red',
                             linewidth=2)
        self.cursor.connect_event('button_press_event', self.cursorOnclick)

        # Setup initial value for the scale to 30 seconds
        self.currentDelta = pd.Timedelta(30, unit='s')
        # Set tick format and number on bottom graph
        self.ax2.xaxis.set_major_formatter(self.HMSFmt)
        self.ax2.get_xaxis().set_major_locator(LinearLocator(numticks=7))

        # Set function to format coordinates to be displayed on bottom right corner
        self.ax.format_coord = self.format_coord
        # Call function to search for annotation in the data
        # if it was loaded from CSV file and graph them
        if type == "CSV" and 'annotated' in df.columns:
            print("calling searchForAnnotation")
            self.searchForAnnotation()

    # Function that derives values to be shown on bottom right corner.
    # It will display Timestamp and magnitude of where cursor is located
    def format_coord(self, x, y):
        dateclickOn = mdates.num2date(x)
        idx = self.df['epoch'].searchsorted(dateclickOn)
        idx = idx - 1
        vx = self.df['valuex'].values[idx][0]
        vy = self.df['valuey'].values[idx][0]
        vz = self.df['valuez'].values[idx][0]
        vm = self.df['mag'].values[idx][0]
        return "{:%H:%M:%S} M:{:1.3f}".format(dateclickOn, vm)

    # Function to read calibrationResultsXXXXXXXXXXXX.csv
    # to obtain offset and gain values of x,y, and z-axis
    # from MetaWear device
    def readCalibration(self, filename):
        self.dfCal = pd.read_csv(filename)
        print('Results: %s', self.dfCal)
        print('title: %s', self.dfCal['Titles'])
        print('values: %s', self.dfCal['Values'])
        print('ZGain: %s', self.dfCal['Values'][1])
        self.ZOffset = 0
        self.ZGain = 1
        self.YOffset = 2
        self.YGain = 3
        self.XOffset = 4
        self.XGain = 5

    # Function used to redraw top and bottom graphs
    # with calibrated values applied to x, y, and z values
    def redrawAfterCalibrate(self):

        print("Computing new data")

        #Computes new x, y, and z values with calibration
        if self.dfCal is not None:
            self.df["valuex"] = np.subtract(
                self.df["valuex"], self.dfCal['Values'][
                    self.XOffset]) / self.dfCal['Values'][self.XGain]
            self.df["valuey"] = np.subtract(
                self.df["valuey"], self.dfCal['Values'][
                    self.YOffset]) / self.dfCal['Values'][self.YGain]
            self.df["valuez"] = np.subtract(
                self.df["valuez"], self.dfCal['Values'][
                    self.ZOffset]) / self.dfCal['Values'][self.ZGain]
        print("Computing new mag")

        self.df["mag"] = np.sqrt(np.square(self.df["valuex"]) + np.square(self.df['valuey']) + \
                            np.square(self.df["valuez"]))-1.0
        print("setting titles")
        # Bottom Title changed to say Calibrated
        bottomtitle = 'Magnitude Graph for Start Date ' + "{:%Y-%m-%d}".format(
            self.startDate)
        if self.dfCal is not None:
            bottomtitle = 'Calibrated ' + bottomtitle
        self.ax.set_title(bottomtitle)

        # Top Title changed to say Calibrated
        toptitle = 'Zoomed-In Graph for Start Date ' + "{:%Y-%m-%d}".format(
            self.startDate)
        if self.dfCal is not None:
            toptitle = 'Calibrated ' + toptitle
        self.ax2.set_title(toptitle)

        print("redraw graph")

        # Refreshes graphs with new data values
        self.plotTop.set_ydata(self.df["mag"])
        self.lm.set_ydata(self.df["mag"])
        self.lx.set_ydata(self.df["valuex"])
        self.ly.set_ydata(self.df["valuey"])
        self.lz.set_ydata(self.df["valuez"])
        self.fig.canvas.draw()

    # Function used to determine where mouse event occurs
    def onmouseover(self, event):

        if event.inaxes == self.rax:
            None

        if event.inaxes == self.ax:
            self.cursor.onmove(event)

    # Function used to determine if calibration file is found in same directory
    # as GUI application
    def searchOpenCalibration(self, filename):
        directory = os.path.dirname(filename)
        print("directory=" + directory)
        tosearch = re.compile('^calibrationResults.*')
        for sChild in os.listdir(directory):
            print("file=" + sChild)
            if tosearch.match(sChild) is not None:
                if directory == "":
                    directory = "."
                calibfilename = directory + "/" + sChild
                print("calibfilename=" + calibfilename)
                self.readCalibration(calibfilename)
                break

    # Function used to determine if annotations have been applied to
    # recently opened csv file
    def searchForAnnotation(self):
        colorKick = self.colorChoose(255, 0, 0)
        colorSleep = self.colorChoose(0, 0, 255)
        colorRandom = self.colorChoose(0, 255, 0)
        randomColor = self.color

        maskKick = self.df['annotated'] == "Kick"
        maskSleep = self.df['annotated'] == "Sleep"
        maskRandom = self.df['annotated'] == "Random"
        maskColorRandom = self.df['annotated'] == "annotated"

        print("masked found")

        # Creates mask of locations of annotations
        # Calls reDrawAnnotation to replot rectangles on graphs
        if len(self.df.loc[maskKick, 'epoch']) > 0:
            self.reDrawAnnotation(maskKick, colorKick)
        if len(self.df.loc[maskSleep, 'epoch']) > 0:
            self.reDrawAnnotation(maskSleep, colorSleep)
        if len(self.df.loc[maskRandom, 'epoch']) > 0:
            self.reDrawAnnotation(maskRandom, colorRandom)
        if len(self.df.loc[maskColorRandom, 'epoch']) > 0:
            self.reDrawAnnotation(maskColorRandom, randomColor)

    # Function used to redraw all annotations seen when
    # opening csv file that has annotations
    def reDrawAnnotation(self, mask, color):

        [ymin, ymax] = self.ax.get_ylim()
        height = ymax - ymin

        # Determines index positions of annotations
        positionKick = np.array(self.df.index[mask].tolist())

        # Determines where gaps between annotations occur
        diff = np.subtract(positionKick[1:-1], positionKick[0:-2])

        maskcuts = diff > 1
        posInCuts = np.where(maskcuts)
        arrposcut = posInCuts[0]
        arrpostcut1 = arrposcut + 1

        # Initializes first seen annotation
        xmin = self.df.loc[mask, 'epoch'].iloc[0]
        xmax = self.df.loc[mask, 'epoch'].iloc[-1]
        xmin = xmin.to_pydatetime()
        xmax = xmax.to_pydatetime()

        # converts values to proper time format
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))
        xmin = mdates.date2num(xmin)
        xmax = mdates.date2num(xmax)
        print("len(posInCuts)", len(posInCuts[0]))
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))

        # For loop used to run through and create rectangles to plot for annotations
        for idx in range(len(posInCuts[0])):
            print("idx=", idx)
            print("xmin={v1} xmax={v2}".format(
                v1=xmin,
                v2=self.df['epoch'].iloc[positionKick[arrposcut[idx]]]))

            rectmin = xmin
            rectmax = self.df['epoch'].iloc[positionKick[arrposcut[idx]]]
            rectmax = rectmax.to_pydatetime()
            rectmax = mdates.date2num(rectmax)

            width = rectmax - rectmin
            rect = ECERectangle(rectmin,
                                ymin,
                                width,
                                height,
                                color=color,
                                index=1)
            rect2 = ECERectangle(xmin,
                                 ymin,
                                 width,
                                 height,
                                 color=color,
                                 index=2)
            rect.setNext(rect2)
            rect2.setPrev(rect)
            self.ax.add_patch(rect)
            self.ax2.add_patch(rect2)
            self.fig.canvas.draw()
            xmin = self.df['epoch'].iloc[positionKick[arrpostcut1[idx]]]
            xmin = xmin.to_pydatetime()
            xmin = mdates.date2num(xmin)

        rectmin = xmin
        rectmax = xmax

        width = rectmax - rectmin
        rect = ECERectangle(rectmin, ymin, width, height, color=color, index=1)
        rect2 = ECERectangle(xmin, ymin, width, height, color=color, index=2)
        rect.setNext(rect2)
        rect2.setPrev(rect)
        self.ax.add_patch(rect)
        self.ax2.add_patch(rect2)
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))
        self.fig.canvas.draw()

    # Sets up sqlite3 connection to database file
    def getConnection(self, filename):
        """
        get the connection of sqlite given file name
        :param filename:
        :return: sqlite3 connection
        """

        conn = sqlite3.connect(filename)
        return conn

    # Old Function used to read calibration file. No longer relevant
    def readCalibration(self, filename):
        self.dfCal = pd.read_csv(filename)
        print('Results: %s', self.dfCal)
        print('title: %s', self.dfCal['Titles'])
        print('values: %s', self.dfCal['Values'])
        print('ZGain: %s', self.dfCal['Values'][1])
        self.ZOffset = 0
        self.ZGain = 1
        self.YOffset = 2
        self.YGain = 3
        self.XOffset = 4
        self.XGain = 5

    # Function used to create calibration button. No longer relevant
    def createCalibrationAction(self):
        image_file = 'square-upload'
        callback = self.toolbar.importCal
        text = "Calibrate"
        tooltip_text = "Calibrate"
        a = self.toolbar.addAction(self.toolbar._icon(image_file + '.png'),
                                   text, callback)
        print("action created")
        self.toolbar._actions[callback] = a
        print("action added to array")

        a.setToolTip(tooltip_text)

    # Function to execute query
    def executeQuery(self, query):
        return None

    # Function used to set range of colors
    def colorChoose(self, r, g, b):
        """
        create a normalized color map give r, g, b in 0-255 scale
        :param r: red
        :param g: green
        :param b: blue
        :return: tuple of color that can be used for matplotlib
        """
        if self.rangeOff(r) or self.rangeOff(g) or self.rangeOff(r):
            return (0, 0, 0)
        return r / 255.0, g / 255.0, b / 255.0

    # Function to set range off
    def rangeOff(self, val):
        return (val > 255) or (val < 0)

    # Function to determine event of creating annotations
    def onpick(self, event):
        print("pick event")
        if isinstance(event.artist, Rectangle):
            patch = event.artist
            # patch.remove()
            self.removedObj = patch
            print('onpick1 patch:', patch.get_path())
        else:
            print(event.artist.get_xdata())
            print(event.artist.get_xdata())
            print(len(event.artist.get_ydata()))

    # Function used to check if annotation is selected
    def onclick(self, event):
        if not self.toolbar._actions['zoom'].isChecked(
        ) and event.button == 1:  # left = 1, middle = 2, right = 3
            self.timeTrack = time.time()
            print("clicked {}".format(self.timeTrack))
            print("clicked X: {}".format(event.xdata))
            self.xpos = event.xdata

    # Function used to determine when annotation event occurs
    def onrelease(self, event):
        if not self.toolbar._actions['zoom'].isChecked() \
                and event.button == 1 and self.isAnnotate:

            curTime = time.time()
            if (curTime - self.timeTrack) > PRE_DEF_CLICK_TIME:
                print("dragged")
                xmin = self.xpos
                xmax = event.xdata
                width = xmax - xmin

                [ymin, ymax] = self.ax.get_ylim()
                height = ymax - ymin

                self.create_annotation(xmin, xmax)

                rect = ECERectangle(xmin,
                                    ymin,
                                    width,
                                    height,
                                    color=self.color,
                                    index=1)
                rect2 = ECERectangle(xmin,
                                     ymin,
                                     width,
                                     height,
                                     color=self.color,
                                     index=2)

                rect.setNext(rect2)
                rect2.setPrev(rect)
                self.ax.add_patch(rect)
                self.ax2.add_patch(rect2)

                self.fig.canvas.draw()

    # Key commands running from keyboard
    def onpress(self, event):
        # print(event.key())
        print(event.key)
        if event.key == 'r':
            self.annoteText = "Kick"
            self.color = self.colorChoose(255, 0, 0)
        elif event.key == 'b':
            self.annoteText = "Sleep"
            self.color = self.colorChoose(0, 0, 255)
        elif event.key == 'g':
            self.annoteText = "Random"
            self.color = self.colorChoose(0, 255, 0)
        elif event.key == "right":
            print("right pressed")
            self.startX += self.currentDelta
            self.endX += self.currentDelta
            self.ax2.set_xlim(self.startX, self.endX)

            self.run()
        elif event.key == "left":
            # TODO check left and right limit
            print("left pressed")
            self.startX -= self.currentDelta
            self.endX -= self.currentDelta
            self.ax2.set_xlim(self.startX, self.endX)

            self.run()
        elif event.key == "delete":
            print("delete")
            if self.removedObj is not None:
                if isinstance(self.removedObj, ECERectangle):
                    print("deleting ECERect")
                    rect_min_x = self.removedObj.get_x()
                    rect_max_x = rect_min_x + self.removedObj.get_width()
                    self.remove_annotation(rect_min_x, rect_max_x)
                    ind = self.removedObj.getIndex()
                    self.removedObj.remove()
                    if ind == 1:
                        nextRect = self.removedObj.getNext()
                    else:
                        nextRect = self.removedObj.getPrev()
                    self.removedObj = None

                    if nextRect.axes is not None:
                        nextRect.remove()
                    self.fig.canvas.draw()

    # Redraws graphs
    def run(self):
        self.fig.canvas.draw()

    # Creates annotation to dataframe
    def create_annotation(self, xmin, xmax):
        print("Annoated")
        cond = self.get_x_in_ranges(xmin, xmax)
        self.df.loc[cond, 'annotated'] = self.annoteText
        self.df.loc[cond, 'colorHex'] = self.get_color_in_hex()

    # Deletes annotation labels
    def remove_annotation(self, xmin, xmax):
        cond = self.get_x_in_ranges(xmin, xmax)
        self.df.loc[cond, 'annotated'] = " "
        self.df.loc[cond, 'colorHex'] = " "

    # Obtain mask of range of x values
    def get_x_in_ranges(self, xmin, xmax):
        return (self.df["epoch"] <= mdates.num2date(xmax)) \
               & (self.df["epoch"] >= mdates.num2date(xmin))

    # Get hexdecimal color
    def get_color_in_hex(self):
        return "#{:02X}{:02X}{:02X}".format(int(self.color[0] * 255),
                                            int(self.color[1] * 255),
                                            int(self.color[2] * 255))

    # Unselect annotation when not active
    def setSelect(self):
        self.isAnnotate = not self.isAnnotate

    # Function used to determine what click and hold effect does for annotations
    def cursorOnclick(self, event):
        'on button press we will see if the mouse is over us '
        if (self.ax == event.inaxes) and (not self.isAnnotate):
            xdata = event.xdata
            dateclicked = mdates.num2date(xdata)
            self.startX = dateclicked
            dateEnd = dateclicked + self.currentDelta
            self.endX = dateEnd

            mask = (self.df["epoch"] >= dateclicked) & (self.df["epoch"] <=
                                                        dateEnd)
            tX = self.df.loc[mask, "epoch"]

            self.ax2.set_xlim(tX.iloc[0], tX.iloc[len(tX) - 1])

            self.ax2.get_xaxis().set_major_locator(LinearLocator(numticks=12))

            self.ax2.autoscale_view(True, True, True)

            width = mdates.date2num(dateEnd) - xdata
            [ymin, ymax] = self.ax.get_ylim()
            height = ymax - ymin
            if self.rect is not None:
                self.rect.remove()
            self.rect = Rectangle((xdata, ymin),
                                  width,
                                  height,
                                  fill=True,
                                  alpha=0.4,
                                  color=(1, 0, 0),
                                  picker=False)

            self.ax.add_patch(self.rect)

            self.fig.canvas.draw()

    # Sets checks on visibility of x,y, and z -axis plots
    def oncheck(self, label):
        if label == 'valuex':
            self.lx.set_visible(not self.lx.get_visible())
        elif label == 'valuey':
            self.ly.set_visible(not self.ly.get_visible())
        elif label == 'valuez':
            self.lz.set_visible(not self.lz.get_visible())
        elif label == 'mag':
            self.lm.set_visible(not self.lm.get_visible())
        self.fig.canvas.draw()

    # Adjust time scale values to plot on bottom graph
    def changeScale(self, label):
        index = self.labels.index(label)
        self.currentDelta = self.scaleDeltaArray[index]
Exemple #4
0
class ThresholdSelection(cytoflow.views.HistogramView):
    """
    Plots, and lets the user interact with, a threshold on the X axis.
    
    TODO - beautify!
    
    Attributes
    ----------
    op : Instance(ThresholdOp)
        the ThresholdOp we're working on.
        
    huefacet : Str
        The conditioning variable to show multiple colors on this plot

    subset : Str    
        the string passed to Experiment.subset() defining the subset we plot

    interactive : Bool
        is this view interactive?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.HistogramView`, but
    they must both be unset!
        
    Examples
    --------
    In an IPython notebook with `%matplotlib notebook`
    
    >>> t = flow.ThresholdOp(name = "Threshold",
    ...                      channel = "Y2-A")
    >>> tv = t.default_view()
    >>> tv.plot(ex2)
    >>> tv.interactive = True
    >>> # .... draw a threshold on the plot
    >>> ex3 = thresh.apply(ex2)
    """
    
    id = Constant('edu.mit.synbio.cytoflow.views.threshold')
    friendly_id = Constant("Threshold Selection")
    
    op = Instance(IOperation)
    name = DelegatesTo('op')
    channel = DelegatesTo('op')
    interactive = Bool(False, transient = True)

    # internal state
    _ax = Any(transient = True)
    _line = Instance(Line2D, transient = True)
    _cursor = Instance(Cursor, transient = True)
    
    def plot(self, experiment, **kwargs):
        """Plot the histogram and then plot the threshold on top of it."""
        
        if not experiment:
            raise util.CytoflowViewError("No experiment specified")
        
        if self.xfacet:
            raise util.CytoflowViewError("ThresholdSelection.xfacet must be empty")
        
        if self.yfacet:
            raise util.CytoflowViewError("ThresholdSelection.yfacet must be empty")
        
        super(ThresholdSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()        
        self._draw_threshold()
        self._interactive()
    
    @on_trait_change('op.threshold', post_init = True)
    def _draw_threshold(self):
        if not self._ax or not self.op.threshold:
            return
        
        if self._line:
            # when used in the GUI, _draw_threshold gets called *twice* without
            # the plot being updated inbetween: and then the line can't be 
            # removed from the plot, because it was never added.  so check
            # explicitly first.  this is likely to be an issue in other
            # interactive plots, too.
            if self._line and self._line in self._ax.lines:
                self._line.remove()
 
            self._line = None
        
        if self.op.threshold:    
            self._line = plt.axvline(self.op.threshold, linewidth=3, color='blue')
            
        plt.draw_if_interactive()
        
    @on_trait_change('interactive', post_init = True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax, 
                                  horizOn=False,
                                  vertOn=True,
                                  color='blue')
            self._cursor.connect_event('button_press_event', self._onclick)
            
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None
            
    def _onclick(self, event):
        """Update the threshold location"""
        self.op.threshold = event.xdata
Exemple #5
0
class FCGateManager(EventGenerator):
    """Manages gate creation widgets and gates."""
    def __init__(self, ax, callback_list=None):
        self.gates = []
        self.fig = ax.figure
        self.ax = ax
        self._plt_data = None
        self.active_gate = None
        self.sample = None
        self.canvas = self.fig.canvas
        self.key_handler_cid = self.canvas.mpl_connect(
            'key_press_event',
            lambda event: key_press_handler(event, self.canvas, self))
        self.pick_event_cid = self.canvas.mpl_connect('pick_event',
                                                      self.pick_event_handler)
        self.gate_num = 1
        self.current_channels = 'd1', 'd2'
        self.add_callback(callback_list)

    def disconnect_events(self):
        self.canvas.mpl_disconnect(self.key_handler_cid)
        self.canvas.mpl_disconnect(self.pick_event_cid)

    def pick_event_handler(self, event):
        """ Handles pick events """
        info = {
            'options': self.get_available_channels(),
            'guiEvent': event.mouseevent.guiEvent,
        }

        if hasattr(self, 'xlabel_artist') and (event.artist
                                               == self.xlabel_artist):
            info['axis_num'] = 0
            self.callback(Event('axis_click', info))

        if hasattr(self, 'ylabel_artist') and (event.artist
                                               == self.ylabel_artist):
            info['axis_num'] = 1
            self.callback(Event('axis_click', info))

    def add_gate(self, gate):
        self.gates.append(gate)
        self.set_active_gate(gate)

    def remove_active_gate(self):
        if self.active_gate is not None:
            self.gates.remove(self.active_gate)
            self.active_gate.remove()
            self.active_gate = None

    def set_active_gate(self, gate):
        if self.active_gate is None:
            self.active_gate = gate
            gate.activate()
        elif self.active_gate is not gate:
            self.active_gate.inactivate()
            self.active_gate = gate
            gate.activate()

    def _get_next_gate_name(self):
        gate_name = 'gate{0}'.format(self.gate_num)
        self.gate_num += 1
        return gate_name

    def _handle_gate_events(self, event):
        self.set_active_gate(event.info['caller'])

    def create_gate_widget(self, kind):
        def clean_drawing_tools():
            self._drawing_tool.disconnect_events()
            self.canvas.draw_idle()
            self._drawing_tool = None

        def create_gate(*args):
            cancelled = False  # TODO allow drawing tool to cancel
            verts = args[0]
            ch = self.current_channels
            verts = [dict(zip(ch, v)) for v in verts]

            if kind == 'poly':
                gate_type = PolyGate
            elif 'threshold' in kind or 'quad' in kind:
                gate_type = ThresholdGate

            # FIXME: This is very specific implementation
            if 'vertical' in kind:
                verts = [{ch[0]: v[ch[0]]} for v in verts]
            elif 'horizontal' in kind:
                if len(ch) == 1:
                    cancelled = True
                else:
                    verts = [{ch[1]: v[ch[1]]} for v in verts]

            if not cancelled:
                gate = BaseGate(verts,
                                gate_type,
                                name=self._get_next_gate_name(),
                                callback_list=self._handle_gate_events)
                gate.spawn(ch, self.ax)
                self.add_gate(gate)

            clean_drawing_tools()

        def start_drawing(kind):
            if kind == 'poly':
                self._drawing_tool = PolyDrawer(self.ax,
                                                oncreated=create_gate,
                                                lineprops=dict(color='k',
                                                               marker='o'))
            elif kind == 'quad':
                self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=1)
            elif kind == 'horizontal threshold':
                self._drawing_tool = Cursor(self.ax, vertOn=0, horizOn=1)
            elif kind == 'vertical threshold':
                self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=0)

            if isinstance(self._drawing_tool, Cursor):

                def finish_drawing(event):
                    self._drawing_tool.clear(None)
                    return create_gate([(event.xdata, event.ydata)])

                self._drawing_tool.connect_event('button_press_event',
                                                 finish_drawing)

        start_drawing(kind)

    ####################
    ### Loading Data ###
    ####################

    def load_fcs(self, filepath=None, parent=None):
        ax = self.ax

        if parent is None:
            parent = self.fig.canvas

        if filepath is None:
            from FlowCytometryTools.gui import dialogs
            filepath = dialogs.open_file_dialog('Select an FCS file to load',
                                                'FCS files (*.fcs)|*.fcs',
                                                parent=parent)

        if filepath is not None:
            self.sample = FCMeasurement('temp', datafile=filepath)
            print('WARNING: Data is raw (not transformation).')
            self._sample_loaded_event()

    def load_measurement(self, measurement):
        self.sample = measurement.copy()
        self._sample_loaded_event()

    def _sample_loaded_event(self):
        if self.sample is not None:
            self.current_channels = list(self.sample.channel_names[0:2])
            self.set_axes(self.current_channels, self.ax)

    def get_available_channels(self):
        return self.sample.channel_names

    def change_axis(self, axis_num, channel_name):
        """
        TODO: refactor that and set_axes
        what to do with ax?

        axis_num: int
           axis number
        channel_name: str
           new channel to plot on that axis
        """
        current_channels = list(self.current_channels)
        if len(current_channels) == 1:
            if axis_num == 0:
                new_channels = channel_name,
            else:
                new_channels = current_channels[0], channel_name
        else:
            new_channels = list(current_channels)
            new_channels[axis_num] = channel_name

        self.set_axes(new_channels, self.ax)

    def set_axes(self, channels, ax):
        """
        channels : iterable of string
            each value corresponds to a channel names
            names must be unique
        """
        # To make sure displayed as hist
        if len(set(channels)) == 1:
            channels = channels[0],

        self.current_channels = channels
        # Remove existing gates
        for gate in self.gates:
            gate.remove_spawned_gates()
        ##
        # Has a clear axis command inside!!
        # which will "force kill" spawned gates
        self.plot_data()

        for gate in self.gates:
            sgate = gate.spawn(channels, ax)
            gate._refresh_activation()

    def close(self):
        for gate in self.gates:
            gate.remove()
        self.disconnect_events()

    ####################
    ### Plotting Data ##
    ####################

    def plot_data(self):
        """Plots the loaded data"""
        # Clear the plot before plotting onto it
        self.ax.cla()

        if self.sample is None:
            return

        if self.current_channels is None:
            self.current_channels = self.sample.channel_names[:2]

        channels = self.current_channels
        channels_to_plot = channels[0] if len(channels) == 1 else channels
        self.sample.plot(channels_to_plot, ax=self.ax)

        xaxis = self.ax.get_xaxis()
        yaxis = self.ax.get_yaxis()
        self.xlabel_artist = xaxis.get_label()
        self.ylabel_artist = yaxis.get_label()
        self.xlabel_artist.set_picker(5)
        self.ylabel_artist.set_picker(5)

        self.fig.canvas.draw()

    def get_generation_code(self):
        """Return python code that generates all drawn gates."""
        if len(self.gates) < 1:
            code = ''
        else:
            import_list = set(
                [gate._gencode_gate_class for gate in self.gates])
            import_list = 'from FlowCytometryTools import ' + ', '.join(
                import_list)
            code_list = [gate.get_generation_code() for gate in self.gates]
            code_list.sort()
            code_list = '\n'.join(code_list)
            code = import_list + 2 * '\n' + code_list

        self.callback(Event('generated_code', {'code': code}))
        return code
Exemple #6
0
class ThresholdSelection(cytoflow.views.HistogramView):
    """
    Plots, and lets the user interact with, a threshold on the X axis.
    
    TODO - beautify!
    
    Attributes
    ----------
    op : Instance(ThresholdOp)
        the ThresholdOp we're working on.
        
    huefacet : Str
        The conditioning variable to show multiple colors on this plot

    subset : Str    
        the string passed to Experiment.subset() defining the subset we plot

    interactive : Bool
        is this view interactive?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.HistogramView`, but
    they must both be unset!
        
    Examples
    --------
    In an IPython notebook with `%matplotlib notebook`
    
    >>> t = flow.ThresholdOp(name = "Threshold",
    ...                      channel = "Y2-A")
    >>> tv = t.default_view()
    >>> tv.plot(ex2)
    >>> tv.interactive = True
    >>> # .... draw a threshold on the plot
    >>> ex3 = thresh.apply(ex2)
    """

    id = Constant('edu.mit.synbio.cytoflow.views.threshold')
    friendly_id = Constant("Threshold Selection")

    op = Instance(IOperation)
    name = DelegatesTo('op')
    channel = DelegatesTo('op')
    threshold = DelegatesTo('op')
    interactive = Bool(False, transient=True)

    # internal state
    _ax = Any(transient=True)
    _line = Instance(Line2D, transient=True)
    _cursor = Instance(Cursor, transient=True)

    def plot(self, experiment, **kwargs):
        """Plot the histogram and then plot the threshold on top of it."""

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if self.xfacet:
            raise util.CytoflowViewError(
                "ThresholdSelection.xfacet must be empty")

        if self.yfacet:
            raise util.CytoflowViewError(
                "ThresholdSelection.yfacet must be empty")

        super(ThresholdSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_threshold()
        self._interactive()

    @on_trait_change('threshold', post_init=True)
    def _draw_threshold(self):
        if not self._ax or not self.threshold:
            return

        if self._line:
            # when used in the GUI, _draw_threshold gets called *twice* without
            # the plot being updated inbetween: and then the line can't be
            # removed from the plot, because it was never added.  so check
            # explicitly first.  this is likely to be an issue in other
            # interactive plots, too.
            if self._line and self._line in self._ax.lines:
                self._line.remove()

            self._line = None

        if self.threshold:
            self._line = plt.axvline(self.threshold, linewidth=3, color='blue')

        plt.draw()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax,
                                  horizOn=False,
                                  vertOn=True,
                                  color='blue',
                                  useblit=True)
            self._cursor.connect_event('button_press_event', self._onclick)

        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None

    def _onclick(self, event):
        """Update the threshold location"""
        # sometimes the axes aren't set up and we don't get xdata (??)
        if event.xdata:
            self.threshold = event.xdata
class FCGateManager(EventGenerator):
    """Manages gate creation widgets and gates."""

    def __init__(self, ax, callback_list=None):
        self.gates = []
        self.fig = ax.figure
        self.ax = ax
        self._plt_data = None
        self.active_gate = None
        self.sample = None
        self.canvas = self.fig.canvas
        self.key_handler_cid = self.canvas.mpl_connect('key_press_event',
                                                       lambda event: key_press_handler(event,
                                                                                       self.canvas,
                                                                                       self))
        self.pick_event_cid = self.canvas.mpl_connect('pick_event', self.pick_event_handler)
        self.gate_num = 1
        self.current_channels = 'd1', 'd2'
        self.add_callback(callback_list)

    def disconnect_events(self):
        self.canvas.mpl_disconnect(self.key_handler_cid)
        self.canvas.mpl_disconnect(self.pick_event_cid)

    def pick_event_handler(self, event):
        """ Handles pick events """
        info = {'options': self.get_available_channels(),
                'guiEvent': event.mouseevent.guiEvent,
                }

        if hasattr(self, 'xlabel_artist') and (event.artist == self.xlabel_artist):
            info['axis_num'] = 0
            self.callback(Event('axis_click', info))

        if hasattr(self, 'ylabel_artist') and (event.artist == self.ylabel_artist):
            info['axis_num'] = 1
            self.callback(Event('axis_click', info))

    def add_gate(self, gate):
        self.gates.append(gate)
        self.set_active_gate(gate)

    def remove_active_gate(self):
        if self.active_gate is not None:
            self.gates.remove(self.active_gate)
            self.active_gate.remove()
            self.active_gate = None

    def set_active_gate(self, gate):
        if self.active_gate is None:
            self.active_gate = gate
            gate.activate()
        elif self.active_gate is not gate:
            self.active_gate.inactivate()
            self.active_gate = gate
            gate.activate()

    def _get_next_gate_name(self):
        gate_name = 'gate{0}'.format(self.gate_num)
        self.gate_num += 1
        return gate_name

    def _handle_gate_events(self, event):
        self.set_active_gate(event.info['caller'])

    def create_gate_widget(self, kind):
        def clean_drawing_tools():
            self._drawing_tool.disconnect_events()
            self.canvas.draw_idle()
            self._drawing_tool = None

        def create_gate(*args):
            cancelled = False  # TODO allow drawing tool to cancel
            verts = args[0]
            ch = self.current_channels
            verts = [dict(zip(ch, v)) for v in verts]

            if kind == 'poly':
                gate_type = PolyGate
            elif 'threshold' in kind or 'quad' in kind:
                gate_type = ThresholdGate

            # FIXME: This is very specific implementation
            if 'vertical' in kind:
                verts = [{ch[0]: v[ch[0]]} for v in verts]
            elif 'horizontal' in kind:
                if len(ch) == 1:
                    cancelled = True
                else:
                    verts = [{ch[1]: v[ch[1]]} for v in verts]

            if not cancelled:
                gate = BaseGate(verts, gate_type, name=self._get_next_gate_name(),
                                callback_list=self._handle_gate_events)
                gate.spawn(ch, self.ax)
                self.add_gate(gate)

            clean_drawing_tools()

        def start_drawing(kind):
            if kind == 'poly':
                self._drawing_tool = PolyDrawer(self.ax, oncreated=create_gate,
                                                lineprops=dict(color='k', marker='o'))
            elif kind == 'quad':
                self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=1)
            elif kind == 'horizontal threshold':
                self._drawing_tool = Cursor(self.ax, vertOn=0, horizOn=1)
            elif kind == 'vertical threshold':
                self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=0)

            if isinstance(self._drawing_tool, Cursor):
                def finish_drawing(event):
                    self._drawing_tool.clear(None)
                    return create_gate([(event.xdata, event.ydata)])

                self._drawing_tool.connect_event('button_press_event', finish_drawing)

        start_drawing(kind)

    ####################
    ### Loading Data ###
    ####################

    def load_fcs(self, filepath=None, parent=None):
        ax = self.ax

        if parent is None:
            parent = self.fig.canvas

        if filepath is None:
            from FlowCytometryTools.gui import dialogs
            filepath = dialogs.open_file_dialog('Select an FCS file to load',
                                                'FCS files (*.fcs)|*.fcs', parent=parent)

        if filepath is not None:
            self.sample = FCMeasurement('temp', datafile=filepath)
            print('WARNING: Data is raw (not transformation).')
            self._sample_loaded_event()

    def load_measurement(self, measurement):
        self.sample = measurement.copy()
        self._sample_loaded_event()

    def _sample_loaded_event(self):
        if self.sample is not None:
            self.current_channels = list(self.sample.channel_names[0:2])
            self.set_axes(self.current_channels, self.ax)

    def get_available_channels(self):
        return self.sample.channel_names

    def change_axis(self, axis_num, channel_name):
        """
        TODO: refactor that and set_axes
        what to do with ax?

        axis_num: int
           axis number
        channel_name: str
           new channel to plot on that axis
        """
        current_channels = list(self.current_channels)
        if len(current_channels) == 1:
            if axis_num == 0:
                new_channels = channel_name,
            else:
                new_channels = current_channels[0], channel_name
        else:
            new_channels = list(current_channels)
            new_channels[axis_num] = channel_name

        self.set_axes(new_channels, self.ax)

    def set_axes(self, channels, ax):
        """
        channels : iterable of string
            each value corresponds to a channel names
            names must be unique
        """
        # To make sure displayed as hist
        if len(set(channels)) == 1:
            channels = channels[0],

        self.current_channels = channels
        # Remove existing gates
        for gate in self.gates:
            gate.remove_spawned_gates()
        ## 
        # Has a clear axis command inside!!
        # which will "force kill" spawned gates
        self.plot_data()

        for gate in self.gates:
            sgate = gate.spawn(channels, ax)
            gate._refresh_activation()

    def close(self):
        for gate in self.gates:
            gate.remove()
        self.disconnect_events()

    ####################
    ### Plotting Data ##
    ####################

    def plot_data(self):
        """Plots the loaded data"""
        # Clear the plot before plotting onto it
        self.ax.cla()

        if self.sample is None:
            return

        if self.current_channels is None:
            self.current_channels = self.sample.channel_names[:2]

        channels = self.current_channels
        channels_to_plot = channels[0] if len(channels) == 1 else channels
        self.sample.plot(channels_to_plot, ax=self.ax)

        xaxis = self.ax.get_xaxis()
        yaxis = self.ax.get_yaxis()
        self.xlabel_artist = xaxis.get_label()
        self.ylabel_artist = yaxis.get_label()
        self.xlabel_artist.set_picker(5)
        self.ylabel_artist.set_picker(5)

        self.fig.canvas.draw()

    def get_generation_code(self):
        """Return python code that generates all drawn gates."""
        if len(self.gates) < 1:
            code = ''
        else:
            import_list = set([gate._gencode_gate_class for gate in self.gates])
            import_list = 'from FlowCytometryTools import ' + ', '.join(import_list)
            code_list = [gate.get_generation_code() for gate in self.gates]
            code_list.sort()
            code_list = '\n'.join(code_list)
            code = import_list + 2 * '\n' + code_list

        self.callback(Event('generated_code',
                            {'code': code}))
        return code
Exemple #8
0
class MaskEditor(ToolWindow, DoubleFileChooserDialog):
    def __init__(self, *args, **kwargs):
        self.mask = None
        self._undo_stack = []
        self._im = None
        self._selector = None
        self._cursor = None
        self.exposureloader = None
        self.plot2d = None
        ToolWindow.__init__(self, *args, **kwargs)
        DoubleFileChooserDialog.__init__(
            self, self.widget, 'Open mask file...', 'Save mask file...', [('Mask files', '*.mat'), ('All files', '*')],
            self.instrument.config['path']['directories']['mask'],
            os.path.abspath(self.instrument.config['path']['directories']['mask']),
        )

    def init_gui(self, *args, **kwargs):
        self.exposureloader = ExposureLoader(self.instrument)
        self.builder.get_object('loadexposure_expander').add(self.exposureloader)
        self.exposureloader.connect('open', self.on_loadexposure)
        self.plot2d = PlotImageWidget()
        self.builder.get_object('plotbox').pack_start(self.plot2d.widget, True, True, 0)
        self.builder.get_object('toolbar').set_sensitive(False)

    def on_loadexposure(self, exposureloader: ExposureLoader, im: Exposure):
        if self.mask is None:
            self.mask = im.mask
        self._im = im
        self.plot2d.set_image(im.intensity)
        self.plot2d.set_mask(self.mask)
        self.builder.get_object('toolbar').set_sensitive(True)

    def on_new(self, button):
        if self._im is None or self.mask is None:
            return False
        self.mask = np.ones_like(self.mask)
        self.plot2d.set_mask(self.mask)
        self.set_last_filename(None)

    def on_open(self, button):
        filename = self.get_open_filename()
        if filename is not None:
            mask = loadmat(filename)
            self.mask = mask[[k for k in mask.keys() if not k.startswith('__')][0]]
            self.plot2d.set_mask(self.mask)

    def on_save(self, button):
        filename = self.get_last_filename()
        if filename is None:
            return self.on_saveas(button)
        maskname = os.path.splitext(os.path.split(filename)[1])[0]
        savemat(filename, {maskname: self.mask})

    def on_saveas(self, button):
        filename = self.get_save_filename(None)
        if filename is not None:
            self.on_save(button)

    def suggest_filename(self):
        return 'mask_dist_{0.year:d}{0.month:02d}{0.day:02d}.mat'.format(datetime.date.today())

    def on_selectcircle_toggled(self, button):
        if button.get_active():
            self.set_sensitive(False, 'Ellipse selection not ready',
                               ['new_button', 'save_button', 'saveas_button', 'open_button', 'undo_button',
                                'selectrectangle_button', 'selectpolygon_button', 'pixelhunting_button',
                                'loadexposure_expander', 'close_button', self.plot2d.toolbar,
                                self.plot2d.settings_expander])
            while self.plot2d.toolbar.mode != '':
                # turn off zoom, pan, etc. modes.
                self.plot2d.toolbar.zoom()
            self._selector = EllipseSelector(self.plot2d.axis,
                                             self.on_ellipse_selected,
                                             rectprops={'facecolor': 'white', 'edgecolor': 'none', 'alpha': 0.7,
                                                        'fill': True, 'zorder': 10},
                                             button=[1, ],
                                             interactive=False, lineprops={'zorder': 10})
            self._selector.state.add('square')
            self._selector.state.add('center')
        else:
            assert isinstance(self._selector, EllipseSelector)
            self._selector.set_active(False)
            self._selector.set_visible(False)
            self._selector = None
            self.plot2d.replot(keepzoom=False)
            self.set_sensitive(True)

    def on_ellipse_selected(self, pos1, pos2):
        # pos1 and pos2 are mouse button press and release events, with xdata and ydata carrying
        # the two opposite corners of the bounding box of the circle. These are NOT the exact
        # button presses and releases!
        row = np.arange(self.mask.shape[0])[:, np.newaxis]
        column = np.arange(self.mask.shape[1])[np.newaxis, :]
        row0 = 0.5 * (pos1.ydata + pos2.ydata)
        col0 = 0.5 * (pos1.xdata + pos2.xdata)
        r2 = ((pos2.xdata - pos1.xdata) ** 2 + (pos2.ydata - pos1.ydata) ** 2) / 8
        tobemasked = (row - row0) ** 2 + (column - col0) ** 2 <= r2
        self._undo_stack.append(self.mask)
        if self.builder.get_object('mask_button').get_active():
            self.mask &= ~tobemasked
        elif self.builder.get_object('unmask_button').get_active():
            self.mask |= tobemasked
        elif self.builder.get_object('invertmask_button').get_active():
            self.mask[tobemasked] = ~self.mask[tobemasked]
        else:
            pass
        self.builder.get_object('selectcircle_button').set_active(False)
        self.plot2d.set_mask(self.mask)

    def on_selectrectangle_toggled(self, button):
        if button.get_active():
            self.set_sensitive(False, 'Rectangle selection not ready',
                               ['new_button', 'save_button', 'saveas_button', 'open_button', 'undo_button',
                                'selectcircle_button', 'selectpolygon_button', 'pixelhunting_button',
                                'loadexposure_expander', 'close_button', self.plot2d.toolbar,
                                self.plot2d.settings_expander])
            while self.plot2d.toolbar.mode != '':
                # turn off zoom, pan, etc. modes.
                self.plot2d.toolbar.zoom()
            self._selector = RectangleSelector(self.plot2d.axis,
                                               self.on_rectangle_selected,
                                               rectprops={'facecolor': 'white', 'edgecolor': 'none', 'alpha': 0.7,
                                                          'fill': True, 'zorder': 10},
                                               button=[1, ],
                                               interactive=False, lineprops={'zorder': 10})
        else:
            self._selector.set_active(False)
            self._selector.set_visible(False)
            self._selector = None
            self.plot2d.replot(keepzoom=False)
            self.set_sensitive(True)

    def on_rectangle_selected(self, pos1, pos2):
        # pos1 and pos2 are mouse button press and release events, with xdata and ydata
        # carrying the two opposite corners of the bounding box of the rectangle. These
        # are NOT the exact button presses and releases!
        row = np.arange(self.mask.shape[0])[:, np.newaxis]
        column = np.arange(self.mask.shape[1])[np.newaxis, :]
        tobemasked = ((row >= min(pos1.ydata, pos2.ydata)) & (row <= max(pos1.ydata, pos2.ydata)) &
                      (column >= min(pos1.xdata, pos2.xdata)) & (column <= max(pos1.xdata, pos2.xdata)))
        self._undo_stack.append(self.mask)
        if self.builder.get_object('mask_button').get_active():
            self.mask = self.mask & (~tobemasked)
        elif self.builder.get_object('unmask_button').get_active():
            self.mask = self.mask | tobemasked
        elif self.builder.get_object('invertmask_button').get_active():
            self.mask[tobemasked] = ~self.mask[tobemasked]
        else:
            pass
        self.builder.get_object('selectrectangle_button').set_active(False)
        self.plot2d.set_mask(self.mask)

    def on_selectpolygon_toggled(self, button):
        if button.get_active():
            self.set_sensitive(False, 'Polygon selection not ready',
                               ['new_button', 'save_button', 'saveas_button', 'open_button', 'undo_button',
                                'selectrectangle_button', 'selectcircle_button', 'pixelhunting_button',
                                'loadexposure_expander', 'close_button', self.plot2d.toolbar,
                                self.plot2d.settings_expander])
            while self.plot2d.toolbar.mode != '':
                # turn off zoom, pan, etc. modes.
                self.plot2d.toolbar.zoom()
            self._selector = LassoSelector(self.plot2d.axis,
                                           self.on_polygon_selected,
                                           lineprops={'color': 'white', 'zorder': 10},
                                           button=[1, ],
                                           )
        else:
            self._selector.set_active(False)
            self._selector.set_visible(False)
            self._selector = None
            self.plot2d.replot(keepzoom=False)
            self.set_sensitive(True)

    def on_polygon_selected(self, vertices):
        path = Path(vertices)
        col, row = np.meshgrid(np.arange(self.mask.shape[1]),
                               np.arange(self.mask.shape[0]))
        points = np.vstack((col.flatten(), row.flatten())).T
        tobemasked = path.contains_points(points).reshape(self.mask.shape)
        self._undo_stack.append(self.mask)
        if self.builder.get_object('mask_button').get_active():
            self.mask = self.mask & (~tobemasked)
        elif self.builder.get_object('unmask_button').get_active():
            self.mask = self.mask | tobemasked
        elif self.builder.get_object('invertmask_button').get_active():
            self.mask[tobemasked] = ~self.mask[tobemasked]
        else:
            pass
        self.plot2d.set_mask(self.mask)
        self.builder.get_object('selectpolygon_button').set_active(False)

    def on_mask_toggled(self, button):
        pass

    def on_unmask_toggled(self, button):
        pass

    def on_invertmask_toggled(self, button):
        pass

    def on_pixelhunting_toggled(self, button):
        if button.get_active():
            self._cursor = Cursor(self.plot2d.axis, useblit=False, color='white', lw=1)
            self._cursor.connect_event('button_press_event', self.on_cursorclick)
            while self.plot2d.toolbar.mode != '':
                # turn off zoom, pan, etc. modes.
                self.plot2d.toolbar.zoom()
        else:
            self._cursor.disconnect_events()
            self._cursor = None
            self._undo_stack.append(self.mask)
            self.plot2d.replot(keepzoom=False)

    def on_cursorclick(self, event):
        if (event.inaxes == self.plot2d.axis) and (self.plot2d.toolbar.mode == ''):
            self.mask[round(event.ydata), round(event.xdata)] ^= True
            self._cursor.disconnect_events()
            self._cursor = None
            self.plot2d.replot(keepzoom=True)
            self.on_pixelhunting_toggled(self.builder.get_object('pixelhunting_button'))

    def cleanup(self):
        super().cleanup()
        self._undo_stack = []

    def on_undo(self, button):
        try:
            self.mask = self._undo_stack.pop()
        except IndexError:
            return
        self.plot2d.set_mask(self.mask)
Exemple #9
0
class PolygonSelection(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a 2D polygon selection.
    
    Attributes
    ----------
    op : Instance(PolygonOp)
        The operation on which this selection view is operating
        
    huefacet : Str
        The conditioning variable to show multiple colors on this plot
        
    subset : Str
        The string for subsetting the plot

    interactive : Bool
        is this view interactive?  Ie, can the user set the polygon verticies
        with mouse clicks?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterPlotView`, but
    they must both be unset!
        
    Examples
    --------

    In an IPython notebook with `%matplotlib notebook`
    
    >>> s = flow.ScatterplotView(xchannel = "V2-A",
    ...                          ychannel = "Y2-A")
    >>> poly = s.default_view()
    >>> poly.plot(ex2)
    >>> poly.interactive = True
    """

    id = Constant('edu.mit.synbio.cytoflow.views.polygon')
    friendly_id = Constant("Polygon Selection")

    op = Instance(IOperation)
    name = DelegatesTo('op')
    xchannel = DelegatesTo('op')
    ychannel = DelegatesTo('op')
    interactive = Bool(False, transient=True)

    # internal state.
    _ax = Any(transient=True)
    _cursor = Instance(Cursor, transient=True)
    _path = Instance(mpl.path.Path, transient=True)
    _patch = Instance(mpl.patches.PathPatch, transient=True)
    _line = Instance(mpl.lines.Line2D, transient=True)
    _drawing = Bool(transient=True)
    _last_draw_time = Float(0.0, transient=True)
    _last_click_time = Float(0.0, transient=True)

    def plot(self, experiment, **kwargs):
        """Plot self.view, and then plot the selection on top of it."""

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if self.xfacet:
            raise util.CytoflowViewError(
                "RangeSelection.xfacet must be empty or `Undefined`")

        if self.yfacet:
            raise util.CytoflowViewError(
                "RangeSelection.yfacet must be empty or `Undefined`")

        super(PolygonSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_poly()
        self._interactive()

    @on_trait_change('op.vertices', post_init=True)
    def _draw_poly(self):
        if not self._ax:
            return

        if self._patch and self._patch in self._ax.patches:
            self._patch.remove()

        if self._drawing or not self.op.vertices or len(self.op.vertices) < 3 \
                         or any([len(x) != 2 for x in self.op.vertices]):
            return

        patch_vert = np.concatenate(
            (np.array(self.op.vertices), np.array((0, 0), ndmin=2)))

        self._patch = \
            mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True),
                                  edgecolor="black",
                                  linewidth = 1.5,
                                  fill = False)

        self._ax.add_patch(self._patch)
        plt.draw_if_interactive()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax, horizOn=False, vertOn=False)
            self._cursor.connect_event('button_press_event', self._onclick)
            self._cursor.connect_event('motion_notify_event', self._onmove)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None

    def _onclick(self, event):
        """Update selection traits"""
        if not self._ax:
            return

        if (self._cursor.ignore(event)):
            return

        # we have to check the wall clock time because the IPython notebook
        # doesn't seem to register double-clicks
        if event.dblclick or (time.clock() - self._last_click_time < 0.5):
            self._drawing = False
            self.op.vertices = map(tuple, self._path.vertices)
            self.op._xscale = plt.gca().get_xscale()
            self.op._yscale = plt.gca().get_yscale()
            self._path = None
            return

        self._last_click_time = time.clock()

        self._drawing = True
        if self._patch and self._patch in self._ax.patches:
            self._patch.remove()

        if self._path:
            vertices = np.concatenate((self._path.vertices,
                                       np.array((event.xdata, event.ydata),
                                                ndmin=2)))
        else:
            vertices = np.array((event.xdata, event.ydata), ndmin=2)

        self._path = mpl.path.Path(vertices, closed=False)
        self._patch = mpl.patches.PathPatch(self._path,
                                            edgecolor="black",
                                            fill=False)

        self._ax.add_patch(self._patch)
        plt.draw_if_interactive()

    def _onmove(self, event):

        if not self._ax:
            return

        if (self._cursor.ignore(event) or not self._drawing or not self._path
                or self._path.vertices.shape[0] == 0 or not event.xdata
                or not event.ydata):
            return

        # only draw 5 times/sec
        if (time.clock() - self._last_draw_time < 0.2):
            return

        self._last_draw_time = time.clock()

        if self._line and self._line in self._ax.lines:
            self._line.remove()

        xdata = [self._path.vertices[-1, 0], event.xdata]
        ydata = [self._path.vertices[-1, 1], event.ydata]
        self._line = mpl.lines.Line2D(xdata, ydata, linewidth=1, color="black")

        self._ax.add_line(self._line)
        plt.gcf().canvas.draw()
Exemple #10
0
class QuadSelection(Op2DView, ScatterplotView):
    """Plots, and lets the user interact with, a quadrant gate.
    
    Attributes
    ----------
    interactive : Bool
        is this view interactive?  Ie, can the user set the threshold with a 
        mouse click?
        
    Notes
    -----
    We inherit :attr:`xfacet` and :attr:`yfacet` from 
    :class:`cytoflow.views.ScatterplotView`, but they must both be unset!
        
    Examples
    --------
    
    In an Jupyter notebook with `%matplotlib notebook`
    
    >>> q = flow.QuadOp(name = "Quad",
    ...                 xchannel = "V2-A",
    ...                 ychannel = "Y2-A"))
    >>> qv = q.default_view()
    >>> qv.interactive = True
    >>> qv.plot(ex2) 
    """

    id = Constant('edu.mit.synbio.cytoflow.views.quad')
    friendly_id = Constant("Quadrant Selection")

    xfacet = Constant(None)
    yfacet = Constant(None)

    # override the Op2DView
    xscale = util.ScaleEnum
    yscale = util.ScaleEnum

    xthreshold = DelegatesTo('op')
    ythreshold = DelegatesTo('op')

    interactive = Bool(False, transient=True)

    # internal state.
    _ax = Any(transient=True)
    _hline = Instance(Line2D, transient=True)
    _vline = Instance(Line2D, transient=True)
    _cursor = Instance(Cursor, transient=True)

    def plot(self, experiment, **kwargs):
        """
        Plot the underlying scatterplot and then plot the selection on top of it.
        
        Parameters
        ----------
        
        """

        if experiment is None:
            raise util.CytoflowViewError('experiment',
                                         "No experiment specified")

        super().plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_lines()
        self._interactive()

    @on_trait_change('xthreshold, ythreshold', post_init=True)
    def _draw_lines(self):
        if not self._ax:
            return

        if self._hline and self._hline in self._ax.lines:
            self._hline.remove()

        if self._vline and self._vline in self._ax.lines:
            self._vline.remove()

        if self.xthreshold and self.ythreshold:
            self._hline = plt.axhline(self.ythreshold,
                                      linewidth=3,
                                      color='blue')
            self._vline = plt.axvline(self.xthreshold,
                                      linewidth=3,
                                      color='blue')

            plt.draw()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax,
                                  horizOn=True,
                                  vertOn=True,
                                  color='blue',
                                  useblit=True)
            self._cursor.connect_event('button_press_event', self._onclick)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None

    def _onclick(self, event):
        """Update the threshold location"""
        self.xthreshold = event.xdata
        self.ythreshold = event.ydata
Exemple #11
0
class PolygonSelection(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a 2D polygon selection.
    
    Attributes
    ----------
    op : Instance(PolygonOp)
        The operation on which this selection view is operating
        
    huefacet : Str
        The conditioning variable to show multiple colors on this plot
        
    subset : Str
        The string for subsetting the plot

    interactive : Bool
        is this view interactive?  Ie, can the user set the polygon verticies
        with mouse clicks?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterPlotView`, but
    they must both be unset!
        
    Examples
    --------

    In an IPython notebook with `%matplotlib notebook`
    
    >>> s = flow.ScatterplotView(xchannel = "V2-A",
    ...                          ychannel = "Y2-A")
    >>> poly = s.default_view()
    >>> poly.plot(ex2)
    >>> poly.interactive = True
    """
    
    id = Constant('edu.mit.synbio.cytoflow.views.polygon')
    friendly_id = Constant("Polygon Selection")
    
    op = Instance(IOperation)
    name = DelegatesTo('op')
    xchannel = DelegatesTo('op')
    ychannel = DelegatesTo('op')
    interactive = Bool(False, transient = True)

    # internal state.
    _ax = Any(transient = True)
    _cursor = Instance(Cursor, transient = True)
    _path = Instance(mpl.path.Path, transient = True)
    _patch = Instance(mpl.patches.PathPatch, transient = True)
    _line = Instance(mpl.lines.Line2D, transient = True)
    _drawing = Bool(transient = True)
    _last_draw_time = Float(0.0, transient = True)
    _last_click_time = Float(0.0, transient = True)
        
    def plot(self, experiment, **kwargs):
        """Plot self.view, and then plot the selection on top of it."""
        
        if not experiment:
            raise util.CytoflowViewError("No experiment specified")
        
        if self.xfacet:
            raise util.CytoflowViewError("RangeSelection.xfacet must be empty or `Undefined`")
        
        if self.yfacet:
            raise util.CytoflowViewError("RangeSelection.yfacet must be empty or `Undefined`")
        
        super(PolygonSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_poly()
        self._interactive()
    
    @on_trait_change('op.vertices', post_init = True)
    def _draw_poly(self):
        if not self._ax:
            return
         
        if self._patch and self._patch in self._ax.patches:
            self._patch.remove()
            
        if self._drawing or not self.op.vertices or len(self.op.vertices) < 3 \
                         or any([len(x) != 2 for x in self.op.vertices]):
            return
             
        patch_vert = np.concatenate((np.array(self.op.vertices), 
                                    np.array((0,0), ndmin = 2)))
                                    
        self._patch = \
            mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True),
                                  edgecolor="black",
                                  linewidth = 1.5,
                                  fill = False)
            
        self._ax.add_patch(self._patch)
        plt.draw_if_interactive()
    
    @on_trait_change('interactive', post_init = True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax, horizOn = False, vertOn = False)            
            self._cursor.connect_event('button_press_event', self._onclick)
            self._cursor.connect_event('motion_notify_event', self._onmove)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None       
    
    def _onclick(self, event): 
        """Update selection traits"""      
        if not self._ax:
            return
        
        if(self._cursor.ignore(event)):
            return
        
        # we have to check the wall clock time because the IPython notebook
        # doesn't seem to register double-clicks
        if event.dblclick or (time.clock() - self._last_click_time < 0.5):
            self._drawing = False
            self.op.vertices = map(tuple, self._path.vertices)
            self.op._xscale = plt.gca().get_xscale()
            self.op._yscale = plt.gca().get_yscale()
            self._path = None
            return
        
        self._last_click_time = time.clock()
                
        self._drawing = True
        if self._patch and self._patch in self._ax.patches:
            self._patch.remove()
            
        if self._path:
            vertices = np.concatenate((self._path.vertices,
                                      np.array((event.xdata, event.ydata), ndmin = 2)))
        else:
            vertices = np.array((event.xdata, event.ydata), ndmin = 2)

        self._path = mpl.path.Path(vertices, closed = False)
        self._patch = mpl.patches.PathPatch(self._path, 
                                            edgecolor = "black",
                                            fill = False)

        self._ax.add_patch(self._patch)
        plt.draw_if_interactive()
        
    def _onmove(self, event):       
         
        if not self._ax:
            return
         
        if(self._cursor.ignore(event) 
           or not self._drawing
           or not self._path
           or self._path.vertices.shape[0] == 0
           or not event.xdata
           or not event.ydata):
            return

        # only draw 5 times/sec
        if(time.clock() - self._last_draw_time < 0.2):
            return
        
        self._last_draw_time = time.clock()
         
        if self._line and self._line in self._ax.lines:
            self._line.remove()
            
        xdata = [self._path.vertices[-1, 0], event.xdata]
        ydata = [self._path.vertices[-1, 1], event.ydata]
        self._line = mpl.lines.Line2D(xdata, ydata, linewidth = 1, color = "black")
        
        self._ax.add_line(self._line)
        plt.gcf().canvas.draw()
Exemple #12
0
class QuadSelection(cytoflow.views.ScatterplotView):
    """Plots, and lets the user interact with, a quadrant gate.
    
    Attributes
    ----------
    op : Instance(Range2DOp)
        The instance of Range2DOp that we're viewing / editing
        
    huefacet : Str
        The conditioning variable to plot multiple colors
        
    subset : Str
        The string passed to `Experiment.query()` to subset the data before
        plotting
        
    interactive : Bool
        is this view interactive?  Ie, can the user set the threshold with a 
        mouse click?
        
    Notes
    -----
    We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterplotView`, but
    they must both be unset!
        
    Examples
    --------
    
    In an IPython notebook with `%matplotlib notebook`
    
    >>> q = flow.QuadOp(name = "Quad",
    ...                 xchannel = "V2-A",
    ...                 ychannel = "Y2-A"))
    >>> qv = q.default_view()
    >>> qv.interactive = True
    >>> qv.plot(ex2) 
    """

    id = Constant('edu.mit.synbio.cytoflow.views.quad')
    friendly_id = Constant("Quadrant Selection")

    op = Instance(IOperation)
    name = DelegatesTo('op')
    xchannel = DelegatesTo('op')
    ychannel = DelegatesTo('op')
    interactive = Bool(False, transient=True)

    # internal state.
    _ax = Any(transient=True)
    _hline = Instance(Line2D, transient=True)
    _vline = Instance(Line2D, transient=True)
    _cursor = Instance(Cursor, transient=True)

    def plot(self, experiment, **kwargs):
        """Plot the underlying scatterplot and then plot the selection on top of it."""

        if not experiment:
            raise util.CytoflowOpError("No experiment specified")

        if not experiment:
            raise util.CytoflowViewError("No experiment specified")

        if self.xfacet:
            raise util.CytoflowViewError(
                "RangeSelection.xfacet must be empty or `Undefined`")

        if self.yfacet:
            raise util.CytoflowViewError(
                "RangeSelection.yfacet must be empty or `Undefined`")

        super(QuadSelection, self).plot(experiment, **kwargs)
        self._ax = plt.gca()
        self._draw_lines()
        self._interactive()

    @on_trait_change('op.xthreshold, op.ythreshold', post_init=True)
    def _draw_lines(self):
        if not self._ax:
            return

        if self._hline and self._hline in self._ax.lines:
            self._hline.remove()

        if self._vline and self._vline in self._ax.lines:
            self._vline.remove()

        if self.op.xthreshold and self.op.ythreshold:
            self._hline = plt.axhline(self.op.ythreshold,
                                      linewidth=3,
                                      color='blue')
            self._vline = plt.axvline(self.op.xthreshold,
                                      linewidth=3,
                                      color='blue')

            plt.draw_if_interactive()

    @on_trait_change('interactive', post_init=True)
    def _interactive(self):
        if self._ax and self.interactive:
            self._cursor = Cursor(self._ax,
                                  horizOn=True,
                                  vertOn=True,
                                  color='blue')
            self._cursor.connect_event('button_press_event', self._onclick)
        elif self._cursor:
            self._cursor.disconnect_events()
            self._cursor = None

    def _onclick(self, event):
        """Update the threshold location"""
        self.op.xthreshold = event.xdata
        self.op.ythreshold = event.ydata
Exemple #13
0
class ECE4012:
    def __init__(self, figure, filename, toolbar):
        self.dfCal = None
        self.fig = figure
        self.toolbar = toolbar
        self.filename = filename
        self.rect = None

    def initializer(self, type):
        self.fig.clear()
        print("celar figure")
        self.fig.subplots_adjust(hspace=1.0)
        self.annoteText = "annotated"
        #self.fig = figure
        #self.toolbar = toolbar
        self.isAnnotate = False
        self.fig.canvas.mpl_connect('button_release_event', self.onrelease)
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)
        self.fig.canvas.mpl_connect('key_press_event', self.onpress)
        self.fig.canvas.mpl_connect('pick_event', self.onpick)
        # print(self.fig)
        if type == "DB":
            self.searchOpenCalibration(self.filename)
            self.conn = self.getConnection(self.filename)
            # df = pd.read_sql_query("select * from  acc LIMIT 100000;", self.conn)
            t = time.time()
            df = pd.read_sql_query("select * from  acc;", self.conn)
            elapsed = time.time() - t
            print("time taken to read data was {:f}".format(elapsed))
        elif type == "CSV":
            print("CSV getting Calibration")
            self.searchOpenCalibration(self.filename)
            print("CSV opening file")
            df = pd.read_csv(self.filename)
            print("CSV opened file")

        print("Starting")

        if self.dfCal is not None:
            df["valuex"] = np.subtract(df["valuex"], self.dfCal['Values'][
                self.XOffset]) / self.dfCal['Values'][self.XGain]
            df["valuey"] = np.subtract(df["valuey"], self.dfCal['Values'][
                self.YOffset]) / self.dfCal['Values'][self.YGain]
            df["valuez"] = np.subtract(df["valuez"], self.dfCal['Values'][
                self.ZOffset]) / self.dfCal['Values'][self.ZGain]

        # DONE !!TODO this calibartion should change to either actual calibration from
        # metawear C
        # print(len(df["valuex"]))
        # avgX = np.sum(df["valuex"]) / len(df["valuex"])
        # avgY = np.sum(df["valuey"]) / len(df["valuey"])
        # avgZ = np.sum(df["valuez"]) / len(df["valuez"])
        # df["valuex"] = np.subtract(df["valuex"], avgX)
        # df["valuey"] = np.subtract(df["valuey"], avgY)
        # df["valuez"] = np.subtract(df["valuez"], avgZ)
        #self.axTop = self.fig.add_subplot(2, 1, 1)
        #self.axBottom = self.fig.add_subplot(2, 1, 2)
        self.fig.subplots_adjust(bottom=0.3, left=0.2, hspace=1.0)
        self.labels = ['30 sec', '5 min', '30 min', '1 hr', '2 hr']
        sec30 = pd.Timedelta(30, unit='s')
        min5 = pd.Timedelta(5, unit='m')
        min30 = pd.Timedelta(30, unit='m')
        hr1 = pd.Timedelta(1, unit='h')
        hr2 = pd.Timedelta(2, unit='h')

        # Scale Radio Buttons for Zoom
        self.scaleDeltaArray = [sec30, min5, min30, hr1, hr2]
        self.rax = self.fig.add_axes([0.02, 0.7, 0.11, 0.15])
        self.rax.set_title('Scale')
        self.check = RadioButtons(self.rax, self.labels)
        self.fig.canvas.mpl_connect('button_press_event', self.onclick)

        # Data Set Selection Check Box
        self.rcbax = self.fig.add_axes([0.02, 0.25, 0.11, 0.15])
        self.rcbax.set_title('DataSet')
        self.checkBox = CheckButtons(self.rcbax,
                                     ('valuex', 'valuey', 'valuez', 'mag'),
                                     (True, True, True, True))
        self.checkBox.on_clicked(self.oncheck)
        xs = np.linspace(15, 21, 200)
        horiz_line_dataz = np.array([0.41 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_dataz, 'C3-')
        horiz_line_datam = np.array([0.26 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datam, 'C0-')
        horiz_line_datay = np.array([0.58 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datay, 'C2-')
        horiz_line_datax = np.array([0.73 for i in range(len(xs))])
        self.rcbax.plot(xs, horiz_line_datax, 'C1-')

        self.check.on_clicked(self.changeScale)
        df["mag"] = np.sqrt(np.square(df["valuex"]) + np.square(df['valuey']) + \
                            np.square(df["valuez"]))-1.0
        print("Normalize")
        ya_min = df.loc[:, "mag"].min() - 0.1
        ya_max = df.loc[:, "mag"].max() + 0.1
        print("y limit (max={:f},min={:f})".format(ya_max, ya_min))
        # print("Normalized")
        # print(type(ya_min))
        # print(ya_max)

        # print(df["mag"])
        # df["mag"] = df["mag"] - 1 # subtract 1g

        #Convert epoch to datetime to see the right value
        print("Convert Date Time")
        print(df["epoch"].iloc[0])
        print(df["epoch"].iloc[1])
        if type == "DB":
            print("epoch type", df.dtypes[0])
            df['epoch'] = pd.to_datetime(df['epoch'], unit='ms')
        elif type == "CSV":
            print("epoch type", df.dtypes[0])
            if 'annotated' not in df.columns:
                df['epoch'] = pd.to_datetime(df['epoch'], unit='ms')
            else:
                df['epoch'] = pd.to_datetime(df['epoch'])
        print("epoch type after", df.dtypes[0])

        # Adjusting epoch time to be on Eastern Time Zone
        df['epoch'] = df['epoch'].dt.tz_localize('UTC').dt.tz_convert(
            'America/New_York')
        vepoch = df['epoch']
        self.datevalues = vepoch.dt.to_pydatetime()

        if 'annotated' not in df.columns:
            charArr = np.chararray((len(df["mag"], )), unicode=True)
            charArr[:] = ' '
            df["annotated"] = pd.DataFrame(charArr)
            df["colorHex"] = pd.DataFrame(charArr)

        # print(df["epoch"])
        # print(type(df['epoch'][0]))
        #Definition of x-axis formatter of tick
        # self.hoursFmt = mdates.DateFormatter('%Y-%m-%d %H:%M:%S') #Week Month/Day H:M:S
        self.hoursFmt = mdates.DateFormatter('%a-%m-%d %H:%M:%S')
        self.HMSFmt = mdates.DateFormatter('%H:%M:%S')
        #Get total time of the data in hours
        startDate = df["epoch"].iloc[0]
        self.startDate = startDate
        endDate = df["epoch"].iloc[-1]
        totaltime = int(endDate.timestamp() * 1000) - \
                    int(startDate.timestamp() * 1000)
        total_hours = totaltime / 1000 / 3600  #totaltime in hours
        total_minutes = total_hours * 60
        print("total hours {:f}".format(total_hours))

        #create variable to track drag time
        self.timeTrack = 0
        #create variable to track initial click xpos
        self.xpos = 0
        self.df = df

        print("Creating Top Graph")

        self.ax = self.fig.add_subplot(2, 1, 1)
        # second axis
        print("Creating Bottom Graph")
        self.ax2 = self.fig.add_subplot(2, 1, 2)
        #adjust distance between toolbar and graph to give more space
        self.fig.subplots_adjust(bottom=0.145)

        #adjust distance between toolbar and graph if data is 24 hours or more
        if total_hours > 24:
            self.fig.subplots_adjust(hspace=1.3)
        print("Start Plot Setting")

        # Set default values for x aixs
        ##changed temporary pls ignore
        # self.ax.set_ylim(1, 1.1)
        # self.ax2.set_ylim(1, 1.1)
        #self.ax.set_ylim(0, 8)
        #self.ax2.set_ylim(0, 8)
        self.ax.set_xlabel("Time (H:M:S)")
        self.ax2.set_xlabel("Time (H:M:S)")
        self.ax.set_ylabel("Acceleration (g)")
        self.ax2.set_ylabel("Acceleration (g)")
        #self.ax.set_title('Scale')
        self.ax.set_ylim(ya_min, ya_max)
        self.ax2.set_ylim(ya_min, ya_max)
        bottomtitle = 'Magnitude Graph for Start Date ' + "{:%Y-%m-%d}".format(
            startDate)
        if self.dfCal is not None:
            bottomtitle = 'Calibrated ' + bottomtitle
        self.ax.set_title(bottomtitle)

        toptitle = 'Zoomed-In Graph for Start Date ' + "{:%Y-%m-%d}".format(
            startDate)
        if self.dfCal is not None:
            toptitle = 'Calibrated ' + toptitle
        self.ax2.set_title(toptitle)

        # self.ya_min = ya_min
        # self.ya_max = ya_max
        # start date
        # print(type(df["epoch"]))
        # print(len(df["epoch"]))
        # print(df["epoch"])
        # print()
        # TODO we assume time is not empty but it could be good to chek empty
        start_time = df["epoch"].iloc[0]
        print("start time={v}".format(v=start_time))
        # print(type(start_time))
        if total_hours > 1:
            end_time = start_time + hr1
        elif total_minutes > 30:
            end_time = start_time + min30
        elif total_minutes > 5:
            end_time = start_time + min5
        else:
            end_time = start_time + sec30
        print("end time={v}".format(v=end_time))
        # print(type(start_time))
        #end_time = start_time + pd.Timedelta(hours=1)
        #print(end_time)
        self.startX = start_time
        self.endX = end_time

        # set axis 2
        self.ax2.set_xlim(start_time, end_time)
        print("Set Limit")
        # print(type(df["epoch"].iloc[0]))
        mask = (df["epoch"] >= start_time) & (df["epoch"] <= end_time)
        print("Plotting")
        # print(mask)
        # print(df.loc[mask, "epoch"])
        # plot
        t = time.time()
        self.plotTop, = self.ax.plot(df["epoch"].iloc[0:3],
                                     df["mag"].iloc[0:3],
                                     visible=True)
        self.plotTop.set_data(self.datevalues, df["mag"])
        self.ax.relim()
        self.ax.autoscale_view(True, True, True)
        self.fig.canvas.draw()

        #self.ax.plot(df["epoch"], df["mag"])
        elapsed = time.time() - t
        print("time taken to top graph magnitud was {:f}".format(elapsed))
        # print(df.loc[mask, "epoch"])
        print(len(df.loc[mask, "epoch"]))
        t = time.time()
        self.lm, = self.ax2.plot(df["epoch"].iloc[0:3],
                                 df["mag"].iloc[0:3],
                                 visible=True)
        self.lm.set_data(self.datevalues, df["mag"])
        self.ax2.relim()
        self.ax2.autoscale_view(True, True, True)
        self.fig.canvas.draw()
        #self.lm,  = self.ax2.plot(df["epoch"], df["mag"],visible=True)
        elapsed = time.time() - t
        print("time taken to bottom graph magnitud was {:f}".format(elapsed))
        t = time.time()
        try:
            thread1 = myThread(1, "valuex", self)
            thread1.start()
        except:
            print("Error: unable to start thread")
        #self.lx,  = self.ax2.plot(df["epoch"], df["valuex"],visible=False)
        elapsed = time.time() - t
        print("time taken to bottom graph valuex was {:f}".format(elapsed))
        t = time.time()
        try:
            thread2 = myThread(2, "valuey", self)
            thread2.start()
        except:
            print("Error: unable to start thread")
        #self.ly,  = self.ax2.plot(df["epoch"], df["valuey"],visible=False)
        elapsed = time.time() - t
        print("time taken to bottom graph valuey was {:f}".format(elapsed))
        t = time.time()
        try:
            thread3 = myThread(3, "valuez", self)
            thread3.start()
        except:
            print("Error: unable to start thread")
        #self.lz,  = self.ax2.plot(df["epoch"], df["valuez"],visible=False)
        elapsed = time.time() - t
        print("time taken to bottom graph valuez was {:f}".format(elapsed))
        # x in the range
        tX = df.loc[mask, "epoch"]
        self.ax2.set_xlim(tX.iloc[0], tX.iloc[len(tX) - 1])
        # self.ax.xaxis.set_major_formatter(self.hoursFmt)
        self.ax.xaxis.set_major_formatter(self.HMSFmt)
        self.ax.get_xaxis().set_major_locator(LinearLocator(numticks=12))
        #setup format of ticks to Weekday month/day if data is 24 hours or more
        if total_hours > 24:
            self.ax.xaxis.set_major_formatter(self.hoursFmt)

        #Rotate labels on x-axis 45 degrees and set font size to 8
        self.ax.tick_params(axis='x', labelsize=8, rotation=45)
        # self.ax.title("Magnitude")
        # self.ax2.plot(df["epoch"], df["valuex"],
        #               color=self.colorChoose(255, 0, 255))
        # self.ax2.plot(df["epoch"], df["valuey"],
        #               color=self.colorChoose(255, 0, 0))
        # self.ax2.plot(df["epoch"], df["valuez"],
        #               color=self.colorChoose(255, 255, 0))
        self.color = self.colorChoose(random.randint(0, 255),
                                      random.randint(0, 255),
                                      random.randint(0, 255))
        self.removedObj = None
        # self.ax.add_patch(rect)
        self.fig.canvas.draw()
        self.fig.canvas.mpl_connect('motion_notify_event', self.onmouseover)
        self.cursor = Cursor(self.ax,
                             useblit=True,
                             horizOn=False,
                             color='red',
                             linewidth=2)
        self.cursor.connect_event('button_press_event', self.cursorOnclick)
        self.currentDelta = pd.Timedelta(30, unit='s')
        self.ax2.xaxis.set_major_formatter(self.HMSFmt)
        self.ax2.get_xaxis().set_major_locator(LinearLocator(numticks=7))

        #data text axes
        #self.txtax = self.fig.add_axes([0.75, 0.85, 0.11, 0.05])
        #self.txtax.axis('off')

        self.ax.format_coord = self.format_coord
        if type == "CSV" and 'annotated' in df.columns:
            print("calling searchForAnnotation")
            self.searchForAnnotation()

        #self.txt = self.ax.text(0.8, 1.05, 'X,Y,Z Values', transform=self.ax.transAxes)
        #self.txt = self.txtax.text(0.05, 0.05, 'X,Y,Z Values', transform=self.txtax.transAxes)

    def format_coord(self, x, y):
        dateclickOn = mdates.num2date(x)
        idx = self.df['epoch'].searchsorted(dateclickOn)
        idx = idx - 1
        #logger.debug("dateclickOn:%s idx=%s", dateclickOn,idx)
        vx = self.df['valuex'].values[idx][0]
        vy = self.df['valuey'].values[idx][0]
        vz = self.df['valuez'].values[idx][0]
        vm = self.df['mag'].values[idx][0]
        #print("d: {:%H:%M:%S} vx:{:1.5f} vy:{:1.5f} vz:{:1.5f}".format(dateclickOn,vx,vy,vz))
        #self.txt.set_text('%H:%M:%S x=%1.3f, y=%1.3f, z=%1.3f' % (dateclickOn,vx, vy,vz))
        return "{:%H:%M:%S} M:{:1.3f}".format(dateclickOn, vm)

    def readCalibration(self, filename):
        self.dfCal = pd.read_csv(filename)
        print('Results: %s', self.dfCal)
        print('title: %s', self.dfCal['Titles'])
        print('values: %s', self.dfCal['Values'])
        print('ZGain: %s', self.dfCal['Values'][1])
        self.ZOffset = 0
        self.ZGain = 1
        self.YOffset = 2
        self.YGain = 3
        self.XOffset = 4
        self.XGain = 5

    def redrawAfterCalibrate(self):

        print("Computing new data")

        if self.dfCal is not None:
            self.df["valuex"] = np.subtract(
                self.df["valuex"], self.dfCal['Values'][
                    self.XOffset]) / self.dfCal['Values'][self.XGain]
            self.df["valuey"] = np.subtract(
                self.df["valuey"], self.dfCal['Values'][
                    self.YOffset]) / self.dfCal['Values'][self.YGain]
            self.df["valuez"] = np.subtract(
                self.df["valuez"], self.dfCal['Values'][
                    self.ZOffset]) / self.dfCal['Values'][self.ZGain]
        print("Computing new mag")

        self.df["mag"] = np.sqrt(np.square(self.df["valuex"]) + np.square(self.df['valuey']) + \
                            np.square(self.df["valuez"]))-1.0
        print("setting titles")
        bottomtitle = 'Magnitude Graph for Start Date ' + "{:%Y-%m-%d}".format(
            self.startDate)
        if self.dfCal is not None:
            bottomtitle = 'Calibrated ' + bottomtitle
        self.ax.set_title(bottomtitle)

        toptitle = 'Zoomed-In Graph for Start Date ' + "{:%Y-%m-%d}".format(
            self.startDate)
        if self.dfCal is not None:
            toptitle = 'Calibrated ' + toptitle
        self.ax2.set_title(toptitle)

        print("redraw graph")

        self.plotTop.set_ydata(self.df["mag"])
        self.lm.set_ydata(self.df["mag"])
        self.lx.set_ydata(self.df["valuex"])
        self.ly.set_ydata(self.df["valuey"])
        self.lz.set_ydata(self.df["valuez"])
        self.fig.canvas.draw()

    def onmouseover(self, event):

        if event.inaxes == self.rax:
            None
            #logger.debug("click on radiobutton")
            #self.check._clicked(event)
        if event.inaxes == self.ax:
            self.cursor.onmove(event)

    def searchOpenCalibration(self, filename):
        directory = os.path.dirname(filename)
        print("directory=" + directory)
        tosearch = re.compile('^calibrationResults.*')
        for sChild in os.listdir(directory):
            print("file=" + sChild)
            if tosearch.match(sChild) is not None:
                if directory == "":
                    directory = "."
                calibfilename = directory + "/" + sChild
                print("calibfilename=" + calibfilename)
                self.readCalibration(calibfilename)
                break

    def searchForAnnotation(self):
        colorKick = self.colorChoose(255, 0, 0)
        colorSleep = self.colorChoose(0, 0, 255)
        colorRandom = self.colorChoose(0, 255, 0)
        randomColor = self.color

        maskKick = self.df['annotated'] == "Kick"
        maskSleep = self.df['annotated'] == "Sleep"
        maskRandom = self.df['annotated'] == "Random"
        maskColorRandom = self.df['annotated'] == "annotated"

        print("masked found")

        if len(self.df.loc[maskKick, 'epoch']) > 0:
            self.reDrawAnnotation(maskKick, colorKick)
        if len(self.df.loc[maskSleep, 'epoch']) > 0:
            self.reDrawAnnotation(maskSleep, colorSleep)
        if len(self.df.loc[maskRandom, 'epoch']) > 0:
            self.reDrawAnnotation(maskRandom, colorRandom)
        if len(self.df.loc[maskColorRandom, 'epoch']) > 0:
            self.reDrawAnnotation(maskColorRandom, randomColor)

    def reDrawAnnotation(self, mask, color):

        [ymin, ymax] = self.ax.get_ylim()
        height = ymax - ymin

        positionKick = np.array(self.df.index[mask].tolist())

        diff = np.subtract(positionKick[1:-1], positionKick[0:-2])

        maskcuts = diff > 1
        posInCuts = np.where(maskcuts)
        arrposcut = posInCuts[0]
        arrpostcut1 = arrposcut + 1

        xmin = self.df.loc[mask, 'epoch'].iloc[0]
        xmax = self.df.loc[mask, 'epoch'].iloc[-1]
        xmin = xmin.to_pydatetime()
        xmax = xmax.to_pydatetime()
        #xmin=int(xmin.timestamp() * 1000)/1000
        #xmax=int(xmax.timestamp() * 1000)/1000
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))
        xmin = mdates.date2num(xmin)
        xmax = mdates.date2num(xmax)
        print("len(posInCuts)", len(posInCuts[0]))
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))
        for idx in range(len(posInCuts[0])):
            print("idx=", idx)
            print("xmin={v1} xmax={v2}".format(
                v1=xmin,
                v2=self.df['epoch'].iloc[positionKick[arrposcut[idx]]]))

            rectmin = xmin
            rectmax = self.df['epoch'].iloc[positionKick[arrposcut[idx]]]
            rectmax = rectmax.to_pydatetime()
            rectmax = mdates.date2num(rectmax)
            #rectmax=int(rectmax.timestamp() * 1000)/1000
            #rectmax=mdates.num2date(rectmax)
            width = rectmax - rectmin
            rect = ECERectangle(rectmin,
                                ymin,
                                width,
                                height,
                                color=color,
                                index=1)
            rect2 = ECERectangle(xmin,
                                 ymin,
                                 width,
                                 height,
                                 color=color,
                                 index=2)
            rect.setNext(rect2)
            rect2.setPrev(rect)
            self.ax.add_patch(rect)
            self.ax2.add_patch(rect2)
            self.fig.canvas.draw()
            xmin = self.df['epoch'].iloc[positionKick[arrpostcut1[idx]]]
            xmin = xmin.to_pydatetime()
            xmin = mdates.date2num(xmin)
            #xmin=int(xmin.timestamp() * 1000)/1000
            #xmin=mdates.num2date(xmin)

        rectmin = xmin
        rectmax = xmax
        #rectmax=int(rectmax.timestamp() * 1000)/1000
        width = rectmax - rectmin
        rect = ECERectangle(rectmin, ymin, width, height, color=color, index=1)
        rect2 = ECERectangle(xmin, ymin, width, height, color=color, index=2)
        rect.setNext(rect2)
        rect2.setPrev(rect)
        self.ax.add_patch(rect)
        self.ax2.add_patch(rect2)
        print("xmin={v1} xmax={v2}".format(v1=xmin, v2=xmax))
        self.fig.canvas.draw()

    def getConnection(self, filename):
        """
        get the connection of sqlite given file name
        :param filename:
        :return: sqlite3 connection
        """

        conn = sqlite3.connect(filename)
        return conn

    def readCalibration(self, filename):
        self.dfCal = pd.read_csv(filename)
        print('Results: %s', self.dfCal)
        print('title: %s', self.dfCal['Titles'])
        print('values: %s', self.dfCal['Values'])
        print('ZGain: %s', self.dfCal['Values'][1])
        self.ZOffset = 0
        self.ZGain = 1
        self.YOffset = 2
        self.YGain = 3
        self.XOffset = 4
        self.XGain = 5

    def createCalibrationAction(self):
        image_file = 'square-upload'
        callback = self.toolbar.importCal
        text = "Calibrate"
        tooltip_text = "Calibrate"
        a = self.toolbar.addAction(self.toolbar._icon(image_file + '.png'),
                                   text, callback)
        print("action created")
        self.toolbar._actions[callback] = a
        print("action added to array")

        a.setToolTip(tooltip_text)

    def executeQuery(self, query):
        return None

    def colorChoose(self, r, g, b):
        """
        create a normalized color map give r, g, b in 0-255 scale
        :param r: red
        :param g: green
        :param b: blue
        :return: tuple of color that can be used for matplotlib
        """
        if self.rangeOff(r) or self.rangeOff(g) or self.rangeOff(r):
            return (0, 0, 0)
        return r / 255.0, g / 255.0, b / 255.0

    def rangeOff(self, val):
        return (val > 255) or (val < 0)

    def onpick(self, event):
        print("pick event")
        if isinstance(event.artist, Rectangle):
            patch = event.artist
            # patch.remove()
            self.removedObj = patch
            print('onpick1 patch:', patch.get_path())
        else:
            print(event.artist.get_xdata())
            print(event.artist.get_xdata())
            print(len(event.artist.get_ydata()))
            # self.fig.canvas.draw()

    # def onselect(self, xmin, xmax):
    #     print(xmin)
    #     print(xmax)
    #     width = xmax - xmin
    #     [ymin, ymax] = self.ax.get_ylim()
    #     height = ymax - ymin
    #
    #     rect = matpat.Rectangle((xmin, ymin), width, height,
    #                             fill=True, alpha=0.4,
    #                             color=self.color)
    #     self.ax.add_patch(rect)

    def onclick(self, event):
        if not self.toolbar._actions['zoom'].isChecked(
        ) and event.button == 1:  # left = 1, middle = 2, right = 3
            self.timeTrack = time.time()
            print("clicked {}".format(self.timeTrack))
            print("clicked X: {}".format(event.xdata))
            self.xpos = event.xdata
            # [ymin, ymax] = self.ax.get_ylim()
            # self.ax.annotate('sth', xy=(event.xdata, event.ydata),
            #                  xytext=(event.xdata, ymax),
            #                  arrowprops=dict(facecolor='black', shrink=0.05),)
            # self.fig.canvas.draw()

    def onrelease(self, event):
        if not self.toolbar._actions['zoom'].isChecked() \
                and event.button == 1 and self.isAnnotate:
            #and not self.toolbar._actions['pan'].isChecked() \

            curTime = time.time()
            if (curTime - self.timeTrack) > PRE_DEF_CLICK_TIME:
                print("dragged")
                xmin = self.xpos
                xmax = event.xdata
                width = xmax - xmin
                # print(xmin)
                # print(xmax)
                # print(type(xmin))
                # print(mdates.num2date(xmin))
                # print(mdates.num2date(xmax))
                # print(type(xmax))
                # print(width)
                [ymin, ymax] = self.ax.get_ylim()
                height = ymax - ymin

                #annotation problem temporary
                # print(any(cond))
                self.create_annotation(xmin, xmax)
                # print(height)
                rect = ECERectangle(xmin,
                                    ymin,
                                    width,
                                    height,
                                    color=self.color,
                                    index=1)
                rect2 = ECERectangle(xmin,
                                     ymin,
                                     width,
                                     height,
                                     color=self.color,
                                     index=2)
                # Rectangle((xmin, ymin), width, height,
                #                     fill=True, alpha=0.4,
                #                     color=self.color, picker=True)
                rect.setNext(rect2)
                rect2.setPrev(rect)
                self.ax.add_patch(rect)
                self.ax2.add_patch(rect2)

                # self.ax2.add_patch(rect)
                self.fig.canvas.draw()

    def onpress(self, event):
        # print(event.key())
        print(event.key)
        if event.key == 'r':
            self.annoteText = "Kick"
            self.color = self.colorChoose(255, 0, 0)
        elif event.key == 'b':
            self.annoteText = "Sleep"
            self.color = self.colorChoose(0, 0, 255)
        elif event.key == 'g':
            self.annoteText = "Random"
            self.color = self.colorChoose(0, 255, 0)
        elif event.key == "right":
            print("right pressed")
            self.startX += self.currentDelta
            self.endX += self.currentDelta
            self.ax2.set_xlim(self.startX, self.endX)
            # mask = (self.df["epoch"] >= self.startX) & \
            #        (self.df["epoch"] <= self.endX)
            # self.ax2.plot(self.df.loc[mask, "epoch"],
            #               self.df.loc[mask, "mag"], '#1f77b4')
            self.run()
        elif event.key == "left":
            # TODO check left and right limit
            print("left pressed")
            self.startX -= self.currentDelta
            self.endX -= self.currentDelta
            self.ax2.set_xlim(self.startX, self.endX)
            # mask = (self.df["epoch"] >= self.startX) & \
            #        (self.df["epoch"] <= self.endX)
            # self.ax2.plot(self.df.loc[mask, "epoch"],
            #               self.df.loc[mask, "mag"], '#1f77b4')
            self.run()
        elif event.key == "delete":
            print("delete")
            if self.removedObj is not None:
                if isinstance(self.removedObj, ECERectangle):
                    print("deleting ECERect")
                    rect_min_x = self.removedObj.get_x()
                    rect_max_x = rect_min_x + self.removedObj.get_width()
                    self.remove_annotation(rect_min_x, rect_max_x)
                    ind = self.removedObj.getIndex()
                    self.removedObj.remove()
                    if ind == 1:
                        nextRect = self.removedObj.getNext()
                    else:
                        nextRect = self.removedObj.getPrev()
                    self.removedObj = None
                    # print(type())
                    if nextRect.axes is not None:
                        nextRect.remove()
                    self.fig.canvas.draw()

    def run(self):
        self.fig.canvas.draw()

    def create_annotation(self, xmin, xmax):
        print("Annoated")
        cond = self.get_x_in_ranges(xmin, xmax)
        self.df.loc[cond, 'annotated'] = self.annoteText
        self.df.loc[cond, 'colorHex'] = self.get_color_in_hex()

    def remove_annotation(self, xmin, xmax):
        cond = self.get_x_in_ranges(xmin, xmax)
        self.df.loc[cond, 'annotated'] = " "
        self.df.loc[cond, 'colorHex'] = " "

    def get_x_in_ranges(self, xmin, xmax):
        return (self.df["epoch"] <= mdates.num2date(xmax)) \
               & (self.df["epoch"] >= mdates.num2date(xmin))

    def get_color_in_hex(self):
        return "#{:02X}{:02X}{:02X}".format(int(self.color[0] * 255),
                                            int(self.color[1] * 255),
                                            int(self.color[2] * 255))

    def setSelect(self):
        self.isAnnotate = not self.isAnnotate

    def cursorOnclick(self, event):
        'on button press we will see if the mouse is over us '
        if (self.ax == event.inaxes) and (not self.isAnnotate):
            xdata = event.xdata
            dateclicked = mdates.num2date(xdata)
            self.startX = dateclicked
            dateEnd = dateclicked + self.currentDelta
            self.endX = dateEnd
            # df2,epoch,endDate=self.createBottomGraph(dateclicked)

            # mask = (df2["epoch"] >= dateclicked) & (df2["epoch"] <= endDate)
            # self.ax2.clear()
            # self.ax2.set_ylim(1, 1.1)
            # self.ax2.plot(df2.loc[mask, "epoch"], df2.loc[mask, "mag"])
            mask = (self.df["epoch"] >= dateclicked) & (self.df["epoch"] <=
                                                        dateEnd)
            tX = self.df.loc[mask, "epoch"]
            # print(tX)
            self.ax2.set_xlim(tX.iloc[0], tX.iloc[len(tX) - 1])
            # locator = mdates.SecondLocator(interval=120)
            self.ax2.get_xaxis().set_major_locator(LinearLocator(numticks=12))
            # self.ax2.xaxis.set_major_locator(locator)
            # self.ax2.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

            # self.ax2.relim()
            self.ax2.autoscale_view(True, True, True)
            #
            # x_ticks = np.append(self.ax2.get_xticks(), mdates.date2num(df2.iloc[0,0]))
            # x_ticks = np.append(x_ticks,mdates.date2num(df2.iloc[-1,0]))
            #self.ax2.set_xticks(x_ticks)
            # #self.ax2.tick_params(axis='x', labelsize=8,rotation=45)
            #
            width = mdates.date2num(dateEnd) - xdata
            [ymin, ymax] = self.ax.get_ylim()
            height = ymax - ymin
            if self.rect is not None:
                self.rect.remove()
            self.rect = Rectangle((xdata, ymin),
                                  width,
                                  height,
                                  fill=True,
                                  alpha=0.4,
                                  color=(1, 0, 0),
                                  picker=False)

            self.ax.add_patch(self.rect)

            self.fig.canvas.draw()

    def oncheck(self, label):
        if label == 'valuex':
            self.lx.set_visible(not self.lx.get_visible())
        elif label == 'valuey':
            self.ly.set_visible(not self.ly.get_visible())
        elif label == 'valuez':
            self.lz.set_visible(not self.lz.get_visible())
        elif label == 'mag':
            self.lm.set_visible(not self.lm.get_visible())
        self.fig.canvas.draw()

    def changeScale(self, label):
        index = self.labels.index(label)
        self.currentDelta = self.scaleDeltaArray[index]