cam = Camera()

path = "./data/"
ext = ".png"
suit_ptr = 0
rank_ptr = 0
current_dir = ""
allDone = False
for s in SUITS:
    for r in RANKS:
        directory = path + s + "/" + r + "/"
        if not os.path.exists(directory):
            os.makedirs(directory)
print "Current Data: " + str(RANKS[rank_ptr]) + str(SUITS[suit_ptr])
while not allDone:
    keys = disp.checkEvents()
    img = cam.getImage()
    for k in keys:
        if (k == pg.K_SPACE):
            directory = path + SUITS[suit_ptr] + "/" + RANKS[rank_ptr] + "/"
            files = glob.glob(directory + "*.*")
            count = len(files)
            fname = directory + RANKS[rank_ptr] + SUITS[suit_ptr] + "-" + str(
                count) + ext
            img.save(fname)
            print "Saved: " + fname
        if (k == pg.K_n):
            if rank_ptr == len(RANKS) - 1 and suit_ptr == len(SUITS) - 1:
                allDone = True
            elif rank_ptr == len(RANKS) - 1:
                rank_ptr = 0
Exemple #2
0
    def turk(self,
             saveOriginal=False,
             disp_size=(800, 600),
             showKeys=True,
             font_size=16,
             color=Color.RED,
             spacing=10):
        """
        **SUMMARY**

        This function does the turning of the data. The method goes through each image,
        applies the preprocessing (which can return multiple images), displays each image
        with an optional display of the key mapping. The user than selects the key that describes
        the class of the image. The image is then post processed and saved to the directory.
        The escape key kills the turking, the space key skips an image.

        **PARAMETERS**

        * *saveOriginal* - if true save the original image versus the preprocessed image.
        * *disp_size* - size of the display to create.
        * *showKeys* - Show the key mapping for the turking. Note that on small images this may not render correctly.
        * *font_size* - the font size for the turking display.
        * *color* - the font color.
        * *spacing* - the spacing between each line of text on the display.

        **RETURNS**

        Nothing but stores each image in the directory. The image sets are also available
        via the getClass method.

        **EXAMPLE**

        >>>> def GetBlobs(img):
        >>>>     blobs = img.findBlobs()
        >>>>     return [b.mMask for b in blobs]

        >>>> def ScaleIng(img):
        >>>>     return img.resize(100,100).invert()

        >>>> turker = TurkingModule(['./data/'],['./turked/'],['apple','banana','cherry'],['a','b','c'],preProcess=GetBlobs,postProcess=ScaleInv]
        >>>> turker.turk()
        >>>> # ~~~ stuff ~~~
        >>>> turker.save('./derp.pkl')

        ** TODO **
        TODO: fix the display so that it renders correctly no matter what the image size.
        TODO: Make it so you can stop and start turking at any given spot in the process
        """
        disp = Display(disp_size)
        bail = False
        for img in self.srcImgs:
            print img.filename
            samples = self.preProcess(img)
            for sample in samples:
                if (showKeys):
                    sample = self._drawControls(sample, font_size, color,
                                                spacing)

                sample.save(disp)
                gotKey = False
                while (not gotKey):
                    keys = disp.checkEvents(True)
                    for k in keys:
                        if k in self.keyMap:
                            if saveOriginal:
                                self._saveIt(img, self.keyMap[k])
                            else:
                                self._saveIt(sample, self.keyMap[k])
                            gotKey = True
                        if k == 'space':
                            gotKey = True  # skip
                        if k == 'escape':
                            return
