Initial commit of yaml parse test.

A complete new controll system.
Still some bugs remain
This commit is contained in:
CiscoTheWolf 2023-10-02 08:05:48 +02:00
parent 2530dd51be
commit 6403cad191
20 changed files with 1294 additions and 0 deletions

View file

@ -0,0 +1,21 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.22000.0",
"compilerPath": "cl.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64"
}
],
"version": 4
}

View file

@ -0,0 +1,4 @@
FROM ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:main
RUN dpkg --add-architecture armhf
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq python3.9:armhf python3.9-dev:armhf libpython3.9-dev:armhf

272
yaml parse test/Point2D.py Normal file
View file

@ -0,0 +1,272 @@
import hashlib
import json
import os.path
import math
from scipy.optimize import linear_sum_assignment
import numpy as np
from PIL import Image
import fastproot
# location to store array cache
CACHE_FILE_PATH = "./cache//point_array_cache.json"
LOADED_IMAGE_PATH = "./graphics/loaded.png"
CACHE_FILE_PATH = os.path.normpath(CACHE_FILE_PATH)
LOADED_IMAGE_PATH = os.path.normpath(LOADED_IMAGE_PATH)
class Point2D:
x = 0
y = 0
color: tuple[int, int, int] = (0, 0, 0)
def __init__(self, x, y, color: tuple[int, int, int] = (0, 0, 0)):
self.x = x
self.y = y
self.color = color
def round(self):
self.x = round(self.x)
self.y = round(self.y)
return self
def distance(self, other):
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx ** 2 + dy ** 2)
def interpolate(self, other, percentage):
new_x = self.x + (other.x - self.x) * percentage
new_y = self.y + (other.y - self.y) * percentage
new_color = tuple(int((1 - percentage) * self.color[i] + percentage * other.color[i]) for i in range(3))
return Point2D(new_x, new_y, new_color)
def __eq__(self, other):
return (self.x, self.y) == (other.x, other.y)
def divide_points_into_groups(points):
result = fastproot.divide_into_groups(points)
res = [[Point2D(p[0], p[1], p[2]) for p in arr] for arr in result]
return res
def _divide_points_into_groups(points):
def flood_fill(point, group):
if point not in group:
group.append(point)
neighbors = [(point.x + 1, point.y), (point.x - 1, point.y), (point.x, point.y + 1), (point.x, point.y - 1)]
for neighbor_coords in neighbors:
neighbor = next((p for p in points if p.x == neighbor_coords[0] and p.y == neighbor_coords[1]), None)
if neighbor and neighbor not in group:
flood_fill(neighbor, group)
groups = []
remaining_points = [point for point in points if point.x < 64] # Filter points with x < 64
while remaining_points:
group = []
flood_fill(remaining_points[0], group)
groups.append(group)
# Remove points in the group from the remaining points
remaining_points = [point for point in remaining_points if point not in group]
return groups
def compute_bounding_box(points: list[Point2D]) -> tuple[float, float, float, float]:
min_x = min(point.x for point in points)
max_x = max(point.x for point in points)
min_y = min(point.y for point in points)
max_y = max(point.y for point in points)
return min_x, max_x, min_y, max_y
def calculate_distance(points1: list[Point2D], points2: list[Point2D]) -> float:
# Calculate the distance between the centers of two groups
center1_x = (points1[0].x + points1[-1].x) / 2
center1_y = (points1[0].y + points1[-1].y) / 2
center2_x = (points2[0].x + points2[-1].x) / 2
center2_y = (points2[0].y + points2[-1].y) / 2
return ((center1_x - center2_x) ** 2 + (center1_y - center2_y) ** 2) ** 0.5
def pair_groups(set_a: list[list[Point2D]], set_b: list[list[Point2D]]) -> list[tuple[list[Point2D], list[Point2D]]]:
pairs = []
# Create dictionaries to store bounding boxes for each group
bounding_boxes_a: dict[int, tuple[float, float, float, float]] = {}
bounding_boxes_b: dict[int, tuple[float, float, float, float]] = {}
# Calculate bounding boxes for all groups in both sets
for i, group in enumerate(set_a + set_b):
bounding_box = compute_bounding_box(group)
if i < len(set_a):
bounding_boxes_a[i] = bounding_box
else:
bounding_boxes_b[i - len(set_a)] = bounding_box
# Check for overlaps and determine pairs
for i, group_a in enumerate(set_a):
overlap_detected = False
for j, group_b in enumerate(set_b):
bounding_box_a = bounding_boxes_a[i]
bounding_box_b = bounding_boxes_b[j]
if (
bounding_box_a[0] <= bounding_box_b[1] and
bounding_box_a[1] >= bounding_box_b[0] and
bounding_box_a[2] <= bounding_box_b[3] and
bounding_box_a[3] >= bounding_box_b[2]
):
pairs.append((group_a, group_b))
overlap_detected = True
break
if not overlap_detected:
# Find the nearest neighbor in set B
nearest_group = min(set_b, key=lambda group: calculate_distance(group_a, group))
pairs.append((group_a, nearest_group))
return pairs
def mirror_points(points: list[Point2D]) -> list[Point2D]:
mirrored_points = []
for point in points:
mirrored_x = 128 - point.x # Calculate the mirrored x-coordinate
mirrored_point = Point2D(mirrored_x, point.y, point.color)
mirrored_points.append(mirrored_point)
return mirrored_points
def get_image_hash(image):
image_hash = hashlib.sha1(image.tobytes()).hexdigest()
return image_hash
def load_cached_point_arrays():
cached_point_arrays = {}
if os.path.isfile(CACHE_FILE_PATH):
with open(CACHE_FILE_PATH, "r") as file:
cached_point_arrays = json.load(file)
return cached_point_arrays
def save_cached_point_arrays(cached_point_arrays):
if not os.path.isfile(CACHE_FILE_PATH):
open(CACHE_FILE_PATH, "x").close()
with open(CACHE_FILE_PATH, "w") as file:
json.dump(cached_point_arrays, file)
def generate_point_array_from_image(image):
image.load()
image = image.convert("RGB") # Convert image to RGB color mode
image_hash = get_image_hash(image)
cached_point_arrays = load_cached_point_arrays()
if image_hash in cached_point_arrays:
print("Found existing point array matching png. Using existing array.")
return [Point2D(point["x"], point["y"], tuple(point["color"])) for point in cached_point_arrays[image_hash]]
print("No existing point array matching png found. Generating now.")
width, height = image.size
pixel_array = []
image.save(LOADED_IMAGE_PATH)
for y in range(height):
for x in range(width):
pixel = image.getpixel((x, y))
if sum(pixel) > 15: # any pixel which total color value is greater than 15.
point = {"x": x, "y": y, "color": pixel}
pixel_array.append(point)
cached_point_arrays[image_hash] = pixel_array
save_cached_point_arrays(cached_point_arrays)
print("Point array generated and stored.")
return [Point2D(point["x"], point["y"], tuple(point["color"])) for point in pixel_array]
def generate_image_from_point_array(points: list[Point2D], width: int, height: int) -> Image:
# Create a new blank image
image = Image.new("RGB", (width, height), "black")
# Set the pixels corresponding to the points as white
pixels = image.load()
for point in points:
point = point.round()
x = point.x
y = point.y
pixels[x, y] = point.color
return image
def interpolate_point_pairs(pairs: list[tuple[Point2D, Point2D]], percentage: float) -> list[Point2D]:
interpolated_points:list[Point2D] = []
for pair in pairs:
point1, point2 = pair
interpolated_point = point1.interpolate(point2, percentage)
interpolated_points.append(interpolated_point)
return interpolated_points
def pair_points(points1: list[Point2D], points2: list[Point2D]) -> list[tuple[Point2D, Point2D]]:
# Update the size of the point arrays
size1 = len(points1)
size2 = len(points2)
# Duplicate points in the smaller array to match the size of the larger array
if size1 > size2:
num_duplicates = size1 - size2
duplicated_points = np.random.choice(points2, size=num_duplicates).tolist()
points2 += duplicated_points
elif size2 > size1:
num_duplicates = size2 - size1
duplicated_points = np.random.choice(points1, size=num_duplicates).tolist()
points1 += duplicated_points
row_ind, col_ind = fastproot.solve(points1, points2)
# Create pairs of points based on the optimal assignment
pairs = []
for i, j in zip(row_ind, col_ind):
pairs.append((points1[i], points2[j]))
return pairs
def _pair_points(points1: list[Point2D], points2: list[Point2D]) -> list[tuple[Point2D, Point2D]]:
# Update the size of the point arrays
size1 = len(points1)
size2 = len(points2)
# Duplicate points in the smaller array to match the size of the larger array
if size1 > size2:
num_duplicates = size1 - size2
duplicated_points = np.random.choice(points2, size=num_duplicates).tolist()
points2 += duplicated_points
elif size2 > size1:
num_duplicates = size2 - size1
duplicated_points = np.random.choice(points1, size=num_duplicates).tolist()
points1 += duplicated_points
# Create a new cost matrix with the updated sizes
cost_matrix = np.zeros((size1, size2))
for i in range(size1):
for j in range(size2):
cost_matrix[i, j] = points1[i].distance(points2[j])
# Solve the assignment problem using the Hungarian algorithm
row_ind, col_ind = linear_sum_assignment(cost_matrix)
# Create pairs of points based on the optimal assignment
pairs = []
for i, j in zip(row_ind, col_ind):
pairs.append((points1[i], points2[j]))
return pairs

