fix beamformer and qtimage

add ipy
This commit is contained in:
flandre 2025-04-13 16:47:14 +08:00
parent 6973dc619f
commit bec256bc22
12 changed files with 611 additions and 25 deletions

View File

@ -99,12 +99,15 @@ class SoftwareConfig:
j = json.loads(path.read_text(encoding='utf-8')) j = json.loads(path.read_text(encoding='utf-8'))
arg_d = dict() arg_d = dict()
for field in dataclasses.fields(SoftwareConfig): for field in dataclasses.fields(SoftwareConfig):
v = j[field.name] try:
match field.type: v = j[field.name]
case Path(): match field.type:
arg_d[field.name] = Path(v) case Path():
case _: arg_d[field.name] = Path(v)
arg_d[field.name] = v case _:
arg_d[field.name] = v
except KeyError:
pass
sc = SoftwareConfig(**arg_d) sc = SoftwareConfig(**arg_d)
return sc return sc

View File

@ -5,11 +5,14 @@ import cupy as cp
import cv2 import cv2
import zmq import zmq
from flandre.beamformer.das import gen_pwi
from flandre.beamformer.dist import direct_dist
from flandre.config import C from flandre.config import C
from flandre.nodes.Node import Node from flandre.nodes.Node import Node
from flandre.utils.Config import DeviceConfig
from flandre.utils.Msg import BMMsg, ImageArgMsg, SetSeqMetaMsg, Msg, RfFrameWithMetaMsg, BeamformerMsg, \ from flandre.utils.Msg import BMMsg, ImageArgMsg, SetSeqMetaMsg, Msg, RfFrameWithMetaMsg, BeamformerMsg, \
RequestRfFrameMsg, \ RequestRfFrameMsg, \
SeqMetaMsg SeqMetaMsg, RfMatMsg
from flandre.utils.RfFile import RfSequenceMeta from flandre.utils.RfFile import RfSequenceMeta
from flandre.utils.RfMat import RfMat from flandre.utils.RfMat import RfMat
@ -28,22 +31,33 @@ class Beamformer(Node):
self.muxer_req_socket.connect(C.muxer_rep_socket) self.muxer_req_socket.connect(C.muxer_rep_socket)
self.c.poller.register(self.muxer_req_socket, zmq.POLLIN) self.c.poller.register(self.muxer_req_socket, zmq.POLLIN)
def process(self, data: RfMat, arg: ImageArgMsg): def process_pwi(self, data: RfMat, arg: ImageArgMsg, pwi):
if data is None: if data is None:
return return
d2 = (data d2 = (data
.crop(arg.t_start, arg.t_end) .crop(arg.t_start, arg.t_end)
.dct(80, 1500)
.call(lambda m: m.astype(cp.int16))
.call(pwi)
.call(cp.asarray, order='C')
.argrelextrema()
.conv_guass(b=18 * 0.01)
.rotate90() .rotate90()
.grey() .grey()
.cpu() .cpu()
.resize((C.video_width, C.video_height)) # .resize((C.video_width, C.video_height))
.watermark() # .watermark()
.call(cv2.cvtColor, cv2.COLOR_GRAY2RGBA) # .call(cv2.cvtColor, cv2.COLOR_GRAY2RGBA)
) )
self.send(BMMsg(0, d2.__bytes__())) # print(d2.m.dtype)
# input()
# self.send(BMMsg(0, d2.__bytes__()))
self.send(RfMatMsg(d2))
def loop(self): def loop(self):
time.sleep(1) time.sleep(1)
dc = DeviceConfig()
pwi,_ = gen_pwi(direct_dist(dc), dc)
while True: while True:
self.muxer_req_socket.send(b'') self.muxer_req_socket.send(b'')
r = dict(self.c.poller.poll()) r = dict(self.c.poller.poll())
@ -66,4 +80,5 @@ class Beamformer(Node):
fb1 = cp.frombuffer(s, dtype=cp.int16) fb1 = cp.frombuffer(s, dtype=cp.int16)
seq_meta = RfSequenceMeta.from_name(seq_msg.name) seq_meta = RfSequenceMeta.from_name(seq_msg.name)
mat = RfMat(fb1.reshape(seq_meta.shape), b_msg.meta, seq_meta) mat = RfMat(fb1.reshape(seq_meta.shape), b_msg.meta, seq_meta)
self.process(mat, arg_msg) if seq_meta.mode == RfSequenceMeta.RfSequenceMode.PWI:
self.process_pwi(mat, arg_msg, pwi)