class Gui:

    """
    Default layer groups available to display
    First element is the 'base' layer, and must be an image,
    the rest must be features and will be drawn on top
    """
    layersets = {
            'default': ['raw', 'yellow', 'blue', 'ball'],
            'yellow': ['threshY', 'yellow'],
            'blue': ['threshB', 'blue'],
            'ball': ['threshR', 'ball']
            }

    def __init__(self, size=(720, 540)):
        self._layers = {
                # Base layers
                'raw': None,
                'threshY': None,
                'threshB': None,
                'threshR': None,

                # Overlay layers
                'yellow': None,
                'blue': None,
                'ball' : None,
                }

        # These layers are drawn regardless of the current layerset
        self._persistentLayers = {
                'mouse': None
        }

        self._currentLayerset = self.layersets['default']
        self._display = Display(size)
        self._eventHandler = Gui.EventHandler()
        self._lastMouseState = 0
        self._showMouse = True
        self._lastFrame = None
        self._lastFrameTime = time.time()

    def __draw(self):

        iterator = iter(self._currentLayerset)
        
        # First element is the base layer
        baseLayer = self._layers[iterator.next()]

        if baseLayer is None:
            return

        size = baseLayer.size()

        # Draw all entities to one layer (for speed)
        entityLayer = baseLayer.dl()
        for key in iterator:
            toDraw = self._layers[key]
            if toDraw is None:
                continue
            
            elif isinstance(toDraw, DrawingLayer):
                baseLayer.addDrawingLayer(toDraw)

            else:
                toDraw.draw(entityLayer)

        for layer in self._persistentLayers.itervalues():
            if layer is not None:
                baseLayer.addDrawingLayer(layer)

        finalImage = baseLayer.applyLayers()
        self._display.writeFrame(finalImage, fit=False)

    def __updateFps(self):
        smoothConst = 0.1
        thisFrameTime = time.time()

        thisFrame = thisFrameTime - self._lastFrameTime
        if self._lastFrame is not None:
            # Smooth values
            thisFrame = thisFrame * (1 - smoothConst) + smoothConst * self._lastFrame
        
        fps = 1.0 / thisFrame

        self._lastFrame = thisFrame
        self._lastFrameTime = thisFrameTime

        layer = self._layers['raw'].dl()
        layer.ezViewText('{0:.1f} fps'.format(fps), (10, 10))

    def drawCrosshair(self, pos, layerName = None):
        size = self._layers['raw'].size()
        if layerName is not None:
            layer = self.getDrawingLayer()
        else:
            layer = self._layers['raw'].dl()

        layer.line((0, pos[1]), (size[0], pos[1]), color=(0, 0, 255))
        layer.line((pos[0], 0), (pos[0], size[1]), color=(0, 0, 255))

        if layerName is not None:
            self.updateLayer(layerName, layer)

 
    def loop(self):
        """
        Draw the image to the display, and process any events
        """

        for event in pygame.event.get(pygame.KEYDOWN):
            self._eventHandler.processKey(chr(event.key % 0x100))

        self._display.checkEvents()
        mouseX = self._display.mouseX
        mouseY = self._display.mouseY

        if self._showMouse:
            self.drawCrosshair((mouseX, mouseY), 'mouse')

        mouseLeft = self._display.mouseLeft
        # Only fire click event once for each click
        if mouseLeft == 1 and self._lastMouseState == 0:
            self._eventHandler.processClick((mouseX, mouseY))
        
        self._lastMouseState = mouseLeft
        
        # Processing OpenCV events requires calling cv.WaitKey() with a reasonable timeout,
        # which hits our framerate hard (NOTE: Need to confirm this on DICE), so only do
        # this if the focus isn't on the pygame (image) window`
        if not pygame.key.get_focused():
            c = cv.WaitKey(2)
            self._eventHandler.processKey(chr(c % 0x100))

        self.__updateFps()
        self.__draw()

    def getEventHandler(self):
        return self._eventHandler   

    def getDrawingLayer(self):
        return DrawingLayer(self._layers['raw'].size())

    def updateLayer(self, name, layer):
        """
        Update the layer specified by 'name'
        If the layer name is not in the known list of layers, 
        then it will be drawn regardless of the current view setting
        """
        
        if name in self._layers.keys():
            self._layers[name] = layer
        else:
            self._persistentLayers[name] = layer

    def switchLayerset(self, name):
        assert name in self.layersets.keys(), 'Unknown layerset ' + name + '!'

        self._currentLayerset = self.layersets[name]

    def setShowMouse(self, showMouse):
        if not showMouse:
            self.updateLayer('mouse', None)

        self._showMouse = showMouse
        
    class EventHandler:
        
        def __init__(self):
            self._listeners = {}
            self._clickListener = None
        
        def processKey(self, key):
            if key in self._listeners.keys():
                self._listeners[key]()

        def processClick(self, where):
            if self._clickListener is not None:
                self._clickListener(where)
            
        def addListener(self, key, callback):
            """
            Adds a function callback for a key.
            """
            
            assert callable(callback), '"callback" must be callable'
            
            self._listeners[key] = callback

        def setClickListener(self, callback):
            """
            Sets a function to be called on clicking on the image.
            The function will be passed a tuple with the (x,y) of the click.

            Setting a new callback will override the last one (or pass None to clear)
            """
            assert callback is None or callable(callback), '"callback" must be callable'
            
            self._clickListener = callback
    def turk(self,saveOriginal=False,disp_size=(800,600),showKeys=True,font_size=16,color=Color.RED,spacing=10 ):
        """
        **SUMMARY**

        This function does the turning of the data. The method goes through each image,
        applies the preprocessing (which can return multiple images), displays each image
        with an optional display of the key mapping. The user than selects the key that describes
        the class of the image. The image is then post processed and saved to the directory.
        The escape key kills the turking, the space key skips an image.

        **PARAMETERS**

        * *saveOriginal* - if true save the original image versus the preprocessed image.
        * *disp_size* - size of the display to create.
        * *showKeys* - Show the key mapping for the turking. Note that on small images this may not render correctly.
        * *font_size* - the font size for the turking display.
        * *color* - the font color.
        * *spacing* - the spacing between each line of text on the display.

        **RETURNS**

        Nothing but stores each image in the directory. The image sets are also available
        via the getClass method.

        **EXAMPLE**

        >>>> def GetBlobs(img):
        >>>>     blobs = img.findBlobs()
        >>>>     return [b.mMask for b in blobs]

        >>>> def ScaleIng(img):
        >>>>     return img.resize(100,100).invert()

        >>>> turker = TurkingModule(['./data/'],['./turked/'],['apple','banana','cherry'],['a','b','c'],preProcess=GetBlobs,postProcess=ScaleInv]
        >>>> turker.turk()
        >>>> # ~~~ stuff ~~~
        >>>> turker.save('./derp.pkl')

        ** TODO **
        TODO: fix the display so that it renders correctly no matter what the image size.
        TODO: Make it so you can stop and start turking at any given spot in the process
        """
        disp = Display(disp_size)
        bail = False
        for img in self.srcImgs:
            print img.filename
            samples = self.preProcess(img)
            for sample in samples:
                if( showKeys ):
                    sample = self._drawControls(sample,font_size,color,spacing )

                sample.save(disp)
                gotKey = False
                while( not gotKey ):
                    keys = disp.checkEvents(True)
                    for k in keys:
                        if k in self.keyMap:
                            if saveOriginal:
                                self._saveIt(img,self.keyMap[k])
                            else:
                                self._saveIt(sample,self.keyMap[k])
                            gotKey = True
                        if k == 'space':
                            gotKey = True # skip
                        if k == 'escape':
                            return
