Skip to content

Conversation

jepler
Copy link
Contributor

@jepler jepler commented Mar 6, 2025

as a part of this, reorganize the code so that the code has package structure, and move the click options handling code into the package as well.

This also adds support for more than 2 color lanes (i.e., 6 lanes in a 3 connector system like the active3). However the C++ coded mappers don't support the additional lanes, so you have to create your own pixel mapper for now.

The new rainbow_spiral_active3.py demo drives 3 64x64 matrices using the active3 pinout. The fbmirror demo also has rudimentary support for the active3 pinout with 6 lanes.

It remains to figure out what "decent support" for more than 2 lanes / 1 connector looks like. Based on internal discussion, we are OK with initially supporting just a basic linear arrangement per connector, which is what those two demos implement. However, it would be good to support this in a way that doesn't lead to people needing to copy & paste the code to calculate the matrix map.

Support for temporal dithering has also been added. This sends out data according to a "schedule sequence". The code supports creating either a simple schedule sequence, identical to the old sequence, in which every bit plane is sent every frame, e.g., with 5 bit depth, the schedule can be written {9 16} {8 8} {7 4} {6 2} {5 1} where the first 9 is the most significant bit and 16 is the relative length of time to illuminate it for.

A temporal dither pattern has several schedules. It does each schedule in turn, but a frame switch is possible after any one individual schedule. For example, here are two more schedules for 5 total planes, but 2 or 4 temporal planes:

{9 8} {8 4} {7 2} {6 2} 
{9 8} {8 4} {7 2} {5 1} 
{9 4} {8 8} 
{9 4} {7 4} 
{9 4} {6 2} 
{9 4} {5 1} 

There are two good effects here: First, each schedule has fewer entries (5 entries became 4 or 2 entries). If each entry takes the same length of time, then frame rate increases by 20% for 2 temporal planes and 60% for 4 temporal planes.

Second, the largest weight number is smaller (here, 8 instead of 16). Because the granularity of control of the OE pin (which illuminates a set of LEDs) is equal to the pixel clock, a large color depth with a "narrow" matrix has to spend extra time illuminating LEDs rather than sending more pixel data. For example the biggest value in a "10/0" schedule is 512, while the biggest value in a "10/4" schedule is 128. So for a LED panel with fewer than 512 pixels across, this reduces the extra delays just to achive the needed LED "on" time for the highest bitplane.

In practice on a 64x64 panel, 10/0 runs at 50fps and 10/4 runs at 100fps, or 2x as fast. 8/0 runs at 92fps while 8/4 gets 132fps, or about 45% faster.

@jepler jepler marked this pull request as ready for review March 7, 2025 16:17
@jepler jepler changed the title Allow defining geometries via Python code Allow defining geometries (including more than 2 color lanes) via Python code Mar 7, 2025
@jepler jepler requested a review from FoamyGuy March 7, 2025 16:19
jepler added 10 commits March 7, 2025 11:42
(however, there's no code yet to generate such schedules)
10/4 planes gives 100fps on the active3 spiral demo on 3 64x64 panels, or
1.2 megapixels/second. And to my eye, there's no brightness shimmer.

All the below settings "look good" to my eye, higher FPS ones tend to look
better to a camera. In "10/4" mode with my camera at 100FPS it still looks
solid but because the beat frequency between the dither pattern and the
shutter is pretty pronounced I can see it shift between the sub-frames.

There's still some brightness variation between modes, with more planes
being a little brighter than fewer planes.

10/0: 50fps
10/2: 72fps
10/4: 100fps

8/0: 92fps
8/2: 109fps
8/4: 134fps

5/0: 135fps
5/2: 153fps
5/4: 210fps
@jepler
Copy link
Contributor Author

jepler commented Mar 10, 2025

@FoamyGuy I know you don't have a multi-connector board to look at, so what'd be useful is to test that the existing examples still work for you on the adafruit boards & that the API changes & additions make sense. You can also check if temporal dithering is giving good results for your eyes & cameras too.

if n_planes is not None:
f = click.option("--num-planes", "n_planes", default=n_planes, help="The number of bit planes (color depth. Lower values can improve refresh rate in frames per second")(f)
f = click.option("--num-planes", "n_planes", default=n_planes, help="The number of bit planes (color depth). Lower values can improve refresh rate in frames per second")(f)
if n_temporal_planes is not None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jepler Does this argument (and/or the others near here) need to be conditional with this not None check?

If I am understanding correctly this means that the --num-temporal-planes argument is not valid unless the example code script includes something like:

@piomatter_click.standard_options(n_lanes=2, n_temporal_planes=4)

Is that understanding correct?

In the case where the code provides a value in the click.standard_option declaration, and the user also passes --num-temporal-planes which would take precedent?

If it's possible have them be just CLI arguments and not need to be declared in the example scripts also I think that would make their usage less complex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This new argument is only added to click if it's specified as non-None.

My thinking, which may be making more trouble than help: If the new command line argument is added to click by default, then any old code that doesn't have the python function parameter of the same name will suddenly break.

We're in alpha releases in this repo, so it's probably OK to just break things for now. The amount of code outside the repo that is using protomatter_click.standard_options is essentially zero.

would you rather I change it so it's included by default?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. I do think it would be good to have it included by default. Agree there is likely little else using it at the moment so it's okay to have it break.

jepler added 5 commits March 11, 2025 09:19
docs are wrong and this might be part of why .. but not all
There are still some problems & some build warnings but at least the
sphinx docs have useful content now.
Copy link
Contributor

@FoamyGuy FoamyGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jepler I'm going to merge this one now. I can update the default arguments behavior in a followup PR. I started working from this branch on the new xdisplay mirror example, I can update the existing examples in that one and make the breaking change after they're all updated so they won't break.

@FoamyGuy FoamyGuy merged commit 8d3355f into main Mar 11, 2025
14 checks passed
@FoamyGuy
Copy link
Contributor

I see now there were newer commits that I missed before. Thank you!

@jepler
Copy link
Contributor Author

jepler commented Mar 11, 2025

Thanks! If anything's not quite right we'll get it fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants