From f62b7904724f0eb991f96821c7d93eebd592fea4 Mon Sep 17 00:00:00 2001 From: Jannes Date: Tue, 14 Oct 2025 13:59:42 +0200 Subject: [PATCH] add number_circle --- frontend/__main__.py | 15 +++++---- frontend/config.py | 6 ++-- frontend/views/__init__.py | 3 +- frontend/views/base.py | 39 ++++++++++++++++++---- frontend/views/circle.py | 46 +++++++++++++------------- frontend/views/number.py | 11 ++++--- frontend/views/number_circle.py | 48 +++++++++++++++++++++++++++ pyproject.toml | 1 + requirements.txt | 2 -- uv.lock | 57 +++++++++++++++++++++++++++++++++ 10 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 frontend/views/number_circle.py delete mode 100644 requirements.txt diff --git a/frontend/__main__.py b/frontend/__main__.py index 164f580..0662b8f 100644 --- a/frontend/__main__.py +++ b/frontend/__main__.py @@ -94,13 +94,8 @@ class WeightApp(tk.Tk): def _on_connect(self, connected): if connected: - # port = self.port.get() - # self.weight_reader.connect(port) - # self.connect_button.config(text="Disconnect") self.show_device_components() else: - # self.weight_reader.disconnect() - # self.connect_button.config(text="Connect") self.hide_device_components() def update_devices(self): @@ -110,7 +105,6 @@ class WeightApp(tk.Tk): self.port.set(self.weight_reader.ports[0]) def update_calib(self): - print(self.calib_weight.get(), self.calib_measurements.get()) self.weight_reader.calib_factor = float(self.calib_weight.get()) / float(self.calib_measurements.get()) def update_view(self): @@ -132,6 +126,12 @@ class WeightApp(tk.Tk): calibrate_command=self.calibrate, padx=50) self.view.pack(side=tk.RIGHT) + elif selected_view == DISPLAY_TYPES.NUMBER_CIRCLE.value: + self.view = NumberCircleView(self, + tare_command=self.weight_reader.tare, + calibrate_command=self.calibrate, + padx=50) + self.view.pack(side=tk.RIGHT) else: raise Exception(f"View {selected_view} not found.") @@ -139,7 +139,7 @@ class WeightApp(tk.Tk): weight = self.weight_reader.value if self.recording: self.record_window.append((time() - self.record_start, weight)) - self.view.update_weight(weight) + self.view.refresh(weight) self.after(20, self.update_weight_display) def calibrate(self): @@ -153,6 +153,7 @@ class WeightApp(tk.Tk): print(self.weight_reader.calib_window) self.calib_measurements.delete(0, 'end') self.calib_measurements.insert(0, mean(self.weight_reader.calib_window)) + self.update_calib() def trigger_record(self): if self.recording: diff --git a/frontend/config.py b/frontend/config.py index 02d5e47..06300da 100644 --- a/frontend/config.py +++ b/frontend/config.py @@ -1,6 +1,7 @@ from enum import Enum -DEFAULT_CALIB = 307333.83 +# DEFAULT_CALIB = 307333.83 +DEFAULT_CALIB = -105030.71880199667 DEFAULT_CALIB_WEIGHT = 100. MOV_AVG_DEFAULTS = { @@ -12,4 +13,5 @@ MOV_AVG_DEFAULTS = { class DISPLAY_TYPES(Enum): NUMBER = 'number' - CIRCLE = 'circle' \ No newline at end of file + CIRCLE = 'circle' + NUMBER_CIRCLE = 'number_circle' \ No newline at end of file diff --git a/frontend/views/__init__.py b/frontend/views/__init__.py index b5728b9..ca02b23 100644 --- a/frontend/views/__init__.py +++ b/frontend/views/__init__.py @@ -1,2 +1,3 @@ from .number import NumberView -from .circle import CircleView \ No newline at end of file +from .circle import CircleView +from .number_circle import NumberCircleView \ No newline at end of file diff --git a/frontend/views/base.py b/frontend/views/base.py index 5145e2b..c1dd5e1 100644 --- a/frontend/views/base.py +++ b/frontend/views/base.py @@ -1,4 +1,7 @@ from tkinter import ttk, Frame, Canvas +from PIL import Image, ImageDraw +from tkinter import PhotoImage +import io class View(Frame): @@ -12,15 +15,37 @@ class View(Frame): self.tare_button = ttk.Button(self.actions, text="Tare", command=tare_command) self.tare_button.pack() - self.canvas = Canvas(self, width=144, height=168, background='white') + self.size = (168, 144) + self.center = (168 // 2, 144 // 2) + self.bkg_im = self._init_im() + self._init_ui() + + self.canvas = Canvas(self, width=168, height=144, background='white', + highlightthickness=1, highlightbackground="black") self.canvas.pack() - self.center = (144 // 2, 168 // 2) + self.refresh(0.0) + + def _init_ui(self): + pass - self._init_canvas() - - def _init_canvas(self): + def _init_im(self): + return Image.new('1', self.size, 'white') + + def update_weight(self, + weight: float) -> None: raise NotImplementedError() - def update_weight(self, weight: float): - raise NotImplementedError() + def refresh(self, weight: float): + # draw weight on bkg_im + im = self.update_weight(weight) + + # Convert PIL image to bytes + buffer = io.BytesIO() + im.save(buffer, format='PNG') + buffer.seek(0) + + # Load into PhotoImage and display on canvas + self.photo = PhotoImage(data=buffer.getvalue()) + self.canvas.delete("all") + self.canvas.create_image(0, 0, anchor="nw", image=self.photo) diff --git a/frontend/views/circle.py b/frontend/views/circle.py index be9d7ff..7117b93 100644 --- a/frontend/views/circle.py +++ b/frontend/views/circle.py @@ -1,16 +1,12 @@ import tkinter as tk from tkinter import ttk +from PIL import Image, ImageDraw, ImageChops from .base import View class CircleView(View): - def _init_canvas(self): - self.target_r = self.center[0] - 10 - self.draw_circle(self.center[0], self.center[1], - self.target_r, - outline="#000000") - + def _init_ui(self): self.target_frame = tk.Frame(self.actions) self.target_frame.pack() self.target_label = ttk.Label(self.target_frame, text="Target (g)") @@ -19,27 +15,31 @@ class CircleView(View): self.target.insert(0, 100.0) self.target.pack(side=tk.LEFT) - def draw_circle(self, x, y, r, **kwargs): - self.canvas.create_oval(x-r, y-r, x+r, y+r, **kwargs) + def _init_im(self): + im = Image.new('1', self.size, 'white') + draw = ImageDraw.Draw(im) + self.target_r = min(self.center)-10 + draw.circle(self.center, self.target_r, + outline="#000000") + return im def update_weight(self, weight): - self.canvas.delete("all") - - weight_r = 0.0 + weight_radius = 0.0 + bkg_im = self.bkg_im.copy() try: target = float(self.target.get()) - weight_r = weight / target * self.target_r + weight_radius = weight / target * self.target_r - self.draw_circle(self.center[0], self.center[1], - weight_r, - fill="#000000") - except: + im = Image.new('1', self.size, 'black') + draw = ImageDraw.Draw(im) + + if weight_radius > 0.1: + draw.circle(self.center, weight_radius, fill="#FFFFFF") + + return ImageChops.logical_xor(bkg_im, im) + + except Exception as e: + print('Invalid target value:', e) pass - finally: - target_outline = "#000000" - if weight_r > self.target_r: - target_outline = "#FFFFFF" - self.draw_circle(self.center[0], self.center[1], - self.target_r, - outline=target_outline) + return bkg_im diff --git a/frontend/views/number.py b/frontend/views/number.py index 701c025..b97476c 100644 --- a/frontend/views/number.py +++ b/frontend/views/number.py @@ -1,9 +1,12 @@ +from PIL import Image, ImageDraw + from .base import View class NumberView(View): - - def _init_canvas(self): - self.label = self.canvas.create_text(50, 68, text="0.0 g", font=("Arial", 18), fill='black', justify='left') def update_weight(self, weight): - self.canvas.itemconfig(self.label, text=f"{weight:.1f} g") + im = self.bkg_im.copy() + im_draw = ImageDraw.Draw(im) + im_draw.text((50, 68), f"{weight:.1f} g", + fill='black', font_size=24) + return im diff --git a/frontend/views/number_circle.py b/frontend/views/number_circle.py new file mode 100644 index 0000000..eaf0cec --- /dev/null +++ b/frontend/views/number_circle.py @@ -0,0 +1,48 @@ +import tkinter as tk +from tkinter import ttk +from PIL import Image, ImageDraw, ImageChops + +from .base import View + +class NumberCircleView(View): + + def _init_ui(self): + self.target_frame = tk.Frame(self.actions) + self.target_frame.pack() + self.target_label = ttk.Label(self.target_frame, text="Target (g)") + self.target_label.pack(side=tk.LEFT) + self.target = ttk.Entry(self.target_frame) + self.target.insert(0, 100.0) + self.target.pack(side=tk.LEFT) + + def _init_im(self): + im = Image.new('1', self.size, 'white') + draw = ImageDraw.Draw(im) + self.target_r = min(self.center)-10 + draw.circle(self.center, self.target_r, + outline="#000000") + return im + + def update_weight(self, weight): + weight_radius = 0.0 + bkg_im = self.bkg_im.copy() + draw = ImageDraw.Draw(bkg_im) + draw.text((50, 68), f"{weight:.1f} g", + fill='black', font_size=24) + + try: + target = float(self.target.get()) + weight_radius = weight / target * self.target_r + + circle_im = Image.new('1', self.size, 'black') + circle_draw = ImageDraw.Draw(circle_im) + + if weight_radius > 0.1: + circle_draw.circle(self.center, weight_radius, fill="#FFFFFF") + + return ImageChops.logical_xor(bkg_im, circle_im) + + except Exception as e: + print('Invalid target value:', e) + pass + return bkg_im diff --git a/pyproject.toml b/pyproject.toml index af4183c..2b5784f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.13" dependencies = [ "pandas>=2.3.3", + "pillow>=11.3.0", "pyserial>=3.5", "python-toolkit", ] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a3402b7..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -numpy -pyserial \ No newline at end of file diff --git a/uv.lock b/uv.lock index 85ef469..cfb6ca1 100644 --- a/uv.lock +++ b/uv.lock @@ -8,6 +8,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "pandas" }, + { name = "pillow" }, { name = "pyserial" }, { name = "python-toolkit" }, ] @@ -15,6 +16,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "pandas", specifier = ">=2.3.3" }, + { name = "pillow", specifier = ">=11.3.0" }, { name = "pyserial", specifier = ">=3.5" }, { name = "python-toolkit", git = "https://git.magnuss.link/JannTer/python-toolkit" }, ] @@ -111,6 +113,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, ] +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +] + [[package]] name = "pyserial" version = "3.5"