# CellTracking.py
# By MW, Jun 2013
# GPLv3+
#
# Class managing the display of a cell instance
import logging, cv2, numpy, gtk, imp, sys
sys.path.append('./bin/Features/')
import Features_gtk
Slider = imp.load_source("Slider", './bin/Players/Slider.py')
[docs]class GtkCell :
    """A generic class for handling cell creation (get polygon as successive clicks)"""
    def __init__(self, cells, delete_f, export_f, state) :
        self.delete_this_cell = delete_f# Forwarded function from the cells.gtk class
        self.export_progress_bar = export_f # Forwarded function from the cells.gtk class, allowing generic creation of an export window and progress bar.
        self.state = state
        self.cells = cells
        self.eps = 7 # A distance in pixels
        self.points = []
        self.closed = False
        self.type = 'pol'
        self.cell = None
        self.panel = None
        self.selected = False
        self.r_panel = None
        self.l_panel = None
        #self.r_panel_inside = None
        self.radiobutton = None # The button on the cell l_panel
        self.radiobutton_none = None # The widget to select to uselect all
        self.visible = True
        self.selected_handle = None
        self.move_ref_with_cell = True
        self.reference_size = None
        self.deleted = False
        #self.features_gtk = Features_gtk.Features_gtk(cell.features, state)
[docs]    def set_cell(self, cell) :
        """Set the cell (no gtk) when loading a savefile"""
        self.cell = cell
        self.closed = True
        self.features_gtk = Features_gtk.Features_gtk(cell.features, self.state)
 
[docs]    def get_selected(self) :
        return self.selected
 
[docs]    def set_r_panel(self, panel) :
        self.r_panel = panel 
[docs]    def set_selected(self, sel) : 
        logging.debug("cell %s %s", self.get_name(), ['unselected', 'selected'][sel])
        self.selected = sel
        self.radiobutton.set_active(sel)
        self.radiobutton.set_sensitive(self.visible)
        if not self.visible and sel:
            self.radiobutton_none.set_active(True)
            
                 
[docs]    def get_name(self) :
        """Return the name of the cell"""
        if self.closed :
            return self.cell.get_name()
        else :
            logging.warning("Trying to get the name of a temporary cell")
            return "TMP cell" 
