From 14ca36b859fccf0a2d22d962770780eb23879cfd Mon Sep 17 00:00:00 2001 From: Max Value Date: Sat, 25 Oct 2025 02:19:03 +0100 Subject: [PATCH] Added comments to python --- cube/camera.py | 15 +++++++++++---- cube/config.py | 19 +++++++++++++++++-- cube/cube.py | 31 ++++++++++++++++++++++++------- cube/frame.py | 11 +++++++++-- cube/graph.py | 3 +++ cube/test.py | 11 +++++++++-- make.py | 4 ++++ 7 files changed, 77 insertions(+), 17 deletions(-) diff --git a/cube/camera.py b/cube/camera.py index f46caff..1c26b4b 100644 --- a/cube/camera.py +++ b/cube/camera.py @@ -1,9 +1,8 @@ -from .config import IMAGE_WIDTH, IMAGE_HEIGHT +from .config import IMAGE_WIDTH, IMAGE_HEIGHT, CAP_WAIT import numpy as np import cv2 as cv -CAP_WAIT = 1 class Camera(): def __init__(self, device): @@ -14,16 +13,24 @@ class Camera(): #self.calibrate() + # get image from camera and fix perspective def get(self, image): cv.imshow("LUT Calibration", image) cv.waitKey(CAP_WAIT) - #_, capture = self.camera.read() - #capture = cv.warpPerspective(capture, self.homography, (IMAGE_WIDTH, IMAGE_HEIGHT)) + """ + _, capture = self.camera.read() + capture = cv.warpPerspective( + capture, + self.homography, + (IMAGE_WIDTH, IMAGE_HEIGHT) + ) + """ return image return capture + # standard calibration function def calibrate(self): calibration_image = cv.imread("../calibration.jpg") diff --git a/cube/config.py b/cube/config.py index a66128e..e992e6a 100644 --- a/cube/config.py +++ b/cube/config.py @@ -1,7 +1,22 @@ +""" +edge length of LUT + +very light testing in python outside of the real world has shown 12 to be +optimal, conventially 33 is used. This will need to be tested with a camera. +""" LUT_SIZE = 12 -IMAGE_WIDTH = 640#1920 -IMAGE_HEIGHT = 360#1080 +IMAGE_WIDTH = 640 #1920 +IMAGE_HEIGHT = 360 #1080 +# size of the qr codes and the space around them (px) QR_SIZE = 100 QR_PADDING = 10 + +# the wait time between each camera check after we stop generating +WAIT_TIME = 0.1 #s +# number of times to wait +WAIT_TICKS = 100 #s + +# how long to wait before capturing image (1 minimum) +CAP_WAIT = 1 diff --git a/cube/cube.py b/cube/cube.py index b89256a..48dad07 100755 --- a/cube/cube.py +++ b/cube/cube.py @@ -1,7 +1,5 @@ -#!.venv/bin/python - from .frame import blank, generate, result -from .config import LUT_SIZE +from .config import LUT_SIZE, WAIT_TIME, WAIT_TICKS from scipy.interpolate import make_interp_spline from tqdm import tqdm @@ -9,14 +7,13 @@ import numpy as np import cv2 as cv import time -WAIT_TIME = 0.1 -WAIT_TICKS = 100 - class Cube(): def __init__(self, camera): self.camera = camera self.size = LUT_SIZE self.lut = np.zeros((LUT_SIZE, LUT_SIZE, LUT_SIZE, 3), dtype=np.uint8) + + # the scaling factor of each color to turn it into a index self.s = 255 / (self.size-1) print(f""" @@ -27,6 +24,7 @@ scaler:\t{self.s} colors:\t{self.size**3} """) + # go through each color and put pass it through the screen/camera def process(self): seq = [i * self.s for i in range(0,self.size)] for r in tqdm(seq): @@ -40,10 +38,22 @@ colors:\t{self.size**3} self.invert() + """ + the rust program needs to be able to index easy into the LUT, this means + getting the color you have, scaling it, and using it as the index. at + the moment we have a cube that was indexed by its inputs not by its + outputs. this is easy if you have a cube of 255 width but we dont. + + the solution to this is to take each contour of each channel, and + interpolate it at the regular inteval of the size of the LUT. this means + that the quality of the interpolation algorithim is the quality of the + LUT. see below. + """ def invert(self): print("inverting LUT...") + # helper function to rotate and resample each channel def iter_channels(a): r = self.lut[:,:,:,0] g = self.lut[:,:,:,1] @@ -59,6 +69,7 @@ colors:\t{self.size**3} return np.stack((r, g, b), axis=-1) + # helper function to resample a channel def resample_channel(c): c = np.reshape(c, (self.size * self.size, self.size)) @@ -83,8 +94,14 @@ colors:\t{self.size**3} self.lut = iter_channels(self.lut) + """ + 'check' what a color looks like through the camera. color returned may + not be the transformed version of the color you put in due to the lag + between the display and the capture. waiting a long time, clearing the + cap etc.. all proved ineffective. therefore all images are tagged with a + qr code of their color. + """ def check(self, color=None): - if color is None: image = blank() else: diff --git a/cube/frame.py b/cube/frame.py index 110bd20..83ab87f 100644 --- a/cube/frame.py +++ b/cube/frame.py @@ -1,5 +1,3 @@ -#!.venv/bin/python - from .config import QR_SIZE, QR_PADDING, IMAGE_WIDTH, IMAGE_HEIGHT from pyzbar.pyzbar import decode @@ -9,6 +7,7 @@ import random import qrcode import os +# helper function to apply non-linear error to each channel of image def cast(a): a = np.array(a, dtype=np.float64) a[...,0] = ((a[...,0] / 255.) ** 2) * 255. @@ -18,6 +17,7 @@ def cast(a): return a +# generate image display color tagged with qr code def generate(color): # make qr code qr = qrcode.QRCode( @@ -50,19 +50,26 @@ def generate(color): return c_image +# make blank image def blank(): image = np.zeros((IMAGE_HEIGHT,IMAGE_WIDTH,3), dtype=np.uint8) return image +# decode captured image to origional color and new color def result(capture): l = QR_SIZE + QR_PADDING + """ + get the mean across a rectangular section of the image described by the + innermost corners of all the qr codes. + """ new = np.mean(capture[l:-l,l:-l], axis=(0,1)).astype(np.uint8) codes = decode(capture) if codes == []: return None, None + # always use the code which has the best quality capture codes.sort(key=lambda x:x.quality) data = codes[0].data data = [int(x) for x in data.split()] diff --git a/cube/graph.py b/cube/graph.py index 79139f0..b505aa8 100755 --- a/cube/graph.py +++ b/cube/graph.py @@ -3,6 +3,7 @@ import matplotlib.pyplot as plt import numpy as np +# make dual plot showing the curves and the 3d plot def show(a): size = a.shape[0] fig = plt.figure(figsize=plt.figaspect(1.)) @@ -11,6 +12,7 @@ def show(a): b = a.astype(np.float64) + # get slice of the LUT to graph as the curves ax.plot(b[1,1,...,2]+50, c="blue") ax.plot(b[1,...,1,1]+25, c="green") ax.plot(b[...,1,1,0], c="red") @@ -39,6 +41,7 @@ def show(a): ax.set_zlabel("b") plt.show() +# unused def compare(a, b): plt.hist(a.flat, bins=range(100), fc='k', ec='k', color="red") plt.hist(b.flat, bins=range(100), fc='k', ec='k', color="blue") diff --git a/cube/test.py b/cube/test.py index f76e0e3..bd4a540 100755 --- a/cube/test.py +++ b/cube/test.py @@ -5,22 +5,28 @@ import cv2 as cv import numpy as np import time +# monolithic validator def validate(cube): print("testing LUT...") + # get test image and apply cast to it image = cv.imread("src/calibration.jpg") height, width, _ = image.shape a = cast(np.flip(image, axis=-1)).astype(np.uint8) casted = cast(np.flip(image, axis=-1)).astype(np.uint8) + # track the application time start = time.time() + # scale cube a = np.divide(a, cube.s) + # calculate the two points enclosing the real point as well as the remainder c1 = np.floor(a).astype(np.uint8) c2 = np.ceil(a).astype(np.uint8) rem = np.remainder(a, 1) + # helper function to index at location def index_lut(a, i): for ih in range(height): for iw in range(width): @@ -30,18 +36,20 @@ def validate(cube): return i + # get the real values for each of the enclosing colors c1 = index_lut(cube.lut, c1) c2 = index_lut(cube.lut, c2) + # average between the two colors weighted with the remainder a = c1 + np.array((c2 - c1) * rem, dtype=np.uint8) a = np.flip(a, axis=-1) dur = time.time() - start + # flip to bgr for display casted = np.flip(casted, axis=-1) # do the diff - diff = np.abs(image.sum(axis=-1, dtype=np.int16) - a.sum(axis=-1, dtype=np.int16)) diff = np.clip(diff, 0, 255).astype(np.uint8) diff = np.stack((diff, diff, diff), axis=-1) @@ -57,7 +65,6 @@ time taken:\t\t{dur}s """) # make the composite image - left = np.vstack((image, a), dtype=np.uint8) right = np.vstack((casted, diff), dtype=np.uint8) diff --git a/make.py b/make.py index 0e65896..531a835 100755 --- a/make.py +++ b/make.py @@ -7,13 +7,17 @@ from numpy import save import matplotlib.pyplot as plt if __name__ == "__main__": + # make camera object eye = Camera(0) + # make the LUT and process through the camera lut = Cube(eye) lut.process() + # use graphing func to show the curves for debug show(lut.lut) + # validate error of LUT validate(lut) save("./cube.npy", lut.lut) -- 2.39.2