View File

@ -1,14 +1,16 @@
import logging
import sys import sys
import numpy as np
from PyQt6.QtCore import QByteArray, Qt from PyQt6.QtCore import QByteArray, Qt
from PyQt6.QtGui import QImage, QPixmap from PyQt6.QtGui import QImage, QPixmap, QKeyEvent
from PyQt6.QtWidgets import QMainWindow, QApplication, QGraphicsPixmapItem, QGraphicsScene from PyQt6.QtWidgets import QMainWindow, QApplication, QGraphicsPixmapItem, QGraphicsScene
from flandre.config import C from flandre.config import C
from flandre.nodes.Node import Node from flandre.nodes.Node import Node
from flandre.pyqt.Image import Ui_MainWindow from flandre.pyqt.Image import Ui_MainWindow
from flandre.pyqt.ZMQReceiver import ZMQReceiver from flandre.pyqt.ZMQReceiver import ZMQReceiver
from flandre.utils.Msg import KillMsg, Msg, BMMsg from flandre.utils.Msg import KillMsg, Msg, BMMsg, RfMatMsg, KeyPressMsg
class Adv(QMainWindow, Ui_MainWindow): class Adv(QMainWindow, Ui_MainWindow):
@ -23,6 +25,24 @@ class Adv(QMainWindow, Ui_MainWindow):
self.s = QGraphicsScene() self.s = QGraphicsScene()
self.s.addItem(self.g) self.s.addItem(self.g)
self.graphicsView.setScene(self.s) self.graphicsView.setScene(self.s)
self.grey = True
self.scale = False
def keyPressEvent(self, a0: QKeyEvent):
t = a0.text()
match t:
case 'm':
self.grey = not self.grey
case 's':
self.scale = not self.scale
def on_key(self, key):
# test for a specific key
logging.info(f'{type(key)}')
# if key == QtCore.Qt.Key_Return:
# print('return key pressed')
# else:
# print('key pressed: %i' % key)
def on_zmq_event(self, msg: QByteArray): def on_zmq_event(self, msg: QByteArray):
msg = Msg.decode_msg(msg.data()) msg = Msg.decode_msg(msg.data())
@ -39,10 +59,33 @@ class Adv(QMainWindow, Ui_MainWindow):
) )
self.g.setPixmap(QPixmap(qImg)) self.g.setPixmap(QPixmap(qImg))
self.graphicsView.fitInView(self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio) self.graphicsView.fitInView(self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
elif isinstance(msg, RfMatMsg):
w = msg.rfmat.m.shape[1]
h = msg.rfmat.m.shape[0]
if self.grey:
d2 = msg.rfmat.grey()
qImg = QImage(
d2.__bytes__(),
w, h, w,
QImage.Format.Format_Grayscale8
)
else:
d2 = msg.rfmat.pseudo_color()
qImg = QImage(
d2.__bytes__(),
w, h, 3 * w,
QImage.Format.Format_BGR888
)
self.g.setPixmap(QPixmap(qImg))
if self.scale:
self.graphicsView.fitInView(self.s.sceneRect())
else:
self.graphicsView.fitInView(self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
class ImageQt(Node): class ImageQt(Node):
topics = [BMMsg] topics = [BMMsg, RfMatMsg, KeyPressMsg]
def __init__(self): def __init__(self):
super().__init__() super().__init__()

View File

@ -4,7 +4,10 @@ import struct
from enum import auto, Enum from enum import auto, Enum
from pathlib import Path from pathlib import Path
from flandre.utils.RfMeta import RfFrameMeta import numpy as np
# from flandre.utils.RfMat import RfMat
from flandre.utils.RfMeta import RfFrameMeta, RfSequenceMeta
class BG(Enum): class BG(Enum):
@ -48,6 +51,8 @@ class BG(Enum):
SetDeviceSwitchMsg = auto() SetDeviceSwitchMsg = auto()
DeviceSwitchMsg = auto() DeviceSwitchMsg = auto()
HeaderByteMsg = auto() HeaderByteMsg = auto()
RfMatMsg = auto()
KeyPressMsg = auto()
class Msg: class Msg:
@ -134,6 +139,11 @@ class StrMsg(Msg):
value: str = '' value: str = ''
@dataclasses.dataclass
class KeyPressMsg(StrMsg):
pass
@dataclasses.dataclass @dataclasses.dataclass
class BoolMsg(Msg): class BoolMsg(Msg):
value: bool value: bool
@ -302,6 +312,32 @@ class RfFrameMsg(Msg):
) )
class RfMatMsg(HeaderByteMsg):
def __init__(self, rfmat: 'RfMat'):
self.rfmat = rfmat
super().__init__(dict(
frame_meta=rfmat.frame_meta.json_str,
seq_meta=rfmat.seq_meta.json_str,
data_shape=rfmat.m.shape,
dtype=str(rfmat.m.dtype),
), rfmat.m.tobytes())
@classmethod
def decode(cls, data) -> 'RfMatMsg':
from flandre.utils.RfMat import RfMat
msg = super(RfMatMsg, cls).decode(data)
dt = np.dtype(msg.header['dtype'])
mat = np.frombuffer(msg.data, dtype=dt).reshape(msg.header['data_shape'])
rfmat = RfMat(
mat,
RfFrameMeta.from_json_str(msg.header['frame_meta']),
RfSequenceMeta.from_json_str(msg.header['seq_meta']),
)
return RfMatMsg(rfmat)
class RfFrameWithMetaMsg(HeaderByteMsg): class RfFrameWithMetaMsg(HeaderByteMsg):
@dataclasses.dataclass @dataclasses.dataclass
class Header: class Header:
@ -327,6 +363,7 @@ class RfFrameWithMetaMsg(HeaderByteMsg):
header = cls.Header(**msg.header) header = cls.Header(**msg.header)
return RfFrameWithMetaMsg(header.sender, RfFrameMeta.from_name(header.meta), msg.data, header.is_zip) return RfFrameWithMetaMsg(header.sender, RfFrameMeta.from_name(header.meta), msg.data, header.is_zip)
@dataclasses.dataclass @dataclasses.dataclass
class BytesMsg(Msg): class BytesMsg(Msg):
value: bytes value: bytes
@ -402,7 +439,11 @@ if __name__ == '__main__':
# c2 = c.decode(c.encode()) # c2 = c.decode(c.encode())
# print(c2.header, c2.data) # print(c2.header, c2.data)
c = RfFrameWithMetaMsg(1, RfFrameMeta(1, 11, 111), b'asdasdasdads',True) # c = RfFrameWithMetaMsg(1, RfFrameMeta(1, 11, 111), b'asdasdasdads', True)
print(c.sender, c.meta, c.data, c._header) # print(c.sender, c.meta, c.data, c._header)
c2 = c.decode(c.encode()) # c2 = c.decode(c.encode())
print(c2.sender, c2.meta, c2.data, c2._header) # print(c2.sender, c2.meta, c2.data, c2._header)
rfmat = RfMat(np.array([1, 2, 3]), RfFrameMeta(), RfSequenceMeta())
e = RfMatMsg(rfmat).encode()
d = RfMatMsg.decode(e)

