Esempio n. 1
0
class ImCanvas(HBox, HasTraits):
    image_path = Unicode()
    _image_scale = Float()

    def __init__(self, width=150, height=150):

        self._canvas = Canvas(width=width, height=height)

        super().__init__([self._canvas])

    @observe('image_path')
    def _draw_image(self, change):
        self._image_scale = draw_img(self._canvas, self.image_path, clear=True)

    # Add value as a read-only property
    @property
    def image_scale(self):
        return self._image_scale

    def _clear_image(self):
        self._canvas.clear()

    # needed to support voila
    # https://ipycanvas.readthedocs.io/en/latest/advanced.html#ipycanvas-in-voila
    def observe_client_ready(self, cb=None):
        self._canvas.on_client_ready(cb)
Esempio n. 2
0
def draw_stimulus(include_source, n_background_dots, n_source_dots, source_sd,
                  source_border, width, height):

    canvas = Canvas(width=425,
                    height=425,
                    layout=widgets.Layout(justify_self='center',
                                          grid_area='canvas'))
    border = 4
    stroke = 2
    dot_radius = 2

    with hold_canvas(canvas):
        # draw box
        canvas.clear()
        canvas.stroke_style = 'black'
        canvas.stroke_rect(stroke, stroke, width, height)

        if include_source:
            bgdots = n_background_dots
            sourcedots = n_source_dots
        else:
            bgdots = n_background_dots + n_source_dots
            sourcedots = 0

        for _ in range(bgdots):
            canvas.fill_style = npr.choice(
                sns.color_palette("Paired").as_hex())
            x = npr.uniform(border, width - border)
            y = npr.uniform(border, height - border)
            canvas.fill_arc(x, y, dot_radius, 0, 2 * math.pi)

        if sourcedots > 0:
            x_center = npr.uniform(source_border, width - source_border)
            y_center = npr.uniform(source_border, width - source_border)
            x = np.clip(npr.normal(x_center, source_sd, sourcedots),
                        source_border, width - source_border)
            y = np.clip(npr.normal(y_center, source_sd, sourcedots),
                        source_border, width - source_border)

            for i in range(n_source_dots):
                canvas.fill_style = npr.choice(
                    sns.color_palette("Paired").as_hex())
                canvas.fill_arc(x[i], y[i], dot_radius, 0, 2 * math.pi)
    return canvas
Esempio n. 3
0
class Paper():
    """
    Base paper class.
    """
    def __init__(self, img):

        f = open('app/config.json')
        cfg = json.load(f)

        self.img = img

        self.width = 1080
        self.height = 770
        self.canvas = Canvas(
            width=self.width,
            height=self.height,
        )
        self.output = Output(
            layout=Layout(
                #border='1px solid cyan',
                width='1170px',
                height='770px',
                min_height='90vh',
                overflow='hidden hidden'), )
        with self.output:
            display(self.canvas)

        def put_image():
            resized = cv2.resize(img, (self.width, self.height),
                                 interpolation=cv2.INTER_AREA)
            self.canvas.put_image_data(resized, 0, 0)

        self.canvas.on_client_ready(put_image)

    def update(self, img, out):

        resized = cv2.resize(img, (self.width, self.height),
                             interpolation=cv2.INTER_AREA)

        with hold_canvas(self.canvas):
            self.canvas.clear()
            self.canvas.put_image_data(resized, 0, 0)
Esempio n. 4
0
class ImCanvas(HBox):
    def __init__(self, width=150, height=150, has_border=False):
        self.has_border = has_border
        self._canvas = Canvas(width=width, height=height)
        super().__init__([self._canvas])

    def _draw_image(self, image_path: str):
        self._image_scale = draw_img(
            self._canvas,
            image_path,
            clear=True,
            has_border=self.has_border
        )

    def _clear_image(self):
        self._canvas.clear()

    # needed to support voila
    # https://ipycanvas.readthedocs.io/en/latest/advanced.html#ipycanvas-in-voila
    def observe_client_ready(self, cb=None):
        self._canvas.on_client_ready(cb)