[docs]    def rb_toggled(self, active) :
        self.selected = active
        if active :
            if self.r_panel.get_child() != None :
                self.r_panel.remove(self.r_panel.get_child())
            mainbox = gtk.VBox()
            name = gtk.Label()
            name.set_markup("<big>Cell : %s</big>" % self.get_name())
            mainbox.pack_start(name, fill=False, expand=False)
            # == Cell parameters
            parameters_frame = gtk.Frame("Cell")
            parameters_box = gtk.VBox()
            
            delete_b = gtk.Button("Delete cell")
            delete_b.connect("clicked", self.delete_cell_evt)
            parameters_box.pack_start(delete_b, fill=False, expand=False)
            parameters_frame.add(parameters_box)
            mainbox.pack_start(parameters_frame, fill=False, expand=False)
            # == Slider parameters
            slider_frame = gtk.Frame("Cell begin & end")
            slider_box = gtk.VBox()
            slider = Slider.Slider((0, self.state.get_frame_nb()-1))
            slider.init_values(self.cell.get_bounds())
            
            begin_b = gtk.Button("Set begin frame")
            end_b = gtk.Button("Set end frame")
            begin_b.connect('clicked', self.begend_evt, slider, 'begin')
            end_b.connect('clicked', self.begend_evt, slider, 'end')
            begend_box = gtk.HBox()
            begend_box.pack_start(begin_b)
            begend_box.pack_start(end_b)
            slider_p = slider.get_panel()            
            slider_box.pack_start(begend_box, fill=True)
            slider_box.pack_start(slider_p, fill=True)
            slider_frame.add(slider_box)
            mainbox.pack_start(slider_frame, fill=False, expand=False)
            # == Reference frame
            reference_frame = gtk.Frame("Reference")
            reference_box = gtk.VBox()
            move_ref_with_cell_cb=gtk.CheckButton("Move reference with cell")
            reference_box.pack_start(move_ref_with_cell_cb, fill=False, expand=False)
            move_ref_with_cell_cb.connect("toggled", self.move_refwcell_evt)
            move_ref_with_cell_cb.set_active(True)
            bottom_box = gtk.HBox()
            refsize_l = gtk.Label("Reference size")
            refsize = self.reference_size
            if refsize == None :
                refsize = self.state.get_subclass('player').reference_size
            adj = gtk.Adjustment(refsize, 2, 100, 1, 5, 0)
            refsize_sb = gtk.SpinButton(adj)
            refsize_sb.connect('value-changed', self.refsize_sb_evt)
            reset_ref_b = gtk.Button("Reset reference")
            reset_ref_b.connect('clicked', self.reset_ref_evt)
            bottom_box.pack_start(refsize_l, fill=False, expand=False)
            bottom_box.pack_start(refsize_sb, fill=False, expand=False)
            bottom_box.pack_start(reset_ref_b, fill=False, expand=False)
            reference_box.pack_start(bottom_box, fill=False, expand=False)
            reference_frame.add(reference_box)
            mainbox.pack_start(reference_frame, expand=False, fill=False)
            # == Slider multi
            slidermulti_frame = gtk.Frame("Features")
            #f = Features_gtk.Features_gtk(self.cell.get_features(), self.state)
            f = self.features_gtk
            f.set_cell(self.cell)
            slidermulti_frame.add(f.get_panel((0, self.state.get_frame_nb()-1)))
            mainbox.pack_start(slidermulti_frame, expand=False, fill=False)
            # == Exports
            exports_frame = gtk.Frame('Export')
            export_box = gtk.HBox()
            quant_b = gtk.Button('Quantification')
            quant_b.set_tooltip_text("Export the cell quantification in CSV format")
            quant_b.connect('clicked', self.quant_evt)
            tiff_b = gtk.Button('TIFF series')
            tiff_b.set_tooltip_text("Exports the cell as a tiff series. Still EXPERIMENTAL")
            tiff_b.connect('clicked', self.tiff_evt)
            export_box.pack_start(quant_b, expand=False, fill=False)
            export_box.pack_start(tiff_b, expand=False, fill=False)
            exports_frame.add(export_box)
            mainbox.pack_start(exports_frame, expand=False, fill=False)
            mainbox.pack_start(gtk.Label(''), fill=True, expand=True)
            mainbox.show_all()
            #self.r_panel_inside = mainbox
            self.r_panel.add_with_viewport(mainbox)
            # == Bindings
            slider.connect('slider-changed', self.slider_changed_evt, f)
 
[docs]    def get_panel(self, rb, rb_none) :
            """Return the panel to manage a cell
            rb is the radiobutton instance determining which 
            cell is selected"""
            # Create expander and header
            self.panel = gtk.Expander(label="Cell: "+self.get_name())
            self.radiobutton = rb
            self.radiobutton_none = rb
            header_cb = gtk.CheckButton("Show")
            header_cb.connect('clicked', self.show_evt)
            header_cb.set_active(bool(self.visible))
            box = gtk.HBox()
            box.pack_start(header_cb, fill=False, expand=False)
            box.pack_start(gtk.VSeparator(), expand=False, fill=False)
            box.pack_start(self.panel, fill=False, expand=False)
            box.show_all()
            # Fill panel
            l = gtk.Label('Not implemented yer!')
            l.show()
            self.panel.add(l)
            return box
 
[docs]    def show_evt(self, evt) :
        self.visible = evt.get_active()
        self.set_selected(evt.get_active())
 
[docs]    def delete_cell_evt(self, evt) :
        """Function calling a forwarded function from Cells.gtk"""
        self.delete() 
[docs]    def delete(self) :
        self.delete_this_cell(self.get_name()) # (cells.gtk.delete_cell)
        self.cells.delete_cell(self.get_name())
        self.visble = False
 
[docs]    def unclick(self) :
        """Function called when the user click is released"""
        self.selected_handle = None 
[docs]    def click(self, tup) :
        """Function that receive a click from the viewer.
        This function returns True if the click lies inside the cell (core or reference polygon)
        In theory, the coordinates already have been converted"""
        if self.visible :
            i = self.state.get_subclass('player').get_current_index()
            in_c = self.cell.is_point_in_cell(tup, i)
            in_r = self.cell.is_point_in_ref(tup, i)
            if self.selected_handle == None :
                c_c = self.cell.is_point_on_cell_handle(tup, i)
                c_r = self.cell.is_point_on_ref_handle(tup, i)
                if c_c != None :
                    self.selected_handle = ('cell', c_c)
                elif c_r != None :
                    self.selected_handle = ('ref', c_r)
                    
            if in_c or in_r or (self.selected_handle != None):
                # Select cell
                logging.debug("%s selected", str(self.get_name()))
                self.set_selected(True)
                return True
            else :
                #unselect cell
                logging.debug("%s unselected", str(self.get_name()))
                self.set_selected(False)
                return False
        return False
 