View File

@ -1,7 +1,20 @@
import cupyx
import cv2 import cv2
import matplotlib
import numpy as np import numpy as np
import cupy as cp import cupy as cp
import cupyx
import cupyx.scipy.fft
import cupyx.scipy.ndimage
import cupyx.scipy.signal
import cv2
import scipy
import scipy.signal
from cupyx.scipy.fft import dctn, idctn
from scipy.stats import norm as norms
from flandre.utils.RfFile import RfFrame, RfSequenceMeta from flandre.utils.RfFile import RfFrame, RfSequenceMeta
from flandre.utils.RfMeta import RfFrameMeta from flandre.utils.RfMeta import RfFrameMeta
@ -74,6 +87,19 @@ class RfMat:
m = self.norm().m m = self.norm().m
return self.copy((m * 255).astype(self.p.uint8)) return self.copy((m * 255).astype(self.p.uint8))
def pseudo_color(self):
m = self.norm().m * 0.7
p = self.p
h = m
s = p.zeros_like(h) + 1
v = p.zeros_like(h) + 1
hsv = p.stack((h, s, v), axis=2)
if p == cp:
rgb = matplotlib.colors.hsv_to_rgb(hsv.get())
else:
rgb = matplotlib.colors.hsv_to_rgb(hsv)
return self.copy((rgb * 255).astype(np.uint8))
def cpu(self): def cpu(self):
if self.device == 'cpu': if self.device == 'cpu':
return self return self
@ -139,6 +165,57 @@ class RfMat:
def rotate90(self): def rotate90(self):
return self.copy(self.p.rot90(self.m, k=3)) return self.copy(self.p.rot90(self.m, k=3))
def dct(self, mmin, mmax):
dct_ = scipy.fft.dct
idct = scipy.fft.idct
if self.p == cp:
dct_ = cupyx.scipy.fft.dct
idct = cupyx.scipy.fft.idct
m_dct = dct_(self.m)
if self.seq_meta.d() == 2:
m_dct[:, mmax:] = 0
m_dct[:, :mmin] = 0
elif self.seq_meta.d() == 3:
m_dct[:, :, mmax:] = 0
m_dct[:, :, :mmin] = 0
else:
raise NotImplementedError()
return self.copy(idct(m_dct))
def argrelextrema(self, axis=1):
arg = scipy.signal.argrelextrema
m = self.m
p = self.p
if p == cp:
arg = cupyx.scipy.signal.argrelextrema
rm = p.zeros_like(m)
indies1 = arg(m, p.greater, axis=axis)
bl = p.zeros_like(m, dtype=p.bool_)
indies2 = arg(m, p.less, axis=axis)
bl2 = p.zeros_like(m, dtype=p.bool_)
bl[indies1] = True
bl2[indies2] = True
i1 = bl & (m > 0)
i2 = bl2 & (m < 0)
idx = i1 | i2
rm[idx] = m[idx].__abs__()
return self.copy(rm)
def conv_guass(self, b=0.01, axis=1):
cv = scipy.ndimage.convolve1d
m = self.m
p = self.p
if p == cp:
cv = cupyx.scipy.ndimage.convolve1d
rv = norms(loc=0, scale=b)
x2 = np.arange(-1, 1.1, 0.1)
w = rv.pdf(x2)
if p == cp:
w = cp.asarray(w)
rm = cv(m, w, axis=axis)
return self.copy(rm)
if __name__ == '__main__': if __name__ == '__main__':
cp.zeros((1, 2, 3)) + 1 cp.zeros((1, 2, 3)) + 1

