Exemple #1
0
class BBoxCanvas(HBox, traitlets.HasTraits):
    debug_output = widgets.Output(layout={'border': '1px solid black'})
    image_path = traitlets.Unicode()
    bbox_coords = traitlets.Dict()
    _canvas_bbox_coords = traitlets.Dict()
    _image_scale = traitlets.Float()

    def __init__(self, width, height):
        super().__init__()

        self.is_drawing = False
        self._start_point = ()
        self._image_scale = 1.0

        self._bg_layer = 0
        self._image_layer = 1
        self._box_layer = 2

        # Define each of the children...
        self._image = Image(layout=Layout(display='flex',
                                          justify_content='center',
                                          align_items='center',
                                          align_content='center'))
        self._multi_canvas = MultiCanvas(3, width=width, height=height)
        self._im_name_box = Label()

        children = [VBox([self._multi_canvas, self._im_name_box])]
        self.children = children
        draw_bg(self._multi_canvas[self._bg_layer])

        # link drawing events
        self._multi_canvas[self._box_layer].on_mouse_move(self._update_pos)
        self._multi_canvas[self._box_layer].on_mouse_down(self._start_drawing)
        self._multi_canvas[self._box_layer].on_mouse_up(self._stop_drawing)

    @debug_output.capture(clear_output=False)
    def _update_pos(self, x, y):
        if self.is_drawing:
            self._canvas_bbox_coords = points2bbox_coords(
                *self._start_point, x, y)

    @debug_output.capture(clear_output=True)
    def _start_drawing(self, x, y):
        self._start_point = (x, y)
        self.is_drawing = True

    @debug_output.capture(clear_output=False)
    def _stop_drawing(self, x, y):
        self.is_drawing = False
        self.bbox_coords = {
            k: v / self._image_scale
            for k, v in self._canvas_bbox_coords.items()
        }

    @traitlets.observe('bbox_coords')
    def _update_canvas_bbox_coords(self, change):
        self._canvas_bbox_coords = {
            k: v * self._image_scale
            for k, v in self.bbox_coords.items()
        }

    @traitlets.observe('_canvas_bbox_coords')
    def _draw_bbox(self, change):
        if not self._canvas_bbox_coords:
            self._clear_bbox()
            return
        coords = [
            self._canvas_bbox_coords['x'], self._canvas_bbox_coords['y'],
            self._canvas_bbox_coords['width'],
            self._canvas_bbox_coords['height']
        ]
        draw_bounding_box(self._multi_canvas[self._box_layer],
                          coords,
                          color='white',
                          border_ratio=2,
                          clear=True)

    def _clear_bbox(self):
        self._multi_canvas[self._box_layer].clear()

    @traitlets.observe('image_path')
    def _draw_image(self, image):
        self._image_scale = draw_img(self._multi_canvas[self._image_layer],
                                     self.image_path,
                                     clear=True)
        self._im_name_box.value = Path(self.image_path).name

    @property
    def image_scale(self):
        return self._image_scale

    def _clear_image(self):
        self._multi_canvas[self._image_layer].clear()

    # needed to support voila
    # https://ipycanvas.readthedocs.io/en/latest/advanced.html#ipycanvas-in-voila
    def observe_client_ready(self, cb=None):
        self._multi_canvas.on_client_ready(cb)
