Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 merge #36

Merged
merged 26 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
00efefb
Simplifying to processify the detector
TheMariday Oct 14, 2024
9a5b7ba
pytest passes, that's a start. Lots of stuff stripped:
TheMariday Oct 18, 2024
3bb9cbb
getting there
TheMariday Oct 18, 2024
f88ee5a
oooh this is gross
TheMariday Oct 18, 2024
3225b64
getting there, I should stop
TheMariday Oct 18, 2024
5ad182e
That's enough for tonight
TheMariday Oct 18, 2024
29e77c5
That's enough for tonight
TheMariday Oct 18, 2024
f1cba74
Normals are back! Next stop, cameras
TheMariday Oct 19, 2024
3e30233
cameras are back!
TheMariday Oct 19, 2024
da330b9
sorted out a lot of multiprocessing issues
TheMariday Oct 19, 2024
df2f962
Fixed tests
TheMariday Oct 19, 2024
b10c4aa
I can reconstruct!
TheMariday Oct 19, 2024
298e498
tidied up logging
TheMariday Oct 20, 2024
dda9b92
tidied up logging
TheMariday Oct 20, 2024
440354d
just a bit of tidying
TheMariday Oct 20, 2024
f499110
- Added strips back into the visualiser
TheMariday Oct 20, 2024
ba16995
- added merging back in
TheMariday Oct 20, 2024
48f5db7
quick fix to sort out normals of interpolated leds
TheMariday Oct 20, 2024
9549e9f
This needs a read-over
TheMariday Oct 23, 2024
d30144c
fix to unions
TheMariday Oct 23, 2024
6b9ca38
Tests should now be able to run pretty much anywhere
TheMariday Oct 24, 2024
8b732bf
- tidied up some bugs
TheMariday Oct 24, 2024
7ed22c2
- removed some dead code
TheMariday Oct 24, 2024
5cc021f
- removed changes to github pipeline
TheMariday Oct 24, 2024
7feb2b7
- style fix
TheMariday Oct 24, 2024
b1c8d85
- small bugs fixes
TheMariday Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
paths-ignore:
- "README.md"
- "docs/**"
- "scripts/**"
- "marimapper/scripts/**"
pull_request:
branches: [ "main" ]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
paths-ignore:
- "README.md"
- "docs/**"
- "scripts/**"
- "marimapper/scripts/**"
pull_request:
branches: [ "main" ]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
paths-ignore:
- "README.md"
- "docs/**"
- "scripts/**"
- "marimapper/scripts/**"
pull_request:
branches: [ "main" ]