[docs]    def move(self, tup) :
         """Function called when the cell is selected and the user tries to
         drag'n'drop it"""
         #print 'moving point %s' % self.selected_handle
         if self.selected_handle != None :
             i = self.state.get_subclass('player').get_current_index()
             if self.selected_handle[0] == 'cell' :
                 tr = self.cell.cell_pol.move_point(self.selected_handle[1], tup,i)
                 if self.move_ref_with_cell :
                     self.cell.cell_ref.translate(tr, i)
             elif self.selected_handle[0] == 'ref' :
                 tr = self.cell.cell_ref.move_point(self.selected_handle[1], tup,i)
                 if self.move_ref_with_cell :
                     self.cell.cell_pol.translate(tr, i)
             #if self.move_ref_with_cell :
             #   refsize = self.state.get_subclass('player').reference_size
             #    self.cell.update_ref(i, refsize)
             
[docs]    def get_type(self) :
        """Return the cell type ('pol' for instance)"""
        return self.type 
[docs]    def add_point(self, tup) :
        if not self.closed :
            if (len(self.points)>=1) and (self.dist(self.points[0], tup)<self.eps) :
                self.close() # Close the polygon
                return False # erase tmp_cell
            else :
                self.points.append(tup)
                print "point added: %s" % str(tup)
        else :
            logging.warning("Trying to add a point to a closed polygon")
 
[docs]    def reset_polygon(self) :
        self.points = []
        self.state.get_subclass('player').set_current_frame(None) # updating display
 
[docs]    def close(self) :
        """Function that close the polygon and create a new cell instance instead"""
        n = self.cells.get_name() # Get an unused name
        self.cell = self.cells.new_cell(str(n))
        i = self.state.get_subclass('player').get_current_index()
        refsize = self.state.get_subclass('player').reference_size
        self.cell.reset_polygon(self.points, i)
        self.cell.init_ref(refsize)
        self.closed = True
        self.refsize = refsize
        self.features_gtk = Features_gtk.Features_gtk(self.cell.features, self.state) 
[docs]    def new_cell(self, name=None) :
        """Function that initialize an empty cell
          .. warning :
             You should immediately add a polygon to the cell as it doesn't 
             like empty timetracks much...
        """
        if name == None :
            name = self.cells.get_name()
        self.cell = self.cells.new_cell(str(name))
        self.closed = True
 
[docs]    def init_ref(self, refsize) :
        self.refsize = refsize
        self.cell.init_ref(refsize)
 
[docs]    def dist(self, p1, p2) :
        s = 0
        for i in range(len(p1)) :
            s += (p1[i]-p2[i])**2
        return s**0.5
 
[docs]    def draw_polygon(self, im) :
        """Draw the polygon on the image
        :param im: the image to draw on.
        :param type:  a numpy matrix (x*y*3)
        :returns: numpy matrix -- the output image"""
        if self.visible and not self.deleted :
            if self.closed :
                i = self.state.get_subclass('player').get_current_index()
                (beg, end) = self.cell.get_bounds()
                if (i>=beg) and (i<=end) :
                    points = self.cell.cell_pol.get_polygon(i)
                    points_ref = self.cell.cell_ref.get_polygon(i)
                else :
                    points = []
                    points_ref = []
                if self.selected :
                    color = (255,0,0)
                    color_ref = (128,0,0)
                else :
                    color = (0,0,255)
                    color_ref = (0,0,128)
                points_ref = numpy.array(points_ref)*self.state. get_subclass('player').get_current_zoom()
            else :
                points = self.points
                color = (255,0,0)
            points = numpy.array(points)*self.state.get_subclass('player') .get_current_zoom()
            
            if len(points)==1 : # Draw first point if necessary
                p = points[0]
                cv2.circle(im, (int(p[0]), int(p[1])), 3, color=color)
            
                # Draw polygon
            #if self.points != [] : # Not sure whether this is required...
            if points != [] :
                
                cv2.polylines(im, [numpy.array(points, dtype=numpy.int32)], self.closed, color=color)
                if self.closed :
                    cv2.polylines(im, [numpy.array(points_ref, dtype=numpy.int32)], self.closed, color=color_ref)
                    center_pol = numpy.array(points).mean(axis=0)
                    cv2.circle(im,tuple(numpy.int_(center_pol)), 5, color=color) 
                    if not self.move_ref_with_cell :
                        center_ref = numpy.array(points_ref).mean(axis=0)#+numpy.array([10,0])
                        cv2.circle(im,tuple(numpy.int_(center_ref)), 5, color=color_ref) 
                        
                    for p in points :
                        cv2.circle(im, (int(p[0]), int(p[1])), 3, color=color)        
                    for p in points_ref :
                        cv2.circle(im, (int(p[0]), int(p[1])), 3, color=color_ref)                                
                    coord = (int(points[0][0]), int(points[0][1])-3)
                    cv2.putText(im, self.get_name(), coord, cv2.FONT_HERSHEY_PLAIN, fontScale=1,color=color)
        return im 
