Skip to content

Commit

Permalink
Merge branch 'main' into v3.0
Browse files Browse the repository at this point in the history
# Conflicts:
#	marimapper/led.py
#	marimapper/scanner.py
#	marimapper/sfm_process.py
#	marimapper/utils.py
#	pyproject.toml
#	test/test_led_functions.py
  • Loading branch information
TheMariday committed Jan 10, 2025
2 parents 26dfdaf + 094244e commit 03d58da
Show file tree
Hide file tree
Showing 22 changed files with 285 additions and 150 deletions.
98 changes: 24 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,12 @@ You can run the scripts anywhere by just typing them into a console
Run `marimapper_check_camera` to ensure your camera is compatible with MariMapper, or check the list below:

<details>

<summary>Working Cameras</summary>

- HP 4310 (settings may not revert)
- Logitech C920
- Dell Latitude 5521 built-in
- HP Envy x360 built-in
- If your camera works, please drop me a line, so I can add it to the list!

</details>
If your camera works, please drop me a line, so I can add it to the list!


Test LED identification by turning down the lights and holding a torch or led up to the camera.
Expand All @@ -71,84 +66,34 @@ Wrong webcam? MariMapper tools use `--device 0` by default, use `--device 1` to


> [!TIP]
> If your camera doesn't support exposure adjustment, or the image is still too bright, try dimming the lights and playing around with:
> If the image is still too bright or you can't see a crosshair on your LED, try dimming the lights and playing around with:
>
> - `--exposure` - The lower the darker, defaults to `-10`, my webcam only goes down to `-11`
> - `--threshold` - The lower the more detections, ranges between `0-255`, defaults to `128`
## Step 2: Choose your backend

For the Marimapper to communicate with your leds, it requires a backend.

The following backends are built-in:

<details>
<summary>Fadecandy</summary>