View File

@ -1,9 +1,11 @@
import dataclasses
import json import json
from dataclasses import dataclass
from enum import Enum, auto from enum import Enum, auto
from pathlib import Path from pathlib import Path
from typing import Annotated, get_type_hints from typing import Annotated, get_type_hints
from attr import dataclass # from attr import dataclass
COMMIT_KEY = 'COMMIT' COMMIT_KEY = 'COMMIT'
@ -88,6 +90,19 @@ class RfMeta:
def json_str(self): def json_str(self):
return json.dumps(self.__dict__) return json.dumps(self.__dict__)
@classmethod
def from_json_str(clz, j: str):
j = json.loads(j)
meta = clz()
for field in dataclasses.fields(clz):
v = j[field.name]
match v:
case None:
pass
case _:
meta.__setattr__(field.name, field.type(v))
return meta
@dataclass @dataclass
class RfFrameMeta(RfMeta): class RfFrameMeta(RfMeta):
@ -129,7 +144,49 @@ class RfSequenceMeta(RfMeta):
res *= int(i) res *= int(i)
return res return res
def d(self):
return self.shape.__len__()
@property
def json_str(self):
d = dict()
for field in dataclasses.fields(RfSequenceMeta):
v = self.__getattribute__(field.name)
match v:
case self.RfSequenceMode():
d[field.name] = v.name
case _:
d[field.name] = v
return json.dumps(d)
@classmethod
def from_json_str(clz, j: str):
j = json.loads(j)
meta = RfSequenceMeta()
for field in dataclasses.fields(RfSequenceMeta):
v = j[field.name]
match field.name:
case 'mode':
meta.mode = RfSequenceMeta.RfSequenceMode[v]
case _:
if v is None:
meta.__setattr__(field.name, None)
else:
meta.__setattr__(field.name, field.type(v))
return meta
if __name__ == '__main__': if __name__ == '__main__':
r = RfSequenceMeta.from_name('asdasd1,S=(64 1501),M=PWI,U=30') # r = RfSequenceMeta.from_name('asdasd1,S=(64 1501),M=PWI,U=30')
# print(r)
# print(r.json_str)
#
# r2 = RfSequenceMeta.from_json_str(r.json_str)
# print(r2)
r = RfFrameMeta.from_name('E=1,S=123')
print(r) print(r)
print(r.json_str)
r2 = RfFrameMeta.from_json_str(r.json_str)
print(r2)

