introducing active-map

Let's keep tweaking our GUI. I decided to implement what I call an active map. Code below. Instead of changing the camera angle by clicking up 10 degrees, left 10 degrees, up another 10 degrees, how about we have a map. The map covers the full 0-180 * 0-180 angle range (of the two camera servos), and you click on the location you want the camera to move to. After an afternoon of coding, I now had:

I'll dive straight into the code. First we need these two helper functions:
def constrain(val, min_val, max_val):
    val = max(val, min_val)
    val = min(val, max_val)
    return val

def num_map(value, fromLow, fromHigh, toLow, toHigh):
    return (toHigh-toLow)*(value-fromLow) / (fromHigh-fromLow) + toLow
constrain ensures val is in the range min_val <= val <= max_val, while num_map maps val from the range [fromLow, fromHigh] to the range [toLow, toHigh]. And here is our new active-map class:
class ActiveMap():
    def __init__(self, name, width, val, maxi, mini, xpos, ypos, action=None):
        self.width = width
        self.val = val  # start value
        self.maxi = maxi  # maximum at position right
        self.mini = mini  # minimum at position left
        self.xpos = xpos  # x-location on screen
        self.ypos = ypos  # y-location on screen
        self.hit = False  # the hit attribute indicates slider movement due to mouse interaction
        self.call_back = action

        w = self.width
        s = w // 10  # step size
        c = w // 2  # center
        self.surface = pygame.surface.Surface((w + 50, w + 50))
        self.rect = pygame.Rect(xpos, ypos, xpos + w, ypos + w)
        self.surface.fill(WHITE)
        pygame.draw.rect(self.surface, GREY, [0, 0, w, w], 0)
        pygame.draw.rect(self.surface, BLACK, [0, 0, w, w], 1)
        pygame.draw.rect(self.surface, BLACK, [s, s, w - 2 * s, w - 2 * s], 1)
        pygame.draw.rect(self.surface, BLACK, [2 * s, 2 * s, w - 4 * s, w - 4 * s], 1)
        pygame.draw.rect(self.surface, BLACK, [3 * s, 3 * s, w - 6 * s, w - 6 * s], 1)
        pygame.draw.rect(self.surface, BLACK, [4 * s, 4 * s, w - 8 * s, w - 8 * s], 1)
        pygame.draw.line(self.surface, BLACK, (0, c), (w, c), 1)
        pygame.draw.line(self.surface, BLACK, (c, 0), (c, w), 1)

        # button surface:
        self.button_surf = pygame.surface.Surface((20, 20))
        self.button_surf.fill(TRANS)
        self.button_surf.set_colorkey(TRANS)
        pygame.draw.circle(self.button_surf, BLACK, (10, 10), 6, 0)
        pygame.draw.circle(self.button_surf, ORANGE, (10, 10), 4, 0)

        # define our font for the horizontal and vertical values:
        self.font = pygame.font.SysFont("Verdana", 16)

        # horizontal value surface:
        self.horizontal_surf = pygame.surface.Surface((20, 20))
        self.txt_surf_x = self.font.render(str(int(self.val[0])), 1, BLACK)
        self.txt_rect_x = self.txt_surf_x.get_rect(center=(self.width // 2, self.width + 20))

        # vertical value surface:
        self.vertical_surf = pygame.surface.Surface((20, 20))
        self.txt_surf_y = self.font.render(str(int(self.val[1])), 1, BLACK)
        self.txt_rect_y = self.txt_surf_y.get_rect(center=(self.width + 30, self.width // 2))

    def draw(self):
        # static
        surface = self.surface.copy()

        # current-location button:
        # num_map(value, fromLow, fromHigh, toLow, toHigh)
        xpos = num_map(self.val[0], self.mini, self.maxi, 0, self.width)
        ypos = num_map(self.val[1], self.mini, self.maxi, 0, self.width)
        self.button_rect = self.button_surf.get_rect(center=(xpos, ypos))
        surface.blit(self.button_surf, self.button_rect)
        self.button_rect.move_ip(self.xpos, self.ypos)  # move of button box to correct screen position

        # horizontal value:
        surface.blit(self.txt_surf_x, self.txt_rect_x)

        # vertical value:
        surface.blit(self.txt_surf_y, self.txt_rect_y)

        # screen
        screen.blit(surface, (self.xpos, self.ypos))

    def move(self):
        self.val[0] = num_map(pygame.mouse.get_pos()[0] - self.xpos, 0, self.width, self.mini, self.maxi)
        self.val[1] = num_map(pygame.mouse.get_pos()[1] - self.ypos, 0, self.width, self.mini, self.maxi)
        self.val[0] = int(constrain(self.val[0], self.mini, self.maxi))
        self.val[1] = int(constrain(self.val[1], self.mini, self.maxi))

        self.txt_surf_x = self.font.render(str(self.val[0]), 1, BLACK)
        self.txt_surf_y = self.font.render(str(self.val[1]), 1, BLACK)
        if self.call_back is not None:
            self.call_back(self.val)
Now let's define our camera active map:
    # define our camera active map:
    camera_map = ActiveMap('camera', 230, [90, 90], 180, 0, 420, 285, map_moved)

    # define our list of active maps:
    active_maps = [camera_map]
Define our map_moved() call-back function:
def map_moved(val):
    global state_servo_2
    global state_servo_3
    state_servo_2 = val[0]
    state_servo_3 = val[1]
    if have_smbus:
        write_servo(CMD_SERVO2, 180 - state_servo_2 - fine_servo_2.val)
        write_servo(CMD_SERVO3, 180 - state_servo_3 - fine_servo_3.val)
Then finally, the main event loop:
    # the event loop:
    while True:
        if have_camera:
            if cam.query_image():
                img = cam.get_image()
            screen.blit(img, (2, 2))
            pygame.display.flip()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                if have_camera:
                    cam.stop()
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                pos = pygame.mouse.get_pos()
                for button in buttons:
                    if button.rect.collidepoint(pos):
                        button.call_back_down()
                for s in slides:
                    if s.button_rect.collidepoint(pos):
                        s.hit = True
                for amap in active_maps:
                    if amap.rect.collidepoint(pos):
                        amap.hit = True

            elif event.type == pygame.MOUSEBUTTONUP:
                pos = pygame.mouse.get_pos()
                for button in buttons:
                    if button.rect.collidepoint(pos):
                        button.call_back_up()
                for s in slides:
                    s.hit = False
                for amap in active_maps:
                    amap.hit = False

        # draw buttons:
        for button in buttons:
            button.draw()

        # move slides:
        for s in slides:
            if s.hit:
                s.move()

        # draw slides:
        for s in slides:
            s.draw()

        # move active map:
        for amap in active_maps:
            if amap.hit:
                amap.move()

        # draw active maps:
        for amap in active_maps:
            amap.draw()

        pygame.display.flip()
        pygame.time.wait(40)
Putting it all together, we have main_v3.py:
Now that that was in place, I made some tweaks so we can change the camera size from (320, 240) to say (640, 480), or even larger, but that seems to slow the GUI. And with the size change, the buttons/sliders/active-map all auto-place themselves to reasonable positions. See main_v4.py. Next post we start work on opencv.

Comments

Popular posts from this blog

using edge-enhance

panorama-scan