Expand Down
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,9 @@ Fill out the blanks and check it by running `marimapper_check_backend --backend

## Step 3: [It's time to thunderize!](https://youtu.be/-5KJiHc3Nuc?t=121)

Run `marimapper --dir my_scan --backend fadecandy`
In a new folder, run `marimapper --backend fadecandy`

Change `my_scan` to the directory you want to save your scan
and `fadecandy` to whatever backend you're using
and `fadecandy` to whatever backend you're using and use `--help` to show more options

Set up your LEDs so most of them are in view and when you're ready, type `y` when prompted with `Start scan? [y/n]`

Expand Down Expand Up @@ -167,20 +166,6 @@ Here is an example reconstruction of a test tube of LEDs I have
- Use `1`, `2` & `3` keys to change colour scheme
</details>

# Random other stuff that doesn't really fit anywhere

There's also a tool to turn your 3D scan into a 3D model, run it with `marimapper_remesh my_3d_map.csv`

You can visualise 2D scans with `marimapper_view_2d_scan led_map_2d_0.csv`

If you want to develop with MariMapper, you can use
`pip install "marimapper[develop] @ git+http://github.com/themariday/marimapper"`
to grab all the tools you need. Flake8, Black, etc.

When installing Marimapper, it will adjust your Python packages to the correct versions.
If you don't want this, then run it inside a venv.
If you're worried about library pollution then I assume you know how to use a venv.

# Feedback

I would really love to hear what you think and if you have any bugs or improvements, please raise them here or drop me a
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions marimapper/backends/dummy/dummy_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Backend:

def __init__(self):
pass

def get_led_count(self):
return 0

def set_led(self, led_index: int, on: bool):
pass
6 changes: 4 additions & 2 deletions marimapper/backends/fcmega/fcmega.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import serial
import struct
import serial.tools.list_ports
from marimapper import logging
from multiprocessing import get_logger

logger = get_logger()


class FCMega:
Expand All @@ -24,7 +26,7 @@ def __init__(self, port=None):
def _get_port(self):
for device in serial.tools.list_ports.comports():
if device.serial_number.startswith("FCM"):
logging.info(f"found port {device.name}")
logger.info(f"found port {device.name}")
return device.name

return None
Expand Down
6 changes: 4 additions & 2 deletions marimapper/backends/pixelblaze/pixelblaze_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from marimapper import logging
from multiprocessing import get_logger
import pixelblaze

logger = get_logger()


class Backend:

Expand All @@ -12,7 +14,7 @@ def __init__(self, pixelblaze_ip="4.3.2.1"):

def get_led_count(self):
pixel_count = self.pb.getPixelCount()
logging.info(f"Pixelblaze reports {pixel_count} pixels")
logger.info(f"Pixelblaze reports {pixel_count} pixels")
return pixel_count

def set_led(self, led_index: int, on: bool):
Expand Down
12 changes: 7 additions & 5 deletions marimapper/backends/pixelblaze/upload_map_to_pixelblaze.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import csv

from marimapper import utils
from marimapper import logging
from multiprocessing import get_logger

logger = get_logger()


def read_coordinates_from_csv(csv_file_name):
logging.info(f"Loading coordinates from {csv_file_name}")
logger.info(f"Loading coordinates from {csv_file_name}")
with open(csv_file_name, newline="") as csvfile:
csv_reader = csv.DictReader(csvfile)
list_of_leds = []
Expand Down Expand Up @@ -33,17 +35,17 @@ def read_coordinates_from_csv(csv_file_name):

def upload_map_to_pixelblaze(cli_args):
final_coordinate_list = read_coordinates_from_csv(cli_args.csv_file)
logging.info(final_coordinate_list)
logger.info(final_coordinate_list)

upload_coordinates = utils.get_user_confirmation(
"Upload coordinates to Pixelblaze? [y/n]: "
)
if not upload_coordinates:
return

logging.info(
logger.info(
f"Uploading coordinates to pixelblaze {cli_args.server if cli_args.server is not None else ''}"
)
led_backend = utils.get_backend("pixelblaze", cli_args.server)
led_backend.set_map_coordinates(final_coordinate_list)
logging.info("Finished")
logger.info("Finished")
68 changes: 22 additions & 46 deletions marimapper/camera.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import cv2
from marimapper import logging
from multiprocessing import get_logger

logger = get_logger()


class CameraSettings:

def __init__(self, camera):
self.width = camera.get_width()
self.height = camera.get_height()
self.af_mode = camera.get_af_mode()
self.focus = camera.get_focus()
self.exposure_mode = camera.get_exposure_mode()
self.exposure = camera.get_exposure()
self.gain = camera.get_gain()

def apply(self, camera):
camera.set_resolution(self.width, self.height)
camera.set_autofocus(self.af_mode, self.focus)
camera.set_exposure_mode(self.exposure_mode)
camera.set_gain(self.gain)
Expand All @@ -24,27 +23,26 @@ def apply(self, camera):
class Camera:

def __init__(self, device_id):
logging.info(f"Connecting to camera {device_id} ...")
logger.info(f"Connecting to device {device_id} ...")
self.device_id = device_id

for capture_method in [cv2.CAP_DSHOW, cv2.CAP_V4L2, cv2.CAP_ANY]:
self.device = cv2.VideoCapture(device_id, capture_method)
if self.device.isOpened():
logging.debug(
f"Connected to camera {device_id} with capture method {capture_method}"
logger.debug(
f"Connected to device {device_id} with capture method {capture_method}"
)
break

if not self.device.isOpened():
raise RuntimeError(f"Failed to connect to camera {device_id}")

self.set_resolution(self.get_width(), self.get_height()) # Don't ask
self.default_settings = CameraSettings(self)

def get_width(self):
return int(self.device.get(cv2.CAP_PROP_FRAME_WIDTH))
self.state = "default"

def get_height(self):
return int(self.device.get(cv2.CAP_PROP_FRAME_HEIGHT))
def reset(self):
self.default_settings.apply(self)

def get_af_mode(self):
return int(self.device.get(cv2.CAP_PROP_AUTOFOCUS))
Expand All @@ -61,66 +59,44 @@ def get_exposure(self):
def get_gain(self):
return int(self.device.get(cv2.CAP_PROP_GAIN))

def set_resolution(self, width, height):

logging.debug(f"Setting camera resolution to {width} x {height} ...")

self.device.set(cv2.CAP_PROP_FRAME_WIDTH, width)
self.device.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

new_width = self.get_width()
new_height = self.get_height()

# this is cov ignored as it's a strange position to be in but ultimately fine
if width != new_width or height != new_height: # pragma: no cover
logging.error(
f"Failed to set camera {self.device_id} resolution to {width} x {height}",
)

logging.debug(f"Camera resolution set to {new_width} x {new_height}")

def set_autofocus(self, mode, focus=0):

logging.debug(f"Setting autofocus to mode {mode} with focus {focus}")
logger.debug(f"Setting autofocus to mode {mode} with focus {focus}")

if not self.device.set(cv2.CAP_PROP_AUTOFOCUS, mode):
logging.error(f"Failed to set autofocus to {mode}")
logger.error(f"Failed to set autofocus to {mode}")

if not self.device.set(cv2.CAP_PROP_FOCUS, focus):
logging.error(f"Failed to set focus to {focus}")
logger.error(f"Failed to set focus to {focus}")

def set_exposure_mode(self, mode):

logging.debug(f"Setting exposure to mode {mode}")
logger.debug(f"Setting exposure to mode {mode}")

if not self.device.set(cv2.CAP_PROP_AUTO_EXPOSURE, mode):
logging.error(f"Failed to put camera into manual exposure mode {mode}")
logger.error(f"Failed to put camera into manual exposure mode {mode}")

def set_gain(self, gain):

logging.debug(f"Setting gain to {gain}")
logger.debug(f"Setting gain to {gain}")

if not self.device.set(cv2.CAP_PROP_GAIN, gain):
logging.error(f"failed to set camera gain to {gain}")
logger.error(f"failed to set camera gain to {gain}")

def set_exposure(self, exposure):

logging.debug(f"Setting exposure to {exposure}")
logger.debug(f"Setting exposure to {exposure}")

if not self.device.set(cv2.CAP_PROP_EXPOSURE, exposure):
logging.error(f"Failed to set exposure to {exposure}")
logger.error(f"Failed to set exposure to {exposure}")

def eat(self, count=30):
for _ in range(count):
self.read()

def read(self, color=False):
def read(self):
ret_val, image = self.device.read()
if not ret_val:
logging.error("Failed to grab frame")
return None
raise Exception("Failed to read image")

if not color:
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
return image
return image
31 changes: 18 additions & 13 deletions marimapper/database_populator.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
from itertools import combinations
from math import radians, tan

import os
from multiprocessing import get_logger
import numpy as np

from marimapper.pycolmap_tools.database import COLMAPDatabase
from marimapper.led import LED2D, get_view_ids, get_leds_with_view

ARBITRARY_SCALE = 2000

logger = get_logger()

def populate(db_path, led_maps_2d):

map_features = np.zeros((len(led_maps_2d), 1, 2))
def populate_database(db_path: os.path, leds: list[LED2D]):
logger.debug(f"Populating sfm database with {len(leds)} leds, path: {db_path}")
views = get_view_ids(leds)
map_features = np.zeros((max(views) + 1, 1, 2))

for map_index, led_map_2d in enumerate(led_maps_2d):
for view in views:

for led_index in led_map_2d.led_indexes():
for led in get_leds_with_view(leds, view):

pad_needed = led_index - map_features.shape[1] + 1
pad_needed = led.led_id - map_features.shape[1] + 1
if pad_needed > 0:
map_features = np.pad(map_features, [(0, 0), (0, pad_needed), (0, 0)])

map_features[map_index][led_index] = (
np.array(led_map_2d.get_detection(led_index).pos()) * 2000
)
map_features[view][led.led_id] = led.point.position * ARBITRARY_SCALE

db = COLMAPDatabase.connect(db_path)

Expand All @@ -29,8 +34,8 @@ def populate(db_path, led_maps_2d):
# model=0 means that it's a "SIMPLE PINHOLE" with just 1 focal length parameter that I think should get optimised
# the params here should be f, cx, cy

width = 2000
height = 2000
width = ARBITRARY_SCALE
height = ARBITRARY_SCALE
fov = 60 # degrees, this gets optimised so doesn't //really// matter that much

SIMPLE_PINHOLE = 0
Expand All @@ -45,13 +50,13 @@ def populate(db_path, led_maps_2d):

# Create dummy images_all_the_same.

image_ids = [db.add_image(str(i), camera_id) for i in range(len(led_maps_2d))]
image_ids = [db.add_image(str(view), camera_id) for view in range(max(views) + 1)]

# Create some keypoints
for i, keypoints in enumerate(map_features):
db.add_keypoints(image_ids[i], keypoints)

for view_1_id, view_2_id in combinations(range(len(led_maps_2d)), 2):
for view_1_id, view_2_id in combinations(views, 2):
view_1_keypoints = map_features[view_1_id]
view_2_keypoints = map_features[view_2_id]

Expand Down
Loading