Esempio n. 5
0
class Core:
    # All constants that will be injected into global scope in the user"s cell
    global_constants = {
        "pi": pi
    }

    # All methods/fields from this class that will be exposed as global in user"s scope
    global_fields = {
        "canvas", "size", "width", "height",
        "mouse_x", "mouse_y", "mouse_is_pressed",
        "fill_style", "stroke_style",
        "clear", "background",
        "rect", "square", "fill_rect", "stroke_rect", "clear_rect",
        "fill_text", "stroke_text", "text_align",
        "draw_line",
        "circle", "fill_circle", "stroke_circle", "fill_arc", "stroke_arc",
        "print"
    }

    # All methods that user will be able to define and override
    global_methods = {
        "draw", "setup",
        "mouse_down", "mouse_up", "mouse_moved"
    }

    def __init__(self, globals_dict):
        self.status_text = display(Code(""), display_id=True)
        self._globals_dict = globals_dict
        self._methods = {}

        self.stop_button = Button(description="Stop")
        self.stop_button.on_click(self.on_stop_button_clicked)

        self.canvas = Canvas()
        self.output_text = ""
        self.width, self.height = DEFAULT_CANVAS_SIZE
        self.mouse_x = 0
        self.mouse_y = 0
        self.mouse_is_pressed = False

    ### Properties ###

    @property
    def mouse_x(self):
        return self._globals_dict["mouse_x"]

    @mouse_x.setter
    def mouse_x(self, val):
        self._globals_dict["mouse_x"] = val

    @property
    def mouse_y(self):
        return self._globals_dict["mouse_y"]

    @mouse_y.setter
    def mouse_y(self, val):
        self._globals_dict["mouse_y"] = val

    @property
    def mouse_is_pressed(self):
        return self._globals_dict["mouse_is_pressed"]

    @mouse_is_pressed.setter
    def mouse_is_pressed(self, val):
        self._globals_dict["mouse_is_pressed"] = val

    @property
    def width(self):
        return self._globals_dict["width"]

    @width.setter
    def width(self, val):
        self._globals_dict["width"] = val
        self.canvas.width = val

    @property
    def height(self):
        return self._globals_dict["height"]

    @height.setter
    def height(self, val):
        self._globals_dict["height"] = val
        self.canvas.height = val

    ### Library init ###

    # Updates last activity time
    @staticmethod
    def refresh_last_activity():
        global _sparkplug_last_activity
        _sparkplug_last_activity = time.time()

    # Creates canvas and starts thread
    def start(self, methods):
        self._methods = methods
        draw = self._methods.get("draw", None)
        
        if draw:
            self.print_status("Running...")
            display(self.stop_button)
        else:
            self.print_status("Done drawing")

        display(self.canvas)
        
        self.output_text_code = display(Code(self.output_text), display_id=True)

        self.canvas.on_mouse_down(self.on_mouse_down)
        self.canvas.on_mouse_up(self.on_mouse_up)
        self.canvas.on_mouse_move(self.on_mouse_move)

        thread = threading.Thread(target=self.loop)
        thread.start()

    def stop(self, message="Stopped"):
        global _sparkplug_running

        if not _sparkplug_running:
            return

        _sparkplug_running = False
        self.print_status(message)
        # Assuming we're using IPython to draw the canvas through the display() function.
        # Commenting this out for now, it throws exception since it does not derive BaseException
        # raise IpyExit

    # Loop method that handles drawing and setup
    def loop(self):
        global _sparkplug_active_thread_id, _sparkplug_running

        # Set active thread to this thread. This will stop any other active thread.
        current_thread_id = threading.current_thread().native_id
        _sparkplug_active_thread_id = current_thread_id
        _sparkplug_running = True
        self.refresh_last_activity()

        draw = self._methods.get("draw", None)
        setup = self._methods.get("setup", None)

        if setup:
            try:
                setup()
            except Exception as e:
                self.print_status("Error in setup() function: " + str(e))
                return

        while _sparkplug_running:
            if _sparkplug_active_thread_id != current_thread_id or time.time() - _sparkplug_last_activity > NO_ACTIVITY_THRESHOLD:
                self.stop("Stopped due to inactivity")
                return

            if not draw:
                return

            with hold_canvas(self.canvas):
                try:
                    draw()
                except Exception as e:
                    self.print_status("Error in draw() function: " + str(e))
                    return

            time.sleep(1 / FRAME_RATE)

    # Prints status to embedded error box
    def print_status(self, msg):
        self.status_text.update(Code(msg))
    
    # Prints output to embedded output box
    def print(self, msg):
        global _sparkplug_running
        self.output_text += str(msg) + "\n"

        if _sparkplug_running:
            self.output_text_code.update(Code(self.output_text))

    # Update mouse_x, mouse_y, and call mouse_down handler
    def on_mouse_down(self, x, y):
        self.refresh_last_activity()
        self.mouse_x, self.mouse_y = int(x), int(y)
        self.mouse_is_pressed = True

        mouse_down = self._methods.get("mouse_down", None)
        if mouse_down:
            mouse_down()

    # Update mouse_x, mouse_y, and call mouse_up handler
    def on_mouse_up(self, x, y):
        self.refresh_last_activity()
        self.mouse_x, self.mouse_y = int(x), int(y)
        self.mouse_is_pressed = False

        mouse_up = self._methods.get("mouse_up", None)
        if mouse_up:
            mouse_up()

    # Update mouse_x, mouse_y, and call mouse_moved handler
    def on_mouse_move(self, x, y):
        self.refresh_last_activity()
        self.mouse_x, self.mouse_y = int(x), int(y)

        mouse_moved = self._methods.get("mouse_moved", None)
        if mouse_moved:
            mouse_moved()
    
    def on_stop_button_clicked(self, button):
        self.stop()

    ### Global functions ###

    # Sets canvas size
    def size(self, *args):
        if len(args) == 2:
            self.width = args[0]
            self.height = args[1]

    # Sets fill style
    # 1 arg: HTML string value
    # 3 args: r, g, b are int between 0 and 255
    # 4 args: r, g, b, a, where r, g, b are ints between 0 and 255, and  a (alpha) is a float between 0 and 1.0
    def fill_style(self, *args):
        self.canvas.fill_style = self.parse_color("fill_style", *args)

    def stroke_style(self, *args):
        self.canvas.stroke_style = self.parse_color("stroke_style", *args)

    # Combines fill_rect and stroke_rect into one wrapper function
    def rect(self, *args):
        self.check_coords("rect", *args)
        
        self.canvas.fill_rect(*args)
        self.canvas.stroke_rect(*args)

    # Similar to self.rect wrapper, except only accepts x, y and size
    def square(self, *args):
        self.check_coords("square", *args, width_only=True)
        rect_args = (*args, args[2]) # Copy the width arg into the height
        self.rect(*rect_args)

    # Draws filled rect
    def fill_rect(self, *args):
        self.check_coords("fill_rect", *args)
        self.canvas.fill_rect(*args)
    
    # Strokes a rect
    def stroke_rect(self, *args):
        self.check_coords("stroke_rect", *args)
        self.canvas.stroke_rect(*args)

    #Clears a rect
    def clear_rect(self, *args):
        self.check_coords('clear_rect', *args)
        self.canvas.clear_rect(*args)

    # Draws circle at given coordinates
    def circle(self, *args):
        self.check_coords("circle", *args, width_only=True)
        arc_args = self.arc_args(*args)
        self.canvas.fill_arc(*arc_args)
        self.canvas.stroke_arc(*arc_args)

    # Draws filled circle
    def fill_circle(self, *args):
        self.check_coords("fill_circle", *args, width_only=True)
        arc_args = self.arc_args(*args)
        self.canvas.fill_arc(*arc_args)

    # Draws circle stroke
    def stroke_circle(self, *args):
        self.check_coords("stroke_circle", *args, width_only=True)
        arc_args = self.arc_args(*args)
        self.canvas.stroke_arc(*arc_args)
        
    def fill_arc(self, *args):
        self.canvas.fill_arc(*args)

    def stroke_arc(self, *args):
        self.canvas.stroke_arc(*args)
    
    def fill_text(self, *args):
        self.canvas.font = "{px}px sans-serif".format(px = args[4])
        self.canvas.fill_text(args[0:3])
        self.canvas.font = "12px sans-serif"

    def stroke_text(self, *args):
        self.canvas.font = "{px}px sans-serif".format(px = args[4])
        self.canvas.stroke_text(args[0:3])
        self.canvas.font = "12px sans-serif"

    def text_align(self, *args):
        self.canvas.text_align(*args)

    def draw_line(self, *args):
        if len(args) == 4:
            self.canvas.line_width = args[4]
        else:
            self.canvas.line_width = 1
            
        self.canvas.begin_path()
        self.canvas.move_to(args[0],args[1])
        self.canvas.line_to(args[2],args[4])
        self.canvas.close_path()

    # Clears canvas
    def clear(self, *args):
        self.canvas.clear()

    
    # Draws background on canvas
    def background(self, *args):
        old_fill = self.canvas.fill_style
        argc = len(args)

        if argc == 3:
            if ((not type(args[0]) is int) or (not type(args[1]) is int) or (not type(args[2]) is int)):
                raise TypeError("Enter Values between 0 and 255(integers only) for all 3 values")
            elif (not (args[0] >= 0 and args[0] <= 255) or not (args[1] >= 0 and args[1] <= 255) or not (
                args[2] >= 0 and args[2] <= 255)):
                raise TypeError("Enter Values between 0 and 255(integers only) for all 3 values")
            self.clear()
            self.fill_style(args[0], args[1], args[2])
            self.fill_rect(0, 0, self.width, self.height)
        elif argc == 1:
            if (not type(args[0]) is str):
                raise TypeError("Enter colour value in Hex i.e #000000 for black and so on")
            self.clear()
            self.fill_style(args[0])
            self.fill_rect(0, 0, self.width, self.height)
        elif argc == 4:
            if ((not type(args[0]) is int) or (not type(args[1]) is int) or (not type(args[2]) is int) or (
            not type(args[3]) is float)):
                raise TypeError("Enter Values between 0 and 255(integers only) for all 3 values")
            elif (not (args[0] >= 0 and args[0] <= 255) or not (args[1] >= 0 and args[1] <= 255) or not (
                args[2] >= 0 and args[2] <= 255) or not (args[3] >= 0.0 and args[3] <= 1.0)):
                raise TypeError(
                "Enter Values between 0 and 255(integers only) for all 3 values and a value between 0.0 and 1.0 for opacity(last argument")
            self.clear()
            self.fill_style(args[0], args[1], args[2], args[3])
            self.fill_rect(0, 0, self.width, self.height)
        
        self.canvas.fill_style = old_fill
    
    ### Helper Functions ###

    # Tests if input is numeric
    # Note: No support for complex numbers
    def check_type_is_num(self, n, func_name=None):
        if not isinstance(n, (int, float)):
            msg = "Expected {} to be a number".format(n)
            if func_name:
                msg = "{} expected {} to be a number".format(func_name, self.quote_if_string(n))
            raise TypeError(msg)

    # Tests if input is an int
    def check_type_is_int(self, n, func_name=None):
        if type(n) is not int:
            msg = "Expected {} to be an int".format(n)
            if func_name:
                msg = "{} expected {} to be an int".format(func_name, self.quote_if_string(n))
            raise TypeError(msg)

    # Tests if input is a float
    # allow_int: Set to True to allow ints as a float. Defaults to True.
    def check_type_is_float(self, n, func_name=None, allow_int=True):
        if type(n) is not float:
            if not allow_int or type(n) is not int:
                msg = "Expected {} to be a float".format(n)
                if func_name:
                    msg = "{} expected {} to be a float".format(func_name, self.quote_if_string(n))
                raise TypeError(msg)

    @staticmethod
    def quote_if_string(val):
        if type(val) is str:
            return "\"{}\"".format(val)
        else:
            return val
    
    # Parse a string, rgb or rgba input into an HTML color string
    def parse_color(self, func_name, *args):
        argc = len(args)

        if argc == 1:
            return args[0]
        elif argc == 3 or argc == 4:
            color_args = args[:3]
            for col in color_args:
                self.check_type_is_int(col, func_name)
            color_args = np.clip(color_args, 0, 255)

            if argc == 3:
                return "rgb({}, {}, {})".format(*color_args)
            else:
                # Clip alpha between 0 and 1
                alpha_arg = args[3]
                self.check_type_is_float(alpha_arg, func_name)
                alpha_arg = np.clip(alpha_arg, 0, 1.0)
                return "rgba({}, {}, {}, {})".format(*color_args, alpha_arg)
        else:
            raise TypeError("{} expected {}, {} or {} arguments, got {}".format(func_name, 1, 3, 4, argc))

    # Check a set of 4 args are valid coordinates
    # x, y, w, h
    def check_coords(self, func_name, *args, width_only=False):
        argc = len(args)
        if argc != 4 and not width_only:
            raise TypeError("{} expected {} arguments for x, y, w, h, got {} arguments".format(func_name, 4, argc))
        elif argc != 3 and width_only:
            raise TypeError("{} expected {} arguments for x, y, size, got {} arguments".format(func_name, 3, argc))

        for arg in args:
            self.check_type_is_float(arg, func_name)

    # Convert a tuple of circle args into arc args 
    def arc_args(self, *args):
        return (args[0], args[1], args[2] / 2, 0, 2 * pi)