[docs]    def move_refwcell_evt(self, evt) :
        self.move_ref_with_cell = evt.get_active()
        self.cell.center_ref = ['mean', None][evt.get_active()] 
[docs]    def reset_ref_evt(self, evt) :
        self.cell.init_ref(self.reference_size) 
[docs]    def refsize_sb_evt(self, evt) :
        """Function called when the value of the spinbutton "Set reference 
        size" is changed"""
        self.reference_size = evt.get_value()
        self.cell.init_ref(self.reference_size) 
[docs]    def begend_evt(self, evt, slider, begend) :
        """Receives signals from the "Set begin frame" and "Set end frame" 
         buttons and forward them to :
        1. The slider
        2. The cell object"""
        # Update slider
        dict_convert ={'begin': 'left', 'end': 'right'}
        i = self.state.get_subclass('player').get_current_index()
        slider.set_value(dict_convert[begend], i)
        if begend == 'begin' :
            self.cell.set_begin(i)
        elif begend == 'end' :
            self.cell.set_end(i) 
[docs]    def slider_changed_evt(self, w, features_gtk) :
        b = w.get_cur_value()
        self.cell.set_begin(b[0])
        self.cell.set_end(b[1])
        features_gtk.update() 
[docs]    def tiff_evt(self, w) :
        """Binding called when clicking on the "Export as TIFF series" 
        button
        Many parameters exist, but they cannot be tuned for the moment
        This is used as a way to experiment for the quantification export
        This function should call a function displaying a progress bar,
        and call a cell object returning an iterator on the number of frames,
        such as in:
        http://stackoverflow.com/questions/496814/progress-bar-not-updating-during-operation
        or: http://faq.pygtk.org/index.py?req=show&file=faq23.020.htp
        """
        # Get path
        logging.info("Saving tiff series as...")
        dialog = gtk.FileChooserDialog("Select output folder", action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,  gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
        resp = dialog.run()
        f = None
        if resp == gtk.RESPONSE_OK :
            f = dialog.get_filename()
        dialog.destroy()
        state_player = self.state.get_subclass('player')
        state_player.set_lock(True)
        ch = state_player.get_current_channel()
        #self.cell.export_as_tiff(f, ch)
        self.export_progress_bar([(self.cell, ch, f)], 'tiff')
        state_player.set_lock(False) 
[docs]    def quant_evt(self, w) :
        """Binding called when clicking on the "Export quantification" 
        button"""
        # Get path
        logging.info("Saving quantification as...")
        dialog = gtk.FileChooserDialog("Select output folder", action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,  gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
        resp = dialog.run()
        f = None
        if resp == gtk.RESPONSE_OK :
            f = dialog.get_filename()
        dialog.destroy()
        state_player = self.state.get_subclass('player')
        state_player.set_lock(True)
        ch = state_player.get_current_channel()
        ##self.cell.export_as_csv(f, ch) # DEBUG
        self.export_progress_bar([(self.cell, ch, f)], 'csv')
        state_player.set_lock(False)
  
[docs]class GtkRect(GtkCell) :
    """A generic class to handle polygons as untilted rectangles"""
    def __init__(self, cells, state) :
        self.state = state
        TmpCell.__init__(self, cells)
 
[docs]class GtkRect_ct(GtkRect) :
    def __init__(self, cells, state) :
        self.state = state
        TmpCell.__init__(self, cells)
 
[docs]class GtkRect_tp(GtkRect) :
    def __init__(self, cells, state) :
        self.state = state
        TmpCell.__init__(self, cells)