Exemple #5
0
class Gui:

    _layer_sets = { 'default': ['raw', 'robot0', 'robot1', 'robot2', 'robot3', 'ball'],
                    'squares1': ['squares', 'robot0', 'robot1', 'robot2', 'robot3', 'ball'],
                    'squares2': ['squares', 'robot0', 'robot1', 'robot2', 'robot3', 'ball'],
                    'squares3': ['squares', 'robot0', 'robot1', 'robot2', 'robot3', 'ball'],
                    'squares4': ['squares', 'robot0', 'robot1', 'robot2', 'robot3', 'ball'],
                    'ball': ['threshR', 'ball'] }

    _layers = { 'raw': None,
                'threshR': None,
                'ball': None,
                'robot0': None,
                'robot1': None,
                'robot2': None,
                'robot3': None,
                'squares': None }

    _persistent_layers = { 'mouse': None }

    def __init__(self, size=(720, 540)):

        self._current_layer_set = self._layer_sets['default']
        self._display = Display(size)
        self._event_handler = Gui.EventHandler()
        self._last_mouse_state = 0
        self._show_mouse = True
        self._last_frame = None
        self._last_frame_time = time.time()

    def __draw(self):

        iterator = iter(self._current_layer_set)
        base_layer = self._layers[iterator.next()]

        if base_layer is None:
            return

        size = base_layer.size()
        entity_layer = base_layer.dl()

        for key in iterator:
            to_draw = self._layers[key]
            if isinstance(to_draw, DrawingLayer):
                base_layer.addDrawingLayer(to_draw)
            elif not to_draw is None:
                to_draw.draw(entity_layer)

        for layer in self._persistent_layers.itervalues():
            if layer is not None:
                base_layer.addDrawingLayer(layer)

        final_image = base_layer.applyLayers()
        self._display.writeFrame(final_image, fit=False)

    def __update_fps(self):

        this_frame_time = time.time()
        this_frame = this_frame_time - self._last_frame_time
        fps = 1.0 / this_frame
        self._lastFrame = this_frame
        self._last_frame_time = this_frame_time
        layer = self._layers[self._current_layer_set[0]].dl()
        layer.ezViewText('{0:.1f} fps'.format(fps), (10, 10))

    def draw_crosshair(self, pos, layer_name = None):

        size = self._layers['raw'].size()

        if layer_name is not None:
            layer = DrawingLayer(self._layers['raw'].size())
        else:
            layer = self._layers['raw'].dl()

        layer.line((0, pos[1]), (size[0], pos[1]), color=(0, 0, 255))
        layer.line((pos[0], 0), (pos[0], size[1]), color=(0, 0, 255))

        if layer_name is not None:
            self.update_layer(layer_name, layer)

    def process_update(self):
        """Draw the image to the display, and process any events
        """
        for event in pygame.event.get(pygame.KEYDOWN):
            self._event_handler.process_key(chr(event.key % 0x100))

        self._display.checkEvents()
        mouseX = self._display.mouseX
        mouseY = self._display.mouseY

        if self._show_mouse:
            self.draw_crosshair((mouseX, mouseY), 'mouse')

        mouse_left = self._display.mouseLeft
        # Only fire click event once for each click
        if mouse_left == 1 and self._last_mouse_state == 0:
            self._event_handler.process_click((mouseX, mouseY))

        self._last_mouse_state = mouse_left

        # Processing OpenCV events requires calling cv.WaitKey() with a reasonable timeout,
        # which hits our framerate hard (NOTE: Need to confirm this on DICE), so only do
        # this if the focus isn't on the pygame (image) window
        if not pygame.key.get_focused():
            c = cv.WaitKey(2)
            self._event_handler.process_key(chr(c % 0x100))

        self.__update_fps()
        self.__draw()

    def get_event_handler(self):
        return self._event_handler

    def update_layer(self, name, layer):
        """Update the layer specified by 'name'
        If the layer name is not in the known list of layers, 
        then it will be drawn regardless of the current view setting
        """
        if name in self._layers.keys():
            self._layers[name] = layer
        else:
            self._persistent_layers[name] = layer

    def switch_layer_set(self, name):

        assert name in self._layer_sets.keys(), 'Unknown layerset ' + name + '!'

        self._current_layer_set = self._layer_sets[name]

    def set_show_mouse(self, show_mouse):

        if not show_mouse:
            self.update_layer('mouse', None)

        self._show_mouse = show_mouse

    class EventHandler:
        
        def __init__(self):
            self._listeners = {}
            self._click_listener = None
        
        def process_key(self, key):
            if key in self._listeners.keys():
                self._listeners[key]()

        def process_click(self, where):
            if self._click_listener is not None:
                self._click_listener(where)
            
        def add_listener(self, key, callback):
            """Adds a function callback for a key.
            """
            assert callable(callback), '"callback" must be callable'
            self._listeners[key] = callback

        def set_click_listener(self, callback):
            """Sets a function to be called on clicking on the image.
            The function will be passed a tuple with the (x, y) of the click.

            Setting a new callback will override the last one (or pass None to clear)
            """
            assert callback is None or callable(callback), '"callback" must be callable'
            self._click_listener = callback