To use the Fadecandy backend, please ensure that you are running the Fadecandy server
A fork of the Fadecandy repo can be found [here](https://github.com/TheMariday/fadecandy)

Use
`--backend fadecandy --server 127.0.0.1:7890` to enable this backend, adjusting the server IP and port where needed

</details>

<details>
<summary>WLED</summary>

More info can be found [here](https://kno.wled.ge/)

</details>

<details>
<summary>FCMega</summary>

This is a custom driver I've written for the Teensy 4.1 to drive up to 9600 leds.
Source code can be found [here](https://github.com/TheMariday/fcmega)

</details>
Please see below for documentation on how to run the following backends:

<details>
<summary>PixelBlaze</summary>
- [FadeCandy](https://github.com/TheMariday/marimapper/tree/main/docs/backends/FadeCandy.md)
- [WLED](https://github.com/TheMariday/marimapper/tree/main/docs/backends/WLED.md)
- [FCMega](https://github.com/TheMariday/marimapper/tree/main/docs/backends/FCMEGA.md)
- [PixelBlaze](https://github.com/TheMariday/marimapper/tree/main/docs/backends/PixelBlaze.md)

Using Pixelblaze as a backend requires you to upload the
[marimapper.epe](marimapper/backends/pixelblaze/marimapper.epe)
into the PixelBlaze editor and upload it as a new pattern.
If your LED backend isn't supported, you need to write your own,
[it's super simple](https://github.com/TheMariday/marimapper/tree/main/docs/backends/custom.md)!

`--backend PixelBlaze --server 192.168.4.1` to enable this backend, adjusting the server IP where needed
## Step 3: Setup your scene

Once you've completed your 3D map, upload it to pixelblaze using
`marimapper_upload_to_pixelblaze --help`
🪨 Make sure that your camera is stable and won't move, try mounting it on a tripod if you can

</details>
💡 Make sure there are no light sources in your cameras view, tape up power leds and notification lights

If your LED backend isn't supported, you need to write your own.
Open a new python file called `my_backend.py` and copy the below stub into it.
✋ Make sure you can move your camera around without changing the layout of your leds,
even a small nudge can throw off the reconstructor!

```python
class Backend:

def __init__(self):
# connect to some device here!

def get_led_count(self) -> int:
# return the number of leds your system can control here

def set_led(self, led_index: int, on: bool) -> None:
# Write your code for controlling your LEDs here
# It should turn on or off the LED at the led_index depending on the "on" variable
# For example:
# if on:
# some_led_library.set_led(led_index, (255, 255, 255))
# else:
# some_led_library.set_led(led_index, (0, 0, 0))
```

If your backend needs any external libraries for example, `requests`, add them to marimapper with `pipx inject marimapper requests`

Fill out the blanks and check it by running `marimapper_check_backend --backend my_backend.py`


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

In a new folder, run `marimapper --backend fadecandy`

Expand Down Expand Up @@ -176,16 +121,21 @@ Here is an example reconstruction of a test tube of LEDs I have

![](docs/images/live_example.png)

<details>
<summary>How to move the model around</summary>

### How to move the model around

- Click and drag to rotate the model around.
- Hold shift to roll the camera
- Use the scroll wheel to zoom in / out
- Use the `n` key to hide / show normals
- Use the `+` / `-` keys to increase / decrease point sizes
- Use `1`, `2` & `3` keys to change colour scheme
</details>
- Use `1`, `2`, `3` & `4` keys to change colour scheme

### LED Colors:
By default (`1`), the colors of the leds in the visualiser are as follows:

- Green: Reconstructed
- Blue: Interpolated

# Not working?

Expand Down
17 changes: 17 additions & 0 deletions docs/backends/FCMEGA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# FC Mega Backend Tutorial

Why are you here? You don't have an FC Mega, shoo.

In all seriousness, the FC mega is an LED driver of my own creation for one of my upcoming projects.

This was born out of a fondness of the FadeCandy boards back in the day. I loved their plug and play approach.

So I've developed a new one, more powerful and more buggy than ever before!

The key thing is the Teensy that drove the FadeCandy board has been upgraded from a Teensy 2.0 to a 4.1 which means I can control OVER 10'000 LEDS OVER USB MUHAHAHAHAHA!

Source code can be found [here](https://github.com/TheMariday/fcmega) if you are absolutely insane like me.

And again, why are you here? Please don't tell me you're gonna make one of these and use it...

Oh no.
6 changes: 6 additions & 0 deletions docs/backends/FadeCandy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fadecandy Backend Instructions
To use the Fadecandy backend, please ensure that you are running the Fadecandy server
A fork of the Fadecandy repo can be found [here](https://github.com/TheMariday/fadecandy)

Use
`--backend fadecandy --server 127.0.0.1:7890` to enable this backend, adjusting the server IP and port where needed
19 changes: 19 additions & 0 deletions docs/backends/PixelBlaze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# PixelBlaze Backend Tutorial

To use PixelBlaze with marimapper you first need to upload a pattern file to your controller.

You can do this via the web UI by uploading [this file](https://github.com/TheMariday/marimapper/blob/main/marimapper/backends/pixelblaze/marimapper.epe)
as a new pattern.

Once this is done, run `marimapper_check_backend --backend pixelblaze` to test it. It should cause LED 0 to blink.

By default, marimapper tools will use the address `4.3.2.1`, but this can be changed by using the `--server` argument.

Once you've checked your PixelBlaze setup is talking nicely with marimapper, you can go ahead and start mapping!

Once you're done, you can upload your 3D map to pixelblaze by running `marimapper_upload_mapping_to_pixelblaze`
in the same folder as your `led_map_3d.csv`.

Don't forget to add the `--server` argument if you've needed to change it in the previous steps

Now you've learnt the PixelBlaze specifics, shoo! Back to the main README.md with you!
11 changes: 11 additions & 0 deletions docs/backends/WLED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# WLED Backend Tutorial

WLED should work pretty much out of the box, test your backend with `marimapper_check_backend --backend wled`. It should cause LED 0 to blink.

If you need to change the server address, use the `--server` argument

Once you've checked your WLED setup is talking nicely with marimapper, you can go ahead and start mapping!

More info about WLED can be found [here](https://kno.wled.ge/)

Few, that was a short tutorial. Treat yourself to a biscuit for all your hard work!
35 changes: 35 additions & 0 deletions docs/backends/custom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Custom Backend Instructions

Your backend isn't listed? You've your own LED driver? Well check you out clever-clogs!

Luckily, writing your own custom backend is super simple with a dash of Python!

Open a new python file called `my_backend.py` and copy the below stub into it.

```python
class Backend:

def __init__(self):
# connect to some device here!

def get_led_count(self) -> int:
# return the number of leds your system can control here

def set_led(self, led_index: int, on: bool) -> None:
# Write your code for controlling your LEDs here
# It should turn on or off the LED at the led_index depending on the "on" variable
# For example:
# if on:
# some_led_library.set_led(led_index, (255, 255, 255))
# else:
# some_led_library.set_led(led_index, (0, 0, 0))
```

Fill out the blanks and check it by running `marimapper_check_backend --backend my_backend.py` in the same directory

Once you've checked it works, you can run marimapper in the same directory with `marimapper --backend my_backend.py` and it will use your backend!

If your backend needs any external libraries for example, `requests`, add them to marimapper with `pipx inject marimapper requests`

If you think others would find your backend useful (behave), please drop me a Github Issue,
find me on Telegram or open a pull request so we can add it into marimapper!
1 change: 1 addition & 0 deletions marimapper/backends/fadecandy/fadecandy_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
class Backend:

def __init__(self, uri="localhost:7890"):

self.client = opc.Client(uri)
self.buffer = [(0, 0, 0) for _ in range(self.get_led_count())]
self.client.put_pixels(self.buffer)
Expand Down
21 changes: 17 additions & 4 deletions marimapper/backends/pixelblaze/pixelblaze_backend.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
from multiprocessing import get_logger
import pixelblaze

from ipaddress import ip_address
logger = get_logger()


class Backend:

def __init__(self, pixelblaze_ip="4.3.2.1"):

try:
ip_address(pixelblaze_ip)
except ValueError:
raise RuntimeError(f"Pixelblaze backend failed to start due as {pixelblaze_ip} is not a valid IP address")

self.pb = pixelblaze.Pixelblaze(pixelblaze_ip)
self.pb.setActivePatternByName(
"marimapper"
) # Need to install marimapper.js to your pixelblaze
try:
self.pb.setActivePatternByName(
"marimapper"
) # Need to install marimapper.js to your pixelblaze
except TypeError as e:
if "'NoneType' has no len()" in str(e):
raise RuntimeError("Pixelblaze may have failed to find the effect 'marimapper'. "
"Have you uploaded marimapper.epe to your controller?")
else:
raise e

def get_led_count(self):
pixel_count = self.pb.getPixelCount()
Expand Down
7 changes: 7 additions & 0 deletions marimapper/backends/wled/wled_backend.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import requests
from ipaddress import ip_address


class Backend:

def __init__(self, wled_base_url="4.3.2.1"):

try:
ip_address(wled_base_url)
except ValueError:
raise RuntimeError(f"WLED backend failed to start due as {wled_base_url} is not a valid IP address")

self.state_endpoint = f"http://{wled_base_url}/json/state"
self.info_endpoint = f"http://{wled_base_url}/json/info"
self.reset_wled()
Expand Down
6 changes: 5 additions & 1 deletion marimapper/database_populator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ def populate_database(db_path: Path, leds: list[LED2D]):
if pad_needed > 0:
map_features = np.pad(map_features, [(0, 0), (0, pad_needed), (0, 0)])

map_features[view][led.led_id] = led.point.position * ARBITRARY_SCALE
# we flip this here so that the resulting 3D model is oriented with y+ up
map_features[view][led.led_id] = (
np.array((1 - led.point.position[0], 1 - led.point.position[1]))
* ARBITRARY_SCALE
)

db = COLMAPDatabase.connect(db_path)

Expand Down
5 changes: 4 additions & 1 deletion marimapper/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def set_cam_default(cam: Camera) -> None:
cam.eat()


def set_cam_dark(cam: Camera, exposure: int) -> None:
def set_cam_dark(cam: Camera, exposure: int) -> bool:
logger.info("setting cam to dark mode")
cam.set_autofocus(0, 0)
cam.set_exposure_mode(0)
Expand All @@ -111,8 +111,11 @@ def set_cam_dark(cam: Camera, exposure: int) -> None:
f"try darkening the scene and adjusting the threshold with --threshold "
)

exposure_success = cam.set_exposure(exposure)
cam.eat()

return exposure_success


def find_led(
cam: Camera, threshold: int = 128, display: bool = True
Expand Down
4 changes: 3 additions & 1 deletion marimapper/file_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def get_all_2d_led_maps(directory: Path) -> list[LED2D]:
for view_id, filename in enumerate(sorted(os.listdir(directory))):
full_path = Path(directory, filename)

detections = load_detections(full_path, view_id) # this is wrong
detections = load_detections(
full_path, view_id
) # this is wrong < WHY DID I WRITE THIS???? IS IT NOT???

if detections is not None:
points.extend(detections)
Expand Down
6 changes: 4 additions & 2 deletions marimapper/led.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ def get_info(self) -> LEDInfo:
return LEDInfo.NONE

def get_color(self):

info = self.get_info()
return get_color(info)

Expand Down Expand Up @@ -332,7 +331,10 @@ def get_overlap_and_percentage(leds_2d, leds_3d, view) -> tuple[int, int]:
leds_3d_ids = set([led.led_id for led in leds_3d])
view_ids = [led.led_id for led in get_leds_with_view(leds_2d, view)]
overlap_len = len(leds_3d_ids.intersection(view_ids))
overlap_percentage = int((overlap_len / len(view_ids)) * 100)
if len(view_ids) > 0:
overlap_percentage = int((overlap_len / len(view_ids)) * 100)
else:
overlap_percentage = 0

return overlap_len, overlap_percentage

Expand Down
13 changes: 11 additions & 2 deletions marimapper/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,23 @@ def __init__(
self.output_dir = output_dir

self.detector = DetectorProcess(
device, exposure, threshold, backend, server, check_movement, fast
device=device,
dark_exposure=exposure,
threshold=threshold,
led_backend_name=backend,
led_backend_server=server,
display=True,
check_movement=check_movement,
fast=fast
)

self.file_writer = FileWriterProcess(self.output_dir)

existing_leds = get_all_2d_led_maps(self.output_dir)

self.sfm = SFM(max_fill, existing_leds)
led_count = led_end - led_start

self.sfm = SFM(max_fill, existing_leds, led_count)

self.current_view = last_view(existing_leds) + 1

Expand Down
2 changes: 0 additions & 2 deletions marimapper/scripts/check_backend_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ def main():
default=0,
)

parser.add_argument("-v", "--verbose", action="store_true")

args = parser.parse_args()

if args.verbose:
Expand Down
Loading

0 comments on commit 03d58da

Please sign in to comment.