Exemple #2
0
class Board:
    def __init__(self, scale=3, buckets_height=150):
        self.scale = scale
        self.buckets_height = buckets_height

        self.toposim = TopographySimulator()
        self.particles = Particles(self.toposim, scale)
        self.buckets = Buckets(self.particles, scale)

        self.setup_canvas()
        self.setup_play_widgets()
        self.setup_particles_widgets()
        self.setup_toposim_widgets()
        self.setup_layout()

        self.process = None
        self._running = False

    def setup_canvas(self):
        # canvas 0: topography
        # canvas 1: particles
        # canvas 2: buckets

        canvas_size = (self.scale * self.toposim.shape[0],
                       self.scale * self.toposim.shape[1] +
                       self.buckets_height)

        self.canvas = MultiCanvas(ncanvases=3, size=canvas_size)
        self.canvas.on_client_ready(self.redraw)

    def setup_play_widgets(self):
        self.play_widgets = {
            'start': Button(description="Start", icon='play'),
            'stop': Button(description="Stop/Reset",
                           icon='stop',
                           disabled=True)
        }

        self.play_widgets['start'].on_click(self.start)
        self.play_widgets['stop'].on_click(self.stop)

    def setup_particles_widgets(self):
        self.particles_labels = {
            'size': Label(value='Number of particles'),
            'speed': Label(value='Particle "speed"')
        }

        self.particles_widgets = {
            'size': IntSlider(value=10000, min=500, max=15000, step=500),
            'speed': FloatSlider(value=0.5, min=0.1, max=1., step=0.1)
        }

        self.particles_widgets['size'].observe(self.on_change_size,
                                               names='value')
        self.particles_widgets['speed'].observe(self.on_change_speed,
                                                names='value')

    def on_change_size(self, change):
        self.particles.n_particles = change.new
        self.initialize()

    def on_change_speed(self, change):
        self.particles.speed_factor = change.new

    def setup_toposim_widgets(self):
        self.toposim_labels = {
            'kf': Label(value='River incision coefficient'),
            'g': Label(value='River transport coefficient'),
            'kd': Label(value='Hillslope diffusivity'),
            'p': Label(value='Flow partition exponent'),
            'u': Label(value='Plateau uplift rate')
        }

        self.toposim_widgets = {
            'kf':
            FloatSlider(value=1e-4,
                        min=5e-5,
                        max=3e-4,
                        step=1e-5,
                        readout_format='.1e'),
            'g':
            FloatSlider(value=1.,
                        min=0.5,
                        max=1.5,
                        step=0.1,
                        readout_format='.1f'),
            'kd':
            FloatSlider(
                value=0.02,
                min=0.,
                max=0.1,
                step=0.01,
            ),
            'p':
            FloatSlider(value=1.,
                        min=0.,
                        max=10.,
                        step=0.2,
                        readout_format='.1f'),
            'u':
            FloatSlider(value=0.,
                        min=0.,
                        max=1e-3,
                        step=1e-5,
                        readout_format='.1e')
        }

    def set_erosion_params(self):
        self.toposim.set_erosion_params(kf=self.toposim_widgets['kf'].value,
                                        g=self.toposim_widgets['g'].value,
                                        kd=self.toposim_widgets['kd'].value,
                                        p=self.toposim_widgets['p'].value,
                                        u=self.toposim_widgets['u'].value)

    def setup_layout(self):
        play_box = HBox(tuple(self.play_widgets.values()))

        particles_hboxes = []
        for k in self.particles_widgets:
            self.particles_labels[k].layout = Layout(width='200px')
            self.particles_widgets[k].layout = Layout(width='200px')

            particles_hboxes.append(
                HBox([self.particles_labels[k], self.particles_widgets[k]]))

        particles_label = HTML(value='<b>Particles parameters</b>')
        particles_box = VBox(particles_hboxes)
        particles_box.layout = Layout(grid_gap='6px')

        toposim_hboxes = []
        for k in self.toposim_widgets:
            self.toposim_labels[k].layout = Layout(width='200px')
            self.toposim_widgets[k].layout = Layout(width='200px')

            toposim_hboxes.append(
                HBox([self.toposim_labels[k], self.toposim_widgets[k]]))

        toposim_label = HTML(
            value='<b>Landscape evolution model parameters</b>')
        toposim_box = VBox(toposim_hboxes)
        toposim_box.layout = Layout(grid_gap='6px')

        control_box = VBox((play_box, particles_label, particles_box,
                            toposim_label, toposim_box))
        control_box.layout = Layout(grid_gap='10px')

        self.main_box = HBox((self.canvas, control_box))
        self.main_box.layout = Layout(grid_gap='30px')

    def initialize(self):
        self.toposim.initialize()
        self.particles.initialize()
        self.buckets.initialize()

        self.redraw()

    def run(self):
        while self._running and not self.buckets.all_in_buckets:
            self.set_erosion_params()

            self.toposim.run_step()
            self.particles.run_step()
            self.buckets.run_step()

            self.redraw()

        self.draw_winner()
        self.play_widgets['stop'].description = "Reset"
        self.play_widgets['stop'].icon = "retweet"

    def toggle_disabled(self):
        for w in self.play_widgets.values():
            w.disabled = not w.disabled

        w = self.particles_widgets['size']
        w.disabled = not w.disabled

    def start(self, b):
        self.process = Thread(target=self.run)
        self._running = True
        self.process.start()
        self.toggle_disabled()

    def stop(self, b):
        self._running = False
        self.process.join()
        self.reset()
        self.toggle_disabled()

    def reset(self):
        self.toposim.reset()
        self.particles.reset()
        self.buckets.reset()

        self.redraw()

        self.play_widgets['stop'].description = "Stop/Reset"
        self.play_widgets['stop'].icon = "stop"

    def redraw(self):
        self.draw_topography()
        self.draw_particles()
        self.draw_buckets()

    def draw_topography(self):
        with hold_canvas(self.canvas[0]):
            self.canvas[0].save()
            self.canvas[0].scale(self.scale)
            self.canvas[0].clear()
            self.canvas[0].put_image_data(self.toposim.shaded_topography, 0, 0)
            self.canvas[0].restore()

    def draw_particles(self):
        x, y = self.particles.positions

        with hold_canvas(self.canvas[1]):
            self.canvas[1].clear()
            self.canvas[1].global_alpha = 0.4
            self.canvas[1].fill_style = '#3378b8'
            self.canvas[1].fill_rects(x, y, self.particles.sizes)

    def draw_buckets(self):
        xsize, ysize = self.canvas[2].size

        with hold_canvas(self.canvas[2]):
            self.canvas[2].clear()

            self.canvas[2].font = '20px serif'
            self.canvas[2].fill_style = 'black'

            for i, x in enumerate(self.buckets.x_separators[0:-1]):
                self.canvas[2].fill_text(f"{i+1:02d}", x + 15, ysize - 120)

            self.canvas[2].fill_rects(self.buckets.x_separators,
                                      self.toposim.shape[0] * self.scale, 1,
                                      self.buckets_height)

            self.canvas[2].fill_style = '#3378b8'
            self.canvas[2].fill_rects(self.buckets.x_separators + 5,
                                      ysize - self.buckets.bar_heights,
                                      self.buckets.bar_width - 10,
                                      self.buckets.bar_heights)

            self.canvas[2].fill_style = 'black'
            self.canvas[2].fill_rect(0, ysize - 3, xsize, 3)
            self.canvas[2].fill_rect(0, ysize - 155, xsize, 10)

    def draw_winner(self):
        xsize, ysize = self.canvas[2].size

        winner = self.buckets.count.argmax()

        self.canvas[2].font = '50px serif'
        self.canvas[2].fill_style = '#3378b8'
        self.canvas[2].fill_text(f"{winner+1:02d} wins!", xsize // 3,
                                 ysize // 2.5)

    def show(self):
        self.initialize()

        return self.main_box