Exemple #1
0
def server(registration, board, flip):
    """Runs camera server process used to provide gateway to captured images
    and fiducial locations.

    Serves HTTP API on port 5000, which can be used by `pdserver`.
    """
    from purpledrop.pdcam.server import create_app

    board_name = board
    if board_name is not None:
        board = load_board(board_name)
        if board is None:
            raise ValueError(f"No board found with name {board_name}")

    if registration is not None:
        registration = load_registration(registration)
    elif board is not None:
        registration = board.registration

    if board is None:
        layout = None
    else:
        layout = board.layout

    app = create_app(registration, layout, flip)
    app.run(host="0.0.0.0")
Exemple #2
0
def test_load_misl_v5():
    """Test loading `board/misl_v5.json` definition
    """
    board = electrode_board.load_board('misl_v5')
    assert isinstance(board, electrode_board.Board)
    assert isinstance(board.layout, electrode_board.Layout)
    assert board.registration is None
Exemple #3
0
def test_load_misl_v6():
    """Test loading `board/misl_v6.json` definition
    """
    board = electrode_board.load_board('misl_v6')
    assert isinstance(board, electrode_board.Board)
    assert isinstance(board.layout, electrode_board.Layout)
    assert len(board.registration.fiducials) == 2
    assert len(board.registration.control_points) == 6
Exemple #4
0
def test_pin_polygon():
    board = electrode_board.load_board('misl_v6')

    # Test getting a grid electrode
    assert np.array_equal(
        board.layout.pin_polygon(15),
        [[18.75, 8.25], [18.75, 12.0], [22.5, 12.0], [22.5, 8.25]])

    # Test getting a peripheral electrode
    np.testing.assert_array_equal(
        board.layout.pin_polygon(76),
        np.array([[1.25, 0.0], [-1.25, 0.0], [-1.25, 2.5], [0.0, 4.0],
                  [1.25, 2.5], [1.25, 0.0]]) + [60.625, 38.25])
Exemple #5
0
def test_find_grid_transform_v4_1_sample1():
    """Load a sample image and try to find the registration
    """
    img = load_image('electrode_board_v4.1_sample1.jpg')
    board = load_board('misl_v4.1')
    reference = board.registration

    transform, fiducials = find_grid_transform(img, reference)

    print(f"tranform: {transform}")
    print(f"fiducials: {fiducials}")
    # There should be 3 fiducials, and they should be labeled with integers
    # 4, 5, 6 in no particular order
    assert len(fiducials) == 3
    assert sorted([f.label for f in fiducials]) == [4, 5, 6]

    assert np.allclose(TRANSFORM_V4_1_SAMPLE1, transform)
def main(verbose, board_file, replay_file):
    """Runs hardware gateway

    Will auto-connect to any detected purpledrop USB devices, and provides HTTP interfaces for control.
    Optionally, supports replay mode in which a recorded event stream is played back
    """
    if verbose == 0:
        console_log_level = logging.WARNING
    elif verbose == 1:
        print("Setting stdout logging to INFO")
        console_log_level = logging.INFO
    else:
        print("Setting stdout logging to DEBUG")
        console_log_level = logging.DEBUG

    logging.basicConfig(
        format="%(asctime)s.%(msecs)03d %(levelname)s (%(name)s): %(message)s",
        datefmt="%H:%M:%S",
        level=console_log_level)

    board = load_board(board_file)
    if board is None:
        print(f"Could not load board definition for {board_file}")
        sys.exit(1)

    video_client = None
    if replay_file is not None:
        print(f"Computing seek index for {replay_file}...")
        index, end_time = index_log(replay_file)
        start_time = index[0].timestamp
        print(f"Done. Loaded {end_time - start_time} seconds of data.")
        print("Launching replay server...")
        pd_control = PlaybackPurpleDrop(replay_file, index, board)
    else:
        pd_dev = PersistentPurpleDropDevice()
        pd_control = PurpleDropController(pd_dev, board)
        print("Launching HW server...")
        # TODO: make video host configurable
        video_host = "localhost:5000"
        video_client = VideoClientProtobuf(video_host)

    server.run_server(pd_control, video_client)
def find_reference_from_fiducials(search_labels: Tuple[int]) -> Optional[Registration]:
    """Look for a board definition that matches the fiducial tags found

    A matching board definition must include all of the fiducial labels, and it
    must be unique. It's possible for multiple boards to match, in which case
    no reference is returned. Each of the provided fiducials must have a unique
    label, or no board can be matched.

    This function is memoized to prevent searching boards on repeated image.
    This means that the process must be restarted if the board definition files
    are changed for the new data to take effect.

    Args:
        search_labels: Tuple of integer labels for the fiducials to lookup
    Returns: 
        A Registration object if one is found, or None otherwise
    """
    board_names = list_boards()
    matched_boards = []
    for l in search_labels:
        if search_labels.count(l) != 1:
            logger.warn(f"Cannot lookup reference board because of repeated tag in {search_labels}")
            return None
            
    for name in board_names:
        board = load_board(name)
        if board is not None and board.registration is not None:
            board_labels = [f.label for f in board.registration.fiducials]
            if all([l in board_labels for l in search_labels]):
                matched_boards.append(board)
    
    if len(matched_boards) == 1:
        return matched_boards[0].registration
    if len(matched_boards) > 1:
        logger.warn(f"Found multiple boards matching fiducials {search_labels}")
    else:
        logger.warn(f"Found no matching board for fiducials {search_labels}")
    return None