cam = Camera()

path = "./data/"
ext = ".png"
suit_ptr = 0
rank_ptr = 0
current_dir = ""
allDone = False
for s in SUITS:
    for r in RANKS:
        directory = path+s+"/"+r+"/"
        if not os.path.exists(directory):
            os.makedirs(directory)
print "Current Data: " + str(RANKS[rank_ptr])+str(SUITS[suit_ptr])
while not allDone :
    keys = disp.checkEvents()
    img = cam.getImage()
    for k in keys:
        if( k == pg.K_SPACE ):
            directory = path+SUITS[suit_ptr]+"/"+RANKS[rank_ptr]+"/"
            files = glob.glob(directory+"*.*")
            count = len(files)
            fname = directory+RANKS[rank_ptr]+SUITS[suit_ptr]+"-"+str(count)+ext
            img.save(fname)
            print "Saved: " + fname
        if( k == pg.K_n ):
            if rank_ptr == len(RANKS)-1 and suit_ptr == len(SUITS)-1:
                allDone = True
            elif rank_ptr == len(RANKS)-1:
                rank_ptr = 0
                suit_ptr = suit_ptr + 1
Exemple #7
0
class Gui:

    """
    Default layer groups available to display
    First element is the 'base' layer, and must be an image,
    the rest must be features and will be drawn on top
    """
    layersets = {
            'default': ['raw', 'yellow', 'blue', 'ball'],
            'yellow': ['threshY', 'yellow'],
            'blue': ['threshB', 'blue'],
            'ball': ['threshR', 'ball']
            }

    h_min = s_min = v_min = 255
    h_max = s_max = v_max = 0

    # Show pixel mode: turn on/off by 'p', show the HSV of the pixel at the mouse
    showPixel = False
    # Record pixel mode: use the current pixel to update threshold
    recordPixel = False

    def __init__(self, noGui):
        self._layers = {
                # Base layers
                'raw': None,
                'threshY': None,
                'threshB': None,
                'threshR': None,

                # Overlay layers
                'yellow': None,
                'blue': None,
                'ball': None,
                }

        # These layers are drawn regardless of the current layerset
        self._persistentLayers = {
                'mouse': None
        }

        self._currentLayerset = self.layersets['default']
        self._display = Display()
        self._eventHandler = Gui.EventHandler()
        self._lastMouseState = 0
        self._showMouse = True
        self._lastFrame = None
        self._lastFrameTime = time.time()

    def __draw(self):
        iterator = iter(self._currentLayerset)
        # First element is the base layer
        baseLayer = self._layers[iterator.next()]

        if baseLayer is None:
            return

        # Draw all entities to one layer (for speed)
        entityLayer = baseLayer.dl()
        for key in iterator:
            toDraw = self._layers[key]
            if toDraw is None:
                continue
            elif isinstance(toDraw, DrawingLayer):
                baseLayer.addDrawingLayer(toDraw)
            else:
                toDraw.draw(entityLayer)

        for layer in self._persistentLayers.itervalues():
            if layer is not None:
                baseLayer.addDrawingLayer(layer)

        finalImage = baseLayer.applyLayers()
        self._display.writeFrame(finalImage, fit=False)

    def __updateFps(self):
        smoothConst = 0.1
        thisFrameTime = time.time()

        thisFrame = thisFrameTime - self._lastFrameTime
        if self._lastFrame is not None:
            # Smooth values
            thisFrame = thisFrame * (1 - smoothConst) + smoothConst * self._lastFrame

        self._lastFrame = thisFrame
        self._lastFrameTime = thisFrameTime

    def drawCrosshair(self, pos, layerName=None):
        size = self._layers['raw'].size()
        if layerName is not None:
            layer = self.getDrawingLayer()
        else:
            layer = self._layers['raw'].dl()

        layer.line((0, pos[1]), (size[0], pos[1]), color=(0, 0, 255))
        layer.line((pos[0], 0), (pos[0], size[1]), color=(0, 0, 255))

        if layerName is not None:
            self.updateLayer(layerName, layer)

    def loop(self):
        """
        Draw the image to the display, and process any events
        """

        for event in pygame.event.get(pygame.KEYDOWN):
            self._eventHandler.processKey(chr(event.key % 0x100))

        self._display.checkEvents()
        mouseX = self._display.mouseX
        mouseY = self._display.mouseY

        if self._showMouse:
            self.drawCrosshair((mouseX, mouseY), 'mouse')

        mouseLeft = self._display.mouseLeft
        # Only fire click event once for each click
        if mouseLeft == 1 and self._lastMouseState == 0:
            self._eventHandler.processClick((mouseX, mouseY))

        self._lastMouseState = mouseLeft

        # Processing OpenCV events requires calling cv.WaitKey() with a reasonable timeout,
        # which hits our framerate hard (NOTE: Need to confirm this on DICE), so only do
        # this if the focus isn't on the pygame (image) window`
        if not pygame.key.get_focused():
            c = cv.WaitKey(2)
            self._eventHandler.processKey(chr(c % 0x100))

        if self.showPixel:
            self.__updatePixel()
        self.__updateFps()
        self.__draw()

    def getEventHandler(self):
        return self._eventHandler

    def getDrawingLayer(self):
        return DrawingLayer(self._layers['raw'].size())

    def updateLayer(self, name, layer):
        """
        Update the layer specified by 'name'
        If the layer name is not in the known list of layers,
        then it will be drawn regardless of the current view setting
        """

        if name in self._layers.keys():
            self._layers[name] = layer
        else:
            self._persistentLayers[name] = layer

    def switchLayerset(self, name):
        assert name in self.layersets.keys(), 'Unknown layerset ' + name + '!'

        self._currentLayerset = self.layersets[name]

    def setShowMouse(self, showMouse):
        if not showMouse:
            self.updateLayer('mouse', None)

        self._showMouse = showMouse

    def toggleShowPixel(self):
        self.showPixel = not self.showPixel

    def toggleRecordPixel(self):
        self.recordPixel = not self.recordPixel

    def getMousePos(self):
        return (self._display.mouseX, self._display.mouseY)

    def getPixelHSV(self, x, y):
        return self._layers['raw'].crop(x, y, 1, 1).toHSV()[0, 0]

    def getPixel(self, x, y):
        return self._layers['raw'][x, y]

    #check (x,y) is in the image range
    def inRange(self, x, y):
        h = self._layers['raw'].height
        w = self._layers['raw'].width
        return (0 < x and x < w and 0 < y and y < h)

    # Display the pixel HSV
    def __updatePixel(self):
        (x, y) = self.getMousePos()
        if self.inRange(x, y):
            (h, s, v) = self.getPixelHSV(x, y)
        else:
            return

        if self.recordPixel:
            self.updateMinMax(h, s, v)

        # drawingLayer = self.getDrawingLayer()
        # drawingLayer.text('Pixel ({0}, {1}) HSV = ({2}, {3}, {4})'.format(x,y,h,s,v),(10,10), fgcolor(255, 255, 255))
        # drawingLayer.ezViewText('HSV_min =  ({0}, {1}, {2}) '.format(self.h_min, self.s_min, self.v_min),(10,30))
        # drawingLayer.ezViewText('HSV_max =  ({0}, {1}, {2}) '.format(self.h_max, self.s_max, self.v_max),(10,50))
        print ('Pixel ({0}, {1}) HSV = ({2}, {3}, {4})'.format(x, y, h, s, v))
        print ('HSV_min =  ({0}, {1}, {2}) '.format(self.h_min, self.s_min, self.v_min))
        print ('HSV_max =  ({0}, {1}, {2}) '.format(self.h_max, self.s_max, self.v_max))
        if self.recordPixel:
            print 'Recording Pixel'
            #drawingLayer.ezViewText('Recording Pixel',(10,70))

        #print 'pixel ',(x,y), ' HSV = ', (h,s,v), ' RGB = ', (r,g,b),
        #print 'HSV_min = ', (self.h_min, self.s_min, self.v_min),
        #print 'HSV_max = ', (self.h_max, self.s_max, self.v_max)

    def resetMinMax(self):
        self.h_min = self.s_min = self.v_min = 255
        self.h_max = self.s_max = self.v_max = 0

    # use the given HSV to update threshold
    def updateMinMax(self, h, s, v):
        self.h_min = min(self.h_min, h)
        self.s_min = min(self.s_min, s)
        self.v_min = min(self.v_min, v)
        self.h_max = max(self.h_max, h)
        self.s_max = max(self.s_max, s)
        self.v_max = max(self.v_max, v)

    class EventHandler:

        def __init__(self):
            self._listeners = {}
            self._clickListener = None

        def processKey(self, key):
            if key in self._listeners.keys():
                self._listeners[key]()

        def processClick(self, where):
            if self._clickListener is not None:
                self._clickListener(where)

        def addListener(self, key, callback):
            """
            Adds a function callback for a key.
            """
            assert callable(callback), '"callback" must be callable'

            self._listeners[key] = callback

        def setClickListener(self, callback):
            """
            Sets a function to be called on clicking on the image.
            The function will be passed a tuple with the (x,y) of the click.

            Setting a new callback will override the last one (or pass None to clear)
            """
            assert callback is None or callable(callback), '"callback" must be callable'

            self._clickListener = callback