View File

@ -22,8 +22,6 @@ def folder_to_zip(folder: Path):
meta.blake2b = b2b meta.blake2b = b2b
print(i, b2b, file, meta.json_str) print(i, b2b, file, meta.json_str)
src = file src = file
dst = TEMP_FOLDER / f'{i}.zst' dst = TEMP_FOLDER / f'{i}.zst'
dstj = TEMP_FOLDER / f'{i}.json' dstj = TEMP_FOLDER / f'{i}.json'
@ -34,6 +32,25 @@ def folder_to_zip(folder: Path):
subprocess.run(['zip', '-0', '-j', '-r', TEMP_FOLDER.parent / f'{folder.name}.zip', TEMP_FOLDER]) subprocess.run(['zip', '-0', '-j', '-r', TEMP_FOLDER.parent / f'{folder.name}.zip', TEMP_FOLDER])
def to_zip(li: list[tuple[Path, 'RfFrameMeta', list[tuple[Path, str]]]], temp_dst: Path, zipdst: Path):
shutil.rmtree(temp_dst)
temp_dst.mkdir(parents=True, exist_ok=True)
for i, (file, meta, farr) in enumerate(li):
b2b = hashlib.blake2b(file.read_bytes(), digest_size=4).hexdigest()
meta.blake2b = b2b
src = file
dst = temp_dst / f'{i}.zst'
subprocess.run(['zstd', '-f', src, '-o', dst])
dstj = temp_dst / f'{i}.json'
dstj.write_text(meta.json_str)
for srcf, suffix in farr:
dstf = temp_dst / f'{i}{suffix}'
shutil.copy(srcf, dstf)
subprocess.run(['zip', '-0', '-j', '-r', zipdst, temp_dst])
def zip_to_bytes(file: Path, name: int): def zip_to_bytes(file: Path, name: int):
return zstd.loads(zipfile.ZipFile(file).open(f'{name}.zst').read()) return zstd.loads(zipfile.ZipFile(file).open(f'{name}.zst').read())

View File

@ -18,6 +18,7 @@ dependencies = [
"pyjoystick>=1.2.4", "pyjoystick>=1.2.4",
"platformdirs>=4.3.6", "platformdirs>=4.3.6",
"zstd>=1.5.6.7", "zstd>=1.5.6.7",
"matplotlib>=3.10.1",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]

30
test/process_leagacy.py Normal file
View File

@ -0,0 +1,30 @@
from pathlib import Path
from flandre.utils.RfMeta import RfFrameMeta, RfSequenceMeta
from flandre.utils.archive import to_zip
if __name__ == '__main__':
# rr = RfSequenceMeta
tempdst = Path('/mnt/16T/private_dataset/ustemp')
arr = []
for (i,
file) in enumerate(Path('/mnt/16T/private_dataset/New Folder/steel-top/').glob('*pwi.bin')):
file = Path(file)
x, y, _ = file.name.split('_')
r = RfFrameMeta(encoder=0, robot_x=int(x) * 100, robot_y=int(y) * 100)
arr.append((file, r))
arr.sort(key=lambda item: (item[1].robot_y, item[1].robot_x))
arg = []
for i, item in enumerate(arr):
file, meta = item
meta.sequence_id = i
print(file.name, meta.sequence_id, meta.robot_x, meta.robot_y)
pic = file.with_suffix('.png')
farr = []
if pic.exists():
farr.append((pic, '.png'))
arg.append((file, meta, farr))
to_zip(arg, tempdst, Path('/mnt/16T/private_dataset/us/steel-top,U=30,M=PWI,S=(256 1502).zip'))