View file

@ -0,0 +1,5 @@
Crosscompile instructions:
* Build docker image with python armv7 installed:
`docker build -t my-image .`
* Use cross to crosscompile:
`cross.exe build --target=armv7-unknown-linux-gnueabihf --release`

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,67 @@
#include <Python.h>
static PyObject* divide_points_into_groups(PyObject* self, PyObject* args) {
PyObject* points_list; // Input list of points
if (!PyArg_ParseTuple(args, "O", &points_list)) {
PyErr_SetString(PyExc_TypeError, "Expected a list of points.");
return NULL;
}
// Check if the input is a list
if (!PyList_Check(points_list)) {
PyErr_SetString(PyExc_TypeError, "Input must be a list of points.");
return NULL;
}
// Create a Python list to store groups
PyObject* groups_list = PyList_New(0);
// Iterate through the input points and divide them into groups
// (Simplified logic - you should implement your own logic here)
// In this example, we assume points are (x, y) pairs as Python tuples
// and groups are lists of points.
Py_ssize_t num_points = PyList_Size(points_list);
PyObject* current_group = PyList_New(0);
for (Py_ssize_t i = 0; i < num_points; i++) {
PyObject* point = PyList_GetItem(points_list, i);
PyObject* x_obj = PyTuple_GetItem(point, 0);
PyObject* y_obj = PyTuple_GetItem(point, 1);
// Process the point (x, y) here
// (You should implement your grouping logic)
// For simplicity, we add points to the current group
PyList_Append(current_group, point);
// Assume a condition for creating a new group (e.g., x > 50)
if (PyLong_AsLong(x_obj) > 50) {
PyList_Append(groups_list, current_group);
current_group = PyList_New(0);
}
}
// Append the last group if it's not empty
if (PyList_Size(current_group) > 0) {
PyList_Append(groups_list, current_group);
}
return groups_list;
}
static PyMethodDef methods[] = {
{"divide_points_into_groups", divide_points_into_groups, METH_VARARGS, "Divide points into groups."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"point_extension",
NULL,
-1,
methods
};
PyMODINIT_FUNC PyInit_point_extension(void) {
return PyModule_Create(&module);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

317
yaml parse test/fastproot/Cargo.lock generated Normal file
View file

@ -0,0 +1,317 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "fastproot"
version = "0.1.0"
dependencies = [
"lsap",
"pyo3",
]
[[package]]
name = "indoc"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "libc"
version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "lsap"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f4e260c26278f22a66a40b147d6e37ba24c6528210f11226c5ad6de567a774"
dependencies = [
"derive_more",
]
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"parking_lot",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
[[package]]
name = "smallvec"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unindent"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

View file

@ -0,0 +1,18 @@
[package]
name = "fastproot"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lsap = "1.0.2"
pyo3 = { version = "0.19.2", features = ["abi3-py39"] }
[lib]
name = "fastproot"
crate-type = ["cdylib"]
[package.metadata.cross.target.armv7-unknown-linux-gnueabihf]
image = "my-image"
pre-build = ["ln -s /usr/bin/python3.9 /usr/bin/python"]

View file

@ -0,0 +1,131 @@
use pyo3::prelude::*;
#[derive(Copy, Clone, PartialEq, FromPyObject)]
pub struct Point {
x: u8,
y: u8,
color: (u8, u8, u8),
}
type TuplePoint = (u8,u8, (u8,u8,u8));
impl Point {
fn distance(self, other: Point) -> f64 {
let dx = self.x as f64 - other.x as f64;
let dy = self.y as f64 - other.y as f64;
(dx.powf(2.0) + dy.powf(2.0)).sqrt()
}
fn into_tuple(&self) -> TuplePoint {
(self.x, self.y, self.color)
}
}
#[pyfunction]
pub fn solve(points1: Vec<Point>, points2: Vec<Point>) -> (Vec<usize>, Vec<usize>) {
let mut cost_matrix = Vec::with_capacity(points1.len() * points2.len());
for i in &points1 {
for j in &points2 {
cost_matrix.push(i.distance(*j))
}
}
lsap::solve(points1.len(), points2.len(), &cost_matrix, false).unwrap()
}
pub fn flood_fill(point: Point, mut group: Vec<Point>, points: Vec<Point>) -> Vec<Point> {
if !group.contains(&point) {
group.push(point);
let neighbors = [(point.x + 1, point.y), (point.x - 1, point.y), (point.x, point.y + 1), (point.x, point.y - 1)];
for neighbor_coords in neighbors {
let neighbor = points.iter().find(|p| p.x == neighbor_coords.0 && p.y == neighbor_coords.1);
if let Some(neighbor) = neighbor {
if !group.contains(&neighbor) {
group = flood_fill(*neighbor, group, points.clone())
}
}
}
}
group
}
#[pyfunction]
pub fn divide_into_groups(points: Vec<Point>) -> Vec<Vec<TuplePoint>> {
let mut groups = vec![];
let mut remaining = points.iter().filter(|p| p.x < 64).collect::<Vec<_>>();
while let Some(&point) = remaining.get(0) {
let group = flood_fill(*point, vec![], points.clone());
groups.push(group.iter().map(|v| v.into_tuple()).collect());
remaining = remaining.into_iter().filter(|p| !group.contains(p)).collect();
}
groups
}
// #[pyfunction]
// fn pair_groups(set_a: Vec<Vec<Point>>, set_b: Vec<Vec<Point>>) -> Vec<(TuplePoint, TuplePoint)> {
// let mut pairs = vec![];
// // Create dictionaries to store bounding boxes for each group
// let mut bounding_boxes_a: std::collections::HashMap::new(); // dict[int, tuple[float, float, float, float]] = {}
// let mut bounding_boxes_a: std::collections::HashMap::new();
// // Calculate bounding boxes for all groups in both sets
// for (i, group) in set_a.iter().chain(set_b).iter().enumerate() {
// let bounding_box = compute_bounding_box(group);
// if i < set_a.len() {
// bounding_boxes_a[i] = bounding_box;
// } else {
// bounding_boxes_b[i - set_a.len()] = bounding_box;
// }
// }
// // Check for overlaps and determine pairs
// for (i, group_a) in set_a.iter().enumerate() {
// let overlap_detected = false;
// for (j, group_b) in set_b.iter().enenumerate() {
// let bounding_box_a = bounding_boxes_a[i];
// let bounding_box_b = bounding_boxes_b[j];
// if (
// bounding_box_a[0] <= bounding_box_b[1] &&
// bounding_box_a[1] >= bounding_box_b[0] &&
// bounding_box_a[2] <= bounding_box_b[3] &&
// bounding_box_a[3] >= bounding_box_b[2]
// ) {
// pairs.push((group_a, group_b));
// overlap_detected = true;
// break
// }
// }
// if !overlap_detected {
// // Find the nearest neighbor in set B
// let mut nearest_group = set_b[0];
// let mut nearest_value = 0.0f64;
// for val in set_b.iter() {
// if calculate_distance(group_a, val) < nearest_value {
// nearest_group = val;
// }
// }
// pairs.append((group_a, nearest_group));
// }
// }
// pairs
// }
#[pymodule]
fn fastproot(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(solve, m)?)?;
m.add_function(wrap_pyfunction!(divide_into_groups, m)?)?;
// m.add_function(wrap_pyfunction!(pair_groups, m)?)?;
Ok(())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
yaml parse test/neutral.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

366
yaml parse test/prootOS.py Normal file
View file

@ -0,0 +1,366 @@
import yaml
import paho.mqtt.client as mqtt
import threading
from Point2D import divide_points_into_groups, generate_image_from_point_array, generate_point_array_from_image, interpolate_point_pairs, mirror_points, pair_groups, pair_points
from PIL import Image
import time
import random
from rgbmatrix import RGBMatrix, RGBMatrixOptions
# Configuration for the matrix screens
options = RGBMatrixOptions()
options.rows = 32
options.cols = 64
options.chain_length = 2
options.parallel = 1
options.hardware_mapping = 'regular'
matrix = RGBMatrix(options=options)
class DuplicateTriggerError(Exception):
"""Custom exception for duplicated triggers."""
def load_animations(yaml_file):
try:
with open(yaml_file, 'r') as file:
animations_data = yaml.safe_load(file)
animations = animations_data.get('animations', [])
# Create a dictionary to store animations by trigger
trigger_animations = {}
# Iterate through animations and modify graphics as needed
for animation in animations:
trigger = animation.get('trigger')
# Check if the trigger is already in the dictionary
if trigger in trigger_animations:
trigger_animations[trigger].append(animation.get('name', 'unnamed animation'))
else:
trigger_animations[trigger] = [animation.get('name', 'unnamed animation')]
graphics = animation.get('graphics', [])
for graphic in graphics:
if graphic.get('type') == 'transition':
to_file = graphic.get('to_file')
point_array = generate_point_array_from_image(Image.open(to_file))
graphic['to_point_array'] = point_array # Store the point array
elif graphic.get('type') == 'image' or graphic.get('type') == 'animation':
# Check if the 'source' is set to 'current'
if graphic.get('source') != 'current':
graphic['source_file'] = graphic.get('source_file')
source_file = graphic.get('source_file')
point_array = generate_point_array_from_image(Image.open(source_file))
graphic['point_array'] = point_array # Store the point array
# Check for duplicated triggers
duplicated_triggers = [trigger for trigger, animations in trigger_animations.items() if len(animations) > 1]
if duplicated_triggers:
error_message = "Duplicated trigger(s) found:\n"
for trigger in duplicated_triggers:
error_message += f"Trigger: {trigger}, Animations: {', '.join(trigger_animations[trigger])}\n"
raise DuplicateTriggerError(error_message)
return animations
except DuplicateTriggerError as e:
print(e) # Print the error message with duplicated triggers
raise e
except Exception as e:
print(f"Error parsing YAML file: {e}")
return []
# Function to simulate playing an animation
def play_animation(animation):
print(f"Playing animation: {animation['name']}")
# Decision-Making Layer
class DecisionLayer:
def __init__(self, animations, rendering_layer):
self.animations = animations
self.rendering_layer = rendering_layer
def get_animation_by_trigger(self, trigger):
"""
Get an animation from the list of parsed animations based on the specified trigger.
Args:
animations (list): List of parsed animations.
trigger (str): The trigger to match.
Returns:
dict: The animation dictionary matching the trigger, or None if not found.
"""
for animation in self.animations:
if animation.get('trigger') == trigger:
return animation
return None # Return None if no animation with the specified trigger is found
def handle_trigger(self, source, payload):
trigger = None
if source == "boot":
trigger = "boot"
elif source == "blinkTimer":
if len(self.rendering_layer.animation_queue) == 0:
trigger = "blinkTimer"
elif source == "mqtt":
print("Received message '" + str(payload))
if len(str(payload)) < 17:
print("received massage too short to be valid")
sensor_type = str(payload)[2:6]
sensor_specifier = str(payload)[7:11]
sensor_state = str(payload)[12:16]
if sensor_type == "Bean":
if sensor_state == "0000":
print("Bean ID:" + sensor_specifier + " Fell.")
elif sensor_state == "0001":
print("Bean ID:" + sensor_specifier + " Rose.")
else:
print("Bean ID:" + sensor_specifier + " in illegal state.")
elif sensor_type == "Butn":
if sensor_state == "0000":
print("Button ID:" + sensor_specifier + " Fell.")
elif sensor_state == "0001":
print("Button ID:" + sensor_specifier + " Rose.")
else:
print("Received illegal state: " + sensor_state + " for Button ID:" + sensor_specifier)
elif sensor_type == "Move":
if sensor_specifier == "000X":
print("Movement in X axis: " + sensor_state)
elif sensor_specifier == "000Y":
print("Movement in Y axis: " + sensor_state)
elif sensor_specifier == "000Z":
print("Movement in Z axis: " + sensor_state)
else:
print("Received illegal movement axis.")
elif sensor_type == "Rott":
if sensor_specifier == "000X":
print("Rotation in X axis: " + sensor_state)
elif sensor_specifier == "000Y":
print("Rotation in Y axis: " + sensor_state)
elif sensor_specifier == "000Z":
print("Rotation in Z axis: " + sensor_state)
else:
print("Received illegal Rotation axis.")
elif sensor_type == "Gest":
if sensor_specifier == "XXXX":
print("Gesture received: " + sensor_state)
else:
print("Received illegal gesture")
else:
print("received illegal sensor type: " + sensor_type)
trigger = str(payload)[2:16]
# Implement logic to decide which animation to play based on the trigger and payload
for animation in self.animations:
if animation.get('trigger') == trigger:
self.rendering_layer.play_animation(animation)
class RenderingLayer:
def __init__(self, animations, frame_rate=40):
self.animations = animations
self.current_point_array = []
self.current_animation_action = {}
self.frame_rate = frame_rate # Set the desired frame rate
self.frame_duration = 1.0 / frame_rate # Calculate the frame duration
self.animation_queue = [] # Initialize the animation queue
def play_animation_by_name(self, animation_name):
for animation in self.animations:
if animation.get('name') == animation_name:
self.play_animation(animation)
def play_animation(self, animation):
if len(self.animation_queue) > 0:
print("Stopping current animation...")
# Replace the currently playing animation with the new one
self.animation_queue = self.generate_animation_queue(animation) # Add frames to the queue
def append_animation(self, animation):
self.animation_queue = self.animation_queue + self.generate_animation_queue(animation) # Add frames to the queue
def generate_animation_queue(self, animation):
animation_queue = []
graphics = animation.get('graphics', [])
for graphic in graphics:
if graphic.get('type') == 'image':
point_array = graphic.get('point_array')
duration = graphic.get('duration', 1) # Default duration is 1 frame if not specified
# Add frames to the queue based on the specified duration
for _ in range(int(duration)):
animation_queue.append({'type': graphic.get('type'), 'point_array': point_array})
if graphic.get('type') == 'transition':
to_point_array = graphic.get('to_point_array')
duration = graphic.get('duration', 1) # Default duration is 1 frame if not specified
# Add frames to the queue based on the specified duration
for i in range(int(duration)):
animation_queue.append({'type': graphic.get('type'), 'to_point_array': to_point_array, 'stepPercentage' : (1/(int(duration)-i))})
return animation_queue
def start_rendering(self):
frameCount = 0
new_image = Image.new("RGB", (128, 32), "black")
devisiontime = 0
pairgrouptime = 0
pairpointtime = 0
imagingtime = 0
transitionFrameCount = 1
while True:
start_time = time.time() # Get the current time before rendering
if len(self.animation_queue) > 0:
current_animation_action = self.animation_queue.pop(0)
print("update action is: " + current_animation_action.get('type'))
# Render the next frame in the queue
if current_animation_action.get('type') == "image":
new_image = generate_image_from_point_array(current_animation_action.get('point_array'), 128, 32)
self.current_point_array = current_animation_action.get('point_array')
print("image generated")
elif current_animation_action.get('type') == "transition":
transitionFrameCount += 1
divtime_start = time.time()
groupsa = divide_points_into_groups(self.current_point_array)
groupsb = divide_points_into_groups(current_animation_action.get('to_point_array'))
devisiontime += time.time() - divtime_start
pairgrouptime_start = time.time()
paired_groups = pair_groups(groupsa, groupsb)
pairgrouptime += time.time() - pairgrouptime_start
new_point_array = []
for pair in paired_groups:
pairpointtime_start = time.time()
point_pairs = pair_points(pair[0], pair[1])
pairpointtime += time.time() - pairpointtime_start
print(str(current_animation_action.get('stepPercentage')))
new_point_array += interpolate_point_pairs(point_pairs, current_animation_action.get('stepPercentage'))
imagingtime_start = time.time()
new_image = generate_image_from_point_array(new_point_array + mirror_points(new_point_array), 128, 32)
imagingtime += time.time() - imagingtime_start
self.current_point_array = new_point_array
offscreen_canvas = matrix.CreateFrameCanvas()
offscreen_canvas.SetImage(new_image, unsafe=False)
matrix.SwapOnVSync(offscreen_canvas)
# Save the image to a file with the desired format and file name
# new_image.save("output/frameNumber"+str(frameCount)+".png")
frameCount += 1
elapsed_time = time.time() - start_time # Calculate time elapsed during rendering
# Calculate the time to sleep to achieve the desired frame rate
sleep_time = self.frame_duration - elapsed_time
print("remaining time in frame: " + str(sleep_time))
if sleep_time > 0:
time.sleep(sleep_time)
print("average time cost per part for transition frames:")
print("devisiontime :" + str(devisiontime /transitionFrameCount ))
print("pairgrouptime :" + str(pairgrouptime /transitionFrameCount))
print("pairpointtime :" + str(pairpointtime /transitionFrameCount))
print("imagingtime :" + str(imagingtime /transitionFrameCount))
devisiontime = 0
pairgrouptime = 0
pairpointtime = 0
imagingtime = 0
transitionFrameCount = 0
# Function responsible for the blinking behaviour when Idle
def random_blinks():
while True:
time.sleep(random.randint(5, 7))
decision_layer.handle_trigger("blinkTimer", "")
# functions called by the MQTT listener
def on_connect(client, userdata, flags, response_code):
print("Connected to MQTT broker with result code " + str(response_code))
client.subscribe("test")
# Function to handle MQTT message reception
def on_message(client, userdata, message):
# Pass the received message to the decision-making layer
decision_layer.handle_trigger("mqtt", message.payload)
def main():
yaml_file = 'testAnimationYaml.yaml' # Replace with the path to your YAML file
# Parse the YAML file to get animations
animations = load_animations(yaml_file)
# MQTT broker configuration
broker_address = "localhost"
broker_port = 1883
broker_keepalive = 60
mqtt_client = mqtt.Client()
mqtt_client.on_connect = on_connect
mqtt_client.on_message = on_message
mqtt_client.connect(broker_address, broker_port, broker_keepalive)
mqtt_client.loop_start()
# Initialize the rendering layer
rendering_layer = RenderingLayer(animations, frame_rate=10)
rendering_thread = threading.Thread(target=rendering_layer.start_rendering)
rendering_thread.start()
# Initialize the decision-making layer
global decision_layer
decision_layer = DecisionLayer(animations, rendering_layer)
# Create and start random blinks interrupts
screen_update_thread = threading.Thread(target=random_blinks)
screen_update_thread.start()
decision_layer.handle_trigger("boot", "")
if __name__ == "__main__":
main()

BIN
yaml parse test/shared.so Normal file

Binary file not shown.

9
yaml parse test/test.py Normal file
View file

@ -0,0 +1,9 @@
from timeit import default_timer as timer
from ctypes import *
proot = CDLL("./fastproot/target/debug/fastproot.dll")
start = timer()
proot.test()
end = timer()
print(end - start)

View file

@ -0,0 +1,83 @@
animations:
- name: example animation
description: This is the example animation to showcase some of the options
loop_count: 1 # Number of loops (1 for single play, 0 for infinite)
# Define the trigger for this animation.
# In this case, it triggers when a button is pressed.
trigger: Butn_0001_0001 #Button 1 pressed
# Specify whether this animation can be overridden by another animation.
overrideable: true
graphics:
- type: transition
to_file: animation1_frame1.png # Specify the new PNG image for the transition
duration: 10 # The amount of frames the transition will take.
- type: image
source_file: animation1_frame1.png
duration: 10 # The amount of frames the image will be shown.
# This is the initial frame of the animation.
- type: transition
to_file: animation1_frame2.png
duration: 10 # The amount of frames the transition will take.
# This is a transition from the initial frame to the next frame.
# Transitions can be used to create smooth animations.
# You can add more graphics elements as needed for this animation.
# Additional comments or configuration options for Animation1 can go here.
# For example, you can specify the duration, sound effects, or other details.
- name: blink
description: Animation for blinking
loop_count: 1
trigger: blinkTimer #Button 2 pressed
overrideable: true # blink can be interupted at any time
graphics:
- type: transition # close the eye from whatever the current state
to_file: dizzyFace.png
duration: 5
- type: image # hold eye closed
source_file: dizzyFace.png
duration: 10
- type: transition # open the eye again from being closed
to_file: neutral.png
duration: 5
- name: openEye
description: Animation for blinking
loop_count: 1
trigger: boot
overrideable: true # blink can be interupted at any time
graphics:
- type: image # hold eye closed
source_file: eyesClosed_neutral.png
duration: 1
- type: transition # open the eye again from being closed
to_file: dizzyFace.png
duration: 5
- name: make dizzy
description: Animation for making dizzy
loop_count: 1
trigger: Butn_0002_0001
overrideable: true # blink can be interupted at any time
graphics:
- type: transition # open the eye again from being closed
to_file: dizzyFace.png
duration: 5