Esempio n. 6
0
class Turtle:
    direction = 0
    color = "black"
    drawing = True

    def __init__(self, x_max=400, y_max=400):
        self.canvas = Canvas(width=x_max, height=y_max)
        self.x_max = x_max
        self.x_pos = x_max / 2
        self.y_max = y_max
        self.y_pos = y_max / 2

    def forward(self, steps=50):
        x_end, y_end = self.calculate_endpoint(steps)
        if self.is_pen_down():
            self.canvas.stroke_line(self.x_pos, self.y_pos, x_end, y_end)
        self.x_pos = x_end
        self.y_pos = y_end

    def calculate_endpoint(self, steps):
        new_x = cos(radians(self.direction)) * steps + self.x_pos
        new_y = sin(radians(self.direction)) * steps + self.y_pos
        return (new_x, new_y)

    def turn(self, degree):
        self.direction = (self.direction + degree) % 360

    def reset(self):
        self.x_pos = 200
        self.y_pos = 200
        self.direction = 0
        self.set_color("black")

    def clear(self):
        self.canvas.clear()

    def show(self):
        self.canvas.fill_style = "green"
        self.canvas.fill_rect(self.x_pos - 2, self.y_pos - 2, 4, 4)
        self.canvas.fill_style = self.color
        self.canvas.stroke_style = self.color
        return self.canvas

    def set_color(self, color):
        self.color = color
        self.canvas.fill_style = self.color
        self.canvas.stroke_style = self.color

    def is_pen_down(self):
        return self.drawing

    def pen_up(self):
        self.drawing = False

    def pen_down(self):
        self.drawing = True

    def is_x_inside_boundries(self):
        return self.x_pos >= 0 and self.x_pos < self.x_max

    def is_y_inside_boundries(self):
        return self.y_pos >= 0 and self.y_pos < self.y_max

    def is_inside_boundries(self):
        return self.is_x_inside_boundries() and self.is_y_inside_boundries()

    def get_x_pos(self):
        return self.x_pos

    def get_y_pos(self):
        return self.y_pos

    def set_canvas_size(self, width, height):
        self.canvas = Canvas()
Esempio n. 7
0
class Detection_Experiment():
    def __init__(self, subj_num):
        self.subject_num = subj_num
        self.output_file = 'sdt-' + str(subj_num) + '.csv'

        # create two buttons with these names, randomize the position
        if npr.random() < 0.5:
            self.labels = [[
                'Present',
                widgets.ButtonStyle(button_color='darkseagreen')
            ], ['Absent', widgets.ButtonStyle(button_color='salmon')]]
            self.position = 'left'
        else:
            self.labels = [[
                'Absent', widgets.ButtonStyle(button_color='salmon')
            ], ['Present',
                widgets.ButtonStyle(button_color='darkseagreen')]]
            self.position = 'right'

        self.buttons = [
            widgets.Button(description=desc[0],
                           layout=widgets.Layout(width='auto',
                                                 grid_area=f'button{idx}'),
                           value=idx,
                           style=desc[1])  # create button
            for idx, desc in enumerate(self.labels)
        ]  # puts buttons into a list

        self.canvas = Canvas(width=425,
                             height=425,
                             layout=widgets.Layout(justify_self='center',
                                                   grid_area='canvas'))

        # create output widget for displaying feedback/reward
        self.out = widgets.Output(layout=widgets.Layout(
            width='auto', object_position='center',
            grid_area='output'))  # output widgets wrapped in VBoxes

        np.random.seed(24)
        self.create_trials(25, [10, 15, 25, 60])
        self.done = False

    def create_trials(self, ntrials, vals):
        signal_present = np.array([0] * (ntrials * len(vals)) + [1] *
                                  (ntrials * len(vals)))
        l = [[val] * ntrials for val in vals]
        l = [item for sublist in l for item in sublist]
        signal_type = np.array([0] * (ntrials * len(vals)) + l)

        self.trials = pd.DataFrame({
            'signal_present': signal_present,
            'signal_type': signal_type
        })
        self.trials = self.trials.sample(frac=1).reset_index(
            drop=True)  # shuffle
        self.trials['trial_num'] = np.arange(self.trials.shape[0])
        self.trials['button_position'] = self.position
        self.trials['subject_number'] = self.subject_num
        self.trial_iterator = self.trials.iterrows()
        self.responses = []
        self.rt = []
        self.correct_resp = []

    # create function called when button clicked.
    # the argument to this fuction will be the clicked button
    # widget instance
    def on_button_clicked(self, button):
        # "linking function with output'
        if not self.done:
            with self.out:
                # what happens when we press the button
                choice = button.description
                if choice == 'Present':
                    choice_code = 1
                elif choice == 'Absent':
                    choice_code = 0

                # reminds us what we clicked
                self.responses.append(choice_code)

                # record reaction time here?
                if choice_code == self.current_trial['signal_present']:
                    correct = 1
                else:
                    correct = 0
                self.correct_resp.append(correct)

                self.rt.append(
                    (datetime.now() - self.dt).total_seconds() * 1000)

                self.next_trial()
        else:
            clear_output()
            self.save_data(self.output_file)
            print("The experiment is finished!")
            print("Data saved to .csv")
            print("Also available as exp.trials")
            print("-------------")
            print("Thanks so much for your time!")

    def save_data(self, fn):
        # create dataframe
        self.trials['responses'] = self.responses
        self.trials['rt'] = self.rt
        self.trials['correct_resp'] = self.correct_resp

        self.trials.to_csv(self.output_file)

    def draw_stimulus(self, include_source, n_background_dots, n_source_dots,
                      source_sd, source_border, width, height):

        border = 4
        stroke = 2
        dot_radius = 2

        with hold_canvas(self.canvas):
            # draw box
            self.canvas.clear()
            self.canvas.stroke_style = 'black'
            self.canvas.stroke_rect(stroke, stroke, width, height)

            if include_source:
                bgdots = n_background_dots
                sourcedots = n_source_dots
            else:
                bgdots = n_background_dots + n_source_dots
                sourcedots = 0

            for _ in range(bgdots):
                self.canvas.fill_style = npr.choice(
                    sns.color_palette("Paired").as_hex())
                x = npr.uniform(border, width - border)
                y = npr.uniform(border, height - border)
                self.canvas.fill_arc(x, y, dot_radius, 0, 2 * math.pi)

            if sourcedots > 0:
                x_center = npr.uniform(source_border, width - source_border)
                y_center = npr.uniform(source_border, width - source_border)
                x = np.clip(npr.normal(x_center, source_sd, sourcedots),
                            source_border, width - source_border)
                y = np.clip(npr.normal(y_center, source_sd, sourcedots),
                            source_border, width - source_border)

                for i in range(n_source_dots):
                    self.canvas.fill_style = npr.choice(
                        sns.color_palette("Paired").as_hex())
                    self.canvas.fill_arc(x[i], y[i], dot_radius, 0,
                                         2 * math.pi)

    def next_trial(self):
        try:
            self.current_trial = next(self.trial_iterator)[1]
            self.draw_stimulus(
                include_source=self.current_trial['signal_present'],
                n_background_dots=30 * 30,
                n_source_dots=self.current_trial['signal_type'],
                source_sd=10,
                source_border=20,
                width=400,
                height=400)
            self.dt = datetime.now()  # reset clock

        except StopIteration:
            self.done = True

    def start_experiment(self):
        # linking button and function together using a button's method
        [button.on_click(self.on_button_clicked) for button in self.buttons]

        self.next_trial()

        return widgets.GridBox(children=self.buttons + [self.canvas],
                               layout=widgets.Layout(
                                   width='50%',
                                   justify_items='center',
                                   grid_template_rows='auto auto',
                                   grid_template_columns='10% 40% 40% 10%',
                                   grid_template_areas='''
                           ". canvas canvas ."
                           ". button0 button1 ."
                           "output output output output"
                           '''))