14
test/testcupy.py Normal file
View File

@ -0,0 +1,14 @@
import json
import cupy as cp
import numpy as np
if __name__ == '__main__':
arr = np.array([
[1, 2, 3],
[1, 2, 3],
])
print(type(arr.shape))
print(np.frombuffer(arr.tobytes(), dtype=np.dtype(str(arr.dtype))).reshape(arr.shape))

163
us.ipynb Normal file

File diff suppressed because one or more lines are too long

125
uv.lock
View File

@ -253,6 +253,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/fb/08b3f4bf05da99aba8ffea52a558758def16e8516bc75ca94ff73587e7d3/construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30", size = 63020 }, { url = "https://files.pythonhosted.org/packages/b2/fb/08b3f4bf05da99aba8ffea52a558758def16e8516bc75ca94ff73587e7d3/construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30", size = 63020 },
] ]
[[package]]
name = "contourpy"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/6b/175f60227d3e7f5f1549fcb374592be311293132207e451c3d7c654c25fb/contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509", size = 271494 },
{ url = "https://files.pythonhosted.org/packages/6b/6a/7833cfae2c1e63d1d8875a50fd23371394f540ce809d7383550681a1fa64/contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc", size = 255444 },
{ url = "https://files.pythonhosted.org/packages/7f/b3/7859efce66eaca5c14ba7619791b084ed02d868d76b928ff56890d2d059d/contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454", size = 307628 },
{ url = "https://files.pythonhosted.org/packages/48/b2/011415f5e3f0a50b1e285a0bf78eb5d92a4df000553570f0851b6e309076/contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80", size = 347271 },
{ url = "https://files.pythonhosted.org/packages/84/7d/ef19b1db0f45b151ac78c65127235239a8cf21a59d1ce8507ce03e89a30b/contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec", size = 318906 },
{ url = "https://files.pythonhosted.org/packages/ba/99/6794142b90b853a9155316c8f470d2e4821fe6f086b03e372aca848227dd/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9", size = 323622 },
{ url = "https://files.pythonhosted.org/packages/3c/0f/37d2c84a900cd8eb54e105f4fa9aebd275e14e266736778bb5dccbf3bbbb/contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b", size = 1266699 },
{ url = "https://files.pythonhosted.org/packages/3a/8a/deb5e11dc7d9cc8f0f9c8b29d4f062203f3af230ba83c30a6b161a6effc9/contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d", size = 1326395 },
{ url = "https://files.pythonhosted.org/packages/1a/35/7e267ae7c13aaf12322ccc493531f1e7f2eb8fba2927b9d7a05ff615df7a/contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e", size = 175354 },
{ url = "https://files.pythonhosted.org/packages/a1/35/c2de8823211d07e8a79ab018ef03960716c5dff6f4d5bff5af87fd682992/contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d", size = 220971 },
]
[[package]] [[package]]
name = "croniter" name = "croniter"
version = "6.0.0" version = "6.0.0"
@ -311,6 +332,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/14/11/8bc53cab1466605ba88ba3c7243078a784ee2cd8974f5602a491882af9af/cupy_cuda12x-13.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:88ef1478f00ae252da0026e7f04f70c9bb6a2dc130ba5f1e5bc5e8069a928bf5", size = 69499349 }, { url = "https://files.pythonhosted.org/packages/14/11/8bc53cab1466605ba88ba3c7243078a784ee2cd8974f5602a491882af9af/cupy_cuda12x-13.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:88ef1478f00ae252da0026e7f04f70c9bb6a2dc130ba5f1e5bc5e8069a928bf5", size = 69499349 },
] ]
[[package]]
name = "cycler"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
]
[[package]] [[package]]
name = "debugpy" name = "debugpy"
version = "1.8.11" version = "1.8.11"
@ -382,6 +412,7 @@ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "cupy-cuda12x" }, { name = "cupy-cuda12x" },
{ name = "jupyter" }, { name = "jupyter" },
{ name = "matplotlib" },
{ name = "mido", extra = ["ports-rtmidi"] }, { name = "mido", extra = ["ports-rtmidi"] },
{ name = "opencv-python" }, { name = "opencv-python" },
{ name = "platformdirs" }, { name = "platformdirs" },
@ -399,6 +430,7 @@ requires-dist = [
{ name = "click", specifier = ">=8.1.8" }, { name = "click", specifier = ">=8.1.8" },
{ name = "cupy-cuda12x", specifier = ">=13.3.0" }, { name = "cupy-cuda12x", specifier = ">=13.3.0" },
{ name = "jupyter", specifier = ">=1.1.1" }, { name = "jupyter", specifier = ">=1.1.1" },
{ name = "matplotlib", specifier = ">=3.10.1" },
{ name = "mido", extras = ["ports-rtmidi"], specifier = ">=1.3.3" }, { name = "mido", extras = ["ports-rtmidi"], specifier = ">=1.3.3" },
{ name = "opencv-python", specifier = ">=4.10.0.84" }, { name = "opencv-python", specifier = ">=4.10.0.84" },
{ name = "platformdirs", specifier = ">=4.3.6" }, { name = "platformdirs", specifier = ">=4.3.6" },
@ -411,6 +443,23 @@ requires-dist = [
{ name = "zstd", specifier = ">=1.5.6.7" }, { name = "zstd", specifier = ">=1.5.6.7" },
] ]
[[package]]
name = "fonttools"
version = "4.57.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824 },
{ url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072 },
{ url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020 },
{ url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096 },
{ url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356 },
{ url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546 },
{ url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776 },
{ url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956 },
{ url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605 },
]
[[package]] [[package]]
name = "fqdn" name = "fqdn"
version = "1.5.1" version = "1.5.1"
@ -827,6 +876,29 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 },
] ]
[[package]]
name = "kiwisolver"
version = "1.4.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152 },
{ url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555 },
{ url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067 },
{ url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443 },
{ url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728 },
{ url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388 },
{ url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849 },
{ url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533 },
{ url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898 },
{ url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605 },
{ url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801 },
{ url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077 },
{ url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410 },
{ url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853 },
{ url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424 },
]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "3.0.2" version = "3.0.2"
@ -845,6 +917,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
] ]
[[package]]
name = "matplotlib"
version = "3.10.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "contourpy" },
{ name = "cycler" },
{ name = "fonttools" },
{ name = "kiwisolver" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "pillow" },
{ name = "pyparsing" },
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488 },
{ url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264 },
{ url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048 },
{ url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111 },
{ url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771 },
{ url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742 },
]
[[package]] [[package]]
name = "matplotlib-inline" name = "matplotlib-inline"
version = "0.1.7" version = "0.1.7"
@ -1076,6 +1173,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 },
] ]
[[package]]
name = "pillow"
version = "11.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185 },
{ url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306 },
{ url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121 },
{ url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707 },
{ url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921 },
{ url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523 },
{ url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836 },
{ url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390 },
{ url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309 },
{ url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768 },
{ url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087 },
]
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.3.6" version = "4.3.6"
@ -1188,6 +1304,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/65/858dc1f7f4c65b9e9c8e769019cb156e6b8b96fd7f2f8f1f6bfef8717d95/pyjoystick-1.2.4-py3-none-any.whl", hash = "sha256:773989a5d796d7f32261d25338d583233ddae9a091c0c13c3fc09d250b0870a9", size = 1185266 }, { url = "https://files.pythonhosted.org/packages/a5/65/858dc1f7f4c65b9e9c8e769019cb156e6b8b96fd7f2f8f1f6bfef8717d95/pyjoystick-1.2.4-py3-none-any.whl", hash = "sha256:773989a5d796d7f32261d25338d583233ddae9a091c0c13c3fc09d250b0870a9", size = 1185266 },
] ]
[[package]]
name = "pyparsing"
version = "3.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 },
]
[[package]] [[package]]
name = "pyqt6" name = "pyqt6"
version = "6.8.0" version = "6.8.0"