Exemple #8
0
def test_find_grid_transform_v4_1_sample2():
    """Load a sample image and try to find the registration
    
    This sample is the same as sample1, but one of the fiducials
    is covered. We should still be able to find a transform with just two
    fiducials detected.
    """
    img = load_image('electrode_board_v4.1_sample2.jpg')
    board = load_board('misl_v4.1')
    reference = board.registration

    transform, fiducials = find_grid_transform(img, reference)

    print(f"tranform: {transform}")
    print(f"fiducials: {fiducials}")
    # Only 2 fiducials are visible, 5 and 6, and are returned in no particular
    # order
    assert len(fiducials) == 2
    assert sorted([f.label for f in fiducials]) == [5, 6]

    # Allow some tolerance in transform; using a different set of fiducials will
    # result in a similar but different result
    assert transform is not None
    assert_allclose(TRANSFORM_V4_1_SAMPLE1, transform, rtol=0.1, atol=0.6)
Exemple #9
0
def measure(imagefile, outfile, v4, v4_1, v5, layout, points, verbose):
    """Launch UI to make calibration measurements from an image of an
    electrode board.
    """

    # TODO: Instead of hardcoding the layouts, load the electrode layout from
    # board definition files (e.g. with --board option), and auto compute a 
    # good set of control electrodes
    img = cv2.cvtColor(cv2.imread(imagefile), cv2.COLOR_BGR2RGB)

    def read_input_tuple(x):
        try:
            return tuple(int(n) for n in x.split(','))
        except ValueError as ex:
            raise ValueError(f"Failed parsing point {x}: {ex}")

    fiducials = find_fiducials(img)
    for f in fiducials:
        print(f)
        mark_fiducial(img, f.corners)
    
    electrode_layout = ELECTRODE_LAYOUT_v3
    control_electrodes = CONTROL_ELECTRODES_v3
    pitch = 1.0
    grid_origin = (0.0, 0.0)
    if layout:
        if points is None or len(points) < 4:
            raise ValueError("If providing a custom layout, you must provide at least 4 calibration points with --point")
        board = load_board(layout)
        electrode_layout = board.layout.grids[0]['pins']
        control_electrodes = [read_input_tuple(p) for p in points]
        pitch = board.layout.grids[0]['pitch']
        origin = board.layout.grids[0]['origin']
    if v4:
        electrode_layout = ELECTRODE_LAYOUT_v4
        control_electrodes = CONTROL_ELECTRODES_v4
    elif v4_1:
        electrode_layout = ELECTRODE_LAYOUT_v4_1
        control_electrodes = CONTROL_ELECTRODES_v4_1
    elif v5: 
        electrode_layout = ELECTRODE_LAYOUT_v5
        control_electrodes = CONTROL_ELECTRODES_v5


    if verbose:
        print(f"Using control points: {control_electrodes}")
        print(f"Using layout: {electrode_layout}")

    alignment_electrodes = control_electrodes
    fig = plt.figure()
    gs = fig.add_gridspec(4, 4)
    
    ax1 = fig.add_subplot(gs[:, :-1])
    plt.imshow(img)
    ax1.set_title('Click the top-left corner of the indicated electrode')

    ax2 = fig.add_subplot(gs[:1, -1])
    plot_template(ax2, electrode_layout, [alignment_electrodes[0]])
    ax2.invert_yaxis()
    
    alignment_points = []
    def onclick(event):
        ix, iy = event.xdata, event.ydata

        alignment_points.append((ix, iy))
        
        if len(alignment_points) == len(alignment_electrodes):
            fig.canvas.mpl_disconnect(cid)
            plt.close(1)
        else:
            ax2.clear()
            plot_template(ax2, electrode_layout, [alignment_electrodes[len(alignment_points)]])
            ax2.invert_yaxis()
            
            ax1.plot(alignment_points[-1][0], alignment_points[-1][1], 'ro')
            fig.canvas.draw()
    
    cid = fig.canvas.mpl_connect('button_press_event', onclick)
    fig.tight_layout()
    plt.ion()
    plt.show(block=True)

    if len(alignment_points) != len(alignment_electrodes):
        print("Figure closed without collecting enough control points. No file will be saved")
        return

    for(n, p) in zip(alignment_electrodes, alignment_points):
        print("%s: (%d, %d)\n" % (n, p[0], p[1]))
    
    def map_fiducial(f):
        def to_tuple(point):
            return (point[0], point[1])
        return [to_tuple(p) for p in f.corners]

    data = {
        'fiducials': [f.to_dict() for f in fiducials],
        'control_points': [ {"grid": (n[0]*pitch + origin[0], n[1]*pitch + origin[1]), "image": p} for n,p in zip(alignment_electrodes, alignment_points) ]
    }

    print("Storing reference data to %s" % outfile)
    with open(outfile, 'w') as f:
        f.write(json.dumps(data))