From 45dc4d2430abcdc2976e2ce1fb88e81c8b7e96de Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 30 Jun 2025 14:34:02 +0200 Subject: [PATCH 001/109] Revert "chore: update apt versions based on rebuild" This reverts commit da2bea54 --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index d0917b89..145a82d3 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.5 libpam-modules=1.4.0-11ubuntu2.5 libpam-runtime=1.4.0-11ubuntu2.5 libpam0g=1.4.0-11ubuntu2.5 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-141.151 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev logsave mount openssl util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.9 libpython3.10-stdlib=3.10.12-1~22.04.9 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.9 python3.10=3.10.12-1~22.04.9 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation @@ -28,7 +28,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.24.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.2.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.2.2-1~ubuntu.22.04~jammy docker-ce=5:28.2.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.36.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -69,4 +69,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file From a7f10c3c5636824bfdb7d07b39d835f6d44baf30 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 30 Jun 2025 14:35:21 +0200 Subject: [PATCH 002/109] Revert "WIP: remove apt versions for rebuild" This reverts commit b89263947111dfa63b04e0c34fbd21759ed37013. --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 145a82d3..d0917b89 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt apt-utils libapt-pkg6.0 +RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev logsave mount openssl util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.5 libpam-modules=1.4.0-11ubuntu2.5 libpam-runtime=1.4.0-11ubuntu2.5 libpam0g=1.4.0-11ubuntu2.5 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-141.151 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.9 libpython3.10-stdlib=3.10.12-1~22.04.9 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.9 python3.10=3.10.12-1~22.04.9 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation @@ -28,7 +28,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.24.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.2.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.2.2-1~ubuntu.22.04~jammy docker-ce=5:28.2.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.36.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -69,4 +69,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm From f9be4ee1ddd163aeb068270730af9f26097ec50e Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 30 Jun 2025 14:36:18 +0200 Subject: [PATCH 003/109] Revert "chore: update apt versions based on rebuild" This reverts commit da2bea54 --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index d0917b89..145a82d3 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.5 libpam-modules=1.4.0-11ubuntu2.5 libpam-runtime=1.4.0-11ubuntu2.5 libpam0g=1.4.0-11ubuntu2.5 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-141.151 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev logsave mount openssl util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.9 libpython3.10-stdlib=3.10.12-1~22.04.9 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.9 python3.10=3.10.12-1~22.04.9 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation @@ -28,7 +28,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.24.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.2.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.2.2-1~ubuntu.22.04~jammy docker-ce=5:28.2.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.36.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -69,4 +69,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file From efa15b209232ca414ba55b8d0fb3fb74a5ea1e1f Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 30 Jun 2025 14:38:23 +0200 Subject: [PATCH 004/109] revert --- docker_config/Dockerfile_ODELIA | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 145a82d3..ac3a61f0 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,11 +15,10 @@ RUN apt update RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev logsave mount openssl util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.5 libpam-modules=1.4.0-11ubuntu2.5 libpam-runtime=1.4.0-11ubuntu2.5 libpam0g=1.4.0-11ubuntu2.5 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-141.151 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip - +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.9 libpython3.10-stdlib=3.10.12-1~22.04.9 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.9 python3.10=3.10.12-1~22.04.9 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ From 0c917e09e7d5109bd0bd020888e24c799ec5f0dc Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Mon, 7 Jul 2025 14:58:56 +0200 Subject: [PATCH 005/109] Potential fix for code scanning alert no. 3: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/pr-test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-test.yaml b/.github/workflows/pr-test.yaml index ccaf7a5c..eb9d0a2b 100644 --- a/.github/workflows/pr-test.yaml +++ b/.github/workflows/pr-test.yaml @@ -8,6 +8,9 @@ on: - main - dev +permissions: + contents: read + jobs: validate-swarm: runs-on: self-hosted From ee7f57d2881d6cb15f9a54865ec71f8fc4317a3a Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 10:29:30 +0200 Subject: [PATCH 006/109] updated apt package versions --- docker_config/Dockerfile_ODELIA | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c6b4f894..26348579 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm From f7c9afcbe8ccf94fae15e2b4b38845af3775d046 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 13:32:33 +0200 Subject: [PATCH 007/109] install einops and x-transformers which apparently is no longer available --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 26348579..57da5891 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -43,7 +43,7 @@ RUN python3 -m pip install --upgrade psutil==7.0.0 RUN python3 -m pip install Flask==3.0.2 Flask-JWT-Extended==4.6.0 Flask-SQLAlchemy==3.1.1 PyJWT==2.10.1 SQLAlchemy==2.0.16 Werkzeug==3.0.1 blinker==1.9.0 docker==7.1.0 greenlet==3.1.1 grpcio==1.62.1 gunicorn==23.0.0 itsdangerous==2.2.0 msgpack==1.1.0 protobuf==4.24.4 pyhocon==0.3.61 pyparsing==3.0.9 websockets==15.0 # Install additional Python packages for swarm training at defined versions -RUN python3 -m pip install Deprecated==1.2.14 SimpleITK==2.2.1 absl-py==2.1.0 aiohttp==3.9.5 aiosignal==1.3.1 async-timeout==4.0.3 cachetools==5.3.3 contourpy==1.2.1 cycler==0.12.1 et-xmlfile==1.1.0 fonttools==4.53.1 frozenlist==1.4.1 google-auth-oauthlib==1.0.0 google-auth==2.31.0 huggingface_hub==0.23.4 humanize==4.9.0 joblib==1.4.2 kiwisolver==1.4.5 lightning-utilities==0.11.3.post0 markdown-it-py==3.0.0 markdown==3.6 matplotlib==3.7.2 mdurl==0.1.2 monai==1.3.0 multidict==6.0.5 nibabel==5.2.1 oauthlib==3.2.2 openpyxl==3.1.0 pandas==2.2.2 pyasn1-modules==0.4.0 pyasn1==0.6.0 pydicom==2.4.4 python-dateutil==2.9.0.post0 pytorch-lightning==1.9.0 requests-oauthlib==2.0.0 rich==13.7.1 rsa==4.9 safetensors==0.4.3 scikit-learn==1.3.0 scipy==1.14.0 seaborn==0.12.2 shellingham==1.5.4 tensorboard-data-server==0.7.2 tensorboard-plugin-wit==1.8.1 tensorboard==2.12.1 threadpoolctl==3.5.0 timm==0.9.16 torchio==0.19.6 torchmetrics==1.4.0.post0 torchvision==0.17.0 tqdm==4.65.0 typer==0.12.3 tzdata==2024.1 wrapt==1.16.0 yarl==1.9.4 +RUN python3 -m pip install Deprecated==1.2.14 SimpleITK==2.2.1 absl-py==2.1.0 aiohttp==3.9.5 aiosignal==1.3.1 async-timeout==4.0.3 cachetools==5.3.3 contourpy==1.2.1 cycler==0.12.1 einops==0.8.1 et-xmlfile==1.1.0 fonttools==4.53.1 frozenlist==1.4.1 google-auth-oauthlib==1.0.0 google-auth==2.31.0 huggingface_hub==0.23.4 humanize==4.9.0 joblib==1.4.2 kiwisolver==1.4.5 lightning-utilities==0.11.3.post0 markdown-it-py==3.0.0 markdown==3.6 matplotlib==3.7.2 mdurl==0.1.2 monai==1.3.0 multidict==6.0.5 nibabel==5.2.1 oauthlib==3.2.2 openpyxl==3.1.0 pandas==2.2.2 pyasn1-modules==0.4.0 pyasn1==0.6.0 pydicom==2.4.4 python-dateutil==2.9.0.post0 pytorch-lightning==1.9.0 requests-oauthlib==2.0.0 rich==13.7.1 rsa==4.9 safetensors==0.4.3 scikit-learn==1.3.0 scipy==1.14.0 seaborn==0.12.2 shellingham==1.5.4 tensorboard-data-server==0.7.2 tensorboard-plugin-wit==1.8.1 tensorboard==2.12.1 threadpoolctl==3.5.0 timm==0.9.16 torchio==0.19.6 torchmetrics==1.4.0.post0 torchvision==0.17.0 tqdm==4.65.0 typer==0.12.3 tzdata==2024.1 wrapt==1.16.0 x-transformers==2.4.9 yarl==1.9.4 # Install packages needed for testing and for listing licenses of installed packages RUN python3 -m pip install coverage==7.5.4 mock==5.1.0 From f68b582d24a7064edbfc4dee3bb6e850bf94dcc5 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 13:46:17 +0200 Subject: [PATCH 008/109] removed precision specification that apparently does not work any more --- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index b24d1281..ce7f3740 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -122,7 +122,6 @@ def prepare_training(logger, max_epochs: int, site_name: str): trainer = Trainer( accelerator='gpu', accumulate_grad_batches=1, - precision='16-mixed', default_root_dir=str(path_run_dir), callbacks=[checkpointing], enable_checkpointing=True, From fce123d353835104285f5d1590b1a5918eb55ccf Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 10:18:37 +0200 Subject: [PATCH 009/109] add pre-trained model to docker image --- .gitignore | 3 +++ buildDockerImageAndStartupKits.sh | 8 ++++++-- docker_config/Dockerfile_ODELIA | 11 +++++++---- docker_config/master_template.yml | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 57af2b10..86efbffa 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,6 @@ provision # Ignore provisioned files /workspace/ + +# Ignore directory for caching pre-trained models +docker_config/torch_home_cache diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index dac8be1f..b39e92c2 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -30,8 +30,9 @@ DOCKER_IMAGE=jefftud/odelia:$VERSION # prepare clean version of source code repository clone for building Docker image CWD=`pwd` CLEAN_SOURCE_DIR=`mktemp -d` -cp -r . $CLEAN_SOURCE_DIR/ -cd $CLEAN_SOURCE_DIR +mkdir $CLEAN_SOURCE_DIR/MediSwarm +cp -r . $CLEAN_SOURCE_DIR/MediSwarm/ +cd $CLEAN_SOURCE_DIR/MediSwarm git clean -x -q -f . cd docker_config/NVFlare git clean -x -q -f . @@ -40,6 +41,9 @@ rm .git -rf chmod a+rX . -R cd $CWD +cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache +chmod a+rX $CLEAN_SOURCE_DIR/torch_home_cache -R + docker build $DOCKER_BUILD_ARGS -t $DOCKER_IMAGE $CLEAN_SOURCE_DIR -f docker_config/Dockerfile_ODELIA echo "Docker image $DOCKER_IMAGE built successfully" diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 57da5891..a57e5b5d 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -54,18 +54,21 @@ RUN python3 -m pip cache purge # install ODELIA fork of NVFlare from local source WORKDIR /workspace/ -COPY ./docker_config/NVFlare /workspace/nvflare +COPY ./MediSwarm/docker_config/NVFlare /workspace/nvflare ## use startup kit template in the dashboard -COPY ./docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ +COPY ./MediSwarm/docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ RUN python3 -m pip install /workspace/nvflare RUN rm -rf /workspace/nvflare # Install the ODELIA controller package from local source -COPY ./controller /workspace/controller +COPY ./MediSwarm/controller /workspace/controller RUN python3 -m pip install /workspace/controller RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm -COPY . /MediSwarm +COPY ./MediSwarm /MediSwarm RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm + +# Copy pre-trained model weights to image +COPY ./torch_home_cache /torch_home diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 968bffe7..e9cfba12 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -701,7 +701,7 @@ docker_cln_sh: | ENV_VARS="--env SITE_NAME={~~client_name~~} \ --env DATA_DIR=/data \ --env SCRATCH_DIR=/scratch \ - --env TORCH_HOME=/scratch \ + --env TORCH_HOME=/torch_home \ --env GPU_DEVICE=$GPU2USE \ --env MODEL_NAME=MST \ --env CONFIG=unilateral" From 95e589f3af26468d3605be124964a6e53b08d835 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 14:15:51 +0200 Subject: [PATCH 010/109] check that the correct version of the pretrained weights is available and the code license has not changed --- buildDockerImageAndStartupKits.sh | 32 +++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index b39e92c2..8ae98191 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -27,7 +27,9 @@ fi VERSION=`./getVersionNumber.sh` DOCKER_IMAGE=jefftud/odelia:$VERSION + # prepare clean version of source code repository clone for building Docker image + CWD=`pwd` CLEAN_SOURCE_DIR=`mktemp -d` mkdir $CLEAN_SOURCE_DIR/MediSwarm @@ -41,9 +43,31 @@ rm .git -rf chmod a+rX . -R cd $CWD -cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache + +# prepare pre-trained model weights for being included in Docker image + +MODEL_WEIGHTS_FILE='docker_config/torch_home_cache/hub/checkpoints/dinov2_vits14_pretrain.pth' +MODEL_LICENSE_FILE='docker_config/torch_home_cache/hub/facebookresearch_dinov2_main/LICENSE' +if [[ ! -f $MODEL_WEIGHTS_FILE || ! -f $MODEL_LICENSE_FILE ]]; then + read -p "Pre-trained model not available. Build the image without them? " -n 1 -r + if [[ ! $REPLY = ^[Yy]$ ]]; then + BUILT_WITHOUT_PRETRAINED_WEIGHTS=1 + mkdir $CLEAN_SOURCE_DIR/torch_home_cache + else + exit 1 + fi +else + if echo 2e405cee1bad14912278296d4f42e993 $MODEL_WEIGHTS_FILE | md5sum --check - && echo 153d2db1c329326a2d9f881317ea942e $MODEL_LICENSE_FILE | md5sum --check -; then + cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache + else + exit 1 + fi +fi chmod a+rX $CLEAN_SOURCE_DIR/torch_home_cache -R + +# build and print follow-up steps + docker build $DOCKER_BUILD_ARGS -t $DOCKER_IMAGE $CLEAN_SOURCE_DIR -f docker_config/Dockerfile_ODELIA echo "Docker image $DOCKER_IMAGE built successfully" @@ -53,4 +77,8 @@ echo "Startup kits built successfully" rm -rf $CLEAN_SOURCE_DIR -echo "If you wish, manually push $DOCKER_IMAGE now" +if [ -z BUILT_WITHOUT_PRETRAINED_WEIGHTS ]; then + echo "If you wish, manually push $DOCKER_IMAGE now" +else + echo "Now run a dummy training to download the pretrained model weights, export them to docker_config/torch_home_cache/hub, and re-build the image" +fi From 953857d5d27fa3194d6b6bb94b7cb39421045a67 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 15:02:04 +0200 Subject: [PATCH 011/109] download pretrained model weights if not already available --- buildDockerImageAndStartupKits.sh | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index 8ae98191..e1d582c8 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -49,19 +49,20 @@ cd $CWD MODEL_WEIGHTS_FILE='docker_config/torch_home_cache/hub/checkpoints/dinov2_vits14_pretrain.pth' MODEL_LICENSE_FILE='docker_config/torch_home_cache/hub/facebookresearch_dinov2_main/LICENSE' if [[ ! -f $MODEL_WEIGHTS_FILE || ! -f $MODEL_LICENSE_FILE ]]; then - read -p "Pre-trained model not available. Build the image without them? " -n 1 -r - if [[ ! $REPLY = ^[Yy]$ ]]; then - BUILT_WITHOUT_PRETRAINED_WEIGHTS=1 - mkdir $CLEAN_SOURCE_DIR/torch_home_cache - else - exit 1 - fi + echo "Pre-trained model not available. Attempting download" + HUBDIR=$(dirname $(dirname $MODEL_LICENSE_FILE)) + mkdir -p $(dirname $MODEL_WEIGHTS_FILE) + wget https://dl.fbaipublicfiles.com/dinov2/dinov2_vits14/dinov2_vits14_pretrain.pth -O $MODEL_WEIGHTS_FILE + wget https://github.com/facebookresearch/dinov2/archive/refs/heads/main.zip -O /tmp/dinov2.zip + unzip /tmp/dinov2.zip -d $HUBDIR + mv $HUBDIR/dinov2-main $HUBDIR/$(basename $(dirname $MODEL_LICENSE_FILE)) + touch $HUBDIR/trusted_list +fi + +if echo 2e405cee1bad14912278296d4f42e993 $MODEL_WEIGHTS_FILE | md5sum --check - && echo 153d2db1c329326a2d9f881317ea942e $MODEL_LICENSE_FILE | md5sum --check -; then + cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache else - if echo 2e405cee1bad14912278296d4f42e993 $MODEL_WEIGHTS_FILE | md5sum --check - && echo 153d2db1c329326a2d9f881317ea942e $MODEL_LICENSE_FILE | md5sum --check -; then - cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache - else - exit 1 - fi + exit 1 fi chmod a+rX $CLEAN_SOURCE_DIR/torch_home_cache -R @@ -77,8 +78,4 @@ echo "Startup kits built successfully" rm -rf $CLEAN_SOURCE_DIR -if [ -z BUILT_WITHOUT_PRETRAINED_WEIGHTS ]; then - echo "If you wish, manually push $DOCKER_IMAGE now" -else - echo "Now run a dummy training to download the pretrained model weights, export them to docker_config/torch_home_cache/hub, and re-build the image" -fi +echo "If you wish, manually push $DOCKER_IMAGE now" From 7ad01789a5d4f3383ecdcfd1fd3cb8a548070c44 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 15:32:20 +0200 Subject: [PATCH 012/109] quoted, as suggested by copilot --- scripts/dev_utils/remove_old_odelia_docker_images.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev_utils/remove_old_odelia_docker_images.sh b/scripts/dev_utils/remove_old_odelia_docker_images.sh index c24081fd..7da4ee25 100755 --- a/scripts/dev_utils/remove_old_odelia_docker_images.sh +++ b/scripts/dev_utils/remove_old_odelia_docker_images.sh @@ -8,7 +8,7 @@ docker image list echo "The following Docker images are old ODELIA docker images:" -echo $OLD_ODELIA_DOCKER_IMAGES +echo "$OLD_ODELIA_DOCKER_IMAGES" read -p "Delete these Docker images, unless they have additional tags? (y/n): " answer From ee26af0d1ee16f2867039d0a053f73155daa89f1 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 15:34:11 +0200 Subject: [PATCH 013/109] adapted to changed argument in script --- .github/workflows/pr-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-test.yaml b/.github/workflows/pr-test.yaml index eb9d0a2b..a9cd48a1 100644 --- a/.github/workflows/pr-test.yaml +++ b/.github/workflows/pr-test.yaml @@ -67,7 +67,7 @@ jobs: - name: Run 3D CNN preflight check continue-on-error: false run: | - ./runTestsInDocker.sh preflight_check + ./runTestsInDocker.sh run_3dcnn_tests echo "Preflight check finished" echo "=== Checking synthetic log output ===" ls -lh workspace/*/prod_00/client_A/logs || echo "No logs found for preflight" From 4860629db55090522d595d80f7d1e66f39d96048 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 17:05:28 +0200 Subject: [PATCH 014/109] restored base image and pip package installation/versions that were accidentally downdated in a previous merge --- docker_config/Dockerfile_ODELIA | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index a57e5b5d..bd53ebbc 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -1,5 +1,5 @@ # Use the specified PyTorch image as the base -ARG PYTORCH_IMAGE=pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime +ARG PYTORCH_IMAGE=pytorch/pytorch:2.2.2-cuda12.1-cudnn8-runtime FROM ${PYTORCH_IMAGE} # Specify the NVFlare version @@ -36,18 +36,18 @@ RUN rm -rf /var/lib/apt/lists/* RUN python3 -m pip uninstall -y conda conda-package-handling conda_index # Install specific versions of pip and setuptools -RUN python3 -m pip install -U pip==23.3.1 setuptools==75.8.2 +RUN python3 -m pip install -U pip==25.1.1 setuptools==80.8.0 # Install dependencies of NVFlare at fixed versions RUN python3 -m pip install --upgrade psutil==7.0.0 -RUN python3 -m pip install Flask==3.0.2 Flask-JWT-Extended==4.6.0 Flask-SQLAlchemy==3.1.1 PyJWT==2.10.1 SQLAlchemy==2.0.16 Werkzeug==3.0.1 blinker==1.9.0 docker==7.1.0 greenlet==3.1.1 grpcio==1.62.1 gunicorn==23.0.0 itsdangerous==2.2.0 msgpack==1.1.0 protobuf==4.24.4 pyhocon==0.3.61 pyparsing==3.0.9 websockets==15.0 +RUN python3 -m pip install Flask==3.0.2 Flask-JWT-Extended==4.6.0 Flask-SQLAlchemy==3.1.1 PyJWT==2.10.1 SQLAlchemy==2.0.16 Werkzeug==3.0.1 blinker==1.9.0 docker==7.1.0 greenlet==3.2.2 grpcio==1.62.1 gunicorn==23.0.0 itsdangerous==2.2.0 msgpack==1.1.0 protobuf==4.24.4 pyhocon==0.3.61 pyparsing==3.2.3 websockets==15.0.1 -# Install additional Python packages for swarm training at defined versions -RUN python3 -m pip install Deprecated==1.2.14 SimpleITK==2.2.1 absl-py==2.1.0 aiohttp==3.9.5 aiosignal==1.3.1 async-timeout==4.0.3 cachetools==5.3.3 contourpy==1.2.1 cycler==0.12.1 einops==0.8.1 et-xmlfile==1.1.0 fonttools==4.53.1 frozenlist==1.4.1 google-auth-oauthlib==1.0.0 google-auth==2.31.0 huggingface_hub==0.23.4 humanize==4.9.0 joblib==1.4.2 kiwisolver==1.4.5 lightning-utilities==0.11.3.post0 markdown-it-py==3.0.0 markdown==3.6 matplotlib==3.7.2 mdurl==0.1.2 monai==1.3.0 multidict==6.0.5 nibabel==5.2.1 oauthlib==3.2.2 openpyxl==3.1.0 pandas==2.2.2 pyasn1-modules==0.4.0 pyasn1==0.6.0 pydicom==2.4.4 python-dateutil==2.9.0.post0 pytorch-lightning==1.9.0 requests-oauthlib==2.0.0 rich==13.7.1 rsa==4.9 safetensors==0.4.3 scikit-learn==1.3.0 scipy==1.14.0 seaborn==0.12.2 shellingham==1.5.4 tensorboard-data-server==0.7.2 tensorboard-plugin-wit==1.8.1 tensorboard==2.12.1 threadpoolctl==3.5.0 timm==0.9.16 torchio==0.19.6 torchmetrics==1.4.0.post0 torchvision==0.17.0 tqdm==4.65.0 typer==0.12.3 tzdata==2024.1 wrapt==1.16.0 x-transformers==2.4.9 yarl==1.9.4 +# Install additional Python packages for application code at defined versions +RUN python3 -m pip install Deprecated==1.2.18 SimpleITK==2.5.0 absl-py==2.2.2 aiohttp==3.11.18 aiosignal==1.3.2 async-timeout==5.0.1 cachetools==5.5.2 contourpy==1.3.2 cycler==0.12.1 et-xmlfile==2.0.0 fonttools==4.58.0 frozenlist==1.6.0 google-auth-oauthlib==1.2.2 google-auth==2.40.2 huggingface_hub==0.29.3 datasets==3.4.1 coral_pytorch==1.4.0 humanize==4.12.3 joblib==1.5.1 kiwisolver==1.4.8 lightning-utilities==0.14.3 markdown-it-py==3.0.0 markdown==3.8 matplotlib==3.9.2 mdurl==0.1.2 monai==1.4.0 multidict==6.4.4 nibabel==5.3.2 oauthlib==3.2.2 openpyxl==3.1.5 pandas==2.2.3 numpy==1.26.4 pyasn1-modules==0.4.2 pyasn1==0.6.1 pydicom==3.0.1 python-dateutil==2.9.0.post0 x-transformers==2.3.5 pytorch-lightning==2.4.0 requests==2.32.3 requests-oauthlib==2.0.0 rich==14.0.0 rsa==4.9.1 safetensors==0.5.3 scikit-learn==1.5.2 scipy==1.15.3 seaborn==0.13.2 wandb==0.18.6 einops==0.8.0 shellingham==1.5.4 tensorboard-data-server==0.7.2 tensorboard-plugin-wit==1.8.1 tensorboard==2.19.0 threadpoolctl==3.6.0 timm==1.0.15 torchio==0.20.1 torchmetrics==1.7.1 torchvision==0.17.2 torchaudio==2.2.2 tqdm==4.67.0 typer==0.15.4 tzdata==2025.2 wrapt==1.17.2 yarl==1.20.0 aiohappyeyeballs==2.6.1 annotated-types==0.7.0 dill==0.3.8 docker-pycreds==0.4.0 einx==0.3.0 frozendict==2.4.6 gitdb==4.0.12 gitpython==3.1.44 hf-xet==1.1.2 importlib-resources==6.5.2 loguru==0.7.3 multiprocess==0.70.16 propcache==0.3.1 pyarrow==20.0.0 pydantic==2.11.5 pydantic-core==2.33.2 sentry-sdk==2.29.1 setproctitle==1.3.6 smmap==5.0.2 typing-extensions==4.13.2 typing-inspection==0.4.1 xxhash==3.5.0 # Install packages needed for testing and for listing licenses of installed packages -RUN python3 -m pip install coverage==7.5.4 mock==5.1.0 -RUN python3 -m pip install pip-licenses==5.0.0 prettytable==3.14.0 +RUN python3 -m pip install coverage==7.8.2 mock==5.2.0 +RUN python3 -m pip install pip-licenses==5.0.0 prettytable==3.16.0 # Clean up pip cache RUN python3 -m pip cache purge From 0d00d52a9e8ced4cd33696c444b71ff113d4e33b Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 8 Jul 2025 17:05:56 +0200 Subject: [PATCH 015/109] =?UTF-8?q?Revert=20"removed=20precision=20specifi?= =?UTF-8?q?cation=20that=20apparently=20does=20not=20work=20any=20more"=20?= =?UTF-8?q?=E2=80=93=20should=20work=20again=20with=20intended=20pytorch?= =?UTF-8?q?=20lightning=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f68b582d24a7064edbfc4dee3bb6e850bf94dcc5. --- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index ce7f3740..b24d1281 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -122,6 +122,7 @@ def prepare_training(logger, max_epochs: int, site_name: str): trainer = Trainer( accelerator='gpu', accumulate_grad_batches=1, + precision='16-mixed', default_root_dir=str(path_run_dir), callbacks=[checkpointing], enable_checkpointing=True, From 6de586b303aa0e16bd3093cb47d48e4574cfc957 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 9 Jul 2025 11:02:19 +0200 Subject: [PATCH 016/109] chore: simplify APT package installation in Dockerfile and update build script for better error handling Signed-off-by: GitHub CI --- .github/workflows/pr-test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-test.yaml b/.github/workflows/pr-test.yaml index a9cd48a1..a75c14a5 100644 --- a/.github/workflows/pr-test.yaml +++ b/.github/workflows/pr-test.yaml @@ -64,13 +64,13 @@ jobs: echo "=== Checking log output ===" ls -lh workspace/*/prod_00/client_A/logs || echo "No logs found for dummy training" - - name: Run 3D CNN preflight check + - name: Run 3D CNN tests continue-on-error: false run: | ./runTestsInDocker.sh run_3dcnn_tests - echo "Preflight check finished" + echo "3D CNN tests check finished" echo "=== Checking synthetic log output ===" - ls -lh workspace/*/prod_00/client_A/logs || echo "No logs found for preflight" + ls -lh workspace/*/prod_00/client_A/logs || echo "No logs found for 3D CNN tests" - name: Run Unit Tests inside Docker continue-on-error: true From 8232bc2c872fa7fbaff2e5edcd8321d34321b2cb Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 9 Jul 2025 11:16:27 +0200 Subject: [PATCH 017/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c6b4f894..c32087b5 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* From 003f5fbca16e4f1aea5b8d2765fcfb7434bbe845 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 9 Jul 2025 11:19:56 +0200 Subject: [PATCH 018/109] chore: update apt versions based on rebuild --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c32087b5..95cf5035 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt apt-utils libapt-pkg6.0 +RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.13 git=1:2.34.1-1ubuntu1.13 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm From 40914c2eab04c1c5a64eda882289d4214bd848ac Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 9 Jul 2025 16:09:59 +0200 Subject: [PATCH 019/109] fix echo --- .github/workflows/update-apt-versions.yml | 17 ++++++++--------- scripts/ci/update_apt_versions.sh | 10 +++------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index baeeea8f..6074ff5a 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -2,7 +2,6 @@ name: Auto Update APT Versions on: schedule: - # Every day at 05:00 UTC - cron: '0 5 * * *' workflow_dispatch: @@ -30,10 +29,6 @@ jobs: git config --global user.email "ci@github.com" git config --global user.name "GitHub CI" - - name: Create and switch to apt-update branch - run: | - git checkout -b ci/apt-update || git switch ci/apt-update - - name: Make update script executable run: chmod +x scripts/ci/update_apt_versions.sh @@ -43,18 +38,22 @@ jobs: - name: Show git diff for debugging run: git diff - - name: Push ci/apt-update to origin - if: env.NO_CHANGES == 'false' - run: git push origin ci/apt-update --force - - name: Create Pull Request if: env.NO_CHANGES == 'false' + id: cpr uses: peter-evans/create-pull-request@v5 with: commit-message: "chore: update apt versions in Dockerfile_ODELIA" branch: ci/apt-update + branch-suffix: timestamp title: "chore: Update APT versions in Dockerfile" body: | This PR automatically updates APT package version numbers in `Dockerfile_ODELIA` based on a rebuild and inspection of installation logs. base: main + delete-branch: false + + - name: Print created PR URL + if: env.NO_CHANGES == 'false' + run: | + echo "Created PR: ${{ steps.cpr.outputs.pull-request-url }}" diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index 9c3af31b..6dfc7b6c 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - set -e DOCKERFILE_PATH="docker_config/Dockerfile_ODELIA" @@ -22,7 +21,6 @@ if [ "$exit_code" -ne 0 ]; then exit "$exit_code" fi - echo "[INFO] Re-adding updated APT version pins to Dockerfile..." scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py "$DOCKERFILE_PATH" "$LOG_PATH" rm "$LOG_PATH" @@ -42,11 +40,9 @@ while IFS= read -r match; do fi done < <(grep -oP '\b[a-z0-9\.\-]+=[a-zA-Z0-9:~.+-]+\b' "$DOCKERFILE_PATH") -if git diff --quiet; then - echo "[INFO] No changes to apt versions found. Skipping commit." +git fetch origin main +if git diff --quiet origin/main..HEAD; then echo "NO_CHANGES=true" >> "$GITHUB_ENV" else - echo "[INFO] Committing updated apt versions..." - git commit "$DOCKERFILE_PATH" -m "chore: update apt versions based on rebuild" echo "NO_CHANGES=false" >> "$GITHUB_ENV" -fi +fi \ No newline at end of file From 3c7e3e81bf7bf93f9ca8d0a367d7e5b13355024e Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 9 Jul 2025 16:26:08 +0200 Subject: [PATCH 020/109] slightly extended README and marked todos --- README.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1defd46f..17ae0dfa 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,11 @@ A VPN is necessary so that the swarm nodes can communicate with each other secur # Usage for Swarm Participants ## Setup 1. Make sure your compute node satisfies the specification and has the necessary software installed. -2. Clone the repository and connect the client node to the VPN as described above. +2. Clone the repository and connect the client node to the VPN as described above. TODO is cloning the repository necessary for swarm participants? 3. TODO anything else? ## Prepare Dataset -1. TODO which data is expected in which folder structure + table structure +1. see Step 3: Prepare Data in (this document)[application/jobs/ODELIA_ternary_classification/app/scripts/README.md] ## Prepare Training Participation 1. Extract startup kit provided by swarm operator @@ -56,7 +56,7 @@ A VPN is necessary so that the swarm nodes can communicate with each other secur ## Run Pre-Flight Check 1. Directories ```bash - export SITE_NAME= # TODO should be defined above, also needed for dataset location + export SITE_NAME= # TODO should be defined above, also needed for dataset location export DATADIR= export SCRATCHDIR= ``` @@ -76,23 +76,24 @@ A VPN is necessary so that the swarm nodes can communicate with each other secur ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check ``` * Training time depends on the size of the local dataset. - * This will download pre-trained model weights if used in the training, if not already cached locally ## Configurable Parameters for docker.sh +TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is to ensure everyone runs the same training + When launching the client using `./docker.sh`, the following environment variables are automatically passed into the container. You can override them to customize training behavior: | Environment Variable | Default | Description | |----------------------|----------------|-----------------------------------------------------------------------------| -| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | -| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | -| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | -| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | -| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | -| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | -| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | -| `NUM_EPOCHS` | `1` (test mode)| Number of training epochs (used in preflight/local training) | -| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | +| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | +| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | +| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | +| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | +| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | +| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | +| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | +| `NUM_EPOCHS` | `1` (test mode)| Number of training epochs (used in preflight/local training) | +| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or exporting before run: @@ -102,8 +103,6 @@ export CONFIG=original ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client ``` ---- - ## Start Swarm Node 1. From the directory where you unpacked the startup kit: @@ -117,7 +116,7 @@ export CONFIG=original ``` If you have multiple GPUs and 0 is busy, use a different one. -3. Console output is captured in `nohup.out`, which may have been created by the root user in the container, so make it readable: +3. Console output is captured in `nohup.out`, which may have been created with limited permissions in the container, so make it readable if necessary: ```bash sudo chmod a+r nohup.out ``` @@ -125,15 +124,17 @@ export CONFIG=original 4. Output files: - **Training logs and checkpoints** are saved under: ``` - $SCRATCHDIR/runs/INSTITUTION/MODEL_TASK_CONFIG_TIMESTAMP/ + $SCRATCHDIR/runs/$SITE_NAME// ``` - **Best checkpoint** usually saved as `best.ckpt` or `last.ckpt` - **Prediction results**, if enabled, will appear in subfolders of the same directory - - **TensorBoard or WandB logs**, if activated, are stored in their respective folders inside the run directory + - **TensorBoard logs**, if activated, are stored in their respective folders inside the run directory + - TODO what is enabled/activated should be hard-coded, adapt accordingly 5. (Optional) You can verify that the container is running properly: ```bash docker ps # Check if odelia_swarm_client_$SITE_NAME is listed + nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) tail -f nohup.out # Follow training log ``` @@ -177,6 +178,7 @@ You should see 3. output of a successful proof-of-concept run run with two nodes 4. output of a set of startup kits being generated 5. output of a dummy training run using one of the startup kits +6. TODO update this to what the tests output now Optionally, uncomment running NVFlare unit tests in `_runTestsInsideDocker.sh`. From b233434321224ef19109fd75b13945c31af88fb9 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 9 Jul 2025 16:32:48 +0200 Subject: [PATCH 021/109] updated apt package versions --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 26348579..901e4356 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.13 git=1:2.34.1-1ubuntu1.13 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* From badbc6aebac4764f54865dc574a95ebff1c7a567 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 9 Jul 2025 16:49:00 +0200 Subject: [PATCH 022/109] incremented version number --- odelia_image.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/odelia_image.version b/odelia_image.version index ecc84fdd..4812aa29 100644 --- a/odelia_image.version +++ b/odelia_image.version @@ -1,2 +1,2 @@ # version of the ODELIA Docker image, read by different scripts -0.9 \ No newline at end of file +1.0 \ No newline at end of file From d4cc4dc39692a51ec668fd05bb05f8fe3b1172c6 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 10:01:08 +0200 Subject: [PATCH 023/109] feat: add Odelia all sites configuration YAML for server and client setup Signed-off-by: GitHub CI --- .../provision/project_Odelia_allsites.yml | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 application/provision/project_Odelia_allsites.yml diff --git a/application/provision/project_Odelia_allsites.yml b/application/provision/project_Odelia_allsites.yml new file mode 100644 index 00000000..4e817c29 --- /dev/null +++ b/application/provision/project_Odelia_allsites.yml @@ -0,0 +1,95 @@ +api_version: 3 +name: odelia___REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_STARTUP_KITS___allsites_test +description: Odelia TUD server all collaborators clients on Odelia challenge dataset provision http based yaml file + +participants: + # change example.com to the FQDN of the server + - name: dl3.tud.de + type: server + org: TUD + fed_learn_port: 8002 + admin_port: 8003 + - name: TUD_1 + type: client + org: TUD + - name: TUD_2 + type: client + org: TUD + # Specifying listening_host will enable the creation of one pair of + # certificate/private key for this client, allowing the client to function + # as a server for 3rd-party integration. + # The value must be a hostname that the external trainer can reach via the network. + # listening_host: site-1-lh + - name: MEVIS_1 + type: client + org: MEVIS + - name: MEVIS_2 + type: client + org: MEVIS + - name: UKA_1 + type: client + org: UKA + - name: CAM_1 + type: client + org: Cambridge + - name: VHIO_1 + type: client + org: VHIO + - name: MHA_1 + type: client + org: MHA + - name: RSH_1 + type: client + org: RSH + - name: USZ_1 + type: client + org: USZ + - name: UMCU_1 + type: client + org: UMCU + - name: RUMC_1 + type: client + org: RUMC + - name: jiefu.zhu@tu-dresden.de + type: admin + org: TUD + role: project_admin + +# The same methods in all builders are called in their order defined in builders section +builders: + - path: nvflare.lighter.impl.workspace.WorkspaceBuilder + args: + template_file: master_template.yml + - path: nvflare.lighter.impl.template.TemplateBuilder + - path: nvflare.lighter.impl.static_file.StaticFileBuilder + args: + # config_folder can be set to inform NVIDIA FLARE where to get configuration + config_folder: config + + # scheme for communication driver (currently supporting the default, grpc, only). + scheme: http + + # app_validator is used to verify if uploaded app has proper structures + # if not set, no app_validator is included in fed_server.json + # app_validator: PATH_TO_YOUR_OWN_APP_VALIDATOR + + # when docker_image is set to a docker image name, docker.sh will be generated on server/client/admin + docker_image: jefftud/odelia:__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_STARTUP_KITS__ + + # download_job_url is set to http://download.server.com/ as default in fed_server.json. You can override this + # to different url. + # download_job_url: http://download.server.com/ + + overseer_agent: + path: nvflare.ha.dummy_overseer_agent.DummyOverseerAgent + # if overseer_exists is true, args here are ignored. Provisioning + # tool will fill role, name and other local parameters automatically. + # if overseer_exists is false, args in this section will be used and the sp_end_point + # must match the server defined above in the format of SERVER_NAME:FL_PORT:ADMIN_PORT + # + overseer_exists: false + args: + sp_end_point: dl3.tud.de:8002:8003 + + - path: nvflare.lighter.impl.cert.CertBuilder + - path: nvflare.lighter.impl.signature.SignatureBuilder From 5e11d99aa0323ce1ddafb4674e3e2dfcce7770cc Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:18:21 +0200 Subject: [PATCH 024/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c6b4f894..c32087b5 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.3 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.3 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.3 gnupg-utils=2.2.27-3ubuntu2.3 gnupg=2.2.27-3ubuntu2.3 gpg-agent=2.2.27-3ubuntu2.3 gpg-wks-client=2.2.27-3ubuntu2.3 gpg-wks-server=2.2.27-3ubuntu2.3 gpg=2.2.27-3ubuntu2.3 gpgconf=2.2.27-3ubuntu2.3 gpgsm=2.2.27-3ubuntu2.3 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.3 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.12 git=1:2.34.1-1ubuntu1.12 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* From 1c17c462f97e86f127f226a8bc8a9c9737adbe91 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:22:38 +0200 Subject: [PATCH 025/109] chore: pin APT package versions in Dockerfile for consistency Signed-off-by: GitHub CI --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c32087b5..e6068793 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt apt-utils libapt-pkg6.0 +RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.14 git=1:2.34.1-1ubuntu1.14 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm From 5c1d0c0816fad2cf406fce6cf7422dd09d91c1ce Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:34:07 +0200 Subject: [PATCH 026/109] chore: enhance APT update workflow with debug logging and final Dockerfile diff Signed-off-by: GitHub CI --- .github/workflows/update-apt-versions.yml | 3 +++ scripts/ci/update_apt_versions.sh | 15 ++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index 6074ff5a..da9ad308 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -57,3 +57,6 @@ jobs: if: env.NO_CHANGES == 'false' run: | echo "Created PR: ${{ steps.cpr.outputs.pull-request-url }}" + + - name: Show final Dockerfile diff + run: cat docker_config/Dockerfile_ODELIA diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index 6dfc7b6c..d86bbc6d 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -14,13 +14,18 @@ git config user.name "GitHub CI" git commit "$DOCKERFILE_PATH" -m "WIP: remove apt versions for rebuild" || echo "[INFO] No version pin removal change to commit." echo "[INFO] Rebuilding Docker image and capturing logs..." -./buildDockerImageAndStartupKits.sh -p "$PROJECT_YML" 2>&1 | tee "$LOG_PATH" -exit_code=${PIPESTATUS[0]} -if [ "$exit_code" -ne 0 ]; then - echo "Build failed with exit code $exit_code" - exit "$exit_code" +if ! ./buildDockerImageAndStartupKits.sh -p "$PROJECT_YML" > "$LOG_PATH" 2>&1; then + echo "Build failed. Output:" + cat "$LOG_PATH" + exit 1 fi +echo "[DEBUG] First 20 lines of build log:" +head -n 20 "$LOG_PATH" + +echo "[DEBUG] Checking for apt install commands:" +grep "apt install" "$LOG_PATH" || echo "[WARN] No apt install command found in log!" + echo "[INFO] Re-adding updated APT version pins to Dockerfile..." scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py "$DOCKERFILE_PATH" "$LOG_PATH" rm "$LOG_PATH" From 8016685ba39f01f1dd1c68bc5cbf3781b4dde985 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 10:42:05 +0200 Subject: [PATCH 027/109] chore: update APT version check to use dpkg for improved accuracy Signed-off-by: GitHub CI --- scripts/ci/update_apt_versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index d86bbc6d..fab30c03 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -36,7 +36,7 @@ while IFS= read -r match; do pkg="$(echo "$match" | cut -d= -f1)" ver="$(echo "$match" | cut -d= -f2)" echo -n "Checking $pkg=$ver... " - if ! apt-cache madison "$pkg" | grep -q "$ver"; then + if ! dpkg -l "$pkg" | grep -q "$ver"; then echo "NOT FOUND – removing pin" sed -i "s|\b$pkg=$ver\b|$pkg|" "$DOCKERFILE_PATH" has_invalid_versions=1 From 2ca49b332ab011906aa75d436917be84fde2c6c0 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:42:32 +0200 Subject: [PATCH 028/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index e6068793..c32087b5 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.14 git=1:2.34.1-1ubuntu1.14 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file From 7a1ff1aec648c3605c13c98315c5e4b81126b4c6 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:50:21 +0200 Subject: [PATCH 029/109] chore: pin APT package versions in Dockerfile and update version check in script Signed-off-by: GitHub CI --- docker_config/Dockerfile_ODELIA | 8 ++++---- scripts/ci/update_apt_versions.sh | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c32087b5..7fc502a1 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv libblkid1=2.37.2-4ubuntu3.4 libc-bin libc-dev-bin libc6-dev libc6 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0 libtasn1-6=4.18.0-4ubuntu0.1 libudev1 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https curl=7.81.0-1ubuntu1.20 dirmngr distro-info-data=0.52ubuntu0.9 gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal libpython3.10-stdlib libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal python3.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man git iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0 libglib2.0-data libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd libpam-systemd libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv systemd-timesyncd systemd xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index fab30c03..1a3a267d 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -36,13 +36,14 @@ while IFS= read -r match; do pkg="$(echo "$match" | cut -d= -f1)" ver="$(echo "$match" | cut -d= -f2)" echo -n "Checking $pkg=$ver... " - if ! dpkg -l "$pkg" | grep -q "$ver"; then + if ! dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null | grep -q "$ver"; then echo "NOT FOUND – removing pin" sed -i "s|\b$pkg=$ver\b|$pkg|" "$DOCKERFILE_PATH" has_invalid_versions=1 else echo "OK" fi + done < <(grep -oP '\b[a-z0-9\.\-]+=[a-zA-Z0-9:~.+-]+\b' "$DOCKERFILE_PATH") git fetch origin main From 242a4832bf27c7a9fc76d9a445b4b61b92b625c5 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 10:50:54 +0200 Subject: [PATCH 030/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 7fc502a1..c32087b5 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv libblkid1=2.37.2-4ubuntu3.4 libc-bin libc-dev-bin libc6-dev libc6 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0 libtasn1-6=4.18.0-4ubuntu0.1 libudev1 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl=7.81.0-1ubuntu1.20 dirmngr distro-info-data=0.52ubuntu0.9 gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal libpython3.10-stdlib libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal python3.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man git iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0 libglib2.0-data libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd libpam-systemd libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv systemd-timesyncd systemd xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file From 8dd171ac801ea9361c0a695ac8a146c86aa623fc Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 11:01:01 +0200 Subject: [PATCH 031/109] chore: update APT package versions in Dockerfile and adjust CI workflow Signed-off-by: GitHub CI --- .github/workflows/update-apt-versions.yml | 44 ++++++++++------------- docker_config/Dockerfile_ODELIA | 8 ++--- scripts/ci/update_apt_versions.sh | 3 +- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index da9ad308..4ac7ae08 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -1,46 +1,46 @@ -name: Auto Update APT Versions +name: Auto Update APT Versions (Self-hosted) on: schedule: - - cron: '0 5 * * *' + # run eveyday at 04:00 UTC + - cron: '0 4 * * *' workflow_dispatch: jobs: update-apt: - name: Update APT Package Versions in Dockerfile - runs-on: ubuntu-latest + runs-on: self-hosted + timeout-minutes: 60 steps: - name: Checkout repository (with submodules) uses: actions/checkout@v3 with: submodules: true + fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install -y git apt-utils - - - name: Configure Git for CI + - name: Set up Git run: | git config --global user.email "ci@github.com" git config --global user.name "GitHub CI" - - name: Make update script executable - run: chmod +x scripts/ci/update_apt_versions.sh + - name: Create and switch to apt-update branch + run: | + git checkout -b ci/apt-update || git switch ci/apt-update - name: Run APT update script - run: scripts/ci/update_apt_versions.sh + run: | + chmod +x scripts/ci/update_apt_versions.sh + scripts/ci/update_apt_versions.sh - name: Show git diff for debugging - run: git diff + run: git diff || true + + - name: Push apt-update branch + if: env.NO_CHANGES == 'false' + run: git push origin ci/apt-update --force - name: Create Pull Request if: env.NO_CHANGES == 'false' - id: cpr uses: peter-evans/create-pull-request@v5 with: commit-message: "chore: update apt versions in Dockerfile_ODELIA" @@ -52,11 +52,3 @@ jobs: based on a rebuild and inspection of installation logs. base: main delete-branch: false - - - name: Print created PR URL - if: env.NO_CHANGES == 'false' - run: | - echo "Created PR: ${{ steps.cpr.outputs.pull-request-url }}" - - - name: Show final Dockerfile diff - run: cat docker_config/Dockerfile_ODELIA diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index c32087b5..7fc502a1 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv libblkid1=2.37.2-4ubuntu3.4 libc-bin libc-dev-bin libc6-dev libc6 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0 libtasn1-6=4.18.0-4ubuntu0.1 libudev1 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https curl=7.81.0-1ubuntu1.20 dirmngr distro-info-data=0.52ubuntu0.9 gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal libpython3.10-stdlib libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal python3.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man git iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0 libglib2.0-data libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd libpam-systemd libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv systemd-timesyncd systemd xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index 1a3a267d..d86bbc6d 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -36,14 +36,13 @@ while IFS= read -r match; do pkg="$(echo "$match" | cut -d= -f1)" ver="$(echo "$match" | cut -d= -f2)" echo -n "Checking $pkg=$ver... " - if ! dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null | grep -q "$ver"; then + if ! apt-cache madison "$pkg" | grep -q "$ver"; then echo "NOT FOUND – removing pin" sed -i "s|\b$pkg=$ver\b|$pkg|" "$DOCKERFILE_PATH" has_invalid_versions=1 else echo "OK" fi - done < <(grep -oP '\b[a-z0-9\.\-]+=[a-zA-Z0-9:~.+-]+\b' "$DOCKERFILE_PATH") git fetch origin main From a4c97b6314a937a8cb59589f0757623a8f0919bc Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 11:01:55 +0200 Subject: [PATCH 032/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 7fc502a1..c32087b5 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv libblkid1=2.37.2-4ubuntu3.4 libc-bin libc-dev-bin libc6-dev libc6 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0 libtasn1-6=4.18.0-4ubuntu0.1 libudev1 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl=7.81.0-1ubuntu1.20 dirmngr distro-info-data=0.52ubuntu0.9 gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal libpython3.10-stdlib libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal python3.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man git iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0 libglib2.0-data libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd libpam-systemd libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv systemd-timesyncd systemd xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -68,4 +68,4 @@ RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm COPY . /MediSwarm RUN mkdir -p /fl_admin/transfer -RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm +RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm \ No newline at end of file From 9a8ad273c5b60221ed584bd613f372f6bbd03fb5 Mon Sep 17 00:00:00 2001 From: Ultimate-Storm Date: Thu, 10 Jul 2025 11:05:22 +0200 Subject: [PATCH 033/109] chore: update apt versions in Dockerfile_ODELIA --- docker_config/Dockerfile_ODELIA | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 5af4aff0..476a3044 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.13 git=1:2.34.1-1ubuntu1.13 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.14 git=1:2.34.1-1ubuntu1.14 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -69,6 +69,3 @@ RUN rm -rf /workspace/controller COPY ./MediSwarm /MediSwarm RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm - -# Copy pre-trained model weights to image -COPY ./torch_home_cache /torch_home From cba7221e49c343f4cd3cdd907c02563b273c4b69 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 11:07:18 +0200 Subject: [PATCH 034/109] Potential fix for code scanning alert no. 4: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/update-apt-versions.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index 4ac7ae08..44d248c3 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -1,9 +1,13 @@ name: Auto Update APT Versions (Self-hosted) +permissions: + contents: read + pull-requests: write + on: schedule: # run eveyday at 04:00 UTC - - cron: '0 4 * * *' + - cron: '0 4 * * *' workflow_dispatch: jobs: From f0cf38673aa8ca1a2df246ee0862d001c3cf47f5 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 11:11:00 +0200 Subject: [PATCH 035/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 5af4aff0..eda17b9b 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install -y apt apt-utils libapt-pkg6.0 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login=1:4.8.1-2ubuntu2.2 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd=1:4.8.1-2ubuntu2.2 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.1-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.1-1~ubuntu.22.04~jammy docker-ce=5:28.3.1-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.13 git=1:2.34.1-1ubuntu1.13 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -71,4 +71,4 @@ RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image -COPY ./torch_home_cache /torch_home +COPY ./torch_home_cache /torch_home \ No newline at end of file From d0f7a65d318c36cf9560e4d26b332c37f8a6cbc1 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 11:15:36 +0200 Subject: [PATCH 036/109] fix: pin APT package versions in Dockerfile for consistent builds Signed-off-by: GitHub CI --- docker_config/Dockerfile_ODELIA | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index eda17b9b..a61e9505 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt apt-utils libapt-pkg6.0 +RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files bash bsdutils ca-certificates coreutils dpkg e2fsprogs gpgv libblkid1 libc-bin libc-dev-bin libc6-dev libc6 libcap2 libcom-err2 libext2fs2 libgnutls30 libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 libmount1 libpam-modules-bin libpam-modules libpam-runtime libpam0g libseccomp2 libsmartcols1 libss2 libssl3 libsystemd0 libtasn1-6 libudev1 libuuid1 linux-libc-dev login logsave mount openssl passwd util-linux +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https curl dirmngr distro-info-data gnupg-l10n gnupg-utils gnupg gpg-agent gpg-wks-client gpg-wks-server gpg gpgconf gpgsm libassuan0 libbrotli1 libcurl4 libexpat1 libksba8 libldap-2.5-0 libldap-common libmpdec3 libnghttp2-14 libnpth0 libpsl5 libpython3-stdlib libpython3.10-minimal libpython3.10-stdlib libreadline8 librtmp1 libsasl2-2 libsasl2-modules-db libsasl2-modules libsqlite3-0 libssh-4 lsb-release media-types pinentry-curses publicsuffix python3-minimal python3.10-minimal python3.10 python3 readline-common unzip zip +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor containerd.io dbus-user-session dbus dmsetup docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0 git-man git iptables less libapparmor1 libargon2-1 libbsd0 libcbor0.8 libcryptsetup12 libcurl3-gnutls libdbus-1-3 libdevmapper1.02.1 libedit2 liberror-perl libfido2-1 libgdbm-compat4 libgdbm6 libgirepository-1.0-1 libglib2.0-0 libglib2.0-data libicu70 libip4tc2 libip6tc2 libjson-c5 libkmod2 libltdl7 libmd0 libmnl0 libnetfilter-conntrack3 libnfnetlink0 libnftnl11 libnss-systemd libpam-systemd libperl5.34 libslirp0 libx11-6 libx11-data libxau6 libxcb1 libxdmcp6 libxext6 libxml2 libxmuu1 libxtables12 netbase networkd-dispatcher openssh-client patch perl-base perl-modules-5.34 perl pigz python3-dbus python3-gi shared-mime-info slirp4netns systemd-sysv systemd-timesyncd systemd xauth xdg-user-dirs xz-utils +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.14 git=1:2.34.1-1ubuntu1.14 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -71,4 +71,4 @@ RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image -COPY ./torch_home_cache /torch_home \ No newline at end of file +COPY ./torch_home_cache /torch_home From 055ad7a0ad6abfc6426c980c993955da6e364fb6 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 12:16:03 +0200 Subject: [PATCH 037/109] chore: copy pre-trained model weights to Docker image Signed-off-by: GitHub CI --- docker_config/Dockerfile_ODELIA | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 476a3044..7fbc0bc3 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -69,3 +69,6 @@ RUN rm -rf /workspace/controller COPY ./MediSwarm /MediSwarm RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm + +# Copy pre-trained model weights to image +COPY ./torch_home_cache /torch_home From de3d561ea0f0300ce52d8470ab372c7b20eec04a Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 12:57:32 +0200 Subject: [PATCH 038/109] docs: add developer and operator usage guides to README files Signed-off-by: GitHub CI --- README.md | 306 ++--------------------- README_old.md | 362 ++++++++++++++++++++++++++++ assets/readme/README.developer.md | 65 +++++ assets/readme/README.operator.md | 85 +++++++ assets/readme/README.participant.md | 145 +++++++++++ 5 files changed, 683 insertions(+), 280 deletions(-) create mode 100644 README_old.md create mode 100644 assets/readme/README.developer.md create mode 100644 assets/readme/README.operator.md create mode 100644 assets/readme/README.participant.md diff --git a/README.md b/README.md index 86533caf..fecfbc6a 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,43 @@ -# Introduction -MediSwarm is an open-source project dedicated to advancing medical deep learning through swarm intelligence, leveraging the NVFlare platform. Developed in collaboration with the Odelia consortium, this repository aims to create a decentralized and collaborative framework for medical research and applications. +# MediSwarm -## Key Features -- **Swarm Learning:** Utilizes swarm intelligence principles to improve model performance and adaptability. -- **NVFlare Integration:** Built on NVFlare, providing robust and scalable federated learning capabilities. -- **Data Privacy:** Ensures data security and compliance with privacy regulations by keeping data local to each institution. -- **Collaborative Research:** Facilitates collaboration among medical researchers and institutions for enhanced outcomes. -- **Extensible Framework:** Designed to support various medical applications and easily integrate with existing workflows. +An open-source platform advancing medical AI via privacy-preserving swarm learning, based on NVFlare and developed with +the ODELIA consortium. -## Prerequisites -### Hardware recommendations -* 64 GB of RAM (32 GB is the absolute minimum) -* 16 CPU cores (8 is the absolute minimum) -* an NVIDIA GPU with 48 GB of RAM (24 GB is the minimum) -* 8 TB of Storage (4 TB is the absolute minimum) +[![PR Tests]([pr-test.yaml](.github/workflows/pr-test.yaml)) +[![Docker Build]([update-apt-versions.yml](.github/workflows/update-apt-versions.yml)) -We demonstrate that the system can run on lightweight hardware like this. For less than 10k EUR, you can configure systems from suppliers like Lambda, Dell Precision, and Dell Alienware. +## Quick Start for Your Role -### Operating System -* Ubuntu 20.04 LTS +Choose your role and follow the instructions: -### Software -* Docker -* openvpn -* git +- [Swarm Participant (Medical Site / Data Scientist)](assets/readme/README.participant.md)) +- [Developer (Docker, Code, Pipeline)](assets/readme/README.developer.md) +- [Swarm Operator (Provisioning, VPN, Server)](assets/readme/README.operator.md)) -### Cloning the repository - ```bash - git clone https://github.com/KatherLab/MediSwarm.git --recurse-submodules - ``` -* The last argument is necessary because we are using a git submodule for the (ODELIA fork of NVFlare)[https://github.com/KatherLab/NVFlare_MediSwarm] -* If you have cloned it without this argument, use `git submodule update --init --recursive` +## Overview -### VPN -A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, -1. Install OpenVPN - ```bash - sudo apt-get install openvpn - ``` -2. If you have a graphical user interface(GUI), follow this guide to connect to the VPN: [VPN setup guide(GUI).pdf](assets/VPN%20setup%20guide%28GUI%29.pdf) -3. If you have a command line interface(CLI), follow this guide to connect to the VPN: [VPN setup guide(CLI).md](assets/VPN%20setup%20guide%28CLI%29.md) +MediSwarm enables: -# Usage for Swarm Participants -## Setup -1. Make sure your compute node satisfies the specification and has the necessary software installed. -2. Clone the repository and connect the client node to the VPN as described above. TODO is cloning the repository - necessary for swarm participants? -3. TODO anything else? +- **Privacy-preserving training** of deep learning models on distributed medical datasets +- **Decentralized collaboration** between institutions +- **Dockerized, reproducible** experiments built on NVFlare -## Prepare Dataset +## License -1. see Step 3: Prepare Data in (this document)[application/jobs/ODELIA_ternary_classification/app/scripts/README.md] +MIT — see [LICENSE](LICENSE). -## Prepare Training Participation -1. Extract startup kit provided by swarm operator +## Maintainers -## Run Pre-Flight Check -1. Directories - ```bash - export SITE_NAME= # TODO should be defined above, also needed for dataset location - export DATADIR= - export SCRATCHDIR= - ``` -2. From the directory where you unpacked the startup kit, - ```bash - cd $SITE_NAME/startup - ``` -3. Verify that your Docker/GPU setup is working - ```bash - ./docker.sh --scratch_dir $SCRATCHDIR --GPU device=0 --dummy_training - ``` - * This will pull the Docker image, which might take a while. - * If you have multiple GPUs and 0 is busy, use a different one. - * The “training” itself should take less than minute and does not yield a meaningful classification performance. -4. Verify that your local data can be accessed and the model can be trained locally - ```bash - ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check - ``` - * Training time depends on the size of the local dataset. +- [Jeff](https://github.com/Ultimate-Storm) +- [Ole Schwen](mailto:ole.schwen@mevis.fraunhofer.de) +- [Steffen Renisch](mailto:steffen.renisch@mevis.fraunhofer.de) -## Configurable Parameters for docker.sh +## Contributing -TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is -to ensure everyone runs the same training +Contributions welcome! [Open an issue](https://github.com/KatherLab/MediSwarm/issues) or submit a PR. -When launching the client using `./docker.sh`, the following environment variables are automatically passed into the -container. You can override them to customize training behavior: +## Credits -| Environment Variable | Default | Description | -|----------------------|-----------------|----------------------------------------------------------------------| -| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | -| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | -| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | -| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | -| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | -| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | -| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | -| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | -| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | +Built on: -These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or -exporting before run: - -```bash -export MODEL=ResNet -export CONFIG=original -./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client -``` - -## Start Swarm Node - -1. From the directory where you unpacked the startup kit: - ```bash - cd $SITE_NAME/startup # Skip this if you just ran the pre-flight check - ``` - -2. Start the client: - ```bash - ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --start_client - ``` - If you have multiple GPUs and 0 is busy, use a different one. - -3. Console output is captured in `nohup.out`, which may have been created with limited permissions in the container, so - make it readable if necessary: - ```bash - sudo chmod a+r nohup.out - ``` - -4. Output files: - - **Training logs and checkpoints** are saved under: - ``` - $SCRATCHDIR/runs/$SITE_NAME// - ``` - - **Best checkpoint** usually saved as `best.ckpt` or `last.ckpt` - - **Prediction results**, if enabled, will appear in subfolders of the same directory - - **TensorBoard logs**, if activated, are stored in their respective folders inside the run directory - - TODO what is enabled/activated should be hard-coded, adapt accordingly - -5. (Optional) You can verify that the container is running properly: - ```bash - docker ps # Check if odelia_swarm_client_$SITE_NAME is listed - nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) - tail -f nohup.out # Follow training log - ``` - -## Run Local Training -1. From the directory where you unpacked the startup kit - ```bash - cd $SITE_NAME/startup - ``` -2. Start local training - ```bash - /docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training - ``` - * TODO update when handling of the number of epochs has been implemented -3. Output files - * TODO describe - -# Usage for MediSwarm and Application Code Developers -## Versioning of ODELIA Docker Images -If needed, update the version number in file (odelia_image.version)[odelia_image.version]. It will be used automatically for the Docker image and startup kits. - -## Build the Docker Image and Startup Kits -The Docker image contains all dependencies for administrative purposes (dashboard, command-line provisioning, admin console, server) as well as for running the 3DCNN pipeline under the pytorch-lightning framework. -The project description specifies the swarm nodes etc. to be used for a swarm training. - ```bash - cd MediSwarm - ./buildDockerImageAndStartupKits.sh -p application/provision/ - ``` - -1. Make sure you have no uncommitted changes. -2. If package versions are still not available, you may have to check what the current version is and update the `Dockerfile` accordingly. Version numbers are hard-coded to avoid issues due to silently different versions being installed. -3. After successful build (and after verifying that everything works as expected, i.e., local tests, building startup kits, running local trainings in the startup kit), you can manually push the image to DockerHub, provided you have the necessary rights. Make sure you are not re-using a version number for this purpose. - -## Running Local Tests - ```bash - ./runTestsInDocker.sh - ``` - -You should see -1. several expected errors and warnings printed from unit tests that should succeed overall, and a coverage report -2. output of a successful simulation run with two nodes -3. output of a successful proof-of-concept run run with two nodes -4. output of a set of startup kits being generated -5. output of a dummy training run using one of the startup kits -6. TODO update this to what the tests output now - -Optionally, uncomment running NVFlare unit tests in `_runTestsInsideDocker.sh`. - -## Distributing Startup Kits -Distribute the startup kits to the clients. - -## Running the Application -1. **CIFAR-10 example:** - See [cifar10/README.md](application/jobs/cifar10/README.md) -2. **Minimal PyTorch CNN example:** - See [application/jobs/minimal_training_pytorch_cnn/README.md](application/jobs/minimal_training_pytorch_cnn/README.md) -3. **3D CNN for classifying breast tumors:** - See [ODELIA_ternary_classification/README.md](application/jobs/ODELIA_ternary_classification/README.md) - -## Contributing Application Code -1. Take a look at application/jobs/minimal_training_pytorch_cnn for a minimal example how pytorch code can be adapted to work with NVFlare -2. Take a look at application/jobs/ODELIA_ternary_classification for a more relastic example of pytorch code that can - run in the swarm -3. Use the local tests to check if the code is swarm-ready -4. TODO more detailed instructions - -# Usage for Swarm Operators -## Setting up a Swarm -Production mode is designed for secure, real-world deployments. It supports both local and remote setups, whether on-premise or in the cloud. For more details, refer to the [NVFLARE Production Mode](https://nvflare.readthedocs.io/en/2.4.1/real_world_fl.html). - -To set up production mode, follow these steps: - -## Edit `/etc/hosts` -Ensure that your `/etc/hosts` file includes the correct host mappings. All hosts need to be able to communicate to the server node. - -For example, add the following line (replace `` with the server's actual IP address): - -```plaintext - dl3.tud.de dl3 -``` - -## Create Startup Kits -### Via Script (recommended) -1. Use, e.g., the file `application/provision/project_MEVIS_test.yml`, adapt as needed (network protocol etc.) -2. Call `buildStartupKits.sh /path/to/project_configuration.yml` to build the startup kits -3. Startup kits are generated to `workspace//prod_00/` -4. Deploy startup kits to the respective server/clients - -### Via the Dashboard (not recommended) -```bash -docker run -d --rm \ - --ipc=host -p 8443:8443 \ - --name=odelia_swarm_admin \ - -v /var/run/docker.sock:/var/run/docker.sock \ - \ - /bin/bash -c "nvflare dashboard --start --local --cred :" -``` -using some credentials chosen for the swarm admin account. - -Access the dashboard in a web browser at `https://localhost:8443` log in with these credentials, and configure the project: -1. enter project short name, name, description -2. enter docker download link: jefftud/odelia: -3. if needed, enter dates -4. click save -5. Server Configuration > Server (DNS name): -6. click make project public - -#### Register client per site -Access the dashboard at `https://:8443`. - -1. register a user -2. enter organziation (corresponding to the site) -3. enter role (e.g., org admin) -4. add a site (note: must not contain spaces, best use alphanumerical name) -5. specify number of GPUs and their memory - -#### Approve clients and finish configuration -Access the dashboard at `https://localhost:8443` log in with the admin credentials. -1. Users Dashboard > approve client user -2. Client Sites > approve client sites -3. Project Home > freeze project - -## Download startup kits -After setting up the project admin configuration, server and clients can download their startup kits. Store the passwords somewhere, they are only displayed once (or you can download them again). - -## Starting a Swarm Training -1. Connect the *server* host to the VPN as described above. -2. Start the *server* startup kit using the respective `startup/docker.sh` script with the option to start the server -3. Provide the *client* startup kits to the swarm participants (be aware that email providers or other channels may prevent encrypted archives) -4. Make sure the participants have started their clients via the respective startup kits, see below -5. Start the *admin* startup kit using the respective `startup/docker.sh` script to start the admin console -6. Deploy a job by `submit_job ` - - -# License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -# Maintainers -[Jeff](https://github.com/Ultimate-Storm) -[Ole Schwen](mailto:ole.schwen@mevis.fraunhofer.de) -[Steffen Renisch](mailto:steffen.renisch@mevis.fraunhofer.de) - -# Contributing -Feel free to dive in! [Open an issue](https://github.com/KatherLab/MediSwarm/issues) or submit pull requests. - -# Credits -This project utilizes platforms and resources from the following repositories: - -- **[NVFLARE](https://github.com/NVIDIA/NVFlare)**: NVFLARE (NVIDIA Federated Learning Application Runtime Environment) is an open-source framework that provides a robust and scalable platform for federated learning applications. We have integrated NVFLARE to efficiently handle the federated learning aspects of our project. - -Special thanks to the contributors and maintainers of these repositories for their valuable work and support. - ---- - -For more details about NVFLARE and its features, please visit the [NVFLARE GitHub repository](https://github.com/NVIDIA/NVFlare). +- [NVFLARE](https://github.com/NVIDIA/NVFlare) diff --git a/README_old.md b/README_old.md new file mode 100644 index 00000000..516d4d0b --- /dev/null +++ b/README_old.md @@ -0,0 +1,362 @@ +# Introduction + +MediSwarm is an open-source project dedicated to advancing medical deep learning through swarm intelligence, leveraging +the NVFlare platform. Developed in collaboration with the Odelia consortium, this repository aims to create a +decentralized and collaborative framework for medical research and applications. + +## Key Features + +- **Swarm Learning:** Utilizes swarm intelligence principles to improve model performance and adaptability. +- **NVFlare Integration:** Built on NVFlare, providing robust and scalable federated learning capabilities. +- **Data Privacy:** Ensures data security and compliance with privacy regulations by keeping data local to each + institution. +- **Collaborative Research:** Facilitates collaboration among medical researchers and institutions for enhanced + outcomes. +- **Extensible Framework:** Designed to support various medical applications and easily integrate with existing + workflows. + +## Prerequisites + +### Hardware recommendations + +* 64 GB of RAM (32 GB is the absolute minimum) +* 16 CPU cores (8 is the absolute minimum) +* an NVIDIA GPU with 48 GB of RAM (24 GB is the minimum) +* 8 TB of Storage (4 TB is the absolute minimum) + +We demonstrate that the system can run on lightweight hardware like this. For less than 10k EUR, you can configure +systems from suppliers like Lambda, Dell Precision, and Dell Alienware. + +### Operating System + +* Ubuntu 20.04 LTS + +### Software + +* Docker +* openvpn +* git + +### Cloning the repository + + ```bash + git clone https://github.com/KatherLab/MediSwarm.git --recurse-submodules + ``` + +* The last argument is necessary because we are using a git submodule for the (ODELIA fork of + NVFlare)[https://github.com/KatherLab/NVFlare_MediSwarm] +* If you have cloned it without this argument, use `git submodule update --init --recursive` + +### VPN + +A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, + +1. Install OpenVPN + ```bash + sudo apt-get install openvpn + ``` +2. If you have a graphical user interface(GUI), follow this guide to connect to the + VPN: [VPN setup guide(GUI).pdf](assets/VPN%20setup%20guide%28GUI%29.pdf) +3. If you have a command line interface(CLI), follow this guide to connect to the + VPN: [VPN setup guide(CLI).md](assets/VPN%20setup%20guide%28CLI%29.md) + +# Usage for Swarm Participants + +## Setup + +1. Make sure your compute node satisfies the specification and has the necessary software installed. +2. Clone the repository and connect the client node to the VPN as described above. TODO is cloning the repository + necessary for swarm participants? +3. TODO anything else? + +## Prepare Dataset + +1. see Step 3: Prepare Data in (this document)[application/jobs/ODELIA_ternary_classification/app/scripts/README.md] + +## Prepare Training Participation + +1. Extract startup kit provided by swarm operator + +## Run Pre-Flight Check + +1. Directories + ```bash + export SITE_NAME= # TODO should be defined above, also needed for dataset location + export DATADIR= + export SCRATCHDIR= + ``` +2. From the directory where you unpacked the startup kit, + ```bash + cd $SITE_NAME/startup + ``` +3. Verify that your Docker/GPU setup is working + ```bash + ./docker.sh --scratch_dir $SCRATCHDIR --GPU device=0 --dummy_training + ``` + * This will pull the Docker image, which might take a while. + * If you have multiple GPUs and 0 is busy, use a different one. + * The “training” itself should take less than minute and does not yield a meaningful classification performance. +4. Verify that your local data can be accessed and the model can be trained locally + ```bash + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check + ``` + * Training time depends on the size of the local dataset. + +## Configurable Parameters for docker.sh + +TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is +to ensure everyone runs the same training + +When launching the client using `./docker.sh`, the following environment variables are automatically passed into the +container. You can override them to customize training behavior: + +| Environment Variable | Default | Description | +|----------------------|-----------------|----------------------------------------------------------------------| +| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | +| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | +| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | +| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | +| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | +| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | +| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | +| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | +| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | + +These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or +exporting before run: + +```bash +export MODEL=ResNet +export CONFIG=original +./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client +``` + +## Start Swarm Node + +1. From the directory where you unpacked the startup kit: + ```bash + cd $SITE_NAME/startup # Skip this if you just ran the pre-flight check + ``` + +2. Start the client: + ```bash + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --start_client + ``` + If you have multiple GPUs and 0 is busy, use a different one. + +3. Console output is captured in `nohup.out`, which may have been created with limited permissions in the container, so + make it readable if necessary: + ```bash + sudo chmod a+r nohup.out + ``` + +4. Output files: + - **Training logs and checkpoints** are saved under: + ``` + $SCRATCHDIR/runs/$SITE_NAME// + ``` + - **Best checkpoint** usually saved as `best.ckpt` or `last.ckpt` + - **Prediction results**, if enabled, will appear in subfolders of the same directory + - **TensorBoard logs**, if activated, are stored in their respective folders inside the run directory + - TODO what is enabled/activated should be hard-coded, adapt accordingly + +5. (Optional) You can verify that the container is running properly: + ```bash + docker ps # Check if odelia_swarm_client_$SITE_NAME is listed + nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) + tail -f nohup.out # Follow training log + ``` + +## Run Local Training + +1. From the directory where you unpacked the startup kit + ```bash + cd $SITE_NAME/startup + ``` +2. Start local training + ```bash + /docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training + ``` + * TODO update when handling of the number of epochs has been implemented +3. Output files + * TODO describe + +# Usage for MediSwarm and Application Code Developers + +## Versioning of ODELIA Docker Images + +If needed, update the version number in file (odelia_image.version)[odelia_image.version]. It will be used automatically +for the Docker image and startup kits. + +## Build the Docker Image and Startup Kits + +The Docker image contains all dependencies for administrative purposes (dashboard, command-line provisioning, admin +console, server) as well as for running the 3DCNN pipeline under the pytorch-lightning framework. +The project description specifies the swarm nodes etc. to be used for a swarm training. + +```bash +cd MediSwarm +./buildDockerImageAndStartupKits.sh -p application/provision/ +``` + +1. Make sure you have no uncommitted changes. +2. If package versions are still not available, you may have to check what the current version is and update the + `Dockerfile` accordingly. Version numbers are hard-coded to avoid issues due to silently different versions being + installed. +3. After successful build (and after verifying that everything works as expected, i.e., local tests, building startup + kits, running local trainings in the startup kit), you can manually push the image to DockerHub, provided you have + the necessary rights. Make sure you are not re-using a version number for this purpose. + +## Running Local Tests + + ```bash + ./runTestsInDocker.sh + ``` + +You should see + +1. several expected errors and warnings printed from unit tests that should succeed overall, and a coverage report +2. output of a successful simulation run with two nodes +3. output of a successful proof-of-concept run run with two nodes +4. output of a set of startup kits being generated +5. output of a dummy training run using one of the startup kits +6. TODO update this to what the tests output now + +Optionally, uncomment running NVFlare unit tests in `_runTestsInsideDocker.sh`. + +## Distributing Startup Kits + +Distribute the startup kits to the clients. + +## Running the Application + +1. **CIFAR-10 example:** + See [cifar10/README.md](application/jobs/cifar10/README.md) +2. **Minimal PyTorch CNN example:** + See [application/jobs/minimal_training_pytorch_cnn/README.md](application/jobs/minimal_training_pytorch_cnn/README.md) +3. **3D CNN for classifying breast tumors:** + See [ODELIA_ternary_classification/README.md](application/jobs/ODELIA_ternary_classification/README.md) + +## Contributing Application Code + +1. Take a look at application/jobs/minimal_training_pytorch_cnn for a minimal example how pytorch code can be adapted to + work with NVFlare +2. Take a look at application/jobs/ODELIA_ternary_classification for a more relastic example of pytorch code that can + run in the swarm +3. Use the local tests to check if the code is swarm-ready +4. TODO more detailed instructions + +# Usage for Swarm Operators + +## Setting up a Swarm + +Production mode is designed for secure, real-world deployments. It supports both local and remote setups, whether +on-premise or in the cloud. For more details, refer to +the [NVFLARE Production Mode](https://nvflare.readthedocs.io/en/2.4.1/real_world_fl.html). + +To set up production mode, follow these steps: + +## Edit `/etc/hosts` + +Ensure that your `/etc/hosts` file includes the correct host mappings. All hosts need to be able to communicate to the +server node. + +For example, add the following line (replace `` with the server's actual IP address): + +```plaintext + dl3.tud.de dl3 +``` + +## Create Startup Kits + +### Via Script (recommended) + +1. Use, e.g., the file `application/provision/project_MEVIS_test.yml`, adapt as needed (network protocol etc.) +2. Call `buildStartupKits.sh /path/to/project_configuration.yml` to build the startup kits +3. Startup kits are generated to `workspace//prod_00/` +4. Deploy startup kits to the respective server/clients + +### Via the Dashboard (not recommended) + +```bash +docker run -d --rm \ + --ipc=host -p 8443:8443 \ + --name=odelia_swarm_admin \ + -v /var/run/docker.sock:/var/run/docker.sock \ + \ + /bin/bash -c "nvflare dashboard --start --local --cred :" +``` + +using some credentials chosen for the swarm admin account. + +Access the dashboard in a web browser at `https://localhost:8443` log in with these credentials, and configure the +project: + +1. enter project short name, name, description +2. enter docker download link: jefftud/odelia: +3. if needed, enter dates +4. click save +5. Server Configuration > Server (DNS name): +6. click make project public + +#### Register client per site + +Access the dashboard at `https://:8443`. + +1. register a user +2. enter organziation (corresponding to the site) +3. enter role (e.g., org admin) +4. add a site (note: must not contain spaces, best use alphanumerical name) +5. specify number of GPUs and their memory + +#### Approve clients and finish configuration + +Access the dashboard at `https://localhost:8443` log in with the admin credentials. + +1. Users Dashboard > approve client user +2. Client Sites > approve client sites +3. Project Home > freeze project + +## Download startup kits + +After setting up the project admin configuration, server and clients can download their startup kits. Store the +passwords somewhere, they are only displayed once (or you can download them again). + +## Starting a Swarm Training + +1. Connect the *server* host to the VPN as described above. +2. Start the *server* startup kit using the respective `startup/docker.sh` script with the option to start the server +3. Provide the *client* startup kits to the swarm participants (be aware that email providers or other channels may + prevent encrypted archives) +4. Make sure the participants have started their clients via the respective startup kits, see below +5. Start the *admin* startup kit using the respective `startup/docker.sh` script to start the admin console +6. Deploy a job by `submit_job ` + +# License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +# Maintainers + +[Jeff](https://github.com/Ultimate-Storm) +[Ole Schwen](mailto:ole.schwen@mevis.fraunhofer.de) +[Steffen Renisch](mailto:steffen.renisch@mevis.fraunhofer.de) + +# Contributing + +Feel free to dive in! [Open an issue](https://github.com/KatherLab/MediSwarm/issues) or submit pull requests. + +# Credits + +This project utilizes platforms and resources from the following repositories: + +- **[NVFLARE](https://github.com/NVIDIA/NVFlare)**: NVFLARE (NVIDIA Federated Learning Application Runtime Environment) + is an open-source framework that provides a robust and scalable platform for federated learning applications. We have + integrated NVFLARE to efficiently handle the federated learning aspects of our project. + +Special thanks to the contributors and maintainers of these repositories for their valuable work and support. + +--- + +For more details about NVFLARE and its features, please visit +the [NVFLARE GitHub repository](https://github.com/NVIDIA/NVFlare). diff --git a/assets/readme/README.developer.md b/assets/readme/README.developer.md new file mode 100644 index 00000000..a43026c7 --- /dev/null +++ b/assets/readme/README.developer.md @@ -0,0 +1,65 @@ +# Usage for MediSwarm and Application Code Developers + +## Versioning of ODELIA Docker Images + +If needed, update the version number in file [odelia_image.version](../../odelia_image.version). It will be used +automatically for the Docker image and startup kits. + +## Build the Docker Image and Startup Kits + +The Docker image contains all dependencies for administrative purposes (dashboard, command-line provisioning, admin +console, server) as well as for running the 3DCNN pipeline under the pytorch-lightning framework. +The project description specifies the swarm nodes etc. to be used for a swarm training. + + ```bash + cd MediSwarm + ./buildDockerImageAndStartupKits.sh -p application/provision/ + ``` + +1. Make sure you have no uncommitted changes. +2. If package versions are still not available, you may have to check what the current version is and update the + `Dockerfile` accordingly. Version numbers are hard-coded to avoid issues due to silently different versions being + installed. +3. After successful build (and after verifying that everything works as expected, i.e., local tests, building startup + kits, running local trainings in the startup kit), you can manually push the image to DockerHub, provided you have + the necessary rights. Make sure you are not re-using a version number for this purpose. + +## Running Local Tests + + ```bash + ./runTestsInDocker.sh + ``` + +You should see + +1. several expected errors and warnings printed from unit tests that should succeed overall, and a coverage report +2. output of a successful simulation run with two nodes +3. output of a successful proof-of-concept run run with two nodes +4. output of a set of startup kits being generated +5. output of a dummy training run using one of the startup kits +6. TODO update this to what the tests output now + +Optionally, uncomment running NVFlare unit tests in `_runTestsInsideDocker.sh`. + +## Distributing Startup Kits + +Distribute the startup kits to the clients. + +## Running the Application + +1. **CIFAR-10 example:** + See [README.md](../../application/jobs/cifar10/README.md) +2. **Minimal PyTorch CNN example:** + See [README.md](../../application/jobs/minimal_training_pytorch_cnn/README.md) +3. **3D CNN for classifying breast tumors:** + See [README.md](../../application/jobs/ODELIA_ternary_classification/README.md) + +## Contributing Application Code + +1. Take a look at application/jobs/minimal_training_pytorch_cnn for a minimal example how pytorch code can be adapted to + work with NVFlare +2. Take a look at application/jobs/ODELIA_ternary_classification for a more relastic example of pytorch code that can + run in the swarm +3. Use the local tests to check if the code is swarm-ready +4. TODO more detailed instructions + diff --git a/assets/readme/README.operator.md b/assets/readme/README.operator.md new file mode 100644 index 00000000..101d5266 --- /dev/null +++ b/assets/readme/README.operator.md @@ -0,0 +1,85 @@ +# Usage for Swarm Operators + +## Setting up a Swarm + +Production mode is designed for secure, real-world deployments. It supports both local and remote setups, whether +on-premise or in the cloud. For more details, refer to +the [NVFLARE Production Mode](https://nvflare.readthedocs.io/en/2.4.1/real_world_fl.html). + +To set up production mode, follow these steps: + +## Edit `/etc/hosts` + +Ensure that your `/etc/hosts` file includes the correct host mappings. All hosts need to be able to communicate to the +server node. + +For example, add the following line (replace `` with the server's actual IP address): + +```plaintext + dl3.tud.de dl3 +``` + +## Create Startup Kits + +### Via Script (recommended) + +1. Use, e.g., the file `application/provision/project_MEVIS_test.yml`, adapt as needed (network protocol etc.) +2. Call `buildStartupKits.sh /path/to/project_configuration.yml` to build the startup kits +3. Startup kits are generated to `workspace//prod_00/` +4. Deploy startup kits to the respective server/clients + +### Via the Dashboard (not recommended) + +```bash +docker run -d --rm \ + --ipc=host -p 8443:8443 \ + --name=odelia_swarm_admin \ + -v /var/run/docker.sock:/var/run/docker.sock \ + \ + /bin/bash -c "nvflare dashboard --start --local --cred :" +``` + +using some credentials chosen for the swarm admin account. + +Access the dashboard in a web browser at `https://localhost:8443` log in with these credentials, and configure the +project: + +1. enter project short name, name, description +2. enter docker download link: jefftud/odelia: +3. if needed, enter dates +4. click save +5. Server Configuration > Server (DNS name): +6. click make project public + +#### Register client per site + +Access the dashboard at `https://:8443`. + +1. register a user +2. enter organziation (corresponding to the site) +3. enter role (e.g., org admin) +4. add a site (note: must not contain spaces, best use alphanumerical name) +5. specify number of GPUs and their memory + +#### Approve clients and finish configuration + +Access the dashboard at `https://localhost:8443` log in with the admin credentials. + +1. Users Dashboard > approve client user +2. Client Sites > approve client sites +3. Project Home > freeze project + +## Download startup kits + +After setting up the project admin configuration, server and clients can download their startup kits. Store the +passwords somewhere, they are only displayed once (or you can download them again). + +## Starting a Swarm Training + +1. Connect the *server* host to the VPN as described above. +2. Start the *server* startup kit using the respective `startup/docker.sh` script with the option to start the server +3. Provide the *client* startup kits to the swarm participants (be aware that email providers or other channels may + prevent encrypted archives) +4. Make sure the participants have started their clients via the respective startup kits, see below +5. Start the *admin* startup kit using the respective `startup/docker.sh` script to start the admin console +6. Deploy a job by `submit_job ` diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md new file mode 100644 index 00000000..0a24120d --- /dev/null +++ b/assets/readme/README.participant.md @@ -0,0 +1,145 @@ +# MediSwarm Participant Guide + +This guide is for data scientists and medical research sites participating in a Swarm Learning project. + +## Prerequisites + +- Hardware: Min. 32GB RAM, 8 cores, NVIDIA GPU with 24GB VRAM, 4TB storage +- OS: Ubuntu 20.04 LTS +- Software: Docker, OpenVPN, Git + +## Setup + +1. Make sure your compute node satisfies the specification and has the necessary software installed. +2. Clone the repository and connect the client node to the VPN as described above. TODO is cloning the repository + necessary for swarm participants? +3. TODO anything else? + +## Prepare Dataset + +1. see Step 3: Prepare Data in [README.md](../../application/jobs/ODELIA_ternary_classification/app/scripts/README.md) + +## Prepare Training Participation + +1. Extract startup kit provided by swarm operator + +## Local Testing on Your Data + +1. Directories + ```bash + export SITE_NAME= # TODO should be defined above, also needed for dataset location + export DATADIR= + export SCRATCHDIR= + ``` +2. From the directory where you unpacked the startup kit, + ```bash + cd $SITE_NAME/startup + ``` +3. Verify that your Docker/GPU setup is working + ```bash + ./docker.sh --scratch_dir $SCRATCHDIR --GPU device=0 --dummy_training + ``` + * This will pull the Docker image, which might take a while. + * If you have multiple GPUs and 0 is busy, use a different one. + * The “training” itself should take less than minute and does not yield a meaningful classification performance. +4. Verify that your local data can be accessed and the model can be trained locally + ```bash + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check + ``` + * Training time depends on the size of the local dataset. + +## Configurable Parameters for docker.sh + +TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is +to ensure everyone runs the same training + +When launching the client using `./docker.sh`, the following environment variables are automatically passed into the +container. You can override them to customize training behavior: + +| Environment Variable | Default | Description | +|----------------------|-----------------|----------------------------------------------------------------------| +| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | +| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | +| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | +| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | +| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | +| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | +| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | +| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | +| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | + +These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or +exporting before run: + +```bash +export MODEL=ResNet +export CONFIG=original +./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client +``` + +## Start Swarm Node + +### VPN + +A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, + +1. Install OpenVPN + ```bash + sudo apt-get install openvpn + ``` +2. If you have a graphical user interface(GUI), follow this guide to connect to the + VPN: [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) +3. If you have a command line interface(CLI), follow this guide to connect to the + VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) + +### Start the Client + +1. From the directory where you unpacked the startup kit: + ```bash + cd $SITE_NAME/startup # Skip this if you just ran the pre-flight check + ``` + +2. Start the client: + ```bash + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --start_client + ``` + If you have multiple GPUs and 0 is busy, use a different one. + +3. Console output is captured in `nohup.out`, which may have been created with limited permissions in the container, so + make it readable if necessary: + ```bash + sudo chmod a+r nohup.out + ``` + +4. Output files: + - **Training logs and checkpoints** are saved under: + ``` + $SCRATCHDIR/runs/$SITE_NAME// + ``` + - **Best checkpoint** usually saved as `best.ckpt` or `last.ckpt` + - **Prediction results**, if enabled, will appear in subfolders of the same directory + - **TensorBoard logs**, if activated, are stored in their respective folders inside the run directory + - TODO what is enabled/activated should be hard-coded, adapt accordingly + +5. (Optional) You can verify that the container is running properly: + ```bash + docker ps # Check if odelia_swarm_client_$SITE_NAME is listed + nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) + tail -f nohup.out # Follow training log + ``` + +For any issues, contact your Swarm Operator or check with `docker ps`, `nvidia-smi`, and `tail -f nohup.out`. + +## (Optional) Run Local Training + +1. From the directory where you unpacked the startup kit + ```bash + cd $SITE_NAME/startup + ``` +2. Start local training + ```bash + /docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training + ``` + * TODO update when handling of the number of epochs has been implemented +3. Output files + * TODO describe \ No newline at end of file From d1893bab6d6a5d25485049e518d7dadd770731bd Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 13:00:48 +0200 Subject: [PATCH 039/109] docs: update README badges for PR tests and build status Signed-off-by: GitHub CI --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fecfbc6a..0c66e8fa 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ An open-source platform advancing medical AI via privacy-preserving swarm learning, based on NVFlare and developed with the ODELIA consortium. -[![PR Tests]([pr-test.yaml](.github/workflows/pr-test.yaml)) -[![Docker Build]([update-apt-versions.yml](.github/workflows/update-apt-versions.yml)) +[![PR Tests](https://github.com/KatherLab/MediSwarm/actions/workflows/pr-test.yaml/badge.svg)](https://github.com/KatherLab/MediSwarm/actions/workflows/pr-test.yaml) +[![Build](https://github.com/KatherLab/MediSwarm/actions/workflows/update-apt-versions.yml/badge.svg)](https://github.com/KatherLab/MediSwarm/actions/workflows/update-apt-versions.yml) ## Quick Start for Your Role From d2e3b5de51781092d22fe4e80778f32930220925 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 13:13:34 +0200 Subject: [PATCH 040/109] fix: comment out git fetch and diff check in update_apt_versions.sh Signed-off-by: GitHub CI --- scripts/ci/update_apt_versions.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index d86bbc6d..d5a4ca13 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -45,9 +45,9 @@ while IFS= read -r match; do fi done < <(grep -oP '\b[a-z0-9\.\-]+=[a-zA-Z0-9:~.+-]+\b' "$DOCKERFILE_PATH") -git fetch origin main -if git diff --quiet origin/main..HEAD; then - echo "NO_CHANGES=true" >> "$GITHUB_ENV" -else - echo "NO_CHANGES=false" >> "$GITHUB_ENV" -fi \ No newline at end of file +#git fetch origin main +#if git diff --quiet origin/main..HEAD; then +# echo "NO_CHANGES=true" >> "$GITHUB_ENV" +#else +# echo "NO_CHANGES=false" >> "$GITHUB_ENV" +#fi \ No newline at end of file From 5c1a536a1e9ea2f2055b62dfc98089bc2ded426f Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 13:26:08 +0200 Subject: [PATCH 041/109] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c66e8fa..2eb29561 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ the ODELIA consortium. Choose your role and follow the instructions: -- [Swarm Participant (Medical Site / Data Scientist)](assets/readme/README.participant.md)) +- [Swarm Participant (Medical Site / Data Scientist)](assets/readme/README.participant.md) - [Developer (Docker, Code, Pipeline)](assets/readme/README.developer.md) -- [Swarm Operator (Provisioning, VPN, Server)](assets/readme/README.operator.md)) +- [Swarm Operator (Provisioning, VPN, Server)](assets/readme/README.operator.md) ## Overview From ac86350eae021d34f527cc70a15994113ec8b698 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 13:26:30 +0200 Subject: [PATCH 042/109] Update assets/readme/README.participant.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/readme/README.participant.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 0a24120d..5609fc72 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -138,7 +138,7 @@ For any issues, contact your Swarm Operator or check with `docker ps`, `nvidia-s ``` 2. Start local training ```bash - /docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training ``` * TODO update when handling of the number of epochs has been implemented 3. Output files From 73b685e1db31e5b39da67b6e2986a016b8090144 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 13:26:43 +0200 Subject: [PATCH 043/109] Update assets/readme/README.participant.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/readme/README.participant.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 5609fc72..717c30d0 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -11,7 +11,7 @@ This guide is for data scientists and medical research sites participating in a ## Setup 1. Make sure your compute node satisfies the specification and has the necessary software installed. -2. Clone the repository and connect the client node to the VPN as described above. TODO is cloning the repository +2. Clone the repository and connect the client node to the VPN as described in the VPN setup section below. TODO is cloning the repository necessary for swarm participants? 3. TODO anything else? From 2405785ecfcecf5333479736b14728220dfbf697 Mon Sep 17 00:00:00 2001 From: JieFu Zhu Date: Thu, 10 Jul 2025 13:26:57 +0200 Subject: [PATCH 044/109] Update assets/readme/README.developer.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- assets/readme/README.developer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/readme/README.developer.md b/assets/readme/README.developer.md index a43026c7..ac3f75a4 100644 --- a/assets/readme/README.developer.md +++ b/assets/readme/README.developer.md @@ -58,7 +58,7 @@ Distribute the startup kits to the clients. 1. Take a look at application/jobs/minimal_training_pytorch_cnn for a minimal example how pytorch code can be adapted to work with NVFlare -2. Take a look at application/jobs/ODELIA_ternary_classification for a more relastic example of pytorch code that can +2. Take a look at application/jobs/ODELIA_ternary_classification for a more realistic example of pytorch code that can run in the swarm 3. Use the local tests to check if the code is swarm-ready 4. TODO more detailed instructions From 77ebb12419c80f3e5de8f955d472d495dfdc27c9 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 13:27:43 +0200 Subject: [PATCH 045/109] chore: rename README_old.md for improved organization Signed-off-by: GitHub CI --- README_old.md => assets/readme/README_old.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README_old.md => assets/readme/README_old.md (100%) diff --git a/README_old.md b/assets/readme/README_old.md similarity index 100% rename from README_old.md rename to assets/readme/README_old.md From 40893b4e78b7251660d17fa84e51f74394d09ed5 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 10 Jul 2025 14:09:29 +0200 Subject: [PATCH 046/109] iterated README for swarm participants --- assets/readme/README.participant.md | 93 ++++++++++++++--------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 717c30d0..76f56829 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -11,8 +11,16 @@ This guide is for data scientists and medical research sites participating in a ## Setup 1. Make sure your compute node satisfies the specification and has the necessary software installed. -2. Clone the repository and connect the client node to the VPN as described in the VPN setup section below. TODO is cloning the repository - necessary for swarm participants? +2. Set up the VPN. A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, + 1. Install OpenVPN + ```bash + sudo apt-get install openvpn + ``` + 2. If you have a graphical user interface(GUI), follow this guide to connect to the + VPN: [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) + 3. If you have a command line interface(CLI), follow this guide to connect to the + VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) + 4. You may want to clone this repository or selectively download VPN-related scripts for this purpose. 3. TODO anything else? ## Prepare Dataset @@ -23,7 +31,7 @@ This guide is for data scientists and medical research sites participating in a 1. Extract startup kit provided by swarm operator -## Local Testing on Your Data +### Local Testing on Your Data 1. Directories ```bash @@ -48,51 +56,13 @@ This guide is for data scientists and medical research sites participating in a ``` * Training time depends on the size of the local dataset. -## Configurable Parameters for docker.sh +### Start Swarm Node -TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is -to ensure everyone runs the same training - -When launching the client using `./docker.sh`, the following environment variables are automatically passed into the -container. You can override them to customize training behavior: - -| Environment Variable | Default | Description | -|----------------------|-----------------|----------------------------------------------------------------------| -| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | -| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | -| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | -| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | -| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | -| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | -| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | -| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | -| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | +#### VPN -These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or -exporting before run: +1. Connect to VPN as described in [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) (GUI) or [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) (command line). -```bash -export MODEL=ResNet -export CONFIG=original -./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client -``` - -## Start Swarm Node - -### VPN - -A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, - -1. Install OpenVPN - ```bash - sudo apt-get install openvpn - ``` -2. If you have a graphical user interface(GUI), follow this guide to connect to the - VPN: [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) -3. If you have a command line interface(CLI), follow this guide to connect to the - VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) - -### Start the Client +#### Start the Client 1. From the directory where you unpacked the startup kit: ```bash @@ -130,7 +100,7 @@ A VPN is necessary so that the swarm nodes can communicate with each other secur For any issues, contact your Swarm Operator or check with `docker ps`, `nvidia-smi`, and `tail -f nohup.out`. -## (Optional) Run Local Training +### (Optional) Run Local Training 1. From the directory where you unpacked the startup kit ```bash @@ -142,4 +112,33 @@ For any issues, contact your Swarm Operator or check with `docker ps`, `nvidia-s ``` * TODO update when handling of the number of epochs has been implemented 3. Output files - * TODO describe \ No newline at end of file + * TODO describe + +### Configurable Parameters for docker.sh + +TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is +to ensure everyone runs the same training + +When launching the client using `./docker.sh`, the following environment variables are automatically passed into the +container. You can override them to customize training behavior: + +| Environment Variable | Default | Description | +|----------------------|-----------------|----------------------------------------------------------------------| +| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | +| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | +| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | +| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | +| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | +| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | +| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | +| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | +| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | + +These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or +exporting before run: + +```bash +export MODEL=ResNet +export CONFIG=original +./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client +``` From 9a2b3fdda86903effc77380cba1909b4ba2249b8 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 10 Jul 2025 14:11:08 +0200 Subject: [PATCH 047/109] note on site name --- assets/readme/README.participant.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 76f56829..6a71bc73 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -35,7 +35,7 @@ This guide is for data scientists and medical research sites participating in a 1. Directories ```bash - export SITE_NAME= # TODO should be defined above, also needed for dataset location + export SITE_NAME= # this should end in `_1`, e.g., `UKA_1`, unless you participate with multiple nodes export DATADIR= export SCRATCHDIR= ``` From 3b4196eb10157ecc3e395604532abf0c2c62b4c7 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 10 Jul 2025 14:13:12 +0200 Subject: [PATCH 048/109] indentation for nested list --- assets/readme/README.participant.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 6a71bc73..04beb0f9 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -12,15 +12,15 @@ This guide is for data scientists and medical research sites participating in a 1. Make sure your compute node satisfies the specification and has the necessary software installed. 2. Set up the VPN. A VPN is necessary so that the swarm nodes can communicate with each other securely across firewalls. For that purpose, - 1. Install OpenVPN - ```bash - sudo apt-get install openvpn - ``` - 2. If you have a graphical user interface(GUI), follow this guide to connect to the - VPN: [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) - 3. If you have a command line interface(CLI), follow this guide to connect to the - VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) - 4. You may want to clone this repository or selectively download VPN-related scripts for this purpose. + 1. Install OpenVPN + ```bash + sudo apt-get install openvpn + ``` + 2. If you have a graphical user interface(GUI), follow this guide to connect to the + VPN: [VPN setup guide(GUI).pdf](../VPN%20setup%20guide%28GUI%29.pdf) + 3. If you have a command line interface(CLI), follow this guide to connect to the + VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) + 4. You may want to clone this repository or selectively download VPN-related scripts for this purpose. 3. TODO anything else? ## Prepare Dataset From 85387a09de66009219300f28b317ff4d7cb4d2d4 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 10 Jul 2025 14:28:21 +0200 Subject: [PATCH 049/109] moved local training before swarm training --- assets/readme/README.participant.md | 35 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 04beb0f9..d889724e 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -56,6 +56,20 @@ This guide is for data scientists and medical research sites participating in a ``` * Training time depends on the size of the local dataset. +### (Optional) Run Local Training + +1. From the directory where you unpacked the startup kit + ```bash + cd $SITE_NAME/startup + ``` +2. Start local training + ```bash + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --local_training + ``` + * TODO update when handling of the number of epochs has been implemented +3. Output files + * TODO describe + ### Start Swarm Node #### VPN @@ -93,26 +107,11 @@ This guide is for data scientists and medical research sites participating in a 5. (Optional) You can verify that the container is running properly: ```bash - docker ps # Check if odelia_swarm_client_$SITE_NAME is listed - nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) + docker ps # Check if odelia_swarm_client_$SITE_NAME is listed + nvidia-smi # Check if the GPU is busy training (it will be idling while waiting for model transfer) tail -f nohup.out # Follow training log ``` - -For any issues, contact your Swarm Operator or check with `docker ps`, `nvidia-smi`, and `tail -f nohup.out`. - -### (Optional) Run Local Training - -1. From the directory where you unpacked the startup kit - ```bash - cd $SITE_NAME/startup - ``` -2. Start local training - ```bash - ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU all --local_training - ``` - * TODO update when handling of the number of epochs has been implemented -3. Output files - * TODO describe +For any issues, check if the commands above point to problems and contact your Swarm Operator. ### Configurable Parameters for docker.sh From b0c78298981caa7604f8f56c7a1ab9c2e5bb9982 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 10 Jul 2025 15:59:19 +0200 Subject: [PATCH 050/109] fix: update script paths in README for improved clarity Signed-off-by: GitHub CI --- .../app/scripts/README.md | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/scripts/README.md b/application/jobs/ODELIA_ternary_classification/app/scripts/README.md index 5afef8d5..caf02444 100644 --- a/application/jobs/ODELIA_ternary_classification/app/scripts/README.md +++ b/application/jobs/ODELIA_ternary_classification/app/scripts/README.md @@ -25,13 +25,13 @@ ## Step 2: Prepare Data ([DUKE](https://sites.duke.edu/mazurowski/resources/breast-cancer-mri-dataset/)) * Specify the path to the parent folder as `path_root=...` and `dataset=DUKE` in the following scripts -* Run [scripts/preprocessing/duke/step1_dicom2nifti.py](scripts/preprocessing/duke/step1_dicom2nifti.py) - It will +* Run [step1_dicom2nifti.py](preprocessing/duke/step1_dicom2nifti.py) - It will store DICOM files as NIFTI files in a new folder `data` -* Run [scripts/preprocessing/step2_compute_sub.py](scripts/preprocessing/step2_compute_sub.py) - computes the +* Run [scripts/preprocessing/step2_compute_sub.py](preprocessing/step2_compute_sub.py) - computes the subtraction image -* Run [scripts/preprocessing/step3_unilateral.py](scripts/preprocessing/step3_unilateral.py) - splits breasts into left +* Run [scripts/preprocessing/step3_unilateral.py](preprocessing/step3_unilateral.py) - splits breasts into left and right side and resamples to uniform shape. The result is stored in a new folder `data_unilateral` -* Run [scripts/preprocessing/duke/step4_create_split.py](scripts/preprocessing/duke/step4_create_split.py) - creates a +* Run [scripts/preprocessing/duke/step4_create_split.py](preprocessing/duke/step4_create_split.py) - creates a stratified five-fold split and stores the result in `metadata/split.csv`
@@ -43,22 +43,19 @@ * Create a folder `metadata` with the following file inside: * Challenge: `annotation.xlsx` * Local Training: `ODELIA annotation scheme-2.0.xlsx` -* Overwrite [scripts/preprocessing/odelia/step1_dicom2nifti.py](scripts/preprocessing/odelia/step1_dicom2nifti.py). It +* Overwrite [scripts/preprocessing/odelia/step1_dicom2nifti.py](preprocessing/odelia/step1_dicom2nifti.py). It should create a subfolder `data` and subfolders with files named as `T2.nii.gz`, `Pre.nii.gz`, `Post_1.nii.gz`, `Post_2.nii.gz`, etc. The subfolder should be labeled as follows: * Challenge: Folders must have the same name as the entries in the `ID` column of the `annotation.xlsx` file. * Local Training: Folders must have the same name as the entries in the `StudyInstanceUID` column of the `ODELIA annotation scheme-2.0.xlsx` file. -* Run [scripts/preprocessing/step2_compute_sub.py](scripts/preprocessing/step2_compute_sub.py) - computes the +* Run [scripts/preprocessing/step2_compute_sub.py](preprocessing/step2_compute_sub.py) - computes the subtraction image -* Run [scripts/preprocessing/step3_unilateral.py](scripts/preprocessing/step3_unilateral.py) - splits breasts into left +* Run [scripts/preprocessing/step3_unilateral.py](preprocessing/step3_unilateral.py) - splits breasts into left and right side and resamples to uniform shape. The result is stored in a new folder `data_unilateral` * To create a five-fold stratified split and store the result in `metadata/split.csv`, run the following script: - * - Challenge: [scripts/preprocessing/odelia/step4_create_split_challenge.py](scripts/preprocessing/odelia/step4_create_split_challenge.py) - * Local - Training: [scripts/preprocessing/odelia/step4_create_split.py](scripts/preprocessing/odelia/step4_create_split.py) + * Local Training: [scripts/preprocessing/odelia/step4_create_split.py](preprocessing/odelia/step4_create_split.py) * The final folder structure should look like: ```bash @@ -83,10 +80,11 @@ ## Step 4: Run Training -* Specify path to downloaded folder as `PATH_ROOT=` in [dataset_3d_odelia.py](odelia/data/datasets/dataset_3d_odelia.py) -* Run Script: [scripts/main_train.py --institution DUKE](scripts/main_train.py) +* Specify path to downloaded folder as `PATH_ROOT=` + in [dataset_3d_odelia.py](../custom/data/datasets/dataset_3d_odelia.py) +* Run Script: [main_train.py](main_train.py) ## Step 5: Predict & Evaluate Performance -* Run Script: [scripts/main_predict.py](scripts/main_predict.py) +* Run Script: [main_predict.py](main_predict.py) * Set `path_run` to root directory of latest model From bf21531ee8cc2fc1f2d2baef672533796aeb9a42 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Fri, 11 Jul 2025 09:05:25 +0200 Subject: [PATCH 051/109] addressed some todos and moved description of docker.sh (in startup kits) to developer README --- assets/readme/README.developer.md | 32 ++++++++++- assets/readme/README.participant.md | 88 +++++++++++++++++------------ 2 files changed, 82 insertions(+), 38 deletions(-) diff --git a/assets/readme/README.developer.md b/assets/readme/README.developer.md index ac3f75a4..fb6aafc3 100644 --- a/assets/readme/README.developer.md +++ b/assets/readme/README.developer.md @@ -45,6 +45,37 @@ Optionally, uncomment running NVFlare unit tests in `_runTestsInsideDocker.sh`. Distribute the startup kits to the clients. +## Running the Startup Kits + +See [README.participant.md](./README.participant.md). + +### Configurable Parameters for docker.sh + +* The `docker.sh` script run by the swarm participants passes the following environment variables into the container automatically. +* You can override them to customize training behavior. +* Only do this for testing and debugging purposes! The startup kits are designed to ensure that all sites run the same training code, manipulating `docker.sh` might break this. + +| Environment Variable | Default | Description | +|----------------------|-----------------|----------------------------------------------------------------------| +| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | +| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | +| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | +| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | +| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | +| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | +| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | +| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | +| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | + +These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or exporting before run: + +```bash +export MODEL=ResNet +export CONFIG=original +./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client +``` + + ## Running the Application 1. **CIFAR-10 example:** @@ -62,4 +93,3 @@ Distribute the startup kits to the clients. run in the swarm 3. Use the local tests to check if the code is swarm-ready 4. TODO more detailed instructions - diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index d889724e..d1dd593e 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -21,11 +21,55 @@ This guide is for data scientists and medical research sites participating in a 3. If you have a command line interface(CLI), follow this guide to connect to the VPN: [VPN setup guide(CLI).md](../VPN%20setup%20guide%28CLI%29.md) 4. You may want to clone this repository or selectively download VPN-related scripts for this purpose. -3. TODO anything else? ## Prepare Dataset -1. see Step 3: Prepare Data in [README.md](../../application/jobs/ODELIA_ternary_classification/app/scripts/README.md) +The dataset must be in the following format. + +### Folder Structure + + ```bash + + ├── data_unilateral + │ ├── ID_001_left + │ │ └── Sub_1.nii.gz + │ ├── ID_001_right + │ │ └── Sub_1.nii.gz + │ ├── ID_002_left + │ │ └── Sub_1.nii.gz + │ ├── ID_002_right + │ │ └── Sub_1.nii.gz + │ └── ... + └── metadata_unilateral + ├── annotation.csv + └── split.csv + ``` + +* The name of your site should usually end in `_1`, e.g., `UKA_1`, unless you participate with multiple nodes. +* `ID_001`, `ID_002` need to be unique identifiers in your dataset, not specifically of this format +* You might have additional images in the folder like `Pre.nii.gz`, `Post_1.nii.gz`, `Post_2.nii.gz`, `T2.nii.gz`, and you might have additional folders like `data_raw`, `data`, `metadata` etc. These will be ignored and should not cause problems. +* If you clone the repository, you will find a script that generates a synthetic dataset as an example. + +### Table Format + +#### Annotation + +* `split.csv` defines the class labels +* The file contains the columns `UID`, `PatientID`, `Age`, `Lesion` + * `UID` is the identifier used in the folder name, e.g., `ID_001_left`. + * `PatientID` is the identifier of the patient, in this case, `ID_001`. + * `Age` is the age of the patient at the time of the scan in days. + * `Lesion` is 0 for no lesion, 1 for benign lesion, and 2 for malicious lesion. + +#### Split + +* `split.csv` defines the training/validation/test split. +* These splits are hard-coded rather than randomized during training in order to have consistent and documented splits. +* The file contains the columns `UID`, `Split`, and `Fold`. + * `UID` is the identifier used in the folder name, e.g., `ID_001_left`. + * `Split` is either `train`, `val`, or `test`. The test set is currently ignored. + * `Fold` is the 0-based index of the fold (for a potential cross-validation). + ## Prepare Training Participation @@ -35,8 +79,8 @@ This guide is for data scientists and medical research sites participating in a 1. Directories ```bash - export SITE_NAME= # this should end in `_1`, e.g., `UKA_1`, unless you participate with multiple nodes - export DATADIR= + export SITE_NAME= + export DATADIR= export SCRATCHDIR= ``` 2. From the directory where you unpacked the startup kit, @@ -68,7 +112,7 @@ This guide is for data scientists and medical research sites participating in a ``` * TODO update when handling of the number of epochs has been implemented 3. Output files - * TODO describe + * Same as for the swarm training (see below). ### Start Swarm Node @@ -101,9 +145,8 @@ This guide is for data scientists and medical research sites participating in a $SCRATCHDIR/runs/$SITE_NAME// ``` - **Best checkpoint** usually saved as `best.ckpt` or `last.ckpt` - - **Prediction results**, if enabled, will appear in subfolders of the same directory - - **TensorBoard logs**, if activated, are stored in their respective folders inside the run directory - - TODO what is enabled/activated should be hard-coded, adapt accordingly + - TODO describe prediction results once implemented + - **TensorBoard logs** are stored in their respective folders inside the run directory 5. (Optional) You can verify that the container is running properly: ```bash @@ -112,32 +155,3 @@ This guide is for data scientists and medical research sites participating in a tail -f nohup.out # Follow training log ``` For any issues, check if the commands above point to problems and contact your Swarm Operator. - -### Configurable Parameters for docker.sh - -TODO consider what should be described and recommended as configurable here, given that the goal of the startup kits is -to ensure everyone runs the same training - -When launching the client using `./docker.sh`, the following environment variables are automatically passed into the -container. You can override them to customize training behavior: - -| Environment Variable | Default | Description | -|----------------------|-----------------|----------------------------------------------------------------------| -| `SITE_NAME` | *from flag* | Name of your local site, e.g. `TUD_1`, passed via `--start_client` | -| `DATA_DIR` | *from flag* | Path to the host folder that contains your local data | -| `SCRATCH_DIR` | *from flag* | Path for saving training outputs and temporary files | -| `GPU_DEVICE` | `device=0` | GPU identifier to use inside the container (or `all`) | -| `MODEL` | `MST` | Model architecture, choices: `MST`, `ResNet` | -| `INSTITUTION` | `ODELIA` | Institution name, used to group experiment logs | -| `CONFIG` | `unilateral` | Configuration schema for dataset (e.g. label scheme) | -| `NUM_EPOCHS` | `1` (test mode) | Number of training epochs (used in preflight/local training) | -| `TRAINING_MODE` | derived | Internal use. Automatically set based on flags like `--start_client` | - -These are injected into the container as `--env` variables. You can modify their defaults by editing `docker.sh` or -exporting before run: - -```bash -export MODEL=ResNet -export CONFIG=original -./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=1 --start_client -``` From 0304a590daf1e06d4703aa37492802e34b2bc2bc Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Fri, 11 Jul 2025 11:25:49 +0200 Subject: [PATCH 052/109] updated apt package versions --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index a61e9505..919ca771 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras docker-ce docker-compose-plugin gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.14 git=1:2.34.1-1ubuntu1.14 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.2-1~ubuntu.22.04~jammy docker-ce=5:28.3.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.15 git=1:2.34.1-1ubuntu1.15 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* From 88786031ab45da101370da0914a724e196344b18 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Fri, 11 Jul 2025 11:33:15 +0200 Subject: [PATCH 053/109] fix: enable change detection in update_apt_versions.sh Signed-off-by: GitHub CI --- scripts/ci/update_apt_versions.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/ci/update_apt_versions.sh b/scripts/ci/update_apt_versions.sh index d5a4ca13..d86bbc6d 100755 --- a/scripts/ci/update_apt_versions.sh +++ b/scripts/ci/update_apt_versions.sh @@ -45,9 +45,9 @@ while IFS= read -r match; do fi done < <(grep -oP '\b[a-z0-9\.\-]+=[a-zA-Z0-9:~.+-]+\b' "$DOCKERFILE_PATH") -#git fetch origin main -#if git diff --quiet origin/main..HEAD; then -# echo "NO_CHANGES=true" >> "$GITHUB_ENV" -#else -# echo "NO_CHANGES=false" >> "$GITHUB_ENV" -#fi \ No newline at end of file +git fetch origin main +if git diff --quiet origin/main..HEAD; then + echo "NO_CHANGES=true" >> "$GITHUB_ENV" +else + echo "NO_CHANGES=false" >> "$GITHUB_ENV" +fi \ No newline at end of file From bc4081546554f24f8258b1c1c678e81e477f4bc3 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Fri, 11 Jul 2025 14:30:50 +0200 Subject: [PATCH 054/109] feat: add new client configuration for MEVIS_3 in project_Odelia_allsites.yml Signed-off-by: GitHub CI --- application/provision/project_Odelia_allsites.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/provision/project_Odelia_allsites.yml b/application/provision/project_Odelia_allsites.yml index 4e817c29..530b356d 100644 --- a/application/provision/project_Odelia_allsites.yml +++ b/application/provision/project_Odelia_allsites.yml @@ -26,6 +26,9 @@ participants: - name: MEVIS_2 type: client org: MEVIS + - name: MEVIS_3 + type: client + org: MEVIS - name: UKA_1 type: client org: UKA From 42dc4229a884414bc28ade81b51a5a921febc982 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 11:39:57 +0200 Subject: [PATCH 055/109] added tee to capture command line output to files --- assets/readme/README.participant.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index d1dd593e..1eaa0941 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -89,14 +89,14 @@ The dataset must be in the following format. ``` 3. Verify that your Docker/GPU setup is working ```bash - ./docker.sh --scratch_dir $SCRATCHDIR --GPU device=0 --dummy_training + ./docker.sh --scratch_dir $SCRATCHDIR --GPU device=0 --dummy_training 2>&1 | tee dummy_training_console_output.txt ``` * This will pull the Docker image, which might take a while. * If you have multiple GPUs and 0 is busy, use a different one. * The “training” itself should take less than minute and does not yield a meaningful classification performance. 4. Verify that your local data can be accessed and the model can be trained locally ```bash - ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --preflight_check 2>&1 | tee preflight_check_console_output.txt ``` * Training time depends on the size of the local dataset. @@ -108,7 +108,7 @@ The dataset must be in the following format. ``` 2. Start local training ```bash - ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --local_training + ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --local_training 2>&1 | tee local_training_console_output.txt ``` * TODO update when handling of the number of epochs has been implemented 3. Output files From fce2d9afd7857dc701ad669fbf82276645fd8dd1 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 11:40:42 +0200 Subject: [PATCH 056/109] explanation on local training, not marked as optional for now --- assets/readme/README.participant.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index 1eaa0941..afc6e269 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -100,9 +100,11 @@ The dataset must be in the following format. ``` * Training time depends on the size of the local dataset. -### (Optional) Run Local Training +### Run Local Training -1. From the directory where you unpacked the startup kit +To have a baseline for swarm training, train the same model in a comparable way on the local data only. + +1. From the directory where you unpacked the startup kit (unless you just ran the pre-flight check) ```bash cd $SITE_NAME/startup ``` @@ -110,7 +112,7 @@ The dataset must be in the following format. ```bash ./docker.sh --data_dir $DATADIR --scratch_dir $SCRATCHDIR --GPU device=0 --local_training 2>&1 | tee local_training_console_output.txt ``` - * TODO update when handling of the number of epochs has been implemented + * This currently runs 100 epochs (somewhat comparable to 20 rounds with 5 epochs each in the swarm case). 3. Output files * Same as for the swarm training (see below). From a3ff6e843b2f507f051213285797c066f438c0b2 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 16:55:29 +0200 Subject: [PATCH 057/109] made source more readable --- .../app/custom/data/datasets/dataset_3d_odelia.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/data/datasets/dataset_3d_odelia.py b/application/jobs/ODELIA_ternary_classification/app/custom/data/datasets/dataset_3d_odelia.py index 0b6b27c7..eba4aa12 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/data/datasets/dataset_3d_odelia.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/data/datasets/dataset_3d_odelia.py @@ -62,8 +62,7 @@ def __init__( institutions = [institutions] self.institutions = institutions - flip_axes = (0, 1) if config == "original" else (0, 1, - 2) # Do not flip horizontal axis 2, otherwise labels incorrect + flip_axes = (0, 1) if config == "original" else (0, 1, 2) # Do not flip horizontal axis 2, otherwise labels incorrect if transform is None: self.transform = tio.Compose([ tio.ToCanonical() if config == "original" else tio.Lambda(lambda x: x), From c4829e4029ff952aae126e3be389e1e9b64180fc Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 17:23:26 +0200 Subject: [PATCH 058/109] output what is actually used as number of classes --- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index 616c7b8a..868cdbf9 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -31,10 +31,11 @@ def set_up_logging(): def set_up_data_module(logger): torch.set_float32_matmul_precision('high') ds_train, ds_val, path_run_dir, run_name = prepare_odelia_dataset() + num_classes = sum(ds_train.class_labels_num) logger.info(f"Dataset path: {ds_train}") logger.info(f"Run directory: {path_run_dir}") logger.info(f"Run name: {run_name}") - logger.info(f"Number of classes: {len(ds_train.labels)}") + logger.info(f"Number of classes: {num_classes}") logger.info(f"Length of train dataset: {len(ds_train)}") logger.info(f"Length of val dataset: {len(ds_val)}") @@ -56,7 +57,6 @@ def set_up_data_module(logger): # logger.info(f"Number of unique labels: {len(distribution['counts'])}") # ------------ Initialize Model ------------ - num_classes = sum(ds_train.class_labels_num) loss_kwargs = {} return dm, path_run_dir, run_name, num_classes, loss_kwargs From 5e4ef3306763a8131cff58296a8c83fe1b7b5785 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 17:23:40 +0200 Subject: [PATCH 059/109] removed misleading comment --- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index 868cdbf9..f41dbdbd 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -56,7 +56,6 @@ def set_up_data_module(logger): # logger.info(f"Label '{label}': {pct:.2f}% of training set, Count: {distribution['counts'][label]}") # logger.info(f"Number of unique labels: {len(distribution['counts'])}") - # ------------ Initialize Model ------------ loss_kwargs = {} return dm, path_run_dir, run_name, num_classes, loss_kwargs From 3b6d5ce6a2eff6594c55f51b826db83f94f619a5 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 14 Jul 2025 17:36:38 +0200 Subject: [PATCH 060/109] removed confusing output for now --- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index f41dbdbd..3b3d815e 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -35,7 +35,7 @@ def set_up_data_module(logger): logger.info(f"Dataset path: {ds_train}") logger.info(f"Run directory: {path_run_dir}") logger.info(f"Run name: {run_name}") - logger.info(f"Number of classes: {num_classes}") + # logger.info(f"Number of classes: {num_classes}") # number of possible classes, not number of classes present, thus misleading logger.info(f"Length of train dataset: {len(ds_train)}") logger.info(f"Length of val dataset: {len(ds_val)}") From 346d21c99a5c7b55bfcf45747fec9d136db91e0d Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 15 Jul 2025 13:49:47 +0200 Subject: [PATCH 061/109] use http for communication also in tests --- application/provision/project_MEVIS_test.yml | 2 +- tests/provision/dummy_project_for_testing.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/provision/project_MEVIS_test.yml b/application/provision/project_MEVIS_test.yml index 3a4d1ba7..b4e962c1 100644 --- a/application/provision/project_MEVIS_test.yml +++ b/application/provision/project_MEVIS_test.yml @@ -44,7 +44,7 @@ builders: config_folder: config # scheme for communication driver (currently supporting the default, grpc, only). - scheme: grpc + scheme: http # app_validator is used to verify if uploaded app has proper structures # if not set, no app_validator is included in fed_server.json diff --git a/tests/provision/dummy_project_for_testing.yml b/tests/provision/dummy_project_for_testing.yml index 1ab98c45..7e259592 100644 --- a/tests/provision/dummy_project_for_testing.yml +++ b/tests/provision/dummy_project_for_testing.yml @@ -28,7 +28,7 @@ builders: - path: nvflare.lighter.impl.static_file.StaticFileBuilder args: config_folder: config - scheme: grpc + scheme: http docker_image: jefftud/odelia:__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_STARTUP_KITS__ overseer_agent: path: nvflare.ha.dummy_overseer_agent.DummyOverseerAgent From 85bf8dd7860e4977c539503873ed49a7938531cd Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 15 Jul 2025 13:53:16 +0200 Subject: [PATCH 062/109] updated apt package versions login and passwd are not updated, removed --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 919ca771..0491561a 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,7 +15,7 @@ RUN apt update RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.6 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 login logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 passwd util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 From 9509e132b59509c20b6f77bc00edc3350c6f263a Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 16 Jul 2025 10:45:13 +0200 Subject: [PATCH 063/109] chore: update APT version update workflow with permissions and branch condition Signed-off-by: GitHub CI --- .github/workflows/update-apt-versions.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index 4ac7ae08..890a5cb1 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -1,5 +1,9 @@ name: Auto Update APT Versions (Self-hosted) +permissions: + contents: read + pull-requests: write + on: schedule: # run eveyday at 04:00 UTC @@ -8,6 +12,7 @@ on: jobs: update-apt: + if: github.ref == 'refs/heads/ci/apt-update' runs-on: self-hosted timeout-minutes: 60 @@ -23,10 +28,6 @@ jobs: git config --global user.email "ci@github.com" git config --global user.name "GitHub CI" - - name: Create and switch to apt-update branch - run: | - git checkout -b ci/apt-update || git switch ci/apt-update - - name: Run APT update script run: | chmod +x scripts/ci/update_apt_versions.sh @@ -35,7 +36,7 @@ jobs: - name: Show git diff for debugging run: git diff || true - - name: Push apt-update branch + - name: Push updated branch if: env.NO_CHANGES == 'false' run: git push origin ci/apt-update --force From 064e53fa218aa35ea5b9efdf01e01c5c93061f1a Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 16 Jul 2025 10:55:20 +0200 Subject: [PATCH 064/109] chore: refine APT update workflow by removing branch condition and enhancing branch handling Signed-off-by: GitHub CI --- .github/workflows/update-apt-versions.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index 890a5cb1..6ace3792 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -12,7 +12,6 @@ on: jobs: update-apt: - if: github.ref == 'refs/heads/ci/apt-update' runs-on: self-hosted timeout-minutes: 60 @@ -27,16 +26,17 @@ jobs: run: | git config --global user.email "ci@github.com" git config --global user.name "GitHub CI" - + - name: Create and switch to apt-update branch + run: | + git checkout -b ci/apt-update || git switch ci/apt-update - name: Run APT update script run: | chmod +x scripts/ci/update_apt_versions.sh scripts/ci/update_apt_versions.sh - - name: Show git diff for debugging run: git diff || true - - name: Push updated branch + - name: Push apt-update branch if: env.NO_CHANGES == 'false' run: git push origin ci/apt-update --force @@ -52,4 +52,4 @@ jobs: This PR automatically updates APT package version numbers in `Dockerfile_ODELIA` based on a rebuild and inspection of installation logs. base: main - delete-branch: false + delete-branch: false \ No newline at end of file From dc40756cf8386e28ec65c25eb2bbe1c3dad2ecc1 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 16 Jul 2025 11:09:05 +0200 Subject: [PATCH 065/109] chore: update APT version update workflow with permissions and branch condition Signed-off-by: GitHub CI --- .github/workflows/update-apt-versions.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/update-apt-versions.yml b/.github/workflows/update-apt-versions.yml index b9c0d79b..8e4eedc8 100644 --- a/.github/workflows/update-apt-versions.yml +++ b/.github/workflows/update-apt-versions.yml @@ -1,9 +1,5 @@ name: Auto Update APT Versions (Self-hosted) -permissions: - contents: read - pull-requests: write - on: schedule: # run eveyday at 04:00 UTC @@ -26,10 +22,12 @@ jobs: run: | git config --global user.email "ci@github.com" git config --global user.name "GitHub CI" + - name: Create and switch to apt-update branch run: | git checkout -b ci/apt-update || git switch ci/apt-update - name: Run APT update script + run: | chmod +x scripts/ci/update_apt_versions.sh scripts/ci/update_apt_versions.sh From c103523192c5761da3fc254d97a8844c51e357b4 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 17 Jul 2025 14:46:06 +0200 Subject: [PATCH 066/109] feat: enhance Docker build script and add initial Dockerfile for STAMP integration Signed-off-by: GitHub CI --- application/jobs/stamp/README.md | 97 ++++++ application/jobs/stamp/getting-started.md | 398 ++++++++++++++++++++++ application/jobs/stamp/meta.conf | 9 + buildDockerImageAndStartupKits.sh | 84 +++-- docker_config/Dockerfile_stamp | 58 ++++ 5 files changed, 612 insertions(+), 34 deletions(-) create mode 100644 application/jobs/stamp/README.md create mode 100644 application/jobs/stamp/getting-started.md create mode 100644 application/jobs/stamp/meta.conf create mode 100644 docker_config/Dockerfile_stamp diff --git a/application/jobs/stamp/README.md b/application/jobs/stamp/README.md new file mode 100644 index 00000000..7de86729 --- /dev/null +++ b/application/jobs/stamp/README.md @@ -0,0 +1,97 @@ +# STAMP: A Protocol for Solid Tumor Associative Modeling in Pathology + + + +![CI](https://github.com/KatherLab/STAMP/actions/workflows/ci.yml/badge.svg) + +This repository contains the accompanying code for the steps described in the [Nature Protocols paper][stamp paper]: +"From Whole Slide Image to Biomarker Prediction: +A Protocol for End-to-End Deep Learning in Computational Pathology". + +> [!NOTE] +> This repo contains an updated version of the codebase. +> For a version compatible with the instructions in the paper, +> please check out [version 1 of STAMP][stamp v1]. + +[stamp paper]: https://www.nature.com/articles/s41596-024-01047-2 "From whole-slide image to biomarker prediction: end-to-end weakly supervised deep learning in computational pathology" + +[stamp v1]: https://github.com/KatherLab/STAMP/tree/v1 + +## Installing stamp + +We recommend installing STAMP with [uv](https://docs.astral.sh/uv/): + +```bash +git clone https://github.com/KatherLab/STAMP.git + +cd STAMP/ + +uv sync --all-extras + +source .venv/bin/activate +``` + +> [!IMPORTANT] +> STAMP additionally requires OpenSlide to be installed, as well as OpenCV dependencies. +> +> For Ubuntu < 23.10: +> ```bash +> apt update && apt install -y openslide-tools libgl1-mesa-glx # libgl1-mesa-glx is needed for OpenCV +> ``` +> +> For Ubuntu >= 23.10: +> ```bash +> apt update && apt install -y openslide-tools libgl1 libglx-mesa0 libglib2.0-0 # libgl1, libglx-mesa0, libglib2.0-0 are needed for OpenCV +> ``` + +If the installation was successful, running `stamp` in your terminal should yield the following output: + +``` +$ stamp +usage: stamp [-h] [--config CONFIG_FILE_PATH] {init,preprocess,encode_slides,encode_patients,train,crossval,deploy,statistics,config,heatmaps} ... + +STAMP: Solid Tumor Associative Modeling in Pathology + +positional arguments: + {init,preprocess,encode_slides,encode_patients,train,crossval,deploy,statistics,config,heatmaps} + init Create a new STAMP configuration file at the path specified by --config + preprocess Preprocess whole-slide images into feature vectors + encode_slides Encode patch-level features into slide-level embeddings + encode_patients Encode features into patient-level embeddings + train Train a Vision Transformer model + crossval Train a Vision Transformer model with cross validation for modeling.n_splits folds + deploy Deploy a trained Vision Transformer model + statistics Generate AUROCs and AUPRCs with 95%CI for a trained Vision Transformer model + config Print the loaded configuration + heatmaps Generate heatmaps for a trained model + +options: + -h, --help show this help message and exit + --config CONFIG_FILE_PATH, -c CONFIG_FILE_PATH + Path to config file. Default: config.yaml +``` + +## Running stamp + +For a quick introduction how to run stamp, +check out our [getting started guide](getting-started.md). + +## Reference + +If you find our work useful in your research +or if you use parts of this code +please consider citing our [Nature Protocols publication](https://www.nature.com/articles/s41596-024-01047-2): + +``` +@Article{ElNahhas2024, + author={El Nahhas, Omar S. M. and van Treeck, Marko and W{\"o}lflein, Georg and Unger, Michaela and Ligero, Marta and Lenz, Tim and Wagner, Sophia J. and Hewitt, Katherine J. and Khader, Firas and Foersch, Sebastian and Truhn, Daniel and Kather, Jakob Nikolas}, + title={From whole-slide image to biomarker prediction: end-to-end weakly supervised deep learning in computational pathology}, + journal={Nature Protocols}, + year={2024}, + month={Sep}, + day={16}, + issn={1750-2799}, + doi={10.1038/s41596-024-01047-2}, + url={https://doi.org/10.1038/s41596-024-01047-2} +} +``` diff --git a/application/jobs/stamp/getting-started.md b/application/jobs/stamp/getting-started.md new file mode 100644 index 00000000..87010516 --- /dev/null +++ b/application/jobs/stamp/getting-started.md @@ -0,0 +1,398 @@ +# Getting Started with Stamp + +This guide is designed to help you with your first steps using the stamp pipeline +to predict biomarkers and other attributes from whole slide images (WSIs). +To follow along, +you will need some WSIs, +a table mapping each of these slides to a patient +as well as some ground truth we will eventually train a neural network on. + +## Whole Slide Images + +The whole slide images have to be in any of the formats [supported by OpenSlide][openslide]. +For the next steps we assume that all these WSIs are stored in the same directory. +We will call this directory the _WSI directory_. + +[openslide]: https://openslide.org/#about-openslide "About OpenSlide" + +## Creating a Configuration File + +Stamp is configured using configuration files. +We recommend creating one configuration file per experiment +and storing in the same folder as the eventual results, +as this makes it easier to reconstruct which data and parameters a model was trained with later. + +The `stamp init` command creates a new configuration file with dummy values. +By default, it is created in `$PWD/config.yaml`, +but we can use the `--config` option to specify its location: + +```sh +# Create a directory to save our experiment results to +mkdir stamp-test-experiment +# Create a new config file in said directory +stamp --config stamp-test-experiment/config.yaml init +``` + +## Feature Extraction + +To do any kind of training on our data, we first have to convert it into a form +more easily usable by neural networks. +We do this using a _feature extractor_. +A feature extractor is a neural network has been trained on a large amount of WSIs +to extract extract the information relevant for our domain from images. +This way, we can compress WSIs into a more compact representation, +which in turn allows us to efficiently train machine learning models with them. + +Stamp currently supports the following feature extractors: + +- [ctranspath][ctranspath] +- [chief_ctranspath][chief_ctranspath] +- [DinoBloom][dinobloom] +- [CONCH][conch] +- [CONCHv1.5][conch1_5] +- [UNI][uni] +- [UNI2][uni2] +- [Virchow][virchow] +- [Virchow2][virchow2] +- [Gigapath][gigapath] +- [H-optimus-0][h_optimus_0] +- [H-optimus-1][h_optimus_1] +- [mSTAR][mstar] +- [MUSK][musk] +- [PLIP][plip] + +As some of the above require you to request access to the model on huggingface, +we will stick with ctranspath for this example. + +In order to use a feature extractor, +you also have to install their respective dependencies. +You can do so by specifying the feature extractor you want to use +when installing stamp: + +```sh +# Install stamp including the dependencies for all feature extractors +pip install "git+https://github.com/KatherLab/stamp@v2[all]" +``` + +Open the `stamp-test-experiment/config.yaml` we created in the last step +and modify the `output_dir`, `wsi_dir` and `cache_dir` entries +in the `preprocessing` section +to contain the absolute paths of the directory the configuration file resides in. +`wsi_dir` Needs to point to a path containing the WSIs you want to extract features from. + +The `cache_dir` will be used to save intermediate data. +Should you decide to try another feature extractor later, +using the same cache dir again will significantly speed up the extraction process. +If you will only extract features once, it can be set to `none`. + +```yaml +# stamp-test-experiment/config.yaml + +preprocessing: + output_dir: "/absolute/path/to/stamp-test-experiment" + wsi_dir: "/absolute/path/to/wsi_dir" + + # Other possible values are "mahmood-uni" and "mahmood-conch" + extractor: "ctranspath" + + # Having a cache dir will speed up extracting features multiple times, + # e.g. with different feature extractors. + # Optional. + cache_dir: "/absolute/path/to/stamp-test-experiment/../cache" + # If you do not want to use a cache, + # change the cache dir to the following: + # cache_dir: null + + # Device to run feature extraction on. + # Set this to "cpu" if you do not have a CUDA-capable GPU. + device: "cuda" + + # How many workers to use for tile extraction. Should be less or equal to + # the number of cores of your system. + max_workers: 8 +``` + +Extracting the features is then as easy as running + +```sh +stamp --config stamp-test-experiment/config.yaml preprocess +``` + +Depending on the size of your dataset and your hardware, +this process may take anything between a few hours and days. + +You can interrupt this process at any time. +It will continue where you stopped it the next time you run `stamp preprocess`. + +As the preprocessing is running, +you can see the output directory fill up with the features, saved in `.h5` files, +as well as `.jpg`s showing from which parts of the slide features are extracted. +Most of the background should be marked in red, +meaning ignored that it was ignored during feature extraction. + +> **If you are using the UNI or CONCH models** +> and working in an environment where your home directory storage is limited, +> you may want to also specify your huggingface storage directory +> by setting the `HF_HOME` environment variable: +> ```sh +> export HF_HOME=/path/to/directory/to/store/huggingface/data/in +> huggingface-cli login # only needs to be done once per $HF_HOME +> stamp -c stamp-test-experiment/config.yaml preprocess +> ``` + +[ctranspath]: https://www.sciencedirect.com/science/article/abs/pii/S1361841522002043 "Transformer-based unsupervised contrastive learning for histopathological image classification" + +[dinobloom]: https://github.com/marrlab/DinoBloom "DinoBloom: A Foundation Model for Generalizable Cell Embeddings in Hematology" + +[uni]: https://www.nature.com/articles/s41591-024-02857-3 "Towards a general-purpose foundation model for computational pathology" + +[uni2]: https://huggingface.co/MahmoodLab/UNI2-h + +[conch]: https://www.nature.com/articles/s41591-024-02856-4 "A visual-language foundation model for computational pathology" + +[conch1_5]: https://huggingface.co/MahmoodLab/conchv1_5 + +[virchow]: https://huggingface.co/paige-ai/Virchow "A foundation model for clinical-grade computational pathology and rare cancers detection" + +[virchow2]: https://huggingface.co/paige-ai/Virchow2 + +[chief_ctranspath]: https://github.com/hms-dbmi/CHIEF + +[gigapath]: https://huggingface.co/prov-gigapath/prov-gigapath + +[h_optimus_0]: https://huggingface.co/bioptimus/H-optimus-0 + +[h_optimus_1]: https://huggingface.co/bioptimus/H-optimus-1 + +[mstar]: https://huggingface.co/Wangyh/mSTAR + +[musk]: https://huggingface.co/xiangjx/musk + +[plip]: https://github.com/PathologyFoundation/plip + +[TITAN]: https://huggingface.co/MahmoodLab/TITAN + +[COBRA2]: https://huggingface.co/KatherLab/COBRA + +[EAGLE]: https://github.com/KatherLab/EAGLE + +[MADELEINE]: https://huggingface.co/MahmoodLab/madeleine + +## Doing Cross-Validation on the Data Set + +One way to quickly ascertain if a neural network can be trained to recognize a specific pattern +without the need to source a separate testing set +is to perform a cross-validation on it. +During a cross validation, +we train multiple models on a subset of the data, +testing its effectiveness on the held-out part of the data not used during training. +To perform a cross-validation, add the following lines to your `stamp-test-experiment/config.yaml`, +with `feature_dir` adapted to match the directory the `.h5` files were output to in the last step. +`clini_table` and `slide_table` both need to point to tables, +either in excel or `.csv` format, +with contents as described below. +Finally, `ground_truth_label` needs to contain the column name +of the data we want to train our model on. +Stamp only can be used to train neural networks for categorical targets. +We recommend explicitly setting the possible classes using the `categories` field. + +```yaml +# stamp-test-experiment/config.yaml + +crossval: + output_dir: "/absolute/path/to/stamp-test-experiment" + + # An excel (.xlsx) or CSV (.csv) table containing the clinical information of + # patients. Patients not present in this file will be ignored during training. + # Has to contain at least two columns, one titled "PATIENT", containing a patient ID, + # and a second column containing the categorical ground truths for that patient. + clini_table: "metadata-CRC/TCGA-CRC-DX_CLINI.xlsx" + + # Directory the extracted features are saved in. + feature_dir: "/absolute/path/to/stamp-test-experiment/xiyuewang-ctranspath-7c998680-112fc79c" + + # A table (.xlsx or .csv) relating every patient to their feature files. + # The table must contain at least two columns, one titled "PATIENT", + # containing the patient ID (matching those in the `clini_table`), and one + # called "FILENAME", containing the feature file path relative to `feature_dir`. + # Patient IDs not present in the clini table as well as non-existent feature + # paths are ignored. + slide_table: "slide.csv" + + # Name of the column from the clini table to train on. + ground_truth_label: "isMSIH" + + # Optional settings: + + # The categories occurring in the target label column of the clini table. + # If unspecified, they will be inferred from the table itself. + categories: [ "yes", "no" ] + + # Number of folds to split the data into for cross-validation + #n_splits: 5 +``` + +After specifying all the parameters of our cross-validation, +we can run it by invoking: + +```sh +stamp --config stamp-test-experiment/config.yaml crossval +``` + +## Generating Statistics + +After training and validating your model, you may want to generate statistics to evaluate its performance. +This can be done by adding a `statistics` section to your `stamp-test-experiment/config.yaml` file. +The configuration should look like this: + +```yaml +# stamp-test-experiment/config.yaml + +statistics: + output_dir: "/absolute/path/to/stamp-test-experiment/statistics" + + # Name of the target label. + ground_truth_label: "isMSIH" + + # A lot of the statistics are computed "one-vs-all", i.e. there needs to be + # a positive class to calculate the statistics for. + true_class: "yes" + + pred_csvs: + - "/absolute/path/to/stamp-test-experiment/split-0/patient-preds.csv" + - "/absolute/path/to/stamp-test-experiment/split-1/patient-preds.csv" + - "/absolute/path/to/stamp-test-experiment/split-2/patient-preds.csv" + - "/absolute/path/to/stamp-test-experiment/split-3/patient-preds.csv" + - "/absolute/path/to/stamp-test-experiment/split-4/patient-preds.csv" +``` + +To generate the statistics, run the following command: + +```sh +stamp --config stamp-test-experiment/config.yaml statistics +``` + +Afterwards, the `output_dir` should contain the following files: + +- `isMSIH-categorical-stats-individual.csv` contains statistical scores + for each individual split. +- `isMSIH-categorical-stats-aggregated.csv` contains the mean + as well as the 95% confidence interval for the statistical scores + for the splits. +- `roc-curve_isMSIH=yes.svg` and `pr-curve_isMSIH=yes.svg` + contain the ROC and precision recall curves of the splits. + +## Slide-Level Encoding + +Tile-Level features can be enconded into a single feature per slide, this is useful +when trying to capture global patterns across whole slides. + +STAMP currently supports the following encoders: + +- [CHIEF][CHIEF_CTRANSPATH] +- [TITAN] +- [GIGAPATH] +- [COBRA2] +- [EAGLE] +- [MADELEINE] + +Slide encoders take as input the already extracted tile-level features in the +preprocessing step. Each encoder accepts only certain extractors and most +work only on CUDA devices: + +| Encoder | Required Extractor | Compatible Devices | +|-----------|-------------------------------------|--------------------| +| CHIEF | CTRANSPATH, CHIEF-CTRANSPATH | CUDA only | +| TITAN | CONCH1.5 | CUDA, cpu, mps +| GIGAPATH | GIGAPATH | CUDA only +| COBRA2 | CONCH, UNI, VIRCHOW2 or H-OPTIMUS-0 | CUDA only +| EAGLE | CTRANSPATH, CHIEF-CTRANSPATH | CUDA only +| MADELEINE | CONCH | CUDA only + +As with feature extractors, most of these models require you to request +access. The following example uses CHIEF, which is available if you installed +STAMP with `uv sync --all-extras`. The configuration should look like this: + +```yaml +# stamp-test-experiment/config.yaml + +slide_encoding: + # Encoder to use for slide encoding. Possible options are "cobra", + # "eagle", "titan", "gigapath", "chief", "prism", "madeleine". + encoder: "chief" + + # Directory to save the output files. + output_dir: "/path/to/save/files/to" + + # Directory where the extracted features are stored. + feat_dir: "/path/your/extracted/features/are/stored/in" + + # Device to run slide encoding on ("cpu", "cuda", "cuda:0", etc.) + device: "cuda" + + # Optional settings: + # Directory where the aggregated features are stored. Needed for + # some encoders such as eagle (it requires virchow2 features). + #agg_feat_dir: "/path/your/aggregated/features/are/stored/in" + + # Add a hash of the entire preprocessing codebase in the feature folder name. + #generate_hash: True + ``` + +Don't forget to put in `feat_dir` a path containing, in this case, `ctranspath` or +`chief-ctranspath` tile-level features. Once everything is set, you can simply run: + +```sh +stamp --config stamp-test-experiment/config.yaml encode_slides +``` + +The output will be one `.h5` file per slide. + +## Patient-Level Encoding + +Even though the available encoders are designed for slide-level use, this +option concatenates the slides of a patient along the x-axis, creating a single +"virtual" slide that contains two blocks of tissue. The configuration is the same +except for `slide_table` which is required to link slides with patients. + +```yaml +# stamp-test-experiment/config.yaml + +patient_encoding: + # Encoder to use for patient encoding. Possible options are "cobra", + # "eagle", "titan", "gigapath", "chief", "prism", "madeleine". + encoder: "eagle" + + # Directory to save the output files. + output_dir: "/path/to/save/files/to" + + # Directory where the extracted features are stored. + feat_dir: "/path/your/extracted/features/are/stored/in" + + # A table (.xlsx or .csv) relating every slide to their feature files. + # The table must contain at least two columns, one titled "SLIDE", + # containing the slide ID, and one called "FILENAME", containing the feature file path relative to `feat_dir`. + slide_table: "/path/of/slide.csv" + + # Device to run slide encoding on ("cpu", "cuda", "cuda:0", etc.) + device: "cuda" + + # Optional settings: + patient_label: "PATIENT" + filename_label: "FILENAME" + + # Directory where the aggregated features are stored. Needed for + # some encoders such as eagle (it requires virchow2 features). + #agg_feat_dir: "/path/your/aggregated/features/are/stored/in" + + # Add a hash of the entire preprocessing codebase in the feature folder name. + #generate_hash: True + ``` + +Then run: + + ```sh +stamp --config stamp-test-experiment/config.yaml encode_patients +``` + +The output `.h5` features will have the patient's id as name. \ No newline at end of file diff --git a/application/jobs/stamp/meta.conf b/application/jobs/stamp/meta.conf new file mode 100644 index 00000000..13b8071b --- /dev/null +++ b/application/jobs/stamp/meta.conf @@ -0,0 +1,9 @@ +name = "stamp" +resource_spec {} +deploy_map { + app = [ + "@ALL" + ] +} +min_clients = 2 +mandatory_clients = [] diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index e1d582c8..b55d9692 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -8,72 +8,88 @@ if ! git diff --quiet || ! git diff --staged --quiet ; then exit 1 fi -DOCKER_BUILD_ARGS="--no-cache --progress=plain"; +DOCKER_BUILD_ARGS="--no-cache --progress=plain" +DOCKERFILE_SUFFIX="ODELIA" +PROJECT_FILE="" while [[ "$#" -gt 0 ]]; do case $1 in - -p) PROJECT_FILE="$2"; shift ;; - --use-docker-cache) DOCKER_BUILD_ARGS="";; + -p) PROJECT_FILE="$2"; shift ;; + --use-docker-cache) DOCKER_BUILD_ARGS="" ;; + -d) DOCKERFILE_SUFFIX="$2"; shift ;; *) echo "Unknown parameter passed: $1"; exit 1 ;; esac shift done if [ -z "$PROJECT_FILE" ]; then - echo "Usage: buildDockerImageAndStartupKits.sh -p [--use-docker-cache]" + echo "Usage: buildDockerImageAndStartupKits.sh -p [-d ] [--use-docker-cache]" exit 1 fi -VERSION=`./getVersionNumber.sh` -DOCKER_IMAGE=jefftud/odelia:$VERSION +VERSION=$(./getVersionNumber.sh) +DOCKER_IMAGE="jefftud/${DOCKERFILE_SUFFIX}:$VERSION" +DOCKERFILE_PATH="docker_config/Dockerfile_${DOCKERFILE_SUFFIX}" +if [[ ! -f $DOCKERFILE_PATH ]]; then + echo "Error: Dockerfile $DOCKERFILE_PATH does not exist." + exit 1 +fi # prepare clean version of source code repository clone for building Docker image -CWD=`pwd` -CLEAN_SOURCE_DIR=`mktemp -d` -mkdir $CLEAN_SOURCE_DIR/MediSwarm -cp -r . $CLEAN_SOURCE_DIR/MediSwarm/ -cd $CLEAN_SOURCE_DIR/MediSwarm +CWD=$(pwd) +CLEAN_SOURCE_DIR=$(mktemp -d) +mkdir -p "$CLEAN_SOURCE_DIR/MediSwarm" +cp -r . "$CLEAN_SOURCE_DIR/MediSwarm/" +cd "$CLEAN_SOURCE_DIR/MediSwarm" git clean -x -q -f . cd docker_config/NVFlare git clean -x -q -f . cd ../.. -rm .git -rf +rm -rf .git chmod a+rX . -R -cd $CWD - +cd "$CWD" # prepare pre-trained model weights for being included in Docker image -MODEL_WEIGHTS_FILE='docker_config/torch_home_cache/hub/checkpoints/dinov2_vits14_pretrain.pth' -MODEL_LICENSE_FILE='docker_config/torch_home_cache/hub/facebookresearch_dinov2_main/LICENSE' -if [[ ! -f $MODEL_WEIGHTS_FILE || ! -f $MODEL_LICENSE_FILE ]]; then - echo "Pre-trained model not available. Attempting download" - HUBDIR=$(dirname $(dirname $MODEL_LICENSE_FILE)) - mkdir -p $(dirname $MODEL_WEIGHTS_FILE) - wget https://dl.fbaipublicfiles.com/dinov2/dinov2_vits14/dinov2_vits14_pretrain.pth -O $MODEL_WEIGHTS_FILE - wget https://github.com/facebookresearch/dinov2/archive/refs/heads/main.zip -O /tmp/dinov2.zip - unzip /tmp/dinov2.zip -d $HUBDIR - mv $HUBDIR/dinov2-main $HUBDIR/$(basename $(dirname $MODEL_LICENSE_FILE)) - touch $HUBDIR/trusted_list -fi - -if echo 2e405cee1bad14912278296d4f42e993 $MODEL_WEIGHTS_FILE | md5sum --check - && echo 153d2db1c329326a2d9f881317ea942e $MODEL_LICENSE_FILE | md5sum --check -; then - cp -r ./docker_config/torch_home_cache $CLEAN_SOURCE_DIR/torch_home_cache +if [[ "$DOCKERFILE_SUFFIX" == "ODELIA" ]]; then + echo "Preparing DINOv2 model weights for ODELIA build..." + + MODEL_WEIGHTS_FILE='docker_config/torch_home_cache/hub/checkpoints/dinov2_vits14_pretrain.pth' + MODEL_LICENSE_FILE='docker_config/torch_home_cache/hub/facebookresearch_dinov2_main/LICENSE' + + if [[ ! -f $MODEL_WEIGHTS_FILE || ! -f $MODEL_LICENSE_FILE ]]; then + echo "Pre-trained model not available. Attempting download" + HUBDIR=$(dirname $(dirname $MODEL_LICENSE_FILE)) + mkdir -p "$(dirname $MODEL_WEIGHTS_FILE)" + wget https://dl.fbaipublicfiles.com/dinov2/dinov2_vits14/dinov2_vits14_pretrain.pth -O "$MODEL_WEIGHTS_FILE" + wget https://github.com/facebookresearch/dinov2/archive/refs/heads/main.zip -O /tmp/dinov2.zip + unzip /tmp/dinov2.zip -d "$HUBDIR" + mv "$HUBDIR/dinov2-main" "$HUBDIR/$(basename $(dirname $MODEL_LICENSE_FILE))" + touch "$HUBDIR/trusted_list" + fi + + if echo 2e405cee1bad14912278296d4f42e993 $MODEL_WEIGHTS_FILE | md5sum --check - \ + && echo 153d2db1c329326a2d9f881317ea942e $MODEL_LICENSE_FILE | md5sum --check -; then + cp -r ./docker_config/torch_home_cache "$CLEAN_SOURCE_DIR/torch_home_cache" + chmod a+rX "$CLEAN_SOURCE_DIR/torch_home_cache" -R + else + echo "Model file checksum verification failed" + exit 1 + fi else - exit 1 + echo "Skipping pre-trained model download; not required for Dockerfile_${DOCKERFILE_SUFFIX}" fi -chmod a+rX $CLEAN_SOURCE_DIR/torch_home_cache -R -# build and print follow-up steps +# build the Docker image -docker build $DOCKER_BUILD_ARGS -t $DOCKER_IMAGE $CLEAN_SOURCE_DIR -f docker_config/Dockerfile_ODELIA +docker build $DOCKER_BUILD_ARGS -t "$DOCKER_IMAGE" "$CLEAN_SOURCE_DIR" -f "$DOCKERFILE_PATH" echo "Docker image $DOCKER_IMAGE built successfully" echo "./_buildStartupKits.sh $PROJECT_FILE $VERSION" -./_buildStartupKits.sh $PROJECT_FILE $VERSION +./_buildStartupKits.sh "$PROJECT_FILE" "$VERSION" echo "Startup kits built successfully" rm -rf $CLEAN_SOURCE_DIR diff --git a/docker_config/Dockerfile_stamp b/docker_config/Dockerfile_stamp new file mode 100644 index 00000000..24e5734e --- /dev/null +++ b/docker_config/Dockerfile_stamp @@ -0,0 +1,58 @@ +# Base image with Python 3.11 (CUDA-enabled PyTorch image) +ARG PYTORCH_IMAGE=pytorch/pytorch:2.2.2-cuda12.1-cudnn8-runtime +FROM ${PYTORCH_IMAGE} + +# ------------------ Metadata ------------------ +ARG NVF_VERSION=2.4.1 +ENV NVF_BRANCH=${NVF_VERSION} +ENV PYTHON_VERSION=3.11 +ENV DEBIAN_FRONTEND=noninteractive +ENV PATH="/workspace/STAMP/.venv/bin:$PATH" +ENV HF_HOME=/workspace/.hf_cache + +# ------------------ System Dependencies ------------------ +RUN apt-get update && apt-get install -y --no-install-recommends \ + git curl wget unzip ca-certificates build-essential \ + libglib2.0-dev libgl1 libglx-mesa0 libglib2.0-0 \ + openslide-tools libgl1-mesa-glx \ + python3.11 python3.11-venv python3.11-dev \ + && rm -rf /var/lib/apt/lists/* + +# ------------------ Set Default Python ------------------ +RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.11 1 && \ + update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 && \ + update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 + +# ------------------ Install uv ------------------ +RUN curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --yes + +# ------------------ NVFlare Installation ------------------ +WORKDIR /workspace/ +COPY ./MediSwarm/docker_config/NVFlare /workspace/nvflare +COPY ./MediSwarm/docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ +RUN python -m pip install /workspace/nvflare && rm -rf /workspace/nvflare + +# ------------------ MediSwarm Controller Installation ------------------ +COPY ./MediSwarm/controller /workspace/controller +RUN python -m pip install /workspace/controller && rm -rf /workspace/controller + +# ------------------ STAMP Installation ------------------ +RUN git clone https://github.com/KatherLab/STAMP.git /workspace/STAMP +WORKDIR /workspace/STAMP +RUN /root/.cargo/bin/uv venv && \ + /root/.cargo/bin/uv pip install --upgrade pip && \ + /root/.cargo/bin/uv sync --all-extras + +# ------------------ Expose stamp as CLI globally ------------------ +RUN ln -s /workspace/STAMP/.venv/bin/stamp /usr/local/bin/stamp + +# ------------------ HuggingFace CLI ------------------ +RUN /workspace/STAMP/.venv/bin/pip install huggingface_hub && \ + /workspace/STAMP/.venv/bin/huggingface-cli login --help || true + +# ------------------ MediSwarm Source + Link ------------------ +COPY ./MediSwarm /MediSwarm +RUN mkdir -p /fl_admin/transfer && ln -s /MediSwarm /fl_admin/transfer/MediSwarm + +# ------------------ Default Workdir ------------------ +WORKDIR /workspace/ From 7475cec6d640248618b69495e5de07826af1df38 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 17 Jul 2025 15:11:51 +0200 Subject: [PATCH 067/109] feat: add initial implementation of VaryPrecisionTransform and related configurations Signed-off-by: GitHub CI --- application/jobs/stamp/app/custom/main.py | 60 +++ .../stamp/app/custom/modeling/__init__.py | 0 .../jobs/stamp/app/custom/modeling/alibi.py | 147 ++++++ .../jobs/stamp/app/custom/modeling/config.py | 62 +++ .../stamp/app/custom/modeling/crossval.py | 225 +++++++++ .../jobs/stamp/app/custom/modeling/data.py | 443 ++++++++++++++++++ .../jobs/stamp/app/custom/modeling/deploy.py | 217 +++++++++ .../app/custom/modeling/lightning_model.py | 222 +++++++++ .../jobs/stamp/app/custom/modeling/train.py | 319 +++++++++++++ .../stamp/app/custom/modeling/transforms.py | 41 ++ .../jobs/stamp/app/custom/modeling/types.py | 55 +++ .../app/custom/modeling/vision_transformer.py | 243 ++++++++++ 12 files changed, 2034 insertions(+) create mode 100644 application/jobs/stamp/app/custom/main.py create mode 100755 application/jobs/stamp/app/custom/modeling/__init__.py create mode 100644 application/jobs/stamp/app/custom/modeling/alibi.py create mode 100644 application/jobs/stamp/app/custom/modeling/config.py create mode 100644 application/jobs/stamp/app/custom/modeling/crossval.py create mode 100755 application/jobs/stamp/app/custom/modeling/data.py create mode 100644 application/jobs/stamp/app/custom/modeling/deploy.py create mode 100644 application/jobs/stamp/app/custom/modeling/lightning_model.py create mode 100644 application/jobs/stamp/app/custom/modeling/train.py create mode 100644 application/jobs/stamp/app/custom/modeling/transforms.py create mode 100644 application/jobs/stamp/app/custom/modeling/types.py create mode 100755 application/jobs/stamp/app/custom/modeling/vision_transformer.py diff --git a/application/jobs/stamp/app/custom/main.py b/application/jobs/stamp/app/custom/main.py new file mode 100644 index 00000000..df9e75fe --- /dev/null +++ b/application/jobs/stamp/app/custom/main.py @@ -0,0 +1,60 @@ +import logging +import os +import sys +from pathlib import Path + +from modeling.train import train_categorical_model_ + +# Set up the logger +_logger = logging.getLogger("stamp") +_logger.setLevel(logging.DEBUG) +_formatter = logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s") + +_stream_handler = logging.StreamHandler(sys.stderr) +_stream_handler.setLevel(logging.INFO) +_stream_handler.setFormatter(_formatter) +_logger.addHandler(_stream_handler) + + +def _add_file_handle_(logger: logging.Logger, *, output_dir: Path) -> None: + output_dir.mkdir(exist_ok=True, parents=True) + + file_handler = logging.FileHandler(output_dir / "logfile.log") + file_handler.setLevel(logging.DEBUG) + + formatter = logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s") + file_handler.setFormatter(formatter) + + logger.addHandler(file_handler) + + +def main(): + output_dir = os.getenv("TRAINING_OUTPUT_DIR") + _add_file_handle_(_logger, output_dir=Path(output_dir)) + _logger.info("Using training configuration from environment variables.") + + train_categorical_model_( + output_dir=Path(output_dir), + clini_table=Path(os.getenv("TRAINING_CLINI_TABLE")), + slide_table=Path(os.getenv("TRAINING_SLIDE_TABLE")), + feature_dir=Path(os.getenv("TRAINING_FEATURE_DIR")), + patient_label=os.getenv("TRAINING_PATIENT_LABEL"), + ground_truth_label=os.getenv("TRAINING_GROUND_TRUTH_LABEL"), + filename_label=os.getenv("TRAINING_FILENAME_LABEL"), + categories=os.getenv("TRAINING_CATEGORIES").split(","), + # Dataset and loader parameters + bag_size=int(os.getenv("TRAINING_BAG_SIZE")), + num_workers=int(os.getenv("TRAINING_NUM_WORKERS")), + # Training parameters + batch_size=int(os.getenv("TRAINING_BATCH_SIZE")), + max_epochs=int(os.getenv("TRAINING_MAX_EPOCHS")), + patience=int(os.getenv("TRAINING_PATIENCE")), + accelerator=os.getenv("TRAINING_ACCELERATOR"), + # Experimental features + use_vary_precision_transform=os.getenv("USE_VARY_PRECISION_TRANSFORM", "False").lower() == "true", + use_alibi=os.getenv("USE_ALIBI", "False").lower() == "true", + ) + + +if __name__ == "__main__": + main() diff --git a/application/jobs/stamp/app/custom/modeling/__init__.py b/application/jobs/stamp/app/custom/modeling/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/application/jobs/stamp/app/custom/modeling/alibi.py b/application/jobs/stamp/app/custom/modeling/alibi.py new file mode 100644 index 00000000..69c61aed --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/alibi.py @@ -0,0 +1,147 @@ +import torch +from jaxtyping import Bool, Float +from torch import Tensor, nn + + +class _RunningMeanScaler(nn.Module): + """Scales values by the inverse of the mean of values seen before.""" + + def __init__(self, dtype=torch.float32) -> None: + super().__init__() + self.running_mean = nn.Buffer(torch.ones(1, dtype=dtype)) + self.items_so_far = nn.Buffer(torch.ones(1, dtype=dtype)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.training: + # Welford's algorithm + self.running_mean.copy_( + (self.running_mean + (x - self.running_mean) / self.items_so_far).mean() + ) + self.items_so_far += 1 + + return x / self.running_mean + + +class _ALiBi(nn.Module): + # See MultiHeadAliBi + def __init__(self) -> None: + super().__init__() + + self.scale_distance = _RunningMeanScaler() + self.bias_scale = nn.Parameter(torch.rand(1)) + + def forward( + self, + *, + q: Float[Tensor, "batch query qk_feature"], + k: Float[Tensor, "batch key qk_feature"], + v: Float[Tensor, "batch key v_feature"], + coords_q: Float[Tensor, "batch query coord"], + coords_k: Float[Tensor, "batch key coord"], + attn_mask: Bool[Tensor, "batch query key"] | None, + alibi_mask: Bool[Tensor, "batch query key"] | None, + ) -> Float[Tensor, "batch query v_feature"]: + """ + Args: + alibi_mask: + Which query-key pairs to mask from ALiBi (i.e. don't apply ALiBi to). + """ + weight_logits = torch.einsum("bqf,bkf->bqk", q, k) * (k.size(-1) ** -0.5) + distances = torch.linalg.norm( + coords_q.unsqueeze(2) - coords_k.unsqueeze(1), dim=-1 + ) + scaled_distances = self.scale_distance(distances) * self.bias_scale + + if alibi_mask is not None: + scaled_distances = scaled_distances.where(~alibi_mask, 0.0) + + weights = torch.softmax(weight_logits, dim=-1) + + if attn_mask is not None: + weights = (weights - scaled_distances).where(~attn_mask, 0.0) + else: + weights = weights - scaled_distances + + attention = torch.einsum("bqk,bkf->bqf", weights, v) + + return attention + + +class MultiHeadALiBi(nn.Module): + """Attention with Linear Biases + + Based on + > PRESS, Ofir; SMITH, Noah A.; LEWIS, Mike. + > Train short, test long: Attention with linear biases enables input length extrapolation. + > arXiv preprint arXiv:2108.12409, 2021. + + Since the distances between in WSIs may be quite large, + we scale the distances by the mean distance seen during training. + """ + + def __init__( + self, + *, + embed_dim: int, + num_heads: int, + ) -> None: + super().__init__() + + if embed_dim % num_heads != 0: + raise ValueError(f"{embed_dim=} has to be divisible by {num_heads=}") + + self.query_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + self.key_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + self.value_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + + self.attentions = nn.ModuleList([_ALiBi() for _ in range(num_heads)]) + + self.fc = nn.Linear(in_features=embed_dim, out_features=embed_dim) + + def forward( + self, + *, + q: Float[Tensor, "batch query mh_qk_feature"], + k: Float[Tensor, "batch key mh_qk_feature"], + v: Float[Tensor, "batch key hm_v_feature"], + coords_q: Float[Tensor, "batch query coord"], + coords_k: Float[Tensor, "batch key coord"], + attn_mask: Bool[Tensor, "batch query key"] | None, + alibi_mask: Bool[Tensor, "batch query key"] | None, + ) -> Float[Tensor, "batch query mh_v_feature"]: + stacked_attentions = torch.stack( + [ + att( + q=q_enc(q), + k=k_enc(k), + v=v_enc(v), + coords_q=coords_q, + coords_k=coords_k, + attn_mask=attn_mask, + alibi_mask=alibi_mask, + ) + for q_enc, k_enc, v_enc, att in zip( + self.query_encoders, + self.key_encoders, + self.value_encoders, + self.attentions, + strict=True, + ) + ] + ) + return self.fc(stacked_attentions.permute(1, 2, 0, 3).flatten(-2, -1)) diff --git a/application/jobs/stamp/app/custom/modeling/config.py b/application/jobs/stamp/app/custom/modeling/config.py new file mode 100644 index 00000000..74f7e429 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/config.py @@ -0,0 +1,62 @@ +import os +from pathlib import Path + +import torch +from pydantic import BaseModel, ConfigDict, Field +from stamp.types import PandasLabel + + +class TrainConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + output_dir: Path = Field(description="The directory to save the results to") + + clini_table: Path = Field(description="Excel or CSV to read clinical data from") + slide_table: Path = Field( + description="Excel or CSV to read patient-slide associations from" + ) + feature_dir: Path = Field(description="Directory containing feature files") + + ground_truth_label: PandasLabel = Field( + description="Name of categorical column in clinical table to train on" + ) + categories: list[str] | None = None + + patient_label: PandasLabel = "PATIENT" + filename_label: PandasLabel = "FILENAME" + + # Dataset and -loader parameters + bag_size: int = 512 + num_workers: int = min(os.cpu_count() or 1, 16) + + # Training paramenters + batch_size: int = 64 + max_epochs: int = 64 + patience: int = 16 + accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + + # Experimental features + use_vary_precision_transform: bool = False + use_alibi: bool = False + + +class CrossvalConfig(TrainConfig): + n_splits: int = Field(5, ge=2) + + +class DeploymentConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + output_dir: Path + + checkpoint_paths: list[Path] + clini_table: Path | None = None + slide_table: Path + feature_dir: Path + + ground_truth_label: PandasLabel | None = None + patient_label: PandasLabel = "PATIENT" + filename_label: PandasLabel = "FILENAME" + + num_workers: int = min(os.cpu_count() or 1, 16) + accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" diff --git a/application/jobs/stamp/app/custom/modeling/crossval.py b/application/jobs/stamp/app/custom/modeling/crossval.py new file mode 100644 index 00000000..4ff21ce7 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/crossval.py @@ -0,0 +1,225 @@ +import logging +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import Any, Final + +import numpy as np +from lightning.pytorch.accelerators.accelerator import Accelerator +from pydantic import BaseModel +from sklearn.model_selection import StratifiedKFold +from stamp.modeling.data import ( + PatientData, + filter_complete_patient_data_, + patient_to_ground_truth_from_clini_table_, + slide_to_patient_from_slide_table_, +) +from stamp.modeling.deploy import _predict, _to_prediction_df +from stamp.modeling.lightning_model import LitVisionTransformer +from stamp.modeling.train import setup_model_for_training, train_model_ +from stamp.modeling.transforms import VaryPrecisionTransform +from stamp.types import ( + Category, + FeaturePath, + GroundTruth, + PandasLabel, + PatientId, +) + +__author__ = "Marko van Treeck" +__copyright__ = "Copyright (C) 2024 Marko van Treeck" +__license__ = "MIT" + +_logger = logging.getLogger("stamp") + + +class _Split(BaseModel): + train_patients: set[PatientId] + test_patients: set[PatientId] + + +class _Splits(BaseModel): + splits: Sequence[_Split] + + +def categorical_crossval_( + clini_table: Path, + slide_table: Path, + feature_dir: Path, + output_dir: Path, + patient_label: PandasLabel, + ground_truth_label: PandasLabel, + filename_label: PandasLabel, + categories: Sequence[Category] | None, + n_splits: int, + # Dataset and -loader parameters + bag_size: int, + num_workers: int, + # Training paramenters + batch_size: int, + max_epochs: int, + patience: int, + accelerator: str | Accelerator, + # Experimental features + use_vary_precision_transform: bool, + use_alibi: bool, +) -> None: + patient_to_ground_truth: Final[dict[PatientId, GroundTruth]] = ( + patient_to_ground_truth_from_clini_table_( + clini_table_path=clini_table, + ground_truth_label=ground_truth_label, + patient_label=patient_label, + ) + ) + slide_to_patient: Final[dict[FeaturePath, PatientId]] = ( + slide_to_patient_from_slide_table_( + slide_table_path=slide_table, + feature_dir=feature_dir, + patient_label=patient_label, + filename_label=filename_label, + ) + ) + + # Clean data (remove slides without ground truth, missing features, etc.) + patient_to_data: Final[Mapping[Category, PatientData]] = ( + filter_complete_patient_data_( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + drop_patients_with_missing_ground_truth=True, + ) + ) + + output_dir.mkdir(parents=True, exist_ok=True) + splits_file = output_dir / "splits.json" + + # Generate the splits, or load them from the splits file if they already exist + if not splits_file.exists(): + splits = _get_splits(patient_to_data=patient_to_data, n_splits=n_splits) + with open(splits_file, "w") as fp: + fp.write(splits.model_dump_json(indent=4)) + else: + _logger.debug(f"reading splits from {splits_file}") + with open(splits_file, "r") as fp: + splits = _Splits.model_validate_json(fp.read()) + + patients_in_splits = { + patient + for split in splits.splits + for patient in [*split.train_patients, *split.test_patients] + } + + if patients_without_ground_truth := patients_in_splits - patient_to_data.keys(): + raise RuntimeError( + "The splits file contains some patients we don't have information for in the clini / slide table: " + f"{patients_without_ground_truth}" + ) + + if ground_truths_not_in_split := patient_to_data.keys() - patients_in_splits: + _logger.warning( + "Some of the entries in the clini / slide table are not in the crossval split: " + f"{ground_truths_not_in_split}" + ) + + categories = categories or sorted( + { + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + } + ) + + for split_i, split in enumerate(splits.splits): + split_dir = output_dir / f"split-{split_i}" + + if (split_dir / "patient-preds.csv").exists(): + _logger.info( + f"skipping training for split {split_i}, " + "as a model checkpoint is already present" + ) + continue + + # Train the model + if not (split_dir / "model.ckpt").exists(): + model, train_dl, valid_dl = setup_model_for_training( + clini_table=clini_table, + slide_table=slide_table, + feature_dir=feature_dir, + ground_truth_label=ground_truth_label, + bag_size=bag_size, + num_workers=num_workers, + batch_size=batch_size, + patient_to_data={ + patient_id: patient_data + for patient_id, patient_data in patient_to_data.items() + if patient_id in split.train_patients + }, + categories=( + categories + or sorted( + { + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + } + ) + ), + train_transform=( + VaryPrecisionTransform(min_fraction_bits=1) + if use_vary_precision_transform + else None + ), + use_alibi=use_alibi, + ) + model = train_model_( + output_dir=split_dir, + model=model, + train_dl=train_dl, + valid_dl=valid_dl, + max_epochs=max_epochs, + patience=patience, + accelerator=accelerator, + ) + else: + model = LitVisionTransformer.load_from_checkpoint(split_dir / "model.ckpt") + + # Deploy on test set + if not (split_dir / "patient-preds.csv").exists(): + predictions = _predict( + model=model, + patient_to_data={ + patient_id: patient_data + for patient_id, patient_data in patient_to_data.items() + if patient_id in split.test_patients + }, + num_workers=num_workers, + accelerator=accelerator, + ) + + _to_prediction_df( + categories=categories, + patient_to_ground_truth=patient_to_ground_truth, + predictions=predictions, + patient_label=patient_label, + ground_truth_label=ground_truth_label, + ).to_csv(split_dir / "patient-preds.csv", index=False) + + +def _get_splits( + *, patient_to_data: Mapping[PatientId, PatientData[Any]], n_splits: int +) -> _Splits: + patients = np.array(list(patient_to_data.keys())) + skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=0) + splits = _Splits( + splits=[ + _Split( + train_patients=set(patients[train_indices]), + test_patients=set(patients[test_indices]), + ) + for train_indices, test_indices in skf.split( + patients, + np.array( + [patient.ground_truth for patient in patient_to_data.values()] + ), + ) + ] + ) + return splits diff --git a/application/jobs/stamp/app/custom/modeling/data.py b/application/jobs/stamp/app/custom/modeling/data.py new file mode 100755 index 00000000..10b157f4 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/data.py @@ -0,0 +1,443 @@ +"""Helper classes to manage pytorch data.""" + +import logging +from collections.abc import Callable, Iterable, Mapping, Sequence +from dataclasses import KW_ONLY, dataclass +from itertools import groupby +from pathlib import Path +from typing import BinaryIO, Generic, TextIO, TypeAlias, cast + +import h5py +import numpy as np +import pandas as pd +import stamp +import torch +from jaxtyping import Bool, Float +from packaging.version import Version +from stamp.types import ( + Bags, + BagSize, + BagSizes, + Category, + CoordinatesBatch, + EncodedTargets, + FeaturePath, + GroundTruth, + GroundTruthType, + Microns, + PandasLabel, + PatientId, + SlideMPP, + TilePixels, +) +from torch import Tensor +from torch.utils.data import DataLoader, Dataset + +_logger = logging.getLogger("stamp") + +__author__ = "Marko van Treeck" +__copyright__ = "Copyright (C) 2022-2025 Marko van Treeck" +__license__ = "MIT" + +_Bag: TypeAlias = Float[Tensor, "tile feature"] +_EncodedTarget: TypeAlias = Bool[Tensor, "category_is_hot"] # noqa: F821 +"""The ground truth, encoded numerically (currently: one-hot)""" +_Coordinates: TypeAlias = Float[Tensor, "tile 2"] + + +@dataclass +class PatientData(Generic[GroundTruthType]): + """All raw (i.e. non-generated) information we have on the patient.""" + + _ = KW_ONLY + ground_truth: GroundTruthType + feature_files: Iterable[FeaturePath | BinaryIO] + + +def dataloader_from_patient_data( + *, + patient_data: Sequence[PatientData[GroundTruth | None]], + bag_size: int | None, + categories: Sequence[Category] | None = None, + batch_size: int, + shuffle: bool, + num_workers: int, + transform: Callable[[Tensor], Tensor] | None, +) -> tuple[ + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + Sequence[Category], +]: + """Creates a dataloader from patient data, encoding the ground truths. + + Args: + categories: + Order of classes for one-hot encoding. + If `None`, classes are inferred from patient data. + """ + + raw_ground_truths = np.array([patient.ground_truth for patient in patient_data]) + categories = ( + categories if categories is not None else list(np.unique(raw_ground_truths)) + ) + one_hot = torch.tensor(raw_ground_truths.reshape(-1, 1) == categories) + ds = BagDataset( + bags=[patient.feature_files for patient in patient_data], + bag_size=bag_size, + ground_truths=one_hot, + transform=transform, + ) + + return ( + cast( + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + DataLoader( + ds, + batch_size=batch_size, + shuffle=shuffle, + num_workers=num_workers, + collate_fn=_collate_to_tuple, + ), + ), + list(categories), + ) + + +def _collate_to_tuple( + items: list[tuple[_Bag, _Coordinates, BagSize, _EncodedTarget]], +) -> tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]: + bags = torch.stack([bag for bag, _, _, _ in items]) + coords = torch.stack([coord for _, coord, _, _ in items]) + bag_sizes = torch.tensor([bagsize for _, _, bagsize, _ in items]) + encoded_targets = torch.stack([encoded_target for _, _, _, encoded_target in items]) + + return (bags, coords, bag_sizes, encoded_targets) + + +@dataclass +class BagDataset(Dataset[tuple[_Bag, _Coordinates, BagSize, _EncodedTarget]]): + """A dataset of bags of instances.""" + + _: KW_ONLY + bags: Sequence[Iterable[FeaturePath | BinaryIO]] + """The `.h5` files containing the bags. + + Each bag consists of the features taken from one or multiple h5 files. + Each of the h5 files needs to have a dataset called `feats` of shape N x F, + where N is the number of instances and F the number of features per instance. + """ + + bag_size: BagSize | None = None + """The number of instances in each bag. + + For bags containing more instances, + a random sample of `bag_size` instances will be drawn. + Smaller bags are padded with zeros. + If `bag_size` is None, all the samples will be used. + """ + + ground_truths: Bool[Tensor, "index category_is_hot"] + """The ground truth for each bag, one-hot encoded.""" + + transform: Callable[[Tensor], Tensor] | None + + def __post_init__(self) -> None: + if len(self.bags) != len(self.ground_truths): + raise ValueError( + "the number of ground truths has to match the number of bags" + ) + + def __len__(self) -> int: + return len(self.bags) + + def __getitem__( + self, index: int + ) -> tuple[_Bag, _Coordinates, BagSize, _EncodedTarget]: + # Collect all the features + feats = [] + coords_um = [] + for bag_file in self.bags[index]: + with h5py.File(bag_file, "r") as h5: + feats.append( + torch.from_numpy(h5["feats"][:]) # pyright: ignore[reportIndexIssue] + ) + coords_um.append(torch.from_numpy(get_coords(h5).coords_um)) + + feats = torch.concat(feats).float() + coords_um = torch.concat(coords_um).float() + + if self.transform is not None: + feats = self.transform(feats) + + # Sample a subset, if required + if self.bag_size is not None: + return ( + *_to_fixed_size_bag(feats, coords=coords_um, bag_size=self.bag_size), + self.ground_truths[index], + ) + else: + return ( + feats, + coords_um, + len(feats), + self.ground_truths[index], + ) + + +@dataclass +class CoordsInfo: + coords_um: np.ndarray + tile_size_um: Microns + tile_size_px: TilePixels | None = None + + @property + def mpp(self) -> SlideMPP: + if not self.tile_size_px: + raise RuntimeError( + "tile size in pixels is not available. Please reextract them using `stamp preprocess`." + ) + return SlideMPP(self.tile_size_um / self.tile_size_px) + + +def get_coords(feature_h5: h5py.File) -> CoordsInfo: + coords: np.ndarray = feature_h5["coords"][:] # type: ignore + coords_um: np.ndarray | None = None + tile_size_um: Microns | None = None + tile_size_px: TilePixels | None = None + if (tile_size := feature_h5.attrs.get("tile_size", None)) and feature_h5.attrs.get( + "unit", None + ) == "um": + # STAMP v2 format + tile_size_um = Microns(float(tile_size)) + coords_um = coords + elif tile_size := feature_h5.attrs.get("tile_size_um", None): + # Newer STAMP format + tile_size_um = Microns(float(tile_size)) + coords_um = coords + elif ( + round( + feature_h5.attrs.get( + "tile_size", get_stride(torch.from_numpy(coords).float()) + ) + ) + == 224 + ): + # Historic STAMP format + _logger.info( + f"{feature_h5.filename}: tile stride is roughly 224, assuming coordinates have unit 256um/224px (historic STAMP format)" + ) + tile_size_um = Microns(256.0) + tile_size_px = TilePixels(224) + coords_um = coords / 224 * 256 + + if (version_str := feature_h5.attrs.get("stamp_version")) and ( + extraction_version := Version(version_str) + ) > Version(stamp.__version__): + raise RuntimeError( + f"features were extracted with a newer version of stamp, please update your stamp to at least version {extraction_version}." + ) + + if not tile_size_px and "tile_size_px" in feature_h5.attrs: + tile_size_px = TilePixels(int(feature_h5.attrs["tile_size_px"])) # pyright: ignore[reportArgumentType] + + if not tile_size_um or coords_um is None: + raise RuntimeError( + "unable to infer coordinates from feature file. Please reextract them using `stamp preprocess`." + ) + + return CoordsInfo(coords_um, tile_size_um, tile_size_px) + + +def _to_fixed_size_bag( + bag: _Bag, coords: _Coordinates, bag_size: BagSize +) -> tuple[_Bag, _Coordinates, BagSize]: + """Samples a fixed-size bag of tiles from an arbitrary one. + + If the original bag did not have enough tiles, + the bag is zero-padded to the right. + """ + # get up to bag_size elements + n_tiles, _dim_feats = bag.shape + bag_idxs = torch.randperm(n_tiles)[:bag_size] + bag_samples = bag[bag_idxs] + coord_samples = coords[bag_idxs] + + # zero-pad if we don't have enough samples + zero_padded_bag = torch.cat( + ( + bag_samples, + torch.zeros(bag_size - bag_samples.shape[0], bag_samples.shape[1]), + ) + ) + zero_padded_coord = torch.cat( + ( + coord_samples, + torch.zeros(bag_size - coord_samples.shape[0], coord_samples.shape[1]), + ) + ) + return zero_padded_bag, zero_padded_coord, min(bag_size, len(bag)) + + +def patient_to_ground_truth_from_clini_table_( + *, + clini_table_path: Path | TextIO, + patient_label: PandasLabel, + ground_truth_label: PandasLabel, +) -> dict[PatientId, GroundTruth]: + """Loads the patients and their ground truths from a clini table.""" + clini_df = _read_table( + clini_table_path, + usecols=[patient_label, ground_truth_label], + dtype=str, + ).dropna() + try: + patient_to_ground_truth: Mapping[PatientId, GroundTruth] = clini_df.set_index( + patient_label, verify_integrity=True + )[ground_truth_label].to_dict() + except KeyError as e: + if patient_label not in clini_df: + raise ValueError( + f"{patient_label} was not found in clini table " + f"(columns in clini table: {clini_df.columns})" + ) from e + elif ground_truth_label not in clini_df: + raise ValueError( + f"{ground_truth_label} was not found in clini table " + f"(columns in clini table: {clini_df.columns})" + ) from e + else: + raise e from e + + return patient_to_ground_truth + + +def slide_to_patient_from_slide_table_( + *, + slide_table_path: Path, + feature_dir: Path, + patient_label: PandasLabel, + filename_label: PandasLabel, +) -> dict[FeaturePath, PatientId]: + """Creates a slide-to-patient mapping from a slide table.""" + slide_df = _read_table( + slide_table_path, + usecols=[patient_label, filename_label], + dtype=str, + ) + + slide_to_patient: Mapping[FeaturePath, PatientId] = { + FeaturePath(feature_dir / cast(str, k)): PatientId(cast(str, patient)) + for k, patient in slide_df.set_index(filename_label, verify_integrity=True)[ + patient_label + ].items() + } + + return slide_to_patient + + +def _read_table(path: Path | TextIO, **kwargs) -> pd.DataFrame: + if not isinstance(path, Path): + return pd.read_csv(path, **kwargs) + elif path.suffix == ".xlsx": + return pd.read_excel(path, **kwargs) + elif path.suffix == ".csv": + return pd.read_csv(path, **kwargs) + else: + raise ValueError( + "table to load has to either be an excel (`*.xlsx`) or csv (`*.csv`) file." + ) + + +def filter_complete_patient_data_( + *, + patient_to_ground_truth: Mapping[PatientId, GroundTruth | None], + slide_to_patient: Mapping[FeaturePath, PatientId], + drop_patients_with_missing_ground_truth: bool, +) -> Mapping[PatientId, PatientData]: + """Aggregate information for all patients for which we have complete data. + + This will sort out slides with missing ground truth, missing features, etc. + Patients with their ground truth set explicitly set to `None` will be _included_. + + Side effects: + Checks feature paths' existance. + """ + + _log_patient_slide_feature_inconsistencies( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + ) + + patient_to_slides: dict[PatientId, set[FeaturePath]] = { + patient: set(slides) + for patient, slides in groupby( + slide_to_patient, lambda slide: slide_to_patient[slide] + ) + } + + if not drop_patients_with_missing_ground_truth: + patient_to_ground_truth = { + **{patient_id: None for patient_id in patient_to_slides}, + **patient_to_ground_truth, + } + + patients = { + patient_id: PatientData( + ground_truth=ground_truth, feature_files=existing_features_for_patient + ) + for patient_id, ground_truth in patient_to_ground_truth.items() + # Restrict to only patients which have slides and features + if (slides := patient_to_slides.get(patient_id)) is not None + and ( + existing_features_for_patient := { + feature_path for feature_path in slides if feature_path.exists() + } + ) + } + + return patients + + +def _log_patient_slide_feature_inconsistencies( + *, + patient_to_ground_truth: Mapping[PatientId, GroundTruthType], + slide_to_patient: Mapping[FeaturePath, PatientId], +) -> None: + """Checks whether the arguments are consistent and logs all irregularities. + + Has no side effects outside of logging. + """ + if ( + patients_without_slides := patient_to_ground_truth.keys() + - slide_to_patient.values() + ): + _logger.warning( + f"some patients have no associated slides: {patients_without_slides}" + ) + + if patients_without_ground_truth := ( + slide_to_patient.values() - patient_to_ground_truth.keys() + ): + _logger.warning( + f"some patients have no clinical information: {patients_without_ground_truth}" + ) + + if slides_without_features := { + slide for slide in slide_to_patient.keys() if not slide.exists() + }: + _logger.warning( + f"some feature files could not be found: {slides_without_features}" + ) + + +def get_stride(coords: Float[Tensor, "tile 2"]) -> float: + """Gets the minimum step width between any two coordintes.""" + xs: Tensor = coords[:, 0].unique(sorted=True) + ys: Tensor = coords[:, 1].unique(sorted=True) + stride = cast( + float, + min( + (xs[1:] - xs[:-1]).min().item(), + (ys[1:] - ys[:-1]).min().item(), + ), + ) + return stride diff --git a/application/jobs/stamp/app/custom/modeling/deploy.py b/application/jobs/stamp/app/custom/modeling/deploy.py new file mode 100644 index 00000000..f0cda653 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/deploy.py @@ -0,0 +1,217 @@ +import logging +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import TypeAlias, cast + +import lightning +import numpy as np +import pandas as pd +import torch +from jaxtyping import Float +from lightning.pytorch.accelerators.accelerator import Accelerator +from stamp.modeling.data import ( + PatientData, + dataloader_from_patient_data, + filter_complete_patient_data_, + patient_to_ground_truth_from_clini_table_, + slide_to_patient_from_slide_table_, +) +from stamp.modeling.lightning_model import LitVisionTransformer +from stamp.types import GroundTruth, PandasLabel, PatientId + +__all__ = ["deploy_categorical_model_"] + +__author__ = "Marko van Treeck" +__copyright__ = "Copyright (C) 2024-2025 Marko van Treeck" +__license__ = "MIT" + +_logger = logging.getLogger("stamp") + +Logit: TypeAlias = float + + +def deploy_categorical_model_( + *, + output_dir: Path, + checkpoint_paths: Sequence[Path], + clini_table: Path | None, + slide_table: Path, + feature_dir: Path, + ground_truth_label: PandasLabel | None, + patient_label: PandasLabel, + filename_label: PandasLabel, + num_workers: int, + accelerator: str | Accelerator, +) -> None: + models = [ + LitVisionTransformer.load_from_checkpoint( + checkpoint_path=checkpoint_path + ).eval() + for checkpoint_path in checkpoint_paths + ] + + # Ensure all models were trained on the same ground truth label + if ( + len(ground_truth_labels := set(model.ground_truth_label for model in models)) + != 1 + ): + raise RuntimeError( + f"ground truth labels differ between models: {ground_truth_labels}" + ) + # Ensure the categories were the same between all models + if len(categories := set(tuple(model.categories) for model in models)) != 1: + raise RuntimeError(f"categories differ between models: {categories}") + + model_ground_truth_label = models[0].ground_truth_label + model_categories = list(models[0].categories) + + if ( + ground_truth_label is not None + and ground_truth_label != model_ground_truth_label + ): + _logger.warning( + "deployment ground truth label differs from training: " + f"{ground_truth_label} vs {model_ground_truth_label}" + ) + ground_truth_label = ground_truth_label or model_ground_truth_label + + output_dir.mkdir(exist_ok=True, parents=True) + + slide_to_patient = slide_to_patient_from_slide_table_( + slide_table_path=slide_table, + feature_dir=feature_dir, + patient_label=patient_label, + filename_label=filename_label, + ) + + patient_to_ground_truth: Mapping[PatientId, GroundTruth | None] + if clini_table is not None: + patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( + clini_table_path=clini_table, + ground_truth_label=ground_truth_label, + patient_label=patient_label, + ) + else: + patient_to_ground_truth = { + patient_id: None for patient_id in set(slide_to_patient.values()) + } + + patient_to_data = filter_complete_patient_data_( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + drop_patients_with_missing_ground_truth=False, + ) + + all_predictions: list[Mapping[PatientId, Float[torch.Tensor, "category"]]] = [] # noqa: F821 + for model_i, model in enumerate(models): + predictions = _predict( + model=model, + patient_to_data=patient_to_data, + num_workers=num_workers, + accelerator=accelerator, + ) + all_predictions.append(predictions) + + _to_prediction_df( + categories=model_categories, + patient_to_ground_truth=patient_to_ground_truth, + predictions=predictions, + patient_label=patient_label, + ground_truth_label=ground_truth_label, + ).to_csv(output_dir / f"patient-preds-{model_i}.csv", index=False) + + # TODO we probably also want to save the 95% confidence interval in addition to the mean + _to_prediction_df( + categories=model_categories, + patient_to_ground_truth=patient_to_ground_truth, + predictions={ + # Mean prediction + patient_id: torch.stack( + [predictions[patient_id] for predictions in all_predictions] + ).mean(dim=0) + for patient_id in patient_to_data.keys() + }, + patient_label=patient_label, + ground_truth_label=ground_truth_label, + ).to_csv(output_dir / "patient-preds.csv", index=False) + + +def _predict( + *, + model: LitVisionTransformer, + patient_to_data: Mapping[PatientId, PatientData[GroundTruth | None]], + num_workers: int, + accelerator: str | Accelerator, +) -> Mapping[PatientId, Float[torch.Tensor, "category"]]: # noqa: F821 + model = model.eval() + torch.set_float32_matmul_precision("medium") + + patients_used_for_training: set[PatientId] = set(model.train_patients) | set( + model.valid_patients + ) + if overlap := patients_used_for_training & set(patient_to_data.keys()): + raise ValueError( + f"some of the patients in the validation set were used during training: {overlap}" + ) + + test_dl, _ = dataloader_from_patient_data( + patient_data=list(patient_to_data.values()), + bag_size=None, # Use all the tiles for deployment + # Use same encoding scheme as during training + categories=list(model.categories), + batch_size=1, + shuffle=False, + num_workers=num_workers, + transform=None, + ) + + trainer = lightning.Trainer( + accelerator=accelerator, + devices=1, # Needs to be 1, otherwise half the predictions are missing for some reason + logger=False, + ) + predictions = torch.softmax( + torch.concat( + cast( + list[torch.Tensor], + trainer.predict(model, test_dl), + ) + ), + dim=1, + ) + + return dict(zip(patient_to_data, predictions, strict=True)) + + +def _to_prediction_df( + *, + categories: Sequence[GroundTruth], + patient_to_ground_truth: Mapping[PatientId, GroundTruth | None], + predictions: Mapping[PatientId, torch.Tensor], + patient_label: PandasLabel, + ground_truth_label: PandasLabel, +) -> pd.DataFrame: + """Compiles deployment results into a DataFrame.""" + return pd.DataFrame( + [ + { + patient_label: patient_id, + ground_truth_label: patient_to_ground_truth.get(patient_id), + "pred": categories[int(prediction.argmax())], + **{ + f"{ground_truth_label}_{category}": prediction[i_cat].item() + for i_cat, category in enumerate(categories) + }, + "loss": ( + torch.nn.functional.cross_entropy( + prediction.reshape(1, -1), + torch.tensor(np.where(np.array(categories) == ground_truth)[0]), + ).item() + if (ground_truth := patient_to_ground_truth.get(patient_id)) + is not None + else None + ), + } + for patient_id, prediction in predictions.items() + ] + ).sort_values(by="loss") diff --git a/application/jobs/stamp/app/custom/modeling/lightning_model.py b/application/jobs/stamp/app/custom/modeling/lightning_model.py new file mode 100644 index 00000000..5a98018b --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/lightning_model.py @@ -0,0 +1,222 @@ +"""Lightning wrapper around the model""" + +from collections.abc import Iterable, Sequence +from typing import TypeAlias + +import lightning +import numpy as np +import torch +from jaxtyping import Bool, Float +from torch import Tensor, nn, optim +from torchmetrics.classification import MulticlassAUROC + +from vision_transformer import VisionTransformer +from .types import ( + Bags, + BagSizes, + Category, + CoordinatesBatch, + EncodedTargets, + PandasLabel, + PatientId, +) + +Loss: TypeAlias = Float[Tensor, ""] + + +class LitVisionTransformer(lightning.LightningModule): + """ + PyTorch Lightning wrapper for the Vision Transformer (ViT) model used in weakly supervised + learning settings, such as Multiple Instance Learning (MIL) for whole-slide images or patch-based data. + + This class encapsulates training, validation, testing, and prediction logic, along with: + - Masking logic that ensures only valid tiles (patches) participate in attention during training. + - AUROC metric tracking during validation for multiclass classification. + - Compatibility checks based on the `stamp` framework version. + - Integration of class imbalance handling through weighted cross-entropy loss. + + The attention mask is applied *only* during training to hide paddings + and is skipped during evaluation and inference for reducing memory usage. + + Args: + categories: List of class labels. + category_weights: Class weights for cross-entropy loss to handle imbalance. + dim_input: Input feature dimensionality per tile. + dim_model: Latent dimensionality used inside the transformer. + dim_feedforward: Dimensionality of the transformer MLP block. + n_heads: Number of self-attention heads. + n_layers: Number of transformer layers. + dropout: Dropout rate used throughout the model. + use_alibi: Whether to use ALiBi-style positional bias in attention (optional). + ground_truth_label: Column name for accessing ground-truth labels from metadata. + train_patients: List of patient IDs used for training. + valid_patients: List of patient IDs used for validation. + stamp_version: Version of the `stamp` framework used during training. + **metadata: Additional metadata to store with the model. + """ + + def __init__( + self, + *, + categories: Sequence[Category], + category_weights: Float[Tensor, "category_weight"], # noqa: F821 + dim_input: int, + dim_model: int, + dim_feedforward: int, + n_heads: int, + n_layers: int, + dropout: float, + # Experimental features + # TODO remove default values for stamp 3; they're only here for backwards compatibility + use_alibi: bool = False, + # Metadata used by other parts of stamp, but not by the model itself + ground_truth_label: PandasLabel, + train_patients: Iterable[PatientId], + valid_patients: Iterable[PatientId], + # stamp_version: Version = Version(stamp.__version__), + # Other metadata + **metadata, + ) -> None: + super().__init__() + + if len(categories) != len(category_weights): + raise ValueError( + "the number of category weights has to match the number of categories!" + ) + + self.vision_transformer = VisionTransformer( + dim_output=len(categories), + dim_input=dim_input, + dim_model=dim_model, + n_layers=n_layers, + n_heads=n_heads, + dim_feedforward=dim_feedforward, + dropout=dropout, + use_alibi=use_alibi, + ) + self.class_weights = category_weights + self.valid_auroc = MulticlassAUROC(len(categories)) + + # Used during deployment + self.ground_truth_label = ground_truth_label + self.categories = np.array(categories) + self.train_patients = train_patients + self.valid_patients = valid_patients + + _ = metadata # unused, but saved in model + + # Check if version is compatible. + # This should only happen when the model is loaded, + # otherwise the default value will make these checks pass. + ''' + if stamp_version < Version("2.0.0.dev8"): + # Update this as we change our model in incompatible ways! + raise ValueError( + f"model has been built with stamp version {stamp_version} " + f"which is incompatible with the current version." + ) + + elif stamp_version > Version(stamp.__version__): + # Let's be strict with models "from the future", + # better fail deadly than have broken results. + raise ValueError( + "model has been built with a stamp version newer than the installed one " + f"({stamp_version} > {stamp.__version__}). " + "Please upgrade stamp to a compatible version." + ) + ''' + self.save_hyperparameters() + + def forward( + self, + bags: Bags, + ) -> Float[Tensor, "batch logit"]: + return self.vision_transformer(bags) + + def _step( + self, + *, + batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], + step_name: str, + use_mask: bool, + ) -> Loss: + bags, coords, bag_sizes, targets = batch + + mask = _mask_from_bags(bags=bags, bag_sizes=bag_sizes) if use_mask else None + + logits = self.vision_transformer(bags, coords=coords, mask=mask) + + loss = nn.functional.cross_entropy( + logits, + targets.type_as(logits), + weight=self.class_weights.type_as(logits), + ) + + self.log( + f"{step_name}_loss", + loss, + on_step=False, + on_epoch=True, + prog_bar=True, + sync_dist=True, + ) + + if step_name == "validation": + # TODO this is a bit ugly, we'd like to have `_step` without special cases + self.valid_auroc.update(logits, targets.long().argmax(dim=-1)) + self.log( + f"{step_name}_auroc", + self.valid_auroc, + on_step=False, + on_epoch=True, + sync_dist=True, + ) + + return loss + + def training_step( + self, + batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], + batch_idx: int, + ) -> Loss: + return self._step(batch=batch, step_name="training", use_mask=True) + + def validation_step( + self, + batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], + batch_idx: int, + ) -> Loss: + return self._step(batch=batch, step_name="validation", use_mask=False) + + def test_step( + self, + batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], + batch_idx: int, + ) -> Loss: + return self._step(batch=batch, step_name="test", use_mask=False) + + def predict_step( + self, + batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], + batch_idx: int, + ) -> Float[Tensor, "batch logit"]: + bags, coords, bag_sizes, _ = batch + # adding a mask here will *drastically* and *unbearably* increase memory usage + return self.vision_transformer(bags, coords=coords, mask=None) + + def configure_optimizers(self) -> optim.Optimizer: + optimizer = optim.Adam(self.parameters(), lr=1e-3) + return optimizer + + +def _mask_from_bags( + *, + bags: Bags, + bag_sizes: BagSizes, +) -> Bool[Tensor, "batch tile"]: + max_possible_bag_size = bags.size(1) + mask = torch.arange(max_possible_bag_size).type_as(bag_sizes).unsqueeze(0).repeat( + len(bags), 1 + ) >= bag_sizes.unsqueeze(1) + + return mask diff --git a/application/jobs/stamp/app/custom/modeling/train.py b/application/jobs/stamp/app/custom/modeling/train.py new file mode 100644 index 00000000..0d71f0ca --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/train.py @@ -0,0 +1,319 @@ +import logging +import shutil +from collections.abc import Callable, Mapping, Sequence +from pathlib import Path +from typing import cast + +import lightning +import lightning.pytorch +import lightning.pytorch.accelerators +import lightning.pytorch.accelerators.accelerator +import torch +from lightning.pytorch.accelerators.accelerator import Accelerator +from lightning.pytorch.callbacks import EarlyStopping, ModelCheckpoint +from lightning.pytorch.loggers import CSVLogger +from sklearn.model_selection import train_test_split +from torch.utils.data.dataloader import DataLoader + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +import nvflare.client.lightning as flare +import nvflare.client as flare_util + +flare_util.init() +SITE_NAME = flare.get_site_name() + +from data import ( + BagDataset, + PatientData, + dataloader_from_patient_data, + filter_complete_patient_data_, + patient_to_ground_truth_from_clini_table_, + slide_to_patient_from_slide_table_, +) +from lightning_model import ( + Bags, + BagSizes, + EncodedTargets, + LitVisionTransformer, +) +from stamp.modeling.transforms import VaryPrecisionTransform +from stamp.types import Category, CoordinatesBatch, GroundTruth, PandasLabel, PatientId + +__author__ = "Marko van Treeck" +__copyright__ = "Copyright (C) 2024 Marko van Treeck" +__license__ = "MIT" + +_logger = logging.getLogger("stamp") + + +def train_categorical_model_( + *, + clini_table: Path, + slide_table: Path, + feature_dir: Path, + output_dir: Path, + patient_label: PandasLabel, + ground_truth_label: PandasLabel, + filename_label: PandasLabel, + categories: Sequence[Category] | None, + # Dataset and -loader parameters + bag_size: int, + num_workers: int, + # Training paramenters + batch_size: int, + max_epochs: int, + patience: int, + accelerator: str | Accelerator, + # Experimental features + use_vary_precision_transform: bool, + use_alibi: bool, +) -> None: + """Trains a model. + + Args: + clini_table: + An excel or csv file to read the clinical information from. + Must at least have the columns specified in the arguments + + `patient_label` (containing a unique patient ID) + and `ground_truth_label` (containing the ground truth to train for). + slide_table: + An excel or csv file to read the patient-slide associations from. + Must at least have the columns specified in the arguments + `patient_label` (containing the patient ID) + and `filename_label` + (containing a filename relative to `feature_dir` + in which some of the patient's features are stored). + feature_dir: + See `slide_table`. + output_dir: + Path into which to output the artifacts (trained model etc.) + generated during training. + patient_label: + See `clini_table`, `slide_table`. + ground_truth_label: + See `clini_table`. + filename_label: + See `slide_table`. + categories: + Categories of the ground truth. + Set to `None` to automatically infer. + """ + # Read and parse data from out clini and slide table + patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( + clini_table_path=clini_table, + ground_truth_label=ground_truth_label, + patient_label=patient_label, + ) + slide_to_patient = slide_to_patient_from_slide_table_( + slide_table_path=slide_table, + feature_dir=feature_dir, + patient_label=patient_label, + filename_label=filename_label, + ) + + # Clean data (remove slides without ground truth, missing features, etc.) + patient_to_data = filter_complete_patient_data_( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + drop_patients_with_missing_ground_truth=True, + ) + + # Train the model + model, train_dl, valid_dl = setup_model_for_training( + patient_to_data=patient_to_data, + categories=categories, + bag_size=bag_size, + batch_size=batch_size, + num_workers=num_workers, + ground_truth_label=ground_truth_label, + clini_table=clini_table, + slide_table=slide_table, + feature_dir=feature_dir, + train_transform=( + VaryPrecisionTransform(min_fraction_bits=1) + if use_vary_precision_transform + else None + ), + use_alibi=use_alibi, + ) + train_model_( + output_dir=output_dir, + model=model, + train_dl=train_dl, + valid_dl=valid_dl, + max_epochs=max_epochs, + patience=patience, + accelerator=accelerator, + ) + + +def train_model_( + *, + output_dir: Path, + model: LitVisionTransformer, + train_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + valid_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + max_epochs: int, + patience: int, + accelerator: str | Accelerator, +) -> LitVisionTransformer: + """Trains a model. + + Returns: + The model with the best validation loss during training. + """ + torch.set_float32_matmul_precision("high") + + model_checkpoint = ModelCheckpoint( + monitor="validation_loss", + mode="min", + filename="checkpoint-{epoch:02d}-{validation_loss:0.3f}", + ) + trainer = lightning.Trainer( + default_root_dir=output_dir, + callbacks=[ + EarlyStopping(monitor="validation_loss", mode="min", patience=patience), + model_checkpoint, + ], + + max_epochs=max_epochs, + # FIXME The number of accelerators is currently fixed to one for the + # following reasons: + # 1. `trainer.predict()` does not return any predictions if used with + # the default strategy no multiple GPUs + # 2. `barspoon.model.SafeMulticlassAUROC` breaks on multiple GPUs + accelerator=accelerator, + devices=1, + gradient_clip_val=0.5, + logger=CSVLogger(save_dir=output_dir), + log_every_n_steps=len(train_dl), + ) + + flare.patch(trainer) # Patch trainer to enable swarm learning + logger.info(f"Site name: {SITE_NAME}") + + while flare.is_running(): + input_model = flare.receive() + logger.info(f"Current round: {input_model.current_round}") + trainer.fit( + model=model, + train_dataloaders=train_dl, + val_dataloaders=valid_dl, + ) + shutil.copy(model_checkpoint.best_model_path, output_dir / "model.ckpt") + + return LitVisionTransformer.load_from_checkpoint(model_checkpoint.best_model_path) + + +def setup_model_for_training( + *, + patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], + categories: Sequence[Category] | None, + bag_size: int, + batch_size: int, + num_workers: int, + train_transform: Callable[[torch.Tensor], torch.Tensor] | None, + use_alibi: bool, + # Metadata, has no effect on model training + ground_truth_label: PandasLabel, + clini_table: Path, + slide_table: Path, + feature_dir: Path, +) -> tuple[ + LitVisionTransformer, + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], +]: + """Creates a model and dataloaders for training""" + + # Do a stratified train-validation split + ground_truths = [ + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + ] + + if len(ground_truths) != len(patient_to_data): + raise ValueError( + "patient_to_data must have a ground truth defined for all targets!" + ) + + train_patients, valid_patients = cast( + tuple[Sequence[PatientId], Sequence[PatientId]], + train_test_split( + list(patient_to_data), stratify=ground_truths, shuffle=True, random_state=0 + ), + ) + + train_dl, train_categories = dataloader_from_patient_data( + patient_data=[patient_to_data[patient] for patient in train_patients], + categories=categories, + bag_size=bag_size, + batch_size=batch_size, + shuffle=True, + num_workers=num_workers, + transform=train_transform, + ) + del categories # Let's not accidentally reuse the original categories + valid_dl, _ = dataloader_from_patient_data( + patient_data=[patient_to_data[patient] for patient in valid_patients], + bag_size=None, # Use all the patient data for validation + categories=train_categories, + batch_size=1, + shuffle=False, + num_workers=num_workers, + transform=None, + ) + if overlap := set(train_patients) & set(valid_patients): + raise RuntimeError( + f"unreachable: unexpected overlap between training and validation set: {overlap}" + ) + + # Sample one bag to infer the input dimensions of the model + bags, coords, bag_sizes, targets = cast( + tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], next(iter(train_dl)) + ) + _, _, dim_feats = bags.shape + + # Weigh classes inversely to their occurrence + category_counts = cast(BagDataset, train_dl.dataset).ground_truths.sum(dim=0) + cat_ratio_reciprocal = category_counts.sum() / category_counts + category_weights = cat_ratio_reciprocal / cat_ratio_reciprocal.sum() + + if len(train_categories) <= 1: + raise ValueError(f"not enough categories to train on: {train_categories}") + elif any(category_counts < 16): + underpopulated_categories = { + category: count + for category, count in zip(train_categories, category_counts, strict=True) + if count < 16 + } + _logger.warning( + f"Some categories do not have enough samples to meaningfully train a model: {underpopulated_categories}. " + "You may want to consider removing these categories; the model will likely overfit on the few samples available." + ) + + # Train the model + model = LitVisionTransformer( + categories=train_categories, + category_weights=category_weights, + dim_input=dim_feats, + dim_model=512, + dim_feedforward=2048, + n_heads=8, + n_layers=2, + dropout=0.25, + use_alibi=use_alibi, + # Metadata, has no effect on model training + ground_truth_label=ground_truth_label, + train_patients=train_patients, + valid_patients=valid_patients, + clini_table=clini_table, + slide_table=slide_table, + feature_dir=feature_dir, + ) + + return model, train_dl, valid_dl diff --git a/application/jobs/stamp/app/custom/modeling/transforms.py b/application/jobs/stamp/app/custom/modeling/transforms.py new file mode 100644 index 00000000..6fd9755f --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/transforms.py @@ -0,0 +1,41 @@ +import torch +from jaxtyping import Float + + +def vary_precision( + data: Float[torch.Tensor, "*dims"], *, min_fraction_bits: int +) -> Float[torch.Tensor, "*dims"]: + """Randomly reduces the precision of the tensor's values.""" + if min_fraction_bits < 1: + raise ValueError("min_fraction bits has to be at least 1") + + if data.dtype == torch.float32: + fraction_bits = 23 + mask_dtype = torch.int32 + elif data.dtype == torch.float16: + fraction_bits = 10 + mask_dtype = torch.int16 + elif data.dtype == torch.bfloat16: + fraction_bits = 7 + mask_dtype = torch.int16 + else: + raise NotImplementedError( + f"precision variation not implemented for {data.dtype}" + ) + + no_of_bits_to_mask = torch.randint(0, fraction_bits - min_fraction_bits, data.shape) + mask = (~0 << no_of_bits_to_mask).to(dtype=mask_dtype, device=data.device) + augmented = (data.view(mask_dtype) & mask).view(data.dtype) + return augmented + + +class VaryPrecisionTransform: + """A transform randomly reducing the precision of its inputs.""" + + def __init__(self, *, min_fraction_bits: int = 1) -> None: + self.min_fraction_bits = min_fraction_bits + + def __call__( + self, batch: Float[torch.Tensor, "*dims"] + ) -> Float[torch.Tensor, "*dims"]: + return vary_precision(data=batch, min_fraction_bits=self.min_fraction_bits) diff --git a/application/jobs/stamp/app/custom/modeling/types.py b/application/jobs/stamp/app/custom/modeling/types.py new file mode 100644 index 00000000..4d48293a --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/types.py @@ -0,0 +1,55 @@ +from pathlib import Path +from typing import ( + Final, + Literal, + NewType, + TypeAlias, + TypeVar, +) + +import torch +from beartype.typing import Mapping +from jaxtyping import Bool, Float, Integer +from torch import Tensor + +# tiling + +ImageExtension: TypeAlias = Literal["png", "jpg"] +EXTENSION_TO_FORMAT: Final[Mapping[ImageExtension, str]] = { + "png": "png", + "jpg": "jpeg", +} + +Microns = NewType("Microns", float) +"""Micrometers, usually referring to the tissue on the slide""" + +SlidePixels = NewType("SlidePixels", int) +"""Pixels of the WSI scan at largest magnification (i.e. coordinates used by OpenSlide)""" + +TilePixels = NewType("TilePixels", int) +"""Pixels after resizing, i.e. how they appear on the final tile""" + +SlideMPP = NewType("SlideMPP", float) + +# modeling + +DeviceLikeType: TypeAlias = str | torch.device | int + +PatientId: TypeAlias = str +GroundTruth: TypeAlias = str +FeaturePath = NewType("FeaturePath", Path) + +Category: TypeAlias = str + +BagSize: TypeAlias = int + +# A batch of the above +Bags: TypeAlias = Float[Tensor, "batch tile feature"] +BagSizes: TypeAlias = Integer[Tensor, "batch"] # noqa: F821 +EncodedTargets: TypeAlias = Bool[Tensor, "batch category_is_hot"] +"""The ground truth, encoded numerically (currently: one-hot)""" +CoordinatesBatch: TypeAlias = Float[Tensor, "batch tile 2"] + +PandasLabel: TypeAlias = str + +GroundTruthType = TypeVar("GroundTruthType", covariant=True) diff --git a/application/jobs/stamp/app/custom/modeling/vision_transformer.py b/application/jobs/stamp/app/custom/modeling/vision_transformer.py new file mode 100755 index 00000000..a2d71c0b --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/vision_transformer.py @@ -0,0 +1,243 @@ +""" +In parts from https://github.com/lucidrains/vit-pytorch/blob/main/vit_pytorch/vit.py +""" + +from collections.abc import Iterable +from typing import assert_never, cast + +import torch +from beartype import beartype +from einops import repeat +from jaxtyping import Bool, Float, jaxtyped +from stamp.modeling.alibi import MultiHeadALiBi +from torch import Tensor, nn + + +def feed_forward( + dim: int, + hidden_dim: int, + dropout: float = 0.5, +) -> nn.Module: + return nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, hidden_dim), + nn.GELU(), + nn.Dropout(dropout), + nn.Linear(hidden_dim, dim), + nn.Dropout(dropout), + ) + + +class SelfAttention(nn.Module): + def __init__( + self, + *, + dim: int, + num_heads: int, + dropout: float, + use_alibi: bool, + ) -> None: + super().__init__() + self.heads = num_heads + self.norm = nn.LayerNorm(dim) + + if use_alibi: + self.mhsa = MultiHeadALiBi( + embed_dim=dim, + num_heads=num_heads, + ) + else: + self.mhsa = nn.MultiheadAttention(dim, num_heads, dropout, batch_first=True) + + @jaxtyped(typechecker=beartype) + def forward( + self, + x: Float[Tensor, "batch sequence proj_feature"], + *, + coords: Float[Tensor, "batch sequence xy"], + attn_mask: Bool[Tensor, "batch sequence sequence"] | None, + # Help, my abstractions are leaking! + alibi_mask: Bool[Tensor, "batch sequence sequence"] | None, + ) -> Float[Tensor, "batch sequence proj_feature"]: + """ + Args: + attn_mask: + Which of the features to ignore during self-attention. + `attn_mask[b,q,k] == False` means that + query `q` of batch `b` can attend to key `k`. + If `attn_mask` is `None`, all tokens can attend to all others. + alibi_mask: + Which query-key pairs to apply ALiBi to. + If this module was constructed using `use_alibi=False`, + this has no effect. + """ + x = self.norm(x) + match self.mhsa: + case nn.MultiheadAttention(): + attn_output, _ = self.mhsa( + x, + x, + x, + need_weights=False, + attn_mask=( + attn_mask.repeat(self.mhsa.num_heads, 1, 1) + if attn_mask is not None + else None + ), + ) + case MultiHeadALiBi(): + attn_output = self.mhsa( + q=x, + k=x, + v=x, + coords_q=coords, + coords_k=coords, + attn_mask=attn_mask, + alibi_mask=alibi_mask, + ) + case _ as unreachable: + assert_never(unreachable) + + return attn_output + + +class Transformer(nn.Module): + def __init__( + self, + *, + dim: int, + depth: int, + heads: int, + mlp_dim: int, + dropout: float, + use_alibi: bool, + ) -> None: + super().__init__() + self.depth = depth + self.layers = nn.ModuleList( + [ + nn.ModuleList( + [ + SelfAttention( + dim=dim, + num_heads=heads, + dropout=dropout, + use_alibi=use_alibi, + ), + feed_forward( + dim, + mlp_dim, + ), + ] + ) + for _ in range(depth) + ] + ) + + self.norm = nn.LayerNorm(dim) + + @jaxtyped(typechecker=beartype) + def forward( + self, + x: Float[Tensor, "batch sequence proj_feature"], + *, + coords: Float[Tensor, "batch sequence 2"], + attn_mask: Bool[Tensor, "batch sequence sequence"] | None, + alibi_mask: Bool[Tensor, "batch sequence sequence"] | None, + ) -> Float[Tensor, "batch sequence proj_feature"]: + for attn, ff in cast(Iterable[tuple[nn.Module, nn.Module]], self.layers): + x_attn = attn(x, coords=coords, attn_mask=attn_mask, alibi_mask=alibi_mask) + x = x_attn + x + x = ff(x) + x + + x = self.norm(x) + return x + + +class VisionTransformer(nn.Module): + def __init__( + self, + *, + dim_output: int, + dim_input: int, + dim_model: int, + n_layers: int, + n_heads: int, + dim_feedforward: int, + dropout: float, + use_alibi: bool, + ) -> None: + super().__init__() + self.class_token = nn.Parameter(torch.randn(dim_model)) + + self.project_features = nn.Sequential( + nn.Linear(dim_input, dim_model, bias=True), + nn.GELU(), + nn.Dropout(dropout), + ) + + self.transformer = Transformer( + dim=dim_model, + depth=n_layers, + heads=n_heads, + mlp_dim=dim_feedforward, + dropout=dropout, + use_alibi=use_alibi, + ) + + self.mlp_head = nn.Sequential(nn.Linear(dim_model, dim_output)) + + @jaxtyped(typechecker=beartype) + def forward( + self, + bags: Float[Tensor, "batch tile feature"], + *, + coords: Float[Tensor, "batch tile 2"], + mask: Bool[Tensor, "batch tile"] | None, + ) -> Float[Tensor, "batch logit"]: + batch_size, _n_tiles, _n_features = bags.shape + + # Map input sequence to latent space of TransMIL + bags = self.project_features(bags) + + # Prepend a class token to every bag, + # include it in the mask. + # TODO should the tiles be able to refer to the class token? Test! + cls_tokens = repeat(self.class_token, "d -> b 1 d", b=batch_size) + bags = torch.cat([cls_tokens, bags], dim=1) + coords = torch.cat( + [torch.zeros(batch_size, 1, 2).type_as(coords), coords], dim=1 + ) + + match mask: + case None: + bags = self.transformer( + bags, coords=coords, attn_mask=None, alibi_mask=None + ) + + case _: + mask_with_class_token = torch.cat( + [torch.zeros(mask.shape[0], 1).type_as(mask), mask], dim=1 + ) + square_attn_mask = torch.einsum( + "bq,bk->bqk", mask_with_class_token, mask_with_class_token + ) + # Don't allow other tiles to reference the class token + square_attn_mask[:, 1:, 0] = True + + # Don't apply ALiBi to the query, as the coordinates don't make sense here + alibi_mask = torch.zeros_like(square_attn_mask) + alibi_mask[:, 0, :] = True + alibi_mask[:, :, 0] = True + + bags = self.transformer( + bags, + coords=coords, + attn_mask=square_attn_mask, + alibi_mask=alibi_mask, + ) + + # Only take class token + bags = bags[:, 0] + + return self.mlp_head(bags) From 5882258be495d92ea33ac456045c2c8574e3676e Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Thu, 17 Jul 2025 15:15:20 +0200 Subject: [PATCH 068/109] feat: add configuration files for federated client and server setup Signed-off-by: GitHub CI --- .../stamp/app/config/config_fed_client.conf | 135 ++++++++++++++++++ .../stamp/app/config/config_fed_server.conf | 27 ++++ 2 files changed, 162 insertions(+) create mode 100644 application/jobs/stamp/app/config/config_fed_client.conf create mode 100644 application/jobs/stamp/app/config/config_fed_server.conf diff --git a/application/jobs/stamp/app/config/config_fed_client.conf b/application/jobs/stamp/app/config/config_fed_client.conf new file mode 100644 index 00000000..b15e14cb --- /dev/null +++ b/application/jobs/stamp/app/config/config_fed_client.conf @@ -0,0 +1,135 @@ +{ + format_version = 2 + app_script = "main.py" + app_config = "" + executors = [ + { + tasks = [ + "train" + ] + executor { + path = "nvflare.app_opt.pt.client_api_launcher_executor.PTClientAPILauncherExecutor" + args { + launcher_id = "launcher" + pipe_id = "pipe" + last_result_transfer_timeout = 3600 + external_pre_init_timeout = 3600 + peer_read_timeout = 3600 + heartbeat_timeout = 3600 + params_exchange_format = "pytorch" + params_transfer_type = "DIFF" + train_with_evaluation = true + } + } + } + { + # All tasks prefixed with swarm_ are routed to SwarmClientController + tasks = ["swarm_*"] + executor { + # client-side controller for training and logic and aggregation management + path = "controller.SwarmClientController" + args { + # train task must be implemented by Executor + learn_task_name = "train" + # how long to wait for current learn task before timing out the gathering + learn_task_timeout = 3600 + learn_task_abort_timeout = 3600 + learn_task_ack_timeout = 3600 + final_result_ack_timeout = 3600 + + # ids must map to corresponding components + persistor_id = "persistor" + aggregator_id = "aggregator" + shareable_generator_id = "shareable_generator" + min_responses_required = 2 + wait_time_after_min_resps_received = 3600 + } + } + } + ] + task_data_filters = [] + task_result_filters = [] + components = [ + { + id = "launcher" + path = "nvflare.app_common.launchers.subprocess_launcher.SubprocessLauncher" + args { + script = "python3 custom/{app_script} {app_config} " + launch_once = true + } + } + { + id = "aggregator" + path = "nvflare.app_common.aggregators.intime_accumulate_model_aggregator.InTimeAccumulateWeightedAggregator" + args { + expected_data_kind = "WEIGHT_DIFF" + } + } + { + id = "pipe" + path = "nvflare.fuel.utils.pipe.cell_pipe.CellPipe" + args { + mode = "PASSIVE" + site_name = "{SITE_NAME}" + token = "{JOB_ID}" + root_url = "{ROOT_URL}" + secure_mode = "{SECURE_MODE}" + workspace_dir = "{WORKSPACE}" + } + } + { + id = "persistor" + path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" + args { + model { + path = "modeling.lightning_model.LitVisionTransformer" + args { + + } + } + } + } + { + id = "shareable_generator" + path = "nvflare.app_common.ccwf.comps.simple_model_shareable_generator.SimpleModelShareableGenerator" + args {} + } + { + id = "metrics_pipe" + path = "nvflare.fuel.utils.pipe.cell_pipe.CellPipe" + args { + mode = "PASSIVE" + site_name = "{SITE_NAME}" + token = "{JOB_ID}" + root_url = "{ROOT_URL}" + secure_mode = "{SECURE_MODE}" + workspace_dir = "{WORKSPACE}" + } + } + { + id = "metric_relay" + path = "nvflare.app_common.widgets.metric_relay.MetricRelay" + args { + pipe_id = "metrics_pipe" + event_type = "fed.analytix_log_stats" + read_interval = 0.1 + } + } + { + id = "model_selector" + path = "nvflare.app_common.widgets.intime_model_selector.IntimeModelSelector" + args { + key_metric = "accuracy" + } + } + { + id = "config_preparer" + path = "nvflare.app_common.widgets.external_configurator.ExternalConfigurator" + args { + component_ids = [ + "metric_relay" + ] + } + } + ] +} diff --git a/application/jobs/stamp/app/config/config_fed_server.conf b/application/jobs/stamp/app/config/config_fed_server.conf new file mode 100644 index 00000000..c8a11577 --- /dev/null +++ b/application/jobs/stamp/app/config/config_fed_server.conf @@ -0,0 +1,27 @@ +format_version = 2 +task_data_filters = [] +task_result_filters = [] +components = [ + { + # write validation results to json file + id = "json_generator" + path = "nvflare.app_common.widgets.validation_json_generator.ValidationJsonGenerator" + args {} + } +] +workflows = [ + { + # server-side controller to manage job life cycle + id = "swarm_controller" + path = "controller.SwarmServerController" + args { + # can also set aggregation clients and train clients, see class for all available args + num_rounds = 20 + start_task_timeout = 3600 + progress_timeout = 3600 + end_workflow_timeout = 3600 + configure_task_timeout = 3600 + max_status_report_interval = 3600 + } + } +] From a023eb213797438939cfda5f55954287b9cf439b Mon Sep 17 00:00:00 2001 From: Ultimate-Storm Date: Mon, 21 Jul 2025 06:28:04 +0200 Subject: [PATCH 069/109] chore: update apt versions in Dockerfile_ODELIA --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 0491561a..47d05edf 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,7 +15,7 @@ RUN apt update RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-143.153 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-144.157 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 From 17ffb1f2b83c636a345f8c50e69c98676eca7d1a Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Mon, 21 Jul 2025 15:27:26 +0200 Subject: [PATCH 070/109] create scratch dir only at user-writable location, avoid need for sudo password --- docker_config/master_template.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index e9cfba12..bb2f99de 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -666,16 +666,8 @@ docker_cln_sh: | # Resolve script directory DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - if [ -t 1 ]; then - # Local only - sudo mkdir -p "$MY_SCRATCH_DIR" - sudo chown -R $(id -u):$(id -g) "$MY_SCRATCH_DIR" - sudo chmod -R 777 "$MY_SCRATCH_DIR" - else - mkdir -p "$MY_SCRATCH_DIR" - chmod -R 777 "$MY_SCRATCH_DIR" - fi - + mkdir -p "$MY_SCRATCH_DIR" + chmod -R 777 "$MY_SCRATCH_DIR" # Networking & Cleanup NETARG="--net=host" From c044ddab160c73719b0289f907a923847e38ca07 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 21 Jul 2025 16:04:49 +0200 Subject: [PATCH 071/109] refactor: reorganize project structure and update import paths for STAMP integration Signed-off-by: GitHub CI --- .../app/config/config_fed_client.conf | 2 +- .../app/config/config_fed_server.conf | 3 +- .../app/config/config_fed_client.conf | 2 +- .../app/config/config_fed_server.conf | 4 +- .../app/custom/main.py | 14 +- .../stamp/app/config/config_fed_client.conf | 26 +- .../stamp/app/config/config_fed_server.conf | 11 +- .../app/custom/{modeling => }/__init__.py | 0 .../stamp/app/custom/{modeling => }/alibi.py | 0 .../stamp/app/custom/{modeling => }/config.py | 3 +- .../app/custom/{modeling => }/crossval.py | 13 +- .../stamp/app/custom/{modeling => }/data.py | 7 +- .../stamp/app/custom/{modeling => }/deploy.py | 3 +- application/jobs/stamp/app/custom/main.py | 381 +- .../app/custom/modeling/lightning_model.py | 116 +- .../jobs/stamp/app/custom/modeling/train.py | 319 -- .../app/custom/modeling/vision_transformer.py | 2 +- .../app/custom/{modeling => }/transforms.py | 0 .../provision/project_Odelia_allsites.yml | 4 +- buildDockerImageAndStartupKits.sh | 4 - docker_config/Dockerfile_stamp | 77 +- docker_config/pyproject.toml | 142 + docker_config/uv.lock | 3641 +++++++++++++++++ 23 files changed, 4303 insertions(+), 471 deletions(-) rename application/jobs/stamp/app/custom/{modeling => }/__init__.py (100%) rename application/jobs/stamp/app/custom/{modeling => }/alibi.py (100%) rename application/jobs/stamp/app/custom/{modeling => }/config.py (97%) rename application/jobs/stamp/app/custom/{modeling => }/crossval.py (95%) rename application/jobs/stamp/app/custom/{modeling => }/data.py (99%) rename application/jobs/stamp/app/custom/{modeling => }/deploy.py (99%) delete mode 100644 application/jobs/stamp/app/custom/modeling/train.py rename application/jobs/stamp/app/custom/{modeling => }/transforms.py (100%) create mode 100644 docker_config/pyproject.toml create mode 100644 docker_config/uv.lock diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 42ac6ebb..3b82afec 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -27,7 +27,7 @@ tasks = ["swarm_*"] executor { # client-side controller for training and logic and aggregation management - path = "controller.SwarmClientController" + path = "nvflare.app_common.ccwf.SwarmClientController" args { # train task must be implemented by Executor learn_task_name = "train" diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_server.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_server.conf index fe11655d..335b173e 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_server.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_server.conf @@ -13,13 +13,12 @@ workflows = [ { # server-side controller to manage job life cycle id = "swarm_controller" - path = "controller.SwarmServerController" + path = "nvflare.app_common.ccwf.SwarmServerController" args { # can also set aggregation clients and train clients, see class for all available args num_rounds = 20 start_task_timeout = 360000 progress_timeout = 360000 - end_workflow_timeout = 360000 configure_task_timeout = 360000 max_status_report_interval = 360000 } diff --git a/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_client.conf b/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_client.conf index ff18ef95..92c80ae5 100644 --- a/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_client.conf +++ b/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_client.conf @@ -27,7 +27,7 @@ tasks = ["swarm_*"] executor { # client-side controller for training and logic and aggregation management - path = "controller.SwarmClientController" + path = "nvflare.app_common.ccwf.SwarmClientController" args { # train task must be implemented by Executor learn_task_name = "train" diff --git a/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_server.conf b/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_server.conf index 0ece834f..309d239f 100644 --- a/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_server.conf +++ b/application/jobs/minimal_training_pytorch_cnn/app/config/config_fed_server.conf @@ -13,13 +13,13 @@ workflows = [ { # server-side controller to manage job life cycle id = "swarm_controller" - path = "controller.SwarmServerController" + path = "nvflare.app_common.ccwf.SwarmServerController" args { # can also set aggregation clients and train clients, see class for all available args num_rounds = 5 start_task_timeout = 36000 progress_timeout = 36000 - end_workflow_timeout = 36000 + #end_workflow_timeout = 36000 configure_task_timeout = 36000 max_status_report_interval = 36000 } diff --git a/application/jobs/minimal_training_pytorch_cnn/app/custom/main.py b/application/jobs/minimal_training_pytorch_cnn/app/custom/main.py index a8ac2752..340f3b97 100755 --- a/application/jobs/minimal_training_pytorch_cnn/app/custom/main.py +++ b/application/jobs/minimal_training_pytorch_cnn/app/custom/main.py @@ -2,14 +2,14 @@ import os -import nvflare.client.lightning as flare import nvflare.client as flare_util +import nvflare.client.lightning as flare import torch import minimal_training TRAINING_MODE = os.getenv("TRAINING_MODE") - +TRAINING_MODE = "swarm" if TRAINING_MODE == "swarm": flare_util.init() SITE_NAME=flare.get_site_name() @@ -34,7 +34,17 @@ def main(): logger.info(f"Site name: {SITE_NAME}") while flare.is_running(): + logger.info('Waiting for input model from server...') input_model = flare.receive() + + if input_model is not None: + logger.info("==== Swarm model received ====") + logger.info( + f"input_model.params.keys() = {list(input_model.params.keys())[:10]} ... total = {len(input_model.params)}") + logger.info( + f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") + + logger.info(f"Current round: {input_model.current_round}") minimal_training.validate_and_train(logger, data_module, model, trainer) diff --git a/application/jobs/stamp/app/config/config_fed_client.conf b/application/jobs/stamp/app/config/config_fed_client.conf index b15e14cb..5565b668 100644 --- a/application/jobs/stamp/app/config/config_fed_client.conf +++ b/application/jobs/stamp/app/config/config_fed_client.conf @@ -12,10 +12,10 @@ args { launcher_id = "launcher" pipe_id = "pipe" - last_result_transfer_timeout = 3600 - external_pre_init_timeout = 3600 - peer_read_timeout = 3600 - heartbeat_timeout = 3600 + last_result_transfer_timeout = 360000 + external_pre_init_timeout = 360000 + peer_read_timeout = 360000 + heartbeat_timeout = 360000 params_exchange_format = "pytorch" params_transfer_type = "DIFF" train_with_evaluation = true @@ -27,22 +27,22 @@ tasks = ["swarm_*"] executor { # client-side controller for training and logic and aggregation management - path = "controller.SwarmClientController" + path = "nvflare.app_common.ccwf.SwarmClientController" args { # train task must be implemented by Executor learn_task_name = "train" # how long to wait for current learn task before timing out the gathering - learn_task_timeout = 3600 - learn_task_abort_timeout = 3600 - learn_task_ack_timeout = 3600 - final_result_ack_timeout = 3600 + #learn_task_timeout = 360000 + #learn_task_abort_timeout = 360000 + #learn_task_ack_timeout = 360000 + #final_result_ack_timeout = 360000 # ids must map to corresponding components persistor_id = "persistor" aggregator_id = "aggregator" shareable_generator_id = "shareable_generator" min_responses_required = 2 - wait_time_after_min_resps_received = 3600 + #wait_time_after_min_resps_received = 360000 } } } @@ -84,7 +84,11 @@ model { path = "modeling.lightning_model.LitVisionTransformer" args { - + dim_model = 512 + dim_feedforward=2048 + n_heads=8 + n_layers=2 + dropout=0.25 } } } diff --git a/application/jobs/stamp/app/config/config_fed_server.conf b/application/jobs/stamp/app/config/config_fed_server.conf index c8a11577..b013ea37 100644 --- a/application/jobs/stamp/app/config/config_fed_server.conf +++ b/application/jobs/stamp/app/config/config_fed_server.conf @@ -13,15 +13,14 @@ workflows = [ { # server-side controller to manage job life cycle id = "swarm_controller" - path = "controller.SwarmServerController" + path = "nvflare.app_common.ccwf.SwarmServerController" args { # can also set aggregation clients and train clients, see class for all available args num_rounds = 20 - start_task_timeout = 3600 - progress_timeout = 3600 - end_workflow_timeout = 3600 - configure_task_timeout = 3600 - max_status_report_interval = 3600 + #start_task_timeout = 360000 + #progress_timeout = 360000 + #configure_task_timeout = 360000 + #max_status_report_interval = 360000 } } ] diff --git a/application/jobs/stamp/app/custom/modeling/__init__.py b/application/jobs/stamp/app/custom/__init__.py similarity index 100% rename from application/jobs/stamp/app/custom/modeling/__init__.py rename to application/jobs/stamp/app/custom/__init__.py diff --git a/application/jobs/stamp/app/custom/modeling/alibi.py b/application/jobs/stamp/app/custom/alibi.py similarity index 100% rename from application/jobs/stamp/app/custom/modeling/alibi.py rename to application/jobs/stamp/app/custom/alibi.py diff --git a/application/jobs/stamp/app/custom/modeling/config.py b/application/jobs/stamp/app/custom/config.py similarity index 97% rename from application/jobs/stamp/app/custom/modeling/config.py rename to application/jobs/stamp/app/custom/config.py index 74f7e429..09fb4086 100644 --- a/application/jobs/stamp/app/custom/modeling/config.py +++ b/application/jobs/stamp/app/custom/config.py @@ -3,7 +3,8 @@ import torch from pydantic import BaseModel, ConfigDict, Field -from stamp.types import PandasLabel + +from modeling.types import PandasLabel class TrainConfig(BaseModel): diff --git a/application/jobs/stamp/app/custom/modeling/crossval.py b/application/jobs/stamp/app/custom/crossval.py similarity index 95% rename from application/jobs/stamp/app/custom/modeling/crossval.py rename to application/jobs/stamp/app/custom/crossval.py index 4ff21ce7..8f5148a9 100644 --- a/application/jobs/stamp/app/custom/modeling/crossval.py +++ b/application/jobs/stamp/app/custom/crossval.py @@ -7,23 +7,24 @@ from lightning.pytorch.accelerators.accelerator import Accelerator from pydantic import BaseModel from sklearn.model_selection import StratifiedKFold -from stamp.modeling.data import ( + +from data import ( PatientData, filter_complete_patient_data_, patient_to_ground_truth_from_clini_table_, slide_to_patient_from_slide_table_, ) -from stamp.modeling.deploy import _predict, _to_prediction_df -from stamp.modeling.lightning_model import LitVisionTransformer -from stamp.modeling.train import setup_model_for_training, train_model_ -from stamp.modeling.transforms import VaryPrecisionTransform -from stamp.types import ( +from deploy import _predict, _to_prediction_df +from main import setup_model_for_training, train_model_ +from modeling.lightning_model import LitVisionTransformer +from modeling.types import ( Category, FeaturePath, GroundTruth, PandasLabel, PatientId, ) +from transforms import VaryPrecisionTransform __author__ = "Marko van Treeck" __copyright__ = "Copyright (C) 2024 Marko van Treeck" diff --git a/application/jobs/stamp/app/custom/modeling/data.py b/application/jobs/stamp/app/custom/data.py similarity index 99% rename from application/jobs/stamp/app/custom/modeling/data.py rename to application/jobs/stamp/app/custom/data.py index 10b157f4..f8d4482b 100755 --- a/application/jobs/stamp/app/custom/modeling/data.py +++ b/application/jobs/stamp/app/custom/data.py @@ -14,7 +14,10 @@ import torch from jaxtyping import Bool, Float from packaging.version import Version -from stamp.types import ( +from torch import Tensor +from torch.utils.data import DataLoader, Dataset + +from modeling.types import ( Bags, BagSize, BagSizes, @@ -30,8 +33,6 @@ SlideMPP, TilePixels, ) -from torch import Tensor -from torch.utils.data import DataLoader, Dataset _logger = logging.getLogger("stamp") diff --git a/application/jobs/stamp/app/custom/modeling/deploy.py b/application/jobs/stamp/app/custom/deploy.py similarity index 99% rename from application/jobs/stamp/app/custom/modeling/deploy.py rename to application/jobs/stamp/app/custom/deploy.py index f0cda653..235f7f8a 100644 --- a/application/jobs/stamp/app/custom/modeling/deploy.py +++ b/application/jobs/stamp/app/custom/deploy.py @@ -17,7 +17,8 @@ slide_to_patient_from_slide_table_, ) from stamp.modeling.lightning_model import LitVisionTransformer -from stamp.types import GroundTruth, PandasLabel, PatientId + +from modeling.types import GroundTruth, PandasLabel, PatientId __all__ = ["deploy_categorical_model_"] diff --git a/application/jobs/stamp/app/custom/main.py b/application/jobs/stamp/app/custom/main.py index df9e75fe..cf85e048 100644 --- a/application/jobs/stamp/app/custom/main.py +++ b/application/jobs/stamp/app/custom/main.py @@ -1,19 +1,347 @@ import logging -import os import sys + +logger = logging.getLogger("stamp") +logger.setLevel(logging.DEBUG) + +if not logger.hasHandlers(): + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s")) + handler.setLevel(logging.DEBUG) + logger.addHandler(handler) + +import shutil +from collections.abc import Callable, Mapping, Sequence from pathlib import Path +from typing import cast + +import lightning +import lightning.pytorch +import lightning.pytorch.accelerators +import lightning.pytorch.accelerators.accelerator +import torch +from lightning.pytorch.accelerators.accelerator import Accelerator +from lightning.pytorch.callbacks import ModelCheckpoint +from lightning.pytorch.loggers import CSVLogger +from sklearn.model_selection import train_test_split +from torch.utils.data.dataloader import DataLoader +import logging +import nvflare.client.lightning as flare +import nvflare.client as flare_util + +from data import ( + BagDataset, + PatientData, + dataloader_from_patient_data, + filter_complete_patient_data_, + patient_to_ground_truth_from_clini_table_, + slide_to_patient_from_slide_table_, +) +from modeling.lightning_model import ( + Bags, + BagSizes, + EncodedTargets, + LitVisionTransformer, +) +from transforms import VaryPrecisionTransform +from modeling.types import Category, CoordinatesBatch, GroundTruth, PandasLabel, PatientId + + +def train_categorical_model_( + *, + clini_table: Path, + slide_table: Path, + feature_dir: Path, + output_dir: Path, + patient_label: PandasLabel, + ground_truth_label: PandasLabel, + filename_label: PandasLabel, + categories: Sequence[Category] | None, + # Dataset and -loader parameters + bag_size: int, + num_workers: int, + # Training paramenters + batch_size: int, + max_epochs: int, + patience: int, + accelerator: str | Accelerator, + # Experimental features + use_vary_precision_transform: bool, + use_alibi: bool, +) -> None: + """Trains a model. + + Args: + clini_table: + An excel or csv file to read the clinical information from. + Must at least have the columns specified in the arguments + + `patient_label` (containing a unique patient ID) + and `ground_truth_label` (containing the ground truth to train for). + slide_table: + An excel or csv file to read the patient-slide associations from. + Must at least have the columns specified in the arguments + `patient_label` (containing the patient ID) + and `filename_label` + (containing a filename relative to `feature_dir` + in which some of the patient's features are stored). + feature_dir: + See `slide_table`. + output_dir: + Path into which to output the artifacts (trained model etc.) + generated during training. + patient_label: + See `clini_table`, `slide_table`. + ground_truth_label: + See `clini_table`. + filename_label: + See `slide_table`. + categories: + Categories of the ground truth. + Set to `None` to automatically infer. + """ + # Read and parse data from out clini and slide table + patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( + clini_table_path=clini_table, + ground_truth_label=ground_truth_label, + patient_label=patient_label, + ) + slide_to_patient = slide_to_patient_from_slide_table_( + slide_table_path=slide_table, + feature_dir=feature_dir, + patient_label=patient_label, + filename_label=filename_label, + ) + + # Clean data (remove slides without ground truth, missing features, etc.) + patient_to_data = filter_complete_patient_data_( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + drop_patients_with_missing_ground_truth=True, + ) + + # Train the model + model, train_dl, valid_dl = setup_model_for_training( + patient_to_data=patient_to_data, + categories=categories, + bag_size=bag_size, + batch_size=batch_size, + num_workers=num_workers, + ground_truth_label=ground_truth_label, + clini_table=clini_table, + slide_table=slide_table, + feature_dir=feature_dir, + train_transform=( + VaryPrecisionTransform(min_fraction_bits=1) + if use_vary_precision_transform + else None + ), + use_alibi=use_alibi, + ) + train_model_( + output_dir=output_dir, + model=model, + train_dl=train_dl, + valid_dl=valid_dl, + max_epochs=max_epochs, + patience=patience, + accelerator=accelerator, + ) + + +def train_model_( + *, + output_dir: Path, + model: LitVisionTransformer, + train_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + valid_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + max_epochs: int, + patience: int, + accelerator: str | Accelerator, +) -> LitVisionTransformer: + """Trains a model. + + Returns: + The model with the best validation loss during training. + """ + torch.set_float32_matmul_precision("high") + + model_checkpoint = ModelCheckpoint( + monitor="validation_loss", + mode="min", + filename="checkpoint-{epoch:02d}-{validation_loss:0.3f}", + ) + trainer = lightning.Trainer( + default_root_dir=output_dir, + callbacks=[ + # EarlyStopping(monitor="validation_loss", mode="min", patience=patience), + model_checkpoint, + ], + + max_epochs=max_epochs, + # FIXME The number of accelerators is currently fixed to one for the + # following reasons: + # 1. `trainer.predict()` does not return any predictions if used with + # the default strategy no multiple GPUs + # 2. `barspoon.model.SafeMulticlassAUROC` breaks on multiple GPUs + accelerator=accelerator, + devices=1, + gradient_clip_val=0.5, + logger=CSVLogger(save_dir=output_dir), + log_every_n_steps=len(train_dl), + # num_sanity_val_steps=0, + ) + flare_util.init() + SITE_NAME = flare.get_site_name() + flare.patch(trainer) # Patch trainer to enable swarm learning + logger.info(f"Site name: {SITE_NAME}") + + logger.info(" About to enter flare.is_running loop") + + while flare.is_running(): + logger.info("[DEBUG] waiting to receive swarm model (10s timeout)") + input_model = flare.receive(timeout=10) -from modeling.train import train_categorical_model_ + if input_model is not None: + logger.info("==== Swarm model received ====") + logger.info( + f"input_model.params.keys() = {list(input_model.params.keys())[:10]} ... total = {len(input_model.params)}") + logger.info( + f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") + try: + model.load_state_dict(input_model.params) + except Exception as e: + logger.error("load_state_dict failed:", exc_info=True) + raise -# Set up the logger -_logger = logging.getLogger("stamp") -_logger.setLevel(logging.DEBUG) -_formatter = logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s") + logger.info(f"[DEBUG] Got input model: {input_model}") + if input_model is None: + logger.info("[DEBUG] no swarm_start received in 10s") + else: + model.load_state_dict(input_model.params) -_stream_handler = logging.StreamHandler(sys.stderr) -_stream_handler.setLevel(logging.INFO) -_stream_handler.setFormatter(_formatter) -_logger.addHandler(_stream_handler) + logger.info("[DEBUG] received swarm_start:", input_model) + logger.info(f"Current round: {input_model.current_round}") + + trainer.fit( + model=model, + train_dataloaders=train_dl, + val_dataloaders=valid_dl, + ) + shutil.copy(model_checkpoint.best_model_path, output_dir / "model.ckpt") + + return LitVisionTransformer.load_from_checkpoint(model_checkpoint.best_model_path) + + +def setup_model_for_training( + *, + patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], + categories: Sequence[Category] | None, + bag_size: int, + batch_size: int, + num_workers: int, + train_transform: Callable[[torch.Tensor], torch.Tensor] | None, + use_alibi: bool, + # Metadata, has no effect on model training + ground_truth_label: PandasLabel, + clini_table: Path, + slide_table: Path, + feature_dir: Path, +) -> tuple[ + LitVisionTransformer, + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], + DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], +]: + """Creates a model and dataloaders for training""" + + # Do a stratified train-validation split + ground_truths = [ + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + ] + + if len(ground_truths) != len(patient_to_data): + raise ValueError( + "patient_to_data must have a ground truth defined for all targets!" + ) + + train_patients, valid_patients = cast( + tuple[Sequence[PatientId], Sequence[PatientId]], + train_test_split( + list(patient_to_data), stratify=ground_truths, shuffle=True, random_state=0 + ), + ) + + train_dl, train_categories = dataloader_from_patient_data( + patient_data=[patient_to_data[patient] for patient in train_patients], + categories=categories, + bag_size=bag_size, + batch_size=batch_size, + shuffle=True, + num_workers=num_workers, + transform=train_transform, + ) + del categories # Let's not accidentally reuse the original categories + valid_dl, _ = dataloader_from_patient_data( + patient_data=[patient_to_data[patient] for patient in valid_patients], + bag_size=None, # Use all the patient data for validation + categories=train_categories, + batch_size=1, + shuffle=False, + num_workers=num_workers, + transform=None, + ) + if overlap := set(train_patients) & set(valid_patients): + raise RuntimeError( + f"unreachable: unexpected overlap between training and validation set: {overlap}" + ) + + # Sample one bag to infer the input dimensions of the model + bags, coords, bag_sizes, targets = cast( + tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], next(iter(train_dl)) + ) + _, _, dim_feats = bags.shape + + # Weigh classes inversely to their occurrence + category_counts = cast(BagDataset, train_dl.dataset).ground_truths.sum(dim=0) + cat_ratio_reciprocal = category_counts.sum() / category_counts + category_weights = cat_ratio_reciprocal / cat_ratio_reciprocal.sum() + + if len(train_categories) <= 1: + raise ValueError(f"not enough categories to train on: {train_categories}") + elif any(category_counts < 16): + underpopulated_categories = { + category: count + for category, count in zip(train_categories, category_counts, strict=True) + if count < 16 + } + _logger.warning( + f"Some categories do not have enough samples to meaningfully train a model: {underpopulated_categories}. " + "You may want to consider removing these categories; the model will likely overfit on the few samples available." + ) + + # Train the model + + model = LitVisionTransformer(dim_model=512, + dim_feedforward=2048, + n_heads=8, + n_layers=2, + dropout=0.25) + model.set( + categories=train_categories, + category_weights=category_weights, + dim_input=dim_feats, + use_alibi=use_alibi, + ground_truth_label=ground_truth_label, + train_patients=train_patients, + valid_patients=valid_patients, + clini_table=clini_table, + slide_table=slide_table, + feature_dir=feature_dir, + ) + + return model, train_dl, valid_dl def _add_file_handle_(logger: logging.Logger, *, output_dir: Path) -> None: @@ -29,9 +357,11 @@ def _add_file_handle_(logger: logging.Logger, *, output_dir: Path) -> None: def main(): + '''' output_dir = os.getenv("TRAINING_OUTPUT_DIR") - _add_file_handle_(_logger, output_dir=Path(output_dir)) - _logger.info("Using training configuration from environment variables.") + print('output_dir:', output_dir) + #_add_file_handle_(_logger, output_dir=Path(output_dir)) + #_logger.info("Using training configuration from environment variables.") train_categorical_model_( output_dir=Path(output_dir), @@ -54,7 +384,32 @@ def main(): use_vary_precision_transform=os.getenv("USE_VARY_PRECISION_TRANSFORM", "False").lower() == "true", use_alibi=os.getenv("USE_ALIBI", "False").lower() == "true", ) +''' + output_dir = "/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/STAMP_crossval_new" + logger.info('output_dir:', output_dir) + # _add_file_handle_(_logger, output_dir=Path(output_dir)) + #_logger.info("Using training configuration from environment variables.") - + train_categorical_model_( + output_dir=Path(output_dir), + clini_table=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/TCGA-CRC-DX_clini.xlsx"), + slide_table=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/TCGA-CRC-DX_slide_h5.csv"), + feature_dir=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/uni2-02627079"), + patient_label="PATIENT", + ground_truth_label="isMSIH", + filename_label="FILENAME", + categories=None, + # Dataset and loader parameters + bag_size=512, + num_workers=15, + # Training parameters + batch_size=64, + max_epochs=1, # 64 + patience=16, + accelerator="gpu", + # Experimental features + use_vary_precision_transform='true', + use_alibi='false', + ) if __name__ == "__main__": main() diff --git a/application/jobs/stamp/app/custom/modeling/lightning_model.py b/application/jobs/stamp/app/custom/modeling/lightning_model.py index 5a98018b..97089551 100644 --- a/application/jobs/stamp/app/custom/modeling/lightning_model.py +++ b/application/jobs/stamp/app/custom/modeling/lightning_model.py @@ -10,7 +10,6 @@ from torch import Tensor, nn, optim from torchmetrics.classification import MulticlassAUROC -from vision_transformer import VisionTransformer from .types import ( Bags, BagSizes, @@ -20,6 +19,7 @@ PandasLabel, PatientId, ) +from .vision_transformer import VisionTransformer Loss: TypeAlias = Float[Tensor, ""] @@ -58,79 +58,85 @@ class LitVisionTransformer(lightning.LightningModule): def __init__( self, *, - categories: Sequence[Category], - category_weights: Float[Tensor, "category_weight"], # noqa: F821 - dim_input: int, dim_model: int, dim_feedforward: int, n_heads: int, n_layers: int, dropout: float, - # Experimental features - # TODO remove default values for stamp 3; they're only here for backwards compatibility - use_alibi: bool = False, - # Metadata used by other parts of stamp, but not by the model itself - ground_truth_label: PandasLabel, - train_patients: Iterable[PatientId], - valid_patients: Iterable[PatientId], - # stamp_version: Version = Version(stamp.__version__), - # Other metadata **metadata, ) -> None: super().__init__() + self.dim_model = dim_model + self.dim_feedforward = dim_feedforward + self.n_heads = n_heads + self.n_layers = n_layers + self.dropout = dropout + self.metadata = metadata + + # Parameters to be set later + self.categories = None + self.category_weights = None + self.ground_truth_label = None + self.train_patients = None + self.valid_patients = None + self.use_alibi = False + self.dim_input = None + + # Optional external metadata + self.clini_table = None + self.slide_table = None + self.feature_dir = None + + self.vision_transformer = None + self.valid_auroc = None + + def set( + self, + *, + categories: Sequence[Category], + category_weights: Float[Tensor, "category_weight"], + dim_input: int, + use_alibi: bool = False, + ground_truth_label: PandasLabel, + train_patients: Iterable[PatientId], + valid_patients: Iterable[PatientId], + clini_table=None, + slide_table=None, + feature_dir=None, + ): if len(categories) != len(category_weights): - raise ValueError( - "the number of category weights has to match the number of categories!" - ) + raise ValueError("the number of category weights must match the number of categories!") + self.categories = np.array(categories) + self.category_weights = category_weights + self.dim_input = dim_input + self.use_alibi = use_alibi + self.ground_truth_label = ground_truth_label + self.train_patients = train_patients + self.valid_patients = valid_patients + + self.clini_table = clini_table + self.slide_table = slide_table + self.feature_dir = feature_dir + # Initialize model and AUROC after all info is available self.vision_transformer = VisionTransformer( dim_output=len(categories), dim_input=dim_input, - dim_model=dim_model, - n_layers=n_layers, - n_heads=n_heads, - dim_feedforward=dim_feedforward, - dropout=dropout, + dim_model=self.dim_model, + n_layers=self.n_layers, + n_heads=self.n_heads, + dim_feedforward=self.dim_feedforward, + dropout=self.dropout, use_alibi=use_alibi, ) self.class_weights = category_weights self.valid_auroc = MulticlassAUROC(len(categories)) + self.save_hyperparameters(ignore=["vision_transformer", "valid_auroc"]) - # Used during deployment - self.ground_truth_label = ground_truth_label - self.categories = np.array(categories) - self.train_patients = train_patients - self.valid_patients = valid_patients - - _ = metadata # unused, but saved in model - - # Check if version is compatible. - # This should only happen when the model is loaded, - # otherwise the default value will make these checks pass. - ''' - if stamp_version < Version("2.0.0.dev8"): - # Update this as we change our model in incompatible ways! - raise ValueError( - f"model has been built with stamp version {stamp_version} " - f"which is incompatible with the current version." - ) - - elif stamp_version > Version(stamp.__version__): - # Let's be strict with models "from the future", - # better fail deadly than have broken results. - raise ValueError( - "model has been built with a stamp version newer than the installed one " - f"({stamp_version} > {stamp.__version__}). " - "Please upgrade stamp to a compatible version." - ) - ''' - self.save_hyperparameters() - - def forward( - self, - bags: Bags, - ) -> Float[Tensor, "batch logit"]: + def forward(self, bags: Bags) -> Float[Tensor, "batch logit"]: + if self.vision_transformer is None: + raise RuntimeError("Model not initialized. Call `.set()` first.") return self.vision_transformer(bags) def _step( diff --git a/application/jobs/stamp/app/custom/modeling/train.py b/application/jobs/stamp/app/custom/modeling/train.py deleted file mode 100644 index 0d71f0ca..00000000 --- a/application/jobs/stamp/app/custom/modeling/train.py +++ /dev/null @@ -1,319 +0,0 @@ -import logging -import shutil -from collections.abc import Callable, Mapping, Sequence -from pathlib import Path -from typing import cast - -import lightning -import lightning.pytorch -import lightning.pytorch.accelerators -import lightning.pytorch.accelerators.accelerator -import torch -from lightning.pytorch.accelerators.accelerator import Accelerator -from lightning.pytorch.callbacks import EarlyStopping, ModelCheckpoint -from lightning.pytorch.loggers import CSVLogger -from sklearn.model_selection import train_test_split -from torch.utils.data.dataloader import DataLoader - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -import nvflare.client.lightning as flare -import nvflare.client as flare_util - -flare_util.init() -SITE_NAME = flare.get_site_name() - -from data import ( - BagDataset, - PatientData, - dataloader_from_patient_data, - filter_complete_patient_data_, - patient_to_ground_truth_from_clini_table_, - slide_to_patient_from_slide_table_, -) -from lightning_model import ( - Bags, - BagSizes, - EncodedTargets, - LitVisionTransformer, -) -from stamp.modeling.transforms import VaryPrecisionTransform -from stamp.types import Category, CoordinatesBatch, GroundTruth, PandasLabel, PatientId - -__author__ = "Marko van Treeck" -__copyright__ = "Copyright (C) 2024 Marko van Treeck" -__license__ = "MIT" - -_logger = logging.getLogger("stamp") - - -def train_categorical_model_( - *, - clini_table: Path, - slide_table: Path, - feature_dir: Path, - output_dir: Path, - patient_label: PandasLabel, - ground_truth_label: PandasLabel, - filename_label: PandasLabel, - categories: Sequence[Category] | None, - # Dataset and -loader parameters - bag_size: int, - num_workers: int, - # Training paramenters - batch_size: int, - max_epochs: int, - patience: int, - accelerator: str | Accelerator, - # Experimental features - use_vary_precision_transform: bool, - use_alibi: bool, -) -> None: - """Trains a model. - - Args: - clini_table: - An excel or csv file to read the clinical information from. - Must at least have the columns specified in the arguments - - `patient_label` (containing a unique patient ID) - and `ground_truth_label` (containing the ground truth to train for). - slide_table: - An excel or csv file to read the patient-slide associations from. - Must at least have the columns specified in the arguments - `patient_label` (containing the patient ID) - and `filename_label` - (containing a filename relative to `feature_dir` - in which some of the patient's features are stored). - feature_dir: - See `slide_table`. - output_dir: - Path into which to output the artifacts (trained model etc.) - generated during training. - patient_label: - See `clini_table`, `slide_table`. - ground_truth_label: - See `clini_table`. - filename_label: - See `slide_table`. - categories: - Categories of the ground truth. - Set to `None` to automatically infer. - """ - # Read and parse data from out clini and slide table - patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( - clini_table_path=clini_table, - ground_truth_label=ground_truth_label, - patient_label=patient_label, - ) - slide_to_patient = slide_to_patient_from_slide_table_( - slide_table_path=slide_table, - feature_dir=feature_dir, - patient_label=patient_label, - filename_label=filename_label, - ) - - # Clean data (remove slides without ground truth, missing features, etc.) - patient_to_data = filter_complete_patient_data_( - patient_to_ground_truth=patient_to_ground_truth, - slide_to_patient=slide_to_patient, - drop_patients_with_missing_ground_truth=True, - ) - - # Train the model - model, train_dl, valid_dl = setup_model_for_training( - patient_to_data=patient_to_data, - categories=categories, - bag_size=bag_size, - batch_size=batch_size, - num_workers=num_workers, - ground_truth_label=ground_truth_label, - clini_table=clini_table, - slide_table=slide_table, - feature_dir=feature_dir, - train_transform=( - VaryPrecisionTransform(min_fraction_bits=1) - if use_vary_precision_transform - else None - ), - use_alibi=use_alibi, - ) - train_model_( - output_dir=output_dir, - model=model, - train_dl=train_dl, - valid_dl=valid_dl, - max_epochs=max_epochs, - patience=patience, - accelerator=accelerator, - ) - - -def train_model_( - *, - output_dir: Path, - model: LitVisionTransformer, - train_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], - valid_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], - max_epochs: int, - patience: int, - accelerator: str | Accelerator, -) -> LitVisionTransformer: - """Trains a model. - - Returns: - The model with the best validation loss during training. - """ - torch.set_float32_matmul_precision("high") - - model_checkpoint = ModelCheckpoint( - monitor="validation_loss", - mode="min", - filename="checkpoint-{epoch:02d}-{validation_loss:0.3f}", - ) - trainer = lightning.Trainer( - default_root_dir=output_dir, - callbacks=[ - EarlyStopping(monitor="validation_loss", mode="min", patience=patience), - model_checkpoint, - ], - - max_epochs=max_epochs, - # FIXME The number of accelerators is currently fixed to one for the - # following reasons: - # 1. `trainer.predict()` does not return any predictions if used with - # the default strategy no multiple GPUs - # 2. `barspoon.model.SafeMulticlassAUROC` breaks on multiple GPUs - accelerator=accelerator, - devices=1, - gradient_clip_val=0.5, - logger=CSVLogger(save_dir=output_dir), - log_every_n_steps=len(train_dl), - ) - - flare.patch(trainer) # Patch trainer to enable swarm learning - logger.info(f"Site name: {SITE_NAME}") - - while flare.is_running(): - input_model = flare.receive() - logger.info(f"Current round: {input_model.current_round}") - trainer.fit( - model=model, - train_dataloaders=train_dl, - val_dataloaders=valid_dl, - ) - shutil.copy(model_checkpoint.best_model_path, output_dir / "model.ckpt") - - return LitVisionTransformer.load_from_checkpoint(model_checkpoint.best_model_path) - - -def setup_model_for_training( - *, - patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], - categories: Sequence[Category] | None, - bag_size: int, - batch_size: int, - num_workers: int, - train_transform: Callable[[torch.Tensor], torch.Tensor] | None, - use_alibi: bool, - # Metadata, has no effect on model training - ground_truth_label: PandasLabel, - clini_table: Path, - slide_table: Path, - feature_dir: Path, -) -> tuple[ - LitVisionTransformer, - DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], - DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], -]: - """Creates a model and dataloaders for training""" - - # Do a stratified train-validation split - ground_truths = [ - patient_data.ground_truth - for patient_data in patient_to_data.values() - if patient_data.ground_truth is not None - ] - - if len(ground_truths) != len(patient_to_data): - raise ValueError( - "patient_to_data must have a ground truth defined for all targets!" - ) - - train_patients, valid_patients = cast( - tuple[Sequence[PatientId], Sequence[PatientId]], - train_test_split( - list(patient_to_data), stratify=ground_truths, shuffle=True, random_state=0 - ), - ) - - train_dl, train_categories = dataloader_from_patient_data( - patient_data=[patient_to_data[patient] for patient in train_patients], - categories=categories, - bag_size=bag_size, - batch_size=batch_size, - shuffle=True, - num_workers=num_workers, - transform=train_transform, - ) - del categories # Let's not accidentally reuse the original categories - valid_dl, _ = dataloader_from_patient_data( - patient_data=[patient_to_data[patient] for patient in valid_patients], - bag_size=None, # Use all the patient data for validation - categories=train_categories, - batch_size=1, - shuffle=False, - num_workers=num_workers, - transform=None, - ) - if overlap := set(train_patients) & set(valid_patients): - raise RuntimeError( - f"unreachable: unexpected overlap between training and validation set: {overlap}" - ) - - # Sample one bag to infer the input dimensions of the model - bags, coords, bag_sizes, targets = cast( - tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], next(iter(train_dl)) - ) - _, _, dim_feats = bags.shape - - # Weigh classes inversely to their occurrence - category_counts = cast(BagDataset, train_dl.dataset).ground_truths.sum(dim=0) - cat_ratio_reciprocal = category_counts.sum() / category_counts - category_weights = cat_ratio_reciprocal / cat_ratio_reciprocal.sum() - - if len(train_categories) <= 1: - raise ValueError(f"not enough categories to train on: {train_categories}") - elif any(category_counts < 16): - underpopulated_categories = { - category: count - for category, count in zip(train_categories, category_counts, strict=True) - if count < 16 - } - _logger.warning( - f"Some categories do not have enough samples to meaningfully train a model: {underpopulated_categories}. " - "You may want to consider removing these categories; the model will likely overfit on the few samples available." - ) - - # Train the model - model = LitVisionTransformer( - categories=train_categories, - category_weights=category_weights, - dim_input=dim_feats, - dim_model=512, - dim_feedforward=2048, - n_heads=8, - n_layers=2, - dropout=0.25, - use_alibi=use_alibi, - # Metadata, has no effect on model training - ground_truth_label=ground_truth_label, - train_patients=train_patients, - valid_patients=valid_patients, - clini_table=clini_table, - slide_table=slide_table, - feature_dir=feature_dir, - ) - - return model, train_dl, valid_dl diff --git a/application/jobs/stamp/app/custom/modeling/vision_transformer.py b/application/jobs/stamp/app/custom/modeling/vision_transformer.py index a2d71c0b..3a710960 100755 --- a/application/jobs/stamp/app/custom/modeling/vision_transformer.py +++ b/application/jobs/stamp/app/custom/modeling/vision_transformer.py @@ -6,10 +6,10 @@ from typing import assert_never, cast import torch +from alibi import MultiHeadALiBi from beartype import beartype from einops import repeat from jaxtyping import Bool, Float, jaxtyped -from stamp.modeling.alibi import MultiHeadALiBi from torch import Tensor, nn diff --git a/application/jobs/stamp/app/custom/modeling/transforms.py b/application/jobs/stamp/app/custom/transforms.py similarity index 100% rename from application/jobs/stamp/app/custom/modeling/transforms.py rename to application/jobs/stamp/app/custom/transforms.py diff --git a/application/provision/project_Odelia_allsites.yml b/application/provision/project_Odelia_allsites.yml index 530b356d..8f1b1531 100644 --- a/application/provision/project_Odelia_allsites.yml +++ b/application/provision/project_Odelia_allsites.yml @@ -1,5 +1,5 @@ api_version: 3 -name: odelia___REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_STARTUP_KITS___allsites_test +name: odelia_1.0-dev.250718.ebb3b10_allsites_test description: Odelia TUD server all collaborators clients on Odelia challenge dataset provision http based yaml file participants: @@ -77,7 +77,7 @@ builders: # app_validator: PATH_TO_YOUR_OWN_APP_VALIDATOR # when docker_image is set to a docker image name, docker.sh will be generated on server/client/admin - docker_image: jefftud/odelia:__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_STARTUP_KITS__ + docker_image: jefftud/odelia:1.0-dev.250718.ebb3b10 # download_job_url is set to http://download.server.com/ as default in fed_server.json. You can override this # to different url. diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index b55d9692..f87b2429 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -3,10 +3,6 @@ set -e # make sure we are building from a state without local changes -if ! git diff --quiet || ! git diff --staged --quiet ; then - echo "Local changes exist, aborting" - exit 1 -fi DOCKER_BUILD_ARGS="--no-cache --progress=plain" DOCKERFILE_SUFFIX="ODELIA" diff --git a/docker_config/Dockerfile_stamp b/docker_config/Dockerfile_stamp index 24e5734e..82dd9ed1 100644 --- a/docker_config/Dockerfile_stamp +++ b/docker_config/Dockerfile_stamp @@ -1,58 +1,53 @@ -# Base image with Python 3.11 (CUDA-enabled PyTorch image) -ARG PYTORCH_IMAGE=pytorch/pytorch:2.2.2-cuda12.1-cudnn8-runtime +# ----------- Base Image with Python 3.11 + CUDA 12.1 ----------- +ARG PYTORCH_IMAGE=pytorch/pytorch:2.2.2-cuda12.1-cudnn8-devel FROM ${PYTORCH_IMAGE} -# ------------------ Metadata ------------------ -ARG NVF_VERSION=2.4.1 -ENV NVF_BRANCH=${NVF_VERSION} -ENV PYTHON_VERSION=3.11 -ENV DEBIAN_FRONTEND=noninteractive -ENV PATH="/workspace/STAMP/.venv/bin:$PATH" -ENV HF_HOME=/workspace/.hf_cache +# ----------- Metadata & Environment Variables ----------- +ENV DEBIAN_FRONTEND=noninteractive \ + CUDA_HOME=/usr/local/cuda \ + HF_HOME=/workspace/.hf_cache \ + PATH="/workspace/STAMP/.venv/bin:$PATH" -# ------------------ System Dependencies ------------------ +# ----------- System Dependencies ----------- RUN apt-get update && apt-get install -y --no-install-recommends \ + python3.11 python3.11-venv python3.11-dev python3-pip \ git curl wget unzip ca-certificates build-essential \ - libglib2.0-dev libgl1 libglx-mesa0 libglib2.0-0 \ - openslide-tools libgl1-mesa-glx \ - python3.11 python3.11-venv python3.11-dev \ - && rm -rf /var/lib/apt/lists/* + openslide-tools libgl1 libglx-mesa0 libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* -# ------------------ Set Default Python ------------------ -RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.11 1 && \ - update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 && \ - update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 +# ----------- Install uv ----------- +RUN pip install uv -# ------------------ Install uv ------------------ -RUN curl -LsSf https://astral.sh/uv/install.sh | sh -s -- --yes - -# ------------------ NVFlare Installation ------------------ +# ----------- Clone STAMP and Install via uv.lock ----------- WORKDIR /workspace/ -COPY ./MediSwarm/docker_config/NVFlare /workspace/nvflare -COPY ./MediSwarm/docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ -RUN python -m pip install /workspace/nvflare && rm -rf /workspace/nvflare - -# ------------------ MediSwarm Controller Installation ------------------ -COPY ./MediSwarm/controller /workspace/controller -RUN python -m pip install /workspace/controller && rm -rf /workspace/controller +RUN git clone https://github.com/KatherLab/STAMP.git +COPY docker_config/pyproject.toml docker_config/uv.lock /workspace/STAMP/ -# ------------------ STAMP Installation ------------------ -RUN git clone https://github.com/KatherLab/STAMP.git /workspace/STAMP WORKDIR /workspace/STAMP -RUN /root/.cargo/bin/uv venv && \ - /root/.cargo/bin/uv pip install --upgrade pip && \ - /root/.cargo/bin/uv sync --all-extras +RUN uv venv && uv pip install --upgrade pip && uv sync --all-extras -# ------------------ Expose stamp as CLI globally ------------------ +# ----------- Expose STAMP CLI globally ----------- RUN ln -s /workspace/STAMP/.venv/bin/stamp /usr/local/bin/stamp -# ------------------ HuggingFace CLI ------------------ -RUN /workspace/STAMP/.venv/bin/pip install huggingface_hub && \ - /workspace/STAMP/.venv/bin/huggingface-cli login --help || true +# ----------- HuggingFace CLI (Optional) ----------- +RUN huggingface-cli login --help || true + +# ----------- Install NVFlare ----------- +COPY ./docker_config/NVFlare /workspace/nvflare +COPY ./docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ +RUN /usr/bin/python3.11 -m pip install /workspace/nvflare && rm -rf /workspace/nvflare -# ------------------ MediSwarm Source + Link ------------------ -COPY ./MediSwarm /MediSwarm +# ----------- Install MediSwarm Controller ----------- +COPY ./controller /workspace/controller +RUN /usr/bin/python3.11 -m pip install /workspace/controller && rm -rf /workspace/controller + +# ----------- Copy MediSwarm Source and Link ----------- +#COPY ./MediSwarm /MediSwarm originally +COPY ./ /MediSwarm RUN mkdir -p /fl_admin/transfer && ln -s /MediSwarm /fl_admin/transfer/MediSwarm -# ------------------ Default Workdir ------------------ +# ----------- Set Default Workdir ----------- WORKDIR /workspace/ + +#docker build -f docker_config/Dockerfile_stamp -t stamp-image . +#docker run --rm -it stamp-image:latest-image bash \ No newline at end of file diff --git a/docker_config/pyproject.toml b/docker_config/pyproject.toml new file mode 100644 index 00000000..e4992f44 --- /dev/null +++ b/docker_config/pyproject.toml @@ -0,0 +1,142 @@ +[project] +name = "stamp" +version = "2.2.0" +authors = [ + { name = "Omar El Nahhas", email = "omar.el_nahhas@tu-dresden.de" }, + { name = "Marko van Treeck", email = "markovantreeck@gmail.com" }, + { name = "Georg Wölflein", email = "georgw7777@gmail.com" }, + { name = "Tim Lenz", email = "tim.lenz@tu-dresden.de" }, + { name = "Laura Žigutytė", email = "laura.zigutyte@tu-dresden.de" }, + { name = "Cornelius Kummer", email = "cornelius.kummer@tu-dresden.de" }, + { name = "Juan Pablo Ricapito", email = "juan_pablo.ricapito@tu-dresden.de" } +] +description = "A protocol for Solid Tumor Associative Modeling in Pathology" +readme = "README.md" +requires-python = ">=3.11" + +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +dependencies = [ + "beartype>=0.19.0", + "einops>=0.8.0", + "h5py>=3.12.1", + "jaxtyping>=0.2.36", + "lightning>=2.4.0", + "matplotlib>=3.9.2", + "numpy>=2.2.2", + "opencv-python>=4.10.0.84", + "openpyxl>=3.1.5", + "openslide-bin>=4.0.0.6", + "openslide-python>=1.4.1", + "packaging>=24.2", + "pandas>=2.2.3", + "pillow>=11.1.0", + "pydantic>=2.10.3", + "pyyaml>=6.0.2", + "scikit-learn>=1.5.2", + "scipy>=1.15.1", + "torch>=2.5.1", + "torchmetrics>=1.6.0", + "torchvision>=0.20.1", + "tqdm>=4.66.6", + "timm>=0.9.11", +] + +[project.optional-dependencies] +conch = [ + "huggingface-hub>=0.26.2", + "conch @ git+https://github.com/Mahmoodlab/CONCH.git@02d6ac59cc20874bff0f581de258c2b257f69a84", +] +conch1_5 = [ + "transformers>=4.45.2", + "einops-exts==0.0.4", +] +ctranspath = [ + "gdown>=5.2.0", +] +chief_ctranspath = [ + "gdown>=5.2.0", + "torch>=2.0.0" +] +gigapath = [ + "gigapath @ git+https://github.com/EzicStar/prov-gigapath.git@d4cf55321df37aaf867e24a31c61bcf490a296eb" +] +uni = [ + "huggingface-hub>=0.26.2", + "uni @ git+https://github.com/mahmoodlab/UNI.git", +] +virchow2 = [ + "huggingface-hub>=0.27.1", + "torch>=2.0.0", +] +cobra = [ + "jinja2>=3.1.4", + "cobra @ git+https://github.com/KatherLab/COBRA.git@f1a576e1133330ffc2d1df6ee110701921c7b7c9", +] +prism = [ + "sacremoses==0.1.1", + "environs==11.0.0", +] +madeleine = [ + "madeleine @ git+https://github.com/mahmoodlab/MADELEINE.git@de7c85acc2bdad352e6df8eee5694f8b6f288012" +] +musk = [ + "musk @ git+https://github.com/lilab-stanford/MUSK.git@e1699c27687f44bbf6d4adfcbb2abe89795d347f", +] +plip = [ + "transformers>=4.45.2" +] + +# Blanket target +all = ["stamp[conch,ctranspath,uni,virchow2,chief_ctranspath,conch1_5,prism,madeleine,musk,plip]"] + +[project.scripts] +"stamp" = "stamp.__main__:main" + +[project.urls] +"Homepage" = "https://github.com/KatherLab/STAMP" +"Bug Tracker" = "https://github.com/KatherLab/STAMP/issues" + +[dependency-groups] +dev = [ + "huggingface-hub>=0.27.1", + "ipykernel>=6.29.5", + "pyright>=1.1.389,!=1.1.391", + "pytest>=8.3.4", + "ruff>=0.8.1", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +# To allow referencing git repos in dependencies +allow-direct-references = true + +[tool.pytest.ini_options] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", +] + +[tool.ruff] +lint.ignore = ["F722"] # https://docs.kidger.site/jaxtyping/faq/#flake8-or-ruff-are-throwing-an-error + +[[tool.uv.dependency-metadata]] +name = "uni" +version = "v0.1.0" +requires-dist = [ + "torch>=2.0.1", + "torchvision", + "timm>=0.9.8", + "numpy", + "pandas", + "scikit-learn", + "tqdm", + "transformers", + "xformers; sys_platform != 'darwin'" # xformers is not supported on macOS +] \ No newline at end of file diff --git a/docker_config/uv.lock b/docker_config/uv.lock new file mode 100644 index 00000000..d29d37c7 --- /dev/null +++ b/docker_config/uv.lock @@ -0,0 +1,3641 @@ +version = 1 +requires-python = ">=3.11" +resolution-markers = [ + "python_version < '0'", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux'", +] + +[manifest] + +[[manifest.dependency-metadata]] +name = "uni" +version = "0.1.0" +requires-dist = ["torch>=2.0.1", "torchvision", "timm>=0.9.8", "numpy", "pandas", "scikit-learn", "tqdm", "transformers", "xformers ; sys_platform != 'darwin'"] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.12.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/28/2d96dffe4deb40faa7f5615b4aa96c87528e65837d8cb5385da4aecf1c07/aiohttp-3.12.6.tar.gz", hash = "sha256:37b1c6034a1e14764adad1829cd710543b1699d7985e1d336f0aa52a2dd76ba9", size = 7784449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/f0/313bd623a40638ed65eddd930fbee3a81efd9c87441ea117067beb66b7e8/aiohttp-3.12.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed4db015494a6d0acaadce035531f9fb321afab2075a4b348811e4f7795e87e6", size = 702633 }, + { url = "https://files.pythonhosted.org/packages/07/59/cd70b7798b5f6c13c65a692dbbbeacf4c085a9ce05a34363c4413384d895/aiohttp-3.12.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59e19517abef2af49cff79b8a863497036ff401051c79d6a3b6149a48213a7be", size = 474917 }, + { url = "https://files.pythonhosted.org/packages/74/89/fe980184d1313669f731d7f32ce824a3ee1af50b4fe83fe723fcb56ca425/aiohttp-3.12.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d557918fefb29884335e1a257df6c961f35ba1caf8eddaabad762b3436cf87ff", size = 463178 }, + { url = "https://files.pythonhosted.org/packages/cb/2a/abe1c72f9b6959b5459f8b99bcdb661a7c2de7901b0c541c26996dd70006/aiohttp-3.12.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e4fb0d7f221c36ed8469c1d2d9a2bb6a27b543cf90aa46ca701f63fb83dd7ed", size = 1733367 }, + { url = "https://files.pythonhosted.org/packages/61/e2/992378c6b1e1b4beed78044ce5b70b749c269cc6e42472fc878339f90f4e/aiohttp-3.12.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:deddf6b1c83ce518a156b7597a0d7a1a7ec5c1d2c973ba3f1a23f18fa2b7d65e", size = 1682037 }, + { url = "https://files.pythonhosted.org/packages/64/11/620be3270f913c8d5ae4e9a88fa5ce80b7a7848324506da8b2916053f5f0/aiohttp-3.12.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eefd98dd043c33c45123c56a79c6c39acb628304337c90f16f33569cc3aa4ba6", size = 1780829 }, + { url = "https://files.pythonhosted.org/packages/c7/92/475e7c1781aa0907671b66355ae414f0d479f0bcf0929469ea44678b72c1/aiohttp-3.12.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efbbde2297e4ab10d187103aba9b565277c85ac7d24d98cae201c033ce885504", size = 1819873 }, + { url = "https://files.pythonhosted.org/packages/70/04/9f4ad736af830d68dbd376db17f7294c648af393ec24a148bcd5cc2112c1/aiohttp-3.12.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a74a566872f41247774980334e5b0309dac11b402e188bde6db8a57de4506cd", size = 1722335 }, + { url = "https://files.pythonhosted.org/packages/76/da/5ec4f8deacc4107bf95590d285879f4054d62ff9300a96d8abb4b1143384/aiohttp-3.12.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24d19cbd1d21d207ee855500d2033f1852b4d2113a741246ff62eb16a3921306", size = 1659410 }, + { url = "https://files.pythonhosted.org/packages/85/38/30df9eedcfe28265f1efb1bfe9b19cc94c5a37aea5d2cd246dde8c8080e3/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:86fb0a5762f936606dcab1ca248f5053587a598ed44825f4744ce3c53ae9a2e9", size = 1707684 }, + { url = "https://files.pythonhosted.org/packages/e9/4c/c8d375fa9b6ea5c381747e5e56bc0249d33bb12c5d7171d4c1b4fcae02b4/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d7ff55a38fc9851fa5cff41b30605534dfe4d57d02f79447abfed01499fe31d3", size = 1702843 }, + { url = "https://files.pythonhosted.org/packages/1e/3c/73b6e184df80ebc5ec07c6e9d398713c40091f6cf4a5e75eb93972ee35d6/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:545f89c389a47bac024655b5676658f35f80b0d007e4c3c7ff865d9aa3bf343a", size = 1683031 }, + { url = "https://files.pythonhosted.org/packages/9d/5a/827ca828af26ceeda69459c2848fc58e76adbbea0ad9994429ae885d1a33/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:25dac87ee297e2b5826ce8e96c7615ebe7a1613856b1614a207e3376b776021b", size = 1776629 }, + { url = "https://files.pythonhosted.org/packages/d6/07/8b9081655c08807fa49134b209eddc823e7501bbcf6b044f48a01f30a504/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c1d8a4a5a7e28d8b9ec815ffecca8712b71130a4eee1c5b45e9f2cc4975f3f7c", size = 1797092 }, + { url = "https://files.pythonhosted.org/packages/5d/18/c761b934543512077c3de8d8f383bcafd94ada30c83273ea6ceeeda8aa2c/aiohttp-3.12.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc4be1d8d68a62859f74f9ada9e174791895366601ce66342f54478d3518c8b3", size = 1710149 }, + { url = "https://files.pythonhosted.org/packages/e0/ba/730f75a17b613f2ecc336b698259140bbd439d8f7b14eae10aea14158085/aiohttp-3.12.6-cp311-cp311-win32.whl", hash = "sha256:a057680218430231eb6ab644d166b7ef398b3ffbac0232f4f789cdce9391400e", size = 420141 }, + { url = "https://files.pythonhosted.org/packages/df/71/8aaff029d07b15f7f79c484ca9b10f34cf8466d8dc4b13ecf32cc46b8de0/aiohttp-3.12.6-cp311-cp311-win_amd64.whl", hash = "sha256:8a88046a5adddf5d99f15a1920f6b8f659f46a4cfb5bfabbd668d06df045df7a", size = 444534 }, + { url = "https://files.pythonhosted.org/packages/e2/71/d4534c305623ba4e759caa381a5902713284f1ee52163d14894e60b3d254/aiohttp-3.12.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cfbf8ed94b57e3b5a886bfe2a530c8eb067064cc4419fd94431a2cbeeddec54c", size = 693736 }, + { url = "https://files.pythonhosted.org/packages/0c/f4/0e4c010b669ef7418fcd5249edc1671bd07492be7989699b92cbc65e19c2/aiohttp-3.12.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:012ea107092d4465aeeb681d5b2fb8b51a847a72f0b71906f40876419fba1355", size = 468347 }, + { url = "https://files.pythonhosted.org/packages/b0/6e/6bc969bab1d4790548220b7bd061b711f246b708d7d8a6d88a0ecb04154c/aiohttp-3.12.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdb03da5ecf74a331511604f3cf91563bf29127eabb28f4e16d390a73cb826da", size = 461191 }, + { url = "https://files.pythonhosted.org/packages/0a/56/de7ac8b49ce179618ede56f01f0050f75a270f3d9eb5d6905793e331a7d7/aiohttp-3.12.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ca81cb1e41d251cc193164409c0bbb0175e696a9997491a10db9171a2f70603", size = 1707982 }, + { url = "https://files.pythonhosted.org/packages/43/dd/74d8f791bf7832077ce5f7592126a64c6de57849182f730bb75dc7030ee7/aiohttp-3.12.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15817882d25e840aba85d1f5706a7128350b81050f8ca9dabfc25a5f521a792c", size = 1690630 }, + { url = "https://files.pythonhosted.org/packages/10/f2/c5e96be25dd3efd0fe4b21a0c583fffadbadfc85f039cc0dd013e08bdc07/aiohttp-3.12.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db5c402ea0aed10af2e54e5946bf32f3ebb02a7604eaaa4c41a608053889de4a", size = 1745727 }, + { url = "https://files.pythonhosted.org/packages/ad/19/6fb117cf46a99d302405012f05faf67e7ebae925e8ba5a0948f5c046a7b4/aiohttp-3.12.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ea77675818fd8cac28491d0d59582e5e2e5b14dbf5e21bef797aa5b23b5ca8b", size = 1791881 }, + { url = "https://files.pythonhosted.org/packages/78/2f/cdde703cbfd281aca8679b75289c3cde865a541efc593354e50c5c6b7c01/aiohttp-3.12.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c232720190ca4240c15abefc7b765e987ef88df44d2384612890db87b33898f3", size = 1711323 }, + { url = "https://files.pythonhosted.org/packages/e8/a1/edfdfe7ea9160f1bbf7bd00964da45ac290a5d19661761098eefa95ac400/aiohttp-3.12.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2f3c974874bd0c76dfdcc60db5a6f96ca023a85318a5ac401603baa7e299272", size = 1627125 }, + { url = "https://files.pythonhosted.org/packages/5e/f0/de34cad1d44c6a7e4bffef9d42f982a28a4cdce8815733134aceb542be1d/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:25de52753386b0c16d5acd2153e7819f52c9e7fc05f5eca804adc174e99b735d", size = 1688185 }, + { url = "https://files.pythonhosted.org/packages/c7/46/e486289bc0a52d2cc4b87091de7428c4c4ddc76465c1aaa22eb953b8bcb7/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3cc06a99e065ed7e766d2cd574671428261c1b8f30fedfbd91ab3c738fd9c08d", size = 1709637 }, + { url = "https://files.pythonhosted.org/packages/1f/f2/fe3d3955a2c9e78c308783ef0b1b53e5a3b56beb87133a52584e8dda52c0/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aac87d78f55057ab48ddcc43055620546d40bbc0888d2658d8705d183c98f901", size = 1650291 }, + { url = "https://files.pythonhosted.org/packages/1c/68/2b425bd8031666be7db81e37918a9b3a9898e02b8d7e672737b05c55e2c5/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:de83f567e31418fd7bc22c5a03526a2b0a82e68c7a7fec23ef91a398228f559b", size = 1729852 }, + { url = "https://files.pythonhosted.org/packages/10/40/a14b0cf78531d504391a311c3e7c190f230cbf7dba5d4ccfbf53a5d121e5/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd1d6116c1364ab00ffed1654a01091dc7f897d315c5103bcc6e5ab7f70172c7", size = 1757824 }, + { url = "https://files.pythonhosted.org/packages/25/1d/250baf6961354772bf7447bb280dffa2df15c08875e535cf6a735a41373e/aiohttp-3.12.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58f79b376a426961418df1d08656ec3a01494b7ba81824ae629e6636deddfff7", size = 1717436 }, + { url = "https://files.pythonhosted.org/packages/dc/43/9b9204284c08c244d89c69e3e556dfd7757e0393d4ef20a9238bf2643795/aiohttp-3.12.6-cp312-cp312-win32.whl", hash = "sha256:561f545dc062e6c31fc53535d8584c06516bda2fc37821a67a61b69202061e71", size = 414878 }, + { url = "https://files.pythonhosted.org/packages/fe/c1/8561f01a6386a7ecdc54aefff155aae51a349c98c04b1325619e12049fbc/aiohttp-3.12.6-cp312-cp312-win_amd64.whl", hash = "sha256:d83ab494eb583ba691af9d4d7c073987526bb9f73aa5a19907258ef3a1e39e8a", size = 440981 }, + { url = "https://files.pythonhosted.org/packages/be/5d/4db8a8972642779aa981aae2d71d88106a12f3f6a8354725ef4dbcf31a70/aiohttp-3.12.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7487f707a4b8167394f6afefa690198300d8a618505583eb536b92202bdec24d", size = 688139 }, + { url = "https://files.pythonhosted.org/packages/9b/b0/f0326159505f05a32e0dd858ca4770bdeb97900797d80ece9e8031f87c76/aiohttp-3.12.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd9211229fa2f474da01d42fafff196f607a63aaf12d8b34928c43a713eb6d5", size = 465812 }, + { url = "https://files.pythonhosted.org/packages/d1/3c/4abaf69965a63aac3bf3c9054c58b1eef68d6cf520ffeb593ed47e590da1/aiohttp-3.12.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3331ef09dd775302aa5f4d3170bd46659ad018843fab3656f5e72e3ff68df21f", size = 458109 }, + { url = "https://files.pythonhosted.org/packages/98/5f/8603860deada8e84ac5954bc736428ef370f8dd600b266c7d8177eea69ad/aiohttp-3.12.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c88ed8c54f7fd6102ef711d24710454707cde4bb3ffdec09982dcb3cb966a3e1", size = 1696923 }, + { url = "https://files.pythonhosted.org/packages/ec/40/209bb8dbb0b03f5758b7de70f86b7ac6acd8450a9bc4b4128cc5e89a20b2/aiohttp-3.12.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:148ffa6b2b825ff8520844ce23df9e2a5b969bb6917c4e35a832fbaa025d260d", size = 1678187 }, + { url = "https://files.pythonhosted.org/packages/26/bf/faa89212e33b6c6ba5913bd7319942f2955f0d199b7c6097896bac35ad6c/aiohttp-3.12.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8da054804352e974f4349fb871b07c8ffa1978e64cfb455e88fbe6fbe4d6dcb", size = 1730257 }, + { url = "https://files.pythonhosted.org/packages/d9/0c/02df1921239913d91a74563547d8e1c79ec6caa052d0bddfbc48e09708a4/aiohttp-3.12.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d162c4f87f9dcdc7151f6329438de96beb527820381e3159ce08544c57e9ced", size = 1779630 }, + { url = "https://files.pythonhosted.org/packages/d8/d0/c72d6b5a204291bbae5ae38fc367df9df11ce32dca6dcca6355d469c9c13/aiohttp-3.12.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da073f88270aa434ef16a78c21a4269c96c68badc2b9ad5011fa175c06143eee", size = 1701959 }, + { url = "https://files.pythonhosted.org/packages/b4/b1/2e2cc4bb3de9d0b185a5c5b6b9d04e3a37c79e52529c634a962ca7a22bfe/aiohttp-3.12.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2e026a9f9ac0df70f14ca5dcaf1f83a55b678e51aa6515d710dd879d2691fd7", size = 1615662 }, + { url = "https://files.pythonhosted.org/packages/bc/55/00c119c8ce2d65879b7b6d64b5a344df3ee8845f0d2a11d190376c9e7256/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b700cf48fd04b4328965d1afe01f835fe6cdecc3b85ca2d950431e5cc0647f7", size = 1668684 }, + { url = "https://files.pythonhosted.org/packages/b8/be/59bc7538ccaff6fe9cf0b3a27f976d8b2729150b700dc37beef71705f009/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:38af291559401d13eb90259ba79ef6ac537ae6b5bdb1251604606a88cd0fd5e0", size = 1700350 }, + { url = "https://files.pythonhosted.org/packages/a2/3c/bcfc532cf09755c5d094e320ba7e9e7a6b977d6487b211278a5d400d0649/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6860351cfba0196db2edc387cfeddaf1dae443e55f261ea2bcb77fecb33aae34", size = 1642945 }, + { url = "https://files.pythonhosted.org/packages/44/27/ebc660cb7624ce8d6b71486490478bb52784074cc46f5fed8fbf5f0306d2/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:06f20adcdc4f383aeb7ce884705faea44c0376cde5cdee4d32ef62d6cb1f97cc", size = 1719080 }, + { url = "https://files.pythonhosted.org/packages/81/6e/fd000fa2708cb3b887c0fe8a144f926ca34960a80ed1c44d3606027fd831/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a52aa39eb1160775a6e80e3025c990e8872c8927c5dd4b51304788bc149b9549", size = 1752550 }, + { url = "https://files.pythonhosted.org/packages/ee/2d/5a0bd6d09ea38fcb3ec683425b25946156b99ab451c69ef84ea3d03b6eaf/aiohttp-3.12.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:52ce7e90ee9dd25bcd2ed4513e650cc4f9a03bef07a39193b82fb58892004bd6", size = 1701441 }, + { url = "https://files.pythonhosted.org/packages/bd/7b/db64cfd8fd522de73b803b600d967cc2821250f82ba97892a90c4f7e204e/aiohttp-3.12.6-cp313-cp313-win32.whl", hash = "sha256:259269870d9783de87c0430760b2498b770201ead3e11ee86761d268ce5d196a", size = 413900 }, + { url = "https://files.pythonhosted.org/packages/7f/d6/4680e3601edf5ec0e1e56cca7746f0de9b9758a33b88067b1935e95f7005/aiohttp-3.12.6-cp313-cp313-win_amd64.whl", hash = "sha256:938afd243c9ee76a6d78fad10ecca14b88b48b71553e0e9c74b8098efff5ddf8", size = 439844 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "autograd" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ea/e16f0c423f7d83cf8b79cae9452040fb7b2e020c7439a167ee7c317de448/autograd-1.8.0-py3-none-any.whl", hash = "sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78", size = 51478 }, +] + +[[package]] +name = "autograd-gamma" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "autograd" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/ae/7f2031ea76140444b2453fa139041e5afd4a09fc5300cfefeb1103291f80/autograd-gamma-0.5.0.tar.gz", hash = "sha256:f27abb7b8bb9cffc8badcbf59f3fe44a9db39e124ecacf1992b6d952934ac9c4", size = 3952 } + +[[package]] +name = "beartype" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/f9/21e5a9c731e14f08addd53c71fea2e70794e009de5b98e6a2c3d2f3015d6/beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244", size = 1437066 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/31/87045d1c66ee10a52486c9d2047bc69f00f2689f69401bb1e998afb4b205/beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0", size = 1191340 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 }, +] + +[[package]] +name = "braceexpand" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/badd4f5ccf25209f3fef2573073da9fe4a45a3da99fca2f800f942130c0f/braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705", size = 7777 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/93/e8c04e80e82391a6e51f218ca49720f64236bc824e92152a2633b74cf7ab/braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014", size = 5923 }, +] + +[[package]] +name = "causal-conv1d" +version = "1.5.0.post8" +source = { git = "https://github.com/KatherLab/causal-conv1d.git?rev=55b4626e1a2d3d6b939811725f2f3ef65b7b3ff1#55b4626e1a2d3d6b939811725f2f3ef65b7b3ff1" } +dependencies = [ + { name = "ninja" }, + { name = "packaging" }, + { name = "torch" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, +] + +[[package]] +name = "cobra" +version = "0.1.0" +source = { git = "https://github.com/KatherLab/COBRA.git?rev=f1a576e1133330ffc2d1df6ee110701921c7b7c9#f1a576e1133330ffc2d1df6ee110701921c7b7c9" } +dependencies = [ + { name = "causal-conv1d" }, + { name = "einops" }, + { name = "h5py" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "mamba-ssm" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "openpyxl" }, + { name = "openslide-bin" }, + { name = "openslide-python" }, + { name = "pandas" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "scikit-learn" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "tqdm" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "conch" +version = "0.1.0" +source = { git = "https://github.com/Mahmoodlab/CONCH.git?rev=02d6ac59cc20874bff0f581de258c2b257f69a84#02d6ac59cc20874bff0f581de258c2b257f69a84" } +dependencies = [ + { name = "ftfy" }, + { name = "h5py" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "regex" }, + { name = "scikit-learn" }, + { name = "timm" }, + { name = "tokenizers" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "transformers" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636 }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636 }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053 }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985 }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750 }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246 }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728 }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762 }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196 }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017 }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580 }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688 }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331 }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963 }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681 }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674 }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480 }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489 }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042 }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630 }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670 }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694 }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986 }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060 }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747 }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895 }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098 }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535 }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096 }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090 }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643 }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443 }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865 }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162 }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355 }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935 }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168 }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550 }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214 }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807 }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729 }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791 }, +] + +[[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]] +name = "debugpy" +version = "1.8.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/e8/57fe0c86915671fd6a3d2d8746e40485fd55e8d9e682388fbb3a3d42b86f/debugpy-1.8.14-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9", size = 2175064 }, + { url = "https://files.pythonhosted.org/packages/3b/97/2b2fd1b1c9569c6764ccdb650a6f752e4ac31be465049563c9eb127a8487/debugpy-1.8.14-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2", size = 3132359 }, + { url = "https://files.pythonhosted.org/packages/c0/ee/b825c87ed06256ee2a7ed8bab8fb3bb5851293bf9465409fdffc6261c426/debugpy-1.8.14-cp311-cp311-win32.whl", hash = "sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2", size = 5133269 }, + { url = "https://files.pythonhosted.org/packages/d5/a6/6c70cd15afa43d37839d60f324213843174c1d1e6bb616bd89f7c1341bac/debugpy-1.8.14-cp311-cp311-win_amd64.whl", hash = "sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01", size = 5158156 }, + { url = "https://files.pythonhosted.org/packages/d9/2a/ac2df0eda4898f29c46eb6713a5148e6f8b2b389c8ec9e425a4a1d67bf07/debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84", size = 2501268 }, + { url = "https://files.pythonhosted.org/packages/10/53/0a0cb5d79dd9f7039169f8bf94a144ad3efa52cc519940b3b7dde23bcb89/debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826", size = 4221077 }, + { url = "https://files.pythonhosted.org/packages/f8/d5/84e01821f362327bf4828728aa31e907a2eca7c78cd7c6ec062780d249f8/debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f", size = 5255127 }, + { url = "https://files.pythonhosted.org/packages/33/16/1ed929d812c758295cac7f9cf3dab5c73439c83d9091f2d91871e648093e/debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f", size = 5297249 }, + { url = "https://files.pythonhosted.org/packages/4d/e4/395c792b243f2367d84202dc33689aa3d910fb9826a7491ba20fc9e261f5/debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f", size = 2485676 }, + { url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514 }, + { url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756 }, + { url = "https://files.pythonhosted.org/packages/e0/62/a7b4a57013eac4ccaef6977966e6bec5c63906dd25a86e35f155952e29a1/debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e", size = 5297119 }, + { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230 }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, +] + +[[package]] +name = "docker-pycreds" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/e6/d1f6c00b7221e2d7c4b470132c931325c8b22c51ca62417e300f5ce16009/docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", size = 8754 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e8/f6bd1eee09314e7e6dee49cbe2c5e22314ccdb38db16c9fc72d2fa80d054/docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49", size = 8982 }, +] + +[[package]] +name = "ecos" +version = "2.0.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/5f/17716c533da95ed110815b159efa22b1064c8c41fd5c862f21aff7a7fec0/ecos-2.0.14.tar.gz", hash = "sha256:64b3201c0e0a7f0129050557c4ac50b00031e80a10534506dba1200c8dc1efe4", size = 142430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/9b/c886a268d4b7adfaa1171244cdbfa3c944e5a599fe7a5e738ee27390ab20/ecos-2.0.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dc90b54eaae16ead128bfdd95e04bf808b73578bdf40ed652c55aa36a6d02e42", size = 92594 }, + { url = "https://files.pythonhosted.org/packages/49/e9/fae34e8ef6a9b78c3098a4428ed0e8f77cdeb334a7dc17c649abb686ed08/ecos-2.0.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8be3b4856838ae351fec40fb3589181d52b41cf75bf4d35342686a508c37a6", size = 220084 }, + { url = "https://files.pythonhosted.org/packages/2f/45/1e52519d6c29dd26bbfaf92ece5b45ca3de3b7c8b2615a818aaeadb7ad63/ecos-2.0.14-cp311-cp311-win_amd64.whl", hash = "sha256:7495b3031ccc2d4cec72cdb40aed8a2d1fdd734fe40519b7e6047aead5e811cf", size = 72199 }, + { url = "https://files.pythonhosted.org/packages/af/c3/84e392f2410f51fa557198937cc52a2e80f887c517ef4e3fb6d46e3bb008/ecos-2.0.14-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4a7e2704a3ef9acfb8146d594deff9942d3a0f0d0399de8fe2e0bd95e8b0855c", size = 92545 }, + { url = "https://files.pythonhosted.org/packages/82/12/42f4d953f9284571726b085f99e13bfa84522bf63bf2e7a81460013b09e6/ecos-2.0.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3cbb1a66ecf10955a1a4bcd6b99db55148000cb79fd176bfac26d98b21a4814", size = 222132 }, + { url = "https://files.pythonhosted.org/packages/56/9a/ca30572f3e3ff3cef6a0ea8aa6cdc12c36f9fefe559f65c7d6265713196a/ecos-2.0.14-cp312-cp312-win_amd64.whl", hash = "sha256:718eb62afb8e45426bcc365ebaf3ca9f610afcbb754de6073ef5f104da8fca1f", size = 72248 }, +] + +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359 }, +] + +[[package]] +name = "einops-exts" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/cc/f2b03b24894faaaa425cf46508e6b1f449d908841f61aeb7c14f0b18a3f3/einops-exts-0.0.4.tar.gz", hash = "sha256:616f145b3411f8e9e3be5da5c968bbe372e55c249de11faa909c7a4b74580a6c", size = 3548 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/01/8da8dd078b354a89602a875d310a0d725dad92b5b4d61069576e0a0e02e4/einops_exts-0.0.4-py3-none-any.whl", hash = "sha256:6d310a4c858e459ebff8288580f90255d354cfa3bde22a53b59baae64b48cb95", size = 3925 }, +] + +[[package]] +name = "environs" +version = "11.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/13/3d448cfbed9f1baff5765f49434cd849501351f14fd3f09f0f2e9bd35322/environs-11.0.0.tar.gz", hash = "sha256:069727a8f73d8ba8d033d3cd95c0da231d44f38f1da773bf076cef168d312ee8", size = 25787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/30/ef8a3022e6cdcedfd7ba03ca88ab29e30334f8e958cdbf5ce120912397e8/environs-11.0.0-py3-none-any.whl", hash = "sha256:e0bcfd41c718c07a7db422f9109e490746450da38793fe4ee197f397b9343435", size = 12216 }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + +[[package]] +name = "fairscale" +version = "0.4.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/08/b3334d7b543ac10dcb129cef4f84723ab696725512f18d69ab3a784b0bf5/fairscale-0.4.13.tar.gz", hash = "sha256:1b797825c427f5dba92253fd0d8daa574e8bd651a2423497775fab1b30cfb768", size = 266261 } + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "flash-attn" +version = "2.8.0.post2" +source = { git = "https://github.com/KatherLab/flash-attention.git?rev=7593c84a0d36b7f2ead10660209b6f8b374ade4e#7593c84a0d36b7f2ead10660209b6f8b374ade4e" } +dependencies = [ + { name = "einops" }, + { name = "torch" }, +] + +[[package]] +name = "fonttools" +version = "4.58.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/7a/30c581aeaa86d94e7a29344bccefd2408870bf5b0e7640b6f4ffede61bd0/fonttools-4.58.1.tar.gz", hash = "sha256:cbc8868e0a29c3e22628dfa1432adf7a104d86d1bc661cecc3e9173070b6ab2d", size = 3519505 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3f/9fecd69149b0eec5ca46ec58de83b2fd34d07204fe2c12c209255082507a/fonttools-4.58.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9966e14729669bcfbb56f83b747a2397c4d97c6d4798cb2e2adc28f9388fa008", size = 2754713 }, + { url = "https://files.pythonhosted.org/packages/c8/19/d04ea5f3ab2afa7799f2b1ebe1d57ff71b479f99f29b82bddc7197d50220/fonttools-4.58.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64cc1647bbe83dea57f5496ec878ad19ccdba7185b0dd34955d3e6f03dc789e6", size = 2316637 }, + { url = "https://files.pythonhosted.org/packages/5c/3f/375f59d756b17318336c050363849011e03ac82904538f39ebe8189835bc/fonttools-4.58.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464f790ce681d08d1583df0735776aa9cb1999594bf336ddd0bf962c17b629ac", size = 4915730 }, + { url = "https://files.pythonhosted.org/packages/2f/90/069f859d6f6480503574cda21b84ceee98bf5f5fd1764f26674e828a2600/fonttools-4.58.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c53c6a720ee70cc25746d511ba88c45c95ec510fd258026ed209b0b9e3ba92f", size = 4936194 }, + { url = "https://files.pythonhosted.org/packages/01/11/339973e588e1c27f20c578f845bdcf84376c5e42bd35fca05419fd8d1648/fonttools-4.58.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6823a633bbce29cf3033508ebb54a433c473fb9833eff7f936bfdc5204fd98d", size = 4978982 }, + { url = "https://files.pythonhosted.org/packages/a7/aa/1c627532a69715f54b8d96ab3a7bc8628f6e89989e9275dfc067dc2d6d56/fonttools-4.58.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5701fe66a1408c1974d2f78c00f964f8aad17cccbc32bc041e1b81421f31f448", size = 5090087 }, + { url = "https://files.pythonhosted.org/packages/77/ce/cf7b624db35bce589ac1f2c98329ea91b28f0283d3b7e9e6126dfaeb5abd/fonttools-4.58.1-cp311-cp311-win32.whl", hash = "sha256:4cad2c74adf9ee31ae43be6b0b376fdb386d4d50c60979790e32c3548efec051", size = 2188923 }, + { url = "https://files.pythonhosted.org/packages/b9/22/c4f1f76eeb1b9353e9cc81451d0ae08acc3d3aa31b9ab8f3791a18af1f89/fonttools-4.58.1-cp311-cp311-win_amd64.whl", hash = "sha256:7ade12485abccb0f6b6a6e2a88c50e587ff0e201e48e0153dd9b2e0ed67a2f38", size = 2236853 }, + { url = "https://files.pythonhosted.org/packages/32/97/ed1078b1e138fbc0b4ee75878000d549a70c02d83bb4e557e416efc34140/fonttools-4.58.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f56085a65769dc0100822c814069327541db9c3c4f21e599c6138f9dbda75e96", size = 2740473 }, + { url = "https://files.pythonhosted.org/packages/28/35/53d49fb7d6b30128153d11628b976fda3ce8ae44234b5a81c4edb3023798/fonttools-4.58.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19c65a88e522c9f1be0c05d73541de20feada99d23d06e9b5354023cc3e517b0", size = 2309936 }, + { url = "https://files.pythonhosted.org/packages/0c/db/8b63c1d673b2bf0cfed77500d47769dc4aa85453b5f0ef525db2cf952895/fonttools-4.58.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b01bb37006e97703300bfde7a73d1c7038574dd1df9d8d92ca99af151becf2ca", size = 4814671 }, + { url = "https://files.pythonhosted.org/packages/a6/13/0b96eeb148b77c521b8e94628c59d15e4fb0e76191c41f5616a656d6adb9/fonttools-4.58.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d629dea240f0fc826d8bb14566e95c663214eece21b5932c9228d3e8907f55aa", size = 4881493 }, + { url = "https://files.pythonhosted.org/packages/ac/b0/9f8aa60e8e5be91aba8dfaa3fa6b33fd950511686921cf27e97bf4154e3d/fonttools-4.58.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef0b33ff35421a04a638e736823c2dee9d200cdd275cfdb43e875ca745150aae", size = 4874960 }, + { url = "https://files.pythonhosted.org/packages/b6/7e/83b409659eb4818f1283a8319f3570497718d6d3b70f4fca2ddf962e948e/fonttools-4.58.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4db9399ee633855c718fe8bea5eecbdc5bf3fdbed2648e50f67f8946b943ed1c", size = 5026677 }, + { url = "https://files.pythonhosted.org/packages/34/52/1eb69802d3b54e569158c97810195f317d350f56390b83c43e1c999551d8/fonttools-4.58.1-cp312-cp312-win32.whl", hash = "sha256:5cf04c4f73d36b30ea1cff091a7a9e65f8d5b08345b950f82679034e9f7573f4", size = 2176201 }, + { url = "https://files.pythonhosted.org/packages/6f/25/8dcfeb771de8d9cdffab2b957a05af4395d41ec9a198ec139d2326366a07/fonttools-4.58.1-cp312-cp312-win_amd64.whl", hash = "sha256:4a3841b59c67fa1f739542b05211609c453cec5d11d21f863dd2652d5a81ec9b", size = 2225519 }, + { url = "https://files.pythonhosted.org/packages/83/7a/7ed2e4e381f9b1f5122d33b7e626a40f646cacc1ef72d8806aacece9e580/fonttools-4.58.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68379d1599fc59569956a97eb7b07e0413f76142ac8513fa24c9f2c03970543a", size = 2731231 }, + { url = "https://files.pythonhosted.org/packages/e7/28/74864dc9248e917cbe07c903e0ce1517c89d42e2fab6b0ce218387ef0e24/fonttools-4.58.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8631905657de4f9a7ae1e12186c1ed20ba4d6168c2d593b9e0bd2908061d341b", size = 2305224 }, + { url = "https://files.pythonhosted.org/packages/e7/f1/ced758896188c1632c5b034a0741457f305e087eb4fa762d86aa3c1ae422/fonttools-4.58.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ecea7289061c2c71468723409a8dd6e70d1ecfce6bc7686e5a74b9ce9154fe", size = 4793934 }, + { url = "https://files.pythonhosted.org/packages/c1/46/8b46469c6edac393de1c380c7ec61922d5440f25605dfca7849e5ffff295/fonttools-4.58.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b8860f8cd48b345bd1df1d7be650f600f69ee971ffe338c5bd5bcb6bdb3b92c", size = 4863415 }, + { url = "https://files.pythonhosted.org/packages/12/1b/82aa678bb96af6663fe163d51493ffb8622948f4908c886cba6b67fbf6c5/fonttools-4.58.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7c9a0acdefcb8d7ccd7c59202056166c400e797047009ecb299b75ab950c2a9c", size = 4865025 }, + { url = "https://files.pythonhosted.org/packages/7d/26/b66ab2f2dc34b962caecd6fa72a036395b1bc9fb849f52856b1e1144cd63/fonttools-4.58.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1fac0be6be3e4309058e156948cb73196e5fd994268b89b5e3f5a26ee2b582", size = 5002698 }, + { url = "https://files.pythonhosted.org/packages/7b/56/cdddc63333ed77e810df56e5e7fb93659022d535a670335d8792be6d59fd/fonttools-4.58.1-cp313-cp313-win32.whl", hash = "sha256:aed7f93a9a072f0ce6fb46aad9474824ac6dd9c7c38a72f8295dd14f2215950f", size = 2174515 }, + { url = "https://files.pythonhosted.org/packages/ba/81/c7f395718e44cebe1010fcd7f1b91957d65d512d5f03114d2d6d00cae1c4/fonttools-4.58.1-cp313-cp313-win_amd64.whl", hash = "sha256:b27d69c97c20c9bca807f7ae7fc7df459eb62994859ff6a2a489e420634deac3", size = 2225290 }, + { url = "https://files.pythonhosted.org/packages/21/ff/995277586691c0cc314c28b24b4ec30610440fd7bf580072aed1409f95b0/fonttools-4.58.1-py3-none-any.whl", hash = "sha256:db88365d0962cd6f5bce54b190a4669aeed9c9941aa7bd60a5af084d8d9173d6", size = 1113429 }, +] + +[[package]] +name = "formulaic" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "interface-meta" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "scipy" }, + { name = "typing-extensions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/30/03b5e3bb62374db3f665ca3020fdfc4304e98ceeaaa9dcd7a47a6b574ebf/formulaic-1.1.1.tar.gz", hash = "sha256:ddf80e4bef976dd99698aa27512015276c7b86c314b601ae6fd360c7741b7231", size = 652602 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/c2/a34097e53efe70a538ae97574ff9e9866e60fc1c792c19da5fd6b56ce7b5/formulaic-1.1.1-py3-none-any.whl", hash = "sha256:bbb7e38f99e4bcdc62cb0a6a818ad33b370b4e98e9e4f0b276561448482c8268", size = 115718 }, +] + +[[package]] +name = "frozenlist" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/b5/bc883b5296ec902115c00be161da93bf661199c465ec4c483feec6ea4c32/frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d", size = 160912 }, + { url = "https://files.pythonhosted.org/packages/6f/93/51b058b563d0704b39c56baa222828043aafcac17fd3734bec5dbeb619b1/frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0", size = 124315 }, + { url = "https://files.pythonhosted.org/packages/c9/e0/46cd35219428d350558b874d595e132d1c17a9471a1bd0d01d518a261e7c/frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe", size = 122230 }, + { url = "https://files.pythonhosted.org/packages/d1/0f/7ad2ce928ad06d6dd26a61812b959ded573d3e9d0ee6109d96c2be7172e9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba", size = 314842 }, + { url = "https://files.pythonhosted.org/packages/34/76/98cbbd8a20a5c3359a2004ae5e5b216af84a150ccbad67c8f8f30fb2ea91/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595", size = 304919 }, + { url = "https://files.pythonhosted.org/packages/9a/fa/258e771ce3a44348c05e6b01dffc2bc67603fba95761458c238cd09a2c77/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a", size = 324074 }, + { url = "https://files.pythonhosted.org/packages/d5/a4/047d861fd8c538210e12b208c0479912273f991356b6bdee7ea8356b07c9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626", size = 321292 }, + { url = "https://files.pythonhosted.org/packages/c0/25/cfec8af758b4525676cabd36efcaf7102c1348a776c0d1ad046b8a7cdc65/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff", size = 301569 }, + { url = "https://files.pythonhosted.org/packages/87/2f/0c819372fa9f0c07b153124bf58683b8d0ca7bb73ea5ccde9b9ef1745beb/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a", size = 313625 }, + { url = "https://files.pythonhosted.org/packages/50/5f/f0cf8b0fdedffdb76b3745aa13d5dbe404d63493cc211ce8250f2025307f/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0", size = 312523 }, + { url = "https://files.pythonhosted.org/packages/e1/6c/38c49108491272d3e84125bbabf2c2d0b304899b52f49f0539deb26ad18d/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606", size = 322657 }, + { url = "https://files.pythonhosted.org/packages/bd/4b/3bd3bad5be06a9d1b04b1c22be80b5fe65b502992d62fab4bdb25d9366ee/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584", size = 303414 }, + { url = "https://files.pythonhosted.org/packages/5b/89/7e225a30bef6e85dbfe22622c24afe932e9444de3b40d58b1ea589a14ef8/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a", size = 320321 }, + { url = "https://files.pythonhosted.org/packages/22/72/7e3acef4dd9e86366cb8f4d8f28e852c2b7e116927e9722b31a6f71ea4b0/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1", size = 323975 }, + { url = "https://files.pythonhosted.org/packages/d8/85/e5da03d20507e13c66ce612c9792b76811b7a43e3320cce42d95b85ac755/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e", size = 316553 }, + { url = "https://files.pythonhosted.org/packages/ac/8e/6c609cbd0580ae8a0661c408149f196aade7d325b1ae7adc930501b81acb/frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860", size = 115511 }, + { url = "https://files.pythonhosted.org/packages/f2/13/a84804cfde6de12d44ed48ecbf777ba62b12ff09e761f76cdd1ff9e14bb1/frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603", size = 120863 }, + { url = "https://files.pythonhosted.org/packages/9c/8a/289b7d0de2fbac832ea80944d809759976f661557a38bb8e77db5d9f79b7/frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1", size = 160193 }, + { url = "https://files.pythonhosted.org/packages/19/80/2fd17d322aec7f430549f0669f599997174f93ee17929ea5b92781ec902c/frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29", size = 123831 }, + { url = "https://files.pythonhosted.org/packages/99/06/f5812da431273f78c6543e0b2f7de67dfd65eb0a433978b2c9c63d2205e4/frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25", size = 121862 }, + { url = "https://files.pythonhosted.org/packages/d0/31/9e61c6b5fc493cf24d54881731204d27105234d09878be1a5983182cc4a5/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576", size = 316361 }, + { url = "https://files.pythonhosted.org/packages/9d/55/22ca9362d4f0222324981470fd50192be200154d51509ee6eb9baa148e96/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8", size = 307115 }, + { url = "https://files.pythonhosted.org/packages/ae/39/4fff42920a57794881e7bb3898dc7f5f539261711ea411b43bba3cde8b79/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9", size = 322505 }, + { url = "https://files.pythonhosted.org/packages/55/f2/88c41f374c1e4cf0092a5459e5f3d6a1e17ed274c98087a76487783df90c/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e", size = 322666 }, + { url = "https://files.pythonhosted.org/packages/75/51/034eeb75afdf3fd03997856195b500722c0b1a50716664cde64e28299c4b/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590", size = 302119 }, + { url = "https://files.pythonhosted.org/packages/2b/a6/564ecde55ee633270a793999ef4fd1d2c2b32b5a7eec903b1012cb7c5143/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103", size = 316226 }, + { url = "https://files.pythonhosted.org/packages/f1/c8/6c0682c32377f402b8a6174fb16378b683cf6379ab4d2827c580892ab3c7/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c", size = 312788 }, + { url = "https://files.pythonhosted.org/packages/b6/b8/10fbec38f82c5d163ca1750bfff4ede69713badf236a016781cf1f10a0f0/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821", size = 325914 }, + { url = "https://files.pythonhosted.org/packages/62/ca/2bf4f3a1bd40cdedd301e6ecfdbb291080d5afc5f9ce350c0739f773d6b9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70", size = 305283 }, + { url = "https://files.pythonhosted.org/packages/09/64/20cc13ccf94abc2a1f482f74ad210703dc78a590d0b805af1c9aa67f76f9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f", size = 319264 }, + { url = "https://files.pythonhosted.org/packages/20/ff/86c6a2bbe98cfc231519f5e6d712a0898488ceac804a917ce014f32e68f6/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046", size = 326482 }, + { url = "https://files.pythonhosted.org/packages/2f/da/8e381f66367d79adca245d1d71527aac774e30e291d41ef161ce2d80c38e/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770", size = 318248 }, + { url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161 }, + { url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548 }, + { url = "https://files.pythonhosted.org/packages/6f/e5/04c7090c514d96ca00887932417f04343ab94904a56ab7f57861bf63652d/frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e", size = 158182 }, + { url = "https://files.pythonhosted.org/packages/e9/8f/60d0555c61eec855783a6356268314d204137f5e0c53b59ae2fc28938c99/frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117", size = 122838 }, + { url = "https://files.pythonhosted.org/packages/5a/a7/d0ec890e3665b4b3b7c05dc80e477ed8dc2e2e77719368e78e2cd9fec9c8/frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4", size = 120980 }, + { url = "https://files.pythonhosted.org/packages/cc/19/9b355a5e7a8eba903a008579964192c3e427444752f20b2144b10bb336df/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3", size = 305463 }, + { url = "https://files.pythonhosted.org/packages/9c/8d/5b4c758c2550131d66935ef2fa700ada2461c08866aef4229ae1554b93ca/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1", size = 297985 }, + { url = "https://files.pythonhosted.org/packages/48/2c/537ec09e032b5865715726b2d1d9813e6589b571d34d01550c7aeaad7e53/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c", size = 311188 }, + { url = "https://files.pythonhosted.org/packages/31/2f/1aa74b33f74d54817055de9a4961eff798f066cdc6f67591905d4fc82a84/frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45", size = 311874 }, + { url = "https://files.pythonhosted.org/packages/bf/f0/cfec18838f13ebf4b37cfebc8649db5ea71a1b25dacd691444a10729776c/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f", size = 291897 }, + { url = "https://files.pythonhosted.org/packages/ea/a5/deb39325cbbea6cd0a46db8ccd76150ae2fcbe60d63243d9df4a0b8c3205/frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85", size = 305799 }, + { url = "https://files.pythonhosted.org/packages/78/22/6ddec55c5243a59f605e4280f10cee8c95a449f81e40117163383829c241/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8", size = 302804 }, + { url = "https://files.pythonhosted.org/packages/5d/b7/d9ca9bab87f28855063c4d202936800219e39db9e46f9fb004d521152623/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f", size = 316404 }, + { url = "https://files.pythonhosted.org/packages/a6/3a/1255305db7874d0b9eddb4fe4a27469e1fb63720f1fc6d325a5118492d18/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f", size = 295572 }, + { url = "https://files.pythonhosted.org/packages/2a/f2/8d38eeee39a0e3a91b75867cc102159ecccf441deb6ddf67be96d3410b84/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6", size = 307601 }, + { url = "https://files.pythonhosted.org/packages/38/04/80ec8e6b92f61ef085422d7b196822820404f940950dde5b2e367bede8bc/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188", size = 314232 }, + { url = "https://files.pythonhosted.org/packages/3a/58/93b41fb23e75f38f453ae92a2f987274c64637c450285577bd81c599b715/frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e", size = 308187 }, + { url = "https://files.pythonhosted.org/packages/6a/a2/e64df5c5aa36ab3dee5a40d254f3e471bb0603c225f81664267281c46a2d/frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4", size = 114772 }, + { url = "https://files.pythonhosted.org/packages/a0/77/fead27441e749b2d574bb73d693530d59d520d4b9e9679b8e3cb779d37f2/frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd", size = 119847 }, + { url = "https://files.pythonhosted.org/packages/df/bd/cc6d934991c1e5d9cafda83dfdc52f987c7b28343686aef2e58a9cf89f20/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64", size = 174937 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/daf945f335abdbfdd5993e9dc348ef4507436936ab3c26d7cfe72f4843bf/frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91", size = 136029 }, + { url = "https://files.pythonhosted.org/packages/51/65/4c3145f237a31247c3429e1c94c384d053f69b52110a0d04bfc8afc55fb2/frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd", size = 134831 }, + { url = "https://files.pythonhosted.org/packages/77/38/03d316507d8dea84dfb99bdd515ea245628af964b2bf57759e3c9205cc5e/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2", size = 392981 }, + { url = "https://files.pythonhosted.org/packages/37/02/46285ef9828f318ba400a51d5bb616ded38db8466836a9cfa39f3903260b/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506", size = 371999 }, + { url = "https://files.pythonhosted.org/packages/0d/64/1212fea37a112c3c5c05bfb5f0a81af4836ce349e69be75af93f99644da9/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0", size = 392200 }, + { url = "https://files.pythonhosted.org/packages/81/ce/9a6ea1763e3366e44a5208f76bf37c76c5da570772375e4d0be85180e588/frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0", size = 390134 }, + { url = "https://files.pythonhosted.org/packages/bc/36/939738b0b495b2c6d0c39ba51563e453232813042a8d908b8f9544296c29/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e", size = 365208 }, + { url = "https://files.pythonhosted.org/packages/b4/8b/939e62e93c63409949c25220d1ba8e88e3960f8ef6a8d9ede8f94b459d27/frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c", size = 385548 }, + { url = "https://files.pythonhosted.org/packages/62/38/22d2873c90102e06a7c5a3a5b82ca47e393c6079413e8a75c72bff067fa8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b", size = 391123 }, + { url = "https://files.pythonhosted.org/packages/44/78/63aaaf533ee0701549500f6d819be092c6065cb5c577edb70c09df74d5d0/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad", size = 394199 }, + { url = "https://files.pythonhosted.org/packages/54/45/71a6b48981d429e8fbcc08454dc99c4c2639865a646d549812883e9c9dd3/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215", size = 373854 }, + { url = "https://files.pythonhosted.org/packages/3f/f3/dbf2a5e11736ea81a66e37288bf9f881143a7822b288a992579ba1b4204d/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2", size = 395412 }, + { url = "https://files.pythonhosted.org/packages/b3/f1/c63166806b331f05104d8ea385c4acd511598568b1f3e4e8297ca54f2676/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911", size = 394936 }, + { url = "https://files.pythonhosted.org/packages/ef/ea/4f3e69e179a430473eaa1a75ff986526571215fefc6b9281cdc1f09a4eb8/frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497", size = 391459 }, + { url = "https://files.pythonhosted.org/packages/d3/c3/0fc2c97dea550df9afd072a37c1e95421652e3206bbeaa02378b24c2b480/frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f", size = 128797 }, + { url = "https://files.pythonhosted.org/packages/ae/f5/79c9320c5656b1965634fe4be9c82b12a3305bdbc58ad9cb941131107b20/frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348", size = 134709 }, + { url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404 }, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, +] + +[[package]] +name = "fvcore" +version = "0.1.5.post20221221" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "iopath" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "pyyaml" }, + { name = "tabulate" }, + { name = "termcolor" }, + { name = "tqdm" }, + { name = "yacs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/93/d056a9c4efc6c79ba7b5159cc66bb436db93d2cc46dca18ed65c59cc8e4e/fvcore-0.1.5.post20221221.tar.gz", hash = "sha256:f2fb0bb90572ae651c11c78e20493ed19b2240550a7e4bbb2d6de87bdd037860", size = 50217 } + +[[package]] +name = "gdown" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "filelock" }, + { name = "requests", extra = ["socks"] }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/6a/37e6b70c5bda3161e40265861e63b64a86bfc6ca6a8f1c35328a675c84fd/gdown-5.2.0.tar.gz", hash = "sha256:2145165062d85520a3cd98b356c9ed522c5e7984d408535409fd46f94defc787", size = 284647 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/70/e07c381e6488a77094f04c85c9caf1c8008cdc30778f7019bc52e5285ef0/gdown-5.2.0-py3-none-any.whl", hash = "sha256:33083832d82b1101bdd0e9df3edd0fbc0e1c5f14c9d8c38d2a35bf1683b526d6", size = 18235 }, +] + +[[package]] +name = "geopandas" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyogrio" }, + { name = "pyproj" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/ca/e62641e5391285cda854c2802e706e6686f62fc9d919ecf78ff7f8d42654/geopandas-1.1.0.tar.gz", hash = "sha256:d176b084170539044ce7554a1219a4433fa1bfba94035b5a519c8986330e429e", size = 331955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/82/79e02a0e5dd4aca81894842b9d6522624a40048a913c6384efb2987a4144/geopandas-1.1.0-py3-none-any.whl", hash = "sha256:b19b18bdc736ee05b237f5e9184211c452768a4c883f7d7f8421b0cbe1da5875", size = 338014 }, +] + +[[package]] +name = "gigapath" +version = "0.1.0" +source = { git = "https://github.com/EzicStar/prov-gigapath.git?rev=d4cf55321df37aaf867e24a31c61bcf490a296eb#d4cf55321df37aaf867e24a31c61bcf490a296eb" } +dependencies = [ + { name = "fairscale" }, + { name = "flash-attn" }, + { name = "fvcore" }, + { name = "iopath" }, + { name = "lifelines" }, + { name = "monai" }, + { name = "ninja" }, + { name = "scikit-image" }, + { name = "scikit-survival" }, + { name = "wandb" }, + { name = "webdataset" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + +[[package]] +name = "h5py" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/2b/50b15fdefb577d073b49699e6ea6a0a77a3a1016c2b67e2149fc50124a10/h5py-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8a8e38ef4ceb969f832cc230c0cf808c613cc47e31e768fd7b1106c55afa1cb8", size = 3422922 }, + { url = "https://files.pythonhosted.org/packages/94/59/36d87a559cab9c59b59088d52e86008d27a9602ce3afc9d3b51823014bf3/h5py-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f35640e81b03c02a88b8bf99fb6a9d3023cc52f7c627694db2f379e0028f2868", size = 2921619 }, + { url = "https://files.pythonhosted.org/packages/37/ef/6f80b19682c0b0835bbee7b253bec9c16af9004f2fd6427b1dd858100273/h5py-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:337af114616f3656da0c83b68fcf53ecd9ce9989a700b0883a6e7c483c3235d4", size = 4259366 }, + { url = "https://files.pythonhosted.org/packages/03/71/c99f662d4832c8835453cf3476f95daa28372023bda4aa1fca9e97c24f09/h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:782ff0ac39f455f21fd1c8ebc007328f65f43d56718a89327eec76677ebf238a", size = 4509058 }, + { url = "https://files.pythonhosted.org/packages/56/89/e3ff23e07131ff73a72a349be9639e4de84e163af89c1c218b939459a98a/h5py-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:22ffe2a25770a2d67213a1b94f58006c14dce06933a42d2aaa0318c5868d1508", size = 2966428 }, + { url = "https://files.pythonhosted.org/packages/d8/20/438f6366ba4ded80eadb38f8927f5e2cd6d2e087179552f20ae3dbcd5d5b/h5py-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:477c58307b6b9a2509c59c57811afb9f598aedede24a67da808262dfa0ee37b4", size = 3384442 }, + { url = "https://files.pythonhosted.org/packages/10/13/cc1cb7231399617d9951233eb12fddd396ff5d4f7f057ee5d2b1ca0ee7e7/h5py-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:57c4c74f627c616f02b7aec608a8c706fe08cb5b0ba7c08555a4eb1dde20805a", size = 2917567 }, + { url = "https://files.pythonhosted.org/packages/9e/d9/aed99e1c858dc698489f916eeb7c07513bc864885d28ab3689d572ba0ea0/h5py-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:357e6dc20b101a805ccfd0024731fbaf6e8718c18c09baf3b5e4e9d198d13fca", size = 4669544 }, + { url = "https://files.pythonhosted.org/packages/a7/da/3c137006ff5f0433f0fb076b1ebe4a7bf7b5ee1e8811b5486af98b500dd5/h5py-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6f13f9b5ce549448c01e4dfe08ea8d1772e6078799af2c1c8d09e941230a90d", size = 4932139 }, + { url = "https://files.pythonhosted.org/packages/25/61/d897952629cae131c19d4c41b2521e7dd6382f2d7177c87615c2e6dced1a/h5py-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:21daf38171753899b5905f3d82c99b0b1ec2cbbe282a037cad431feb620e62ec", size = 2954179 }, + { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040 }, + { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766 }, + { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255 }, + { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580 }, + { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890 }, +] + +[[package]] +name = "hf-xet" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/be/58f20728a5b445f8b064e74f0618897b3439f5ef90934da1916b9dfac76f/hf_xet-1.1.2.tar.gz", hash = "sha256:3712d6d4819d3976a1c18e36db9f503e296283f9363af818f50703506ed63da3", size = 467009 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ae/f1a63f75d9886f18a80220ba31a1c7b9c4752f03aae452f358f538c6a991/hf_xet-1.1.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dfd1873fd648488c70735cb60f7728512bca0e459e61fcd107069143cd798469", size = 2642559 }, + { url = "https://files.pythonhosted.org/packages/50/ab/d2c83ae18f1015d926defd5bfbe94c62d15e93f900e6a192e318ee947105/hf_xet-1.1.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:29b584983b2d977c44157d9241dcf0fd50acde0b7bff8897fe4386912330090d", size = 2541360 }, + { url = "https://files.pythonhosted.org/packages/9f/a7/693dc9f34f979e30a378125e2150a0b2d8d166e6d83ce3950eeb81e560aa/hf_xet-1.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b29ac84298147fe9164cc55ad994ba47399f90b5d045b0b803b99cf5f06d8ec", size = 5183081 }, + { url = "https://files.pythonhosted.org/packages/3d/23/c48607883f692a36c0a7735f47f98bad32dbe459a32d1568c0f21576985d/hf_xet-1.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d921ba32615676e436a0d15e162331abc9ed43d440916b1d836dc27ce1546173", size = 5356100 }, + { url = "https://files.pythonhosted.org/packages/eb/5b/b2316c7f1076da0582b52ea228f68bea95e243c388440d1dc80297c9d813/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d9b03c34e13c44893ab6e8fea18ee8d2a6878c15328dd3aabedbdd83ee9f2ed3", size = 5647688 }, + { url = "https://files.pythonhosted.org/packages/2c/98/e6995f0fa579929da7795c961f403f4ee84af36c625963f52741d56f242c/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01b18608955b3d826307d37da8bd38b28a46cd2d9908b3a3655d1363274f941a", size = 5322627 }, + { url = "https://files.pythonhosted.org/packages/59/40/8f1d5a44a64d8bf9e3c19576e789f716af54875b46daae65426714e75db1/hf_xet-1.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:3562902c81299b09f3582ddfb324400c6a901a2f3bc854f83556495755f4954c", size = 2739542 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/74/c4961b31e0f142a032ea24f477c3a7524dfabfd8126398a968b3cc6bf804/huggingface_hub-0.32.3.tar.gz", hash = "sha256:752c889ebf3a63cbd39803f6d87ccc135a463bbcb36abfa2faff0ccbf1cec087", size = 424525 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/dc/4f4d8080cbce7a38c1d0f1ba4932f9134480b9761af8ef4c65d49254b2bd/huggingface_hub-0.32.3-py3-none-any.whl", hash = "sha256:e46f7ea7fe2b5e5f67cc4e37eb201140091946a314d7c2b134a9673dadd80b6a", size = 512094 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imageio" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "interface-meta" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/75/10526292b332f3479c246750a96f6ec11a28e297839a9c25583b2aadc119/interface_meta-1.3.0.tar.gz", hash = "sha256:8a4493f8bdb73fb9655dcd5115bc897e207319e36c8835f39c516a2d7e9d79a1", size = 15007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/3f/a6ec28c88e2d8e54d32598a1e0b5208a4baa72a8e7f6e241beab5731eb9d/interface_meta-1.3.0-py3-none-any.whl", hash = "sha256:de35dc5241431886e709e20a14d6597ed07c9f1e8b4bfcffde2190ca5b700ee8", size = 14854 }, +] + +[[package]] +name = "iopath" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "portalocker" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/73/b3d451dfc523756cf177d3ebb0af76dc7751b341c60e2a21871be400ae29/iopath-0.1.10.tar.gz", hash = "sha256:3311c16a4d9137223e20f141655759933e1eda24f8bff166af834af3c645ef01", size = 42226 } + +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "9.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/09/4c7e06b96fbd203e06567b60fb41b06db606b6a82db6db7b2c85bb72a15c/ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8", size = 4426460 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/99/9ed3d52d00f1846679e3aa12e2326ac7044b5e7f90dc822b60115fa533ca/ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04", size = 605320 }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, +] + +[[package]] +name = "jaxtyping" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wadler-lindig" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/a8/416bd7ea110ec6b68e8868b0f99c985c735adcf7badc491d3c343937260a/jaxtyping-0.3.2.tar.gz", hash = "sha256:f30483fac4b42e915db8ad2414a85c3b63284aa7d3c100b96b59f755cf4a86ad", size = 44989 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/b9/281e10e2d967ea5e481683eaec99f55ac5a61085ee60551c36942ef32bef/jaxtyping-0.3.2-py3-none-any.whl", hash = "sha256:6a020fd276226ddb5ac4f5725323843dd65e3c7e85c64fd62431e5f738c74e04", size = 55409 }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880 }, +] + +[[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/da/ed/c913ee28936c371418cb167b128066ffb20bbf37771eecc2c97edf8a6e4c/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84", size = 124635 }, + { url = "https://files.pythonhosted.org/packages/4c/45/4a7f896f7467aaf5f56ef093d1f329346f3b594e77c6a3c327b2d415f521/kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561", size = 66717 }, + { url = "https://files.pythonhosted.org/packages/5f/b4/c12b3ac0852a3a68f94598d4c8d569f55361beef6159dce4e7b624160da2/kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/a9/98/1df4089b1ed23d83d410adfdc5947245c753bddfbe06541c4aae330e9e70/kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03", size = 1343994 }, + { url = "https://files.pythonhosted.org/packages/8d/bf/b4b169b050c8421a7c53ea1ea74e4ef9c335ee9013216c558a047f162d20/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954", size = 1434804 }, + { url = "https://files.pythonhosted.org/packages/66/5a/e13bd341fbcf73325ea60fdc8af752addf75c5079867af2e04cc41f34434/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79", size = 1450690 }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5955dcb376ba4a830384cc6fab7d7547bd6759fe75a09564910e9e3bb8ea/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6", size = 1376839 }, + { url = "https://files.pythonhosted.org/packages/3a/97/5edbed69a9d0caa2e4aa616ae7df8127e10f6586940aa683a496c2c280b9/kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0", size = 1435109 }, + { url = "https://files.pythonhosted.org/packages/13/fc/e756382cb64e556af6c1809a1bbb22c141bbc2445049f2da06b420fe52bf/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab", size = 2245269 }, + { url = "https://files.pythonhosted.org/packages/76/15/e59e45829d7f41c776d138245cabae6515cb4eb44b418f6d4109c478b481/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc", size = 2393468 }, + { url = "https://files.pythonhosted.org/packages/e9/39/483558c2a913ab8384d6e4b66a932406f87c95a6080112433da5ed668559/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25", size = 2355394 }, + { url = "https://files.pythonhosted.org/packages/01/aa/efad1fbca6570a161d29224f14b082960c7e08268a133fe5dc0f6906820e/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc", size = 2490901 }, + { url = "https://files.pythonhosted.org/packages/c9/4f/15988966ba46bcd5ab9d0c8296914436720dd67fca689ae1a75b4ec1c72f/kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67", size = 2312306 }, + { url = "https://files.pythonhosted.org/packages/2d/27/bdf1c769c83f74d98cbc34483a972f221440703054894a37d174fba8aa68/kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34", size = 71966 }, + { url = "https://files.pythonhosted.org/packages/4a/c9/9642ea855604aeb2968a8e145fc662edf61db7632ad2e4fb92424be6b6c0/kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2", size = 65311 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156 }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555 }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071 }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053 }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278 }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139 }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517 }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952 }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132 }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997 }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060 }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471 }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793 }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855 }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430 }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294 }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736 }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194 }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341 }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455 }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138 }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857 }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129 }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538 }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661 }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710 }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213 }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097 }, +] + +[[package]] +name = "lifelines" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "autograd" }, + { name = "autograd-gamma" }, + { name = "formulaic" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/4f/f0b363278d40baf7d7a03217bee839cb880946c62109f243391c8754bb09/lifelines-0.30.0.tar.gz", hash = "sha256:f7f6f6275fcb167fe0f5b1ef98f868993f9c074cb74b1dd6e92736efa854be18", size = 383221 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/f7/379e185a75ac8166ac70756d0ba68d9a2b02b555c7fde4983246752396bd/lifelines-0.30.0-py3-none-any.whl", hash = "sha256:ac7c602c8aceced9770d3977817c9d99c250ed8cd86f2567fa0d23e4e8014bf9", size = 349319 }, +] + +[[package]] +name = "lightning" +version = "2.5.1.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/d0/fb3c5077efdd74c28ea3a277fd80bbf03738d866013a8637691138bfebca/lightning-2.5.1.post0.tar.gz", hash = "sha256:fda1ac63c283b3b08a54be8d905dd88469cf09e9845d36dd28b699e78911cbc8", size = 631113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/1b/67201d693a575e8a086831710f33e697fab66166223f792e459ef2b84934/lightning-2.5.1.post0-py3-none-any.whl", hash = "sha256:a228a52ca52f0c5006ff327c92b8942f09e1aea3f2d9b0d7c8a209edd5b9e71d", size = 819001 }, +] + +[[package]] +name = "lightning-utilities" +version = "0.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/bb/63a6a8c9e7a96b6ba92647fa5b1595c2dbee29f8178705adb4704d82ecba/lightning_utilities-0.14.3.tar.gz", hash = "sha256:37e2f83f273890052955a44054382c211a303012ee577619efbaa5df9e65e9f5", size = 30346 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/c1/31b3184cba7b257a4a3b5ca5b88b9204ccb7aa02fe3c992280899293ed54/lightning_utilities-0.14.3-py3-none-any.whl", hash = "sha256:4ab9066aa36cd7b93a05713808901909e96cc3f187ea6fd3052b2fd91313b468", size = 28894 }, +] + +[[package]] +name = "madeleine" +version = "0.0.1" +source = { git = "https://github.com/mahmoodlab/MADELEINE.git?rev=de7c85acc2bdad352e6df8eee5694f8b6f288012#de7c85acc2bdad352e6df8eee5694f8b6f288012" } +dependencies = [ + { name = "einops" }, + { name = "geopandas" }, + { name = "h5py" }, + { name = "huggingface-hub" }, + { name = "scikit-learn" }, + { name = "shapely" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "wandb" }, +] + +[[package]] +name = "mamba-ssm" +version = "2.2.4" +source = { git = "https://github.com/KatherLab/mamba.git?rev=ffef06879361ea36893697d88f43d4f76bb28877#ffef06879361ea36893697d88f43d4f76bb28877" } +dependencies = [ + { name = "einops" }, + { name = "ninja" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "torch" }, + { name = "transformers" }, + { name = "triton", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "triton", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'aarch64' or sys_platform != 'linux'" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { 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/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "marshmallow" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/ff/26df5a9f5ac57ccf693a5854916ab47243039d2aa9e0fe5f5a0331e7b74b/marshmallow-4.0.0.tar.gz", hash = "sha256:3b6e80aac299a7935cfb97ed01d1854fb90b5079430969af92118ea1b12a8d55", size = 220507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/26/6cc45d156f44dbe1d5696d9e54042e4dcaf7b946c0b86df6a97d29706f32/marshmallow-4.0.0-py3-none-any.whl", hash = "sha256:e7b0528337e9990fd64950f8a6b3a1baabed09ad17a0dfb844d701151f92d203", size = 48420 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +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/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873 }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205 }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823 }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464 }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103 }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492 }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689 }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466 }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252 }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321 }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972 }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954 }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318 }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132 }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633 }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031 }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988 }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034 }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223 }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985 }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109 }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082 }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699 }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044 }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + +[[package]] +name = "monai" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/61/dc84aca9934c823dbbb46d6f5b4b09f461afa744ea092b66c591e033cff9/monai-1.5.0.tar.gz", hash = "sha256:8c5e4555839812fe060e13da34d9b3342d750d8ad5934f1b79f9b08eb6b35d64", size = 1676883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/96/f8ede335fa3501c57ec67163ef4d113427effc5ccfc8907c0bb58abe5e2d/monai-1.5.0-py3-none-any.whl", hash = "sha256:93259cfa8b68fbf006dea7c78376a46ce38f369bf8b20b36f74ab1d3f484d37b", size = 2659521 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "multidict" +version = "6.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515 }, + { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609 }, + { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871 }, + { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661 }, + { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422 }, + { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447 }, + { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455 }, + { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666 }, + { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392 }, + { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969 }, + { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433 }, + { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418 }, + { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042 }, + { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280 }, + { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322 }, + { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070 }, + { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667 }, + { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293 }, + { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096 }, + { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214 }, + { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686 }, + { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061 }, + { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412 }, + { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563 }, + { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811 }, + { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524 }, + { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012 }, + { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765 }, + { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888 }, + { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041 }, + { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046 }, + { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106 }, + { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351 }, + { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791 }, + { url = "https://files.pythonhosted.org/packages/df/2a/e166d2ffbf4b10131b2d5b0e458f7cee7d986661caceae0de8753042d4b2/multidict-6.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82ffabefc8d84c2742ad19c37f02cde5ec2a1ee172d19944d380f920a340e4b9", size = 64123 }, + { url = "https://files.pythonhosted.org/packages/8c/96/e200e379ae5b6f95cbae472e0199ea98913f03d8c9a709f42612a432932c/multidict-6.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a2f58a66fe2c22615ad26156354005391e26a2f3721c3621504cd87c1ea87bf", size = 38049 }, + { url = "https://files.pythonhosted.org/packages/75/fb/47afd17b83f6a8c7fa863c6d23ac5ba6a0e6145ed8a6bcc8da20b2b2c1d2/multidict-6.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5883d6ee0fd9d8a48e9174df47540b7545909841ac82354c7ae4cbe9952603bd", size = 37078 }, + { url = "https://files.pythonhosted.org/packages/fa/70/1af3143000eddfb19fd5ca5e78393985ed988ac493bb859800fe0914041f/multidict-6.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abcf56a9511653fa1d052bfc55fbe53dbee8f34e68bd6a5a038731b0ca42d15", size = 224097 }, + { url = "https://files.pythonhosted.org/packages/b1/39/d570c62b53d4fba844e0378ffbcd02ac25ca423d3235047013ba2f6f60f8/multidict-6.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6ed5ae5605d4ad5a049fad2a28bb7193400700ce2f4ae484ab702d1e3749c3f9", size = 230768 }, + { url = "https://files.pythonhosted.org/packages/fd/f8/ed88f2c4d06f752b015933055eb291d9bc184936903752c66f68fb3c95a7/multidict-6.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbfcb60396f9bcfa63e017a180c3105b8c123a63e9d1428a36544e7d37ca9e20", size = 231331 }, + { url = "https://files.pythonhosted.org/packages/9c/6f/8e07cffa32f483ab887b0d56bbd8747ac2c1acd00dc0af6fcf265f4a121e/multidict-6.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f1987787f5f1e2076b59692352ab29a955b09ccc433c1f6b8e8e18666f608b", size = 230169 }, + { url = "https://files.pythonhosted.org/packages/e6/2b/5dcf173be15e42f330110875a2668ddfc208afc4229097312212dc9c1236/multidict-6.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0121ccce8c812047d8d43d691a1ad7641f72c4f730474878a5aeae1b8ead8c", size = 222947 }, + { url = "https://files.pythonhosted.org/packages/39/75/4ddcbcebe5ebcd6faa770b629260d15840a5fc07ce8ad295a32e14993726/multidict-6.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ec4967114295b8afd120a8eec579920c882831a3e4c3331d591a8e5bfbbc0f", size = 215761 }, + { url = "https://files.pythonhosted.org/packages/6a/c9/55e998ae45ff15c5608e384206aa71a11e1b7f48b64d166db400b14a3433/multidict-6.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:995f985e2e268deaf17867801b859a282e0448633f1310e3704b30616d269d69", size = 227605 }, + { url = "https://files.pythonhosted.org/packages/04/49/c2404eac74497503c77071bd2e6f88c7e94092b8a07601536b8dbe99be50/multidict-6.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d832c608f94b9f92a0ec8b7e949be7792a642b6e535fcf32f3e28fab69eeb046", size = 226144 }, + { url = "https://files.pythonhosted.org/packages/62/c5/0cd0c3c6f18864c40846aa2252cd69d308699cb163e1c0d989ca301684da/multidict-6.4.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d21c1212171cf7da703c5b0b7a0e85be23b720818aef502ad187d627316d5645", size = 221100 }, + { url = "https://files.pythonhosted.org/packages/71/7b/f2f3887bea71739a046d601ef10e689528d4f911d84da873b6be9194ffea/multidict-6.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cbebaa076aaecad3d4bb4c008ecc73b09274c952cf6a1b78ccfd689e51f5a5b0", size = 232731 }, + { url = "https://files.pythonhosted.org/packages/e5/b3/d9de808349df97fa75ec1372758701b5800ebad3c46ae377ad63058fbcc6/multidict-6.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c93a6fb06cc8e5d3628b2b5fda215a5db01e8f08fc15fadd65662d9b857acbe4", size = 229637 }, + { url = "https://files.pythonhosted.org/packages/5e/57/13207c16b615eb4f1745b44806a96026ef8e1b694008a58226c2d8f5f0a5/multidict-6.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8cd8f81f1310182362fb0c7898145ea9c9b08a71081c5963b40ee3e3cac589b1", size = 225594 }, + { url = "https://files.pythonhosted.org/packages/3a/e4/d23bec2f70221604f5565000632c305fc8f25ba953e8ce2d8a18842b9841/multidict-6.4.4-cp313-cp313-win32.whl", hash = "sha256:3e9f1cd61a0ab857154205fb0b1f3d3ace88d27ebd1409ab7af5096e409614cd", size = 35359 }, + { url = "https://files.pythonhosted.org/packages/a7/7a/cfe1a47632be861b627f46f642c1d031704cc1c0f5c0efbde2ad44aa34bd/multidict-6.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb40b74400e4455785c2fa37eba434269149ec525fc8329858c862e4b35373", size = 38903 }, + { url = "https://files.pythonhosted.org/packages/68/7b/15c259b0ab49938a0a1c8f3188572802704a779ddb294edc1b2a72252e7c/multidict-6.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6a602151dbf177be2450ef38966f4be3467d41a86c6a845070d12e17c858a156", size = 68895 }, + { url = "https://files.pythonhosted.org/packages/f1/7d/168b5b822bccd88142e0a3ce985858fea612404edd228698f5af691020c9/multidict-6.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d2b9712211b860d123815a80b859075d86a4d54787e247d7fbee9db6832cf1c", size = 40183 }, + { url = "https://files.pythonhosted.org/packages/e0/b7/d4b8d98eb850ef28a4922ba508c31d90715fd9b9da3801a30cea2967130b/multidict-6.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d2fa86af59f8fc1972e121ade052145f6da22758f6996a197d69bb52f8204e7e", size = 39592 }, + { url = "https://files.pythonhosted.org/packages/18/28/a554678898a19583548e742080cf55d169733baf57efc48c2f0273a08583/multidict-6.4.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50855d03e9e4d66eab6947ba688ffb714616f985838077bc4b490e769e48da51", size = 226071 }, + { url = "https://files.pythonhosted.org/packages/ee/dc/7ba6c789d05c310e294f85329efac1bf5b450338d2542498db1491a264df/multidict-6.4.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5bce06b83be23225be1905dcdb6b789064fae92499fbc458f59a8c0e68718601", size = 222597 }, + { url = "https://files.pythonhosted.org/packages/24/4f/34eadbbf401b03768dba439be0fb94b0d187facae9142821a3d5599ccb3b/multidict-6.4.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66ed0731f8e5dfd8369a883b6e564aca085fb9289aacabd9decd70568b9a30de", size = 228253 }, + { url = "https://files.pythonhosted.org/packages/c0/e6/493225a3cdb0d8d80d43a94503fc313536a07dae54a3f030d279e629a2bc/multidict-6.4.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:329ae97fc2f56f44d91bc47fe0972b1f52d21c4b7a2ac97040da02577e2daca2", size = 226146 }, + { url = "https://files.pythonhosted.org/packages/2f/70/e411a7254dc3bff6f7e6e004303b1b0591358e9f0b7c08639941e0de8bd6/multidict-6.4.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c27e5dcf520923d6474d98b96749e6805f7677e93aaaf62656005b8643f907ab", size = 220585 }, + { url = "https://files.pythonhosted.org/packages/08/8f/beb3ae7406a619100d2b1fb0022c3bb55a8225ab53c5663648ba50dfcd56/multidict-6.4.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:058cc59b9e9b143cc56715e59e22941a5d868c322242278d28123a5d09cdf6b0", size = 212080 }, + { url = "https://files.pythonhosted.org/packages/9c/ec/355124e9d3d01cf8edb072fd14947220f357e1c5bc79c88dff89297e9342/multidict-6.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:69133376bc9a03f8c47343d33f91f74a99c339e8b58cea90433d8e24bb298031", size = 226558 }, + { url = "https://files.pythonhosted.org/packages/fd/22/d2b95cbebbc2ada3be3812ea9287dcc9712d7f1a012fad041770afddb2ad/multidict-6.4.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6b15c55721b1b115c5ba178c77104123745b1417527ad9641a4c5e2047450f0", size = 212168 }, + { url = "https://files.pythonhosted.org/packages/4d/c5/62bfc0b2f9ce88326dbe7179f9824a939c6c7775b23b95de777267b9725c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a887b77f51d3d41e6e1a63cf3bc7ddf24de5939d9ff69441387dfefa58ac2e26", size = 217970 }, + { url = "https://files.pythonhosted.org/packages/79/74/977cea1aadc43ff1c75d23bd5bc4768a8fac98c14e5878d6ee8d6bab743c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:632a3bf8f1787f7ef7d3c2f68a7bde5be2f702906f8b5842ad6da9d974d0aab3", size = 226980 }, + { url = "https://files.pythonhosted.org/packages/48/fc/cc4a1a2049df2eb84006607dc428ff237af38e0fcecfdb8a29ca47b1566c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a145c550900deb7540973c5cdb183b0d24bed6b80bf7bddf33ed8f569082535e", size = 220641 }, + { url = "https://files.pythonhosted.org/packages/3b/6a/a7444d113ab918701988d4abdde373dbdfd2def7bd647207e2bf645c7eac/multidict-6.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d83c6619ca5c9672cb78b39ed8542f1975a803dee2cda114ff73cbb076edd", size = 221728 }, + { url = "https://files.pythonhosted.org/packages/2b/b0/fdf4c73ad1c55e0f4dbbf2aa59dd37037334091f9a4961646d2b7ac91a86/multidict-6.4.4-cp313-cp313t-win32.whl", hash = "sha256:3312f63261b9df49be9d57aaa6abf53a6ad96d93b24f9cc16cf979956355ce6e", size = 41913 }, + { url = "https://files.pythonhosted.org/packages/8e/92/27989ecca97e542c0d01d05a98a5ae12198a243a9ee12563a0313291511f/multidict-6.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:ba852168d814b2c73333073e1c7116d9395bea69575a01b0b3c89d2d5a87c8fb", size = 46112 }, + { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481 }, +] + +[[package]] +name = "musk" +version = "1.0.0" +source = { git = "https://github.com/lilab-stanford/MUSK.git?rev=e1699c27687f44bbf6d4adfcbb2abe89795d347f#e1699c27687f44bbf6d4adfcbb2abe89795d347f" } + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406 }, +] + +[[package]] +name = "ninja" +version = "1.11.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/2c/d717d13a413d6f7579cdaa1e28e6e2c98de95461549b08d311c8a5bf4c51/ninja-1.11.1.1.tar.gz", hash = "sha256:9d793b08dd857e38d0b6ffe9e6b7145d7c485a42dcfea04905ca0cdb6017cc3c", size = 132392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/6e/04ed11bb244039908f6f212cb5f3e97933e238655248e4ce307c1687ba1f/ninja-1.11.1.1-py2.py3-none-macosx_10_9_universal2.macosx_10_9_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:376889c76d87b95b5719fdd61dd7db193aa7fd4432e5d52d2e44e4c497bdbbee", size = 270611 }, + { url = "https://files.pythonhosted.org/packages/2c/52/0e5423311eb9939b6f9354059a6d88a6211eb4fa1c7a4ef303ecee1c1fe0/ninja-1.11.1.1-py2.py3-none-manylinux1_i686.manylinux_2_5_i686.whl", hash = "sha256:ecf80cf5afd09f14dcceff28cb3f11dc90fb97c999c89307aea435889cb66877", size = 324256 }, + { url = "https://files.pythonhosted.org/packages/6d/92/8d7aebd4430ab5ff65df2bfee6d5745f95c004284db2d8ca76dcbfd9de47/ninja-1.11.1.1-py2.py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:84502ec98f02a037a169c4b0d5d86075eaf6afc55e1879003d6cab51ced2ea4b", size = 307194 }, + { url = "https://files.pythonhosted.org/packages/01/c8/96424839fd127b4492229acf50763ed9940d864ca35d17d151934aef1f6f/ninja-1.11.1.1-py2.py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:73b93c14046447c7c5cc892433d4fae65d6364bec6685411cb97a8bcf815f93a", size = 155643 }, + { url = "https://files.pythonhosted.org/packages/6b/fa/5ca8e65a98cdb9a71d4f1e38cac7bd757bbb9555a5aef5a4d293aa890e5c/ninja-1.11.1.1-py2.py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18302d96a5467ea98b68e1cae1ae4b4fb2b2a56a82b955193c637557c7273dbd", size = 179538 }, + { url = "https://files.pythonhosted.org/packages/45/ef/60086f02cbc6882da00a02c81d645cefd8d2d65b01fade41b873d8dd85a2/ninja-1.11.1.1-py2.py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aad34a70ef15b12519946c5633344bc775a7656d789d9ed5fdb0d456383716ef", size = 156217 }, + { url = "https://files.pythonhosted.org/packages/1c/00/2fd13ac6aafdb566f00d6b541101fca54e58ae58bf96c00f9780df019607/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:d491fc8d89cdcb416107c349ad1e3a735d4c4af5e1cb8f5f727baca6350fdaea", size = 372069 }, + { url = "https://files.pythonhosted.org/packages/ad/5d/6e97c8a25167d4867694c7fb0b9bdbc9b096d6479c8e56c5bd41b49613f6/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:7563ce1d9fe6ed5af0b8dd9ab4a214bf4ff1f2f6fd6dc29f480981f0f8b8b249", size = 418859 }, + { url = "https://files.pythonhosted.org/packages/43/78/34af88d753389a9412438d16142c77e587e0d69152faf0bbf99701063dd8/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:9df724344202b83018abb45cb1efc22efd337a1496514e7e6b3b59655be85205", size = 419782 }, + { url = "https://files.pythonhosted.org/packages/3b/74/de0633f8bced3b188942fca64a950e8f2206c60c10c97af465b356ae9b25/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:3e0f9be5bb20d74d58c66cc1c414c3e6aeb45c35b0d0e41e8d739c2c0d57784f", size = 415476 }, + { url = "https://files.pythonhosted.org/packages/9a/f3/3e4a56ff77739d1582749b93497bdebf11e003fbc7a66363ef6c772ebd0a/ninja-1.11.1.1-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:76482ba746a2618eecf89d5253c0d1e4f1da1270d41e9f54dfbd91831b0f6885", size = 379229 }, + { url = "https://files.pythonhosted.org/packages/c5/ee/53df34fcc9c0b1db62b2f2e2c848e28d9354e1c7f0dce029ee50b16ca157/ninja-1.11.1.1-py2.py3-none-win32.whl", hash = "sha256:fa2ba9d74acfdfbfbcf06fad1b8282de8a7a8c481d9dee45c859a8c93fcc1082", size = 265049 }, + { url = "https://files.pythonhosted.org/packages/b6/2f/a3bc50fa63fc4fe9348e15b53dc8c87febfd4e0c660fcf250c4b19a3aa3b/ninja-1.11.1.1-py2.py3-none-win_amd64.whl", hash = "sha256:95da904130bfa02ea74ff9c0116b4ad266174fafb1c707aa50212bc7859aebf1", size = 312958 }, + { url = "https://files.pythonhosted.org/packages/73/2a/f5b7b3b7ecd5cf4e31375580bf5c6a01a328ed1ebdfff90fab463e3f4bc7/ninja-1.11.1.1-py2.py3-none-win_arm64.whl", hash = "sha256:185e0641bde601e53841525c4196278e9aaf4463758da6dd1e752c0a0f54136a", size = 272686 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "numexpr" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/8f/2cc977e91adbfbcdb6b49fdb9147e1d1c7566eb2c0c1e737e9a47020b5ca/numexpr-2.11.0.tar.gz", hash = "sha256:75b2c01a4eda2e7c357bc67a3f5c3dd76506c15b5fd4dc42845ef2e182181bad", size = 108960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/d1/1cf8137990b3f3d445556ed63b9bc347aec39bde8c41146b02d3b35c1adc/numexpr-2.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:450eba3c93c3e3e8070566ad8d70590949d6e574b1c960bf68edd789811e7da8", size = 147535 }, + { url = "https://files.pythonhosted.org/packages/b6/5e/bac7649d043f47c7c14c797efe60dbd19476468a149399cd706fe2e47f8c/numexpr-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0eb88dbac8a7e61ee433006d0ddfd6eb921f5c6c224d1b50855bc98fb304c44", size = 136710 }, + { url = "https://files.pythonhosted.org/packages/1b/9f/c88fc34d82d23c66ea0b78b00a1fb3b64048e0f7ac7791b2cd0d2a4ce14d/numexpr-2.11.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a194e3684b3553ea199c3f4837f422a521c7e2f0cce13527adc3a6b4049f9e7c", size = 411169 }, + { url = "https://files.pythonhosted.org/packages/e4/8d/4d78dad430b41d836146f9e6f545f5c4f7d1972a6aa427d8570ab232bf16/numexpr-2.11.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f677668ab2bb2452fee955af3702fbb3b71919e61e4520762b1e5f54af59c0d8", size = 401671 }, + { url = "https://files.pythonhosted.org/packages/83/1c/414670eb41a82b78bd09769a4f5fb49a934f9b3990957f02c833637a511e/numexpr-2.11.0-cp311-cp311-win32.whl", hash = "sha256:7d9e76a77c9644fbd60da3984e516ead5b84817748c2da92515cd36f1941a04d", size = 153159 }, + { url = "https://files.pythonhosted.org/packages/0c/97/8d00ca9b36f3ac68a8fd85e930ab0c9448d8c9ca7ce195ee75c188dabd45/numexpr-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7163b488bfdcd13c300a8407c309e4cee195ef95d07facf5ac2678d66c988805", size = 146224 }, + { url = "https://files.pythonhosted.org/packages/38/45/7a0e5a0b800d92e73825494ac695fa05a52c7fc7088d69a336880136b437/numexpr-2.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4229060be866813122385c608bbd3ea48fe0b33e91f2756810d28c1cdbfc98f1", size = 147494 }, + { url = "https://files.pythonhosted.org/packages/74/46/3a26b84e44f4739ec98de0ede4b95b4b8096f721e22d0e97517eeb02017e/numexpr-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:097aa8835d32d6ac52f2be543384019b4b134d1fb67998cbfc4271155edfe54a", size = 136832 }, + { url = "https://files.pythonhosted.org/packages/75/05/e3076ff25d4a108b47640c169c0a64811748c43b63d9cc052ea56de1631e/numexpr-2.11.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f082321c244ff5d0e252071fb2c4fe02063a45934144a1456a5370ca139bec2", size = 412618 }, + { url = "https://files.pythonhosted.org/packages/70/e8/15e0e077a004db0edd530da96c60c948689c888c464ee5d14b82405ebd86/numexpr-2.11.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7a19435ca3d7dd502b8d8dce643555eb1b6013989e3f7577857289f6db6be16", size = 403363 }, + { url = "https://files.pythonhosted.org/packages/10/14/f22afb3a7ae41d03ba87f62d00fbcfb76389f9cc91b7a82593c39c509318/numexpr-2.11.0-cp312-cp312-win32.whl", hash = "sha256:f326218262c8d8537887cc4bbd613c8409d62f2cac799835c0360e0d9cefaa5c", size = 153307 }, + { url = "https://files.pythonhosted.org/packages/18/70/abc585269424582b3cd6db261e33b2ec96b5d4971da3edb29fc9b62a8926/numexpr-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a184e5930c77ab91dd9beee4df403b825cd9dfc4e9ba4670d31c9fcb4e2c08e", size = 146337 }, + { url = "https://files.pythonhosted.org/packages/74/63/dbf4fb6c48006d413a82db138d03c3c007d0ed0684f693c4b77196448660/numexpr-2.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eb766218abad05c7c3ddad5367d0ec702d6152cb4a48d9fd56a6cef6abade70c", size = 147495 }, + { url = "https://files.pythonhosted.org/packages/3a/e4/2fbbf5b9121f54722dc4d4dfc75bc0b4e8ee2675f92ec86ee5697aecc53f/numexpr-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2036be213a6a1b5ce49acf60de99b911a0f9d174aab7679dde1fae315134f826", size = 136839 }, + { url = "https://files.pythonhosted.org/packages/a8/3f/aa36415919c90f712a11127eaa7c0c8d045768d62a484a29364e4801c383/numexpr-2.11.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:096ec768bee2ef14ac757b4178e3c5f05e5f1cb6cae83b2eea9b4ba3ec1a86dd", size = 416240 }, + { url = "https://files.pythonhosted.org/packages/b9/7d/4911f40d3610fc5557029f0d1f20ef9f571488319567ac4d8ee6d0978ee6/numexpr-2.11.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1719788a787808c15c9bb98b6ff0c97d64a0e59c1a6ebe36d4ae4d7c5c09b95", size = 406641 }, + { url = "https://files.pythonhosted.org/packages/6f/bc/d00e717e77691c410c6c461d7880b4c498896874316acc0e044d7eafacbf/numexpr-2.11.0-cp313-cp313-win32.whl", hash = "sha256:6b5fdfc86cbf5373ea67d554cc6f08863825ea8e928416bed8d5285e387420c6", size = 153313 }, + { url = "https://files.pythonhosted.org/packages/52/a2/93346789e6d73a76fdb68171904ade25c112f25df363a8f602c6b21bc220/numexpr-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ff337b36db141a1a0b49f01282783744f49f0d401cc83a512fc5596eb7db5c6", size = 146340 }, + { url = "https://files.pythonhosted.org/packages/0b/20/c0e3aaf3cc4497e5253df2523a55c83b9d316cb5c9d5caaa4a1156cef6e3/numexpr-2.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b9854fa70edbe93242b8bb4840e58d1128c45766d9a70710f05b4f67eb0feb6e", size = 148206 }, + { url = "https://files.pythonhosted.org/packages/de/49/22fd38ac990ba333f25b771305a5ffcd98c771f4d278868661ffb26deac1/numexpr-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:321736cb98f090ce864b58cc5c37661cb5548e394e0fe24d5f2c7892a89070c3", size = 137573 }, + { url = "https://files.pythonhosted.org/packages/fb/1e/50074e472e9e6bea4fe430869708d9ede333a187d8d0740e70d5a9560aad/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5cc434eb4a4df2fe442bcc50df114e82ff7aa234657baf873b2c9cf3f851e8e", size = 426674 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/7ccbc72b950653df62d29e2531c811ed80cfff93c927a5bfd86a71edb4da/numexpr-2.11.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:238d19465a272ada3967600fada55e4c6900485aefb42122a78dfcaf2efca65f", size = 416037 }, + { url = "https://files.pythonhosted.org/packages/31/7c/bbccad2734dd4b251cc6bdff8cf5ded18b5383f5a05aa8de7bf02acbb65b/numexpr-2.11.0-cp313-cp313t-win32.whl", hash = "sha256:0db4c2dcad09f9594b45fce794f4b903345195a8c216e252de2aa92884fd81a8", size = 153967 }, + { url = "https://files.pythonhosted.org/packages/75/d7/41287384e413e8d20457d35e264d9c9754e65eb13a988af51ceb7057f61b/numexpr-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a69b5c02014448a412012752dc46091902d28932c3be0c6e02e73cecceffb700", size = 147207 }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743 }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616 }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579 }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005 }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570 }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548 }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521 }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866 }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455 }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348 }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362 }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103 }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382 }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462 }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618 }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511 }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783 }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506 }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190 }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828 }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006 }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765 }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736 }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719 }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072 }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213 }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632 }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532 }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885 }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467 }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144 }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217 }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014 }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935 }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122 }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143 }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260 }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225 }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374 }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.4.5.8" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, + { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, + { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, + { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, + { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.2.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, + { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.5.147" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, + { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.6.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, + { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.3.1.170" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, + { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/8e/675498726c605c9441cf46653bd29cb1b8666da1fb1469ffa25f67f20c58/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:067a7f6d03ea0d4841c85f0c6f1991c5dda98211f6302cb83a4ab234ee95bef8", size = 149422781 }, + { url = "https://files.pythonhosted.org/packages/78/a8/bcbb63b53a4b1234feeafb65544ee55495e1bb37ec31b999b963cbccfd1d/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9", size = 150057751 }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.21.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, + { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.4.127" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322 }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197 }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439 }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044 }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, +] + +[[package]] +name = "openslide-bin" +version = "4.0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/49/0a62d150b620bf045ddf1e70ae90e42e423aefdc07b4927d846faefcca19/openslide-bin-4.0.0.8.tar.gz", hash = "sha256:bae3b5e374ada9d6b5110d19f60a71e1d164578be98c46c0761c028dec8949e4", size = 17872279 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/4b/d527b317dddd9fe17addf16a49eed0907943590228ccf2d2f535e541e928/openslide_bin-4.0.0.8-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9389cf7bb1b4ecd9e79e6d00151ae6a43a1e8b07ce7119609324d9260345f3c7", size = 5372459 }, + { url = "https://files.pythonhosted.org/packages/8c/be/f4028886d0e2bc4f4114bfa929427ec29a6be86831327f131d423fde041c/openslide_bin-4.0.0.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:79c94c39a48d497caa39ecdad5ef64a931b31c6592354a142497bc3db2e88494", size = 4221147 }, + { url = "https://files.pythonhosted.org/packages/d7/a0/7255bfd87c47b18f29fe78719b1e9fd075624c8d7cb957d65b951468cc38/openslide_bin-4.0.0.8-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:d9cb149af1a5c2eddbc0e3b75f552d932a840689f011d53724912de37071cc40", size = 4327171 }, + { url = "https://files.pythonhosted.org/packages/b1/05/c01a0c145ba88b19acd9874693774e6bedb7aa1bb1fa4c0b40e1bc42851e/openslide_bin-4.0.0.8-py3-none-win_amd64.whl", hash = "sha256:a06abf5b6f7807fa7aad2b240496a1d825cbd885267d521458eacbcfd782ed44", size = 4176324 }, +] + +[[package]] +name = "openslide-python" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/c7/1b79b6f31aa23d33ae93d7fd34fdf0d03569dbfa35a0689be8ce27471c05/openslide_python-1.4.2.tar.gz", hash = "sha256:610d7a71552afe7be33038c10d8a2b20b9824ca2836710870e81f193257411bc", size = 386361 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/12/b6e8ec85746f02aa5433cf2eaf2493ef8cdf5ef0a8675278a0263db6d88e/openslide_python-1.4.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0f459f0e071048a5e3cafbf814fdac92da55df9b07730a832b54b99191f8a612", size = 33187 }, + { url = "https://files.pythonhosted.org/packages/c5/2f/d3004b89dc7810db756c367cf6c95040864f79323dc25e8f9bbd0cf71c94/openslide_python-1.4.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b24566c2167d5909b4f54d5ee53c1caada5ec1aa14921a19de67a694ed988765", size = 37375 }, + { url = "https://files.pythonhosted.org/packages/82/75/664b94930660f124a36d15afd8e7c4b56d8c9b3e36c0cbe76311aaaa65b0/openslide_python-1.4.2-cp311-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69cc23d608120bc528399d006c47501ac5e390770d4d5090800defd51be1b595", size = 36794 }, + { url = "https://files.pythonhosted.org/packages/7f/5d/5e8825f93cfa305fb2e601b947dd55741bad49fddf12e43bf3b8f07100aa/openslide_python-1.4.2-cp311-abi3-win_amd64.whl", hash = "sha256:e53cfb967c0987c76a9b890bb9551e238b12a99d947362434a08d4aa40ba1739", size = 35385 }, +] + +[[package]] +name = "osqp" +version = "0.6.7.post3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "qdldl" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/35/45d4d1832b31d207f83e0f9734d041be125fb4f0dff49413674bd1b08032/osqp-0.6.7.post3.tar.gz", hash = "sha256:b0c5e0a721f21c9724097a4fd50108304d296468d124e16f34ac67046f7020e1", size = 229274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/dd/123079f0ad8409d3be9074344a3d45073ce928f701890f010ab506ffee9f/osqp-0.6.7.post3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1a1dcd869fd6ac501e06262c21483a3691b6281e4f3f65af6951330958b89ca", size = 251844 }, + { url = "https://files.pythonhosted.org/packages/cd/6d/0d17e8fa61809c125f97685d86e6cd6f7b1e745e01b8d3f96d783c8de41b/osqp-0.6.7.post3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46b93d1110dc0ad311f6691c4df9ee41cbbde5ffc0d8c8d520d4555bf5d8765b", size = 237567 }, + { url = "https://files.pythonhosted.org/packages/4f/74/d748a9f42426fa48ab0139d0738988296a3c599a6b3c78395258d02e436b/osqp-0.6.7.post3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5209104d6fe3ace4fdbf9ace08caa2cba9de1e7ccd5f56279a346c235917138b", size = 293855 }, + { url = "https://files.pythonhosted.org/packages/55/72/8746c4bc488a31641091ccc50e71f92e0a4211e2ef882e00904940531962/osqp-0.6.7.post3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfefa07740e9fb1c574cdc836e5afe2600b73c0c12089955d4ae6587c55f0eb", size = 298312 }, + { url = "https://files.pythonhosted.org/packages/f8/7b/ec42030f389c1b2a7e5517d4ba4a169f1d8fb6f4beb92c5b457e0cc284e4/osqp-0.6.7.post3-cp311-cp311-win_amd64.whl", hash = "sha256:c48c91dfba02ce11e8b8f5d401ec5b67a316782bfdf4f53ca753e49907f7387f", size = 293043 }, + { url = "https://files.pythonhosted.org/packages/22/26/4cf65e82cf63c4f4ff5186618c006d95a1a5bc9f4f015563ad6d87d75a42/osqp-0.6.7.post3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:023af06764f7aba9c64536ecb7204019906bb7e78237f335f82b404f16623eef", size = 252062 }, + { url = "https://files.pythonhosted.org/packages/ce/bc/ece5348baef40bf355c5ef8000103aaf77973060f4c940da9cce0999e00d/osqp-0.6.7.post3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cec7cb5bf1615c4129277275dc08e20a037372a874cff35eb891b4b35a463de", size = 237577 }, + { url = "https://files.pythonhosted.org/packages/31/33/f09c305591606e59edc5f09aa5cba3606c0e29e7b0fff42d044585bcc1f4/osqp-0.6.7.post3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb882ab24b97b14843b7c71d2474fb8b415bafc8dd60aa94870c2ef338c20bfb", size = 295407 }, + { url = "https://files.pythonhosted.org/packages/ef/63/356f01888eb0e4cd8603eb8b7711a6865e26bc2d9a1882a1e4562333debd/osqp-0.6.7.post3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:502fde0ae710cef1e6418fb8d26efef9597d1dcba877489a1c2eb9c3eb2ff2e9", size = 300002 }, + { url = "https://files.pythonhosted.org/packages/57/b5/958d4188cb9347e420d3de2d19d8cb1113f691b7a093cdef67f86b598f30/osqp-0.6.7.post3-cp312-cp312-win_amd64.whl", hash = "sha256:468588cfb690becba4d1f460c2a53e75530584e3efcf2caed59f5219032e6888", size = 293164 }, + { url = "https://files.pythonhosted.org/packages/94/78/f01a209777678e94546b8c43c12a28db16094dfeea689e9db6d59d3b89ad/osqp-0.6.7.post3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cee478eedf9cfad11ff9c27ef0b1e032506a16888b8b874f622816cf8749db7f", size = 251946 }, + { url = "https://files.pythonhosted.org/packages/05/89/2d2dc40ebe25f92901d52d706bf8f31ea5718570e55828f1000fd380911f/osqp-0.6.7.post3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5dd739c4c6c91e40d2e3ea2bb78c635c897e07697ab24a46d3a5d197e254b0f3", size = 237548 }, + { url = "https://files.pythonhosted.org/packages/65/d3/76c076599a290f6ed5b7ad3bd2c68fa0dd198ccd63fec17f004b859843e4/osqp-0.6.7.post3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:002f280f23d15ad3c6386a868688f0b17c90dba13d0f7f8da1c833a14fc4d7f8", size = 295476 }, + { url = "https://files.pythonhosted.org/packages/63/29/b8c9cf93a6a04399960e51c92c92a195d32b80a330c6a25a51d300b86e1a/osqp-0.6.7.post3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a2922fe8cb666964cf01b643da81eadf4bb435139a5f042d5bb6dcb87496778", size = 300072 }, + { url = "https://files.pythonhosted.org/packages/4b/0a/acd48ad432ccf2538972805095108801a3b29a2433b48bd3a34e640df1e4/osqp-0.6.7.post3-cp313-cp313-win_amd64.whl", hash = "sha256:acb219e941f5248da5de3ee9b70e6a5aaddf5f3989dffd1d4c03b0f7b1dfa17b", size = 293170 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { 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/68/08/3fbf4b98924c73037a8e8b4c2c774784805e0fb4ebca6c5bb60795c40125/pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70", size = 3198450 }, + { url = "https://files.pythonhosted.org/packages/84/92/6505b1af3d2849d5e714fc75ba9e69b7255c05ee42383a35a4d58f576b16/pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf", size = 3030550 }, + { url = "https://files.pythonhosted.org/packages/3c/8c/ac2f99d2a70ff966bc7eb13dacacfaab57c0549b2ffb351b6537c7840b12/pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7", size = 4415018 }, + { url = "https://files.pythonhosted.org/packages/1f/e3/0a58b5d838687f40891fff9cbaf8669f90c96b64dc8f91f87894413856c6/pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8", size = 4498006 }, + { url = "https://files.pythonhosted.org/packages/21/f5/6ba14718135f08fbfa33308efe027dd02b781d3f1d5c471444a395933aac/pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600", size = 4517773 }, + { url = "https://files.pythonhosted.org/packages/20/f2/805ad600fc59ebe4f1ba6129cd3a75fb0da126975c8579b8f57abeb61e80/pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788", size = 4607069 }, + { url = "https://files.pythonhosted.org/packages/71/6b/4ef8a288b4bb2e0180cba13ca0a519fa27aa982875882392b65131401099/pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e", size = 4583460 }, + { url = "https://files.pythonhosted.org/packages/62/ae/f29c705a09cbc9e2a456590816e5c234382ae5d32584f451c3eb41a62062/pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e", size = 4661304 }, + { url = "https://files.pythonhosted.org/packages/6e/1a/c8217b6f2f73794a5e219fbad087701f412337ae6dbb956db37d69a9bc43/pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6", size = 2331809 }, + { url = "https://files.pythonhosted.org/packages/e2/72/25a8f40170dc262e86e90f37cb72cb3de5e307f75bf4b02535a61afcd519/pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193", size = 2676338 }, + { url = "https://files.pythonhosted.org/packages/06/9e/76825e39efee61efea258b479391ca77d64dbd9e5804e4ad0fa453b4ba55/pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7", size = 2414918 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098 }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166 }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674 }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005 }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707 }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008 }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420 }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655 }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329 }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388 }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950 }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759 }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284 }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826 }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329 }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049 }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408 }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863 }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938 }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774 }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895 }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234 }, + { url = "https://files.pythonhosted.org/packages/a4/ad/2613c04633c7257d9481ab21d6b5364b59fc5d75faafd7cb8693523945a3/pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed", size = 3181734 }, + { url = "https://files.pythonhosted.org/packages/a4/fd/dcdda4471ed667de57bb5405bb42d751e6cfdd4011a12c248b455c778e03/pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c", size = 2999841 }, + { url = "https://files.pythonhosted.org/packages/ac/89/8a2536e95e77432833f0db6fd72a8d310c8e4272a04461fb833eb021bf94/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd", size = 3437470 }, + { url = "https://files.pythonhosted.org/packages/9d/8f/abd47b73c60712f88e9eda32baced7bfc3e9bd6a7619bb64b93acff28c3e/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076", size = 3460013 }, + { url = "https://files.pythonhosted.org/packages/f6/20/5c0a0aa83b213b7a07ec01e71a3d6ea2cf4ad1d2c686cc0168173b6089e7/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b", size = 3527165 }, + { url = "https://files.pythonhosted.org/packages/58/0e/2abab98a72202d91146abc839e10c14f7cf36166f12838ea0c4db3ca6ecb/pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f", size = 3571586 }, + { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 }, +] + +[[package]] +name = "propcache" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243 }, + { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503 }, + { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934 }, + { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633 }, + { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124 }, + { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283 }, + { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498 }, + { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486 }, + { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675 }, + { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727 }, + { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878 }, + { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558 }, + { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754 }, + { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088 }, + { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859 }, + { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153 }, + { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430 }, + { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637 }, + { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123 }, + { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031 }, + { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100 }, + { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170 }, + { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000 }, + { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262 }, + { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772 }, + { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133 }, + { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741 }, + { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047 }, + { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467 }, + { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022 }, + { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647 }, + { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784 }, + { url = "https://files.pythonhosted.org/packages/58/60/f645cc8b570f99be3cf46714170c2de4b4c9d6b827b912811eff1eb8a412/propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8", size = 77865 }, + { url = "https://files.pythonhosted.org/packages/6f/d4/c1adbf3901537582e65cf90fd9c26fde1298fde5a2c593f987112c0d0798/propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f", size = 45452 }, + { url = "https://files.pythonhosted.org/packages/d1/b5/fe752b2e63f49f727c6c1c224175d21b7d1727ce1d4873ef1c24c9216830/propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111", size = 44800 }, + { url = "https://files.pythonhosted.org/packages/62/37/fc357e345bc1971e21f76597028b059c3d795c5ca7690d7a8d9a03c9708a/propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5", size = 225804 }, + { url = "https://files.pythonhosted.org/packages/0d/f1/16e12c33e3dbe7f8b737809bad05719cff1dccb8df4dafbcff5575002c0e/propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb", size = 230650 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/018b9f2ed876bf5091e60153f727e8f9073d97573f790ff7cdf6bc1d1fb8/propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7", size = 234235 }, + { url = "https://files.pythonhosted.org/packages/45/5f/3faee66fc930dfb5da509e34c6ac7128870631c0e3582987fad161fcb4b1/propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120", size = 228249 }, + { url = "https://files.pythonhosted.org/packages/62/1e/a0d5ebda5da7ff34d2f5259a3e171a94be83c41eb1e7cd21a2105a84a02e/propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654", size = 214964 }, + { url = "https://files.pythonhosted.org/packages/db/a0/d72da3f61ceab126e9be1f3bc7844b4e98c6e61c985097474668e7e52152/propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e", size = 222501 }, + { url = "https://files.pythonhosted.org/packages/18/6d/a008e07ad7b905011253adbbd97e5b5375c33f0b961355ca0a30377504ac/propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b", size = 217917 }, + { url = "https://files.pythonhosted.org/packages/98/37/02c9343ffe59e590e0e56dc5c97d0da2b8b19fa747ebacf158310f97a79a/propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53", size = 217089 }, + { url = "https://files.pythonhosted.org/packages/53/1b/d3406629a2c8a5666d4674c50f757a77be119b113eedd47b0375afdf1b42/propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5", size = 228102 }, + { url = "https://files.pythonhosted.org/packages/cd/a7/3664756cf50ce739e5f3abd48febc0be1a713b1f389a502ca819791a6b69/propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7", size = 230122 }, + { url = "https://files.pythonhosted.org/packages/35/36/0bbabaacdcc26dac4f8139625e930f4311864251276033a52fd52ff2a274/propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef", size = 226818 }, + { url = "https://files.pythonhosted.org/packages/cc/27/4e0ef21084b53bd35d4dae1634b6d0bad35e9c58ed4f032511acca9d4d26/propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24", size = 40112 }, + { url = "https://files.pythonhosted.org/packages/a6/2c/a54614d61895ba6dd7ac8f107e2b2a0347259ab29cbf2ecc7b94fa38c4dc/propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037", size = 44034 }, + { url = "https://files.pythonhosted.org/packages/5a/a8/0a4fd2f664fc6acc66438370905124ce62e84e2e860f2557015ee4a61c7e/propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f", size = 82613 }, + { url = "https://files.pythonhosted.org/packages/4d/e5/5ef30eb2cd81576256d7b6caaa0ce33cd1d2c2c92c8903cccb1af1a4ff2f/propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c", size = 47763 }, + { url = "https://files.pythonhosted.org/packages/87/9a/87091ceb048efeba4d28e903c0b15bcc84b7c0bf27dc0261e62335d9b7b8/propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc", size = 47175 }, + { url = "https://files.pythonhosted.org/packages/3e/2f/854e653c96ad1161f96194c6678a41bbb38c7947d17768e8811a77635a08/propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de", size = 292265 }, + { url = "https://files.pythonhosted.org/packages/40/8d/090955e13ed06bc3496ba4a9fb26c62e209ac41973cb0d6222de20c6868f/propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6", size = 294412 }, + { url = "https://files.pythonhosted.org/packages/39/e6/d51601342e53cc7582449e6a3c14a0479fab2f0750c1f4d22302e34219c6/propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7", size = 294290 }, + { url = "https://files.pythonhosted.org/packages/3b/4d/be5f1a90abc1881884aa5878989a1acdafd379a91d9c7e5e12cef37ec0d7/propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458", size = 282926 }, + { url = "https://files.pythonhosted.org/packages/57/2b/8f61b998c7ea93a2b7eca79e53f3e903db1787fca9373af9e2cf8dc22f9d/propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11", size = 267808 }, + { url = "https://files.pythonhosted.org/packages/11/1c/311326c3dfce59c58a6098388ba984b0e5fb0381ef2279ec458ef99bd547/propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c", size = 290916 }, + { url = "https://files.pythonhosted.org/packages/4b/74/91939924b0385e54dc48eb2e4edd1e4903ffd053cf1916ebc5347ac227f7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf", size = 262661 }, + { url = "https://files.pythonhosted.org/packages/c2/d7/e6079af45136ad325c5337f5dd9ef97ab5dc349e0ff362fe5c5db95e2454/propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27", size = 264384 }, + { url = "https://files.pythonhosted.org/packages/b7/d5/ba91702207ac61ae6f1c2da81c5d0d6bf6ce89e08a2b4d44e411c0bbe867/propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757", size = 291420 }, + { url = "https://files.pythonhosted.org/packages/58/70/2117780ed7edcd7ba6b8134cb7802aada90b894a9810ec56b7bb6018bee7/propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18", size = 290880 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/ecd9ce27710021ae623631c0146719280a929d895a095f6d85efb6a0be2e/propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a", size = 287407 }, + { url = "https://files.pythonhosted.org/packages/3e/66/2e90547d6b60180fb29e23dc87bd8c116517d4255240ec6d3f7dc23d1926/propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d", size = 42573 }, + { url = "https://files.pythonhosted.org/packages/cb/8f/50ad8599399d1861b4d2b6b45271f0ef6af1b09b0a2386a46dbaf19c9535/propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e", size = 46757 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376 }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603 }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283 }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604 }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115 }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070 }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pyogrio" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "numpy" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/c3/5e30f913ad8a975abe6f6582a2d3cf321bdf40fd696940d9283c63880c7a/pyogrio-0.11.0.tar.gz", hash = "sha256:a7e0a97bc10c0d7204f6bf52e1b928cba0554c35a907c32b23065aed1ed97b3f", size = 286915 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/d1/035667f23d8e7066471c500636e9ee77b159a9d92f32b5e4944d541aad69/pyogrio-0.11.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:862b79d36d39c1f755739bde00cfd82fd1034fd287084d9202b14e3a85576f5c", size = 19492247 }, + { url = "https://files.pythonhosted.org/packages/0b/da/558be674dbbf18b9cb2f31b8c9d5691e1a42100bdbd159b4771f608f01e2/pyogrio-0.11.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:21b1924c02513185e3df1301dfc9d313f1450d7c366f8629e26757f51ba31003", size = 20678449 }, + { url = "https://files.pythonhosted.org/packages/c4/78/3761a80818a148ba9544abaf9c41bef5353054054c5ed16872e65cbf9dd6/pyogrio-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:103313202414ffa7378016791d287442541af60ac57b78536f0c67f3a82904a4", size = 27068276 }, + { url = "https://files.pythonhosted.org/packages/ad/6c/9a6faa094b33054957b4eef389106aa4f94e9dbdd384c9db5f03d6a4d379/pyogrio-0.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2e48956e68c41a17cbf3df32d979553de2839a082a7a9b0beef14948aa4ca5df", size = 26571289 }, + { url = "https://files.pythonhosted.org/packages/25/19/6a24c2052f2f99190482c83dcf8ecdc02bde9c8dbc2d604f088f9bbb5dbb/pyogrio-0.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ec5666cc8bf97aef9993c998198f85fe209b8a9ad4737696d3d2ab573b3e9a5b", size = 27769581 }, + { url = "https://files.pythonhosted.org/packages/3d/ad/afc1cdea5dac6afb95d561c9ec73c27722d494d8faab7e0452cf71fba71f/pyogrio-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad3744e679de2a31b1a885dc5ea260e3482f0d5e71461a88f431cda8d536b17", size = 19178064 }, + { url = "https://files.pythonhosted.org/packages/22/39/927036db0c550d35efb4d998dfe90c56515bc14d6ed0166b6c01ca28be24/pyogrio-0.11.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:a6f114d32c5c8a157c6fbf74e3ecfe69be7efb29363102f2aad14c9813de637a", size = 19491944 }, + { url = "https://files.pythonhosted.org/packages/49/78/92db6ca3650996ca80287e59b799aa303ccecd4f1cd677f15832e466d9e2/pyogrio-0.11.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:596e3f26e792882e35f25715634c12c1d6658a3d8d178c0089a9462c56b48be5", size = 20674571 }, + { url = "https://files.pythonhosted.org/packages/c8/a4/bc37ddcee3f47c79197887d6386d31d97182a94cff6a5093cad37d873bc5/pyogrio-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d693ca24e80bd7ede7b27ea3598593be5b41fb7cec315a57f5bb24d15faef8", size = 27033355 }, + { url = "https://files.pythonhosted.org/packages/5c/6f/984a513d5deab8ca94dde440084cab3eda5684825d70395a3bd21c2a9e5d/pyogrio-0.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:961100786ae44e2f27b4049b5262e378a3cba07872fc22051905fed8b4ce42db", size = 26528521 }, + { url = "https://files.pythonhosted.org/packages/39/d6/6026ef8903aef2a15b7ba5ad84c74ca2ce67d29fc6d99e07262a65061619/pyogrio-0.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:334563d24defc5d706bd2a1fa7d7433e33140e64b0fb9cb4afc715e4f6035c2b", size = 27734210 }, + { url = "https://files.pythonhosted.org/packages/94/81/232d4808e54e026b9059f966bc2a4a5de7e42f42e4bd4e3897e1b31ea87f/pyogrio-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf1f9128136abcbd1605d6fc6bf8c529c2092558246d8046ee6fbc383c550074", size = 19165401 }, + { url = "https://files.pythonhosted.org/packages/ba/2b/098692d9be9defb5d40327af50ffdc0c5486a4724c06b3d1f757cd5abd6d/pyogrio-0.11.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0b39e34199460dcd6a606db184094e69bcba89d1babb9a76cee74a134b53b232", size = 19485661 }, + { url = "https://files.pythonhosted.org/packages/00/06/5c197d76ea33d4667f427309b108281e7a3a0224e9a32c3fdb3c54e47133/pyogrio-0.11.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:5a952ef7a68fdfaf796a91b88c706108cb50ddd0a74096418e84aab7ac8a38be", size = 20667327 }, + { url = "https://files.pythonhosted.org/packages/9d/24/08715971846562624e1f185fc6f93d0a305950cc9167ac0b761f571c3c62/pyogrio-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4527abcac23bdac5781f9be9a7dd55fccd9967c7241a8e53de8ea1a06ea0cc2b", size = 27007054 }, + { url = "https://files.pythonhosted.org/packages/0d/07/c6c6d33e5b052b6bb785904477e906ed880509bc3748862ef59ed017739a/pyogrio-0.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:373a29d56a9016978aff57b88a640b5a8c3024dba7be1c059ad5af4ba932b59e", size = 26493010 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/e12bebcf2668bcb83736cc76177f36ee300ac8069880fca3a73f8753fc70/pyogrio-0.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ea2a131369ae8e62e30fa4f7e1442074d4828417d05ded660acea04a6a1d199b", size = 27710440 }, + { url = "https://files.pythonhosted.org/packages/46/8f/a9d134fbbf213db259b79f5bd5bbe7e3de1ff34fbe2a0b0be9d7d2919323/pyogrio-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:bf041d65bd1e89a4bb61845579c2963f2cca1bb33cde79f4ec2c0e0dc6f93afb", size = 19163300 }, +] + +[[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]] +name = "pyproj" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/10/a8480ea27ea4bbe896c168808854d00f2a9b49f95c0319ddcbba693c8a90/pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47", size = 226339 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/0d/63670fc527e664068b70b7cab599aa38b7420dd009bdc29ea257e7f3dfb3/pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d", size = 6264315 }, + { url = "https://files.pythonhosted.org/packages/25/9d/cbaf82cfb290d1f1fa42feb9ba9464013bb3891e40c4199f8072112e4589/pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067", size = 4666267 }, + { url = "https://files.pythonhosted.org/packages/79/53/24f9f9b8918c0550f3ff49ad5de4cf3f0688c9f91ff191476db8979146fe/pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa", size = 9680510 }, + { url = "https://files.pythonhosted.org/packages/3c/ac/12fab74a908d40b63174dc704587febd0729414804bbfd873cabe504ff2d/pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739", size = 9493619 }, + { url = "https://files.pythonhosted.org/packages/c4/45/26311d6437135da2153a178125db5dfb6abce831ce04d10ec207eabac70a/pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02", size = 10709755 }, + { url = "https://files.pythonhosted.org/packages/99/52/4ecd0986f27d0e6c8ee3a7bc5c63da15acd30ac23034f871325b297e61fd/pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274", size = 10642970 }, + { url = "https://files.pythonhosted.org/packages/3f/a5/d3bfc018fc92195a000d1d28acc1f3f1df15ff9f09ece68f45a2636c0134/pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a", size = 5868295 }, + { url = "https://files.pythonhosted.org/packages/92/39/ef6f06a5b223dbea308cfcbb7a0f72e7b506aef1850e061b2c73b0818715/pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970", size = 6279871 }, + { url = "https://files.pythonhosted.org/packages/e6/c9/876d4345b8d17f37ac59ebd39f8fa52fc6a6a9891a420f72d050edb6b899/pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217", size = 6264087 }, + { url = "https://files.pythonhosted.org/packages/ff/e6/5f8691f8c90e7f402cc80a6276eb19d2ec1faa150d5ae2dd9c7b0a254da8/pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394", size = 4669628 }, + { url = "https://files.pythonhosted.org/packages/42/ec/16475bbb79c1c68845c0a0d9c60c4fb31e61b8a2a20bc18b1a81e81c7f68/pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f", size = 9721415 }, + { url = "https://files.pythonhosted.org/packages/b3/a3/448f05b15e318bd6bea9a32cfaf11e886c4ae61fa3eee6e09ed5c3b74bb2/pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0", size = 9556447 }, + { url = "https://files.pythonhosted.org/packages/6a/ae/bd15fe8d8bd914ead6d60bca7f895a4e6f8ef7e3928295134ff9a7dad14c/pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145", size = 10758317 }, + { url = "https://files.pythonhosted.org/packages/9d/d9/5ccefb8bca925f44256b188a91c31238cae29ab6ee7f53661ecc04616146/pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d", size = 10771259 }, + { url = "https://files.pythonhosted.org/packages/2a/7d/31dedff9c35fa703162f922eeb0baa6c44a3288469a5fd88d209e2892f9e/pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274", size = 5859914 }, + { url = "https://files.pythonhosted.org/packages/3e/47/c6ab03d6564a7c937590cff81a2742b5990f096cce7c1a622d325be340ee/pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98", size = 6273196 }, + { url = "https://files.pythonhosted.org/packages/ef/01/984828464c9960036c602753fc0f21f24f0aa9043c18fa3f2f2b66a86340/pyproj-3.7.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:5f8d02ef4431dee414d1753d13fa82a21a2f61494737b5f642ea668d76164d6d", size = 6253062 }, + { url = "https://files.pythonhosted.org/packages/68/65/6ecdcdc829811a2c160cdfe2f068a009fc572fd4349664f758ccb0853a7c/pyproj-3.7.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b853ae99bda66cbe24b4ccfe26d70601d84375940a47f553413d9df570065e0", size = 4660548 }, + { url = "https://files.pythonhosted.org/packages/67/da/dda94c4490803679230ba4c17a12f151b307a0d58e8110820405ca2d98db/pyproj-3.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83db380c52087f9e9bdd8a527943b2e7324f275881125e39475c4f9277bdeec4", size = 9662464 }, + { url = "https://files.pythonhosted.org/packages/6f/57/f61b7d22c91ae1d12ee00ac4c0038714e774ebcd851b9133e5f4f930dd40/pyproj-3.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35ed213892e211a3ce2bea002aa1183e1a2a9b79e51bb3c6b15549a831ae528", size = 9497461 }, + { url = "https://files.pythonhosted.org/packages/b7/f6/932128236f79d2ac7d39fe1a19667fdf7155d9a81d31fb9472a7a497790f/pyproj-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8b15b0463d1303bab113d1a6af2860a0d79013c3a66fcc5475ce26ef717fd4f", size = 10708869 }, + { url = "https://files.pythonhosted.org/packages/1d/0d/07ac7712994454a254c383c0d08aff9916a2851e6512d59da8dc369b1b02/pyproj-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87229e42b75e89f4dad6459200f92988c5998dfb093c7c631fb48524c86cd5dc", size = 10729260 }, + { url = "https://files.pythonhosted.org/packages/b0/d0/9c604bc72c37ba69b867b6df724d6a5af6789e8c375022c952f65b2af558/pyproj-3.7.1-cp313-cp313-win32.whl", hash = "sha256:d666c3a3faaf3b1d7fc4a544059c4eab9d06f84a604b070b7aa2f318e227798e", size = 5855462 }, + { url = "https://files.pythonhosted.org/packages/98/df/68a2b7f5fb6400c64aad82d72bcc4bc531775e62eedff993a77c780defd0/pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e", size = 6266573 }, +] + +[[package]] +name = "pyright" +version = "1.1.401" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/9a/7ab2b333b921b2d6bfcffe05a0e0a0bbeff884bd6fb5ed50cd68e2898e53/pyright-1.1.401.tar.gz", hash = "sha256:788a82b6611fa5e34a326a921d86d898768cddf59edde8e93e56087d277cc6f1", size = 3894193 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/e6/1f908fce68b0401d41580e0f9acc4c3d1b248adcff00dfaad75cd21a1370/pyright-1.1.401-py3-none-any.whl", hash = "sha256:6fde30492ba5b0d7667c16ecaf6c699fab8d7a1263f6a18549e0b00bf7724c06", size = 5629193 }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.5.1.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/0c/cfa6223c525f67ea3a7a2907e36e9e9a9653300f82cfd9af88f8136514ab/pytorch_lightning-2.5.1.post0.tar.gz", hash = "sha256:abc3d5a804d41f941b14e3fd7db5572a1270cd1e9889b50e962984c87d498d94", size = 634368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/a9/e14821cfaf08e8d78185cca0477c9d3a62bafe1b4b530100f7b66bb1f7bb/pytorch_lightning-2.5.1.post0-py3-none-any.whl", hash = "sha256:873fb21392c8b79908218f5ca8f65bd835439216e52550c36ff55d849e99c93e", size = 823084 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/b1/68aa2986129fb1011dabbe95f0136f44509afaf072b12b8f815905a39f33/pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd", size = 8784284 }, + { url = "https://files.pythonhosted.org/packages/b3/bd/d1592635992dd8db5bb8ace0551bc3a769de1ac8850200cfa517e72739fb/pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c", size = 9520748 }, + { url = "https://files.pythonhosted.org/packages/90/b1/ac8b1ffce6603849eb45a91cf126c0fa5431f186c2e768bf56889c46f51c/pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582", size = 8455941 }, + { url = "https://files.pythonhosted.org/packages/6b/ec/4fdbe47932f671d6e348474ea35ed94227fb5df56a7c30cbbb42cd396ed0/pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d", size = 8796239 }, + { url = "https://files.pythonhosted.org/packages/e3/e5/b0627f8bb84e06991bea89ad8153a9e50ace40b2e1195d68e9dff6b03d0f/pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060", size = 9503839 }, + { url = "https://files.pythonhosted.org/packages/1f/32/9ccf53748df72301a89713936645a664ec001abd35ecc8578beda593d37d/pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966", size = 8459470 }, + { url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384 }, + { url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039 }, + { url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "pyzmq" +version = "26.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/6d/234e3b0aa82fd0290b1896e9992f56bdddf1f97266110be54d0177a9d2d9/pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54", size = 1339723 }, + { url = "https://files.pythonhosted.org/packages/4f/11/6d561efe29ad83f7149a7cd48e498e539ed09019c6cd7ecc73f4cc725028/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030", size = 672645 }, + { url = "https://files.pythonhosted.org/packages/19/fd/81bfe3e23f418644660bad1a90f0d22f0b3eebe33dd65a79385530bceb3d/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01", size = 910133 }, + { url = "https://files.pythonhosted.org/packages/97/68/321b9c775595ea3df832a9516252b653fe32818db66fdc8fa31c9b9fce37/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e", size = 867428 }, + { url = "https://files.pythonhosted.org/packages/4e/6e/159cbf2055ef36aa2aa297e01b24523176e5b48ead283c23a94179fb2ba2/pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88", size = 862409 }, + { url = "https://files.pythonhosted.org/packages/05/1c/45fb8db7be5a7d0cadea1070a9cbded5199a2d578de2208197e592f219bd/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6", size = 1205007 }, + { url = "https://files.pythonhosted.org/packages/f8/fa/658c7f583af6498b463f2fa600f34e298e1b330886f82f1feba0dc2dd6c3/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df", size = 1514599 }, + { url = "https://files.pythonhosted.org/packages/4d/d7/44d641522353ce0a2bbd150379cb5ec32f7120944e6bfba4846586945658/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef", size = 1414546 }, + { url = "https://files.pythonhosted.org/packages/72/76/c8ed7263218b3d1e9bce07b9058502024188bd52cc0b0a267a9513b431fc/pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca", size = 579247 }, + { url = "https://files.pythonhosted.org/packages/c3/d0/2d9abfa2571a0b1a67c0ada79a8aa1ba1cce57992d80f771abcdf99bb32c/pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896", size = 644727 }, + { url = "https://files.pythonhosted.org/packages/0d/d1/c8ad82393be6ccedfc3c9f3adb07f8f3976e3c4802640fe3f71441941e70/pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3", size = 559942 }, + { url = "https://files.pythonhosted.org/packages/10/44/a778555ebfdf6c7fc00816aad12d185d10a74d975800341b1bc36bad1187/pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b", size = 1341586 }, + { url = "https://files.pythonhosted.org/packages/9c/4f/f3a58dc69ac757e5103be3bd41fb78721a5e17da7cc617ddb56d973a365c/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905", size = 665880 }, + { url = "https://files.pythonhosted.org/packages/fe/45/50230bcfb3ae5cb98bee683b6edeba1919f2565d7cc1851d3c38e2260795/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b", size = 902216 }, + { url = "https://files.pythonhosted.org/packages/41/59/56bbdc5689be5e13727491ad2ba5efd7cd564365750514f9bc8f212eef82/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63", size = 859814 }, + { url = "https://files.pythonhosted.org/packages/81/b1/57db58cfc8af592ce94f40649bd1804369c05b2190e4cbc0a2dad572baeb/pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5", size = 855889 }, + { url = "https://files.pythonhosted.org/packages/e8/92/47542e629cbac8f221c230a6d0f38dd3d9cff9f6f589ed45fdf572ffd726/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b", size = 1197153 }, + { url = "https://files.pythonhosted.org/packages/07/e5/b10a979d1d565d54410afc87499b16c96b4a181af46e7645ab4831b1088c/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84", size = 1507352 }, + { url = "https://files.pythonhosted.org/packages/ab/58/5a23db84507ab9c01c04b1232a7a763be66e992aa2e66498521bbbc72a71/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f", size = 1406834 }, + { url = "https://files.pythonhosted.org/packages/22/74/aaa837b331580c13b79ac39396601fb361454ee184ca85e8861914769b99/pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44", size = 577992 }, + { url = "https://files.pythonhosted.org/packages/30/0f/55f8c02c182856743b82dde46b2dc3e314edda7f1098c12a8227eeda0833/pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be", size = 640466 }, + { url = "https://files.pythonhosted.org/packages/e4/29/073779afc3ef6f830b8de95026ef20b2d1ec22d0324d767748d806e57379/pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0", size = 556342 }, + { url = "https://files.pythonhosted.org/packages/d7/20/fb2c92542488db70f833b92893769a569458311a76474bda89dc4264bd18/pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3", size = 1339484 }, + { url = "https://files.pythonhosted.org/packages/58/29/2f06b9cabda3a6ea2c10f43e67ded3e47fc25c54822e2506dfb8325155d4/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43", size = 666106 }, + { url = "https://files.pythonhosted.org/packages/77/e4/dcf62bd29e5e190bd21bfccaa4f3386e01bf40d948c239239c2f1e726729/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6", size = 902056 }, + { url = "https://files.pythonhosted.org/packages/1a/cf/b36b3d7aea236087d20189bec1a87eeb2b66009731d7055e5c65f845cdba/pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e", size = 860148 }, + { url = "https://files.pythonhosted.org/packages/18/a6/f048826bc87528c208e90604c3bf573801e54bd91e390cbd2dfa860e82dc/pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771", size = 855983 }, + { url = "https://files.pythonhosted.org/packages/0a/27/454d34ab6a1d9772a36add22f17f6b85baf7c16e14325fa29e7202ca8ee8/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30", size = 1197274 }, + { url = "https://files.pythonhosted.org/packages/f4/3d/7abfeab6b83ad38aa34cbd57c6fc29752c391e3954fd12848bd8d2ec0df6/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86", size = 1507120 }, + { url = "https://files.pythonhosted.org/packages/13/ff/bc8d21dbb9bc8705126e875438a1969c4f77e03fc8565d6901c7933a3d01/pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101", size = 1406738 }, + { url = "https://files.pythonhosted.org/packages/f5/5d/d4cd85b24de71d84d81229e3bbb13392b2698432cf8fdcea5afda253d587/pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637", size = 577826 }, + { url = "https://files.pythonhosted.org/packages/c6/6c/f289c1789d7bb6e5a3b3bef7b2a55089b8561d17132be7d960d3ff33b14e/pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b", size = 640406 }, + { url = "https://files.pythonhosted.org/packages/b3/99/676b8851cb955eb5236a0c1e9ec679ea5ede092bf8bf2c8a68d7e965cac3/pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08", size = 556216 }, + { url = "https://files.pythonhosted.org/packages/65/c2/1fac340de9d7df71efc59d9c50fc7a635a77b103392d1842898dd023afcb/pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4", size = 1333769 }, + { url = "https://files.pythonhosted.org/packages/5c/c7/6c03637e8d742c3b00bec4f5e4cd9d1c01b2f3694c6f140742e93ca637ed/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a", size = 658826 }, + { url = "https://files.pythonhosted.org/packages/a5/97/a8dca65913c0f78e0545af2bb5078aebfc142ca7d91cdaffa1fbc73e5dbd/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b", size = 891650 }, + { url = "https://files.pythonhosted.org/packages/7d/7e/f63af1031eb060bf02d033732b910fe48548dcfdbe9c785e9f74a6cc6ae4/pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d", size = 849776 }, + { url = "https://files.pythonhosted.org/packages/f6/fa/1a009ce582802a895c0d5fe9413f029c940a0a8ee828657a3bb0acffd88b/pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf", size = 842516 }, + { url = "https://files.pythonhosted.org/packages/6e/bc/f88b0bad0f7a7f500547d71e99f10336f2314e525d4ebf576a1ea4a1d903/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c", size = 1189183 }, + { url = "https://files.pythonhosted.org/packages/d9/8c/db446a3dd9cf894406dec2e61eeffaa3c07c3abb783deaebb9812c4af6a5/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8", size = 1495501 }, + { url = "https://files.pythonhosted.org/packages/05/4c/bf3cad0d64c3214ac881299c4562b815f05d503bccc513e3fd4fdc6f67e4/pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364", size = 1395540 }, + { url = "https://files.pythonhosted.org/packages/04/52/a70fcd5592715702248306d8e1729c10742c2eac44529984413b05c68658/pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb", size = 834405 }, + { url = "https://files.pythonhosted.org/packages/25/f9/1a03f1accff16b3af1a6fa22cbf7ced074776abbf688b2e9cb4629700c62/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1", size = 569578 }, + { url = "https://files.pythonhosted.org/packages/76/0c/3a633acd762aa6655fcb71fa841907eae0ab1e8582ff494b137266de341d/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494", size = 798248 }, + { url = "https://files.pythonhosted.org/packages/cd/cc/6c99c84aa60ac1cc56747bed6be8ce6305b9b861d7475772e7a25ce019d3/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9", size = 756757 }, + { url = "https://files.pythonhosted.org/packages/13/9c/d8073bd898eb896e94c679abe82e47506e2b750eb261cf6010ced869797c/pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0", size = 555371 }, +] + +[[package]] +name = "qdldl" +version = "0.1.7.post5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/88/9254835c513a381b8c6d52773060844acf76dfa739648c18f61809c8ee04/qdldl-0.1.7.post5.tar.gz", hash = "sha256:0b1399e1c49b5bed5aac8fd63ef08ab708d340c37fb426fe00128bc1f36b286e", size = 73920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/6c/ce4cab36da9a7c0bff69067377b513ec88ff753de07f33f65959f4141308/qdldl-0.1.7.post5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa22df45e625c763d129b2893b284b7bde16a535a7e900288d588be9dc24fe9f", size = 106139 }, + { url = "https://files.pythonhosted.org/packages/86/cf/641787a0c64019e76eb8bea925930005960323f1a5539361c209613f4747/qdldl-0.1.7.post5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e196871dafe4febb86c2886713c8a2226d19455226e56e3b9480aa78eb59b5e", size = 103421 }, + { url = "https://files.pythonhosted.org/packages/be/87/91d2f0debdd515b653c701c023b939325c51157d74154336b8495f156659/qdldl-0.1.7.post5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ba5ff31a66d1f92b41d0b97d27288d28a8c849dd6db2221a579b1a5a5a6df0f", size = 1179946 }, + { url = "https://files.pythonhosted.org/packages/b8/7e/5fe5a081bd229a2b703a4b93e5ecaf44f51902e9b6a645c8ce4ea325ec0d/qdldl-0.1.7.post5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34872867c2bcac60279034594eac8dee042b9dedd4c45948e55884b8c5c9cd0", size = 1193311 }, + { url = "https://files.pythonhosted.org/packages/53/dc/d6b760217f0fa7007e45c03dc0193c828ee5010f037acb58b79cd0010fbc/qdldl-0.1.7.post5-cp311-cp311-win_amd64.whl", hash = "sha256:b1280e886f734e3d0d67f643e3d76c55d2e23d0e7b06d89b987681dc165892c5", size = 90488 }, + { url = "https://files.pythonhosted.org/packages/14/c1/eba61a848f9dfa0b54e954aa71f18eb35576f8842ef31dc76a3569a50526/qdldl-0.1.7.post5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d67a95d0ba73147a05cf98dc9284103f64150c9e2c214cd35ee0258f06922c5e", size = 106277 }, + { url = "https://files.pythonhosted.org/packages/02/2e/5daa29b8ecf25277c36a220ef3b509d2ec4079ab81ff3adc544bc12cd675/qdldl-0.1.7.post5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e23d684427ce49f5d657e353322363555d1a31605fe72cbe4b965a4e260742c", size = 103179 }, + { url = "https://files.pythonhosted.org/packages/c6/74/5818f5027a0c252d1e8a2eba996359155d1518db90ce545f1becf0dd4a10/qdldl-0.1.7.post5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c4953d4fe61951fb515a6439009248b5a7b73627d74ee929d02b19bea41b19d", size = 1182542 }, + { url = "https://files.pythonhosted.org/packages/ae/55/90ad03c32e673a9b33cfa7cb43f55d1ab0509b60396afbb1031fa1516fd9/qdldl-0.1.7.post5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520dbe4006a333c773ff474d2dc1e0af928c0dc7d9ca36db5637ba738ee608ba", size = 1201734 }, + { url = "https://files.pythonhosted.org/packages/c1/82/730d0d2c6093c4dc574947eea94e0cddeea836f43823a80fc8b064a82ddf/qdldl-0.1.7.post5-cp312-cp312-win_amd64.whl", hash = "sha256:13dfc0b225a5c180512488fa51f1771e8fa3c06d7fce9fd3c1d018bc03ba0eec", size = 90706 }, + { url = "https://files.pythonhosted.org/packages/d1/33/5c4348f6e2e4868ec843c05ec9679ec59b37cd9a251ee7ee9baf00b2f8b4/qdldl-0.1.7.post5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7600985d2321cb15f71f8bb3a92ef2a85284b4fd740d8bbd4960b8c2f7ee6d33", size = 106318 }, + { url = "https://files.pythonhosted.org/packages/c6/1a/aaa6835253c94a53816b4bea7c6051fd406c492bed7024fc683d0da1b939/qdldl-0.1.7.post5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:314153f574641c846a85ff9b4a5c0e0d23e32d0de11d8381866bb27577088bef", size = 103216 }, + { url = "https://files.pythonhosted.org/packages/9f/0e/fd317e269ba7c3ed11e0e999ebfe2f3611de9bdd1fe7052ea32f0a3fd22e/qdldl-0.1.7.post5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b1ee3840d7d8ef4f1e3ffce0620116a71abd72c52ba46e0c194d4b294a0ad2", size = 1182628 }, + { url = "https://files.pythonhosted.org/packages/41/ad/6cd39b1c3ac5c0201eaf4be257303d1c6f7193f0d0f5c54ab151ba8faad9/qdldl-0.1.7.post5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba6df1eedbaea844485e1c7a6ae9013bbdc86f07c4ebb13c89249b003de4ef4", size = 1201935 }, + { url = "https://files.pythonhosted.org/packages/08/f7/abac03a09f6848cee6d5dd7a7a8bd1dfed68766ee77f9cbf3e9de596ad68/qdldl-0.1.7.post5-cp313-cp313-win_amd64.whl", hash = "sha256:cc9be378e7bec67d4c62b7fa27cafb4f77d3e5e059d753c3dce0a5ae1ef5fea0", size = 90735 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "ruff" +version = "0.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597 }, + { url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154 }, + { url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048 }, + { url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062 }, + { url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152 }, + { url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067 }, + { url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807 }, + { url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601 }, + { url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186 }, + { url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032 }, + { url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370 }, + { url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529 }, + { url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642 }, + { url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511 }, + { url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573 }, + { url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770 }, +] + +[[package]] +name = "sacremoses" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/51/fbdc4af4f6e85d26169e28be3763fe50ddfd0d4bf8b871422b0788dcc4d2/sacremoses-0.1.1.tar.gz", hash = "sha256:b6fd5d3a766b02154ed80b962ddca91e1fd25629c0978c7efba21ebccf663934", size = 883188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/f0/89ee2bc9da434bd78464f288fdb346bc2932f2ee80a90b2a4bbbac262c74/sacremoses-0.1.1-py3-none-any.whl", hash = "sha256:31e04c98b169bfd902144824d191825cd69220cdb4ae4bcf1ec58a7db5587b1a", size = 897476 }, +] + +[[package]] +name = "safetensors" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, + { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, + { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, + { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, + { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, + { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, + { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, + { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, + { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, + { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, + { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, + { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, + { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057 }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335 }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783 }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376 }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698 }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000 }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893 }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389 }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435 }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474 }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841 }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862 }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785 }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119 }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116 }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801 }, +] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/2a/e291c29670795406a824567d1dfc91db7b699799a002fdaa452bceea8f6e/scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33", size = 12102620 }, + { url = "https://files.pythonhosted.org/packages/25/92/ee1d7a00bb6b8c55755d4984fd82608603a3cc59959245068ce32e7fb808/scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d", size = 11116234 }, + { url = "https://files.pythonhosted.org/packages/30/cd/ed4399485ef364bb25f388ab438e3724e60dc218c547a407b6e90ccccaef/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2", size = 12592155 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/62fc9a5a659bb58a03cdd7e258956a5824bdc9b4bb3c5d932f55880be569/scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8", size = 13497069 }, + { url = "https://files.pythonhosted.org/packages/a1/a6/c5b78606743a1f28eae8f11973de6613a5ee87366796583fb74c67d54939/scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415", size = 11139809 }, + { url = "https://files.pythonhosted.org/packages/0a/18/c797c9b8c10380d05616db3bfb48e2a3358c767affd0857d56c2eb501caa/scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b", size = 12104516 }, + { url = "https://files.pythonhosted.org/packages/c4/b7/2e35f8e289ab70108f8cbb2e7a2208f0575dc704749721286519dcf35f6f/scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2", size = 11167837 }, + { url = "https://files.pythonhosted.org/packages/a4/f6/ff7beaeb644bcad72bcfd5a03ff36d32ee4e53a8b29a639f11bcb65d06cd/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f", size = 12253728 }, + { url = "https://files.pythonhosted.org/packages/29/7a/8bce8968883e9465de20be15542f4c7e221952441727c4dad24d534c6d99/scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86", size = 13147700 }, + { url = "https://files.pythonhosted.org/packages/62/27/585859e72e117fe861c2079bcba35591a84f801e21bc1ab85bce6ce60305/scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52", size = 11110613 }, + { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001 }, + { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360 }, + { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004 }, + { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776 }, + { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865 }, + { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804 }, + { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530 }, + { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256 }, +] + +[[package]] +name = "scikit-survival" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ecos" }, + { name = "joblib" }, + { name = "numexpr" }, + { name = "numpy" }, + { name = "osqp" }, + { name = "pandas" }, + { name = "scikit-learn" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/cb/0d3aa99c3997f22b0561900c06726c97a1f0799d17ed64fff98c7b1c2f58/scikit_survival-0.24.1.tar.gz", hash = "sha256:059ca5911f980e44f69951baf08efc8d7a7cf1adba7a5422580ef65330cfd88e", size = 2819926 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/42/19b84de0eb6af19cf6005d2a192d680eadc31aa15bbaf3435663fed6ebc3/scikit_survival-0.24.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:eb2902f6da4eb65751a0fe5efb539c0468e012e980c198f4bc956e7339c22996", size = 868890 }, + { url = "https://files.pythonhosted.org/packages/7e/18/4d29805f94e793799512f469a8e0cc97a3d4d73057c4052e62c0781d819a/scikit_survival-0.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51a624760bf88df0a469d363737394c1d45aedd9eaaeb0fedb76d09af8fa8b90", size = 842118 }, + { url = "https://files.pythonhosted.org/packages/73/f4/b474631b68a5a7fe799cc63dc30d9db5432d16e24ac760e6560aeb84aff9/scikit_survival-0.24.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d686d0c94c9ad919c32bd02ccacd89d6d7a296b9deab1f9b797bb670350856e6", size = 3924338 }, + { url = "https://files.pythonhosted.org/packages/cf/e4/8fbba4d983c619aff7235618600e9c32395b007d29db14973066a7e952ff/scikit_survival-0.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc77a649fe960f0f00aa09b6c13b125f5d893451775fe1cfd14f49697d7b885a", size = 826727 }, + { url = "https://files.pythonhosted.org/packages/3d/7e/a4f0f223246e52a91d2ca6f0a1283a4578160febbec2065ab128d4e3303d/scikit_survival-0.24.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4f44b6fd3147fa41771977317be990ff44c471513f0e42191db2b88248f7bd72", size = 876169 }, + { url = "https://files.pythonhosted.org/packages/c1/e4/5090bf732722b6190b79d8cf39a12bee40b1873a2d4f1d4427342ac0e377/scikit_survival-0.24.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e0c690394ad58ff8d20f0525eb5eae93966ed9531f0956a105b66a7503e4ac4", size = 847521 }, + { url = "https://files.pythonhosted.org/packages/67/d2/8e3a115ff6adc5ace14b5cf32a2d8d77b32041ebeb2bad84fb82038dacbe/scikit_survival-0.24.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e06abac35705b471d950fa491bbb0f7473fe9005f753b2996819b231dc0380ec", size = 3885151 }, + { url = "https://files.pythonhosted.org/packages/01/34/6d6b2554c1d3c27371fa4ed8719f2d659802d11f333a26d0e7ee854e9197/scikit_survival-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:62d8d37904d38a7140b8918557d55cc8d3bfe832fd39de63e723b0aad8b9bb21", size = 832557 }, + { url = "https://files.pythonhosted.org/packages/eb/03/a8fe9119ac7dbac03b7554ddeaf632680f7a7d005670a9f1f363ce847bf7/scikit_survival-0.24.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a0126081f84b45804359fc0a68ad4c3a2dbee88656baa0b78fa304b52a03b23", size = 869938 }, + { url = "https://files.pythonhosted.org/packages/ab/d7/554c7fe38e52f1895f0bdfd2de96e4c17da55f23f7264cc1fab34c731a4d/scikit_survival-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1332d310d9bfed12932989c869e6e5757e4a20ab01a89f25b64eb83049d1098d", size = 841411 }, + { url = "https://files.pythonhosted.org/packages/30/3f/d6fa1a1f64cb5d09d33d476779809fed05d2971c5ffd07de4a471ad3f1a0/scikit_survival-0.24.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e978b79bc542e259cf75024b1b6381f2636122ebe440a64fd7aed7f8a9f1afc0", size = 3861630 }, + { url = "https://files.pythonhosted.org/packages/b9/76/ce24305c24ae89ceff0e8ed7305cf5542016cded3bbb57fc599a2d459028/scikit_survival-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:4fa5fdb77c7902b90a353e04655245bd3b81dc980743b7f83ad68cb74ed63d83", size = 830658 }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255 }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035 }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499 }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602 }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415 }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622 }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796 }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684 }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504 }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735 }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284 }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958 }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454 }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199 }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455 }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140 }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549 }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184 }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256 }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540 }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115 }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884 }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018 }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716 }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342 }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869 }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851 }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011 }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407 }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030 }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709 }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045 }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062 }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132 }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503 }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097 }, +] + +[[package]] +name = "sentry-sdk" +version = "2.29.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553 }, +] + +[[package]] +name = "setproctitle" +version = "1.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/af/56efe21c53ac81ac87e000b15e60b3d8104224b4313b6eacac3597bd183d/setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169", size = 26889 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/3b/8288d0cd969a63500dd62fc2c99ce6980f9909ccef0770ab1f86c361e0bf/setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b", size = 17412 }, + { url = "https://files.pythonhosted.org/packages/39/37/43a5a3e25ca1048dbbf4db0d88d346226f5f1acd131bb8e660f4bfe2799f/setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec", size = 11963 }, + { url = "https://files.pythonhosted.org/packages/5b/47/f103c40e133154783c91a10ab08ac9fc410ed835aa85bcf7107cb882f505/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279", size = 31718 }, + { url = "https://files.pythonhosted.org/packages/1f/13/7325dd1c008dd6c0ebd370ddb7505977054a87e406f142318e395031a792/setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235", size = 33027 }, + { url = "https://files.pythonhosted.org/packages/0c/0a/6075bfea05a71379d77af98a9ac61163e8b6e5ef1ae58cd2b05871b2079c/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9", size = 30223 }, + { url = "https://files.pythonhosted.org/packages/cc/41/fbf57ec52f4f0776193bd94334a841f0bc9d17e745f89c7790f336420c65/setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1", size = 31204 }, + { url = "https://files.pythonhosted.org/packages/97/b5/f799fb7a00de29fb0ac1dfd015528dea425b9e31a8f1068a0b3df52d317f/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034", size = 31181 }, + { url = "https://files.pythonhosted.org/packages/b5/b7/81f101b612014ec61723436022c31146178813d6ca6b947f7b9c84e9daf4/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5", size = 30101 }, + { url = "https://files.pythonhosted.org/packages/67/23/681232eed7640eab96719daa8647cc99b639e3daff5c287bd270ef179a73/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4", size = 32438 }, + { url = "https://files.pythonhosted.org/packages/19/f8/4d075a7bdc3609ac71535b849775812455e4c40aedfbf0778a6f123b1774/setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4", size = 30625 }, + { url = "https://files.pythonhosted.org/packages/5f/73/a2a8259ebee166aee1ca53eead75de0e190b3ddca4f716e5c7470ebb7ef6/setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f", size = 11488 }, + { url = "https://files.pythonhosted.org/packages/c9/15/52cf5e1ff0727d53704cfdde2858eaf237ce523b0b04db65faa84ff83e13/setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781", size = 12201 }, + { url = "https://files.pythonhosted.org/packages/8f/fb/99456fd94d4207c5f6c40746a048a33a52b4239cd7d9c8d4889e2210ec82/setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638", size = 17399 }, + { url = "https://files.pythonhosted.org/packages/d5/48/9699191fe6062827683c43bfa9caac33a2c89f8781dd8c7253fa3dba85fd/setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8", size = 11966 }, + { url = "https://files.pythonhosted.org/packages/33/03/b085d192b9ecb9c7ce6ad6ef30ecf4110b7f39430b58a56245569827fcf4/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67", size = 32017 }, + { url = "https://files.pythonhosted.org/packages/ae/68/c53162e645816f97212002111420d1b2f75bf6d02632e37e961dc2cd6d8b/setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2", size = 33419 }, + { url = "https://files.pythonhosted.org/packages/ac/0d/119a45d15a816a6cf5ccc61b19729f82620095b27a47e0a6838216a95fae/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d", size = 30711 }, + { url = "https://files.pythonhosted.org/packages/e3/fb/5e9b5068df9e9f31a722a775a5e8322a29a638eaaa3eac5ea7f0b35e6314/setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d", size = 31742 }, + { url = "https://files.pythonhosted.org/packages/35/88/54de1e73e8fce87d587889c7eedb48fc4ee2bbe4e4ca6331690d03024f86/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc", size = 31925 }, + { url = "https://files.pythonhosted.org/packages/f3/01/65948d7badd66e63e3db247b923143da142790fa293830fdecf832712c2d/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d", size = 30981 }, + { url = "https://files.pythonhosted.org/packages/22/20/c495e61786f1d38d5dc340b9d9077fee9be3dfc7e89f515afe12e1526dbc/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe", size = 33209 }, + { url = "https://files.pythonhosted.org/packages/98/3f/a457b8550fbd34d5b482fe20b8376b529e76bf1fbf9a474a6d9a641ab4ad/setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a", size = 31587 }, + { url = "https://files.pythonhosted.org/packages/44/fe/743517340e5a635e3f1c4310baea20c16c66202f96a6f4cead222ffd6d84/setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28", size = 11487 }, + { url = "https://files.pythonhosted.org/packages/60/9a/d88f1c1f0f4efff1bd29d9233583ee341114dda7d9613941453984849674/setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3", size = 12208 }, + { url = "https://files.pythonhosted.org/packages/89/76/f1a2fdbf9b9602945a7489ba5c52e9863de37381ef1a85a2b9ed0ff8bc79/setproctitle-1.3.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2a9e62647dc040a76d55563580bf3bb8fe1f5b6ead08447c2ed0d7786e5e794", size = 17392 }, + { url = "https://files.pythonhosted.org/packages/5c/5b/4e0db8b10b4543afcb3dbc0827793d46e43ec1de6b377e313af3703d08e0/setproctitle-1.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:751ba352ed922e0af60458e961167fa7b732ac31c0ddd1476a2dfd30ab5958c5", size = 11951 }, + { url = "https://files.pythonhosted.org/packages/dc/fe/d5d00aaa700fe1f6160b6e95c225b29c01f4d9292176d48fd968815163ea/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7890e291bf4708e3b61db9069ea39b3ab0651e42923a5e1f4d78a7b9e4b18301", size = 32087 }, + { url = "https://files.pythonhosted.org/packages/9f/b3/894b827b93ef813c082479bebf88185860f01ac243df737823dd705e7fff/setproctitle-1.3.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2b17855ed7f994f3f259cf2dfbfad78814538536fa1a91b50253d84d87fd88d", size = 33502 }, + { url = "https://files.pythonhosted.org/packages/b2/cd/5330734cca1a4cfcb721432c22cb7899ff15a4101ba868b2ef452ffafea1/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e51ec673513465663008ce402171192a053564865c2fc6dc840620871a9bd7c", size = 30713 }, + { url = "https://files.pythonhosted.org/packages/fa/d3/c2590c5daa2e9a008d3f2b16c0f4a351826193be55f147cb32af49c6d814/setproctitle-1.3.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cc10352dc6cf35a33951656aa660d99f25f574eb78132ce41a85001a638aa7", size = 31792 }, + { url = "https://files.pythonhosted.org/packages/e6/b1/c553ed5af8cfcecd5ae7737e63af58a17a03d26f3d61868c7eb20bf7e3cf/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dba8faee2e4a96e934797c9f0f2d093f8239bf210406a99060b3eabe549628e", size = 31927 }, + { url = "https://files.pythonhosted.org/packages/70/78/2d5385206540127a3dca0ff83225b1ac66873f5cc89d4a6d3806c92f5ae2/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e3e44d08b61de0dd6f205528498f834a51a5c06689f8fb182fe26f3a3ce7dca9", size = 30981 }, + { url = "https://files.pythonhosted.org/packages/31/62/e3e4a4e006d0e549748e53cded4ff3b667be0602860fc61b7de8b412b667/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:de004939fc3fd0c1200d26ea9264350bfe501ffbf46c8cf5dc7f345f2d87a7f1", size = 33244 }, + { url = "https://files.pythonhosted.org/packages/aa/05/4b223fd4ef94e105dc7aff27fa502fb7200cf52be2bb0c064bd2406b5611/setproctitle-1.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f8194b4d631b003a1176a75d1acd545e04b1f54b821638e098a93e6e62830ef", size = 31630 }, + { url = "https://files.pythonhosted.org/packages/1b/ba/5f68eb969f7336f54b54a599fd3ffbd7662f9733b080bc8598705971b3dd/setproctitle-1.3.6-cp313-cp313-win32.whl", hash = "sha256:d714e002dd3638170fe7376dc1b686dbac9cb712cde3f7224440af722cc9866a", size = 11480 }, + { url = "https://files.pythonhosted.org/packages/ba/f5/7f47f0ca35c9c357f16187cee9229f3eda0237bc6fdd3061441336f361c0/setproctitle-1.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:b70c07409d465f3a8b34d52f863871fb8a00755370791d2bd1d4f82b3cdaf3d5", size = 12198 }, + { url = "https://files.pythonhosted.org/packages/39/ad/c3941b8fc6b32a976c9e2d9615a90ae793b69cd010ca8c3575dbc822104f/setproctitle-1.3.6-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:23a57d3b8f1549515c2dbe4a2880ebc1f27780dc126c5e064167563e015817f5", size = 17401 }, + { url = "https://files.pythonhosted.org/packages/04/38/a184f857b988d3a9c401e470a4e38182a5c99ee77bf90432d7665e9d35a3/setproctitle-1.3.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81c443310831e29fabbd07b75ebbfa29d0740b56f5907c6af218482d51260431", size = 11959 }, + { url = "https://files.pythonhosted.org/packages/b7/b9/4878ef9d8483adfd1edf6bf95151362aaec0d05aac306a97ff0383f491b5/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88c63bd395c787b0aa81d8bbc22c1809f311032ce3e823a6517b711129818e4", size = 33463 }, + { url = "https://files.pythonhosted.org/packages/cc/60/3ef49d1931aff2a36a7324a49cca10d77ef03e0278452fd468c33a52d7e3/setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73f14b86d0e2858ece6bf5807c9889670e392c001d414b4293d0d9b291942c3", size = 34959 }, + { url = "https://files.pythonhosted.org/packages/81/c6/dee0a973acecefb0db6c9c2e0ea7f18b7e4db773a72e534741ebdee8bbb8/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3393859eb8f19f5804049a685bf286cb08d447e28ba5c6d8543c7bf5500d5970", size = 32055 }, + { url = "https://files.pythonhosted.org/packages/ea/a5/5dd5c4192cf18d16349a32a07f728a9a48a2a05178e16966cabd6645903e/setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785cd210c0311d9be28a70e281a914486d62bfd44ac926fcd70cf0b4d65dff1c", size = 32986 }, + { url = "https://files.pythonhosted.org/packages/df/a6/1508d37eb8008670d33f13fcdb91cbd8ef54697276469abbfdd3d4428c59/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c051f46ed1e13ba8214b334cbf21902102807582fbfaf0fef341b9e52f0fafbf", size = 32736 }, + { url = "https://files.pythonhosted.org/packages/1a/73/c84ec8880d543766a12fcd6b65dbd013770974a40577889f357409b0441e/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:49498ebf68ca3e75321ffe634fcea5cc720502bfaa79bd6b03ded92ce0dc3c24", size = 31945 }, + { url = "https://files.pythonhosted.org/packages/95/0a/126b9ff7a406a69a62825fe5bd6d1ba8671919a7018c4f9e2c63f49bfcb6/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4431629c178193f23c538cb1de3da285a99ccc86b20ee91d81eb5f1a80e0d2ba", size = 34333 }, + { url = "https://files.pythonhosted.org/packages/9a/fd/5474b04f1c013ff460129d2bc774557dd6e186da4667865efef9a83bf378/setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d136fbf8ad4321716e44d6d6b3d8dffb4872626010884e07a1db54b7450836cf", size = 32508 }, + { url = "https://files.pythonhosted.org/packages/32/21/2503e38520cb076a7ecaef6a35d6a6fa89cf02af3541c84c811fd7500d20/setproctitle-1.3.6-cp313-cp313t-win32.whl", hash = "sha256:d483cc23cc56ab32911ea0baa0d2d9ea7aa065987f47de847a0a93a58bf57905", size = 11482 }, + { url = "https://files.pythonhosted.org/packages/65/23/7833d75a27fba25ddc5cd3b54cd03c4bf8e18b8e2dbec622eb6326278ce8/setproctitle-1.3.6-cp313-cp313t-win_amd64.whl", hash = "sha256:74973aebea3543ad033b9103db30579ec2b950a466e09f9c2180089e8346e0ec", size = 12209 }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, +] + +[[package]] +name = "shapely" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368 }, + { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362 }, + { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005 }, + { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489 }, + { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727 }, + { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311 }, + { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982 }, + { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872 }, + { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021 }, + { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018 }, + { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417 }, + { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224 }, + { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982 }, + { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122 }, + { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437 }, + { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479 }, + { url = "https://files.pythonhosted.org/packages/71/8e/2bc836437f4b84d62efc1faddce0d4e023a5d990bbddd3c78b2004ebc246/shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48", size = 1832107 }, + { url = "https://files.pythonhosted.org/packages/12/a2/12c7cae5b62d5d851c2db836eadd0986f63918a91976495861f7c492f4a9/shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6", size = 1642355 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/6d28b43d53fea56de69c744e34c2b999ed4042f7a811dc1bceb876071c95/shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c", size = 2968871 }, + { url = "https://files.pythonhosted.org/packages/dd/87/1017c31e52370b2b79e4d29e07cbb590ab9e5e58cf7e2bdfe363765d6251/shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a", size = 3080830 }, + { url = "https://files.pythonhosted.org/packages/1d/fe/f4a03d81abd96a6ce31c49cd8aaba970eaaa98e191bd1e4d43041e57ae5a/shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de", size = 3908961 }, + { url = "https://files.pythonhosted.org/packages/ef/59/7605289a95a6844056a2017ab36d9b0cb9d6a3c3b5317c1f968c193031c9/shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8", size = 4079623 }, + { url = "https://files.pythonhosted.org/packages/bc/4d/9fea036eff2ef4059d30247128b2d67aaa5f0b25e9fc27e1d15cc1b84704/shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52", size = 1521916 }, + { url = "https://files.pythonhosted.org/packages/12/d9/6d13b8957a17c95794f0c4dfb65ecd0957e6c7131a56ce18d135c1107a52/shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97", size = 1702746 }, + { url = "https://files.pythonhosted.org/packages/60/36/b1452e3e7f35f5f6454d96f3be6e2bb87082720ff6c9437ecc215fa79be0/shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d", size = 1833482 }, + { url = "https://files.pythonhosted.org/packages/ce/ca/8e6f59be0718893eb3e478141285796a923636dc8f086f83e5b0ec0036d0/shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05", size = 1642256 }, + { url = "https://files.pythonhosted.org/packages/ab/78/0053aea449bb1d4503999525fec6232f049abcdc8df60d290416110de943/shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0", size = 3016614 }, + { url = "https://files.pythonhosted.org/packages/ee/53/36f1b1de1dfafd1b457dcbafa785b298ce1b8a3e7026b79619e708a245d5/shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913", size = 3093542 }, + { url = "https://files.pythonhosted.org/packages/b9/bf/0619f37ceec6b924d84427c88835b61f27f43560239936ff88915c37da19/shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d", size = 3945961 }, + { url = "https://files.pythonhosted.org/packages/93/c9/20ca4afeb572763b07a7997f00854cb9499df6af85929e93012b189d8917/shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9", size = 4089514 }, + { url = "https://files.pythonhosted.org/packages/33/6a/27036a5a560b80012a544366bceafd491e8abb94a8db14047b5346b5a749/shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db", size = 1540607 }, + { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "stamp" +version = "2.1.0" +source = { editable = "." } +dependencies = [ + { name = "beartype" }, + { name = "einops" }, + { name = "h5py" }, + { name = "jaxtyping" }, + { name = "lightning" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "openpyxl" }, + { name = "openslide-bin" }, + { name = "openslide-python" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "timm" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +all = [ + { name = "conch" }, + { name = "einops-exts" }, + { name = "environs" }, + { name = "gdown" }, + { name = "gigapath" }, + { name = "huggingface-hub" }, + { name = "madeleine" }, + { name = "musk" }, + { name = "sacremoses" }, + { name = "torch" }, + { name = "transformers" }, + { name = "uni" }, +] +chief-ctranspath = [ + { name = "gdown" }, + { name = "torch" }, +] +cobra = [ + { name = "cobra" }, + { name = "jinja2" }, +] +conch = [ + { name = "conch" }, + { name = "huggingface-hub" }, +] +conch1-5 = [ + { name = "einops-exts" }, + { name = "transformers" }, +] +ctranspath = [ + { name = "gdown" }, +] +gigapath = [ + { name = "gigapath" }, +] +madeleine = [ + { name = "madeleine" }, +] +musk = [ + { name = "musk" }, +] +plip = [ + { name = "transformers" }, +] +prism = [ + { name = "environs" }, + { name = "sacremoses" }, +] +uni = [ + { name = "huggingface-hub" }, + { name = "uni" }, +] +virchow2 = [ + { name = "huggingface-hub" }, + { name = "torch" }, +] + +[package.dev-dependencies] +dev = [ + { name = "huggingface-hub" }, + { name = "ipykernel" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "beartype", specifier = ">=0.19.0" }, + { name = "cobra", marker = "extra == 'cobra'", git = "https://github.com/KatherLab/COBRA.git?rev=f1a576e1133330ffc2d1df6ee110701921c7b7c9#f1a576e1133330ffc2d1df6ee110701921c7b7c9" }, + { name = "conch", marker = "extra == 'conch'", git = "https://github.com/Mahmoodlab/CONCH.git?rev=02d6ac59cc20874bff0f581de258c2b257f69a84#02d6ac59cc20874bff0f581de258c2b257f69a84" }, + { name = "einops", specifier = ">=0.8.0" }, + { name = "einops-exts", marker = "extra == 'conch1-5'", specifier = "==0.0.4" }, + { name = "environs", marker = "extra == 'prism'", specifier = "==11.0.0" }, + { name = "gdown", marker = "extra == 'chief-ctranspath'", specifier = ">=5.2.0" }, + { name = "gdown", marker = "extra == 'ctranspath'", specifier = ">=5.2.0" }, + { name = "gigapath", marker = "extra == 'gigapath'", git = "https://github.com/EzicStar/prov-gigapath.git?rev=d4cf55321df37aaf867e24a31c61bcf490a296eb#d4cf55321df37aaf867e24a31c61bcf490a296eb" }, + { name = "h5py", specifier = ">=3.12.1" }, + { name = "huggingface-hub", marker = "extra == 'conch'", specifier = ">=0.26.2" }, + { name = "huggingface-hub", marker = "extra == 'uni'", specifier = ">=0.26.2" }, + { name = "huggingface-hub", marker = "extra == 'virchow2'", specifier = ">=0.27.1" }, + { name = "jaxtyping", specifier = ">=0.2.36" }, + { name = "jinja2", marker = "extra == 'cobra'", specifier = ">=3.1.4" }, + { name = "lightning", specifier = ">=2.4.0" }, + { name = "madeleine", marker = "extra == 'madeleine'", git = "https://github.com/mahmoodlab/MADELEINE.git?rev=de7c85acc2bdad352e6df8eee5694f8b6f288012#de7c85acc2bdad352e6df8eee5694f8b6f288012" }, + { name = "matplotlib", specifier = ">=3.9.2" }, + { name = "musk", marker = "extra == 'musk'", git = "https://github.com/lilab-stanford/MUSK.git?rev=e1699c27687f44bbf6d4adfcbb2abe89795d347f#e1699c27687f44bbf6d4adfcbb2abe89795d347f" }, + { name = "numpy", specifier = ">=2.2.2" }, + { name = "opencv-python", specifier = ">=4.10.0.84" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "openslide-bin", specifier = ">=4.0.0.6" }, + { name = "openslide-python", specifier = ">=1.4.1" }, + { name = "packaging", specifier = ">=24.2" }, + { name = "pandas", specifier = ">=2.2.3" }, + { name = "pillow", specifier = ">=11.1.0" }, + { name = "pydantic", specifier = ">=2.10.3" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "sacremoses", marker = "extra == 'prism'", specifier = "==0.1.1" }, + { name = "scikit-learn", specifier = ">=1.5.2" }, + { name = "scipy", specifier = ">=1.15.1" }, + { name = "stamp", extras = ["conch", "ctranspath", "uni", "virchow2", "chief-ctranspath", "conch1-5", "gigapath", "prism", "madeleine", "musk", "plip"], marker = "extra == 'all'" }, + { name = "timm", specifier = ">=0.9.11" }, + { name = "torch", specifier = ">=2.5.1" }, + { name = "torch", marker = "extra == 'chief-ctranspath'", specifier = ">=2.0.0" }, + { name = "torch", marker = "extra == 'virchow2'", specifier = ">=2.0.0" }, + { name = "torchmetrics", specifier = ">=1.6.0" }, + { name = "torchvision", specifier = ">=0.20.1" }, + { name = "tqdm", specifier = ">=4.66.6" }, + { name = "transformers", marker = "extra == 'conch1-5'", specifier = ">=4.45.2" }, + { name = "transformers", marker = "extra == 'plip'", specifier = ">=4.45.2" }, + { name = "uni", marker = "extra == 'uni'", git = "https://github.com/mahmoodlab/UNI.git" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "huggingface-hub", specifier = ">=0.27.1" }, + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "pyright", specifier = ">=1.1.389,!=1.1.391" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "ruff", specifier = ">=0.8.1" }, +] + +[[package]] +name = "sympy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + +[[package]] +name = "termcolor" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684 }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, +] + +[[package]] +name = "tifffile" +version = "2025.6.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/9e/636e3e433c24da41dd639e0520db60750dbf5e938d023b83af8097382ea3/tifffile-2025.6.11.tar.gz", hash = "sha256:0ece4c2e7a10656957d568a093b07513c0728d30c1bd8cc12725901fffdb7143", size = 370125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d8/1ba8f32bfc9cb69e37edeca93738e883f478fbe84ae401f72c0d8d507841/tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542", size = 230800 }, +] + +[[package]] +name = "timm" +version = "0.9.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "torchvision" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/f5/c0306adbd9ffa41b80d0b564b498794356e4986f064351e1ea0d55f2b60f/timm-0.9.11.tar.gz", hash = "sha256:728b433eb4cf0bad0c22a037058388fe7c94662f9b9f7d9f5f7dbe627b41c7fc", size = 2127616 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/aa/4b54f6047c442883243f68f6f9e3a0ab77aaae4b3e6e51a98b371e73dd77/timm-0.9.11-py3-none-any.whl", hash = "sha256:02bba56786633ff46b55ee0ce3b991fa85375556844e500ad18e6b12921dc3da", size = 2231391 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, + { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, + { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, + { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, + { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, + { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, + { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, + { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, + { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, + { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, + { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, + { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, + { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, + { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, +] + +[[package]] +name = "torch" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "sympy" }, + { name = "triton", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/a9/97cbbc97002fff0de394a2da2cdfa859481fdca36996d7bd845d50aa9d8d/torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7979834102cd5b7a43cc64e87f2f3b14bd0e1458f06e9f88ffa386d07c7446e1", size = 766715424 }, + { url = "https://files.pythonhosted.org/packages/6d/fa/134ce8f8a7ea07f09588c9cc2cea0d69249efab977707cf67669431dcf5c/torch-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd0320411fe1a3b3fec7b4d3185aa7d0c52adac94480ab024b5c8f74a0bf1d", size = 95759416 }, + { url = "https://files.pythonhosted.org/packages/11/c5/2370d96b31eb1841c3a0883a492c15278a6718ccad61bb6a649c80d1d9eb/torch-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:46763dcb051180ce1ed23d1891d9b1598e07d051ce4c9d14307029809c4d64f7", size = 204164970 }, + { url = "https://files.pythonhosted.org/packages/0b/fa/f33a4148c6fb46ca2a3f8de39c24d473822d5774d652b66ed9b1214da5f7/torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21", size = 66530713 }, + { url = "https://files.pythonhosted.org/packages/e5/35/0c52d708144c2deb595cd22819a609f78fdd699b95ff6f0ebcd456e3c7c1/torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9", size = 766624563 }, + { url = "https://files.pythonhosted.org/packages/01/d6/455ab3fbb2c61c71c8842753b566012e1ed111e7a4c82e0e1c20d0c76b62/torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb", size = 95607867 }, + { url = "https://files.pythonhosted.org/packages/18/cf/ae99bd066571656185be0d88ee70abc58467b76f2f7c8bfeb48735a71fe6/torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239", size = 204120469 }, + { url = "https://files.pythonhosted.org/packages/81/b4/605ae4173aa37fb5aa14605d100ff31f4f5d49f617928c9f486bb3aaec08/torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989", size = 66532538 }, + { url = "https://files.pythonhosted.org/packages/24/85/ead1349fc30fe5a32cadd947c91bda4a62fbfd7f8c34ee61f6398d38fb48/torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf", size = 766626191 }, + { url = "https://files.pythonhosted.org/packages/dd/b0/26f06f9428b250d856f6d512413e9e800b78625f63801cbba13957432036/torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b", size = 95611439 }, + { url = "https://files.pythonhosted.org/packages/c2/9c/fc5224e9770c83faed3a087112d73147cd7c7bfb7557dcf9ad87e1dda163/torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc", size = 204126475 }, + { url = "https://files.pythonhosted.org/packages/88/8b/d60c0491ab63634763be1537ad488694d316ddc4a20eaadd639cedc53971/torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2", size = 66536783 }, +] + +[[package]] +name = "torchmetrics" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ec/f5a4f94c77a1b4c0a37e5c5c8b666a33bc074130258a6b655346bec560c2/torchmetrics-1.7.2.tar.gz", hash = "sha256:ba401cd01aeaa268e809c0e4f42ef8f95669bf9b485e1d93d54dc765e012338a", size = 566185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/89/b5fd7eb99b27457d71d3b7d9eca0b884fa5992abca7672aab1177c5f22d8/torchmetrics-1.7.2-py3-none-any.whl", hash = "sha256:9cc3bff07a715fcb37fb04d2a1a5ae36267c36066c097578020056653a94f2a8", size = 962510 }, +] + +[[package]] +name = "torchvision" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/3d/b7241abfa3e6651c6e00796f5de2bd1ce4d500bf5159bcbfeea47e711b93/torchvision-0.21.0-1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ff96666b94a55e802ea6796cabe788541719e6f4905fc59c380fed3517b6a64d", size = 2329320 }, + { url = "https://files.pythonhosted.org/packages/52/5b/76ca113a853b19c7b1da761f8a72cb6429b3bd0bf932537d8df4657f47c3/torchvision-0.21.0-1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ffa2a16499508fe6798323e455f312c7c55f2a88901c9a7c0fb1efa86cf7e327", size = 2329878 }, + { url = "https://files.pythonhosted.org/packages/4e/fe/5e193353706dab96fe73ae100d5a633ff635ce310e0d92f3bc2958d075b1/torchvision-0.21.0-1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7e9e9afa150e40cd2a8f0701c43cb82a8d724f512896455c0918b987f94b84a4", size = 2280711 }, + { url = "https://files.pythonhosted.org/packages/29/88/00c69db213ee2443ada8886ec60789b227e06bb869d85ee324578221a7f7/torchvision-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110d115333524d60e9e474d53c7d20f096dbd8a080232f88dddb90566f90064c", size = 1784141 }, + { url = "https://files.pythonhosted.org/packages/be/a2/b0cedf0a411f1a5d75cfc0b87cde56dd1ddc1878be46a42c905cd8580220/torchvision-0.21.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:3891cd086c5071bda6b4ee9d266bb2ac39c998c045c2ebcd1e818b8316fb5d41", size = 7237719 }, + { url = "https://files.pythonhosted.org/packages/8c/a1/ee962ef9d0b2bf7a6f8b14cb95acb70e05cd2101af521032a09e43f8582f/torchvision-0.21.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:54454923a50104c66a9ab6bd8b73a11c2fc218c964b1006d5d1fe5b442c3dcb6", size = 14700617 }, + { url = "https://files.pythonhosted.org/packages/88/53/4ad334b9b1d8dd99836869fec139cb74a27781298360b91b9506c53f1d10/torchvision-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:49bcfad8cfe2c27dee116c45d4f866d7974bcf14a5a9fbef893635deae322f2f", size = 1560523 }, + { url = "https://files.pythonhosted.org/packages/6e/1b/28f527b22d5e8800184d0bc847f801ae92c7573a8c15979d92b7091c0751/torchvision-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97a5814a93c793aaf0179cfc7f916024f4b63218929aee977b645633d074a49f", size = 1784140 }, + { url = "https://files.pythonhosted.org/packages/36/63/0722e153fd27d64d5b0af45b5c8cb0e80b35a68cf0130303bc9a8bb095c7/torchvision-0.21.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:b578bcad8a4083b40d34f689b19ca9f7c63e511758d806510ea03c29ac568f7b", size = 7238673 }, + { url = "https://files.pythonhosted.org/packages/bb/ea/03541ed901cdc30b934f897060d09bbf7a98466a08ad1680320f9ce0cbe0/torchvision-0.21.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5083a5b1fec2351bf5ea9900a741d54086db75baec4b1d21e39451e00977f1b1", size = 14701186 }, + { url = "https://files.pythonhosted.org/packages/4c/6a/c7752603060d076dfed95135b78b047dc71792630cbcb022e3693d6f32ef/torchvision-0.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:6eb75d41e3bbfc2f7642d0abba9383cc9ae6c5a4ca8d6b00628c225e1eaa63b3", size = 1560520 }, + { url = "https://files.pythonhosted.org/packages/f9/56/47d456b61c3bbce7bed4af3925c83d405bb87468e659fd3cf3d9840c3b51/torchvision-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:659b76c86757cb2ee4ca2db245e0740cfc3081fef46f0f1064d11adb4a8cee31", size = 1784141 }, + { url = "https://files.pythonhosted.org/packages/cb/4c/99880813aa50e64447fb1c4c6c804a793d2d78f7f7c53e99ddee7fa175fa/torchvision-0.21.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:084ac3f5a1f50c70d630a488d19bf62f323018eae1b1c1232f2b7047d3a7b76d", size = 7238714 }, + { url = "https://files.pythonhosted.org/packages/0b/2d/3c3ee10608310a395594aac7da8640372ed79c6585910ccae6919658dcdc/torchvision-0.21.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5045a3a5f21ec3eea6962fa5f2fa2d4283f854caec25ada493fcf4aab2925467", size = 2281252 }, + { url = "https://files.pythonhosted.org/packages/ed/b4/fc60e3bc003879d3de842baea258fffc3586f4b49cd435a5ba1e09c33315/torchvision-0.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:9147f5e096a9270684e3befdee350f3cacafd48e0c54ab195f45790a9c146d67", size = 1560519 }, +] + +[[package]] +name = "tornado" +version = "6.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948 }, + { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112 }, + { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672 }, + { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019 }, + { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252 }, + { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930 }, + { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351 }, + { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328 }, + { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396 }, + { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840 }, + { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "transformers" +version = "4.52.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/a9/275037087f9d846580b02f2d7cae0e0a6955d46f84583d0151d6227bd416/transformers-4.52.4.tar.gz", hash = "sha256:aff3764441c1adc192a08dba49740d3cbbcb72d850586075aed6bd89b98203e6", size = 8945376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/f2/25b27b396af03d5b64e61976b14f7209e2939e9e806c10749b6d277c273e/transformers-4.52.4-py3-none-any.whl", hash = "sha256:203f5c19416d5877e36e88633943761719538a25d9775977a24fe77a1e5adfc7", size = 10460375 }, +] + +[[package]] +name = "triton" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/2e/757d2280d4fefe7d33af7615124e7e298ae7b8e3bc4446cdb8e88b0f9bab/triton-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8009a1fb093ee8546495e96731336a33fb8856a38e45bb4ab6affd6dbc3ba220", size = 253157636 }, + { url = "https://files.pythonhosted.org/packages/06/00/59500052cb1cf8cf5316be93598946bc451f14072c6ff256904428eaf03c/triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c", size = 253159365 }, + { url = "https://files.pythonhosted.org/packages/c7/30/37a3384d1e2e9320331baca41e835e90a3767303642c7a80d4510152cbcf/triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0", size = 253154278 }, +] + +[[package]] +name = "triton" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux'", +] +dependencies = [ + { name = "setuptools", marker = "platform_machine == 'aarch64' or sys_platform != 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/c5/4874a81131cc9e934d88377fbc9d24319ae1fb540f3333b4e9c696ebc607/triton-3.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3161a2bf073d6b22c4e2f33f951f3e5e3001462b2570e6df9cd57565bdec2984", size = 156528461 }, + { url = "https://files.pythonhosted.org/packages/11/53/ce18470914ab6cfbec9384ee565d23c4d1c55f0548160b1c7b33000b11fd/triton-3.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b68c778f6c4218403a6bd01be7484f6dc9e20fe2083d22dd8aef33e3b87a10a3", size = 156504509 }, + { url = "https://files.pythonhosted.org/packages/7d/74/4bf2702b65e93accaa20397b74da46fb7a0356452c1bb94dbabaf0582930/triton-3.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47bc87ad66fa4ef17968299acacecaab71ce40a238890acc6ad197c3abe2b8f1", size = 156516468 }, + { url = "https://files.pythonhosted.org/packages/0a/93/f28a696fa750b9b608baa236f8225dd3290e5aff27433b06143adc025961/triton-3.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce4700fc14032af1e049005ae94ba908e71cd6c2df682239aed08e49bc71b742", size = 156580729 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] + +[[package]] +name = "uni" +version = "0.1.0" +source = { git = "https://github.com/mahmoodlab/UNI.git#42715efc11722a496e0a67f3369505a8f277206c" } +dependencies = [ + { name = "numpy" }, + { name = "pandas" }, + { name = "scikit-learn" }, + { name = "timm" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "transformers" }, + { name = "xformers", marker = "sys_platform != 'darwin'" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "wadler-lindig" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/c8/e2112ecb627e01c9e2911f9b388167231c23a114946946d046f4e9535118/wadler_lindig-0.1.6.tar.gz", hash = "sha256:8b6adad9718291a7d82fb088a596b93659ce2346321ca76819810affbc66102b", size = 15812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/9a/937038f3efc70871fb26b0ee6148efcfcfb96643c517c2aaddd7ed07ad76/wadler_lindig-0.1.6-py3-none-any.whl", hash = "sha256:d707f63994c7d3e1e125e7fb7e196f4adb6f80f4a11beb955c6da937754026a3", size = 20483 }, +] + +[[package]] +name = "wandb" +version = "0.19.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docker-pycreds" }, + { name = "gitpython" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "setproctitle" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/98/0ff2925a21b998d4b84731429f4554ca3d9b5cad42c09c075e7306c3aca0/wandb-0.19.11.tar.gz", hash = "sha256:3f50a27dfadbb25946a513ffe856c0e8e538b5626ef207aa50b00c3b0356bff8", size = 39511477 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/2c/f8bab58c73fdde4442f1baffd9ea5d1bb3113906a97a27e8d9ab72db7a69/wandb-0.19.11-py3-none-any.whl", hash = "sha256:ff3bf050ba25ebae7aedc9a775ffab90c28068832edfe5458423f488c2558f82", size = 6481327 }, + { url = "https://files.pythonhosted.org/packages/45/4a/34b364280f690f4c6d7660f528fba9f13bdecabc4c869d266a4632cf836e/wandb-0.19.11-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:0823fd9aa6343f40c04e01959997ca8c6d6adf1bd81c8d45261fa4915f1c6b67", size = 20555751 }, + { url = "https://files.pythonhosted.org/packages/d8/e6/a27868fdb83a60df37b9d15e52c3353dd88d74442f27ae48cf765c6b9554/wandb-0.19.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c758ef5439599d9023db5b3cf1698477055d82f9fae48af2779f63f1d289167c", size = 20377587 }, + { url = "https://files.pythonhosted.org/packages/21/f7/d5cf5b58c2b3015364c7b2b6af6a440cbeda4103b67332e1e64b30f6252d/wandb-0.19.11-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:de2dfd4911e7691735e271654c735e7b90cdee9d29a3796fbf06e9e92d48f3d7", size = 20985041 }, + { url = "https://files.pythonhosted.org/packages/68/06/8b827f16a0b8f18002d2fffa7c5a7fd447946e0d0c68aeec0dd7eb18cdd3/wandb-0.19.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfff738850770d26b13f8f3fe400a6456f1e39e87f3f29d5aa241b249476df95", size = 20017696 }, + { url = "https://files.pythonhosted.org/packages/f9/31/eeb2878b26566c04c3e9b8b20b3ec3c54a2be50535088d36a37c008e07a3/wandb-0.19.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ff673007448df11cc69379ae0df28ead866800dc1ec7bc151b402db0bbcf40", size = 21425857 }, + { url = "https://files.pythonhosted.org/packages/10/30/08988360678ae78334bb16625c28260fcaba49f500b89f8766807cb74d71/wandb-0.19.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:858bc5023fa1b3285d89d15f62be78afdb28301064daa49ea3f4ebde5dcedad2", size = 20023145 }, + { url = "https://files.pythonhosted.org/packages/c8/e9/a639c42c8ca517c4d25e8970d64d0c5a9bd35b784faed5f47d9cca3dcd12/wandb-0.19.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90e4b57649896acb16c3dd41b3093df1a169c2f1d94ff15d76af86b8a60dcdac", size = 21504842 }, + { url = "https://files.pythonhosted.org/packages/44/74/dbe9277dd935b77dd16939cdf15357766fec0813a6e336cf5f1d07eb016e/wandb-0.19.11-py3-none-win32.whl", hash = "sha256:38dea43c7926d8800405a73b80b9adfe81eb315fc6f2ac6885c77eb966634421", size = 20767584 }, + { url = "https://files.pythonhosted.org/packages/36/d5/215cac3edec5c5ac6e7231beb9d22466d5d4e4a132fa3a1d044f7d682c15/wandb-0.19.11-py3-none-win_amd64.whl", hash = "sha256:73402003c56ddc2198878492ab2bff55bb49bce5587eae5960e737d27c0c48f7", size = 20767588 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webdataset" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "braceexpand" }, + { name = "numpy" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/3a/68800d92e065cf4750ebecf973b13979c0c929b439e1293012938862038d/webdataset-1.0.2.tar.gz", hash = "sha256:7f0498be827cfa46cc5430a58768a24e2c6a410676a61be1838f53d61afdaab4", size = 80090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/00/aca6beb3658dab4ed3dbb41a78e6e7f31342e0b41d28088f205525751601/webdataset-1.0.2-py3-none-any.whl", hash = "sha256:3dbfced32b25c0d199c6b9787937b6f85742bc3c84f652c846893075c1c082d9", size = 74956 }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308 }, + { url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488 }, + { url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776 }, + { url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776 }, + { url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420 }, + { url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199 }, + { url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307 }, + { url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025 }, + { url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879 }, + { url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419 }, + { url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773 }, + { url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799 }, + { url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821 }, + { url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919 }, + { url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721 }, + { url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899 }, + { url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222 }, + { url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707 }, + { url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685 }, + { url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567 }, + { url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672 }, + { url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865 }, + { url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800 }, + { url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824 }, + { url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920 }, + { url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690 }, + { url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861 }, + { url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174 }, + { url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721 }, + { url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763 }, + { url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585 }, + { url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676 }, + { url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871 }, + { url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312 }, + { url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062 }, + { url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155 }, + { url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471 }, + { url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208 }, + { url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339 }, + { url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232 }, + { url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476 }, + { url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377 }, + { url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986 }, + { url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750 }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +] + +[[package]] +name = "xformers" +version = "0.0.29.post3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform != 'darwin'" }, + { name = "torch", marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/fd/e9201fbee6a1a6d7a9c67c24a256ad4c2377bc67a634f7dbeaea23bd668a/xformers-0.0.29.post3.tar.gz", hash = "sha256:0b77c67ecc3c9fdd8a0e4399e675adf12e2ff40285e00974cca2d09108157f60", size = 8461348 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/05/9c9faf1c7b3b7b986bbf7a488a185eb67670a8435d0eae94aa59f56181cd/xformers-0.0.29.post3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bbf2f500dfdbcf4649bf568cc2c9f434399f704dc4064fd1fbdbef2b524a8139", size = 43362399 }, + { url = "https://files.pythonhosted.org/packages/e0/9f/8195d17a5ad1b601bb487f24e54331d102df7f1649e2ced6375eef272e28/xformers-0.0.29.post3-cp311-cp311-win_amd64.whl", hash = "sha256:00f2dfd94c894ff6372e21bee3f09e96bce75b55649df366649c43f049eb7a1e", size = 167742633 }, + { url = "https://files.pythonhosted.org/packages/2d/4a/20b2d9ac50efa0d40fbdb13283fd168cc2db28a2f21a159abbdd17a24213/xformers-0.0.29.post3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:08fa92f3e06372c4ce2a5306c54ae3d4a3a399fc7e24e02aac3761112ec3aeed", size = 43364118 }, + { url = "https://files.pythonhosted.org/packages/d9/ec/7846937d26b2601e40cd6e64583657f753415b94ad318e4ca350270e77d2/xformers-0.0.29.post3-cp312-cp312-win_amd64.whl", hash = "sha256:3706eca371767ff9709595185910d809fc817ec3cf4234ef44d70d2b8844d7e2", size = 167743565 }, +] + +[[package]] +name = "yacs" +version = "0.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/3e/4a45cb0738da6565f134c01d82ba291c746551b5bc82e781ec876eb20909/yacs-0.1.8.tar.gz", hash = "sha256:efc4c732942b3103bea904ee89af98bcd27d01f0ac12d8d4d369f1e7a2914384", size = 11100 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/4f/fe9a4d472aa867878ce3bb7efb16654c5d63672b86dc0e6e953a67018433/yacs-0.1.8-py3-none-any.whl", hash = "sha256:99f893e30497a4b66842821bac316386f7bd5c4f47ad35c9073ef089aa33af32", size = 14747 }, +] + +[[package]] +name = "yarl" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/51/c0edba5219027f6eab262e139f73e2417b0f4efffa23bf562f6e18f76ca5/yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307", size = 185258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/82/a59d8e21b20ffc836775fa7daedac51d16bb8f3010c4fcb495c4496aa922/yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3", size = 145178 }, + { url = "https://files.pythonhosted.org/packages/ba/81/315a3f6f95947cfbf37c92d6fbce42a1a6207b6c38e8c2b452499ec7d449/yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a", size = 96859 }, + { url = "https://files.pythonhosted.org/packages/ad/17/9b64e575583158551b72272a1023cdbd65af54fe13421d856b2850a6ddb7/yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2", size = 94647 }, + { url = "https://files.pythonhosted.org/packages/2c/29/8f291e7922a58a21349683f6120a85701aeefaa02e9f7c8a2dc24fe3f431/yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e", size = 355788 }, + { url = "https://files.pythonhosted.org/packages/26/6d/b4892c80b805c42c228c6d11e03cafabf81662d371b0853e7f0f513837d5/yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9", size = 344613 }, + { url = "https://files.pythonhosted.org/packages/d7/0e/517aa28d3f848589bae9593717b063a544b86ba0a807d943c70f48fcf3bb/yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a", size = 370953 }, + { url = "https://files.pythonhosted.org/packages/5f/9b/5bd09d2f1ad6e6f7c2beae9e50db78edd2cca4d194d227b958955573e240/yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2", size = 369204 }, + { url = "https://files.pythonhosted.org/packages/9c/85/d793a703cf4bd0d4cd04e4b13cc3d44149470f790230430331a0c1f52df5/yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2", size = 358108 }, + { url = "https://files.pythonhosted.org/packages/6f/54/b6c71e13549c1f6048fbc14ce8d930ac5fb8bafe4f1a252e621a24f3f1f9/yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8", size = 346610 }, + { url = "https://files.pythonhosted.org/packages/a0/1a/d6087d58bdd0d8a2a37bbcdffac9d9721af6ebe50d85304d9f9b57dfd862/yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902", size = 365378 }, + { url = "https://files.pythonhosted.org/packages/02/84/e25ddff4cbc001dbc4af76f8d41a3e23818212dd1f0a52044cbc60568872/yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791", size = 356919 }, + { url = "https://files.pythonhosted.org/packages/04/76/898ae362353bf8f64636495d222c8014c8e5267df39b1a9fe1e1572fb7d0/yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f", size = 364248 }, + { url = "https://files.pythonhosted.org/packages/1b/b0/9d9198d83a622f1c40fdbf7bd13b224a6979f2e1fc2cf50bfb1d8773c495/yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da", size = 378418 }, + { url = "https://files.pythonhosted.org/packages/c7/ce/1f50c1cc594cf5d3f5bf4a9b616fca68680deaec8ad349d928445ac52eb8/yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/89/1e/a59253a87b35bfec1a25bb5801fb69943330b67cfd266278eb07e0609012/yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5", size = 381218 }, + { url = "https://files.pythonhosted.org/packages/85/b0/26f87df2b3044b0ef1a7cf66d321102bdca091db64c5ae853fcb2171c031/yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6", size = 86606 }, + { url = "https://files.pythonhosted.org/packages/33/46/ca335c2e1f90446a77640a45eeb1cd8f6934f2c6e4df7db0f0f36ef9f025/yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb", size = 93374 }, + { url = "https://files.pythonhosted.org/packages/c3/e8/3efdcb83073df978bb5b1a9cc0360ce596680e6c3fac01f2a994ccbb8939/yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f", size = 147089 }, + { url = "https://files.pythonhosted.org/packages/60/c3/9e776e98ea350f76f94dd80b408eaa54e5092643dbf65fd9babcffb60509/yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e", size = 97706 }, + { url = "https://files.pythonhosted.org/packages/0c/5b/45cdfb64a3b855ce074ae607b9fc40bc82e7613b94e7612b030255c93a09/yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e", size = 95719 }, + { url = "https://files.pythonhosted.org/packages/2d/4e/929633b249611eeed04e2f861a14ed001acca3ef9ec2a984a757b1515889/yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33", size = 343972 }, + { url = "https://files.pythonhosted.org/packages/49/fd/047535d326c913f1a90407a3baf7ff535b10098611eaef2c527e32e81ca1/yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58", size = 339639 }, + { url = "https://files.pythonhosted.org/packages/48/2f/11566f1176a78f4bafb0937c0072410b1b0d3640b297944a6a7a556e1d0b/yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f", size = 353745 }, + { url = "https://files.pythonhosted.org/packages/26/17/07dfcf034d6ae8837b33988be66045dd52f878dfb1c4e8f80a7343f677be/yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae", size = 354178 }, + { url = "https://files.pythonhosted.org/packages/15/45/212604d3142d84b4065d5f8cab6582ed3d78e4cc250568ef2a36fe1cf0a5/yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018", size = 349219 }, + { url = "https://files.pythonhosted.org/packages/e6/e0/a10b30f294111c5f1c682461e9459935c17d467a760c21e1f7db400ff499/yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672", size = 337266 }, + { url = "https://files.pythonhosted.org/packages/33/a6/6efa1d85a675d25a46a167f9f3e80104cde317dfdf7f53f112ae6b16a60a/yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8", size = 360873 }, + { url = "https://files.pythonhosted.org/packages/77/67/c8ab718cb98dfa2ae9ba0f97bf3cbb7d45d37f13fe1fbad25ac92940954e/yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7", size = 360524 }, + { url = "https://files.pythonhosted.org/packages/bd/e8/c3f18660cea1bc73d9f8a2b3ef423def8dadbbae6c4afabdb920b73e0ead/yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594", size = 365370 }, + { url = "https://files.pythonhosted.org/packages/c9/99/33f3b97b065e62ff2d52817155a89cfa030a1a9b43fee7843ef560ad9603/yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6", size = 373297 }, + { url = "https://files.pythonhosted.org/packages/3d/89/7519e79e264a5f08653d2446b26d4724b01198a93a74d2e259291d538ab1/yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1", size = 378771 }, + { url = "https://files.pythonhosted.org/packages/3a/58/6c460bbb884abd2917c3eef6f663a4a873f8dc6f498561fc0ad92231c113/yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b", size = 375000 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/dd7ed1aa23fea996834278d7ff178f215b24324ee527df53d45e34d21d28/yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64", size = 86355 }, + { url = "https://files.pythonhosted.org/packages/ca/c6/333fe0338305c0ac1c16d5aa7cc4841208d3252bbe62172e0051006b5445/yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c", size = 92904 }, + { url = "https://files.pythonhosted.org/packages/0f/6f/514c9bff2900c22a4f10e06297714dbaf98707143b37ff0bcba65a956221/yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f", size = 145030 }, + { url = "https://files.pythonhosted.org/packages/4e/9d/f88da3fa319b8c9c813389bfb3463e8d777c62654c7168e580a13fadff05/yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3", size = 96894 }, + { url = "https://files.pythonhosted.org/packages/cd/57/92e83538580a6968b2451d6c89c5579938a7309d4785748e8ad42ddafdce/yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d", size = 94457 }, + { url = "https://files.pythonhosted.org/packages/e9/ee/7ee43bd4cf82dddd5da97fcaddb6fa541ab81f3ed564c42f146c83ae17ce/yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0", size = 343070 }, + { url = "https://files.pythonhosted.org/packages/4a/12/b5eccd1109e2097bcc494ba7dc5de156e41cf8309fab437ebb7c2b296ce3/yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501", size = 337739 }, + { url = "https://files.pythonhosted.org/packages/7d/6b/0eade8e49af9fc2585552f63c76fa59ef469c724cc05b29519b19aa3a6d5/yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc", size = 351338 }, + { url = "https://files.pythonhosted.org/packages/45/cb/aaaa75d30087b5183c7b8a07b4fb16ae0682dd149a1719b3a28f54061754/yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d", size = 353636 }, + { url = "https://files.pythonhosted.org/packages/98/9d/d9cb39ec68a91ba6e66fa86d97003f58570327d6713833edf7ad6ce9dde5/yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0", size = 348061 }, + { url = "https://files.pythonhosted.org/packages/72/6b/103940aae893d0cc770b4c36ce80e2ed86fcb863d48ea80a752b8bda9303/yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a", size = 334150 }, + { url = "https://files.pythonhosted.org/packages/ef/b2/986bd82aa222c3e6b211a69c9081ba46484cffa9fab2a5235e8d18ca7a27/yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2", size = 362207 }, + { url = "https://files.pythonhosted.org/packages/14/7c/63f5922437b873795d9422cbe7eb2509d4b540c37ae5548a4bb68fd2c546/yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9", size = 361277 }, + { url = "https://files.pythonhosted.org/packages/81/83/450938cccf732466953406570bdb42c62b5ffb0ac7ac75a1f267773ab5c8/yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5", size = 364990 }, + { url = "https://files.pythonhosted.org/packages/b4/de/af47d3a47e4a833693b9ec8e87debb20f09d9fdc9139b207b09a3e6cbd5a/yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877", size = 374684 }, + { url = "https://files.pythonhosted.org/packages/62/0b/078bcc2d539f1faffdc7d32cb29a2d7caa65f1a6f7e40795d8485db21851/yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e", size = 382599 }, + { url = "https://files.pythonhosted.org/packages/74/a9/4fdb1a7899f1fb47fd1371e7ba9e94bff73439ce87099d5dd26d285fffe0/yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384", size = 378573 }, + { url = "https://files.pythonhosted.org/packages/fd/be/29f5156b7a319e4d2e5b51ce622b4dfb3aa8d8204cd2a8a339340fbfad40/yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62", size = 86051 }, + { url = "https://files.pythonhosted.org/packages/52/56/05fa52c32c301da77ec0b5f63d2d9605946fe29defacb2a7ebd473c23b81/yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c", size = 92742 }, + { url = "https://files.pythonhosted.org/packages/d4/2f/422546794196519152fc2e2f475f0e1d4d094a11995c81a465faf5673ffd/yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051", size = 163575 }, + { url = "https://files.pythonhosted.org/packages/90/fc/67c64ddab6c0b4a169d03c637fb2d2a212b536e1989dec8e7e2c92211b7f/yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d", size = 106121 }, + { url = "https://files.pythonhosted.org/packages/6d/00/29366b9eba7b6f6baed7d749f12add209b987c4cfbfa418404dbadc0f97c/yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229", size = 103815 }, + { url = "https://files.pythonhosted.org/packages/28/f4/a2a4c967c8323c03689383dff73396281ced3b35d0ed140580825c826af7/yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1", size = 408231 }, + { url = "https://files.pythonhosted.org/packages/0f/a1/66f7ffc0915877d726b70cc7a896ac30b6ac5d1d2760613603b022173635/yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb", size = 390221 }, + { url = "https://files.pythonhosted.org/packages/41/15/cc248f0504610283271615e85bf38bc014224122498c2016d13a3a1b8426/yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00", size = 411400 }, + { url = "https://files.pythonhosted.org/packages/5c/af/f0823d7e092bfb97d24fce6c7269d67fcd1aefade97d0a8189c4452e4d5e/yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de", size = 411714 }, + { url = "https://files.pythonhosted.org/packages/83/70/be418329eae64b9f1b20ecdaac75d53aef098797d4c2299d82ae6f8e4663/yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5", size = 404279 }, + { url = "https://files.pythonhosted.org/packages/19/f5/52e02f0075f65b4914eb890eea1ba97e6fd91dd821cc33a623aa707b2f67/yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a", size = 384044 }, + { url = "https://files.pythonhosted.org/packages/6a/36/b0fa25226b03d3f769c68d46170b3e92b00ab3853d73127273ba22474697/yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9", size = 416236 }, + { url = "https://files.pythonhosted.org/packages/cb/3a/54c828dd35f6831dfdd5a79e6c6b4302ae2c5feca24232a83cb75132b205/yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145", size = 402034 }, + { url = "https://files.pythonhosted.org/packages/10/97/c7bf5fba488f7e049f9ad69c1b8fdfe3daa2e8916b3d321aa049e361a55a/yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda", size = 407943 }, + { url = "https://files.pythonhosted.org/packages/fd/a4/022d2555c1e8fcff08ad7f0f43e4df3aba34f135bff04dd35d5526ce54ab/yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f", size = 423058 }, + { url = "https://files.pythonhosted.org/packages/4c/f6/0873a05563e5df29ccf35345a6ae0ac9e66588b41fdb7043a65848f03139/yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd", size = 423792 }, + { url = "https://files.pythonhosted.org/packages/9e/35/43fbbd082708fa42e923f314c24f8277a28483d219e049552e5007a9aaca/yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f", size = 422242 }, + { url = "https://files.pythonhosted.org/packages/ed/f7/f0f2500cf0c469beb2050b522c7815c575811627e6d3eb9ec7550ddd0bfe/yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac", size = 93816 }, + { url = "https://files.pythonhosted.org/packages/3f/93/f73b61353b2a699d489e782c3f5998b59f974ec3156a2050a52dfd7e8946/yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe", size = 101093 }, + { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124 }, +] From a4c194d07df83e9e7e6d3dab5d7522af6d42c047 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 10:42:07 +0200 Subject: [PATCH 072/109] ignore openvpn configuration files at recommended location --- assets/openvpn_configs/good_access/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 assets/openvpn_configs/good_access/.gitignore diff --git a/assets/openvpn_configs/good_access/.gitignore b/assets/openvpn_configs/good_access/.gitignore new file mode 100644 index 00000000..2e66e21c --- /dev/null +++ b/assets/openvpn_configs/good_access/.gitignore @@ -0,0 +1 @@ +*.ovpn \ No newline at end of file From f0f710176739a14183baf01165d24c1db30b994e Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 10:48:53 +0200 Subject: [PATCH 073/109] log mediswarm version in training (passed to the Docker container as an environment variable) --- .../ODELIA_ternary_classification/app/custom/env_config.py | 3 ++- .../ODELIA_ternary_classification/app/custom/threedcnn_ptl.py | 1 + buildDockerImageAndStartupKits.sh | 1 + docker_config/master_template.yml | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/env_config.py b/application/jobs/ODELIA_ternary_classification/app/custom/env_config.py index d624e144..93efb091 100755 --- a/application/jobs/ODELIA_ternary_classification/app/custom/env_config.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/env_config.py @@ -16,7 +16,8 @@ def load_environment_variables(): 'use_adaptive_sync': os.environ.get('USE_ADAPTIVE_SYNC', 'False').lower() == 'true', 'sync_frequency': int(os.environ.get('SYNC_FREQUENCY', 1024)), 'model_name': os.environ.get('MODEL_NAME', 'ResNet101'), - 'prediction_flag': os.environ.get('PREDICT_FLAG', 'ext') + 'prediction_flag': os.environ.get('PREDICT_FLAG', 'ext'), + 'mediswarm_version': os.environ.get('MEDISWARM_VERSION', 'unset'), } diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py index 3b3d815e..ad291652 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/threedcnn_ptl.py @@ -78,6 +78,7 @@ def prepare_training(logger, max_epochs: int, site_name: str): if not torch.cuda.is_available(): raise RuntimeError("This example requires a GPU") + logger.info(f"Running code version {env_vars['mediswarm_version']}") logger.info(f"Using GPU for training") model_name = env_vars['model_name'] diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index e1d582c8..51b07f2c 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -41,6 +41,7 @@ git clean -x -q -f . cd ../.. rm .git -rf chmod a+rX . -R +sed -i 's#__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__#'$VERSION'#' docker_config/master_template.yml cd $CWD diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index bb2f99de..0a2306db 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,8 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env GPU_DEVICE=$GPU2USE \ --env MODEL_NAME=MST \ - --env CONFIG=unilateral" + --env CONFIG=unilateral \ + --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" # Execution modes if [[ ! -z "$DUMMY_TRAINING" ]]; then From d0a893c0306e6884bbe38ec439d5d313625336e1 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 13:04:09 +0200 Subject: [PATCH 074/109] try ResNet 10 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 3 ++- docker_config/Dockerfile_ODELIA | 2 ++ docker_config/master_template.yml | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 42ac6ebb..e5da6b6c 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -82,11 +82,12 @@ path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" args { model { - path = "models.mst.MST" + path = "models.resnet.ResNet" args { n_input_channels = 1 num_classes = 3 spatial_dims = 3 + resnet_variant = 10 } } } diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 47d05edf..acef6840 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -72,3 +72,5 @@ RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image COPY ./torch_home_cache /torch_home +RUN mkdir /huggingface_home +RUN chmod a+rwx /huggingface_home diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 0a2306db..99f06c65 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -694,8 +694,9 @@ docker_cln_sh: | --env DATA_DIR=/data \ --env SCRATCH_DIR=/scratch \ --env TORCH_HOME=/torch_home \ + --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=MST \ + --env MODEL_NAME=ResNet10 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 0ed0b3a4946d7a0a9b50241a88d041d93d76148d Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 13:24:27 +0200 Subject: [PATCH 075/109] try ResNet 18 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- .../ODELIA_ternary_classification/app/custom/models/resnet.py | 2 +- docker_config/master_template.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index e5da6b6c..c20582aa 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 10 + resnet_variant = 18 } } } diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py index dfc21a3c..d4c74a79 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py @@ -21,7 +21,7 @@ def __init__(self, n_input_channels: int, num_classes: int, spatial_dims: int, r raise ValueError(f"Unsupported ResNet model number: {resnet_variant}") self.model = Model(n_input_channels=n_input_channels, spatial_dims=spatial_dims, num_classes=num_classes, - feed_forward=False, bias_downsample=False, pretrained=True) + feed_forward=False, shortcut_type='A', bias_downsample=True, pretrained=True) self.model.fc = nn.Linear(512, num_classes) # TODO can we get the number of channels from the ResNet rather than using a hard-coded value only confirmed to work with ResNet 10 and 18? diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 99f06c65..c7a46d17 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet10 \ + --env MODEL_NAME=ResNet18 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 7c248a37158779cd91e7e4c5c54b0622881dd0ab Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 15:45:51 +0200 Subject: [PATCH 076/109] try ResNet 34 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index c20582aa..28a9789a 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 18 + resnet_variant = 34 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index c7a46d17..275442cb 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet18 \ + --env MODEL_NAME=ResNet34 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 1524a6af8e668c812b0a0d37bf58b45766f64a84 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 15:47:45 +0200 Subject: [PATCH 077/109] try ResNet 50 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- .../ODELIA_ternary_classification/app/custom/models/resnet.py | 4 ++-- docker_config/master_template.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 28a9789a..a93bce2f 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 34 + resnet_variant = 50 } } } diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py index d4c74a79..bfc35c86 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py @@ -21,8 +21,8 @@ def __init__(self, n_input_channels: int, num_classes: int, spatial_dims: int, r raise ValueError(f"Unsupported ResNet model number: {resnet_variant}") self.model = Model(n_input_channels=n_input_channels, spatial_dims=spatial_dims, num_classes=num_classes, - feed_forward=False, shortcut_type='A', bias_downsample=True, pretrained=True) - self.model.fc = nn.Linear(512, + feed_forward=False, shortcut_type='B', bias_downsample=False, pretrained=True) + self.model.fc = nn.Linear(2048, num_classes) # TODO can we get the number of channels from the ResNet rather than using a hard-coded value only confirmed to work with ResNet 10 and 18? def forward(self, x): diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 275442cb..e0bfffab 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet34 \ + --env MODEL_NAME=ResNet50 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From dd83ca1c86826a0c0979a784906307e8f59c28a2 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:02:44 +0200 Subject: [PATCH 078/109] try ResNet 101 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index a93bce2f..fccf5379 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 50 + resnet_variant = 101 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index e0bfffab..fba835dd 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet50 \ + --env MODEL_NAME=ResNet101 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 22bbb7243c3726fd2eb5004db9cd561a2b5e5982 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:07:02 +0200 Subject: [PATCH 079/109] try ResNet 152 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index fccf5379..875ff4b7 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 101 + resnet_variant = 152 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index fba835dd..2c68af0c 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet101 \ + --env MODEL_NAME=ResNet152 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 0c6a27d98ff74e1227404b1f87745c00642911e0 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:17:43 +0200 Subject: [PATCH 080/109] set parameters for pre-trained ResNet depending on variant rather than hard-code them --- .../app/custom/models/resnet.py | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py index bfc35c86..49503b26 100644 --- a/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py +++ b/application/jobs/ODELIA_ternary_classification/app/custom/models/resnet.py @@ -20,10 +20,37 @@ def __init__(self, n_input_channels: int, num_classes: int, spatial_dims: int, r if Model is None: raise ValueError(f"Unsupported ResNet model number: {resnet_variant}") + shortcut_type = { + 10: 'B', + 18: 'A', + 34: 'A', + 50: 'B', + 101: 'B', + 152: 'B', + }.get(resnet_variant) + + bias_downsample = { + 10: False, + 18: True, + 34: True, + 50: False, + 101: False, + 152: False, + }.get(resnet_variant) + + num_channels = { + 10: 512, + 18: 512, + 34: 512, + 50: 2048, + 101: 2048, + 152: 2048, + }.get(resnet_variant) + self.model = Model(n_input_channels=n_input_channels, spatial_dims=spatial_dims, num_classes=num_classes, - feed_forward=False, shortcut_type='B', bias_downsample=False, pretrained=True) - self.model.fc = nn.Linear(2048, - num_classes) # TODO can we get the number of channels from the ResNet rather than using a hard-coded value only confirmed to work with ResNet 10 and 18? + feed_forward=False, shortcut_type=shortcut_type, bias_downsample=bias_downsample, pretrained=True) + self.model.fc = nn.Linear(num_channels, + num_classes) def forward(self, x): return self.model(x) From 97d609cb49632013fa3609253aca11d051e3a31f Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:27:18 +0200 Subject: [PATCH 081/109] try ResNet 10 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 875ff4b7..e5da6b6c 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 152 + resnet_variant = 10 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 2c68af0c..99f06c65 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet152 \ + --env MODEL_NAME=ResNet10 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From e5d3baa909e81150547b9247342b7ab3a84aba65 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:27:32 +0200 Subject: [PATCH 082/109] try ResNet 18 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index e5da6b6c..c20582aa 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 10 + resnet_variant = 18 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 99f06c65..c7a46d17 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet10 \ + --env MODEL_NAME=ResNet18 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 1b111e7e9e63001e44f12a7a7b09e7b491eb446e Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:27:44 +0200 Subject: [PATCH 083/109] try ResNet 34 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index c20582aa..28a9789a 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 18 + resnet_variant = 34 } } } From daa6e0f7ae4088c73311a88d48e0f4b8298233d1 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:27:56 +0200 Subject: [PATCH 084/109] try ResNet 50 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 28a9789a..a93bce2f 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 34 + resnet_variant = 50 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index c7a46d17..e0bfffab 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet18 \ + --env MODEL_NAME=ResNet50 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 92e1ba959f51524773e1097345948bb15f7a7c63 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:28:08 +0200 Subject: [PATCH 085/109] try ResNet 101 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index a93bce2f..fccf5379 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 50 + resnet_variant = 101 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index e0bfffab..fba835dd 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet50 \ + --env MODEL_NAME=ResNet101 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From ecf04adf6f1ab31a774a2995e729061a313b9a5f Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:28:21 +0200 Subject: [PATCH 086/109] try ResNet 152 (pretrained; downloaded from within container) --- .../app/config/config_fed_client.conf | 2 +- docker_config/master_template.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index fccf5379..875ff4b7 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -87,7 +87,7 @@ n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 101 + resnet_variant = 152 } } } diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index fba835dd..2c68af0c 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -696,7 +696,7 @@ docker_cln_sh: | --env TORCH_HOME=/torch_home \ --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet101 \ + --env MODEL_NAME=ResNet152 \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From b2b5b56e66865acb46301427ccc58f346eee7aba Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 16:40:47 +0200 Subject: [PATCH 087/109] omit potentially large workspace directory from copy (from which it will be cleaned up anyway) --- buildDockerImageAndStartupKits.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index 51b07f2c..dd457e5b 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -33,7 +33,7 @@ DOCKER_IMAGE=jefftud/odelia:$VERSION CWD=`pwd` CLEAN_SOURCE_DIR=`mktemp -d` mkdir $CLEAN_SOURCE_DIR/MediSwarm -cp -r . $CLEAN_SOURCE_DIR/MediSwarm/ +rsync -ax workspace . $CLEAN_SOURCE_DIR/MediSwarm/ cd $CLEAN_SOURCE_DIR/MediSwarm git clean -x -q -f . cd docker_config/NVFlare From 147173c491701fcdc2c4532dbf171e375b1f2e9a Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 22 Jul 2025 17:10:56 +0200 Subject: [PATCH 088/109] fixed rsync option to exclude directory --- buildDockerImageAndStartupKits.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildDockerImageAndStartupKits.sh b/buildDockerImageAndStartupKits.sh index dd457e5b..1767cfef 100755 --- a/buildDockerImageAndStartupKits.sh +++ b/buildDockerImageAndStartupKits.sh @@ -33,7 +33,7 @@ DOCKER_IMAGE=jefftud/odelia:$VERSION CWD=`pwd` CLEAN_SOURCE_DIR=`mktemp -d` mkdir $CLEAN_SOURCE_DIR/MediSwarm -rsync -ax workspace . $CLEAN_SOURCE_DIR/MediSwarm/ +rsync -ax --exclude workspace . $CLEAN_SOURCE_DIR/MediSwarm/ cd $CLEAN_SOURCE_DIR/MediSwarm git clean -x -q -f . cd docker_config/NVFlare From dc9518fd61571ecaa589a598a643f59a33e013aa Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 24 Jul 2025 16:19:01 +0200 Subject: [PATCH 089/109] back to MST --- .../app/config/config_fed_client.conf | 3 +-- docker_config/Dockerfile_ODELIA | 2 -- docker_config/master_template.yml | 3 +-- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf index 875ff4b7..42ac6ebb 100644 --- a/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf +++ b/application/jobs/ODELIA_ternary_classification/app/config/config_fed_client.conf @@ -82,12 +82,11 @@ path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" args { model { - path = "models.resnet.ResNet" + path = "models.mst.MST" args { n_input_channels = 1 num_classes = 3 spatial_dims = 3 - resnet_variant = 152 } } } diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index acef6840..47d05edf 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -72,5 +72,3 @@ RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image COPY ./torch_home_cache /torch_home -RUN mkdir /huggingface_home -RUN chmod a+rwx /huggingface_home diff --git a/docker_config/master_template.yml b/docker_config/master_template.yml index 2c68af0c..0a2306db 100644 --- a/docker_config/master_template.yml +++ b/docker_config/master_template.yml @@ -694,9 +694,8 @@ docker_cln_sh: | --env DATA_DIR=/data \ --env SCRATCH_DIR=/scratch \ --env TORCH_HOME=/torch_home \ - --env HF_HOME=/huggingface_home \ --env GPU_DEVICE=$GPU2USE \ - --env MODEL_NAME=ResNet152 \ + --env MODEL_NAME=MST \ --env CONFIG=unilateral \ --env MEDISWARM_VERSION=__REPLACED_BY_CURRENT_VERSION_NUMBER_WHEN_BUILDING_DOCKER_IMAGE__" From 9cc8169d320799d80f28401e3a78a20d8790d5ee Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:17:16 +0200 Subject: [PATCH 090/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 307 ++++++++++++++++++++++++++++++-- 1 file changed, 294 insertions(+), 13 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 47d05edf..964d188c 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,13 +12,106 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 +RUN apt install \ + \ + -y \ + apt=2.4.14 \ + apt-utils=2.4.14 \ + libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-144.157 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install \ + \ + -y \ + base-files=12ubuntu4.7 \ + bash=5.1-6ubuntu1.1 \ + bsdutils=1:2.37.2-4ubuntu3.4 \ + ca-certificates=20240203~22.04.1 \ + coreutils=8.32-4.1ubuntu1.2 \ + dpkg=1.21.1ubuntu2.3 \ + e2fsprogs=1.46.5-2ubuntu1.2 \ + gpgv=2.2.27-3ubuntu2.4 \ + libblkid1=2.37.2-4ubuntu3.4 \ + libc-bin=2.35-0ubuntu3.10 \ + libc-dev-bin=2.35-0ubuntu3.10 \ + libc6-dev=2.35-0ubuntu3.10 \ + libc6=2.35-0ubuntu3.10 \ + libcap2=1:2.44-1ubuntu0.22.04.2 \ + libcom-err2=1.46.5-2ubuntu1.2 \ + libext2fs2=1.46.5-2ubuntu1.2 \ + libgnutls30=3.7.3-4ubuntu1.7 \ + libgssapi-krb5-2=1.19.2-2ubuntu0.7 \ + libk5crypto3=1.19.2-2ubuntu0.7 \ + libkrb5-3=1.19.2-2ubuntu0.7 \ + libkrb5support0=1.19.2-2ubuntu0.7 \ + libmount1=2.37.2-4ubuntu3.4 \ + libpam-modules-bin=1.4.0-11ubuntu2.6 \ + libpam-modules=1.4.0-11ubuntu2.6 \ + libpam-runtime=1.4.0-11ubuntu2.6 \ + libpam0g=1.4.0-11ubuntu2.6 \ + libseccomp2=2.5.3-2ubuntu3~22.04.1 \ + libsmartcols1=2.37.2-4ubuntu3.4 \ + libss2=1.46.5-2ubuntu1.2 \ + libssl3=3.0.2-0ubuntu1.19 \ + libsystemd0=249.11-0ubuntu3.16 \ + libtasn1-6=4.18.0-4ubuntu0.1 \ + libudev1=249.11-0ubuntu3.16 \ + libuuid1=2.37.2-4ubuntu3.4 \ + linux-libc-dev=5.15.0-144.157 \ + logsave=1.46.5-2ubuntu1.2 \ + mount=2.37.2-4ubuntu3.4 \ + openssl=3.0.2-0ubuntu1.19 \ + util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install \ + \ + -y \ + apt-transport-https=2.4.14 \ + curl=7.81.0-1ubuntu1.20 \ + dirmngr=2.2.27-3ubuntu2.4 \ + distro-info-data=0.52ubuntu0.9 \ + gnupg-l10n=2.2.27-3ubuntu2.4 \ + gnupg-utils=2.2.27-3ubuntu2.4 \ + gnupg=2.2.27-3ubuntu2.4 \ + gpg-agent=2.2.27-3ubuntu2.4 \ + gpg-wks-client=2.2.27-3ubuntu2.4 \ + gpg-wks-server=2.2.27-3ubuntu2.4 \ + gpg=2.2.27-3ubuntu2.4 \ + gpgconf=2.2.27-3ubuntu2.4 \ + gpgsm=2.2.27-3ubuntu2.4 \ + libassuan0=2.5.5-1build1 \ + libbrotli1=1.0.9-2build6 \ + libcurl4=7.81.0-1ubuntu1.20 \ + libexpat1=2.4.7-1ubuntu0.6 \ + libksba8=1.6.0-2ubuntu0.2 \ + libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 \ + libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 \ + libmpdec3=2.5.1-2build2 \ + libnghttp2-14=1.43.0-1ubuntu0.2 \ + libnpth0=1.6-3build2 \ + libpsl5=0.21.0-1.2build2 \ + libpython3-stdlib=3.10.6-1~22.04.1 \ + libpython3.10-minimal=3.10.12-1~22.04.10 \ + libpython3.10-stdlib=3.10.12-1~22.04.10 \ + libreadline8=8.1.2-1 \ + librtmp1=2.4+20151223.gitfa8646d.1-2build4 \ + libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 \ + libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 \ + libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 \ + libsqlite3-0=3.37.2-2ubuntu0.4 \ + libssh-4=0.9.6-2ubuntu0.22.04.4 \ + lsb-release=11.1.0ubuntu4 \ + media-types=7.0.0 \ + pinentry-curses=1.1.1-1build2 \ + publicsuffix=20211207.1025-1 \ + python3-minimal=3.10.6-1~22.04.1 \ + python3.10-minimal=3.10.12-1~22.04.10 \ + python3.10=3.10.12-1~22.04.10 \ + python3=3.10.6-1~22.04.1 \ + readline-common=8.1.2-1 \ + unzip=6.0-26ubuntu3.2 \ + zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ @@ -27,7 +120,82 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.2-1~ubuntu.22.04~jammy docker-ce=5:28.3.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.15 git=1:2.34.1-1ubuntu1.15 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install \ + \ + -y \ + apparmor=3.0.4-2ubuntu2.4 \ + containerd.io=1.7.27-1 \ + dbus-user-session=1.12.20-2ubuntu4.1 \ + dbus=1.12.20-2ubuntu4.1 \ + dmsetup=2:1.02.175-2.1ubuntu5 \ + docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy \ + docker-ce-cli=5:28.3.2-1~ubuntu.22.04~jammy \ + docker-ce-rootless-extras=5:28.3.2-1~ubuntu.22.04~jammy \ + docker-ce=5:28.3.2-1~ubuntu.22.04~jammy \ + docker-compose-plugin=2.38.2-1~ubuntu.22.04~jammy \ + gir1.2-glib-2.0=1.72.0-1 \ + git-man=1:2.34.1-1ubuntu1.15 \ + git=1:2.34.1-1ubuntu1.15 \ + iptables=1.8.7-1ubuntu5.2 \ + less=590-1ubuntu0.22.04.3 \ + libapparmor1=3.0.4-2ubuntu2.4 \ + libargon2-1=0~20171227-0.3 \ + libbsd0=0.11.5-1 \ + libcbor0.8=0.8.0-2ubuntu1 \ + libcryptsetup12=2:2.4.3-1ubuntu1.3 \ + libcurl3-gnutls=7.81.0-1ubuntu1.20 \ + libdbus-1-3=1.12.20-2ubuntu4.1 \ + libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 \ + libedit2=3.1-20210910-1build1 \ + liberror-perl=0.17029-1 \ + libfido2-1=1.10.0-1 \ + libgdbm-compat4=1.23-1 \ + libgdbm6=1.23-1 \ + libgirepository-1.0-1=1.72.0-1 \ + libglib2.0-0=2.72.4-0ubuntu2.5 \ + libglib2.0-data=2.72.4-0ubuntu2.5 \ + libicu70=70.1-2 \ + libip4tc2=1.8.7-1ubuntu5.2 \ + libip6tc2=1.8.7-1ubuntu5.2 \ + libjson-c5=0.15-3~ubuntu1.22.04.2 \ + libkmod2=29-1ubuntu1 \ + libltdl7=2.4.6-15build2 \ + libmd0=1.0.4-1build1 \ + libmnl0=1.0.4-3build2 \ + libnetfilter-conntrack3=1.0.9-1 \ + libnfnetlink0=1.0.1-3build3 \ + libnftnl11=1.2.1-1build1 \ + libnss-systemd=249.11-0ubuntu3.16 \ + libpam-systemd=249.11-0ubuntu3.16 \ + libperl5.34=5.34.0-3ubuntu1.4 \ + libslirp0=4.6.1-1build1 \ + libx11-6=2:1.7.5-1ubuntu0.3 \ + libx11-data=2:1.7.5-1ubuntu0.3 \ + libxau6=1:1.0.9-1build5 \ + libxcb1=1.14-3ubuntu3 \ + libxdmcp6=1:1.1.3-0ubuntu5 \ + libxext6=2:1.3.4-1build1 \ + libxml2=2.9.13+dfsg-1ubuntu0.7 \ + libxmuu1=2:1.1.3-3 \ + libxtables12=1.8.7-1ubuntu5.2 \ + netbase=6.3 \ + networkd-dispatcher=2.1-2ubuntu0.22.04.2 \ + openssh-client=1:8.9p1-3ubuntu0.13 \ + patch=2.7.6-7build2 \ + perl-base=5.34.0-3ubuntu1.4 \ + perl-modules-5.34=5.34.0-3ubuntu1.4 \ + perl=5.34.0-3ubuntu1.4 \ + pigz=2.6-1 \ + python3-dbus=1.2.18-3build1 \ + python3-gi=3.42.1-0ubuntu1 \ + shared-mime-info=2.1-2 \ + slirp4netns=1.0.1-2 \ + systemd-sysv=249.11-0ubuntu3.16 \ + systemd-timesyncd=249.11-0ubuntu3.16 \ + systemd=249.11-0ubuntu3.16 \ + xauth=1:1.1-1build2 \ + xdg-user-dirs=0.17-2ubuntu4 \ + xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* @@ -36,18 +204,129 @@ RUN rm -rf /var/lib/apt/lists/* RUN python3 -m pip uninstall -y conda conda-package-handling conda_index # Install specific versions of pip and setuptools -RUN python3 -m pip install -U pip==25.1.1 setuptools==80.8.0 +RUN python3 -m pip install \ + -U \ + pip==25.1.1 \ + setuptools==80.8.0 # Install dependencies of NVFlare at fixed versions -RUN python3 -m pip install --upgrade psutil==7.0.0 -RUN python3 -m pip install Flask==3.0.2 Flask-JWT-Extended==4.6.0 Flask-SQLAlchemy==3.1.1 PyJWT==2.10.1 SQLAlchemy==2.0.16 Werkzeug==3.0.1 blinker==1.9.0 docker==7.1.0 greenlet==3.2.2 grpcio==1.62.1 gunicorn==23.0.0 itsdangerous==2.2.0 msgpack==1.1.0 protobuf==4.24.4 pyhocon==0.3.61 pyparsing==3.2.3 websockets==15.0.1 +RUN python3 -m pip install \ + --upgrade \ + psutil==7.0.0 +RUN python3 -m pip install \ + Flask==3.0.2 \ + Flask-JWT-Extended==4.6.0 \ + Flask-SQLAlchemy==3.1.1 \ + PyJWT==2.10.1 \ + SQLAlchemy==2.0.16 \ + Werkzeug==3.0.1 \ + blinker==1.9.0 \ + docker==7.1.0 \ + greenlet==3.2.2 \ + grpcio==1.62.1 \ + gunicorn==23.0.0 \ + itsdangerous==2.2.0 \ + msgpack==1.1.0 \ + protobuf==4.24.4 \ + pyhocon==0.3.61 \ + pyparsing==3.2.3 \ + websockets==15.0.1 # Install additional Python packages for application code at defined versions -RUN python3 -m pip install Deprecated==1.2.18 SimpleITK==2.5.0 absl-py==2.2.2 aiohttp==3.11.18 aiosignal==1.3.2 async-timeout==5.0.1 cachetools==5.5.2 contourpy==1.3.2 cycler==0.12.1 et-xmlfile==2.0.0 fonttools==4.58.0 frozenlist==1.6.0 google-auth-oauthlib==1.2.2 google-auth==2.40.2 huggingface_hub==0.29.3 datasets==3.4.1 coral_pytorch==1.4.0 humanize==4.12.3 joblib==1.5.1 kiwisolver==1.4.8 lightning-utilities==0.14.3 markdown-it-py==3.0.0 markdown==3.8 matplotlib==3.9.2 mdurl==0.1.2 monai==1.4.0 multidict==6.4.4 nibabel==5.3.2 oauthlib==3.2.2 openpyxl==3.1.5 pandas==2.2.3 numpy==1.26.4 pyasn1-modules==0.4.2 pyasn1==0.6.1 pydicom==3.0.1 python-dateutil==2.9.0.post0 x-transformers==2.3.5 pytorch-lightning==2.4.0 requests==2.32.3 requests-oauthlib==2.0.0 rich==14.0.0 rsa==4.9.1 safetensors==0.5.3 scikit-learn==1.5.2 scipy==1.15.3 seaborn==0.13.2 wandb==0.18.6 einops==0.8.0 shellingham==1.5.4 tensorboard-data-server==0.7.2 tensorboard-plugin-wit==1.8.1 tensorboard==2.19.0 threadpoolctl==3.6.0 timm==1.0.15 torchio==0.20.1 torchmetrics==1.7.1 torchvision==0.17.2 torchaudio==2.2.2 tqdm==4.67.0 typer==0.15.4 tzdata==2025.2 wrapt==1.17.2 yarl==1.20.0 aiohappyeyeballs==2.6.1 annotated-types==0.7.0 dill==0.3.8 docker-pycreds==0.4.0 einx==0.3.0 frozendict==2.4.6 gitdb==4.0.12 gitpython==3.1.44 hf-xet==1.1.2 importlib-resources==6.5.2 loguru==0.7.3 multiprocess==0.70.16 propcache==0.3.1 pyarrow==20.0.0 pydantic==2.11.5 pydantic-core==2.33.2 sentry-sdk==2.29.1 setproctitle==1.3.6 smmap==5.0.2 typing-extensions==4.13.2 typing-inspection==0.4.1 xxhash==3.5.0 +RUN python3 -m pip install \ + Deprecated==1.2.18 \ + SimpleITK==2.5.0 \ + absl-py==2.2.2 \ + aiohttp==3.11.18 \ + aiosignal==1.3.2 \ + async-timeout==5.0.1 \ + cachetools==5.5.2 \ + contourpy==1.3.2 \ + cycler==0.12.1 \ + et-xmlfile==2.0.0 \ + fonttools==4.58.0 \ + frozenlist==1.6.0 \ + google-auth-oauthlib==1.2.2 \ + google-auth==2.40.2 \ + huggingface_hub==0.29.3 \ + datasets==3.4.1 \ + coral_pytorch==1.4.0 \ + humanize==4.12.3 \ + joblib==1.5.1 \ + kiwisolver==1.4.8 \ + lightning-utilities==0.14.3 \ + markdown-it-py==3.0.0 \ + markdown==3.8 \ + matplotlib==3.9.2 \ + mdurl==0.1.2 \ + monai==1.4.0 \ + multidict==6.4.4 \ + nibabel==5.3.2 \ + oauthlib==3.2.2 \ + openpyxl==3.1.5 \ + pandas==2.2.3 \ + numpy==1.26.4 \ + pyasn1-modules==0.4.2 \ + pyasn1==0.6.1 \ + pydicom==3.0.1 \ + python-dateutil==2.9.0.post0 \ + x-transformers==2.3.5 \ + pytorch-lightning==2.4.0 \ + requests==2.32.3 \ + requests-oauthlib==2.0.0 \ + rich==14.0.0 \ + rsa==4.9.1 \ + safetensors==0.5.3 \ + scikit-learn==1.5.2 \ + scipy==1.15.3 \ + seaborn==0.13.2 \ + wandb==0.18.6 \ + einops==0.8.0 \ + shellingham==1.5.4 \ + tensorboard-data-server==0.7.2 \ + tensorboard-plugin-wit==1.8.1 \ + tensorboard==2.19.0 \ + threadpoolctl==3.6.0 \ + timm==1.0.15 \ + torchio==0.20.1 \ + torchmetrics==1.7.1 \ + torchvision==0.17.2 \ + torchaudio==2.2.2 \ + tqdm==4.67.0 \ + typer==0.15.4 \ + tzdata==2025.2 \ + wrapt==1.17.2 \ + yarl==1.20.0 \ + aiohappyeyeballs==2.6.1 \ + annotated-types==0.7.0 \ + dill==0.3.8 \ + docker-pycreds==0.4.0 \ + einx==0.3.0 \ + frozendict==2.4.6 \ + gitdb==4.0.12 \ + gitpython==3.1.44 \ + hf-xet==1.1.2 \ + importlib-resources==6.5.2 \ + loguru==0.7.3 \ + multiprocess==0.70.16 \ + propcache==0.3.1 \ + pyarrow==20.0.0 \ + pydantic==2.11.5 \ + pydantic-core==2.33.2 \ + sentry-sdk==2.29.1 \ + setproctitle==1.3.6 \ + smmap==5.0.2 \ + typing-extensions==4.13.2 \ + typing-inspection==0.4.1 \ + xxhash==3.5.0 # Install packages needed for testing and for listing licenses of installed packages -RUN python3 -m pip install coverage==7.8.2 mock==5.2.0 -RUN python3 -m pip install pip-licenses==5.0.0 prettytable==3.16.0 +RUN python3 -m pip install \ + coverage==7.8.2 \ + mock==5.2.0 +RUN python3 -m pip install \ + pip-licenses==5.0.0 \ + prettytable==3.16.0 # Clean up pip cache RUN python3 -m pip cache purge @@ -57,12 +336,14 @@ WORKDIR /workspace/ COPY ./MediSwarm/docker_config/NVFlare /workspace/nvflare ## use startup kit template in the dashboard COPY ./MediSwarm/docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ -RUN python3 -m pip install /workspace/nvflare +RUN python3 -m pip install \ + /workspace/nvflare RUN rm -rf /workspace/nvflare # Install the ODELIA controller package from local source COPY ./MediSwarm/controller /workspace/controller -RUN python3 -m pip install /workspace/controller +RUN python3 -m pip install \ + /workspace/controller RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm @@ -71,4 +352,4 @@ RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image -COPY ./torch_home_cache /torch_home +COPY ./torch_home_cache /torch_home \ No newline at end of file From 1ee9290ca98091437dc9b903bce161a6ab52c984 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:17:26 +0200 Subject: [PATCH 091/109] chore: format APT install lines for better readability --- .../dev_utils/dockerfile_update_addAptVersionNumbers.py | 7 ++++++- scripts/dev_utils/dockerfile_update_removeVersionApt.py | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py index cca37ddd..6afc4bfc 100755 --- a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py +++ b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py @@ -35,6 +35,11 @@ def add_apt_versions(dockerfile: str, versions: dict) -> str: for package, version in versions.items(): outline = outline.replace(f' {package} ', f' {package}={version} ') outline = re.sub(f' {package}$', f' {package}={version}', outline) + parts = outline.split() + if len(parts) > 3: + header = " ".join(parts[:3]) + pkgs = parts[3:] + outline = header + " \\\n " + " \\\n ".join(pkgs) outlines.append(outline) else: outlines.append(line) @@ -55,4 +60,4 @@ def report_non_fixed_versions(dockerfile: str, versions: dict) -> None: versions = parse_apt_versions(installlog) report_non_fixed_versions(dockerfile, versions) dockerfile = add_apt_versions(dockerfile, versions) - save_file(dockerfile, sys.argv[1]) + save_file(dockerfile, sys.argv[1]) \ No newline at end of file diff --git a/scripts/dev_utils/dockerfile_update_removeVersionApt.py b/scripts/dev_utils/dockerfile_update_removeVersionApt.py index 15055b7f..a067578d 100755 --- a/scripts/dev_utils/dockerfile_update_removeVersionApt.py +++ b/scripts/dev_utils/dockerfile_update_removeVersionApt.py @@ -17,6 +17,11 @@ def remove_apt_versions(dockerfile: str) -> str: for line in dockerfile.splitlines(): if line.startswith('RUN apt install'): out_line = re.sub('=[^ ]*', '', line) + parts = out_line.split() + if len(parts) > 3: + header = " ".join(parts[:3]) + pkgs = parts[3:] + out_line = header + " \\\n " + " \\\n ".join(pkgs) output.append(out_line) else: output.append(line) @@ -26,4 +31,4 @@ def remove_apt_versions(dockerfile: str) -> str: if __name__ == '__main__': dockerfile = load_file(sys.argv[1]) dockerfile = remove_apt_versions(dockerfile) - save_file(dockerfile, sys.argv[1]) + save_file(dockerfile, sys.argv[1]) \ No newline at end of file From 2e3c5c4be609d4c2b6e14b576bb8cd9d867e1bdc Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:17:30 +0200 Subject: [PATCH 092/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 964d188c..aacdc9c7 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -13,6 +13,7 @@ ENV PYTHON_VERSION=3.10.14 RUN apt update RUN apt install \ + \ \ -y \ apt=2.4.14 \ @@ -21,6 +22,7 @@ RUN apt install \ # Update versions of installed packages RUN apt install \ + \ \ -y \ base-files=12ubuntu4.7 \ @@ -65,6 +67,7 @@ RUN apt install \ # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install \ + \ \ -y \ apt-transport-https=2.4.14 \ @@ -121,6 +124,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions RUN apt install \ + \ \ -y \ apparmor=3.0.4-2ubuntu2.4 \ From b556bdd0d7b78f91573187028f3d291807334a6d Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:23:05 +0200 Subject: [PATCH 093/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index aacdc9c7..5ce738e2 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -13,6 +13,7 @@ ENV PYTHON_VERSION=3.10.14 RUN apt update RUN apt install \ + \ \ \ -y \ @@ -22,6 +23,7 @@ RUN apt install \ # Update versions of installed packages RUN apt install \ + \ \ \ -y \ @@ -67,6 +69,7 @@ RUN apt install \ # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install \ + \ \ \ -y \ @@ -124,6 +127,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions RUN apt install \ + \ \ \ -y \ From a5fb3112a1ec014486f2eb1ea3501e9c0568acd5 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:23:09 +0200 Subject: [PATCH 094/109] refactor: update parse_apt_versions to return a dictionary of package versions --- .../dockerfile_update_addAptVersionNumbers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py index 6afc4bfc..4ef715d3 100755 --- a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py +++ b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py @@ -12,14 +12,14 @@ def save_file(contents: str, filename: str) -> None: outfile.write(contents) -def parse_apt_versions(installlog: str) -> str: +def parse_apt_versions(installlog: str) -> dict: versions = {} for line in installlog.splitlines(): - if re.match('.*Get:[0-9]* http.*', line): - blocks = line.split(' ') - if len(blocks) > 9: - package = blocks[6] - version = blocks[8] + if "Get:" in line: + match = re.search(r' ([a-zA-Z0-9\-\+\.]+)[/\s]([^\s]+) ', line) + if match: + package = match.group(1) + version = match.group(2) if package in versions and versions[package] != version: print(f'Conflicting versions of {package} found: {versions[package]} and {version} found, using the latter.') versions[package] = version From 1710d4076ae505a3d9e60a79dac876bcebbaa07f Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:23:11 +0200 Subject: [PATCH 095/109] WIP: remove apt versions for rebuild --- docker_config/Dockerfile_ODELIA | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 5ce738e2..118bec0f 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -16,6 +16,7 @@ RUN apt install \ \ \ \ + \ -y \ apt=2.4.14 \ apt-utils=2.4.14 \ @@ -26,6 +27,7 @@ RUN apt install \ \ \ \ + \ -y \ base-files=12ubuntu4.7 \ bash=5.1-6ubuntu1.1 \ @@ -72,6 +74,7 @@ RUN apt install \ \ \ \ + \ -y \ apt-transport-https=2.4.14 \ curl=7.81.0-1ubuntu1.20 \ @@ -130,6 +133,7 @@ RUN apt install \ \ \ \ + \ -y \ apparmor=3.0.4-2ubuntu2.4 \ containerd.io=1.7.27-1 \ From 41ac3779dae5aede05988fb1289bc0eba6735aca Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Mon, 28 Jul 2025 15:28:42 +0200 Subject: [PATCH 096/109] refactor: optimize regex pattern for parsing APT versions --- .../dockerfile_update_addAptVersionNumbers.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py index 4ef715d3..c174dfc3 100755 --- a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py +++ b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py @@ -14,18 +14,19 @@ def save_file(contents: str, filename: str) -> None: def parse_apt_versions(installlog: str) -> dict: versions = {} + pattern = re.compile(r'Get:.*? ([a-z0-9\-\+\.]+)(?:/[^ ]*)? ([0-9a-zA-Z\:\~\.\+\-]+) ') for line in installlog.splitlines(): - if "Get:" in line: - match = re.search(r' ([a-zA-Z0-9\-\+\.]+)[/\s]([^\s]+) ', line) - if match: - package = match.group(1) - version = match.group(2) - if package in versions and versions[package] != version: - print(f'Conflicting versions of {package} found: {versions[package]} and {version} found, using the latter.') - versions[package] = version + match = pattern.search(line) + if match: + package = match.group(1) + version = match.group(2) + if package in versions and versions[package] != version: + print(f'Conflicting versions of {package} found: {versions[package]} and {version} found, using the latter.') + versions[package] = version return versions + def add_apt_versions(dockerfile: str, versions: dict) -> str: dockerfile = dockerfile.replace('RUN apt install', 'RUN_apt_install') outlines = [] From 4a28fe72a1adabd7260dacd78e86bb8e2abd3e10 Mon Sep 17 00:00:00 2001 From: Ultimate-Storm Date: Tue, 29 Jul 2025 06:27:57 +0200 Subject: [PATCH 097/109] chore: update apt versions in Dockerfile_ODELIA --- docker_config/Dockerfile_ODELIA | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 47d05edf..988928dd 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,10 +15,10 @@ RUN apt update RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-144.157 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.4 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 +RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.5 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 # Prepare Docker installation RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \ From 25b662b905d4572a65e5966b9bff00bf475af245 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Tue, 29 Jul 2025 11:21:32 +0200 Subject: [PATCH 098/109] restored lost apt package version --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 988928dd..577fd58d 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -15,7 +15,7 @@ RUN apt update RUN apt install -y apt=2.4.14 apt-utils=2.4.14 libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 +RUN apt install -y base-files=12ubuntu4.7 bash=5.1-6ubuntu1.1 bsdutils=1:2.37.2-4ubuntu3.4 ca-certificates=20240203~22.04.1 coreutils=8.32-4.1ubuntu1.2 dpkg=1.21.1ubuntu2.3 e2fsprogs=1.46.5-2ubuntu1.2 gpgv=2.2.27-3ubuntu2.4 libblkid1=2.37.2-4ubuntu3.4 libc-bin=2.35-0ubuntu3.10 libc-dev-bin=2.35-0ubuntu3.10 libc6-dev=2.35-0ubuntu3.10 libc6=2.35-0ubuntu3.10 libcap2=1:2.44-1ubuntu0.22.04.2 libcom-err2=1.46.5-2ubuntu1.2 libext2fs2=1.46.5-2ubuntu1.2 libgnutls30=3.7.3-4ubuntu1.7 libgssapi-krb5-2=1.19.2-2ubuntu0.7 libk5crypto3=1.19.2-2ubuntu0.7 libkrb5-3=1.19.2-2ubuntu0.7 libkrb5support0=1.19.2-2ubuntu0.7 libmount1=2.37.2-4ubuntu3.4 libpam-modules-bin=1.4.0-11ubuntu2.6 libpam-modules=1.4.0-11ubuntu2.6 libpam-runtime=1.4.0-11ubuntu2.6 libpam0g=1.4.0-11ubuntu2.6 libseccomp2=2.5.3-2ubuntu3~22.04.1 libsmartcols1=2.37.2-4ubuntu3.4 libss2=1.46.5-2ubuntu1.2 libssl3=3.0.2-0ubuntu1.19 libsystemd0=249.11-0ubuntu3.16 libtasn1-6=4.18.0-4ubuntu0.1 libudev1=249.11-0ubuntu3.16 libuuid1=2.37.2-4ubuntu3.4 linux-libc-dev=5.15.0-151.161 logsave=1.46.5-2ubuntu1.2 mount=2.37.2-4ubuntu3.4 openssl=3.0.2-0ubuntu1.19 util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install -y apt-transport-https=2.4.14 curl=7.81.0-1ubuntu1.20 dirmngr=2.2.27-3ubuntu2.4 distro-info-data=0.52ubuntu0.9 gnupg-l10n=2.2.27-3ubuntu2.4 gnupg-utils=2.2.27-3ubuntu2.4 gnupg=2.2.27-3ubuntu2.4 gpg-agent=2.2.27-3ubuntu2.4 gpg-wks-client=2.2.27-3ubuntu2.4 gpg-wks-server=2.2.27-3ubuntu2.4 gpg=2.2.27-3ubuntu2.4 gpgconf=2.2.27-3ubuntu2.4 gpgsm=2.2.27-3ubuntu2.4 libassuan0=2.5.5-1build1 libbrotli1=1.0.9-2build6 libcurl4=7.81.0-1ubuntu1.20 libexpat1=2.4.7-1ubuntu0.6 libksba8=1.6.0-2ubuntu0.2 libldap-2.5-0=2.5.19+dfsg-0ubuntu0.22.04.1 libldap-common=2.5.19+dfsg-0ubuntu0.22.04.1 libmpdec3=2.5.1-2build2 libnghttp2-14=1.43.0-1ubuntu0.2 libnpth0=1.6-3build2 libpsl5=0.21.0-1.2build2 libpython3-stdlib=3.10.6-1~22.04.1 libpython3.10-minimal=3.10.12-1~22.04.10 libpython3.10-stdlib=3.10.12-1~22.04.10 libreadline8=8.1.2-1 librtmp1=2.4+20151223.gitfa8646d.1-2build4 libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 libsqlite3-0=3.37.2-2ubuntu0.5 libssh-4=0.9.6-2ubuntu0.22.04.4 lsb-release=11.1.0ubuntu4 media-types=7.0.0 pinentry-curses=1.1.1-1build2 publicsuffix=20211207.1025-1 python3-minimal=3.10.6-1~22.04.1 python3.10-minimal=3.10.12-1~22.04.10 python3.10=3.10.12-1~22.04.10 python3=3.10.6-1~22.04.1 readline-common=8.1.2-1 unzip=6.0-26ubuntu3.2 zip=3.0-12build2 From 9c2c0829af248cc48975c57504a5ca925ae29255 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Tue, 29 Jul 2025 17:35:43 +0200 Subject: [PATCH 099/109] A working version Signed-off-by: GitHub CI --- .../stamp/app/config/config_fed_client.conf | 10 +- application/jobs/stamp/app/custom/data.py | 9 - application/jobs/stamp/app/custom/main.py | 218 ++- .../jobs/stamp/app/custom/modeling/alibi.py | 147 ++ .../app/custom/modeling/lightning_model.py | 95 +- .../app/custom/modeling/mlp_classifier.py | 123 ++ .../jobs/stamp/app/custom/modeling/module.py | 1654 +++++++++++++++++ .../stamp/app/custom/modeling/registry.py | 34 + .../app/custom/modeling/vision_transformer.py | 3 +- 9 files changed, 2198 insertions(+), 95 deletions(-) create mode 100644 application/jobs/stamp/app/custom/modeling/alibi.py create mode 100644 application/jobs/stamp/app/custom/modeling/mlp_classifier.py create mode 100644 application/jobs/stamp/app/custom/modeling/module.py create mode 100644 application/jobs/stamp/app/custom/modeling/registry.py diff --git a/application/jobs/stamp/app/config/config_fed_client.conf b/application/jobs/stamp/app/config/config_fed_client.conf index 5565b668..826af222 100644 --- a/application/jobs/stamp/app/config/config_fed_client.conf +++ b/application/jobs/stamp/app/config/config_fed_client.conf @@ -84,11 +84,19 @@ model { path = "modeling.lightning_model.LitVisionTransformer" args { + categories = ['WT', 'MUT'] + dim_input = 1536 + category_weights =[0.8676, 0.1324] dim_model = 512 - dim_feedforward=2048 + dim_feedforward=512 n_heads=8 n_layers=2 dropout=0.25 +use_alibi=False + ground_truth_label='isMSIH' + train_patients=['TCGA-AH-6644', 'TCGA-AA-3664', 'TCGA-AZ-4614', 'TCGA-D5-5537', 'TCGA-G4-6628', 'TCGA-EI-6882', 'TCGA-AF-2689', 'TCGA-F4-6805', 'TCGA-AZ-4681', 'TCGA-AG-3882', 'TCGA-A6-6648', 'TCGA-AA-A017', 'TCGA-G4-6303', 'TCGA-EI-6508', 'TCGA-AF-6136', 'TCGA-A6-6654', 'TCGA-AD-6895', 'TCGA-CM-5860', 'TCGA-A6-4105', 'TCGA-F5-6810', 'TCGA-QG-A5YV', 'TCGA-DC-6155', 'TCGA-F5-6812', 'TCGA-QL-A97D', 'TCGA-AZ-4615', 'TCGA-CK-6751', 'TCGA-AH-6547', 'TCGA-AA-3980', 'TCGA-CL-5918', 'TCGA-DM-A282', 'TCGA-AA-3514', 'TCGA-EI-6509', 'TCGA-AF-3911', 'TCGA-CL-5917', 'TCGA-A6-2686', 'TCGA-AA-3950', 'TCGA-AA-3549', 'TCGA-AA-3973', 'TCGA-D5-6922', 'TCGA-AG-3887', 'TCGA-CA-6717', 'TCGA-CM-6171', 'TCGA-AD-6964', 'TCGA-A6-6142', 'TCGA-AD-6890', 'TCGA-A6-6138', 'TCGA-AD-A5EK', 'TCGA-D5-6929', 'TCGA-G4-6588', 'TCGA-AZ-4616', 'TCGA-AA-3561', 'TCGA-QG-A5Z2', 'TCGA-DM-A1HA', 'TCGA-CL-4957', 'TCGA-CA-5255', 'TCGA-DC-6158', 'TCGA-G4-6295', 'TCGA-AY-A69D', 'TCGA-DM-A1D6', 'TCGA-AG-3896', 'TCGA-A6-6652', 'TCGA-CM-6680', 'TCGA-AA-3821', 'TCGA-D5-5540', 'TCGA-DC-5869', 'TCGA-AG-4008', 'TCGA-CK-4952', 'TCGA-AA-3875', 'TCGA-AA-A02W', 'TCGA-EI-6514', 'TCGA-DM-A288', 'TCGA-AG-3890', 'TCGA-AA-3979', 'TCGA-NH-A5IV', 'TCGA-G4-6297', 'TCGA-D5-6927', 'TCGA-AA-3680', 'TCGA-NH-A8F8', 'TCGA-A6-5662', 'TCGA-A6-2674', 'TCGA-AG-A01N', 'TCGA-DM-A1D8', 'TCGA-AA-3837', 'TCGA-AA-A01I', 'TCGA-DM-A0XF', 'TCGA-F4-6855', 'TCGA-CA-6715', 'TCGA-AA-3975', 'TCGA-AA-3989', 'TCGA-AG-A016', 'TCGA-A6-5666', 'TCGA-CK-6746', 'TCGA-CM-4752', 'TCGA-CM-6676', 'TCGA-D5-6541', 'TCGA-A6-5667', 'TCGA-AF-2691', 'TCGA-AD-A5EJ', 'TCGA-F5-6864', 'TCGA-AG-3726', 'TCGA-A6-6649', 'TCGA-AA-A03J', 'TCGA-AF-2687', 'TCGA-AA-3858', 'TCGA-G4-6321', 'TCGA-G5-6641', 'TCGA-G4-6309', 'TCGA-D5-5538', 'TCGA-DM-A28K', 'TCGA-AG-3878', 'TCGA-AZ-6598', 'TCGA-AA-3688', 'TCGA-AG-A01W', 'TCGA-AA-3517', 'TCGA-D5-6898', 'TCGA-CM-5341', 'TCGA-CM-6167', 'TCGA-D5-6531', 'TCGA-DC-6683', 'TCGA-AF-2690', 'TCGA-CM-6161', 'TCGA-A6-5660', 'TCGA-NH-A8F7', 'TCGA-CM-6163', 'TCGA-AA-3968', 'TCGA-CM-5868', 'TCGA-AA-3976', 'TCGA-A6-A565', 'TCGA-AM-5821', 'TCGA-AD-6965', 'TCGA-AY-A71X', 'TCGA-AZ-4682', 'TCGA-AG-3892', 'TCGA-A6-2675', 'TCGA-F4-6569', 'TCGA-AG-3893', 'TCGA-CM-5862', 'TCGA-AZ-6606', 'TCGA-CM-6678', 'TCGA-D5-6931', 'TCGA-D5-6923', 'TCGA-AG-3575', 'TCGA-AA-A00Z', 'TCGA-D5-6540', 'TCGA-A6-3808', 'TCGA-CM-4743', 'TCGA-DC-4749', 'TCGA-T9-A92H', 'TCGA-A6-3810', 'TCGA-AD-6889', 'TCGA-G4-6320', 'TCGA-AA-3971', 'TCGA-AZ-4308', 'TCGA-D5-7000', 'TCGA-DM-A28A', 'TCGA-AG-A01L', 'TCGA-EI-6512', 'TCGA-A6-5661', 'TCGA-AG-3599', 'TCGA-AG-3902', 'TCGA-AD-6548', 'TCGA-AA-A022', 'TCGA-AG-3580', 'TCGA-A6-A567', 'TCGA-AA-3679', 'TCGA-CA-5256', 'TCGA-F4-6808', 'TCGA-D5-6536', 'TCGA-DM-A1D9', 'TCGA-AG-3885', 'TCGA-DM-A1D0', 'TCGA-CM-6674', 'TCGA-EI-6507', 'TCGA-F4-6459', 'TCGA-A6-6137', 'TCGA-AD-6899', 'TCGA-EI-6885', 'TCGA-WS-AB45', 'TCGA-D5-6930', 'TCGA-AU-6004', 'TCGA-AY-6196', 'TCGA-AA-A010', 'TCGA-AG-A00C', 'TCGA-4T-AA8H', 'TCGA-G4-6302', 'TCGA-AA-3966', 'TCGA-AF-2693', 'TCGA-D5-6926', 'TCGA-DM-A0XD', 'TCGA-AA-3854', 'TCGA-D5-6932', 'TCGA-EI-7004', 'TCGA-AG-3594', 'TCGA-EI-6510', 'TCGA-AA-3715', 'TCGA-AA-A01X', 'TCGA-F4-6570', 'TCGA-EI-6511', 'TCGA-CM-5861', 'TCGA-CA-5254', 'TCGA-G4-6317', 'TCGA-DM-A28M', 'TCGA-EI-6506', 'TCGA-AG-A020', 'TCGA-AG-3583', 'TCGA-G4-6294', 'TCGA-CM-6165', 'TCGA-D5-6535', 'TCGA-D5-5541', 'TCGA-DM-A1DB', 'TCGA-AG-A01J', 'TCGA-AG-4001', 'TCGA-AG-A00Y', 'TCGA-AA-3949', 'TCGA-AA-3842', 'TCGA-CA-6716', 'TCGA-CM-5348', 'TCGA-AA-3696', 'TCGA-AA-3833', 'TCGA-NH-A6GC', 'TCGA-CK-4947', 'TCGA-AA-3846', 'TCGA-A6-2677', 'TCGA-A6-2684', 'TCGA-NH-A6GA', 'TCGA-F5-6811', 'TCGA-A6-4107', 'TCGA-F5-6571', 'TCGA-CK-6747', 'TCGA-AA-3666', 'TCGA-CK-5914', 'TCGA-G4-6627', 'TCGA-DC-6681', 'TCGA-AZ-4315', 'TCGA-DT-5265', 'TCGA-CM-4751', 'TCGA-CM-6168', 'TCGA-AY-5543', 'TCGA-AZ-6607', 'TCGA-AG-3901', 'TCGA-AA-3695', 'TCGA-AD-6963', 'TCGA-CK-5913', 'TCGA-AF-2692', 'TCGA-G4-6307', 'TCGA-AA-A01T', 'TCGA-DY-A1DG', 'TCGA-AA-A01C', 'TCGA-AZ-6603', 'TCGA-AG-3727', 'TCGA-AG-3909', 'TCGA-D5-6539', 'TCGA-AZ-5407', 'TCGA-NH-A6GB', 'TCGA-AA-3851', 'TCGA-AF-5654', 'TCGA-AA-3530', 'TCGA-F5-6813', 'TCGA-AA-3845', 'TCGA-AG-3584', 'TCGA-AM-5820', 'TCGA-AA-3982', 'TCGA-D5-6924', 'TCGA-AA-3984', 'TCGA-5M-AATE', 'TCGA-AG-3894', 'TCGA-AU-3779', 'TCGA-A6-2671', 'TCGA-DC-6682', 'TCGA-A6-6653', 'TCGA-DM-A28E', 'TCGA-AZ-5403', 'TCGA-CK-4948', 'TCGA-AF-6655', 'TCGA-AA-A01P', 'TCGA-AA-A024', 'TCGA-F4-6809', 'TCGA-AA-3548', 'TCGA-QG-A5YW', 'TCGA-AA-3818', 'TCGA-EI-6513', 'TCGA-AY-4071', 'TCGA-D5-6532', 'TCGA-AZ-6600', 'TCGA-CM-6172', 'TCGA-AG-A01Y', 'TCGA-AA-3675', 'TCGA-AD-6888', 'TCGA-AH-6897', 'TCGA-AA-3852', 'TCGA-CM-4747', 'TCGA-AG-A02N', 'TCGA-AA-A02F', 'TCGA-DM-A1D7', 'TCGA-AA-3678', 'TCGA-CM-6677', 'TCGA-AA-A004', 'TCGA-D5-5539', 'TCGA-F5-6861', 'TCGA-AA-3544', 'TCGA-F4-6463', 'TCGA-AG-4015', 'TCGA-AF-3400', 'TCGA-CM-6169', 'TCGA-F4-6461', 'TCGA-AD-6901', 'TCGA-DM-A280', 'TCGA-AA-3819', 'TCGA-DM-A0X9', 'TCGA-G4-6315', 'TCGA-AA-3850', 'TCGA-CA-5797', 'TCGA-AA-3856', 'TCGA-AY-6386', 'TCGA-AG-3598', 'TCGA-CM-5863', 'TCGA-AG-3605', 'TCGA-AZ-6599', 'TCGA-CM-5344', 'TCGA-AA-3693', 'TCGA-G4-6306', 'TCGA-F4-6854', 'TCGA-A6-5656', 'TCGA-DY-A0XA', 'TCGA-A6-2685', 'TCGA-AY-6197', 'TCGA-4N-A93T', 'TCGA-NH-A50U', 'TCGA-A6-2683', 'TCGA-AA-3952', 'TCGA-DC-6160', 'TCGA-A6-3807', 'TCGA-AA-3522', 'TCGA-G4-6304', 'TCGA-AA-3526', 'TCGA-DC-4745', 'TCGA-AA-3877', 'TCGA-CM-6162', 'TCGA-AA-3692', 'TCGA-AA-3524', 'TCGA-AG-3881', 'TCGA-AA-3681', 'TCGA-CK-4951', 'TCGA-AA-3534', 'TCGA-A6-A56B', 'TCGA-DM-A1D4', 'TCGA-5M-AAT5', 'TCGA-AG-4022', 'TCGA-AA-3812', 'TCGA-D5-6529', 'TCGA-AA-3673', 'TCGA-EI-6917', 'TCGA-EI-6884', 'TCGA-AG-A011', 'TCGA-AY-A8YK', 'TCGA-EI-6881', 'TCGA-A6-2678', 'TCGA-AA-3947', 'TCGA-A6-5659', 'TCGA-AA-3994', 'TCGA-D5-6534', 'TCGA-AG-3574', 'TCGA-F5-6465', 'TCGA-CA-6719', 'TCGA-AA-3532', 'TCGA-G4-6322', 'TCGA-AF-A56K', 'TCGA-AA-3864', 'TCGA-AA-3685', 'TCGA-AA-3986'] + valid_patients=['TCGA-AG-4021', 'TCGA-AG-A02X', 'TCGA-AA-A02H', 'TCGA-F4-6807', 'TCGA-G4-6311', 'TCGA-AF-A56L', 'TCGA-CM-4748', 'TCGA-AZ-6605', 'TCGA-G4-6299', 'TCGA-AA-3667', 'TCGA-5M-AAT4', 'TCGA-5M-AAT6', 'TCGA-F4-6704', 'TCGA-AH-6544', 'TCGA-3L-AA1B', 'TCGA-CK-4950', 'TCGA-AA-3956', 'TCGA-G4-6323', 'TCGA-AA-3831', 'TCGA-AH-6903', 'TCGA-CA-5796', 'TCGA-AA-3684', 'TCGA-EI-7002', 'TCGA-F4-6460', 'TCGA-AA-3844', 'TCGA-AA-3848', 'TCGA-D5-6928', 'TCGA-A6-5665', 'TCGA-AA-A01S', 'TCGA-DM-A28H', 'TCGA-DC-6157', 'TCGA-DY-A1DD', 'TCGA-AA-3519', 'TCGA-AA-3855', 'TCGA-F4-6806', 'TCGA-AA-3520', 'TCGA-CM-6675', 'TCGA-AG-3898', 'TCGA-CK-6748', 'TCGA-G4-6298', 'TCGA-G4-6626', 'TCGA-QG-A5Z1', 'TCGA-DM-A28G', 'TCGA-A6-2681', 'TCGA-DM-A28F', 'TCGA-G4-6586', 'TCGA-CK-5916', 'TCGA-AG-A002', 'TCGA-AG-A026', 'TCGA-AA-A02E', 'TCGA-F5-6464', 'TCGA-EI-6883', 'TCGA-F4-6856', 'TCGA-AG-A015', 'TCGA-CM-5349', 'TCGA-DM-A1DA', 'TCGA-D5-6530', 'TCGA-A6-A566', 'TCGA-NH-A50V', 'TCGA-F5-6814', 'TCGA-CM-6166', 'TCGA-CI-6622', 'TCGA-AA-3977', 'TCGA-CA-6718', 'TCGA-AA-3841', 'TCGA-AA-3521', 'TCGA-NH-A50T', 'TCGA-AA-A01R', 'TCGA-AG-A008', 'TCGA-CM-4746', 'TCGA-AA-A02R', 'TCGA-DC-6154', 'TCGA-AA-3531', 'TCGA-F5-6863', 'TCGA-F4-6703', 'TCGA-AY-A54L', 'TCGA-AA-3811', 'TCGA-AA-3814', 'TCGA-CK-5912', 'TCGA-A6-3809', 'TCGA-A6-5657', 'TCGA-EF-5830', 'TCGA-CM-6164', 'TCGA-SS-A7HO', 'TCGA-CM-5864', 'TCGA-AF-4110', 'TCGA-AF-6672', 'TCGA-AA-3866', 'TCGA-AA-A01V', 'TCGA-AA-A00N', 'TCGA-D5-6538', 'TCGA-CM-6170', 'TCGA-D5-6537', 'TCGA-AA-A02Y', 'TCGA-DY-A1DC', 'TCGA-AG-3883', 'TCGA-AD-5900', 'TCGA-AA-3955', 'TCGA-AG-3581', 'TCGA-A6-5664', 'TCGA-DY-A1DF', 'TCGA-AA-A02O', 'TCGA-AA-3527', 'TCGA-F5-6702', 'TCGA-AH-6643', 'TCGA-G4-6293', 'TCGA-DC-5337', 'TCGA-AA-A03F', 'TCGA-CI-6624', 'TCGA-AG-3602', 'TCGA-AA-3538', 'TCGA-AA-A01Z', 'TCGA-AA-3558', 'TCGA-AA-3560', 'TCGA-AA-3867', 'TCGA-A6-6650', 'TCGA-CK-5915', 'TCGA-A6-6651', 'TCGA-AY-4070', 'TCGA-AF-A56N', 'TCGA-D5-6533', 'TCGA-AA-3529', 'TCGA-AG-A032', 'TCGA-QG-A5YX'] + } } } diff --git a/application/jobs/stamp/app/custom/data.py b/application/jobs/stamp/app/custom/data.py index f8d4482b..b7f51b0e 100755 --- a/application/jobs/stamp/app/custom/data.py +++ b/application/jobs/stamp/app/custom/data.py @@ -10,10 +10,8 @@ import h5py import numpy as np import pandas as pd -import stamp import torch from jaxtyping import Bool, Float -from packaging.version import Version from torch import Tensor from torch.utils.data import DataLoader, Dataset @@ -230,13 +228,6 @@ def get_coords(feature_h5: h5py.File) -> CoordsInfo: tile_size_px = TilePixels(224) coords_um = coords / 224 * 256 - if (version_str := feature_h5.attrs.get("stamp_version")) and ( - extraction_version := Version(version_str) - ) > Version(stamp.__version__): - raise RuntimeError( - f"features were extracted with a newer version of stamp, please update your stamp to at least version {extraction_version}." - ) - if not tile_size_px and "tile_size_px" in feature_h5.attrs: tile_size_px = TilePixels(int(feature_h5.attrs["tile_size_px"])) # pyright: ignore[reportArgumentType] diff --git a/application/jobs/stamp/app/custom/main.py b/application/jobs/stamp/app/custom/main.py index cf85e048..18f73398 100644 --- a/application/jobs/stamp/app/custom/main.py +++ b/application/jobs/stamp/app/custom/main.py @@ -1,7 +1,14 @@ import logging import sys -logger = logging.getLogger("stamp") +from torch import tensor + +import modeling.lightning_model +from modeling.registry import MODEL_REGISTRY + +print("Training script is using:", modeling.lightning_model.__file__) + +logger = logging.getLogger("test") logger.setLevel(logging.DEBUG) if not logger.hasHandlers(): @@ -23,6 +30,7 @@ from lightning.pytorch.accelerators.accelerator import Accelerator from lightning.pytorch.callbacks import ModelCheckpoint from lightning.pytorch.loggers import CSVLogger + from sklearn.model_selection import train_test_split from torch.utils.data.dataloader import DataLoader import logging @@ -152,13 +160,13 @@ def train_categorical_model_( def train_model_( *, output_dir: Path, - model: LitVisionTransformer, + model: lightning.LightningModule, train_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], valid_dl: DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], max_epochs: int, patience: int, accelerator: str | Accelerator, -) -> LitVisionTransformer: +) -> lightning.LightningModule: """Trains a model. Returns: @@ -177,19 +185,12 @@ def train_model_( # EarlyStopping(monitor="validation_loss", mode="min", patience=patience), model_checkpoint, ], - max_epochs=max_epochs, - # FIXME The number of accelerators is currently fixed to one for the - # following reasons: - # 1. `trainer.predict()` does not return any predictions if used with - # the default strategy no multiple GPUs - # 2. `barspoon.model.SafeMulticlassAUROC` breaks on multiple GPUs accelerator=accelerator, devices=1, gradient_clip_val=0.5, logger=CSVLogger(save_dir=output_dir), log_every_n_steps=len(train_dl), - # num_sanity_val_steps=0, ) flare_util.init() SITE_NAME = flare.get_site_name() @@ -200,12 +201,15 @@ def train_model_( while flare.is_running(): logger.info("[DEBUG] waiting to receive swarm model (10s timeout)") - input_model = flare.receive(timeout=10) - + input_model = flare.receive() + # logger.info('ModelClass state dict keys()', model.state_dict().keys()) + ''' if input_model is not None: logger.info("==== Swarm model received ====") logger.info( f"input_model.params.keys() = {list(input_model.params.keys())[:10]} ... total = {len(input_model.params)}") + # log input_model.params + logger.info(f"input_model.params.keys() = {input_model.params.keys()}") logger.info( f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") try: @@ -222,6 +226,15 @@ def train_model_( logger.info("[DEBUG] received swarm_start:", input_model) logger.info(f"Current round: {input_model.current_round}") + ''' + + logger.info(f"input_model.params.keys() = {input_model.params.keys()}") + logger.info( + f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") + # log type(model) + logger.info(f"model class: {type(model)}") + + #model = input_model.model trainer.fit( model=model, @@ -248,7 +261,7 @@ def setup_model_for_training( slide_table: Path, feature_dir: Path, ) -> tuple[ - LitVisionTransformer, + lightning.LightningModule, DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], ]: @@ -322,25 +335,182 @@ def setup_model_for_training( ) # Train the model - - model = LitVisionTransformer(dim_model=512, - dim_feedforward=2048, + model_info = MODEL_REGISTRY['vit'] + + ModelClass = model_info["model_class"] + ''' + categories = ['WT', 'MUT'] + dim_input = 1536 + category_weights =[0.8676, 0.1324] + dim_model = 512 + dim_feedforward=512 + n_heads=8 + n_layers=2 + dropout=0.25 +use_alibi=False + ground_truth_label='isMSIH' + train_patients=['TCGA-AH-6644', 'TCGA-AA-3664', 'TCGA-AZ-4614', 'TCGA-D5-5537', 'TCGA-G4-6628', 'TCGA-EI-6882', 'TCGA-AF-2689', 'TCGA-F4-6805', 'TCGA-AZ-4681', 'TCGA-AG-3882', 'TCGA-A6-6648', 'TCGA-AA-A017', 'TCGA-G4-6303', 'TCGA-EI-6508', 'TCGA-AF-6136', 'TCGA-A6-6654', 'TCGA-AD-6895', 'TCGA-CM-5860', 'TCGA-A6-4105', 'TCGA-F5-6810', 'TCGA-QG-A5YV', 'TCGA-DC-6155', 'TCGA-F5-6812', 'TCGA-QL-A97D', 'TCGA-AZ-4615', 'TCGA-CK-6751', 'TCGA-AH-6547', 'TCGA-AA-3980', 'TCGA-CL-5918', 'TCGA-DM-A282', 'TCGA-AA-3514', 'TCGA-EI-6509', 'TCGA-AF-3911', 'TCGA-CL-5917', 'TCGA-A6-2686', 'TCGA-AA-3950', 'TCGA-AA-3549', 'TCGA-AA-3973', 'TCGA-D5-6922', 'TCGA-AG-3887', 'TCGA-CA-6717', 'TCGA-CM-6171', 'TCGA-AD-6964', 'TCGA-A6-6142', 'TCGA-AD-6890', 'TCGA-A6-6138', 'TCGA-AD-A5EK', 'TCGA-D5-6929', 'TCGA-G4-6588', 'TCGA-AZ-4616', 'TCGA-AA-3561', 'TCGA-QG-A5Z2', 'TCGA-DM-A1HA', 'TCGA-CL-4957', 'TCGA-CA-5255', 'TCGA-DC-6158', 'TCGA-G4-6295', 'TCGA-AY-A69D', 'TCGA-DM-A1D6', 'TCGA-AG-3896', 'TCGA-A6-6652', 'TCGA-CM-6680', 'TCGA-AA-3821', 'TCGA-D5-5540', 'TCGA-DC-5869', 'TCGA-AG-4008', 'TCGA-CK-4952', 'TCGA-AA-3875', 'TCGA-AA-A02W', 'TCGA-EI-6514', 'TCGA-DM-A288', 'TCGA-AG-3890', 'TCGA-AA-3979', 'TCGA-NH-A5IV', 'TCGA-G4-6297', 'TCGA-D5-6927', 'TCGA-AA-3680', 'TCGA-NH-A8F8', 'TCGA-A6-5662', 'TCGA-A6-2674', 'TCGA-AG-A01N', 'TCGA-DM-A1D8', 'TCGA-AA-3837', 'TCGA-AA-A01I', 'TCGA-DM-A0XF', 'TCGA-F4-6855', 'TCGA-CA-6715', 'TCGA-AA-3975', 'TCGA-AA-3989', 'TCGA-AG-A016', 'TCGA-A6-5666', 'TCGA-CK-6746', 'TCGA-CM-4752', 'TCGA-CM-6676', 'TCGA-D5-6541', 'TCGA-A6-5667', 'TCGA-AF-2691', 'TCGA-AD-A5EJ', 'TCGA-F5-6864', 'TCGA-AG-3726', 'TCGA-A6-6649', 'TCGA-AA-A03J', 'TCGA-AF-2687', 'TCGA-AA-3858', 'TCGA-G4-6321', 'TCGA-G5-6641', 'TCGA-G4-6309', 'TCGA-D5-5538', 'TCGA-DM-A28K', 'TCGA-AG-3878', 'TCGA-AZ-6598', 'TCGA-AA-3688', 'TCGA-AG-A01W', 'TCGA-AA-3517', 'TCGA-D5-6898', 'TCGA-CM-5341', 'TCGA-CM-6167', 'TCGA-D5-6531', 'TCGA-DC-6683', 'TCGA-AF-2690', 'TCGA-CM-6161', 'TCGA-A6-5660', 'TCGA-NH-A8F7', 'TCGA-CM-6163', 'TCGA-AA-3968', 'TCGA-CM-5868', 'TCGA-AA-3976', 'TCGA-A6-A565', 'TCGA-AM-5821', 'TCGA-AD-6965', 'TCGA-AY-A71X', 'TCGA-AZ-4682', 'TCGA-AG-3892', 'TCGA-A6-2675', 'TCGA-F4-6569', 'TCGA-AG-3893', 'TCGA-CM-5862', 'TCGA-AZ-6606', 'TCGA-CM-6678', 'TCGA-D5-6931', 'TCGA-D5-6923', 'TCGA-AG-3575', 'TCGA-AA-A00Z', 'TCGA-D5-6540', 'TCGA-A6-3808', 'TCGA-CM-4743', 'TCGA-DC-4749', 'TCGA-T9-A92H', 'TCGA-A6-3810', 'TCGA-AD-6889', 'TCGA-G4-6320', 'TCGA-AA-3971', 'TCGA-AZ-4308', 'TCGA-D5-7000', 'TCGA-DM-A28A', 'TCGA-AG-A01L', 'TCGA-EI-6512', 'TCGA-A6-5661', 'TCGA-AG-3599', 'TCGA-AG-3902', 'TCGA-AD-6548', 'TCGA-AA-A022', 'TCGA-AG-3580', 'TCGA-A6-A567', 'TCGA-AA-3679', 'TCGA-CA-5256', 'TCGA-F4-6808', 'TCGA-D5-6536', 'TCGA-DM-A1D9', 'TCGA-AG-3885', 'TCGA-DM-A1D0', 'TCGA-CM-6674', 'TCGA-EI-6507', 'TCGA-F4-6459', 'TCGA-A6-6137', 'TCGA-AD-6899', 'TCGA-EI-6885', 'TCGA-WS-AB45', 'TCGA-D5-6930', 'TCGA-AU-6004', 'TCGA-AY-6196', 'TCGA-AA-A010', 'TCGA-AG-A00C', 'TCGA-4T-AA8H', 'TCGA-G4-6302', 'TCGA-AA-3966', 'TCGA-AF-2693', 'TCGA-D5-6926', 'TCGA-DM-A0XD', 'TCGA-AA-3854', 'TCGA-D5-6932', 'TCGA-EI-7004', 'TCGA-AG-3594', 'TCGA-EI-6510', 'TCGA-AA-3715', 'TCGA-AA-A01X', 'TCGA-F4-6570', 'TCGA-EI-6511', 'TCGA-CM-5861', 'TCGA-CA-5254', 'TCGA-G4-6317', 'TCGA-DM-A28M', 'TCGA-EI-6506', 'TCGA-AG-A020', 'TCGA-AG-3583', 'TCGA-G4-6294', 'TCGA-CM-6165', 'TCGA-D5-6535', 'TCGA-D5-5541', 'TCGA-DM-A1DB', 'TCGA-AG-A01J', 'TCGA-AG-4001', 'TCGA-AG-A00Y', 'TCGA-AA-3949', 'TCGA-AA-3842', 'TCGA-CA-6716', 'TCGA-CM-5348', 'TCGA-AA-3696', 'TCGA-AA-3833', 'TCGA-NH-A6GC', 'TCGA-CK-4947', 'TCGA-AA-3846', 'TCGA-A6-2677', 'TCGA-A6-2684', 'TCGA-NH-A6GA', 'TCGA-F5-6811', 'TCGA-A6-4107', 'TCGA-F5-6571', 'TCGA-CK-6747', 'TCGA-AA-3666', 'TCGA-CK-5914', 'TCGA-G4-6627', 'TCGA-DC-6681', 'TCGA-AZ-4315', 'TCGA-DT-5265', 'TCGA-CM-4751', 'TCGA-CM-6168', 'TCGA-AY-5543', 'TCGA-AZ-6607', 'TCGA-AG-3901', 'TCGA-AA-3695', 'TCGA-AD-6963', 'TCGA-CK-5913', 'TCGA-AF-2692', 'TCGA-G4-6307', 'TCGA-AA-A01T', 'TCGA-DY-A1DG', 'TCGA-AA-A01C', 'TCGA-AZ-6603', 'TCGA-AG-3727', 'TCGA-AG-3909', 'TCGA-D5-6539', 'TCGA-AZ-5407', 'TCGA-NH-A6GB', 'TCGA-AA-3851', 'TCGA-AF-5654', 'TCGA-AA-3530', 'TCGA-F5-6813', 'TCGA-AA-3845', 'TCGA-AG-3584', 'TCGA-AM-5820', 'TCGA-AA-3982', 'TCGA-D5-6924', 'TCGA-AA-3984', 'TCGA-5M-AATE', 'TCGA-AG-3894', 'TCGA-AU-3779', 'TCGA-A6-2671', 'TCGA-DC-6682', 'TCGA-A6-6653', 'TCGA-DM-A28E', 'TCGA-AZ-5403', 'TCGA-CK-4948', 'TCGA-AF-6655', 'TCGA-AA-A01P', 'TCGA-AA-A024', 'TCGA-F4-6809', 'TCGA-AA-3548', 'TCGA-QG-A5YW', 'TCGA-AA-3818', 'TCGA-EI-6513', 'TCGA-AY-4071', 'TCGA-D5-6532', 'TCGA-AZ-6600', 'TCGA-CM-6172', 'TCGA-AG-A01Y', 'TCGA-AA-3675', 'TCGA-AD-6888', 'TCGA-AH-6897', 'TCGA-AA-3852', 'TCGA-CM-4747', 'TCGA-AG-A02N', 'TCGA-AA-A02F', 'TCGA-DM-A1D7', 'TCGA-AA-3678', 'TCGA-CM-6677', 'TCGA-AA-A004', 'TCGA-D5-5539', 'TCGA-F5-6861', 'TCGA-AA-3544', 'TCGA-F4-6463', 'TCGA-AG-4015', 'TCGA-AF-3400', 'TCGA-CM-6169', 'TCGA-F4-6461', 'TCGA-AD-6901', 'TCGA-DM-A280', 'TCGA-AA-3819', 'TCGA-DM-A0X9', 'TCGA-G4-6315', 'TCGA-AA-3850', 'TCGA-CA-5797', 'TCGA-AA-3856', 'TCGA-AY-6386', 'TCGA-AG-3598', 'TCGA-CM-5863', 'TCGA-AG-3605', 'TCGA-AZ-6599', 'TCGA-CM-5344', 'TCGA-AA-3693', 'TCGA-G4-6306', 'TCGA-F4-6854', 'TCGA-A6-5656', 'TCGA-DY-A0XA', 'TCGA-A6-2685', 'TCGA-AY-6197', 'TCGA-4N-A93T', 'TCGA-NH-A50U', 'TCGA-A6-2683', 'TCGA-AA-3952', 'TCGA-DC-6160', 'TCGA-A6-3807', 'TCGA-AA-3522', 'TCGA-G4-6304', 'TCGA-AA-3526', 'TCGA-DC-4745', 'TCGA-AA-3877', 'TCGA-CM-6162', 'TCGA-AA-3692', 'TCGA-AA-3524', 'TCGA-AG-3881', 'TCGA-AA-3681', 'TCGA-CK-4951', 'TCGA-AA-3534', 'TCGA-A6-A56B', 'TCGA-DM-A1D4', 'TCGA-5M-AAT5', 'TCGA-AG-4022', 'TCGA-AA-3812', 'TCGA-D5-6529', 'TCGA-AA-3673', 'TCGA-EI-6917', 'TCGA-EI-6884', 'TCGA-AG-A011', 'TCGA-AY-A8YK', 'TCGA-EI-6881', 'TCGA-A6-2678', 'TCGA-AA-3947', 'TCGA-A6-5659', 'TCGA-AA-3994', 'TCGA-D5-6534', 'TCGA-AG-3574', 'TCGA-F5-6465', 'TCGA-CA-6719', 'TCGA-AA-3532', 'TCGA-G4-6322', 'TCGA-AF-A56K', 'TCGA-AA-3864', 'TCGA-AA-3685', 'TCGA-AA-3986'] + valid_patients=['TCGA-AG-4021', 'TCGA-AG-A02X', 'TCGA-AA-A02H', 'TCGA-F4-6807', 'TCGA-G4-6311', 'TCGA-AF-A56L', 'TCGA-CM-4748', 'TCGA-AZ-6605', 'TCGA-G4-6299', 'TCGA-AA-3667', 'TCGA-5M-AAT4', 'TCGA-5M-AAT6', 'TCGA-F4-6704', 'TCGA-AH-6544', 'TCGA-3L-AA1B', 'TCGA-CK-4950', 'TCGA-AA-3956', 'TCGA-G4-6323', 'TCGA-AA-3831', 'TCGA-AH-6903', 'TCGA-CA-5796', 'TCGA-AA-3684', 'TCGA-EI-7002', 'TCGA-F4-6460', 'TCGA-AA-3844', 'TCGA-AA-3848', 'TCGA-D5-6928', 'TCGA-A6-5665', 'TCGA-AA-A01S', 'TCGA-DM-A28H', 'TCGA-DC-6157', 'TCGA-DY-A1DD', 'TCGA-AA-3519', 'TCGA-AA-3855', 'TCGA-F4-6806', 'TCGA-AA-3520', 'TCGA-CM-6675', 'TCGA-AG-3898', 'TCGA-CK-6748', 'TCGA-G4-6298', 'TCGA-G4-6626', 'TCGA-QG-A5Z1', 'TCGA-DM-A28G', 'TCGA-A6-2681', 'TCGA-DM-A28F', 'TCGA-G4-6586', 'TCGA-CK-5916', 'TCGA-AG-A002', 'TCGA-AG-A026', 'TCGA-AA-A02E', 'TCGA-F5-6464', 'TCGA-EI-6883', 'TCGA-F4-6856', 'TCGA-AG-A015', 'TCGA-CM-5349', 'TCGA-DM-A1DA', 'TCGA-D5-6530', 'TCGA-A6-A566', 'TCGA-NH-A50V', 'TCGA-F5-6814', 'TCGA-CM-6166', 'TCGA-CI-6622', 'TCGA-AA-3977', 'TCGA-CA-6718', 'TCGA-AA-3841', 'TCGA-AA-3521', 'TCGA-NH-A50T', 'TCGA-AA-A01R', 'TCGA-AG-A008', 'TCGA-CM-4746', 'TCGA-AA-A02R', 'TCGA-DC-6154', 'TCGA-AA-3531', 'TCGA-F5-6863', 'TCGA-F4-6703', 'TCGA-AY-A54L', 'TCGA-AA-3811', 'TCGA-AA-3814', 'TCGA-CK-5912', 'TCGA-A6-3809', 'TCGA-A6-5657', 'TCGA-EF-5830', 'TCGA-CM-6164', 'TCGA-SS-A7HO', 'TCGA-CM-5864', 'TCGA-AF-4110', 'TCGA-AF-6672', 'TCGA-AA-3866', 'TCGA-AA-A01V', 'TCGA-AA-A00N', 'TCGA-D5-6538', 'TCGA-CM-6170', 'TCGA-D5-6537', 'TCGA-AA-A02Y', 'TCGA-DY-A1DC', 'TCGA-AG-3883', 'TCGA-AD-5900', 'TCGA-AA-3955', 'TCGA-AG-3581', 'TCGA-A6-5664', 'TCGA-DY-A1DF', 'TCGA-AA-A02O', 'TCGA-AA-3527', 'TCGA-F5-6702', 'TCGA-AH-6643', 'TCGA-G4-6293', 'TCGA-DC-5337', 'TCGA-AA-A03F', 'TCGA-CI-6624', 'TCGA-AG-3602', 'TCGA-AA-3538', 'TCGA-AA-A01Z', 'TCGA-AA-3558', 'TCGA-AA-3560', 'TCGA-AA-3867', 'TCGA-A6-6650', 'TCGA-CK-5915', 'TCGA-A6-6651', 'TCGA-AY-4070', 'TCGA-AF-A56N', 'TCGA-D5-6533', 'TCGA-AA-3529', 'TCGA-AG-A032', 'TCGA-QG-A5YX'] + + ''' + model = LitVisionTransformer(categories=['WT', 'MUT'], + dim_input=1536, + category_weights=tensor([0.8676, 0.1324]), + dim_model=512, + dim_feedforward=512, n_heads=8, n_layers=2, - dropout=0.25) - model.set( - categories=train_categories, - category_weights=category_weights, - dim_input=dim_feats, + dropout=0.25, + use_alibi=False, + ground_truth_label='isMSIH', + train_patients=['TCGA-AH-6644', 'TCGA-AA-3664', 'TCGA-AZ-4614', 'TCGA-D5-5537', + 'TCGA-G4-6628', 'TCGA-EI-6882', 'TCGA-AF-2689', 'TCGA-F4-6805', + 'TCGA-AZ-4681', 'TCGA-AG-3882', 'TCGA-A6-6648', 'TCGA-AA-A017', + 'TCGA-G4-6303', 'TCGA-EI-6508', 'TCGA-AF-6136', 'TCGA-A6-6654', + 'TCGA-AD-6895', 'TCGA-CM-5860', 'TCGA-A6-4105', 'TCGA-F5-6810', + 'TCGA-QG-A5YV', 'TCGA-DC-6155', 'TCGA-F5-6812', 'TCGA-QL-A97D', + 'TCGA-AZ-4615', 'TCGA-CK-6751', 'TCGA-AH-6547', 'TCGA-AA-3980', + 'TCGA-CL-5918', 'TCGA-DM-A282', 'TCGA-AA-3514', 'TCGA-EI-6509', + 'TCGA-AF-3911', 'TCGA-CL-5917', 'TCGA-A6-2686', 'TCGA-AA-3950', + 'TCGA-AA-3549', 'TCGA-AA-3973', 'TCGA-D5-6922', 'TCGA-AG-3887', + 'TCGA-CA-6717', 'TCGA-CM-6171', 'TCGA-AD-6964', 'TCGA-A6-6142', + 'TCGA-AD-6890', 'TCGA-A6-6138', 'TCGA-AD-A5EK', 'TCGA-D5-6929', + 'TCGA-G4-6588', 'TCGA-AZ-4616', 'TCGA-AA-3561', 'TCGA-QG-A5Z2', + 'TCGA-DM-A1HA', 'TCGA-CL-4957', 'TCGA-CA-5255', 'TCGA-DC-6158', + 'TCGA-G4-6295', 'TCGA-AY-A69D', 'TCGA-DM-A1D6', 'TCGA-AG-3896', + 'TCGA-A6-6652', 'TCGA-CM-6680', 'TCGA-AA-3821', 'TCGA-D5-5540', + 'TCGA-DC-5869', 'TCGA-AG-4008', 'TCGA-CK-4952', 'TCGA-AA-3875', + 'TCGA-AA-A02W', 'TCGA-EI-6514', 'TCGA-DM-A288', 'TCGA-AG-3890', + 'TCGA-AA-3979', 'TCGA-NH-A5IV', 'TCGA-G4-6297', 'TCGA-D5-6927', + 'TCGA-AA-3680', 'TCGA-NH-A8F8', 'TCGA-A6-5662', 'TCGA-A6-2674', + 'TCGA-AG-A01N', 'TCGA-DM-A1D8', 'TCGA-AA-3837', 'TCGA-AA-A01I', + 'TCGA-DM-A0XF', 'TCGA-F4-6855', 'TCGA-CA-6715', 'TCGA-AA-3975', + 'TCGA-AA-3989', 'TCGA-AG-A016', 'TCGA-A6-5666', 'TCGA-CK-6746', + 'TCGA-CM-4752', 'TCGA-CM-6676', 'TCGA-D5-6541', 'TCGA-A6-5667', + 'TCGA-AF-2691', 'TCGA-AD-A5EJ', 'TCGA-F5-6864', 'TCGA-AG-3726', + 'TCGA-A6-6649', 'TCGA-AA-A03J', 'TCGA-AF-2687', 'TCGA-AA-3858', + 'TCGA-G4-6321', 'TCGA-G5-6641', 'TCGA-G4-6309', 'TCGA-D5-5538', + 'TCGA-DM-A28K', 'TCGA-AG-3878', 'TCGA-AZ-6598', 'TCGA-AA-3688', + 'TCGA-AG-A01W', 'TCGA-AA-3517', 'TCGA-D5-6898', 'TCGA-CM-5341', + 'TCGA-CM-6167', 'TCGA-D5-6531', 'TCGA-DC-6683', 'TCGA-AF-2690', + 'TCGA-CM-6161', 'TCGA-A6-5660', 'TCGA-NH-A8F7', 'TCGA-CM-6163', + 'TCGA-AA-3968', 'TCGA-CM-5868', 'TCGA-AA-3976', 'TCGA-A6-A565', + 'TCGA-AM-5821', 'TCGA-AD-6965', 'TCGA-AY-A71X', 'TCGA-AZ-4682', + 'TCGA-AG-3892', 'TCGA-A6-2675', 'TCGA-F4-6569', 'TCGA-AG-3893', + 'TCGA-CM-5862', 'TCGA-AZ-6606', 'TCGA-CM-6678', 'TCGA-D5-6931', + 'TCGA-D5-6923', 'TCGA-AG-3575', 'TCGA-AA-A00Z', 'TCGA-D5-6540', + 'TCGA-A6-3808', 'TCGA-CM-4743', 'TCGA-DC-4749', 'TCGA-T9-A92H', + 'TCGA-A6-3810', 'TCGA-AD-6889', 'TCGA-G4-6320', 'TCGA-AA-3971', + 'TCGA-AZ-4308', 'TCGA-D5-7000', 'TCGA-DM-A28A', 'TCGA-AG-A01L', + 'TCGA-EI-6512', 'TCGA-A6-5661', 'TCGA-AG-3599', 'TCGA-AG-3902', + 'TCGA-AD-6548', 'TCGA-AA-A022', 'TCGA-AG-3580', 'TCGA-A6-A567', + 'TCGA-AA-3679', 'TCGA-CA-5256', 'TCGA-F4-6808', 'TCGA-D5-6536', + 'TCGA-DM-A1D9', 'TCGA-AG-3885', 'TCGA-DM-A1D0', 'TCGA-CM-6674', + 'TCGA-EI-6507', 'TCGA-F4-6459', 'TCGA-A6-6137', 'TCGA-AD-6899', + 'TCGA-EI-6885', 'TCGA-WS-AB45', 'TCGA-D5-6930', 'TCGA-AU-6004', + 'TCGA-AY-6196', 'TCGA-AA-A010', 'TCGA-AG-A00C', 'TCGA-4T-AA8H', + 'TCGA-G4-6302', 'TCGA-AA-3966', 'TCGA-AF-2693', 'TCGA-D5-6926', + 'TCGA-DM-A0XD', 'TCGA-AA-3854', 'TCGA-D5-6932', 'TCGA-EI-7004', + 'TCGA-AG-3594', 'TCGA-EI-6510', 'TCGA-AA-3715', 'TCGA-AA-A01X', + 'TCGA-F4-6570', 'TCGA-EI-6511', 'TCGA-CM-5861', 'TCGA-CA-5254', + 'TCGA-G4-6317', 'TCGA-DM-A28M', 'TCGA-EI-6506', 'TCGA-AG-A020', + 'TCGA-AG-3583', 'TCGA-G4-6294', 'TCGA-CM-6165', 'TCGA-D5-6535', + 'TCGA-D5-5541', 'TCGA-DM-A1DB', 'TCGA-AG-A01J', 'TCGA-AG-4001', + 'TCGA-AG-A00Y', 'TCGA-AA-3949', 'TCGA-AA-3842', 'TCGA-CA-6716', + 'TCGA-CM-5348', 'TCGA-AA-3696', 'TCGA-AA-3833', 'TCGA-NH-A6GC', + 'TCGA-CK-4947', 'TCGA-AA-3846', 'TCGA-A6-2677', 'TCGA-A6-2684', + 'TCGA-NH-A6GA', 'TCGA-F5-6811', 'TCGA-A6-4107', 'TCGA-F5-6571', + 'TCGA-CK-6747', 'TCGA-AA-3666', 'TCGA-CK-5914', 'TCGA-G4-6627', + 'TCGA-DC-6681', 'TCGA-AZ-4315', 'TCGA-DT-5265', 'TCGA-CM-4751', + 'TCGA-CM-6168', 'TCGA-AY-5543', 'TCGA-AZ-6607', 'TCGA-AG-3901', + 'TCGA-AA-3695', 'TCGA-AD-6963', 'TCGA-CK-5913', 'TCGA-AF-2692', + 'TCGA-G4-6307', 'TCGA-AA-A01T', 'TCGA-DY-A1DG', 'TCGA-AA-A01C', + 'TCGA-AZ-6603', 'TCGA-AG-3727', 'TCGA-AG-3909', 'TCGA-D5-6539', + 'TCGA-AZ-5407', 'TCGA-NH-A6GB', 'TCGA-AA-3851', 'TCGA-AF-5654', + 'TCGA-AA-3530', 'TCGA-F5-6813', 'TCGA-AA-3845', 'TCGA-AG-3584', + 'TCGA-AM-5820', 'TCGA-AA-3982', 'TCGA-D5-6924', 'TCGA-AA-3984', + 'TCGA-5M-AATE', 'TCGA-AG-3894', 'TCGA-AU-3779', 'TCGA-A6-2671', + 'TCGA-DC-6682', 'TCGA-A6-6653', 'TCGA-DM-A28E', 'TCGA-AZ-5403', + 'TCGA-CK-4948', 'TCGA-AF-6655', 'TCGA-AA-A01P', 'TCGA-AA-A024', + 'TCGA-F4-6809', 'TCGA-AA-3548', 'TCGA-QG-A5YW', 'TCGA-AA-3818', + 'TCGA-EI-6513', 'TCGA-AY-4071', 'TCGA-D5-6532', 'TCGA-AZ-6600', + 'TCGA-CM-6172', 'TCGA-AG-A01Y', 'TCGA-AA-3675', 'TCGA-AD-6888', + 'TCGA-AH-6897', 'TCGA-AA-3852', 'TCGA-CM-4747', 'TCGA-AG-A02N', + 'TCGA-AA-A02F', 'TCGA-DM-A1D7', 'TCGA-AA-3678', 'TCGA-CM-6677', + 'TCGA-AA-A004', 'TCGA-D5-5539', 'TCGA-F5-6861', 'TCGA-AA-3544', + 'TCGA-F4-6463', 'TCGA-AG-4015', 'TCGA-AF-3400', 'TCGA-CM-6169', + 'TCGA-F4-6461', 'TCGA-AD-6901', 'TCGA-DM-A280', 'TCGA-AA-3819', + 'TCGA-DM-A0X9', 'TCGA-G4-6315', 'TCGA-AA-3850', 'TCGA-CA-5797', + 'TCGA-AA-3856', 'TCGA-AY-6386', 'TCGA-AG-3598', 'TCGA-CM-5863', + 'TCGA-AG-3605', 'TCGA-AZ-6599', 'TCGA-CM-5344', 'TCGA-AA-3693', + 'TCGA-G4-6306', 'TCGA-F4-6854', 'TCGA-A6-5656', 'TCGA-DY-A0XA', + 'TCGA-A6-2685', 'TCGA-AY-6197', 'TCGA-4N-A93T', 'TCGA-NH-A50U', + 'TCGA-A6-2683', 'TCGA-AA-3952', 'TCGA-DC-6160', 'TCGA-A6-3807', + 'TCGA-AA-3522', 'TCGA-G4-6304', 'TCGA-AA-3526', 'TCGA-DC-4745', + 'TCGA-AA-3877', 'TCGA-CM-6162', 'TCGA-AA-3692', 'TCGA-AA-3524', + 'TCGA-AG-3881', 'TCGA-AA-3681', 'TCGA-CK-4951', 'TCGA-AA-3534', + 'TCGA-A6-A56B', 'TCGA-DM-A1D4', 'TCGA-5M-AAT5', 'TCGA-AG-4022', + 'TCGA-AA-3812', 'TCGA-D5-6529', 'TCGA-AA-3673', 'TCGA-EI-6917', + 'TCGA-EI-6884', 'TCGA-AG-A011', 'TCGA-AY-A8YK', 'TCGA-EI-6881', + 'TCGA-A6-2678', 'TCGA-AA-3947', 'TCGA-A6-5659', 'TCGA-AA-3994', + 'TCGA-D5-6534', 'TCGA-AG-3574', 'TCGA-F5-6465', 'TCGA-CA-6719', + 'TCGA-AA-3532', 'TCGA-G4-6322', 'TCGA-AF-A56K', 'TCGA-AA-3864', + 'TCGA-AA-3685', 'TCGA-AA-3986'], + valid_patients=['TCGA-AG-4021', 'TCGA-AG-A02X', 'TCGA-AA-A02H', 'TCGA-F4-6807', + 'TCGA-G4-6311', 'TCGA-AF-A56L', 'TCGA-CM-4748', 'TCGA-AZ-6605', + 'TCGA-G4-6299', 'TCGA-AA-3667', 'TCGA-5M-AAT4', 'TCGA-5M-AAT6', + 'TCGA-F4-6704', 'TCGA-AH-6544', 'TCGA-3L-AA1B', 'TCGA-CK-4950', + 'TCGA-AA-3956', 'TCGA-G4-6323', 'TCGA-AA-3831', 'TCGA-AH-6903', + 'TCGA-CA-5796', 'TCGA-AA-3684', 'TCGA-EI-7002', 'TCGA-F4-6460', + 'TCGA-AA-3844', 'TCGA-AA-3848', 'TCGA-D5-6928', 'TCGA-A6-5665', + 'TCGA-AA-A01S', 'TCGA-DM-A28H', 'TCGA-DC-6157', 'TCGA-DY-A1DD', + 'TCGA-AA-3519', 'TCGA-AA-3855', 'TCGA-F4-6806', 'TCGA-AA-3520', + 'TCGA-CM-6675', 'TCGA-AG-3898', 'TCGA-CK-6748', 'TCGA-G4-6298', + 'TCGA-G4-6626', 'TCGA-QG-A5Z1', 'TCGA-DM-A28G', 'TCGA-A6-2681', + 'TCGA-DM-A28F', 'TCGA-G4-6586', 'TCGA-CK-5916', 'TCGA-AG-A002', + 'TCGA-AG-A026', 'TCGA-AA-A02E', 'TCGA-F5-6464', 'TCGA-EI-6883', + 'TCGA-F4-6856', 'TCGA-AG-A015', 'TCGA-CM-5349', 'TCGA-DM-A1DA', + 'TCGA-D5-6530', 'TCGA-A6-A566', 'TCGA-NH-A50V', 'TCGA-F5-6814', + 'TCGA-CM-6166', 'TCGA-CI-6622', 'TCGA-AA-3977', 'TCGA-CA-6718', + 'TCGA-AA-3841', 'TCGA-AA-3521', 'TCGA-NH-A50T', 'TCGA-AA-A01R', + 'TCGA-AG-A008', 'TCGA-CM-4746', 'TCGA-AA-A02R', 'TCGA-DC-6154', + 'TCGA-AA-3531', 'TCGA-F5-6863', 'TCGA-F4-6703', 'TCGA-AY-A54L', + 'TCGA-AA-3811', 'TCGA-AA-3814', 'TCGA-CK-5912', 'TCGA-A6-3809', + 'TCGA-A6-5657', 'TCGA-EF-5830', 'TCGA-CM-6164', 'TCGA-SS-A7HO', + 'TCGA-CM-5864', 'TCGA-AF-4110', 'TCGA-AF-6672', 'TCGA-AA-3866', + 'TCGA-AA-A01V', 'TCGA-AA-A00N', 'TCGA-D5-6538', 'TCGA-CM-6170', + 'TCGA-D5-6537', 'TCGA-AA-A02Y', 'TCGA-DY-A1DC', 'TCGA-AG-3883', + 'TCGA-AD-5900', 'TCGA-AA-3955', 'TCGA-AG-3581', 'TCGA-A6-5664', + 'TCGA-DY-A1DF', 'TCGA-AA-A02O', 'TCGA-AA-3527', 'TCGA-F5-6702', + 'TCGA-AH-6643', 'TCGA-G4-6293', 'TCGA-DC-5337', 'TCGA-AA-A03F', + 'TCGA-CI-6624', 'TCGA-AG-3602', 'TCGA-AA-3538', 'TCGA-AA-A01Z', + 'TCGA-AA-3558', 'TCGA-AA-3560', 'TCGA-AA-3867', 'TCGA-A6-6650', + 'TCGA-CK-5915', 'TCGA-A6-6651', 'TCGA-AY-4070', 'TCGA-AF-A56N', + 'TCGA-D5-6533', 'TCGA-AA-3529', 'TCGA-AG-A032', 'TCGA-QG-A5YX'], + ) + + ''' + model = ModelClass( + categories=[np.str_('MUT'), np.str_('WT')], + dim_input = 1536, + category_weights = tensor([0.8676, 0.1324]), + dim_model=512, + dim_feedforward=512, + n_heads=8, + n_layers=2, + dropout=0.25, + use_alibi=use_alibi, ground_truth_label=ground_truth_label, train_patients=train_patients, valid_patients=valid_patients, clini_table=clini_table, slide_table=slide_table, - feature_dir=feature_dir, + feature_dir=feature_dir,) + ''' + logger.info( + f"Model 'instantiated with {len(model.state_dict())} parameters." ) - return model, train_dl, valid_dl @@ -386,7 +556,7 @@ def main(): ) ''' output_dir = "/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/STAMP_crossval_new" - logger.info('output_dir:', output_dir) + #logger.info('output_dir:', output_dir) # _add_file_handle_(_logger, output_dir=Path(output_dir)) #_logger.info("Using training configuration from environment variables.") diff --git a/application/jobs/stamp/app/custom/modeling/alibi.py b/application/jobs/stamp/app/custom/modeling/alibi.py new file mode 100644 index 00000000..69c61aed --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/alibi.py @@ -0,0 +1,147 @@ +import torch +from jaxtyping import Bool, Float +from torch import Tensor, nn + + +class _RunningMeanScaler(nn.Module): + """Scales values by the inverse of the mean of values seen before.""" + + def __init__(self, dtype=torch.float32) -> None: + super().__init__() + self.running_mean = nn.Buffer(torch.ones(1, dtype=dtype)) + self.items_so_far = nn.Buffer(torch.ones(1, dtype=dtype)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.training: + # Welford's algorithm + self.running_mean.copy_( + (self.running_mean + (x - self.running_mean) / self.items_so_far).mean() + ) + self.items_so_far += 1 + + return x / self.running_mean + + +class _ALiBi(nn.Module): + # See MultiHeadAliBi + def __init__(self) -> None: + super().__init__() + + self.scale_distance = _RunningMeanScaler() + self.bias_scale = nn.Parameter(torch.rand(1)) + + def forward( + self, + *, + q: Float[Tensor, "batch query qk_feature"], + k: Float[Tensor, "batch key qk_feature"], + v: Float[Tensor, "batch key v_feature"], + coords_q: Float[Tensor, "batch query coord"], + coords_k: Float[Tensor, "batch key coord"], + attn_mask: Bool[Tensor, "batch query key"] | None, + alibi_mask: Bool[Tensor, "batch query key"] | None, + ) -> Float[Tensor, "batch query v_feature"]: + """ + Args: + alibi_mask: + Which query-key pairs to mask from ALiBi (i.e. don't apply ALiBi to). + """ + weight_logits = torch.einsum("bqf,bkf->bqk", q, k) * (k.size(-1) ** -0.5) + distances = torch.linalg.norm( + coords_q.unsqueeze(2) - coords_k.unsqueeze(1), dim=-1 + ) + scaled_distances = self.scale_distance(distances) * self.bias_scale + + if alibi_mask is not None: + scaled_distances = scaled_distances.where(~alibi_mask, 0.0) + + weights = torch.softmax(weight_logits, dim=-1) + + if attn_mask is not None: + weights = (weights - scaled_distances).where(~attn_mask, 0.0) + else: + weights = weights - scaled_distances + + attention = torch.einsum("bqk,bkf->bqf", weights, v) + + return attention + + +class MultiHeadALiBi(nn.Module): + """Attention with Linear Biases + + Based on + > PRESS, Ofir; SMITH, Noah A.; LEWIS, Mike. + > Train short, test long: Attention with linear biases enables input length extrapolation. + > arXiv preprint arXiv:2108.12409, 2021. + + Since the distances between in WSIs may be quite large, + we scale the distances by the mean distance seen during training. + """ + + def __init__( + self, + *, + embed_dim: int, + num_heads: int, + ) -> None: + super().__init__() + + if embed_dim % num_heads != 0: + raise ValueError(f"{embed_dim=} has to be divisible by {num_heads=}") + + self.query_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + self.key_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + self.value_encoders = nn.ModuleList( + [ + nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) + for _ in range(num_heads) + ] + ) + + self.attentions = nn.ModuleList([_ALiBi() for _ in range(num_heads)]) + + self.fc = nn.Linear(in_features=embed_dim, out_features=embed_dim) + + def forward( + self, + *, + q: Float[Tensor, "batch query mh_qk_feature"], + k: Float[Tensor, "batch key mh_qk_feature"], + v: Float[Tensor, "batch key hm_v_feature"], + coords_q: Float[Tensor, "batch query coord"], + coords_k: Float[Tensor, "batch key coord"], + attn_mask: Bool[Tensor, "batch query key"] | None, + alibi_mask: Bool[Tensor, "batch query key"] | None, + ) -> Float[Tensor, "batch query mh_v_feature"]: + stacked_attentions = torch.stack( + [ + att( + q=q_enc(q), + k=k_enc(k), + v=v_enc(v), + coords_q=coords_q, + coords_k=coords_k, + attn_mask=attn_mask, + alibi_mask=alibi_mask, + ) + for q_enc, k_enc, v_enc, att in zip( + self.query_encoders, + self.key_encoders, + self.value_encoders, + self.attentions, + strict=True, + ) + ] + ) + return self.fc(stacked_attentions.permute(1, 2, 0, 3).flatten(-2, -1)) diff --git a/application/jobs/stamp/app/custom/modeling/lightning_model.py b/application/jobs/stamp/app/custom/modeling/lightning_model.py index 97089551..2dd5f64e 100644 --- a/application/jobs/stamp/app/custom/modeling/lightning_model.py +++ b/application/jobs/stamp/app/custom/modeling/lightning_model.py @@ -55,88 +55,63 @@ class LitVisionTransformer(lightning.LightningModule): **metadata: Additional metadata to store with the model. """ + supported_features = ["tile"] + def __init__( self, *, + categories: Sequence[Category], + category_weights: Float[Tensor, "category_weight"], # noqa: F821 + dim_input: int, dim_model: int, dim_feedforward: int, n_heads: int, n_layers: int, dropout: float, - **metadata, - ) -> None: - super().__init__() - - self.dim_model = dim_model - self.dim_feedforward = dim_feedforward - self.n_heads = n_heads - self.n_layers = n_layers - self.dropout = dropout - self.metadata = metadata - - # Parameters to be set later - self.categories = None - self.category_weights = None - self.ground_truth_label = None - self.train_patients = None - self.valid_patients = None - self.use_alibi = False - self.dim_input = None - - # Optional external metadata - self.clini_table = None - self.slide_table = None - self.feature_dir = None - - self.vision_transformer = None - self.valid_auroc = None - - def set( - self, - *, - categories: Sequence[Category], - category_weights: Float[Tensor, "category_weight"], - dim_input: int, + # Experimental features + # TODO remove default values for stamp 3; they're only here for backwards compatibility use_alibi: bool = False, + # Metadata used by other parts of stamp, but not by the model itself ground_truth_label: PandasLabel, train_patients: Iterable[PatientId], valid_patients: Iterable[PatientId], - clini_table=None, - slide_table=None, - feature_dir=None, - ): - if len(categories) != len(category_weights): - raise ValueError("the number of category weights must match the number of categories!") + # Other metadata + **metadata, + ) -> None: + super().__init__() - self.categories = np.array(categories) - self.category_weights = category_weights - self.dim_input = dim_input - self.use_alibi = use_alibi - self.ground_truth_label = ground_truth_label - self.train_patients = train_patients - self.valid_patients = valid_patients + if len(categories) != len(category_weights): + raise ValueError( + "the number of category weights has to match the number of categories!" + ) - self.clini_table = clini_table - self.slide_table = slide_table - self.feature_dir = feature_dir - # Initialize model and AUROC after all info is available self.vision_transformer = VisionTransformer( dim_output=len(categories), dim_input=dim_input, - dim_model=self.dim_model, - n_layers=self.n_layers, - n_heads=self.n_heads, - dim_feedforward=self.dim_feedforward, - dropout=self.dropout, + dim_model=dim_model, + n_layers=n_layers, + n_heads=n_heads, + dim_feedforward=dim_feedforward, + dropout=dropout, use_alibi=use_alibi, ) self.class_weights = category_weights self.valid_auroc = MulticlassAUROC(len(categories)) - self.save_hyperparameters(ignore=["vision_transformer", "valid_auroc"]) - def forward(self, bags: Bags) -> Float[Tensor, "batch logit"]: - if self.vision_transformer is None: - raise RuntimeError("Model not initialized. Call `.set()` first.") + # Used during deployment + self.ground_truth_label = ground_truth_label + self.categories = np.array(categories) + self.train_patients = train_patients + self.valid_patients = valid_patients + + _ = metadata # unused, but saved in model + + self.save_hyperparameters() + + def forward( + self, + bags: Bags, + ) -> Float[Tensor, "batch logit"]: return self.vision_transformer(bags) def _step( diff --git a/application/jobs/stamp/app/custom/modeling/mlp_classifier.py b/application/jobs/stamp/app/custom/modeling/mlp_classifier.py new file mode 100644 index 00000000..d99ec5e7 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/mlp_classifier.py @@ -0,0 +1,123 @@ +from collections.abc import Iterable, Sequence + +import lightning +import numpy as np +import torch +from torch import Tensor, nn +from torchmetrics.classification import MulticlassAUROC + +from .types import Category, PandasLabel, PatientId + + +class MLPClassifier(nn.Module): + """ + Simple MLP for classification from a single feature vector. + """ + + def __init__( + self, + dim_input: int, + dim_hidden: int, + dim_output: int, + num_layers: int, + dropout: float, + ): + super().__init__() + layers = [] + in_dim = dim_input + for i in range(num_layers - 1): + layers.append(nn.Linear(in_dim, dim_hidden)) + layers.append(nn.ReLU()) + layers.append(nn.Dropout(dropout)) + in_dim = dim_hidden + layers.append(nn.Linear(in_dim, dim_output)) + self.mlp = nn.Sequential(*layers) + + def forward(self, x: Tensor) -> Tensor: + return self.mlp(x) + + +class LitMLPClassifier(lightning.LightningModule): + """ + PyTorch Lightning wrapper for MLPClassifier. + """ + + supported_features = ["patient"] + + def __init__( + self, + *, + categories: Sequence[Category], + category_weights: torch.Tensor, + dim_input: int, + dim_hidden: int, + num_layers: int, + dropout: float, + ground_truth_label: PandasLabel, + train_patients: Iterable[PatientId], + valid_patients: Iterable[PatientId], + **metadata, + ): + super().__init__() + self.save_hyperparameters() + self.model = MLPClassifier( + dim_input=dim_input, + dim_hidden=dim_hidden, + dim_output=len(categories), + num_layers=num_layers, + dropout=dropout, + ) + self.class_weights = category_weights + self.valid_auroc = MulticlassAUROC(len(categories)) + self.ground_truth_label = ground_truth_label + self.categories = np.array(categories) + self.train_patients = train_patients + self.valid_patients = valid_patients + + # TODO: Add version check with version 2.2.1, for both MLP and Transformer + + def forward(self, x: Tensor) -> Tensor: + return self.model(x) + + def _step(self, batch, step_name: str): + feats, targets = batch + logits = self.model(feats) + loss = nn.functional.cross_entropy( + logits, + targets.type_as(logits), + weight=self.class_weights.type_as(logits), + ) + self.log( + f"{step_name}_loss", + loss, + on_step=False, + on_epoch=True, + prog_bar=True, + sync_dist=True, + ) + if step_name == "validation": + self.valid_auroc.update(logits, targets.long().argmax(dim=-1)) + self.log( + f"{step_name}_auroc", + self.valid_auroc, + on_step=False, + on_epoch=True, + sync_dist=True, + ) + return loss + + def training_step(self, batch, batch_idx): + return self._step(batch, "training") + + def validation_step(self, batch, batch_idx): + return self._step(batch, "validation") + + def test_step(self, batch, batch_idx): + return self._step(batch, "test") + + def predict_step(self, batch, batch_idx): + feats, _ = batch + return self.model(feats) + + def configure_optimizers(self): + return torch.optim.Adam(self.parameters(), lr=1e-3) diff --git a/application/jobs/stamp/app/custom/modeling/module.py b/application/jobs/stamp/app/custom/modeling/module.py new file mode 100644 index 00000000..30fa7551 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/module.py @@ -0,0 +1,1654 @@ +# Copyright The Lightning AI team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The LightningModule - an nn.Module with many additional features.""" + +import logging +import numbers +import weakref +from collections.abc import Generator, Mapping, Sequence +from contextlib import contextmanager +from io import BytesIO +from pathlib import Path +from typing import ( + IO, + TYPE_CHECKING, + Any, + Callable, + Literal, + Optional, + Union, + cast, + overload, +) + +import lightning.fabric as lf +import lightning.pytorch as pl +import torch +from lightning.fabric.loggers import Logger as FabricLogger +from lightning.fabric.utilities.apply_func import convert_to_tensors +from lightning.fabric.utilities.cloud_io import get_filesystem +from lightning.fabric.utilities.device_dtype_mixin import _DeviceDtypeModuleMixin +from lightning.fabric.utilities.types import _MAP_LOCATION_TYPE, _PATH +from lightning.fabric.wrappers import _FabricOptimizer +from lightning.pytorch.callbacks.callback import Callback +from lightning.pytorch.core.hooks import CheckpointHooks, DataHooks, ModelHooks +from lightning.pytorch.core.mixins import HyperparametersMixin +from lightning.pytorch.core.optimizer import LightningOptimizer +from lightning.pytorch.core.saving import _load_from_checkpoint +from lightning.pytorch.loggers import Logger +from lightning.pytorch.trainer import call +from lightning.pytorch.trainer.connectors.logger_connector.fx_validator import _FxValidator +from lightning.pytorch.trainer.connectors.logger_connector.result import _get_default_dtype +from lightning.pytorch.utilities import GradClipAlgorithmType +from lightning.pytorch.utilities.exceptions import MisconfigurationException +from lightning.pytorch.utilities.imports import _TORCHMETRICS_GREATER_EQUAL_0_9_1 +from lightning.pytorch.utilities.model_helpers import _restricted_classmethod +from lightning.pytorch.utilities.rank_zero import WarningCache, rank_zero_warn +from lightning.pytorch.utilities.signature_utils import is_param_in_hook_signature +from lightning.pytorch.utilities.types import ( + _METRIC, + STEP_OUTPUT, + LRSchedulerPLType, + LRSchedulerTypeUnion, + OptimizerLRScheduler, +) +from lightning_utilities.core.apply_func import apply_to_collection +from lightning_utilities.core.imports import RequirementCache +from torch import ScriptModule, Tensor +from torch.nn import Module +from torch.optim.optimizer import Optimizer +from torchmetrics import Metric, MetricCollection +from typing_extensions import Self, override + +if TYPE_CHECKING: + from torch.distributed.device_mesh import DeviceMesh + +_ONNX_AVAILABLE = RequirementCache("onnx") + +warning_cache = WarningCache() +log = logging.getLogger(__name__) + +MODULE_OPTIMIZERS = Union[ + Optimizer, LightningOptimizer, _FabricOptimizer, list[Optimizer], list[LightningOptimizer], list[_FabricOptimizer] +] + + +class LightningModule( + _DeviceDtypeModuleMixin, + HyperparametersMixin, + ModelHooks, + DataHooks, + CheckpointHooks, + Module, +): + # Below is for property support of JIT + # since none of these are important when using JIT, we are going to ignore them. + __jit_unused_properties__: list[str] = ( + [ + "example_input_array", + "on_gpu", + "current_epoch", + "global_step", + "global_rank", + "local_rank", + "logger", + "loggers", + "automatic_optimization", + "trainer", + "fabric", + "strict_loading", + "device_mesh", + ] + + _DeviceDtypeModuleMixin.__jit_unused_properties__ + + HyperparametersMixin.__jit_unused_properties__ + ) + _jit_is_scripting = False + + CHECKPOINT_HYPER_PARAMS_KEY = "hyper_parameters" + CHECKPOINT_HYPER_PARAMS_NAME = "hparams_name" + CHECKPOINT_HYPER_PARAMS_TYPE = "hparams_type" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + # pointer to the trainer object + self._trainer: Optional[pl.Trainer] = None + + # attributes that can be set by user + self._example_input_array: Optional[Union[Tensor, tuple, dict]] = None + self._automatic_optimization: bool = True + self._strict_loading: Optional[bool] = None + + # attributes used internally + self._current_fx_name: Optional[str] = None + self._param_requires_grad_state: dict[str, bool] = {} + self._metric_attributes: Optional[dict[int, str]] = None + self._compiler_ctx: Optional[dict[str, Any]] = None + + # attributes only used when using fabric + self._fabric: Optional[lf.Fabric] = None + self._fabric_optimizers: list[_FabricOptimizer] = [] + + # access to device mesh in `conigure_model()` hook + self._device_mesh: Optional[DeviceMesh] = None + + @overload + def optimizers( + self, use_pl_optimizer: Literal[True] = True + ) -> Union[LightningOptimizer, list[LightningOptimizer]]: + ... + + @overload + def optimizers(self, use_pl_optimizer: Literal[False]) -> Union[Optimizer, list[Optimizer]]: + ... + + @overload + def optimizers(self, use_pl_optimizer: bool) -> MODULE_OPTIMIZERS: + ... + + def optimizers(self, use_pl_optimizer: bool = True) -> MODULE_OPTIMIZERS: + """Returns the optimizer(s) that are being used during training. Useful for manual optimization. + + Args: + use_pl_optimizer: If ``True``, will wrap the optimizer(s) in a + :class:`~lightning.pytorch.core.optimizer.LightningOptimizer` for automatic handling of precision, + profiling, and counting of step calls for proper logging and checkpointing. It specifically wraps the + ``step`` method and custom optimizers that don't have this method are not supported. + + Returns: + A single optimizer, or a list of optimizers in case multiple ones are present. + + """ + if self._fabric: + opts: MODULE_OPTIMIZERS = self._fabric_optimizers + elif use_pl_optimizer: + opts = self.trainer.strategy._lightning_optimizers + else: + opts = self.trainer.optimizers + + # single optimizer + if ( + isinstance(opts, list) + and len(opts) == 1 + and isinstance(opts[0], (Optimizer, LightningOptimizer, _FabricOptimizer)) + ): + return opts[0] + # multiple opts + return opts + + def lr_schedulers(self) -> Union[None, list[LRSchedulerPLType], LRSchedulerPLType]: + """Returns the learning rate scheduler(s) that are being used during training. Useful for manual optimization. + + Returns: + A single scheduler, or a list of schedulers in case multiple ones are present, or ``None`` if no + schedulers were returned in :meth:`~lightning.pytorch.core.LightningModule.configure_optimizers`. + + """ + if not self.trainer.lr_scheduler_configs: + return None + + # ignore other keys "interval", "frequency", etc. + lr_schedulers: list[LRSchedulerPLType] = [config.scheduler for config in self.trainer.lr_scheduler_configs] + + # single scheduler + if len(lr_schedulers) == 1: + return lr_schedulers[0] + + # multiple schedulers + return lr_schedulers + + @property + def trainer(self) -> "pl.Trainer": + if self._fabric is not None: + return _TrainerFabricShim(fabric=self._fabric) # type: ignore[return-value] + if not self._jit_is_scripting and self._trainer is None: + raise RuntimeError(f"{self.__class__.__qualname__} is not attached to a `Trainer`.") + return self._trainer # type: ignore[return-value] + + @trainer.setter + def trainer(self, trainer: Optional["pl.Trainer"]) -> None: + for v in self.children(): + if isinstance(v, LightningModule): + v.trainer = trainer + self._trainer = trainer + + @property + def fabric(self) -> Optional["lf.Fabric"]: + return self._fabric + + @fabric.setter + def fabric(self, fabric: Optional["lf.Fabric"]) -> None: + for v in self.children(): + if isinstance(v, LightningModule): + v.fabric = fabric + if fabric is not None and not isinstance(fabric, weakref.ProxyTypes): + fabric = weakref.proxy(fabric) + self._fabric = fabric + + @property + def example_input_array(self) -> Optional[Union[Tensor, tuple, dict]]: + """The example input array is a specification of what the module can consume in the :meth:`forward` method. The + return type is interpreted as follows: + + - Single tensor: It is assumed the model takes a single argument, i.e., + ``model.forward(model.example_input_array)`` + - Tuple: The input array should be interpreted as a sequence of positional arguments, i.e., + ``model.forward(*model.example_input_array)`` + - Dict: The input array represents named keyword arguments, i.e., + ``model.forward(**model.example_input_array)`` + + """ + return self._example_input_array + + @example_input_array.setter + def example_input_array(self, example: Optional[Union[Tensor, tuple, dict]]) -> None: + self._example_input_array = example + + @property + def current_epoch(self) -> int: + """The current epoch in the ``Trainer``, or 0 if not attached.""" + return self.trainer.current_epoch if self._trainer else 0 + + @property + def global_step(self) -> int: + """Total training batches seen across all epochs. + + If no Trainer is attached, this property is 0. + + """ + return self.trainer.global_step if self._trainer else 0 + + @property + def global_rank(self) -> int: + """The index of the current process across all nodes and devices.""" + return self.trainer.global_rank if self._trainer else 0 + + @property + def local_rank(self) -> int: + """The index of the current process within a single node.""" + return self.trainer.local_rank if self._trainer else 0 + + @property + def on_gpu(self) -> bool: + """Returns ``True`` if this model is currently located on a GPU. + + Useful to set flags around the LightningModule for different CPU vs GPU behavior. + + """ + return self.device.type == "cuda" + + @property + def automatic_optimization(self) -> bool: + """If set to ``False`` you are responsible for calling ``.backward()``, ``.step()``, ``.zero_grad()``.""" + return self._automatic_optimization + + @automatic_optimization.setter + def automatic_optimization(self, automatic_optimization: bool) -> None: + self._automatic_optimization = automatic_optimization + + @property + def strict_loading(self) -> bool: + """Determines how Lightning loads this model using `.load_state_dict(..., strict=model.strict_loading)`.""" + # We use None as the default internally to determine whether the user has set a value + return self._strict_loading in (None, True) + + @strict_loading.setter + def strict_loading(self, strict_loading: bool) -> None: + self._strict_loading = strict_loading + + @property + def logger(self) -> Optional[Union[Logger, FabricLogger]]: + """Reference to the logger object in the Trainer.""" + if self._fabric is not None: + return self._fabric.logger + return self._trainer.logger if self._trainer is not None else None + + @property + def loggers(self) -> Union[list[Logger], list[FabricLogger]]: + """Reference to the list of loggers in the Trainer.""" + if self._fabric is not None: + return self._fabric.loggers + if self._trainer is not None: + return self._trainer.loggers + return [] + + @property + def device_mesh(self) -> Optional["DeviceMesh"]: + """Strategies like ``ModelParallelStrategy`` will create a device mesh that can be accessed in the + :meth:`~lightning.pytorch.core.hooks.ModelHooks.configure_model` hook to parallelize the LightningModule.""" + return self._device_mesh + + def _call_batch_hook(self, hook_name: str, *args: Any) -> Any: + trainer = self._trainer + if trainer: + datahook_selector = trainer._data_connector._datahook_selector + assert datahook_selector is not None + obj = datahook_selector.get_instance(hook_name) + if isinstance(obj, self.__class__): + trainer_method = call._call_lightning_module_hook + else: + trainer_method = call._call_lightning_datamodule_hook + + return trainer_method(trainer, hook_name, *args) + hook = getattr(self, hook_name) + return hook(*args) + + def _on_before_batch_transfer(self, batch: Any, dataloader_idx: int = 0) -> Any: + return self._call_batch_hook("on_before_batch_transfer", batch, dataloader_idx) + + def _apply_batch_transfer_handler( + self, batch: Any, device: Optional[torch.device] = None, dataloader_idx: int = 0 + ) -> Any: + device = device or self.device + batch = self._call_batch_hook("transfer_batch_to_device", batch, device, dataloader_idx) + batch = self._call_batch_hook("on_after_batch_transfer", batch, dataloader_idx) + return batch + + def print(self, *args: Any, **kwargs: Any) -> None: + r"""Prints only from process 0. Use this in any distributed mode to log only once. + + Args: + *args: The thing to print. The same as for Python's built-in print function. + **kwargs: The same as for Python's built-in print function. + + Example:: + + def forward(self, x): + self.print(x, 'in forward') + + """ + if self.trainer.is_global_zero: + progress_bar = self.trainer.progress_bar_callback + if progress_bar is not None and progress_bar.is_enabled: + progress_bar.print(*args, **kwargs) + else: + print(*args, **kwargs) + + def log( + self, + name: str, + value: _METRIC, + prog_bar: bool = False, + logger: Optional[bool] = None, + on_step: Optional[bool] = None, + on_epoch: Optional[bool] = None, + reduce_fx: Union[str, Callable] = "mean", + enable_graph: bool = False, + sync_dist: bool = False, + sync_dist_group: Optional[Any] = None, + add_dataloader_idx: bool = True, + batch_size: Optional[int] = None, + metric_attribute: Optional[str] = None, + rank_zero_only: bool = False, + ) -> None: + """Log a key, value pair. + + Example:: + + self.log('train_loss', loss) + + The default behavior per hook is documented here: :ref:`extensions/logging:Automatic Logging`. + + Args: + name: key to log. Must be identical across all processes if using DDP or any other distributed strategy. + value: value to log. Can be a ``float``, ``Tensor``, or a ``Metric``. + prog_bar: if ``True`` logs to the progress bar. + logger: if ``True`` logs to the logger. + on_step: if ``True`` logs at this step. The default value is determined by the hook. + See :ref:`extensions/logging:Automatic Logging` for details. + on_epoch: if ``True`` logs epoch accumulated metrics. The default value is determined by the hook. + See :ref:`extensions/logging:Automatic Logging` for details. + reduce_fx: reduction function over step values for end of epoch. :meth:`torch.mean` by default. + enable_graph: if ``True``, will not auto detach the graph. + sync_dist: if ``True``, reduces the metric across devices. Use with care as this may lead to a significant + communication overhead. + sync_dist_group: the DDP group to sync across. + add_dataloader_idx: if ``True``, appends the index of the current dataloader to + the name (when using multiple dataloaders). If False, user needs to give unique names for + each dataloader to not mix the values. + batch_size: Current batch_size. This will be directly inferred from the loaded batch, + but for some data structures you might need to explicitly provide it. + metric_attribute: To restore the metric state, Lightning requires the reference of the + :class:`torchmetrics.Metric` in your model. This is found automatically if it is a model attribute. + rank_zero_only: Tells Lightning if you are calling ``self.log`` from every process (default) or only from + rank 0. If ``True``, you won't be able to use this metric as a monitor in callbacks + (e.g., early stopping). Warning: Improper use can lead to deadlocks! See + :ref:`Advanced Logging ` for more details. + + """ + if self._fabric is not None: + self._log_dict_through_fabric(dictionary={name: value}, logger=logger) + return + + # check for invalid values + apply_to_collection(value, dict, self.__check_not_nested, name) + apply_to_collection( + value, object, self.__check_allowed, name, value, wrong_dtype=(numbers.Number, Metric, Tensor) + ) + + trainer = self._trainer + if trainer is None: + # not an error to support testing the `*_step` methods without a `Trainer` reference + rank_zero_warn( + "You are trying to `self.log()` but the `self.trainer` reference is not registered on the model yet." + " This is most likely because the model hasn't been passed to the `Trainer`" + ) + return + if trainer.barebones: + rank_zero_warn( + "You are trying to `self.log()` but `Trainer(barebones=True)` is configured." + " Logging can impact raw speed so it is disabled under this setting." + ) + return + results = trainer._results + if results is None: + raise MisconfigurationException( + "You are trying to `self.log()` but the loop's result collection is not registered" + " yet. This is most likely because you are trying to log in a `predict` hook," + " but it doesn't support logging" + ) + if self._current_fx_name is None: + raise MisconfigurationException( + "You are trying to `self.log()` but it is not managed by the `Trainer` control flow" + ) + + on_step, on_epoch = _FxValidator.check_logging_and_get_default_levels( + self._current_fx_name, on_step=on_step, on_epoch=on_epoch + ) + + # make sure user doesn't introduce logic for multi-dataloaders + if "/dataloader_idx_" in name: + raise MisconfigurationException( + f"You called `self.log` with the key `{name}`" + " but it should not contain information about `dataloader_idx`" + ) + + value = apply_to_collection(value, (Tensor, numbers.Number), self.__to_tensor, name) + + if trainer._logger_connector.should_reset_tensors(self._current_fx_name): + # if we started a new epoch (running its first batch) the hook name has changed + # reset any tensors for the new hook name + results.reset(metrics=False, fx=self._current_fx_name) + + if metric_attribute is None and isinstance(value, Metric): + if self._metric_attributes is None: + # compute once + self._metric_attributes = { + id(module): name for name, module in self.named_modules() if isinstance(module, Metric) + } + if not self._metric_attributes: + raise MisconfigurationException( + "Could not find the `LightningModule` attribute for the `torchmetrics.Metric` logged." + " You can fix this by setting an attribute for the metric in your `LightningModule`." + ) + # try to find the passed metric in the LightningModule + metric_attribute = self._metric_attributes.get(id(value), None) + if metric_attribute is None: + raise MisconfigurationException( + "Could not find the `LightningModule` attribute for the `torchmetrics.Metric` logged." + f" You can fix this by calling `self.log({name}, ..., metric_attribute=name)` where `name` is one" + f" of {list(self._metric_attributes.values())}" + ) + + if ( + trainer.training + and is_param_in_hook_signature(self.training_step, "dataloader_iter", explicit=True) + and batch_size is None + ): + raise MisconfigurationException( + "With `def training_step(self, dataloader_iter)`, `self.log(..., batch_size=...)` should be provided." + ) + + if logger and trainer.logger is None: + rank_zero_warn( + f"You called `self.log({name!r}, ..., logger=True)` but have no logger configured. You can enable one" + " by doing `Trainer(logger=ALogger(...))`" + ) + if logger is None: + # we could set false here if there's no configured logger, however, we still need to compute the "logged" + # metrics anyway because that's what the evaluation loops use as return value + logger = True + + results.log( + self._current_fx_name, + name, + value, + prog_bar=prog_bar, + logger=logger, + on_step=on_step, + on_epoch=on_epoch, + reduce_fx=reduce_fx, + enable_graph=enable_graph, + add_dataloader_idx=add_dataloader_idx, + batch_size=batch_size, + sync_dist=sync_dist and trainer._accelerator_connector.is_distributed, + sync_dist_fn=trainer.strategy.reduce, + sync_dist_group=sync_dist_group, + metric_attribute=metric_attribute, + rank_zero_only=rank_zero_only, + ) + + trainer._logger_connector._current_fx = self._current_fx_name + + def log_dict( + self, + dictionary: Union[Mapping[str, _METRIC], MetricCollection], + prog_bar: bool = False, + logger: Optional[bool] = None, + on_step: Optional[bool] = None, + on_epoch: Optional[bool] = None, + reduce_fx: Union[str, Callable] = "mean", + enable_graph: bool = False, + sync_dist: bool = False, + sync_dist_group: Optional[Any] = None, + add_dataloader_idx: bool = True, + batch_size: Optional[int] = None, + rank_zero_only: bool = False, + ) -> None: + """Log a dictionary of values at once. + + Example:: + + values = {'loss': loss, 'acc': acc, ..., 'metric_n': metric_n} + self.log_dict(values) + + Args: + dictionary: key value pairs. + Keys must be identical across all processes if using DDP or any other distributed strategy. + The values can be a ``float``, ``Tensor``, ``Metric``, or ``MetricCollection``. + prog_bar: if ``True`` logs to the progress base. + logger: if ``True`` logs to the logger. + on_step: if ``True`` logs at this step. + ``None`` auto-logs for training_step but not validation/test_step. + The default value is determined by the hook. + See :ref:`extensions/logging:Automatic Logging` for details. + on_epoch: if ``True`` logs epoch accumulated metrics. + ``None`` auto-logs for val/test step but not ``training_step``. + The default value is determined by the hook. + See :ref:`extensions/logging:Automatic Logging` for details. + reduce_fx: reduction function over step values for end of epoch. :meth:`torch.mean` by default. + enable_graph: if ``True``, will not auto-detach the graph + sync_dist: if ``True``, reduces the metric across GPUs/TPUs. Use with care as this may lead to a significant + communication overhead. + sync_dist_group: the ddp group to sync across. + add_dataloader_idx: if ``True``, appends the index of the current dataloader to + the name (when using multiple). If ``False``, user needs to give unique names for + each dataloader to not mix values. + batch_size: Current batch size. This will be directly inferred from the loaded batch, + but some data structures might need to explicitly provide it. + rank_zero_only: Tells Lightning if you are calling ``self.log`` from every process (default) or only from + rank 0. If ``True``, you won't be able to use this metric as a monitor in callbacks + (e.g., early stopping). Warning: Improper use can lead to deadlocks! See + :ref:`Advanced Logging ` for more details. + + """ + if self._fabric is not None: + return self._log_dict_through_fabric(dictionary=dictionary, logger=logger) + + kwargs: dict[str, bool] = {} + + if isinstance(dictionary, MetricCollection): + kwargs["keep_base"] = False + if _TORCHMETRICS_GREATER_EQUAL_0_9_1 and dictionary._enable_compute_groups: + kwargs["copy_state"] = False + + for k, v in dictionary.items(**kwargs): + self.log( + name=k, + value=v, + prog_bar=prog_bar, + logger=logger, + on_step=on_step, + on_epoch=on_epoch, + reduce_fx=reduce_fx, + enable_graph=enable_graph, + sync_dist=sync_dist, + sync_dist_group=sync_dist_group, + add_dataloader_idx=add_dataloader_idx, + batch_size=batch_size, + rank_zero_only=rank_zero_only, + ) + return None + + def _log_dict_through_fabric( + self, dictionary: Union[Mapping[str, _METRIC], MetricCollection], logger: Optional[bool] = None + ) -> None: + if logger is False: + # Passing `logger=False` with Fabric does not make much sense because there is no other destination to + # log to, but we support it in case the original code was written for Trainer use + return + + if any(isinstance(v, dict) for v in dictionary.values()): + raise ValueError(f"`self.log_dict({dictionary})` was called, but nested dictionaries cannot be logged") + for name, value in dictionary.items(): + apply_to_collection(value, object, self.__check_allowed, name, value, wrong_dtype=(numbers.Number, Tensor)) + + assert self._fabric is not None + self._fabric.log_dict(metrics=dictionary) # type: ignore[arg-type] + + @staticmethod + def __check_not_nested(value: dict, name: str) -> None: + # self-imposed restriction. for simplicity + if any(isinstance(v, dict) for v in value.values()): + raise ValueError(f"`self.log({name}, {value})` was called, but nested dictionaries cannot be logged") + + @staticmethod + def __check_allowed(v: Any, name: str, value: Any) -> None: + raise ValueError(f"`self.log({name}, {value})` was called, but `{type(v).__name__}` values cannot be logged") + + def __to_tensor(self, value: Union[Tensor, numbers.Number], name: str) -> Tensor: + value = ( + value.clone().detach() + if isinstance(value, Tensor) + else torch.tensor(value, device=self.device, dtype=_get_default_dtype()) + ) + if not torch.numel(value) == 1: + raise ValueError( + f"`self.log({name}, {value})` was called, but the tensor must have a single element." + f" You can try doing `self.log({name}, {value}.mean())`" + ) + value = value.squeeze() + return value + + def all_gather( + self, data: Union[Tensor, dict, list, tuple], group: Optional[Any] = None, sync_grads: bool = False + ) -> Union[Tensor, dict, list, tuple]: + r"""Gather tensors or collections of tensors from multiple processes. + + This method needs to be called on all processes and the tensors need to have the same shape across all + processes, otherwise your program will stall forever. + + Args: + data: int, float, tensor of shape (batch, ...), or a (possibly nested) collection thereof. + group: the process group to gather results from. Defaults to all processes (world) + sync_grads: flag that allows users to synchronize gradients for the all_gather operation + + Return: + A tensor of shape (world_size, batch, ...), or if the input was a collection + the output will also be a collection with tensors of this shape. For the special case where + world_size is 1, no additional dimension is added to the tensor(s). + + """ + group = group if group is not None else torch.distributed.group.WORLD + all_gather = self.trainer.strategy.all_gather + data = convert_to_tensors(data, device=self.device) + return apply_to_collection(data, Tensor, all_gather, group=group, sync_grads=sync_grads) + + @override + def forward(self, *args: Any, **kwargs: Any) -> Any: + r"""Same as :meth:`torch.nn.Module.forward`. + + Args: + *args: Whatever you decide to pass into the forward method. + **kwargs: Keyword arguments are also possible. + + Return: + Your model's output + + """ + return super().forward(*args, **kwargs) + + def training_step(self, *args: Any, **kwargs: Any) -> STEP_OUTPUT: + r"""Here you compute and return the training loss and some additional metrics for e.g. the progress bar or + logger. + + Args: + batch: The output of your data iterable, normally a :class:`~torch.utils.data.DataLoader`. + batch_idx: The index of this batch. + dataloader_idx: The index of the dataloader that produced this batch. + (only if multiple dataloaders used) + + Return: + - :class:`~torch.Tensor` - The loss tensor + - ``dict`` - A dictionary which can include any keys, but must include the key ``'loss'`` in the case of + automatic optimization. + - ``None`` - In automatic optimization, this will skip to the next batch (but is not supported for + multi-GPU, TPU, or DeepSpeed). For manual optimization, this has no special meaning, as returning + the loss is not required. + + In this step you'd normally do the forward pass and calculate the loss for a batch. + You can also do fancier things like multiple forward passes or something model specific. + + Example:: + + def training_step(self, batch, batch_idx): + x, y, z = batch + out = self.encoder(x) + loss = self.loss(out, x) + return loss + + To use multiple optimizers, you can switch to 'manual optimization' and control their stepping: + + .. code-block:: python + + def __init__(self): + super().__init__() + self.automatic_optimization = False + + + # Multiple optimizers (e.g.: GANs) + def training_step(self, batch, batch_idx): + opt1, opt2 = self.optimizers() + + # do training_step with encoder + ... + opt1.step() + # do training_step with decoder + ... + opt2.step() + + Note: + When ``accumulate_grad_batches`` > 1, the loss returned here will be automatically + normalized by ``accumulate_grad_batches`` internally. + + """ + rank_zero_warn("`training_step` must be implemented to be used with the Lightning Trainer") + + def validation_step(self, *args: Any, **kwargs: Any) -> STEP_OUTPUT: + r"""Operates on a single batch of data from the validation set. In this step you'd might generate examples or + calculate anything of interest like accuracy. + + Args: + batch: The output of your data iterable, normally a :class:`~torch.utils.data.DataLoader`. + batch_idx: The index of this batch. + dataloader_idx: The index of the dataloader that produced this batch. + (only if multiple dataloaders used) + + Return: + - :class:`~torch.Tensor` - The loss tensor + - ``dict`` - A dictionary. Can include any keys, but must include the key ``'loss'``. + - ``None`` - Skip to the next batch. + + .. code-block:: python + + # if you have one val dataloader: + def validation_step(self, batch, batch_idx): ... + + + # if you have multiple val dataloaders: + def validation_step(self, batch, batch_idx, dataloader_idx=0): ... + + Examples:: + + # CASE 1: A single validation dataset + def validation_step(self, batch, batch_idx): + x, y = batch + + # implement your own + out = self(x) + loss = self.loss(out, y) + + # log 6 example images + # or generated text... or whatever + sample_imgs = x[:6] + grid = torchvision.utils.make_grid(sample_imgs) + self.logger.experiment.add_image('example_images', grid, 0) + + # calculate acc + labels_hat = torch.argmax(out, dim=1) + val_acc = torch.sum(y == labels_hat).item() / (len(y) * 1.0) + + # log the outputs! + self.log_dict({'val_loss': loss, 'val_acc': val_acc}) + + If you pass in multiple val dataloaders, :meth:`validation_step` will have an additional argument. We recommend + setting the default value of 0 so that you can quickly switch between single and multiple dataloaders. + + .. code-block:: python + + # CASE 2: multiple validation dataloaders + def validation_step(self, batch, batch_idx, dataloader_idx=0): + # dataloader_idx tells you which dataset this is. + ... + + Note: + If you don't need to validate you don't need to implement this method. + + Note: + When the :meth:`validation_step` is called, the model has been put in eval mode + and PyTorch gradients have been disabled. At the end of validation, + the model goes back to training mode and gradients are enabled. + + """ + + def test_step(self, *args: Any, **kwargs: Any) -> STEP_OUTPUT: + r"""Operates on a single batch of data from the test set. In this step you'd normally generate examples or + calculate anything of interest such as accuracy. + + Args: + batch: The output of your data iterable, normally a :class:`~torch.utils.data.DataLoader`. + batch_idx: The index of this batch. + dataloader_idx: The index of the dataloader that produced this batch. + (only if multiple dataloaders used) + + Return: + - :class:`~torch.Tensor` - The loss tensor + - ``dict`` - A dictionary. Can include any keys, but must include the key ``'loss'``. + - ``None`` - Skip to the next batch. + + .. code-block:: python + + # if you have one test dataloader: + def test_step(self, batch, batch_idx): ... + + + # if you have multiple test dataloaders: + def test_step(self, batch, batch_idx, dataloader_idx=0): ... + + Examples:: + + # CASE 1: A single test dataset + def test_step(self, batch, batch_idx): + x, y = batch + + # implement your own + out = self(x) + loss = self.loss(out, y) + + # log 6 example images + # or generated text... or whatever + sample_imgs = x[:6] + grid = torchvision.utils.make_grid(sample_imgs) + self.logger.experiment.add_image('example_images', grid, 0) + + # calculate acc + labels_hat = torch.argmax(out, dim=1) + test_acc = torch.sum(y == labels_hat).item() / (len(y) * 1.0) + + # log the outputs! + self.log_dict({'test_loss': loss, 'test_acc': test_acc}) + + If you pass in multiple test dataloaders, :meth:`test_step` will have an additional argument. We recommend + setting the default value of 0 so that you can quickly switch between single and multiple dataloaders. + + .. code-block:: python + + # CASE 2: multiple test dataloaders + def test_step(self, batch, batch_idx, dataloader_idx=0): + # dataloader_idx tells you which dataset this is. + ... + + Note: + If you don't need to test you don't need to implement this method. + + Note: + When the :meth:`test_step` is called, the model has been put in eval mode and + PyTorch gradients have been disabled. At the end of the test epoch, the model goes back + to training mode and gradients are enabled. + + """ + + def predict_step(self, *args: Any, **kwargs: Any) -> Any: + """Step function called during :meth:`~lightning.pytorch.trainer.trainer.Trainer.predict`. By default, it calls + :meth:`~lightning.pytorch.core.LightningModule.forward`. Override to add any processing logic. + + The :meth:`~lightning.pytorch.core.LightningModule.predict_step` is used + to scale inference on multi-devices. + + To prevent an OOM error, it is possible to use :class:`~lightning.pytorch.callbacks.BasePredictionWriter` + callback to write the predictions to disk or database after each batch or on epoch end. + + The :class:`~lightning.pytorch.callbacks.BasePredictionWriter` should be used while using a spawn + based accelerator. This happens for ``Trainer(strategy="ddp_spawn")`` + or training on 8 TPU cores with ``Trainer(accelerator="tpu", devices=8)`` as predictions won't be returned. + + Args: + batch: The output of your data iterable, normally a :class:`~torch.utils.data.DataLoader`. + batch_idx: The index of this batch. + dataloader_idx: The index of the dataloader that produced this batch. + (only if multiple dataloaders used) + + Return: + Predicted output (optional). + + Example :: + + class MyModel(LightningModule): + + def predict_step(self, batch, batch_idx, dataloader_idx=0): + return self(batch) + + dm = ... + model = MyModel() + trainer = Trainer(accelerator="gpu", devices=2) + predictions = trainer.predict(model, dm) + + """ + # For backwards compatibility + batch = kwargs.get("batch", args[0]) + return self(batch) + + def configure_callbacks(self) -> Union[Sequence[Callback], Callback]: + """Configure model-specific callbacks. When the model gets attached, e.g., when ``.fit()`` or ``.test()`` gets + called, the list or a callback returned here will be merged with the list of callbacks passed to the Trainer's + ``callbacks`` argument. If a callback returned here has the same type as one or several callbacks already + present in the Trainer's callbacks list, it will take priority and replace them. In addition, Lightning will + make sure :class:`~lightning.pytorch.callbacks.model_checkpoint.ModelCheckpoint` callbacks run last. + + Return: + A callback or a list of callbacks which will extend the list of callbacks in the Trainer. + + Example:: + + def configure_callbacks(self): + early_stop = EarlyStopping(monitor="val_acc", mode="max") + checkpoint = ModelCheckpoint(monitor="val_loss") + return [early_stop, checkpoint] + + """ + return [] + + def configure_optimizers(self) -> OptimizerLRScheduler: + r"""Choose what optimizers and learning-rate schedulers to use in your optimization. Normally you'd need one. + But in the case of GANs or similar you might have multiple. Optimization with multiple optimizers only works in + the manual optimization mode. + + Return: + Any of these 6 options. + + - **Single optimizer**. + - **List or Tuple** of optimizers. + - **Two lists** - The first list has multiple optimizers, and the second has multiple LR schedulers + (or multiple ``lr_scheduler_config``). + - **Dictionary**, with an ``"optimizer"`` key, and (optionally) a ``"lr_scheduler"`` + key whose value is a single LR scheduler or ``lr_scheduler_config``. + - **None** - Fit will run without any optimizer. + + The ``lr_scheduler_config`` is a dictionary which contains the scheduler and its associated configuration. + The default configuration is shown below. + + .. code-block:: python + + lr_scheduler_config = { + # REQUIRED: The scheduler instance + "scheduler": lr_scheduler, + # The unit of the scheduler's step size, could also be 'step'. + # 'epoch' updates the scheduler on epoch end whereas 'step' + # updates it after a optimizer update. + "interval": "epoch", + # How many epochs/steps should pass between calls to + # `scheduler.step()`. 1 corresponds to updating the learning + # rate after every epoch/step. + "frequency": 1, + # Metric to monitor for schedulers like `ReduceLROnPlateau` + "monitor": "val_loss", + # If set to `True`, will enforce that the value specified 'monitor' + # is available when the scheduler is updated, thus stopping + # training if not found. If set to `False`, it will only produce a warning + "strict": True, + # If using the `LearningRateMonitor` callback to monitor the + # learning rate progress, this keyword can be used to specify + # a custom logged name + "name": None, + } + + When there are schedulers in which the ``.step()`` method is conditioned on a value, such as the + :class:`torch.optim.lr_scheduler.ReduceLROnPlateau` scheduler, Lightning requires that the + ``lr_scheduler_config`` contains the keyword ``"monitor"`` set to the metric name that the scheduler + should be conditioned on. + + .. testcode:: + + # The ReduceLROnPlateau scheduler requires a monitor + def configure_optimizers(self): + optimizer = Adam(...) + return { + "optimizer": optimizer, + "lr_scheduler": { + "scheduler": ReduceLROnPlateau(optimizer, ...), + "monitor": "metric_to_track", + "frequency": "indicates how often the metric is updated", + # If "monitor" references validation metrics, then "frequency" should be set to a + # multiple of "trainer.check_val_every_n_epoch". + }, + } + + + # In the case of two optimizers, only one using the ReduceLROnPlateau scheduler + def configure_optimizers(self): + optimizer1 = Adam(...) + optimizer2 = SGD(...) + scheduler1 = ReduceLROnPlateau(optimizer1, ...) + scheduler2 = LambdaLR(optimizer2, ...) + return ( + { + "optimizer": optimizer1, + "lr_scheduler": { + "scheduler": scheduler1, + "monitor": "metric_to_track", + }, + }, + {"optimizer": optimizer2, "lr_scheduler": scheduler2}, + ) + + Metrics can be made available to monitor by simply logging it using + ``self.log('metric_to_track', metric_val)`` in your :class:`~lightning.pytorch.core.LightningModule`. + + Note: + Some things to know: + + - Lightning calls ``.backward()`` and ``.step()`` automatically in case of automatic optimization. + - If a learning rate scheduler is specified in ``configure_optimizers()`` with key + ``"interval"`` (default "epoch") in the scheduler configuration, Lightning will call + the scheduler's ``.step()`` method automatically in case of automatic optimization. + - If you use 16-bit precision (``precision=16``), Lightning will automatically handle the optimizer. + - If you use :class:`torch.optim.LBFGS`, Lightning handles the closure function automatically for you. + - If you use multiple optimizers, you will have to switch to 'manual optimization' mode and step them + yourself. + - If you need to control how often the optimizer steps, override the :meth:`optimizer_step` hook. + + """ + rank_zero_warn("`configure_optimizers` must be implemented to be used with the Lightning Trainer") + + def manual_backward(self, loss: Tensor, *args: Any, **kwargs: Any) -> None: + """Call this directly from your :meth:`training_step` when doing optimizations manually. By using this, + Lightning can ensure that all the proper scaling gets applied when using mixed precision. + + See :ref:`manual optimization` for more examples. + + Example:: + + def training_step(...): + opt = self.optimizers() + loss = ... + opt.zero_grad() + # automatically applies scaling, etc... + self.manual_backward(loss) + opt.step() + + Args: + loss: The tensor on which to compute gradients. Must have a graph attached. + *args: Additional positional arguments to be forwarded to :meth:`~torch.Tensor.backward` + **kwargs: Additional keyword arguments to be forwarded to :meth:`~torch.Tensor.backward` + + """ + if self._fabric: + self._fabric.backward(loss, *args, **kwargs) + else: + self._verify_is_manual_optimization("manual_backward") + self.trainer.strategy.backward(loss, None, *args, **kwargs) + + def backward(self, loss: Tensor, *args: Any, **kwargs: Any) -> None: + """Called to perform backward on the loss returned in :meth:`training_step`. Override this hook with your own + implementation if you need to. + + Args: + loss: The loss tensor returned by :meth:`training_step`. If gradient accumulation is used, the loss here + holds the normalized value (scaled by 1 / accumulation steps). + + Example:: + + def backward(self, loss): + loss.backward() + + """ + if self._fabric: + self._fabric.backward(loss, *args, **kwargs) + else: + loss.backward(*args, **kwargs) + + def toggle_optimizer(self, optimizer: Union[Optimizer, LightningOptimizer]) -> None: + """Makes sure only the gradients of the current optimizer's parameters are calculated in the training step to + prevent dangling gradients in multiple-optimizer setup. + + It works with :meth:`untoggle_optimizer` to make sure ``param_requires_grad_state`` is properly reset. + + Args: + optimizer: The optimizer to toggle. + + """ + # Iterate over all optimizer parameters to preserve their `requires_grad` information + # in case these are pre-defined during `configure_optimizers` + param_requires_grad_state = {} + for opt in self.trainer.optimizers: + for group in opt.param_groups: + for param in group["params"]: + # If a param already appear in param_requires_grad_state, continue + if param in param_requires_grad_state: + continue + param_requires_grad_state[param] = param.requires_grad + param.requires_grad = False + + # Then iterate over the current optimizer's parameters and set its `requires_grad` + # properties accordingly + for group in optimizer.param_groups: + for param in group["params"]: + param.requires_grad = param_requires_grad_state[param] + self._param_requires_grad_state = param_requires_grad_state + + def untoggle_optimizer(self, optimizer: Union[Optimizer, LightningOptimizer]) -> None: + """Resets the state of required gradients that were toggled with :meth:`toggle_optimizer`. + + Args: + optimizer: The optimizer to untoggle. + + """ + for opt in self.trainer.optimizers: + if not (opt is optimizer or (isinstance(optimizer, LightningOptimizer) and opt is optimizer.optimizer)): + for group in opt.param_groups: + for param in group["params"]: + if param in self._param_requires_grad_state: + param.requires_grad = self._param_requires_grad_state[param] + # save memory + self._param_requires_grad_state = {} + + @contextmanager + def toggled_optimizer(self, optimizer: Union[Optimizer, LightningOptimizer]) -> Generator: + """Makes sure only the gradients of the current optimizer's parameters are calculated in the training step to + prevent dangling gradients in multiple-optimizer setup. Combines :meth:`toggle_optimizer` and + :meth:`untoggle_optimizer` into context manager. + + Args: + optimizer: The optimizer to toggle. + + Example:: + + def training_step(...): + opt = self.optimizers() + with self.toggled_optimizer(opt): + loss = ... + opt.zero_grad() + self.manual_backward(loss) + opt.step() + + """ + self.toggle_optimizer(optimizer) + try: + yield + finally: + self.untoggle_optimizer(optimizer) + + def clip_gradients( + self, + optimizer: Optimizer, + gradient_clip_val: Optional[Union[int, float]] = None, + gradient_clip_algorithm: Optional[str] = None, + ) -> None: + """Handles gradient clipping internally. + + Note: + - Do not override this method. If you want to customize gradient clipping, consider using + :meth:`configure_gradient_clipping` method. + - For manual optimization (``self.automatic_optimization = False``), if you want to use + gradient clipping, consider calling + ``self.clip_gradients(opt, gradient_clip_val=0.5, gradient_clip_algorithm="norm")`` + manually in the training step. + + Args: + optimizer: Current optimizer being used. + gradient_clip_val: The value at which to clip gradients. + gradient_clip_algorithm: The gradient clipping algorithm to use. Pass ``gradient_clip_algorithm="value"`` + to clip by value, and ``gradient_clip_algorithm="norm"`` to clip by norm. + + """ + + if self.fabric is not None: + self.fabric.clip_gradients( + self, + optimizer, + clip_val=gradient_clip_val if gradient_clip_algorithm == GradClipAlgorithmType.VALUE else None, + max_norm=None if gradient_clip_algorithm == GradClipAlgorithmType.VALUE else gradient_clip_val, + ) + return + + if gradient_clip_val is None: + gradient_clip_val = self.trainer.gradient_clip_val or 0.0 + elif self.trainer.gradient_clip_val is not None and self.trainer.gradient_clip_val != gradient_clip_val: + raise MisconfigurationException( + f"You have set `Trainer(gradient_clip_val={self.trainer.gradient_clip_val!r})`" + f" and have passed `clip_gradients(gradient_clip_val={gradient_clip_val!r})`." + " Please use only one of them." + ) + + if gradient_clip_algorithm is None: + gradient_clip_algorithm = self.trainer.gradient_clip_algorithm or "norm" + else: + gradient_clip_algorithm = gradient_clip_algorithm.lower() + if ( + self.trainer.gradient_clip_algorithm is not None + and self.trainer.gradient_clip_algorithm != gradient_clip_algorithm + ): + raise MisconfigurationException( + f"You have set `Trainer(gradient_clip_algorithm={self.trainer.gradient_clip_algorithm.value!r})`" + f" and have passed `clip_gradients(gradient_clip_algorithm={gradient_clip_algorithm!r})" + " Please use only one of them." + ) + + if not isinstance(gradient_clip_val, (int, float)): + raise TypeError(f"`gradient_clip_val` should be an int or a float. Got {gradient_clip_val}.") + + if not GradClipAlgorithmType.supported_type(gradient_clip_algorithm.lower()): + raise MisconfigurationException( + f"`gradient_clip_algorithm` {gradient_clip_algorithm} is invalid." + f" Allowed algorithms: {GradClipAlgorithmType.supported_types()}." + ) + + gradient_clip_algorithm = GradClipAlgorithmType(gradient_clip_algorithm) + self.trainer.precision_plugin.clip_gradients(optimizer, gradient_clip_val, gradient_clip_algorithm) + + def configure_gradient_clipping( + self, + optimizer: Optimizer, + gradient_clip_val: Optional[Union[int, float]] = None, + gradient_clip_algorithm: Optional[str] = None, + ) -> None: + """Perform gradient clipping for the optimizer parameters. Called before :meth:`optimizer_step`. + + Args: + optimizer: Current optimizer being used. + gradient_clip_val: The value at which to clip gradients. By default, value passed in Trainer + will be available here. + gradient_clip_algorithm: The gradient clipping algorithm to use. By default, value + passed in Trainer will be available here. + + Example:: + + def configure_gradient_clipping(self, optimizer, gradient_clip_val, gradient_clip_algorithm): + # Implement your own custom logic to clip gradients + # You can call `self.clip_gradients` with your settings: + self.clip_gradients( + optimizer, + gradient_clip_val=gradient_clip_val, + gradient_clip_algorithm=gradient_clip_algorithm + ) + + """ + self.clip_gradients( + optimizer, gradient_clip_val=gradient_clip_val, gradient_clip_algorithm=gradient_clip_algorithm + ) + + def lr_scheduler_step(self, scheduler: LRSchedulerTypeUnion, metric: Optional[Any]) -> None: + r"""Override this method to adjust the default way the :class:`~lightning.pytorch.trainer.trainer.Trainer` calls + each scheduler. By default, Lightning calls ``step()`` and as shown in the example for each scheduler based on + its ``interval``. + + Args: + scheduler: Learning rate scheduler. + metric: Value of the monitor used for schedulers like ``ReduceLROnPlateau``. + + Examples:: + + # DEFAULT + def lr_scheduler_step(self, scheduler, metric): + if metric is None: + scheduler.step() + else: + scheduler.step(metric) + + # Alternative way to update schedulers if it requires an epoch value + def lr_scheduler_step(self, scheduler, metric): + scheduler.step(epoch=self.current_epoch) + + """ + if metric is None: + scheduler.step() # type: ignore[call-arg] + else: + scheduler.step(metric) + + def optimizer_step( + self, + epoch: int, + batch_idx: int, + optimizer: Union[Optimizer, LightningOptimizer], + optimizer_closure: Optional[Callable[[], Any]] = None, + ) -> None: + r"""Override this method to adjust the default way the :class:`~lightning.pytorch.trainer.trainer.Trainer` calls + the optimizer. + + By default, Lightning calls ``step()`` and ``zero_grad()`` as shown in the example. + This method (and ``zero_grad()``) won't be called during the accumulation phase when + ``Trainer(accumulate_grad_batches != 1)``. Overriding this hook has no benefit with manual optimization. + + Args: + epoch: Current epoch + batch_idx: Index of current batch + optimizer: A PyTorch optimizer + optimizer_closure: The optimizer closure. This closure must be executed as it includes the + calls to ``training_step()``, ``optimizer.zero_grad()``, and ``backward()``. + + Examples:: + + def optimizer_step(self, epoch, batch_idx, optimizer, optimizer_closure): + # Add your custom logic to run directly before `optimizer.step()` + + optimizer.step(closure=optimizer_closure) + + # Add your custom logic to run directly after `optimizer.step()` + + """ + optimizer.step(closure=optimizer_closure) + + def optimizer_zero_grad(self, epoch: int, batch_idx: int, optimizer: Optimizer) -> None: + """Override this method to change the default behaviour of ``optimizer.zero_grad()``. + + Args: + epoch: Current epoch + batch_idx: Index of current batch + optimizer: A PyTorch optimizer + + Examples:: + + # DEFAULT + def optimizer_zero_grad(self, epoch, batch_idx, optimizer): + optimizer.zero_grad() + + # Set gradients to `None` instead of zero to improve performance (not required on `torch>=2.0.0`). + def optimizer_zero_grad(self, epoch, batch_idx, optimizer): + optimizer.zero_grad(set_to_none=True) + + See :meth:`torch.optim.Optimizer.zero_grad` for the explanation of the above example. + + """ + optimizer.zero_grad() + + def freeze(self) -> None: + r"""Freeze all params for inference. + + Example:: + + model = MyLightningModule(...) + model.freeze() + + """ + for param in self.parameters(): + param.requires_grad = False + + self.eval() + + def unfreeze(self) -> None: + """Unfreeze all parameters for training. + + .. code-block:: python + + model = MyLightningModule(...) + model.unfreeze() + + """ + for param in self.parameters(): + param.requires_grad = True + + self.train() + + def _verify_is_manual_optimization(self, fn_name: str) -> None: + if self.automatic_optimization: + raise MisconfigurationException( + f"to use {fn_name}, please disable automatic optimization:" + " set model property `automatic_optimization` as False" + ) + + @torch.no_grad() + def to_onnx(self, file_path: Union[str, Path, BytesIO], input_sample: Optional[Any] = None, **kwargs: Any) -> None: + """Saves the model in ONNX format. + + Args: + file_path: The path of the file the onnx model should be saved to. + input_sample: An input for tracing. Default: None (Use self.example_input_array) + **kwargs: Will be passed to torch.onnx.export function. + + Example:: + + class SimpleModel(LightningModule): + def __init__(self): + super().__init__() + self.l1 = torch.nn.Linear(in_features=64, out_features=4) + + def forward(self, x): + return torch.relu(self.l1(x.view(x.size(0), -1) + + model = SimpleModel() + input_sample = torch.randn(1, 64) + model.to_onnx("export.onnx", input_sample, export_params=True) + + """ + if not _ONNX_AVAILABLE: + raise ModuleNotFoundError(f"`{type(self).__name__}.to_onnx()` requires `onnx` to be installed.") + + mode = self.training + + if input_sample is None: + if self.example_input_array is None: + raise ValueError( + "Could not export to ONNX since neither `input_sample` nor" + " `model.example_input_array` attribute is set." + ) + input_sample = self.example_input_array + + input_sample = self._on_before_batch_transfer(input_sample) + input_sample = self._apply_batch_transfer_handler(input_sample) + + file_path = str(file_path) if isinstance(file_path, Path) else file_path + # PyTorch (2.5) declares file_path to be str | PathLike[Any] | None, but + # BytesIO does work, too. + torch.onnx.export(self, input_sample, file_path, **kwargs) # type: ignore + self.train(mode) + + @torch.no_grad() + def to_torchscript( + self, + file_path: Optional[Union[str, Path]] = None, + method: Optional[str] = "script", + example_inputs: Optional[Any] = None, + **kwargs: Any, + ) -> Union[ScriptModule, dict[str, ScriptModule]]: + """By default compiles the whole model to a :class:`~torch.jit.ScriptModule`. If you want to use tracing, + please provided the argument ``method='trace'`` and make sure that either the `example_inputs` argument is + provided, or the model has :attr:`example_input_array` set. If you would like to customize the modules that are + scripted you should override this method. In case you want to return multiple modules, we recommend using a + dictionary. + + Args: + file_path: Path where to save the torchscript. Default: None (no file saved). + method: Whether to use TorchScript's script or trace method. Default: 'script' + example_inputs: An input to be used to do tracing when method is set to 'trace'. + Default: None (uses :attr:`example_input_array`) + **kwargs: Additional arguments that will be passed to the :func:`torch.jit.script` or + :func:`torch.jit.trace` function. + + Note: + - Requires the implementation of the + :meth:`~lightning.pytorch.core.LightningModule.forward` method. + - The exported script will be set to evaluation mode. + - It is recommended that you install the latest supported version of PyTorch + to use this feature without limitations. See also the :mod:`torch.jit` + documentation for supported features. + + Example:: + + class SimpleModel(LightningModule): + def __init__(self): + super().__init__() + self.l1 = torch.nn.Linear(in_features=64, out_features=4) + + def forward(self, x): + return torch.relu(self.l1(x.view(x.size(0), -1))) + + model = SimpleModel() + model.to_torchscript(file_path="model.pt") + + torch.jit.save(model.to_torchscript( + file_path="model_trace.pt", method='trace', example_inputs=torch.randn(1, 64)) + ) + + Return: + This LightningModule as a torchscript, regardless of whether `file_path` is + defined or not. + + """ + mode = self.training + + if method == "script": + with _jit_is_scripting(): + torchscript_module = torch.jit.script(self.eval(), **kwargs) + elif method == "trace": + # if no example inputs are provided, try to see if model has example_input_array set + if example_inputs is None: + if self.example_input_array is None: + raise ValueError( + "Choosing method=`trace` requires either `example_inputs`" + " or `model.example_input_array` to be defined." + ) + example_inputs = self.example_input_array + + if kwargs.get("check_inputs") is not None: + kwargs["check_inputs"] = self._on_before_batch_transfer(kwargs["check_inputs"]) + kwargs["check_inputs"] = self._apply_batch_transfer_handler(kwargs["check_inputs"]) + + # automatically send example inputs to the right device and use trace + example_inputs = self._on_before_batch_transfer(example_inputs) + example_inputs = self._apply_batch_transfer_handler(example_inputs) + with _jit_is_scripting(): + torchscript_module = torch.jit.trace(func=self.eval(), example_inputs=example_inputs, **kwargs) + else: + raise ValueError(f"The 'method' parameter only supports 'script' or 'trace', but value given was: {method}") + + self.train(mode) + + if file_path is not None: + fs = get_filesystem(file_path) + with fs.open(file_path, "wb") as f: + torch.jit.save(torchscript_module, f) + + return torchscript_module + + @_restricted_classmethod + def load_from_checkpoint( + cls, + checkpoint_path: Union[_PATH, IO], + map_location: _MAP_LOCATION_TYPE = None, + hparams_file: Optional[_PATH] = None, + strict: Optional[bool] = None, + **kwargs: Any, + ) -> Self: + r"""Primary way of loading a model from a checkpoint. When Lightning saves a checkpoint it stores the arguments + passed to ``__init__`` in the checkpoint under ``"hyper_parameters"``. + + Any arguments specified through \*\*kwargs will override args stored in ``"hyper_parameters"``. + + Args: + checkpoint_path: Path to checkpoint. This can also be a URL, or file-like object + map_location: + If your checkpoint saved a GPU model and you now load on CPUs + or a different number of GPUs, use this to map to the new setup. + The behaviour is the same as in :func:`torch.load`. + hparams_file: Optional path to a ``.yaml`` or ``.csv`` file with hierarchical structure + as in this example:: + + drop_prob: 0.2 + dataloader: + batch_size: 32 + + You most likely won't need this since Lightning will always save the hyperparameters + to the checkpoint. + However, if your checkpoint weights don't have the hyperparameters saved, + use this method to pass in a ``.yaml`` file with the hparams you'd like to use. + These will be converted into a :class:`~dict` and passed into your + :class:`LightningModule` for use. + + If your model's ``hparams`` argument is :class:`~argparse.Namespace` + and ``.yaml`` file has hierarchical structure, you need to refactor your model to treat + ``hparams`` as :class:`~dict`. + strict: Whether to strictly enforce that the keys in :attr:`checkpoint_path` match the keys + returned by this module's state dict. Defaults to ``True`` unless ``LightningModule.strict_loading`` is + set, in which case it defaults to the value of ``LightningModule.strict_loading``. + \**kwargs: Any extra keyword args needed to init the model. Can also be used to override saved + hyperparameter values. + + Return: + :class:`LightningModule` instance with loaded weights and hyperparameters (if available). + + Note: + ``load_from_checkpoint`` is a **class** method. You should use your :class:`LightningModule` + **class** to call it instead of the :class:`LightningModule` instance, or a + ``TypeError`` will be raised. + + Note: + To ensure all layers can be loaded from the checkpoint, this function will call + :meth:`~lightning.pytorch.core.hooks.ModelHooks.configure_model` directly after instantiating the + model if this hook is overridden in your LightningModule. However, note that ``load_from_checkpoint`` does + not support loading sharded checkpoints, and you may run out of memory if the model is too large. In this + case, consider loading through the Trainer via ``.fit(ckpt_path=...)``. + + Example:: + + # load weights without mapping ... + model = MyLightningModule.load_from_checkpoint('path/to/checkpoint.ckpt') + + # or load weights mapping all weights from GPU 1 to GPU 0 ... + map_location = {'cuda:1':'cuda:0'} + model = MyLightningModule.load_from_checkpoint( + 'path/to/checkpoint.ckpt', + map_location=map_location + ) + + # or load weights and hyperparameters from separate files. + model = MyLightningModule.load_from_checkpoint( + 'path/to/checkpoint.ckpt', + hparams_file='/path/to/hparams_file.yaml' + ) + + # override some of the params with new values + model = MyLightningModule.load_from_checkpoint( + PATH, + num_layers=128, + pretrained_ckpt_path=NEW_PATH, + ) + + # predict + pretrained_model.eval() + pretrained_model.freeze() + y_hat = pretrained_model(x) + + """ + loaded = _load_from_checkpoint( + cls, + checkpoint_path, + map_location, + hparams_file, + strict, + **kwargs, + ) + return cast(Self, loaded) + + @override + def __getstate__(self) -> dict[str, Any]: + state = dict(self.__dict__) + state["_trainer"] = None + return state + + +@contextmanager +def _jit_is_scripting() -> Generator: + """Workaround for https://github.com/pytorch/pytorch/issues/67146.""" + LightningModule._jit_is_scripting = True + try: + yield + finally: + LightningModule._jit_is_scripting = False + + +class _TrainerFabricShim: + """Intercepts attribute access on LightningModule's trainer reference and redirects it to the Fabric object.""" + + def __init__(self, fabric: lf.Fabric) -> None: + super().__init__() + self._fabric = fabric + + def __getattr__(self, item: Any) -> Any: + try: + return getattr(self._fabric, item) + except AttributeError: + raise AttributeError( + f"Your LightningModule code tried to access `self.trainer.{item}` but this attribute is not available" + f" when using Fabric with a LightningModule." + ) diff --git a/application/jobs/stamp/app/custom/modeling/registry.py b/application/jobs/stamp/app/custom/modeling/registry.py new file mode 100644 index 00000000..e826cadd --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/registry.py @@ -0,0 +1,34 @@ +from enum import StrEnum +from typing import Sequence, Type, TypedDict + +import lightning + +from .lightning_model import LitVisionTransformer +from .mlp_classifier import LitMLPClassifier + + +class ModelName(StrEnum): + """Enum for available model names.""" + + VIT = "vit" + MLP = "mlp" + + +class ModelInfo(TypedDict): + """A dictionary to map a model to supported feature types. For example, + a linear classifier is not compatible with tile-evel feats.""" + + model_class: Type[lightning.LightningModule] + supported_features: Sequence[str] + + +MODEL_REGISTRY: dict[ModelName, ModelInfo] = { + ModelName.VIT: { + "model_class": LitVisionTransformer, + "supported_features": LitVisionTransformer.supported_features, + }, + ModelName.MLP: { + "model_class": LitMLPClassifier, + "supported_features": LitMLPClassifier.supported_features, + }, +} diff --git a/application/jobs/stamp/app/custom/modeling/vision_transformer.py b/application/jobs/stamp/app/custom/modeling/vision_transformer.py index 3a710960..3853c40b 100755 --- a/application/jobs/stamp/app/custom/modeling/vision_transformer.py +++ b/application/jobs/stamp/app/custom/modeling/vision_transformer.py @@ -6,12 +6,13 @@ from typing import assert_never, cast import torch -from alibi import MultiHeadALiBi from beartype import beartype from einops import repeat from jaxtyping import Bool, Float, jaxtyped from torch import Tensor, nn +from .alibi import MultiHeadALiBi + def feed_forward( dim: int, From e005b37b03289aed07eb44b69c3493d576b06bc7 Mon Sep 17 00:00:00 2001 From: Ultimate-Storm Date: Wed, 30 Jul 2025 06:28:13 +0200 Subject: [PATCH 100/109] chore: update apt versions in Dockerfile_ODELIA --- docker_config/Dockerfile_ODELIA | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 577fd58d..4c94781d 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -27,7 +27,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.2-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.2-1~ubuntu.22.04~jammy docker-ce=5:28.3.2-1~ubuntu.22.04~jammy docker-compose-plugin=2.38.2-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.15 git=1:2.34.1-1ubuntu1.15 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.4 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.4 perl-modules-5.34=5.34.0-3ubuntu1.4 perl=5.34.0-3ubuntu1.4 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 +RUN apt install -y apparmor=3.0.4-2ubuntu2.4 containerd.io=1.7.27-1 dbus-user-session=1.12.20-2ubuntu4.1 dbus=1.12.20-2ubuntu4.1 dmsetup=2:1.02.175-2.1ubuntu5 docker-buildx-plugin=0.26.1-1~ubuntu.22.04~jammy docker-ce-cli=5:28.3.3-1~ubuntu.22.04~jammy docker-ce-rootless-extras=5:28.3.3-1~ubuntu.22.04~jammy docker-ce=5:28.3.3-1~ubuntu.22.04~jammy docker-compose-plugin=2.39.1-1~ubuntu.22.04~jammy gir1.2-glib-2.0=1.72.0-1 git-man=1:2.34.1-1ubuntu1.15 git=1:2.34.1-1ubuntu1.15 iptables=1.8.7-1ubuntu5.2 less=590-1ubuntu0.22.04.3 libapparmor1=3.0.4-2ubuntu2.4 libargon2-1=0~20171227-0.3 libbsd0=0.11.5-1 libcbor0.8=0.8.0-2ubuntu1 libcryptsetup12=2:2.4.3-1ubuntu1.3 libcurl3-gnutls=7.81.0-1ubuntu1.20 libdbus-1-3=1.12.20-2ubuntu4.1 libdevmapper1.02.1=2:1.02.175-2.1ubuntu5 libedit2=3.1-20210910-1build1 liberror-perl=0.17029-1 libfido2-1=1.10.0-1 libgdbm-compat4=1.23-1 libgdbm6=1.23-1 libgirepository-1.0-1=1.72.0-1 libglib2.0-0=2.72.4-0ubuntu2.5 libglib2.0-data=2.72.4-0ubuntu2.5 libicu70=70.1-2 libip4tc2=1.8.7-1ubuntu5.2 libip6tc2=1.8.7-1ubuntu5.2 libjson-c5=0.15-3~ubuntu1.22.04.2 libkmod2=29-1ubuntu1 libltdl7=2.4.6-15build2 libmd0=1.0.4-1build1 libmnl0=1.0.4-3build2 libnetfilter-conntrack3=1.0.9-1 libnfnetlink0=1.0.1-3build3 libnftnl11=1.2.1-1build1 libnss-systemd=249.11-0ubuntu3.16 libpam-systemd=249.11-0ubuntu3.16 libperl5.34=5.34.0-3ubuntu1.5 libslirp0=4.6.1-1build1 libx11-6=2:1.7.5-1ubuntu0.3 libx11-data=2:1.7.5-1ubuntu0.3 libxau6=1:1.0.9-1build5 libxcb1=1.14-3ubuntu3 libxdmcp6=1:1.1.3-0ubuntu5 libxext6=2:1.3.4-1build1 libxml2=2.9.13+dfsg-1ubuntu0.7 libxmuu1=2:1.1.3-3 libxtables12=1.8.7-1ubuntu5.2 netbase=6.3 networkd-dispatcher=2.1-2ubuntu0.22.04.2 openssh-client=1:8.9p1-3ubuntu0.13 patch=2.7.6-7build2 perl-base=5.34.0-3ubuntu1.5 perl-modules-5.34=5.34.0-3ubuntu1.5 perl=5.34.0-3ubuntu1.5 pigz=2.6-1 python3-dbus=1.2.18-3build1 python3-gi=3.42.1-0ubuntu1 shared-mime-info=2.1-2 slirp4netns=1.0.1-2 systemd-sysv=249.11-0ubuntu3.16 systemd-timesyncd=249.11-0ubuntu3.16 systemd=249.11-0ubuntu3.16 xauth=1:1.1-1build2 xdg-user-dirs=0.17-2ubuntu4 xz-utils=5.2.5-2ubuntu1 # Clean up apt cache RUN rm -rf /var/lib/apt/lists/* From 3a73505cf34430c6fd47916bb230b95718d923fa Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 30 Jul 2025 11:21:48 +0200 Subject: [PATCH 101/109] fix: update max_epochs parameter to 64 for improved training duration Signed-off-by: GitHub CI --- application/jobs/stamp/app/custom/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/jobs/stamp/app/custom/main.py b/application/jobs/stamp/app/custom/main.py index 18f73398..107cf0de 100644 --- a/application/jobs/stamp/app/custom/main.py +++ b/application/jobs/stamp/app/custom/main.py @@ -574,7 +574,7 @@ def main(): num_workers=15, # Training parameters batch_size=64, - max_epochs=1, # 64 + max_epochs=64, # 64 patience=16, accelerator="gpu", # Experimental features From 618444bf7240641834d88654323d7728c08dee92 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Wed, 30 Jul 2025 11:24:35 +0200 Subject: [PATCH 102/109] fix: remove unnecessary line breaks in APT installation commands Signed-off-by: GitHub CI --- docker_config/Dockerfile_ODELIA | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 118bec0f..2d4d6ef9 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -13,10 +13,6 @@ ENV PYTHON_VERSION=3.10.14 RUN apt update RUN apt install \ - \ - \ - \ - \ -y \ apt=2.4.14 \ apt-utils=2.4.14 \ @@ -24,10 +20,6 @@ RUN apt install \ # Update versions of installed packages RUN apt install \ - \ - \ - \ - \ -y \ base-files=12ubuntu4.7 \ bash=5.1-6ubuntu1.1 \ @@ -71,10 +63,6 @@ RUN apt install \ # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions RUN apt install \ - \ - \ - \ - \ -y \ apt-transport-https=2.4.14 \ curl=7.81.0-1ubuntu1.20 \ @@ -130,10 +118,6 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions RUN apt install \ - \ - \ - \ - \ -y \ apparmor=3.0.4-2ubuntu2.4 \ containerd.io=1.7.27-1 \ @@ -348,14 +332,12 @@ WORKDIR /workspace/ COPY ./MediSwarm/docker_config/NVFlare /workspace/nvflare ## use startup kit template in the dashboard COPY ./MediSwarm/docker_config/master_template.yml /workspace/nvflare/nvflare/lighter/impl/ -RUN python3 -m pip install \ - /workspace/nvflare +RUN python3 -m pip install /workspace/nvflare RUN rm -rf /workspace/nvflare # Install the ODELIA controller package from local source COPY ./MediSwarm/controller /workspace/controller -RUN python3 -m pip install \ - /workspace/controller +RUN python3 -m pip install /workspace/controller RUN rm -rf /workspace/controller # Copy the source code for local training and deploying to the swarm From ee53d3ac87c1cd50252d68712cdb31e94a756be8 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 30 Jul 2025 13:27:08 +0200 Subject: [PATCH 103/109] keep argument -y in same line as RUN apt install adapted scripts for easier handling of 'RUN apt install -y' commands --- docker_config/Dockerfile_ODELIA | 12 ++---- .../dockerfile_update_addAptVersionNumbers.py | 40 ++++++++++--------- .../dockerfile_update_removeVersionApt.py | 28 +++++++------ 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 2d4d6ef9..9208e15b 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -12,15 +12,13 @@ ENV PYTHON_VERSION=3.10.14 # Install updates of installed packages RUN apt update -RUN apt install \ - -y \ +RUN apt install -y \ apt=2.4.14 \ apt-utils=2.4.14 \ libapt-pkg6.0=2.4.14 # Update versions of installed packages -RUN apt install \ - -y \ +RUN apt install -y \ base-files=12ubuntu4.7 \ bash=5.1-6ubuntu1.1 \ bsdutils=1:2.37.2-4ubuntu3.4 \ @@ -62,8 +60,7 @@ RUN apt install \ util-linux=2.37.2-4ubuntu3.4 # Install apt-transport-https curl gnupg lsb-release zip and dependencies at defined versions -RUN apt install \ - -y \ +RUN apt install -y \ apt-transport-https=2.4.14 \ curl=7.81.0-1ubuntu1.20 \ dirmngr=2.2.27-3ubuntu2.4 \ @@ -117,8 +114,7 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings && apt update # Install docker-ce docker-ce-cli containerd.io and dependencies at fixed versions -RUN apt install \ - -y \ +RUN apt install -y \ apparmor=3.0.4-2ubuntu2.4 \ containerd.io=1.7.27-1 \ dbus-user-session=1.12.20-2ubuntu4.1 \ diff --git a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py index c174dfc3..186fc955 100755 --- a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py +++ b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py @@ -3,6 +3,11 @@ import re import sys +LINE_BREAK_IN_COMMAND = ' \\\n ' +LINE_BREAK_REPLACEMENT = ' λινε βρεακ ρεπλαζεμεντ ' +APT_INSTALL_COMMAND = 'RUN apt install -y' +APT_INSTALL_REPLACEMENT = 'ΡΥΝ απτ ινσταλλ -υ' + def load_file(filename: str) -> str: with open(filename, 'r') as infile: return infile.read() @@ -14,38 +19,34 @@ def save_file(contents: str, filename: str) -> None: def parse_apt_versions(installlog: str) -> dict: versions = {} - pattern = re.compile(r'Get:.*? ([a-z0-9\-\+\.]+)(?:/[^ ]*)? ([0-9a-zA-Z\:\~\.\+\-]+) ') for line in installlog.splitlines(): - match = pattern.search(line) - if match: - package = match.group(1) - version = match.group(2) - if package in versions and versions[package] != version: - print(f'Conflicting versions of {package} found: {versions[package]} and {version} found, using the latter.') - versions[package] = version + if re.match('.*Get:[0-9]* http.*', line): + blocks = line.split(' ') + if len(blocks) > 9: + package = blocks[6] + version = blocks[8] + if package in versions and versions[package] != version: + print(f'Conflicting versions of {package} found: {versions[package]} and {version} found, using the latter.') + versions[package] = version return versions - def add_apt_versions(dockerfile: str, versions: dict) -> str: - dockerfile = dockerfile.replace('RUN apt install', 'RUN_apt_install') + dockerfile = dockerfile.replace(LINE_BREAK_IN_COMMAND, LINE_BREAK_REPLACEMENT) + dockerfile = dockerfile.replace(APT_INSTALL_COMMAND, APT_INSTALL_REPLACEMENT) outlines = [] for line in dockerfile.splitlines(): - if line.startswith('RUN_apt_install'): + if line.startswith(APT_INSTALL_REPLACEMENT): outline = '' + line for package, version in versions.items(): outline = outline.replace(f' {package} ', f' {package}={version} ') outline = re.sub(f' {package}$', f' {package}={version}', outline) - parts = outline.split() - if len(parts) > 3: - header = " ".join(parts[:3]) - pkgs = parts[3:] - outline = header + " \\\n " + " \\\n ".join(pkgs) outlines.append(outline) else: outlines.append(line) dockerfile = '\n'.join(outlines) + '\n' - dockerfile = dockerfile.replace('RUN_apt_install', 'RUN apt install') + dockerfile = dockerfile.replace(APT_INSTALL_REPLACEMENT, APT_INSTALL_COMMAND) + dockerfile = dockerfile.replace(LINE_BREAK_REPLACEMENT, LINE_BREAK_IN_COMMAND) return dockerfile @@ -58,7 +59,10 @@ def report_non_fixed_versions(dockerfile: str, versions: dict) -> None: if __name__ == '__main__': dockerfile = load_file(sys.argv[1]) installlog = load_file(sys.argv[2]) + if LINE_BREAK_REPLACEMENT in dockerfile or APT_INSTALL_REPLACEMENT in dockerfile: + raise Exception('Line break replacement {LINE_BREAK_REPLACEMENT} or apt command replacement {APT_INSTALL_REPLACEMENT} in Dockerfile, cannot process it.') + versions = parse_apt_versions(installlog) report_non_fixed_versions(dockerfile, versions) dockerfile = add_apt_versions(dockerfile, versions) - save_file(dockerfile, sys.argv[1]) \ No newline at end of file + save_file(dockerfile, sys.argv[1]) diff --git a/scripts/dev_utils/dockerfile_update_removeVersionApt.py b/scripts/dev_utils/dockerfile_update_removeVersionApt.py index a067578d..7f87aa21 100755 --- a/scripts/dev_utils/dockerfile_update_removeVersionApt.py +++ b/scripts/dev_utils/dockerfile_update_removeVersionApt.py @@ -3,6 +3,9 @@ import re import sys +LINE_BREAK_IN_COMMAND = ' \\\n ' +LINE_BREAK_REPLACEMENT = ' λινε βρεακ ρεπλαζεμεντ ' + def load_file(filename: str) -> str: with open(filename, 'r') as infile: return infile.read() @@ -12,23 +15,22 @@ def save_file(contents: str, filename: str) -> None: outfile.write(contents) -def remove_apt_versions(dockerfile: str) -> str: +def remove_apt_versions(contents: str) -> str: + contents = contents.replace(LINE_BREAK_IN_COMMAND, LINE_BREAK_REPLACEMENT) output = [] - for line in dockerfile.splitlines(): - if line.startswith('RUN apt install'): + for line in contents.splitlines(): + if line.startswith('RUN apt install -y'): out_line = re.sub('=[^ ]*', '', line) - parts = out_line.split() - if len(parts) > 3: - header = " ".join(parts[:3]) - pkgs = parts[3:] - out_line = header + " \\\n " + " \\\n ".join(pkgs) output.append(out_line) else: output.append(line) - return '\n'.join(output) - + output = '\n'.join(output) + '\n' + output = output.replace(LINE_BREAK_REPLACEMENT, LINE_BREAK_IN_COMMAND) + return output if __name__ == '__main__': - dockerfile = load_file(sys.argv[1]) - dockerfile = remove_apt_versions(dockerfile) - save_file(dockerfile, sys.argv[1]) \ No newline at end of file + contents = load_file(sys.argv[1]) + if LINE_BREAK_REPLACEMENT in contents: + raise Exception('Line break replacement {LINE_BREAK_REPLACEMENT} in Dockerfile, cannot process it.') + contents = remove_apt_versions(contents) + save_file(contents, sys.argv[1]) From 9bbb0559f01cc53dbfc9ba73acecd1a5987a977d Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 30 Jul 2025 14:33:18 +0200 Subject: [PATCH 104/109] updated apt version numbers --- docker_config/Dockerfile_ODELIA | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docker_config/Dockerfile_ODELIA b/docker_config/Dockerfile_ODELIA index 9208e15b..83253588 100644 --- a/docker_config/Dockerfile_ODELIA +++ b/docker_config/Dockerfile_ODELIA @@ -53,7 +53,7 @@ RUN apt install -y \ libtasn1-6=4.18.0-4ubuntu0.1 \ libudev1=249.11-0ubuntu3.16 \ libuuid1=2.37.2-4ubuntu3.4 \ - linux-libc-dev=5.15.0-144.157 \ + linux-libc-dev=5.15.0-151.161 \ logsave=1.46.5-2ubuntu1.2 \ mount=2.37.2-4ubuntu3.4 \ openssl=3.0.2-0ubuntu1.19 \ @@ -93,7 +93,7 @@ RUN apt install -y \ libsasl2-2=2.1.27+dfsg2-3ubuntu1.2 \ libsasl2-modules-db=2.1.27+dfsg2-3ubuntu1.2 \ libsasl2-modules=2.1.27+dfsg2-3ubuntu1.2 \ - libsqlite3-0=3.37.2-2ubuntu0.4 \ + libsqlite3-0=3.37.2-2ubuntu0.5 \ libssh-4=0.9.6-2ubuntu0.22.04.4 \ lsb-release=11.1.0ubuntu4 \ media-types=7.0.0 \ @@ -120,11 +120,11 @@ RUN apt install -y \ dbus-user-session=1.12.20-2ubuntu4.1 \ dbus=1.12.20-2ubuntu4.1 \ dmsetup=2:1.02.175-2.1ubuntu5 \ - docker-buildx-plugin=0.25.0-1~ubuntu.22.04~jammy \ - docker-ce-cli=5:28.3.2-1~ubuntu.22.04~jammy \ - docker-ce-rootless-extras=5:28.3.2-1~ubuntu.22.04~jammy \ - docker-ce=5:28.3.2-1~ubuntu.22.04~jammy \ - docker-compose-plugin=2.38.2-1~ubuntu.22.04~jammy \ + docker-buildx-plugin=0.26.1-1~ubuntu.22.04~jammy \ + docker-ce-cli=5:28.3.3-1~ubuntu.22.04~jammy \ + docker-ce-rootless-extras=5:28.3.3-1~ubuntu.22.04~jammy \ + docker-ce=5:28.3.3-1~ubuntu.22.04~jammy \ + docker-compose-plugin=2.39.1-1~ubuntu.22.04~jammy \ gir1.2-glib-2.0=1.72.0-1 \ git-man=1:2.34.1-1ubuntu1.15 \ git=1:2.34.1-1ubuntu1.15 \ @@ -159,7 +159,7 @@ RUN apt install -y \ libnftnl11=1.2.1-1build1 \ libnss-systemd=249.11-0ubuntu3.16 \ libpam-systemd=249.11-0ubuntu3.16 \ - libperl5.34=5.34.0-3ubuntu1.4 \ + libperl5.34=5.34.0-3ubuntu1.5 \ libslirp0=4.6.1-1build1 \ libx11-6=2:1.7.5-1ubuntu0.3 \ libx11-data=2:1.7.5-1ubuntu0.3 \ @@ -174,9 +174,9 @@ RUN apt install -y \ networkd-dispatcher=2.1-2ubuntu0.22.04.2 \ openssh-client=1:8.9p1-3ubuntu0.13 \ patch=2.7.6-7build2 \ - perl-base=5.34.0-3ubuntu1.4 \ - perl-modules-5.34=5.34.0-3ubuntu1.4 \ - perl=5.34.0-3ubuntu1.4 \ + perl-base=5.34.0-3ubuntu1.5 \ + perl-modules-5.34=5.34.0-3ubuntu1.5 \ + perl=5.34.0-3ubuntu1.5 \ pigz=2.6-1 \ python3-dbus=1.2.18-3build1 \ python3-gi=3.42.1-0ubuntu1 \ @@ -342,4 +342,4 @@ RUN mkdir -p /fl_admin/transfer RUN ln -s /MediSwarm /fl_admin/transfer/MediSwarm # Copy pre-trained model weights to image -COPY ./torch_home_cache /torch_home \ No newline at end of file +COPY ./torch_home_cache /torch_home From 494f6a3c14134ec811a52f8a6109d34b38004246 Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Wed, 30 Jul 2025 14:37:47 +0200 Subject: [PATCH 105/109] refactored to remove duplicate definitions --- .../dockerfile_update_addAptVersionNumbers.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py index 186fc955..cd9c94c7 100755 --- a/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py +++ b/scripts/dev_utils/dockerfile_update_addAptVersionNumbers.py @@ -3,20 +3,11 @@ import re import sys -LINE_BREAK_IN_COMMAND = ' \\\n ' -LINE_BREAK_REPLACEMENT = ' λινε βρεακ ρεπλαζεμεντ ' +from dockerfile_update_removeVersionApt import LINE_BREAK_IN_COMMAND, LINE_BREAK_REPLACEMENT, load_file, save_file + APT_INSTALL_COMMAND = 'RUN apt install -y' APT_INSTALL_REPLACEMENT = 'ΡΥΝ απτ ινσταλλ -υ' -def load_file(filename: str) -> str: - with open(filename, 'r') as infile: - return infile.read() - -def save_file(contents: str, filename: str) -> None: - with open(filename, 'w') as outfile: - outfile.write(contents) - - def parse_apt_versions(installlog: str) -> dict: versions = {} for line in installlog.splitlines(): From 19f6b3ceb1bb6cd2c2aaee56656fff05c089203c Mon Sep 17 00:00:00 2001 From: Ole Schwen Date: Thu, 31 Jul 2025 11:28:04 +0200 Subject: [PATCH 106/109] added potential pitfalls --- assets/readme/README.participant.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/assets/readme/README.participant.md b/assets/readme/README.participant.md index afc6e269..0ffcc77c 100644 --- a/assets/readme/README.participant.md +++ b/assets/readme/README.participant.md @@ -157,3 +157,9 @@ To have a baseline for swarm training, train the same model in a comparable way tail -f nohup.out # Follow training log ``` For any issues, check if the commands above point to problems and contact your Swarm Operator. + +## Troubleshooting + +* Image files need to have the correct file name including capitalization +* The directories listed as identifiers in the tables `annotation.csv` and `split.csv` should all be present, only those directories should be present +* The tables should not have additional or duplicate columns, entries need to have the correct captitalization From 3b730ea1fc1ca9c47b7dd6b1192cf41054f15318 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Tue, 19 Aug 2025 10:29:51 +0200 Subject: [PATCH 107/109] delete unused files Signed-off-by: GitHub CI --- application/jobs/stamp/app/custom/alibi.py | 147 ------------ application/jobs/stamp/app/custom/crossval.py | 226 ------------------ application/jobs/stamp/app/custom/deploy.py | 218 ----------------- 3 files changed, 591 deletions(-) delete mode 100644 application/jobs/stamp/app/custom/alibi.py delete mode 100644 application/jobs/stamp/app/custom/crossval.py delete mode 100644 application/jobs/stamp/app/custom/deploy.py diff --git a/application/jobs/stamp/app/custom/alibi.py b/application/jobs/stamp/app/custom/alibi.py deleted file mode 100644 index 69c61aed..00000000 --- a/application/jobs/stamp/app/custom/alibi.py +++ /dev/null @@ -1,147 +0,0 @@ -import torch -from jaxtyping import Bool, Float -from torch import Tensor, nn - - -class _RunningMeanScaler(nn.Module): - """Scales values by the inverse of the mean of values seen before.""" - - def __init__(self, dtype=torch.float32) -> None: - super().__init__() - self.running_mean = nn.Buffer(torch.ones(1, dtype=dtype)) - self.items_so_far = nn.Buffer(torch.ones(1, dtype=dtype)) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - if self.training: - # Welford's algorithm - self.running_mean.copy_( - (self.running_mean + (x - self.running_mean) / self.items_so_far).mean() - ) - self.items_so_far += 1 - - return x / self.running_mean - - -class _ALiBi(nn.Module): - # See MultiHeadAliBi - def __init__(self) -> None: - super().__init__() - - self.scale_distance = _RunningMeanScaler() - self.bias_scale = nn.Parameter(torch.rand(1)) - - def forward( - self, - *, - q: Float[Tensor, "batch query qk_feature"], - k: Float[Tensor, "batch key qk_feature"], - v: Float[Tensor, "batch key v_feature"], - coords_q: Float[Tensor, "batch query coord"], - coords_k: Float[Tensor, "batch key coord"], - attn_mask: Bool[Tensor, "batch query key"] | None, - alibi_mask: Bool[Tensor, "batch query key"] | None, - ) -> Float[Tensor, "batch query v_feature"]: - """ - Args: - alibi_mask: - Which query-key pairs to mask from ALiBi (i.e. don't apply ALiBi to). - """ - weight_logits = torch.einsum("bqf,bkf->bqk", q, k) * (k.size(-1) ** -0.5) - distances = torch.linalg.norm( - coords_q.unsqueeze(2) - coords_k.unsqueeze(1), dim=-1 - ) - scaled_distances = self.scale_distance(distances) * self.bias_scale - - if alibi_mask is not None: - scaled_distances = scaled_distances.where(~alibi_mask, 0.0) - - weights = torch.softmax(weight_logits, dim=-1) - - if attn_mask is not None: - weights = (weights - scaled_distances).where(~attn_mask, 0.0) - else: - weights = weights - scaled_distances - - attention = torch.einsum("bqk,bkf->bqf", weights, v) - - return attention - - -class MultiHeadALiBi(nn.Module): - """Attention with Linear Biases - - Based on - > PRESS, Ofir; SMITH, Noah A.; LEWIS, Mike. - > Train short, test long: Attention with linear biases enables input length extrapolation. - > arXiv preprint arXiv:2108.12409, 2021. - - Since the distances between in WSIs may be quite large, - we scale the distances by the mean distance seen during training. - """ - - def __init__( - self, - *, - embed_dim: int, - num_heads: int, - ) -> None: - super().__init__() - - if embed_dim % num_heads != 0: - raise ValueError(f"{embed_dim=} has to be divisible by {num_heads=}") - - self.query_encoders = nn.ModuleList( - [ - nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) - for _ in range(num_heads) - ] - ) - self.key_encoders = nn.ModuleList( - [ - nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) - for _ in range(num_heads) - ] - ) - self.value_encoders = nn.ModuleList( - [ - nn.Linear(in_features=embed_dim, out_features=embed_dim // num_heads) - for _ in range(num_heads) - ] - ) - - self.attentions = nn.ModuleList([_ALiBi() for _ in range(num_heads)]) - - self.fc = nn.Linear(in_features=embed_dim, out_features=embed_dim) - - def forward( - self, - *, - q: Float[Tensor, "batch query mh_qk_feature"], - k: Float[Tensor, "batch key mh_qk_feature"], - v: Float[Tensor, "batch key hm_v_feature"], - coords_q: Float[Tensor, "batch query coord"], - coords_k: Float[Tensor, "batch key coord"], - attn_mask: Bool[Tensor, "batch query key"] | None, - alibi_mask: Bool[Tensor, "batch query key"] | None, - ) -> Float[Tensor, "batch query mh_v_feature"]: - stacked_attentions = torch.stack( - [ - att( - q=q_enc(q), - k=k_enc(k), - v=v_enc(v), - coords_q=coords_q, - coords_k=coords_k, - attn_mask=attn_mask, - alibi_mask=alibi_mask, - ) - for q_enc, k_enc, v_enc, att in zip( - self.query_encoders, - self.key_encoders, - self.value_encoders, - self.attentions, - strict=True, - ) - ] - ) - return self.fc(stacked_attentions.permute(1, 2, 0, 3).flatten(-2, -1)) diff --git a/application/jobs/stamp/app/custom/crossval.py b/application/jobs/stamp/app/custom/crossval.py deleted file mode 100644 index 8f5148a9..00000000 --- a/application/jobs/stamp/app/custom/crossval.py +++ /dev/null @@ -1,226 +0,0 @@ -import logging -from collections.abc import Mapping, Sequence -from pathlib import Path -from typing import Any, Final - -import numpy as np -from lightning.pytorch.accelerators.accelerator import Accelerator -from pydantic import BaseModel -from sklearn.model_selection import StratifiedKFold - -from data import ( - PatientData, - filter_complete_patient_data_, - patient_to_ground_truth_from_clini_table_, - slide_to_patient_from_slide_table_, -) -from deploy import _predict, _to_prediction_df -from main import setup_model_for_training, train_model_ -from modeling.lightning_model import LitVisionTransformer -from modeling.types import ( - Category, - FeaturePath, - GroundTruth, - PandasLabel, - PatientId, -) -from transforms import VaryPrecisionTransform - -__author__ = "Marko van Treeck" -__copyright__ = "Copyright (C) 2024 Marko van Treeck" -__license__ = "MIT" - -_logger = logging.getLogger("stamp") - - -class _Split(BaseModel): - train_patients: set[PatientId] - test_patients: set[PatientId] - - -class _Splits(BaseModel): - splits: Sequence[_Split] - - -def categorical_crossval_( - clini_table: Path, - slide_table: Path, - feature_dir: Path, - output_dir: Path, - patient_label: PandasLabel, - ground_truth_label: PandasLabel, - filename_label: PandasLabel, - categories: Sequence[Category] | None, - n_splits: int, - # Dataset and -loader parameters - bag_size: int, - num_workers: int, - # Training paramenters - batch_size: int, - max_epochs: int, - patience: int, - accelerator: str | Accelerator, - # Experimental features - use_vary_precision_transform: bool, - use_alibi: bool, -) -> None: - patient_to_ground_truth: Final[dict[PatientId, GroundTruth]] = ( - patient_to_ground_truth_from_clini_table_( - clini_table_path=clini_table, - ground_truth_label=ground_truth_label, - patient_label=patient_label, - ) - ) - slide_to_patient: Final[dict[FeaturePath, PatientId]] = ( - slide_to_patient_from_slide_table_( - slide_table_path=slide_table, - feature_dir=feature_dir, - patient_label=patient_label, - filename_label=filename_label, - ) - ) - - # Clean data (remove slides without ground truth, missing features, etc.) - patient_to_data: Final[Mapping[Category, PatientData]] = ( - filter_complete_patient_data_( - patient_to_ground_truth=patient_to_ground_truth, - slide_to_patient=slide_to_patient, - drop_patients_with_missing_ground_truth=True, - ) - ) - - output_dir.mkdir(parents=True, exist_ok=True) - splits_file = output_dir / "splits.json" - - # Generate the splits, or load them from the splits file if they already exist - if not splits_file.exists(): - splits = _get_splits(patient_to_data=patient_to_data, n_splits=n_splits) - with open(splits_file, "w") as fp: - fp.write(splits.model_dump_json(indent=4)) - else: - _logger.debug(f"reading splits from {splits_file}") - with open(splits_file, "r") as fp: - splits = _Splits.model_validate_json(fp.read()) - - patients_in_splits = { - patient - for split in splits.splits - for patient in [*split.train_patients, *split.test_patients] - } - - if patients_without_ground_truth := patients_in_splits - patient_to_data.keys(): - raise RuntimeError( - "The splits file contains some patients we don't have information for in the clini / slide table: " - f"{patients_without_ground_truth}" - ) - - if ground_truths_not_in_split := patient_to_data.keys() - patients_in_splits: - _logger.warning( - "Some of the entries in the clini / slide table are not in the crossval split: " - f"{ground_truths_not_in_split}" - ) - - categories = categories or sorted( - { - patient_data.ground_truth - for patient_data in patient_to_data.values() - if patient_data.ground_truth is not None - } - ) - - for split_i, split in enumerate(splits.splits): - split_dir = output_dir / f"split-{split_i}" - - if (split_dir / "patient-preds.csv").exists(): - _logger.info( - f"skipping training for split {split_i}, " - "as a model checkpoint is already present" - ) - continue - - # Train the model - if not (split_dir / "model.ckpt").exists(): - model, train_dl, valid_dl = setup_model_for_training( - clini_table=clini_table, - slide_table=slide_table, - feature_dir=feature_dir, - ground_truth_label=ground_truth_label, - bag_size=bag_size, - num_workers=num_workers, - batch_size=batch_size, - patient_to_data={ - patient_id: patient_data - for patient_id, patient_data in patient_to_data.items() - if patient_id in split.train_patients - }, - categories=( - categories - or sorted( - { - patient_data.ground_truth - for patient_data in patient_to_data.values() - if patient_data.ground_truth is not None - } - ) - ), - train_transform=( - VaryPrecisionTransform(min_fraction_bits=1) - if use_vary_precision_transform - else None - ), - use_alibi=use_alibi, - ) - model = train_model_( - output_dir=split_dir, - model=model, - train_dl=train_dl, - valid_dl=valid_dl, - max_epochs=max_epochs, - patience=patience, - accelerator=accelerator, - ) - else: - model = LitVisionTransformer.load_from_checkpoint(split_dir / "model.ckpt") - - # Deploy on test set - if not (split_dir / "patient-preds.csv").exists(): - predictions = _predict( - model=model, - patient_to_data={ - patient_id: patient_data - for patient_id, patient_data in patient_to_data.items() - if patient_id in split.test_patients - }, - num_workers=num_workers, - accelerator=accelerator, - ) - - _to_prediction_df( - categories=categories, - patient_to_ground_truth=patient_to_ground_truth, - predictions=predictions, - patient_label=patient_label, - ground_truth_label=ground_truth_label, - ).to_csv(split_dir / "patient-preds.csv", index=False) - - -def _get_splits( - *, patient_to_data: Mapping[PatientId, PatientData[Any]], n_splits: int -) -> _Splits: - patients = np.array(list(patient_to_data.keys())) - skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=0) - splits = _Splits( - splits=[ - _Split( - train_patients=set(patients[train_indices]), - test_patients=set(patients[test_indices]), - ) - for train_indices, test_indices in skf.split( - patients, - np.array( - [patient.ground_truth for patient in patient_to_data.values()] - ), - ) - ] - ) - return splits diff --git a/application/jobs/stamp/app/custom/deploy.py b/application/jobs/stamp/app/custom/deploy.py deleted file mode 100644 index 235f7f8a..00000000 --- a/application/jobs/stamp/app/custom/deploy.py +++ /dev/null @@ -1,218 +0,0 @@ -import logging -from collections.abc import Mapping, Sequence -from pathlib import Path -from typing import TypeAlias, cast - -import lightning -import numpy as np -import pandas as pd -import torch -from jaxtyping import Float -from lightning.pytorch.accelerators.accelerator import Accelerator -from stamp.modeling.data import ( - PatientData, - dataloader_from_patient_data, - filter_complete_patient_data_, - patient_to_ground_truth_from_clini_table_, - slide_to_patient_from_slide_table_, -) -from stamp.modeling.lightning_model import LitVisionTransformer - -from modeling.types import GroundTruth, PandasLabel, PatientId - -__all__ = ["deploy_categorical_model_"] - -__author__ = "Marko van Treeck" -__copyright__ = "Copyright (C) 2024-2025 Marko van Treeck" -__license__ = "MIT" - -_logger = logging.getLogger("stamp") - -Logit: TypeAlias = float - - -def deploy_categorical_model_( - *, - output_dir: Path, - checkpoint_paths: Sequence[Path], - clini_table: Path | None, - slide_table: Path, - feature_dir: Path, - ground_truth_label: PandasLabel | None, - patient_label: PandasLabel, - filename_label: PandasLabel, - num_workers: int, - accelerator: str | Accelerator, -) -> None: - models = [ - LitVisionTransformer.load_from_checkpoint( - checkpoint_path=checkpoint_path - ).eval() - for checkpoint_path in checkpoint_paths - ] - - # Ensure all models were trained on the same ground truth label - if ( - len(ground_truth_labels := set(model.ground_truth_label for model in models)) - != 1 - ): - raise RuntimeError( - f"ground truth labels differ between models: {ground_truth_labels}" - ) - # Ensure the categories were the same between all models - if len(categories := set(tuple(model.categories) for model in models)) != 1: - raise RuntimeError(f"categories differ between models: {categories}") - - model_ground_truth_label = models[0].ground_truth_label - model_categories = list(models[0].categories) - - if ( - ground_truth_label is not None - and ground_truth_label != model_ground_truth_label - ): - _logger.warning( - "deployment ground truth label differs from training: " - f"{ground_truth_label} vs {model_ground_truth_label}" - ) - ground_truth_label = ground_truth_label or model_ground_truth_label - - output_dir.mkdir(exist_ok=True, parents=True) - - slide_to_patient = slide_to_patient_from_slide_table_( - slide_table_path=slide_table, - feature_dir=feature_dir, - patient_label=patient_label, - filename_label=filename_label, - ) - - patient_to_ground_truth: Mapping[PatientId, GroundTruth | None] - if clini_table is not None: - patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( - clini_table_path=clini_table, - ground_truth_label=ground_truth_label, - patient_label=patient_label, - ) - else: - patient_to_ground_truth = { - patient_id: None for patient_id in set(slide_to_patient.values()) - } - - patient_to_data = filter_complete_patient_data_( - patient_to_ground_truth=patient_to_ground_truth, - slide_to_patient=slide_to_patient, - drop_patients_with_missing_ground_truth=False, - ) - - all_predictions: list[Mapping[PatientId, Float[torch.Tensor, "category"]]] = [] # noqa: F821 - for model_i, model in enumerate(models): - predictions = _predict( - model=model, - patient_to_data=patient_to_data, - num_workers=num_workers, - accelerator=accelerator, - ) - all_predictions.append(predictions) - - _to_prediction_df( - categories=model_categories, - patient_to_ground_truth=patient_to_ground_truth, - predictions=predictions, - patient_label=patient_label, - ground_truth_label=ground_truth_label, - ).to_csv(output_dir / f"patient-preds-{model_i}.csv", index=False) - - # TODO we probably also want to save the 95% confidence interval in addition to the mean - _to_prediction_df( - categories=model_categories, - patient_to_ground_truth=patient_to_ground_truth, - predictions={ - # Mean prediction - patient_id: torch.stack( - [predictions[patient_id] for predictions in all_predictions] - ).mean(dim=0) - for patient_id in patient_to_data.keys() - }, - patient_label=patient_label, - ground_truth_label=ground_truth_label, - ).to_csv(output_dir / "patient-preds.csv", index=False) - - -def _predict( - *, - model: LitVisionTransformer, - patient_to_data: Mapping[PatientId, PatientData[GroundTruth | None]], - num_workers: int, - accelerator: str | Accelerator, -) -> Mapping[PatientId, Float[torch.Tensor, "category"]]: # noqa: F821 - model = model.eval() - torch.set_float32_matmul_precision("medium") - - patients_used_for_training: set[PatientId] = set(model.train_patients) | set( - model.valid_patients - ) - if overlap := patients_used_for_training & set(patient_to_data.keys()): - raise ValueError( - f"some of the patients in the validation set were used during training: {overlap}" - ) - - test_dl, _ = dataloader_from_patient_data( - patient_data=list(patient_to_data.values()), - bag_size=None, # Use all the tiles for deployment - # Use same encoding scheme as during training - categories=list(model.categories), - batch_size=1, - shuffle=False, - num_workers=num_workers, - transform=None, - ) - - trainer = lightning.Trainer( - accelerator=accelerator, - devices=1, # Needs to be 1, otherwise half the predictions are missing for some reason - logger=False, - ) - predictions = torch.softmax( - torch.concat( - cast( - list[torch.Tensor], - trainer.predict(model, test_dl), - ) - ), - dim=1, - ) - - return dict(zip(patient_to_data, predictions, strict=True)) - - -def _to_prediction_df( - *, - categories: Sequence[GroundTruth], - patient_to_ground_truth: Mapping[PatientId, GroundTruth | None], - predictions: Mapping[PatientId, torch.Tensor], - patient_label: PandasLabel, - ground_truth_label: PandasLabel, -) -> pd.DataFrame: - """Compiles deployment results into a DataFrame.""" - return pd.DataFrame( - [ - { - patient_label: patient_id, - ground_truth_label: patient_to_ground_truth.get(patient_id), - "pred": categories[int(prediction.argmax())], - **{ - f"{ground_truth_label}_{category}": prediction[i_cat].item() - for i_cat, category in enumerate(categories) - }, - "loss": ( - torch.nn.functional.cross_entropy( - prediction.reshape(1, -1), - torch.tensor(np.where(np.array(categories) == ground_truth)[0]), - ).item() - if (ground_truth := patient_to_ground_truth.get(patient_id)) - is not None - else None - ), - } - for patient_id, prediction in predictions.items() - ] - ).sort_values(by="loss") From 48c37700b384bddd125967ecdc97b49e0481b489 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Tue, 19 Aug 2025 10:34:51 +0200 Subject: [PATCH 108/109] feat: enhance configuration and data loading for patient-level features Signed-off-by: GitHub CI --- application/jobs/stamp/app/custom/config.py | 64 ++++-- application/jobs/stamp/app/custom/data.py | 195 +++++++++++++++++- .../app/custom/modeling/lightning_model.py | 50 ++++- .../app/custom/modeling/mlp_classifier.py | 39 +++- 4 files changed, 311 insertions(+), 37 deletions(-) diff --git a/application/jobs/stamp/app/custom/config.py b/application/jobs/stamp/app/custom/config.py index 09fb4086..c4274211 100644 --- a/application/jobs/stamp/app/custom/config.py +++ b/application/jobs/stamp/app/custom/config.py @@ -1,10 +1,12 @@ import os +from collections.abc import Sequence from pathlib import Path import torch from pydantic import BaseModel, ConfigDict, Field -from modeling.types import PandasLabel +from modeling.registry import ModelName +from modeling.types import Category, PandasLabel class TrainConfig(BaseModel): @@ -13,28 +15,23 @@ class TrainConfig(BaseModel): output_dir: Path = Field(description="The directory to save the results to") clini_table: Path = Field(description="Excel or CSV to read clinical data from") - slide_table: Path = Field( - description="Excel or CSV to read patient-slide associations from" + slide_table: Path | None = Field( + default=None, description="Excel or CSV to read patient-slide associations from" ) feature_dir: Path = Field(description="Directory containing feature files") ground_truth_label: PandasLabel = Field( description="Name of categorical column in clinical table to train on" ) - categories: list[str] | None = None + categories: Sequence[Category] | None = None patient_label: PandasLabel = "PATIENT" filename_label: PandasLabel = "FILENAME" - # Dataset and -loader parameters - bag_size: int = 512 - num_workers: int = min(os.cpu_count() or 1, 16) - - # Training paramenters - batch_size: int = 64 - max_epochs: int = 64 - patience: int = 16 - accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + params_path: Path | None = Field( + default=None, + description="Optional: Path to a YAML file with advanced training parameters.", + ) # Experimental features use_vary_precision_transform: bool = False @@ -61,3 +58,44 @@ class DeploymentConfig(BaseModel): num_workers: int = min(os.cpu_count() or 1, 16) accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + + +class VitModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + dim_model: int = 512 + dim_feedforward: int = 512 + n_heads: int = 8 + n_layers: int = 2 + dropout: float = 0.0 + # Experimental feature: Use ALiBi positional embedding + use_alibi: bool = False + + +class MlpModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + dim_hidden: int = 512 + num_layers: int = 2 + dropout: float = 0.25 + + +class ModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + vit: VitModelParams + mlp: MlpModelParams + + +class AdvancedConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + bag_size: int = 512 + num_workers: int = min(os.cpu_count() or 1, 16) + batch_size: int = 64 + max_epochs: int = 32 + patience: int = 16 + accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + max_lr: float = 1e-4 + div_factor: float = 25.0 + model_name: ModelName | None = Field( + default=None, + description='Optional: "vit" or "mlp". Defaults based on feature type.', + ) + model_params: ModelParams diff --git a/application/jobs/stamp/app/custom/data.py b/application/jobs/stamp/app/custom/data.py index b7f51b0e..52ab10c8 100755 --- a/application/jobs/stamp/app/custom/data.py +++ b/application/jobs/stamp/app/custom/data.py @@ -5,13 +5,14 @@ from dataclasses import KW_ONLY, dataclass from itertools import groupby from pathlib import Path -from typing import BinaryIO, Generic, TextIO, TypeAlias, cast +from typing import IO, BinaryIO, Generic, TextIO, TypeAlias, cast, Union import h5py import numpy as np import pandas as pd import torch from jaxtyping import Bool, Float +from packaging.version import Version from torch import Tensor from torch.utils.data import DataLoader, Dataset @@ -33,6 +34,8 @@ ) _logger = logging.getLogger("stamp") +_logged_stamp_v1_warning = False + __author__ = "Marko van Treeck" __copyright__ = "Copyright (C) 2022-2025 Marko van Treeck" @@ -40,6 +43,7 @@ _Bag: TypeAlias = Float[Tensor, "tile feature"] _EncodedTarget: TypeAlias = Bool[Tensor, "category_is_hot"] # noqa: F821 +_BinaryIOLike: TypeAlias = Union[BinaryIO, IO[bytes]] """The ground truth, encoded numerically (currently: one-hot)""" _Coordinates: TypeAlias = Float[Tensor, "tile 2"] @@ -53,7 +57,7 @@ class PatientData(Generic[GroundTruthType]): feature_files: Iterable[FeaturePath | BinaryIO] -def dataloader_from_patient_data( +def tile_bag_dataloader( *, patient_data: Sequence[PatientData[GroundTruth | None]], bag_size: int | None, @@ -66,7 +70,7 @@ def dataloader_from_patient_data( DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], Sequence[Category], ]: - """Creates a dataloader from patient data, encoding the ground truths. + """Creates a dataloader from patient data for tile-level (bagged) features. Args: categories: @@ -112,12 +116,115 @@ def _collate_to_tuple( return (bags, coords, bag_sizes, encoded_targets) +def patient_feature_dataloader( + *, + patient_data: Sequence[PatientData[GroundTruth | None]], + categories: Sequence[Category] | None = None, + batch_size: int, + shuffle: bool, + num_workers: int, + transform: Callable[[Tensor], Tensor] | None, +) -> tuple[DataLoader, Sequence[Category]]: + """ + Creates a dataloader for patient-level features (one feature vector per patient). + """ + feature_files = [next(iter(p.feature_files)) for p in patient_data] + raw_ground_truths = np.array([patient.ground_truth for patient in patient_data]) + categories = ( + categories if categories is not None else list(np.unique(raw_ground_truths)) + ) + one_hot = torch.tensor(raw_ground_truths.reshape(-1, 1) == categories) + ds = PatientFeatureDataset(feature_files, one_hot, transform=transform) + dl = DataLoader(ds, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) + return dl, categories + + +def detect_feature_type(feature_dir: Path) -> str: + """ + Detects feature type by inspecting all .h5 files in feature_dir. + + Returns: + "tile" if all files are tile-level, "patient" if all are patient-level. + If files have mixed types, raises an error. + If no .h5 files are found, raises an error. + """ + feature_types = set() + files_checked = 0 + + for file in feature_dir.glob("*.h5"): + files_checked += 1 + with h5py.File(file, "r") as h5: + feat_type = h5.attrs.get("feat_type") + encoder = h5.attrs.get("encoder") + + if feat_type is not None or encoder is not None: + feature_types.add(str(feat_type)) + else: + # If feat_type is missing, always treat as tile-level feature + feature_types.add("tile") + + if files_checked == 0: + raise RuntimeError("No .h5 feature files found in feature_dir.") + + if len(feature_types) > 1: + raise RuntimeError( + f"Multiple feature types detected in {feature_dir}: {feature_types}. " + "All feature files must have the same type." + ) + + return feature_types.pop() + + +def load_patient_level_data( + *, + clini_table: Path, + feature_dir: Path, + patient_label: PandasLabel, + ground_truth_label: PandasLabel, + feature_ext: str = ".h5", +) -> dict[PatientId, PatientData]: + """ + Loads PatientData for patient-level features, matching patients in the clinical table + to feature files in feature_dir named {patient_id}.h5. + """ + # TODO: I'm not proud at all of this. Any other alternative for mapping + # clinical data to the patient-level feature paths that avoids + # creating another slide table for encoded featuress is welcome :P. + + clini_df = read_table( + clini_table, + usecols=[patient_label, ground_truth_label], + dtype=str, + ).dropna() + + patient_to_data: dict[PatientId, PatientData] = {} + missing_features = [] + for _, row in clini_df.iterrows(): + patient_id = PatientId(str(row[patient_label])) + ground_truth = row[ground_truth_label] + feature_file = feature_dir / f"{patient_id}{feature_ext}" + if feature_file.exists(): + patient_to_data[patient_id] = PatientData( + ground_truth=ground_truth, + feature_files=[FeaturePath(feature_file)], + ) + else: + missing_features.append(patient_id) + + if missing_features: + _logger.warning( + f"Some patients have no feature file in {feature_dir}: {missing_features}" + ) + + return patient_to_data + + @dataclass class BagDataset(Dataset[tuple[_Bag, _Coordinates, BagSize, _EncodedTarget]]): """A dataset of bags of instances.""" _: KW_ONLY - bags: Sequence[Iterable[FeaturePath | BinaryIO]] + bags: Sequence[Iterable[FeaturePath | _BinaryIOLike]] """The `.h5` files containing the bags. Each bag consists of the features taken from one or multiple h5 files. @@ -182,6 +289,47 @@ def __getitem__( ) +class PatientFeatureDataset(Dataset): + """ + Dataset for single feature vector per sample (e.g. slide-level or patient-level). + Each item is a (feature_vector, label_onehot) tuple. + """ + + def __init__( + self, + feature_files: Sequence[FeaturePath | BinaryIO], + ground_truths: Tensor, # shape: [num_samples, num_classes] + transform: Callable[[Tensor], Tensor] | None, + ): + if len(feature_files) != len(ground_truths): + raise ValueError("Number of feature files and ground truths must match.") + self.feature_files = feature_files + self.ground_truths = ground_truths + self.transform = transform + + def __len__(self): + return len(self.feature_files) + + def __getitem__(self, idx: int): + feature_file = self.feature_files[idx] + with h5py.File(feature_file, "r") as h5: + feats = torch.from_numpy(h5["feats"][:]) # pyright: ignore[reportIndexIssue] + # Accept [V] or [1, V] + if feats.ndim == 2 and feats.shape[0] == 1: + feats = feats[0] + elif feats.ndim == 1: + pass + else: + raise RuntimeError( + f"Expected single feature vector (shape [F] or [1, F]), got {feats.shape} in {feature_file}." + "Check that the features are patient-level." + ) + if self.transform is not None: + feats = self.transform(feats) + label = self.ground_truths[idx] + return feats, label + + @dataclass class CoordsInfo: coords_um: np.ndarray @@ -221,13 +369,24 @@ def get_coords(feature_h5: h5py.File) -> CoordsInfo: == 224 ): # Historic STAMP format - _logger.info( - f"{feature_h5.filename}: tile stride is roughly 224, assuming coordinates have unit 256um/224px (historic STAMP format)" - ) + # TODO: find a better way to get this warning just once + global _logged_stamp_v1_warning + if not _logged_stamp_v1_warning: + _logger.info( + f"{feature_h5.filename}: tile stride is roughly 224, assuming coordinates have unit 256um/224px (historic STAMP format)" + ) + _logged_stamp_v1_warning = True tile_size_um = Microns(256.0) tile_size_px = TilePixels(224) coords_um = coords / 224 * 256 + if (version_str := feature_h5.attrs.get("stamp_version")) and ( + extraction_version := Version(version_str) + ) > Version(stamp.__version__): + raise RuntimeError( + f"features were extracted with a newer version of stamp, please update your stamp to at least version {extraction_version}." + ) + if not tile_size_px and "tile_size_px" in feature_h5.attrs: tile_size_px = TilePixels(int(feature_h5.attrs["tile_size_px"])) # pyright: ignore[reportArgumentType] @@ -276,7 +435,7 @@ def patient_to_ground_truth_from_clini_table_( ground_truth_label: PandasLabel, ) -> dict[PatientId, GroundTruth]: """Loads the patients and their ground truths from a clini table.""" - clini_df = _read_table( + clini_df = read_table( clini_table_path, usecols=[patient_label, ground_truth_label], dtype=str, @@ -309,12 +468,26 @@ def slide_to_patient_from_slide_table_( patient_label: PandasLabel, filename_label: PandasLabel, ) -> dict[FeaturePath, PatientId]: - """Creates a slide-to-patient mapping from a slide table.""" - slide_df = _read_table( + """ + Creates a slide-to-patient mapping from a slide table. + Side effects: + Verifies that all files in the slide tables filename_label + column has an .h5 extension. + """ + slide_df = read_table( slide_table_path, usecols=[patient_label, filename_label], dtype=str, ) + # Verify the slide table contains a feature path with .h5 extension by + # checking the filename_label. + for x in slide_df[filename_label]: + if not str(x).endswith(".h5"): + raise ValueError( + "One or more files are missing the .h5 extension in the " + "filename_label column. The first file missing the .h5 " + "extension is: " + str(x) + "." + ) slide_to_patient: Mapping[FeaturePath, PatientId] = { FeaturePath(feature_dir / cast(str, k)): PatientId(cast(str, patient)) @@ -326,7 +499,7 @@ def slide_to_patient_from_slide_table_( return slide_to_patient -def _read_table(path: Path | TextIO, **kwargs) -> pd.DataFrame: +def read_table(path: Path | TextIO, **kwargs) -> pd.DataFrame: if not isinstance(path, Path): return pd.read_csv(path, **kwargs) elif path.suffix == ".xlsx": diff --git a/application/jobs/stamp/app/custom/modeling/lightning_model.py b/application/jobs/stamp/app/custom/modeling/lightning_model.py index 2dd5f64e..c45f9d6e 100644 --- a/application/jobs/stamp/app/custom/modeling/lightning_model.py +++ b/application/jobs/stamp/app/custom/modeling/lightning_model.py @@ -7,6 +7,7 @@ import numpy as np import torch from jaxtyping import Bool, Float +from packaging.version import Version from torch import Tensor, nn, optim from torchmetrics.classification import MulticlassAUROC @@ -30,13 +31,12 @@ class LitVisionTransformer(lightning.LightningModule): learning settings, such as Multiple Instance Learning (MIL) for whole-slide images or patch-based data. This class encapsulates training, validation, testing, and prediction logic, along with: - - Masking logic that ensures only valid tiles (patches) participate in attention during training. + - Masking logic that ensures only valid tiles (patches) participate in attention during training (deactivated) - AUROC metric tracking during validation for multiclass classification. - Compatibility checks based on the `stamp` framework version. - Integration of class imbalance handling through weighted cross-entropy loss. - The attention mask is applied *only* during training to hide paddings - and is skipped during evaluation and inference for reducing memory usage. + The attention mask is currently deactivated to reduce memory usage. Args: categories: List of class labels. @@ -47,6 +47,9 @@ class LitVisionTransformer(lightning.LightningModule): n_heads: Number of self-attention heads. n_layers: Number of transformer layers. dropout: Dropout rate used throughout the model. + total_steps: Number of steps done in the LR Scheduler cycle. + max_lr: max learning rate. + div_factor: Determines the initial learning rate via initial_lr = max_lr/div_factor use_alibi: Whether to use ALiBi-style positional bias in attention (optional). ground_truth_label: Column name for accessing ground-truth labels from metadata. train_patients: List of patient IDs used for training. @@ -68,9 +71,12 @@ def __init__( n_heads: int, n_layers: int, dropout: float, + # Learning Rate Scheduler params, not used in inference + total_steps: int, + max_lr: float, + div_factor: float, # Experimental features - # TODO remove default values for stamp 3; they're only here for backwards compatibility - use_alibi: bool = False, + use_alibi: bool, # Metadata used by other parts of stamp, but not by the model itself ground_truth_label: PandasLabel, train_patients: Iterable[PatientId], @@ -97,6 +103,9 @@ def __init__( ) self.class_weights = category_weights self.valid_auroc = MulticlassAUROC(len(categories)) + self.total_steps = total_steps + self.max_lr = max_lr + self.div_factor = div_factor # Used during deployment self.ground_truth_label = ground_truth_label @@ -160,7 +169,7 @@ def training_step( batch: tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], batch_idx: int, ) -> Loss: - return self._step(batch=batch, step_name="training", use_mask=True) + return self._step(batch=batch, step_name="training", use_mask=False) def validation_step( self, @@ -185,9 +194,32 @@ def predict_step( # adding a mask here will *drastically* and *unbearably* increase memory usage return self.vision_transformer(bags, coords=coords, mask=None) - def configure_optimizers(self) -> optim.Optimizer: - optimizer = optim.Adam(self.parameters(), lr=1e-3) - return optimizer + def configure_optimizers( + self, + ) -> tuple[list[optim.Optimizer], list[optim.lr_scheduler.LRScheduler]]: + optimizer = optim.AdamW( + self.parameters(), lr=1e-3 + ) # this lr value should be ignored with the scheduler + + scheduler = optim.lr_scheduler.OneCycleLR( + optimizer=optimizer, + total_steps=self.total_steps, + max_lr=self.max_lr, + div_factor=self.div_factor, + ) + return [optimizer], [scheduler] + + def on_train_batch_end(self, outputs, batch, batch_idx): + # Log learning rate at the end of each training batch + current_lr = self.trainer.optimizers[0].param_groups[0]["lr"] + self.log( + "learning_rate", + current_lr, + on_step=False, + on_epoch=True, + prog_bar=True, + sync_dist=True, + ) def _mask_from_bags( diff --git a/application/jobs/stamp/app/custom/modeling/mlp_classifier.py b/application/jobs/stamp/app/custom/modeling/mlp_classifier.py index d99ec5e7..0bf13517 100644 --- a/application/jobs/stamp/app/custom/modeling/mlp_classifier.py +++ b/application/jobs/stamp/app/custom/modeling/mlp_classifier.py @@ -3,7 +3,8 @@ import lightning import numpy as np import torch -from torch import Tensor, nn +from packaging.version import Version +from torch import Tensor, nn, optim from torchmetrics.classification import MulticlassAUROC from .types import Category, PandasLabel, PatientId @@ -56,6 +57,10 @@ def __init__( ground_truth_label: PandasLabel, train_patients: Iterable[PatientId], valid_patients: Iterable[PatientId], + # Learning Rate Scheduler params, used only in training + total_steps: int, + max_lr: float, + div_factor: float, **metadata, ): super().__init__() @@ -73,8 +78,10 @@ def __init__( self.categories = np.array(categories) self.train_patients = train_patients self.valid_patients = valid_patients + self.total_steps = total_steps + self.max_lr = max_lr + self.div_factor = div_factor - # TODO: Add version check with version 2.2.1, for both MLP and Transformer def forward(self, x: Tensor) -> Tensor: return self.model(x) @@ -119,5 +126,29 @@ def predict_step(self, batch, batch_idx): feats, _ = batch return self.model(feats) - def configure_optimizers(self): - return torch.optim.Adam(self.parameters(), lr=1e-3) + def configure_optimizers( + self, + ) -> tuple[list[optim.Optimizer], list[optim.lr_scheduler.LRScheduler]]: + optimizer = optim.AdamW( + self.parameters(), lr=1e-3 + ) # this lr value should be ignored with the scheduler + + scheduler = optim.lr_scheduler.OneCycleLR( + optimizer=optimizer, + total_steps=self.total_steps, + max_lr=self.max_lr, + div_factor=25.0, + ) + return [optimizer], [scheduler] + + def on_train_batch_end(self, outputs, batch, batch_idx): + # Log learning rate at the end of each training batch + current_lr = self.trainer.optimizers[0].param_groups[0]["lr"] + self.log( + "learning_rate", + current_lr, + on_step=False, + on_epoch=True, + prog_bar=True, + sync_dist=True, + ) From f06cd1ee771b206484678be001d1059c2e92f148 Mon Sep 17 00:00:00 2001 From: GitHub CI Date: Tue, 19 Aug 2025 14:07:09 +0200 Subject: [PATCH 109/109] feat: add configuration classes for training and deployment settings Signed-off-by: GitHub CI --- application/jobs/stamp/app/custom/main.py | 809 +++++++----------- .../jobs/stamp/app/custom/modeling/config.py | 100 +++ docker_config/Dockerfile_stamp | 2 +- 3 files changed, 433 insertions(+), 478 deletions(-) create mode 100644 application/jobs/stamp/app/custom/modeling/config.py diff --git a/application/jobs/stamp/app/custom/main.py b/application/jobs/stamp/app/custom/main.py index 107cf0de..c42220cf 100644 --- a/application/jobs/stamp/app/custom/main.py +++ b/application/jobs/stamp/app/custom/main.py @@ -1,23 +1,6 @@ import logging -import sys - -from torch import tensor - -import modeling.lightning_model -from modeling.registry import MODEL_REGISTRY - -print("Training script is using:", modeling.lightning_model.__file__) - -logger = logging.getLogger("test") -logger.setLevel(logging.DEBUG) - -if not logger.hasHandlers(): - handler = logging.StreamHandler(sys.stderr) - handler.setFormatter(logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s")) - handler.setLevel(logging.DEBUG) - logger.addHandler(handler) - import shutil +from collections import Counter from collections.abc import Callable, Mapping, Sequence from pathlib import Path from typing import cast @@ -26,136 +9,326 @@ import lightning.pytorch import lightning.pytorch.accelerators import lightning.pytorch.accelerators.accelerator +import nvflare.client as flare_util +import nvflare.client.lightning as flare import torch from lightning.pytorch.accelerators.accelerator import Accelerator -from lightning.pytorch.callbacks import ModelCheckpoint +from lightning.pytorch.callbacks import EarlyStopping, ModelCheckpoint from lightning.pytorch.loggers import CSVLogger - from sklearn.model_selection import train_test_split from torch.utils.data.dataloader import DataLoader -import logging -import nvflare.client.lightning as flare -import nvflare.client as flare_util -from data import ( - BagDataset, - PatientData, - dataloader_from_patient_data, - filter_complete_patient_data_, - patient_to_ground_truth_from_clini_table_, - slide_to_patient_from_slide_table_, -) +from modeling.config import AdvancedConfig, TrainConfig from modeling.lightning_model import ( Bags, BagSizes, EncodedTargets, - LitVisionTransformer, ) -from transforms import VaryPrecisionTransform +from modeling.registry import MODEL_REGISTRY, ModelName from modeling.types import Category, CoordinatesBatch, GroundTruth, PandasLabel, PatientId +from .data import ( + BagDataset, + PatientData, + PatientFeatureDataset, + detect_feature_type, + filter_complete_patient_data_, + load_patient_level_data, + patient_feature_dataloader, + patient_to_ground_truth_from_clini_table_, + slide_to_patient_from_slide_table_, + tile_bag_dataloader, +) +from .transforms import VaryPrecisionTransform + +__author__ = "Marko van Treeck" +__copyright__ = "Copyright (C) 2024 Marko van Treeck" +__license__ = "MIT" + +_logger = logging.getLogger("stamp") def train_categorical_model_( *, - clini_table: Path, - slide_table: Path, - feature_dir: Path, - output_dir: Path, - patient_label: PandasLabel, - ground_truth_label: PandasLabel, - filename_label: PandasLabel, - categories: Sequence[Category] | None, - # Dataset and -loader parameters - bag_size: int, - num_workers: int, - # Training paramenters - batch_size: int, - max_epochs: int, - patience: int, - accelerator: str | Accelerator, - # Experimental features - use_vary_precision_transform: bool, - use_alibi: bool, + config: TrainConfig, + advanced: AdvancedConfig, ) -> None: - """Trains a model. - - Args: - clini_table: - An excel or csv file to read the clinical information from. - Must at least have the columns specified in the arguments - - `patient_label` (containing a unique patient ID) - and `ground_truth_label` (containing the ground truth to train for). - slide_table: - An excel or csv file to read the patient-slide associations from. - Must at least have the columns specified in the arguments - `patient_label` (containing the patient ID) - and `filename_label` - (containing a filename relative to `feature_dir` - in which some of the patient's features are stored). - feature_dir: - See `slide_table`. - output_dir: - Path into which to output the artifacts (trained model etc.) - generated during training. - patient_label: - See `clini_table`, `slide_table`. - ground_truth_label: - See `clini_table`. - filename_label: - See `slide_table`. - categories: - Categories of the ground truth. - Set to `None` to automatically infer. - """ - # Read and parse data from out clini and slide table - patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( - clini_table_path=clini_table, - ground_truth_label=ground_truth_label, - patient_label=patient_label, - ) - slide_to_patient = slide_to_patient_from_slide_table_( - slide_table_path=slide_table, - feature_dir=feature_dir, - patient_label=patient_label, - filename_label=filename_label, - ) - - # Clean data (remove slides without ground truth, missing features, etc.) - patient_to_data = filter_complete_patient_data_( - patient_to_ground_truth=patient_to_ground_truth, - slide_to_patient=slide_to_patient, - drop_patients_with_missing_ground_truth=True, - ) + """Trains a model based on the feature type.""" + feature_type = detect_feature_type(config.feature_dir) + _logger.info(f"Detected feature type: {feature_type}") + + if feature_type == "tile": + if config.slide_table is None: + raise ValueError("A slide table is required for tile-level modeling") + patient_to_ground_truth = patient_to_ground_truth_from_clini_table_( + clini_table_path=config.clini_table, + ground_truth_label=config.ground_truth_label, + patient_label=config.patient_label, + ) + slide_to_patient = slide_to_patient_from_slide_table_( + slide_table_path=config.slide_table, + feature_dir=config.feature_dir, + patient_label=config.patient_label, + filename_label=config.filename_label, + ) + patient_to_data = filter_complete_patient_data_( + patient_to_ground_truth=patient_to_ground_truth, + slide_to_patient=slide_to_patient, + drop_patients_with_missing_ground_truth=True, + ) + elif feature_type == "patient": + # Patient-level: ignore slide_table + if config.slide_table is not None: + _logger.warning("slide_table is ignored for patient-level features.") + patient_to_data = load_patient_level_data( + clini_table=config.clini_table, + feature_dir=config.feature_dir, + patient_label=config.patient_label, + ground_truth_label=config.ground_truth_label, + ) + elif feature_type == "slide": + raise RuntimeError( + "Slide-level features are not supported for training." + "Please rerun the encoding step with patient-level encoding." + ) + else: + raise RuntimeError(f"Unknown feature type: {feature_type}") - # Train the model + # Train the model (the rest of the logic is unchanged) model, train_dl, valid_dl = setup_model_for_training( patient_to_data=patient_to_data, - categories=categories, - bag_size=bag_size, - batch_size=batch_size, - num_workers=num_workers, - ground_truth_label=ground_truth_label, - clini_table=clini_table, - slide_table=slide_table, - feature_dir=feature_dir, + categories=config.categories, + advanced=advanced, + ground_truth_label=config.ground_truth_label, + clini_table=config.clini_table, + slide_table=config.slide_table, + feature_dir=config.feature_dir, train_transform=( VaryPrecisionTransform(min_fraction_bits=1) - if use_vary_precision_transform + if config.use_vary_precision_transform else None ), - use_alibi=use_alibi, + feature_type=feature_type, ) train_model_( - output_dir=output_dir, + output_dir=config.output_dir, model=model, train_dl=train_dl, valid_dl=valid_dl, - max_epochs=max_epochs, - patience=patience, - accelerator=accelerator, + max_epochs=advanced.max_epochs, + patience=advanced.patience, + accelerator=advanced.accelerator, + ) + + +def setup_model_for_training( + *, + patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], + categories: Sequence[Category] | None, + train_transform: Callable[[torch.Tensor], torch.Tensor] | None, + feature_type: str, + advanced: AdvancedConfig, + # Metadata, has no effect on model training + ground_truth_label: PandasLabel, + clini_table: Path, + slide_table: Path | None, + feature_dir: Path, +) -> tuple[ + lightning.LightningModule, + DataLoader, + DataLoader, +]: + """Creates a model and dataloaders for training""" + + train_dl, valid_dl, train_categories, dim_feats, train_patients, valid_patients = ( + setup_dataloaders_for_training( + patient_to_data=patient_to_data, + categories=categories, + bag_size=advanced.bag_size, + batch_size=advanced.batch_size, + num_workers=advanced.num_workers, + train_transform=train_transform, + feature_type=feature_type, + ) + ) + + _logger.info( + "Training dataloaders: bag_size=%s, batch_size=%s, num_workers=%s", + advanced.bag_size, + advanced.batch_size, + advanced.num_workers, ) + category_weights = _compute_class_weights_and_check_categories( + train_dl=train_dl, + feature_type=feature_type, + train_categories=train_categories, + ) + + # 1. Default to a model if none is specified + if advanced.model_name is None: + advanced.model_name = ModelName.VIT if feature_type == "tile" else ModelName.MLP + _logger.info( + f"No model specified, defaulting to '{advanced.model_name.value}' for feature type '{feature_type}'" + ) + + # 2. Validate that the chosen model supports the feature type + model_info = MODEL_REGISTRY[advanced.model_name] + if feature_type not in model_info["supported_features"]: + raise ValueError( + f"Model '{advanced.model_name.value}' does not support feature type '{feature_type}'. " + f"Supported types are: {model_info['supported_features']}" + ) + + # 3. Get model-specific hyperparameters + model_specific_params = advanced.model_params.model_dump()[ + advanced.model_name.value + ] + + # 4. Calculate total steps for scheduler + steps_per_epoch = len(train_dl) + total_steps = steps_per_epoch * advanced.max_epochs + + # 5. Prepare common parameters + common_params = { + "categories": train_categories, + "category_weights": category_weights, + "dim_input": dim_feats, + "total_steps": total_steps, + "max_lr": advanced.max_lr, + "div_factor": advanced.div_factor, + # Metadata, has no effect on model training + "model_name": advanced.model_name.value, + "ground_truth_label": ground_truth_label, + "train_patients": train_patients, + "valid_patients": valid_patients, + "clini_table": clini_table, + "slide_table": slide_table, + "feature_dir": feature_dir, + } + + # 6. Instantiate the model dynamically + ModelClass = model_info["model_class"] + all_params = {**common_params, **model_specific_params} + _logger.info( + f"Instantiating model '{advanced.model_name.value}' with parameters: {model_specific_params}" + ) + _logger.info( + "Other params: max_epochs=%s, patience=%s", + advanced.max_epochs, + advanced.patience, + ) + model = ModelClass(**all_params) + + return model, train_dl, valid_dl + + +def setup_dataloaders_for_training( + *, + patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], + categories: Sequence[Category] | None, + bag_size: int, + batch_size: int, + num_workers: int, + train_transform: Callable[[torch.Tensor], torch.Tensor] | None, + feature_type: str, +) -> tuple[ + DataLoader, + DataLoader, + Sequence[Category], + int, + Sequence[PatientId], + Sequence[PatientId], +]: + """ + Creates train/val dataloaders for tile-level or patient-level features. + + Returns: + train_dl, valid_dl, categories, feature_dim, train_patients, valid_patients + """ + # Sample count for training + log_total_class_summary(patient_to_data, categories) + + # Stratified split + ground_truths = [ + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + ] + if len(ground_truths) != len(patient_to_data): + raise ValueError( + "patient_to_data must have a ground truth defined for all targets!" + ) + + train_patients, valid_patients = cast( + tuple[Sequence[PatientId], Sequence[PatientId]], + train_test_split( + list(patient_to_data), stratify=ground_truths, shuffle=True, random_state=0 + ), + ) + + if feature_type == "tile": + # Use existing BagDataset logic + train_dl, train_categories = tile_bag_dataloader( + patient_data=[patient_to_data[pid] for pid in train_patients], + categories=categories, + bag_size=bag_size, + batch_size=batch_size, + shuffle=True, + num_workers=num_workers, + transform=train_transform, + ) + valid_dl, _ = tile_bag_dataloader( + patient_data=[patient_to_data[pid] for pid in valid_patients], + bag_size=None, + categories=train_categories, + batch_size=1, + shuffle=False, + num_workers=num_workers, + transform=None, + ) + bags, _, _, _ = next(iter(train_dl)) + dim_feats = bags.shape[-1] + return ( + train_dl, + valid_dl, + train_categories, + dim_feats, + train_patients, + valid_patients, + ) + + elif feature_type == "patient": + train_dl, train_categories = patient_feature_dataloader( + patient_data=[patient_to_data[pid] for pid in train_patients], + categories=categories, + batch_size=batch_size, + shuffle=True, + num_workers=num_workers, + transform=train_transform, + ) + valid_dl, _ = patient_feature_dataloader( + patient_data=[patient_to_data[pid] for pid in valid_patients], + categories=train_categories, + batch_size=1, + shuffle=False, + num_workers=num_workers, + transform=None, + ) + feats, _ = next(iter(train_dl)) + dim_feats = feats.shape[-1] + return ( + train_dl, + valid_dl, + train_categories, + dim_feats, + train_patients, + valid_patients, + ) + else: + raise RuntimeError( + f"Unsupported feature type: {feature_type}. Only 'tile' and 'patient' are supported." + ) + def train_model_( *, @@ -179,13 +352,19 @@ def train_model_( mode="min", filename="checkpoint-{epoch:02d}-{validation_loss:0.3f}", ) + trainer = lightning.Trainer( default_root_dir=output_dir, callbacks=[ - # EarlyStopping(monitor="validation_loss", mode="min", patience=patience), + EarlyStopping(monitor="validation_loss", mode="min", patience=patience), model_checkpoint, ], max_epochs=max_epochs, + # FIXME The number of accelerators is currently fixed to one for the + # following reasons: + # 1. `trainer.predict()` does not return any predictions if used with + # the default strategy no multiple GPUs + # 2. `barspoon.model.SafeMulticlassAUROC` breaks on multiple GPUs accelerator=accelerator, devices=1, gradient_clip_val=0.5, @@ -195,47 +374,8 @@ def train_model_( flare_util.init() SITE_NAME = flare.get_site_name() flare.patch(trainer) # Patch trainer to enable swarm learning - logger.info(f"Site name: {SITE_NAME}") - - logger.info(" About to enter flare.is_running loop") - while flare.is_running(): - logger.info("[DEBUG] waiting to receive swarm model (10s timeout)") input_model = flare.receive() - # logger.info('ModelClass state dict keys()', model.state_dict().keys()) - ''' - if input_model is not None: - logger.info("==== Swarm model received ====") - logger.info( - f"input_model.params.keys() = {list(input_model.params.keys())[:10]} ... total = {len(input_model.params)}") - # log input_model.params - logger.info(f"input_model.params.keys() = {input_model.params.keys()}") - logger.info( - f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") - try: - model.load_state_dict(input_model.params) - except Exception as e: - logger.error("load_state_dict failed:", exc_info=True) - raise - - logger.info(f"[DEBUG] Got input model: {input_model}") - if input_model is None: - logger.info("[DEBUG] no swarm_start received in 10s") - else: - model.load_state_dict(input_model.params) - - logger.info("[DEBUG] received swarm_start:", input_model) - logger.info(f"Current round: {input_model.current_round}") - ''' - - logger.info(f"input_model.params.keys() = {input_model.params.keys()}") - logger.info( - f"model.state_dict().keys() = {list(model.state_dict().keys())[:10]} ... total = {len(model.state_dict())}") - # log type(model) - logger.info(f"model class: {type(model)}") - - #model = input_model.model - trainer.fit( model=model, train_dataloaders=train_dl, @@ -243,81 +383,28 @@ def train_model_( ) shutil.copy(model_checkpoint.best_model_path, output_dir / "model.ckpt") - return LitVisionTransformer.load_from_checkpoint(model_checkpoint.best_model_path) + # Reload the best model using the same class as the input model + ModelClass = type(model) + return ModelClass.load_from_checkpoint(model_checkpoint.best_model_path) -def setup_model_for_training( +def _compute_class_weights_and_check_categories( *, - patient_to_data: Mapping[PatientId, PatientData[GroundTruth]], - categories: Sequence[Category] | None, - bag_size: int, - batch_size: int, - num_workers: int, - train_transform: Callable[[torch.Tensor], torch.Tensor] | None, - use_alibi: bool, - # Metadata, has no effect on model training - ground_truth_label: PandasLabel, - clini_table: Path, - slide_table: Path, - feature_dir: Path, -) -> tuple[ - lightning.LightningModule, - DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], - DataLoader[tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets]], -]: - """Creates a model and dataloaders for training""" - - # Do a stratified train-validation split - ground_truths = [ - patient_data.ground_truth - for patient_data in patient_to_data.values() - if patient_data.ground_truth is not None - ] - - if len(ground_truths) != len(patient_to_data): - raise ValueError( - "patient_to_data must have a ground truth defined for all targets!" - ) - - train_patients, valid_patients = cast( - tuple[Sequence[PatientId], Sequence[PatientId]], - train_test_split( - list(patient_to_data), stratify=ground_truths, shuffle=True, random_state=0 - ), - ) - - train_dl, train_categories = dataloader_from_patient_data( - patient_data=[patient_to_data[patient] for patient in train_patients], - categories=categories, - bag_size=bag_size, - batch_size=batch_size, - shuffle=True, - num_workers=num_workers, - transform=train_transform, - ) - del categories # Let's not accidentally reuse the original categories - valid_dl, _ = dataloader_from_patient_data( - patient_data=[patient_to_data[patient] for patient in valid_patients], - bag_size=None, # Use all the patient data for validation - categories=train_categories, - batch_size=1, - shuffle=False, - num_workers=num_workers, - transform=None, - ) - if overlap := set(train_patients) & set(valid_patients): - raise RuntimeError( - f"unreachable: unexpected overlap between training and validation set: {overlap}" - ) - - # Sample one bag to infer the input dimensions of the model - bags, coords, bag_sizes, targets = cast( - tuple[Bags, CoordinatesBatch, BagSizes, EncodedTargets], next(iter(train_dl)) - ) - _, _, dim_feats = bags.shape - - # Weigh classes inversely to their occurrence - category_counts = cast(BagDataset, train_dl.dataset).ground_truths.sum(dim=0) + train_dl: DataLoader, + feature_type: str, + train_categories: Sequence[str], +) -> torch.Tensor: + """ + Computes class weights and checks for category issues. + Logs warnings if there are too few or underpopulated categories. + Returns normalized category weights as a torch.Tensor. + """ + if feature_type == "tile": + category_counts = cast(BagDataset, train_dl.dataset).ground_truths.sum(dim=0) + else: + category_counts = cast( + PatientFeatureDataset, train_dl.dataset + ).ground_truths.sum(dim=0) cat_ratio_reciprocal = category_counts.sum() / category_counts category_weights = cat_ratio_reciprocal / cat_ratio_reciprocal.sum() @@ -325,7 +412,7 @@ def setup_model_for_training( raise ValueError(f"not enough categories to train on: {train_categories}") elif any(category_counts < 16): underpopulated_categories = { - category: count + category: int(count) for category, count in zip(train_categories, category_counts, strict=True) if count < 16 } @@ -333,253 +420,21 @@ def setup_model_for_training( f"Some categories do not have enough samples to meaningfully train a model: {underpopulated_categories}. " "You may want to consider removing these categories; the model will likely overfit on the few samples available." ) + return category_weights - # Train the model - model_info = MODEL_REGISTRY['vit'] - ModelClass = model_info["model_class"] - ''' - categories = ['WT', 'MUT'] - dim_input = 1536 - category_weights =[0.8676, 0.1324] - dim_model = 512 - dim_feedforward=512 - n_heads=8 - n_layers=2 - dropout=0.25 -use_alibi=False - ground_truth_label='isMSIH' - train_patients=['TCGA-AH-6644', 'TCGA-AA-3664', 'TCGA-AZ-4614', 'TCGA-D5-5537', 'TCGA-G4-6628', 'TCGA-EI-6882', 'TCGA-AF-2689', 'TCGA-F4-6805', 'TCGA-AZ-4681', 'TCGA-AG-3882', 'TCGA-A6-6648', 'TCGA-AA-A017', 'TCGA-G4-6303', 'TCGA-EI-6508', 'TCGA-AF-6136', 'TCGA-A6-6654', 'TCGA-AD-6895', 'TCGA-CM-5860', 'TCGA-A6-4105', 'TCGA-F5-6810', 'TCGA-QG-A5YV', 'TCGA-DC-6155', 'TCGA-F5-6812', 'TCGA-QL-A97D', 'TCGA-AZ-4615', 'TCGA-CK-6751', 'TCGA-AH-6547', 'TCGA-AA-3980', 'TCGA-CL-5918', 'TCGA-DM-A282', 'TCGA-AA-3514', 'TCGA-EI-6509', 'TCGA-AF-3911', 'TCGA-CL-5917', 'TCGA-A6-2686', 'TCGA-AA-3950', 'TCGA-AA-3549', 'TCGA-AA-3973', 'TCGA-D5-6922', 'TCGA-AG-3887', 'TCGA-CA-6717', 'TCGA-CM-6171', 'TCGA-AD-6964', 'TCGA-A6-6142', 'TCGA-AD-6890', 'TCGA-A6-6138', 'TCGA-AD-A5EK', 'TCGA-D5-6929', 'TCGA-G4-6588', 'TCGA-AZ-4616', 'TCGA-AA-3561', 'TCGA-QG-A5Z2', 'TCGA-DM-A1HA', 'TCGA-CL-4957', 'TCGA-CA-5255', 'TCGA-DC-6158', 'TCGA-G4-6295', 'TCGA-AY-A69D', 'TCGA-DM-A1D6', 'TCGA-AG-3896', 'TCGA-A6-6652', 'TCGA-CM-6680', 'TCGA-AA-3821', 'TCGA-D5-5540', 'TCGA-DC-5869', 'TCGA-AG-4008', 'TCGA-CK-4952', 'TCGA-AA-3875', 'TCGA-AA-A02W', 'TCGA-EI-6514', 'TCGA-DM-A288', 'TCGA-AG-3890', 'TCGA-AA-3979', 'TCGA-NH-A5IV', 'TCGA-G4-6297', 'TCGA-D5-6927', 'TCGA-AA-3680', 'TCGA-NH-A8F8', 'TCGA-A6-5662', 'TCGA-A6-2674', 'TCGA-AG-A01N', 'TCGA-DM-A1D8', 'TCGA-AA-3837', 'TCGA-AA-A01I', 'TCGA-DM-A0XF', 'TCGA-F4-6855', 'TCGA-CA-6715', 'TCGA-AA-3975', 'TCGA-AA-3989', 'TCGA-AG-A016', 'TCGA-A6-5666', 'TCGA-CK-6746', 'TCGA-CM-4752', 'TCGA-CM-6676', 'TCGA-D5-6541', 'TCGA-A6-5667', 'TCGA-AF-2691', 'TCGA-AD-A5EJ', 'TCGA-F5-6864', 'TCGA-AG-3726', 'TCGA-A6-6649', 'TCGA-AA-A03J', 'TCGA-AF-2687', 'TCGA-AA-3858', 'TCGA-G4-6321', 'TCGA-G5-6641', 'TCGA-G4-6309', 'TCGA-D5-5538', 'TCGA-DM-A28K', 'TCGA-AG-3878', 'TCGA-AZ-6598', 'TCGA-AA-3688', 'TCGA-AG-A01W', 'TCGA-AA-3517', 'TCGA-D5-6898', 'TCGA-CM-5341', 'TCGA-CM-6167', 'TCGA-D5-6531', 'TCGA-DC-6683', 'TCGA-AF-2690', 'TCGA-CM-6161', 'TCGA-A6-5660', 'TCGA-NH-A8F7', 'TCGA-CM-6163', 'TCGA-AA-3968', 'TCGA-CM-5868', 'TCGA-AA-3976', 'TCGA-A6-A565', 'TCGA-AM-5821', 'TCGA-AD-6965', 'TCGA-AY-A71X', 'TCGA-AZ-4682', 'TCGA-AG-3892', 'TCGA-A6-2675', 'TCGA-F4-6569', 'TCGA-AG-3893', 'TCGA-CM-5862', 'TCGA-AZ-6606', 'TCGA-CM-6678', 'TCGA-D5-6931', 'TCGA-D5-6923', 'TCGA-AG-3575', 'TCGA-AA-A00Z', 'TCGA-D5-6540', 'TCGA-A6-3808', 'TCGA-CM-4743', 'TCGA-DC-4749', 'TCGA-T9-A92H', 'TCGA-A6-3810', 'TCGA-AD-6889', 'TCGA-G4-6320', 'TCGA-AA-3971', 'TCGA-AZ-4308', 'TCGA-D5-7000', 'TCGA-DM-A28A', 'TCGA-AG-A01L', 'TCGA-EI-6512', 'TCGA-A6-5661', 'TCGA-AG-3599', 'TCGA-AG-3902', 'TCGA-AD-6548', 'TCGA-AA-A022', 'TCGA-AG-3580', 'TCGA-A6-A567', 'TCGA-AA-3679', 'TCGA-CA-5256', 'TCGA-F4-6808', 'TCGA-D5-6536', 'TCGA-DM-A1D9', 'TCGA-AG-3885', 'TCGA-DM-A1D0', 'TCGA-CM-6674', 'TCGA-EI-6507', 'TCGA-F4-6459', 'TCGA-A6-6137', 'TCGA-AD-6899', 'TCGA-EI-6885', 'TCGA-WS-AB45', 'TCGA-D5-6930', 'TCGA-AU-6004', 'TCGA-AY-6196', 'TCGA-AA-A010', 'TCGA-AG-A00C', 'TCGA-4T-AA8H', 'TCGA-G4-6302', 'TCGA-AA-3966', 'TCGA-AF-2693', 'TCGA-D5-6926', 'TCGA-DM-A0XD', 'TCGA-AA-3854', 'TCGA-D5-6932', 'TCGA-EI-7004', 'TCGA-AG-3594', 'TCGA-EI-6510', 'TCGA-AA-3715', 'TCGA-AA-A01X', 'TCGA-F4-6570', 'TCGA-EI-6511', 'TCGA-CM-5861', 'TCGA-CA-5254', 'TCGA-G4-6317', 'TCGA-DM-A28M', 'TCGA-EI-6506', 'TCGA-AG-A020', 'TCGA-AG-3583', 'TCGA-G4-6294', 'TCGA-CM-6165', 'TCGA-D5-6535', 'TCGA-D5-5541', 'TCGA-DM-A1DB', 'TCGA-AG-A01J', 'TCGA-AG-4001', 'TCGA-AG-A00Y', 'TCGA-AA-3949', 'TCGA-AA-3842', 'TCGA-CA-6716', 'TCGA-CM-5348', 'TCGA-AA-3696', 'TCGA-AA-3833', 'TCGA-NH-A6GC', 'TCGA-CK-4947', 'TCGA-AA-3846', 'TCGA-A6-2677', 'TCGA-A6-2684', 'TCGA-NH-A6GA', 'TCGA-F5-6811', 'TCGA-A6-4107', 'TCGA-F5-6571', 'TCGA-CK-6747', 'TCGA-AA-3666', 'TCGA-CK-5914', 'TCGA-G4-6627', 'TCGA-DC-6681', 'TCGA-AZ-4315', 'TCGA-DT-5265', 'TCGA-CM-4751', 'TCGA-CM-6168', 'TCGA-AY-5543', 'TCGA-AZ-6607', 'TCGA-AG-3901', 'TCGA-AA-3695', 'TCGA-AD-6963', 'TCGA-CK-5913', 'TCGA-AF-2692', 'TCGA-G4-6307', 'TCGA-AA-A01T', 'TCGA-DY-A1DG', 'TCGA-AA-A01C', 'TCGA-AZ-6603', 'TCGA-AG-3727', 'TCGA-AG-3909', 'TCGA-D5-6539', 'TCGA-AZ-5407', 'TCGA-NH-A6GB', 'TCGA-AA-3851', 'TCGA-AF-5654', 'TCGA-AA-3530', 'TCGA-F5-6813', 'TCGA-AA-3845', 'TCGA-AG-3584', 'TCGA-AM-5820', 'TCGA-AA-3982', 'TCGA-D5-6924', 'TCGA-AA-3984', 'TCGA-5M-AATE', 'TCGA-AG-3894', 'TCGA-AU-3779', 'TCGA-A6-2671', 'TCGA-DC-6682', 'TCGA-A6-6653', 'TCGA-DM-A28E', 'TCGA-AZ-5403', 'TCGA-CK-4948', 'TCGA-AF-6655', 'TCGA-AA-A01P', 'TCGA-AA-A024', 'TCGA-F4-6809', 'TCGA-AA-3548', 'TCGA-QG-A5YW', 'TCGA-AA-3818', 'TCGA-EI-6513', 'TCGA-AY-4071', 'TCGA-D5-6532', 'TCGA-AZ-6600', 'TCGA-CM-6172', 'TCGA-AG-A01Y', 'TCGA-AA-3675', 'TCGA-AD-6888', 'TCGA-AH-6897', 'TCGA-AA-3852', 'TCGA-CM-4747', 'TCGA-AG-A02N', 'TCGA-AA-A02F', 'TCGA-DM-A1D7', 'TCGA-AA-3678', 'TCGA-CM-6677', 'TCGA-AA-A004', 'TCGA-D5-5539', 'TCGA-F5-6861', 'TCGA-AA-3544', 'TCGA-F4-6463', 'TCGA-AG-4015', 'TCGA-AF-3400', 'TCGA-CM-6169', 'TCGA-F4-6461', 'TCGA-AD-6901', 'TCGA-DM-A280', 'TCGA-AA-3819', 'TCGA-DM-A0X9', 'TCGA-G4-6315', 'TCGA-AA-3850', 'TCGA-CA-5797', 'TCGA-AA-3856', 'TCGA-AY-6386', 'TCGA-AG-3598', 'TCGA-CM-5863', 'TCGA-AG-3605', 'TCGA-AZ-6599', 'TCGA-CM-5344', 'TCGA-AA-3693', 'TCGA-G4-6306', 'TCGA-F4-6854', 'TCGA-A6-5656', 'TCGA-DY-A0XA', 'TCGA-A6-2685', 'TCGA-AY-6197', 'TCGA-4N-A93T', 'TCGA-NH-A50U', 'TCGA-A6-2683', 'TCGA-AA-3952', 'TCGA-DC-6160', 'TCGA-A6-3807', 'TCGA-AA-3522', 'TCGA-G4-6304', 'TCGA-AA-3526', 'TCGA-DC-4745', 'TCGA-AA-3877', 'TCGA-CM-6162', 'TCGA-AA-3692', 'TCGA-AA-3524', 'TCGA-AG-3881', 'TCGA-AA-3681', 'TCGA-CK-4951', 'TCGA-AA-3534', 'TCGA-A6-A56B', 'TCGA-DM-A1D4', 'TCGA-5M-AAT5', 'TCGA-AG-4022', 'TCGA-AA-3812', 'TCGA-D5-6529', 'TCGA-AA-3673', 'TCGA-EI-6917', 'TCGA-EI-6884', 'TCGA-AG-A011', 'TCGA-AY-A8YK', 'TCGA-EI-6881', 'TCGA-A6-2678', 'TCGA-AA-3947', 'TCGA-A6-5659', 'TCGA-AA-3994', 'TCGA-D5-6534', 'TCGA-AG-3574', 'TCGA-F5-6465', 'TCGA-CA-6719', 'TCGA-AA-3532', 'TCGA-G4-6322', 'TCGA-AF-A56K', 'TCGA-AA-3864', 'TCGA-AA-3685', 'TCGA-AA-3986'] - valid_patients=['TCGA-AG-4021', 'TCGA-AG-A02X', 'TCGA-AA-A02H', 'TCGA-F4-6807', 'TCGA-G4-6311', 'TCGA-AF-A56L', 'TCGA-CM-4748', 'TCGA-AZ-6605', 'TCGA-G4-6299', 'TCGA-AA-3667', 'TCGA-5M-AAT4', 'TCGA-5M-AAT6', 'TCGA-F4-6704', 'TCGA-AH-6544', 'TCGA-3L-AA1B', 'TCGA-CK-4950', 'TCGA-AA-3956', 'TCGA-G4-6323', 'TCGA-AA-3831', 'TCGA-AH-6903', 'TCGA-CA-5796', 'TCGA-AA-3684', 'TCGA-EI-7002', 'TCGA-F4-6460', 'TCGA-AA-3844', 'TCGA-AA-3848', 'TCGA-D5-6928', 'TCGA-A6-5665', 'TCGA-AA-A01S', 'TCGA-DM-A28H', 'TCGA-DC-6157', 'TCGA-DY-A1DD', 'TCGA-AA-3519', 'TCGA-AA-3855', 'TCGA-F4-6806', 'TCGA-AA-3520', 'TCGA-CM-6675', 'TCGA-AG-3898', 'TCGA-CK-6748', 'TCGA-G4-6298', 'TCGA-G4-6626', 'TCGA-QG-A5Z1', 'TCGA-DM-A28G', 'TCGA-A6-2681', 'TCGA-DM-A28F', 'TCGA-G4-6586', 'TCGA-CK-5916', 'TCGA-AG-A002', 'TCGA-AG-A026', 'TCGA-AA-A02E', 'TCGA-F5-6464', 'TCGA-EI-6883', 'TCGA-F4-6856', 'TCGA-AG-A015', 'TCGA-CM-5349', 'TCGA-DM-A1DA', 'TCGA-D5-6530', 'TCGA-A6-A566', 'TCGA-NH-A50V', 'TCGA-F5-6814', 'TCGA-CM-6166', 'TCGA-CI-6622', 'TCGA-AA-3977', 'TCGA-CA-6718', 'TCGA-AA-3841', 'TCGA-AA-3521', 'TCGA-NH-A50T', 'TCGA-AA-A01R', 'TCGA-AG-A008', 'TCGA-CM-4746', 'TCGA-AA-A02R', 'TCGA-DC-6154', 'TCGA-AA-3531', 'TCGA-F5-6863', 'TCGA-F4-6703', 'TCGA-AY-A54L', 'TCGA-AA-3811', 'TCGA-AA-3814', 'TCGA-CK-5912', 'TCGA-A6-3809', 'TCGA-A6-5657', 'TCGA-EF-5830', 'TCGA-CM-6164', 'TCGA-SS-A7HO', 'TCGA-CM-5864', 'TCGA-AF-4110', 'TCGA-AF-6672', 'TCGA-AA-3866', 'TCGA-AA-A01V', 'TCGA-AA-A00N', 'TCGA-D5-6538', 'TCGA-CM-6170', 'TCGA-D5-6537', 'TCGA-AA-A02Y', 'TCGA-DY-A1DC', 'TCGA-AG-3883', 'TCGA-AD-5900', 'TCGA-AA-3955', 'TCGA-AG-3581', 'TCGA-A6-5664', 'TCGA-DY-A1DF', 'TCGA-AA-A02O', 'TCGA-AA-3527', 'TCGA-F5-6702', 'TCGA-AH-6643', 'TCGA-G4-6293', 'TCGA-DC-5337', 'TCGA-AA-A03F', 'TCGA-CI-6624', 'TCGA-AG-3602', 'TCGA-AA-3538', 'TCGA-AA-A01Z', 'TCGA-AA-3558', 'TCGA-AA-3560', 'TCGA-AA-3867', 'TCGA-A6-6650', 'TCGA-CK-5915', 'TCGA-A6-6651', 'TCGA-AY-4070', 'TCGA-AF-A56N', 'TCGA-D5-6533', 'TCGA-AA-3529', 'TCGA-AG-A032', 'TCGA-QG-A5YX'] - - ''' - model = LitVisionTransformer(categories=['WT', 'MUT'], - dim_input=1536, - category_weights=tensor([0.8676, 0.1324]), - dim_model=512, - dim_feedforward=512, - n_heads=8, - n_layers=2, - dropout=0.25, - use_alibi=False, - ground_truth_label='isMSIH', - train_patients=['TCGA-AH-6644', 'TCGA-AA-3664', 'TCGA-AZ-4614', 'TCGA-D5-5537', - 'TCGA-G4-6628', 'TCGA-EI-6882', 'TCGA-AF-2689', 'TCGA-F4-6805', - 'TCGA-AZ-4681', 'TCGA-AG-3882', 'TCGA-A6-6648', 'TCGA-AA-A017', - 'TCGA-G4-6303', 'TCGA-EI-6508', 'TCGA-AF-6136', 'TCGA-A6-6654', - 'TCGA-AD-6895', 'TCGA-CM-5860', 'TCGA-A6-4105', 'TCGA-F5-6810', - 'TCGA-QG-A5YV', 'TCGA-DC-6155', 'TCGA-F5-6812', 'TCGA-QL-A97D', - 'TCGA-AZ-4615', 'TCGA-CK-6751', 'TCGA-AH-6547', 'TCGA-AA-3980', - 'TCGA-CL-5918', 'TCGA-DM-A282', 'TCGA-AA-3514', 'TCGA-EI-6509', - 'TCGA-AF-3911', 'TCGA-CL-5917', 'TCGA-A6-2686', 'TCGA-AA-3950', - 'TCGA-AA-3549', 'TCGA-AA-3973', 'TCGA-D5-6922', 'TCGA-AG-3887', - 'TCGA-CA-6717', 'TCGA-CM-6171', 'TCGA-AD-6964', 'TCGA-A6-6142', - 'TCGA-AD-6890', 'TCGA-A6-6138', 'TCGA-AD-A5EK', 'TCGA-D5-6929', - 'TCGA-G4-6588', 'TCGA-AZ-4616', 'TCGA-AA-3561', 'TCGA-QG-A5Z2', - 'TCGA-DM-A1HA', 'TCGA-CL-4957', 'TCGA-CA-5255', 'TCGA-DC-6158', - 'TCGA-G4-6295', 'TCGA-AY-A69D', 'TCGA-DM-A1D6', 'TCGA-AG-3896', - 'TCGA-A6-6652', 'TCGA-CM-6680', 'TCGA-AA-3821', 'TCGA-D5-5540', - 'TCGA-DC-5869', 'TCGA-AG-4008', 'TCGA-CK-4952', 'TCGA-AA-3875', - 'TCGA-AA-A02W', 'TCGA-EI-6514', 'TCGA-DM-A288', 'TCGA-AG-3890', - 'TCGA-AA-3979', 'TCGA-NH-A5IV', 'TCGA-G4-6297', 'TCGA-D5-6927', - 'TCGA-AA-3680', 'TCGA-NH-A8F8', 'TCGA-A6-5662', 'TCGA-A6-2674', - 'TCGA-AG-A01N', 'TCGA-DM-A1D8', 'TCGA-AA-3837', 'TCGA-AA-A01I', - 'TCGA-DM-A0XF', 'TCGA-F4-6855', 'TCGA-CA-6715', 'TCGA-AA-3975', - 'TCGA-AA-3989', 'TCGA-AG-A016', 'TCGA-A6-5666', 'TCGA-CK-6746', - 'TCGA-CM-4752', 'TCGA-CM-6676', 'TCGA-D5-6541', 'TCGA-A6-5667', - 'TCGA-AF-2691', 'TCGA-AD-A5EJ', 'TCGA-F5-6864', 'TCGA-AG-3726', - 'TCGA-A6-6649', 'TCGA-AA-A03J', 'TCGA-AF-2687', 'TCGA-AA-3858', - 'TCGA-G4-6321', 'TCGA-G5-6641', 'TCGA-G4-6309', 'TCGA-D5-5538', - 'TCGA-DM-A28K', 'TCGA-AG-3878', 'TCGA-AZ-6598', 'TCGA-AA-3688', - 'TCGA-AG-A01W', 'TCGA-AA-3517', 'TCGA-D5-6898', 'TCGA-CM-5341', - 'TCGA-CM-6167', 'TCGA-D5-6531', 'TCGA-DC-6683', 'TCGA-AF-2690', - 'TCGA-CM-6161', 'TCGA-A6-5660', 'TCGA-NH-A8F7', 'TCGA-CM-6163', - 'TCGA-AA-3968', 'TCGA-CM-5868', 'TCGA-AA-3976', 'TCGA-A6-A565', - 'TCGA-AM-5821', 'TCGA-AD-6965', 'TCGA-AY-A71X', 'TCGA-AZ-4682', - 'TCGA-AG-3892', 'TCGA-A6-2675', 'TCGA-F4-6569', 'TCGA-AG-3893', - 'TCGA-CM-5862', 'TCGA-AZ-6606', 'TCGA-CM-6678', 'TCGA-D5-6931', - 'TCGA-D5-6923', 'TCGA-AG-3575', 'TCGA-AA-A00Z', 'TCGA-D5-6540', - 'TCGA-A6-3808', 'TCGA-CM-4743', 'TCGA-DC-4749', 'TCGA-T9-A92H', - 'TCGA-A6-3810', 'TCGA-AD-6889', 'TCGA-G4-6320', 'TCGA-AA-3971', - 'TCGA-AZ-4308', 'TCGA-D5-7000', 'TCGA-DM-A28A', 'TCGA-AG-A01L', - 'TCGA-EI-6512', 'TCGA-A6-5661', 'TCGA-AG-3599', 'TCGA-AG-3902', - 'TCGA-AD-6548', 'TCGA-AA-A022', 'TCGA-AG-3580', 'TCGA-A6-A567', - 'TCGA-AA-3679', 'TCGA-CA-5256', 'TCGA-F4-6808', 'TCGA-D5-6536', - 'TCGA-DM-A1D9', 'TCGA-AG-3885', 'TCGA-DM-A1D0', 'TCGA-CM-6674', - 'TCGA-EI-6507', 'TCGA-F4-6459', 'TCGA-A6-6137', 'TCGA-AD-6899', - 'TCGA-EI-6885', 'TCGA-WS-AB45', 'TCGA-D5-6930', 'TCGA-AU-6004', - 'TCGA-AY-6196', 'TCGA-AA-A010', 'TCGA-AG-A00C', 'TCGA-4T-AA8H', - 'TCGA-G4-6302', 'TCGA-AA-3966', 'TCGA-AF-2693', 'TCGA-D5-6926', - 'TCGA-DM-A0XD', 'TCGA-AA-3854', 'TCGA-D5-6932', 'TCGA-EI-7004', - 'TCGA-AG-3594', 'TCGA-EI-6510', 'TCGA-AA-3715', 'TCGA-AA-A01X', - 'TCGA-F4-6570', 'TCGA-EI-6511', 'TCGA-CM-5861', 'TCGA-CA-5254', - 'TCGA-G4-6317', 'TCGA-DM-A28M', 'TCGA-EI-6506', 'TCGA-AG-A020', - 'TCGA-AG-3583', 'TCGA-G4-6294', 'TCGA-CM-6165', 'TCGA-D5-6535', - 'TCGA-D5-5541', 'TCGA-DM-A1DB', 'TCGA-AG-A01J', 'TCGA-AG-4001', - 'TCGA-AG-A00Y', 'TCGA-AA-3949', 'TCGA-AA-3842', 'TCGA-CA-6716', - 'TCGA-CM-5348', 'TCGA-AA-3696', 'TCGA-AA-3833', 'TCGA-NH-A6GC', - 'TCGA-CK-4947', 'TCGA-AA-3846', 'TCGA-A6-2677', 'TCGA-A6-2684', - 'TCGA-NH-A6GA', 'TCGA-F5-6811', 'TCGA-A6-4107', 'TCGA-F5-6571', - 'TCGA-CK-6747', 'TCGA-AA-3666', 'TCGA-CK-5914', 'TCGA-G4-6627', - 'TCGA-DC-6681', 'TCGA-AZ-4315', 'TCGA-DT-5265', 'TCGA-CM-4751', - 'TCGA-CM-6168', 'TCGA-AY-5543', 'TCGA-AZ-6607', 'TCGA-AG-3901', - 'TCGA-AA-3695', 'TCGA-AD-6963', 'TCGA-CK-5913', 'TCGA-AF-2692', - 'TCGA-G4-6307', 'TCGA-AA-A01T', 'TCGA-DY-A1DG', 'TCGA-AA-A01C', - 'TCGA-AZ-6603', 'TCGA-AG-3727', 'TCGA-AG-3909', 'TCGA-D5-6539', - 'TCGA-AZ-5407', 'TCGA-NH-A6GB', 'TCGA-AA-3851', 'TCGA-AF-5654', - 'TCGA-AA-3530', 'TCGA-F5-6813', 'TCGA-AA-3845', 'TCGA-AG-3584', - 'TCGA-AM-5820', 'TCGA-AA-3982', 'TCGA-D5-6924', 'TCGA-AA-3984', - 'TCGA-5M-AATE', 'TCGA-AG-3894', 'TCGA-AU-3779', 'TCGA-A6-2671', - 'TCGA-DC-6682', 'TCGA-A6-6653', 'TCGA-DM-A28E', 'TCGA-AZ-5403', - 'TCGA-CK-4948', 'TCGA-AF-6655', 'TCGA-AA-A01P', 'TCGA-AA-A024', - 'TCGA-F4-6809', 'TCGA-AA-3548', 'TCGA-QG-A5YW', 'TCGA-AA-3818', - 'TCGA-EI-6513', 'TCGA-AY-4071', 'TCGA-D5-6532', 'TCGA-AZ-6600', - 'TCGA-CM-6172', 'TCGA-AG-A01Y', 'TCGA-AA-3675', 'TCGA-AD-6888', - 'TCGA-AH-6897', 'TCGA-AA-3852', 'TCGA-CM-4747', 'TCGA-AG-A02N', - 'TCGA-AA-A02F', 'TCGA-DM-A1D7', 'TCGA-AA-3678', 'TCGA-CM-6677', - 'TCGA-AA-A004', 'TCGA-D5-5539', 'TCGA-F5-6861', 'TCGA-AA-3544', - 'TCGA-F4-6463', 'TCGA-AG-4015', 'TCGA-AF-3400', 'TCGA-CM-6169', - 'TCGA-F4-6461', 'TCGA-AD-6901', 'TCGA-DM-A280', 'TCGA-AA-3819', - 'TCGA-DM-A0X9', 'TCGA-G4-6315', 'TCGA-AA-3850', 'TCGA-CA-5797', - 'TCGA-AA-3856', 'TCGA-AY-6386', 'TCGA-AG-3598', 'TCGA-CM-5863', - 'TCGA-AG-3605', 'TCGA-AZ-6599', 'TCGA-CM-5344', 'TCGA-AA-3693', - 'TCGA-G4-6306', 'TCGA-F4-6854', 'TCGA-A6-5656', 'TCGA-DY-A0XA', - 'TCGA-A6-2685', 'TCGA-AY-6197', 'TCGA-4N-A93T', 'TCGA-NH-A50U', - 'TCGA-A6-2683', 'TCGA-AA-3952', 'TCGA-DC-6160', 'TCGA-A6-3807', - 'TCGA-AA-3522', 'TCGA-G4-6304', 'TCGA-AA-3526', 'TCGA-DC-4745', - 'TCGA-AA-3877', 'TCGA-CM-6162', 'TCGA-AA-3692', 'TCGA-AA-3524', - 'TCGA-AG-3881', 'TCGA-AA-3681', 'TCGA-CK-4951', 'TCGA-AA-3534', - 'TCGA-A6-A56B', 'TCGA-DM-A1D4', 'TCGA-5M-AAT5', 'TCGA-AG-4022', - 'TCGA-AA-3812', 'TCGA-D5-6529', 'TCGA-AA-3673', 'TCGA-EI-6917', - 'TCGA-EI-6884', 'TCGA-AG-A011', 'TCGA-AY-A8YK', 'TCGA-EI-6881', - 'TCGA-A6-2678', 'TCGA-AA-3947', 'TCGA-A6-5659', 'TCGA-AA-3994', - 'TCGA-D5-6534', 'TCGA-AG-3574', 'TCGA-F5-6465', 'TCGA-CA-6719', - 'TCGA-AA-3532', 'TCGA-G4-6322', 'TCGA-AF-A56K', 'TCGA-AA-3864', - 'TCGA-AA-3685', 'TCGA-AA-3986'], - valid_patients=['TCGA-AG-4021', 'TCGA-AG-A02X', 'TCGA-AA-A02H', 'TCGA-F4-6807', - 'TCGA-G4-6311', 'TCGA-AF-A56L', 'TCGA-CM-4748', 'TCGA-AZ-6605', - 'TCGA-G4-6299', 'TCGA-AA-3667', 'TCGA-5M-AAT4', 'TCGA-5M-AAT6', - 'TCGA-F4-6704', 'TCGA-AH-6544', 'TCGA-3L-AA1B', 'TCGA-CK-4950', - 'TCGA-AA-3956', 'TCGA-G4-6323', 'TCGA-AA-3831', 'TCGA-AH-6903', - 'TCGA-CA-5796', 'TCGA-AA-3684', 'TCGA-EI-7002', 'TCGA-F4-6460', - 'TCGA-AA-3844', 'TCGA-AA-3848', 'TCGA-D5-6928', 'TCGA-A6-5665', - 'TCGA-AA-A01S', 'TCGA-DM-A28H', 'TCGA-DC-6157', 'TCGA-DY-A1DD', - 'TCGA-AA-3519', 'TCGA-AA-3855', 'TCGA-F4-6806', 'TCGA-AA-3520', - 'TCGA-CM-6675', 'TCGA-AG-3898', 'TCGA-CK-6748', 'TCGA-G4-6298', - 'TCGA-G4-6626', 'TCGA-QG-A5Z1', 'TCGA-DM-A28G', 'TCGA-A6-2681', - 'TCGA-DM-A28F', 'TCGA-G4-6586', 'TCGA-CK-5916', 'TCGA-AG-A002', - 'TCGA-AG-A026', 'TCGA-AA-A02E', 'TCGA-F5-6464', 'TCGA-EI-6883', - 'TCGA-F4-6856', 'TCGA-AG-A015', 'TCGA-CM-5349', 'TCGA-DM-A1DA', - 'TCGA-D5-6530', 'TCGA-A6-A566', 'TCGA-NH-A50V', 'TCGA-F5-6814', - 'TCGA-CM-6166', 'TCGA-CI-6622', 'TCGA-AA-3977', 'TCGA-CA-6718', - 'TCGA-AA-3841', 'TCGA-AA-3521', 'TCGA-NH-A50T', 'TCGA-AA-A01R', - 'TCGA-AG-A008', 'TCGA-CM-4746', 'TCGA-AA-A02R', 'TCGA-DC-6154', - 'TCGA-AA-3531', 'TCGA-F5-6863', 'TCGA-F4-6703', 'TCGA-AY-A54L', - 'TCGA-AA-3811', 'TCGA-AA-3814', 'TCGA-CK-5912', 'TCGA-A6-3809', - 'TCGA-A6-5657', 'TCGA-EF-5830', 'TCGA-CM-6164', 'TCGA-SS-A7HO', - 'TCGA-CM-5864', 'TCGA-AF-4110', 'TCGA-AF-6672', 'TCGA-AA-3866', - 'TCGA-AA-A01V', 'TCGA-AA-A00N', 'TCGA-D5-6538', 'TCGA-CM-6170', - 'TCGA-D5-6537', 'TCGA-AA-A02Y', 'TCGA-DY-A1DC', 'TCGA-AG-3883', - 'TCGA-AD-5900', 'TCGA-AA-3955', 'TCGA-AG-3581', 'TCGA-A6-5664', - 'TCGA-DY-A1DF', 'TCGA-AA-A02O', 'TCGA-AA-3527', 'TCGA-F5-6702', - 'TCGA-AH-6643', 'TCGA-G4-6293', 'TCGA-DC-5337', 'TCGA-AA-A03F', - 'TCGA-CI-6624', 'TCGA-AG-3602', 'TCGA-AA-3538', 'TCGA-AA-A01Z', - 'TCGA-AA-3558', 'TCGA-AA-3560', 'TCGA-AA-3867', 'TCGA-A6-6650', - 'TCGA-CK-5915', 'TCGA-A6-6651', 'TCGA-AY-4070', 'TCGA-AF-A56N', - 'TCGA-D5-6533', 'TCGA-AA-3529', 'TCGA-AG-A032', 'TCGA-QG-A5YX'], - ) - - ''' - model = ModelClass( - categories=[np.str_('MUT'), np.str_('WT')], - dim_input = 1536, - category_weights = tensor([0.8676, 0.1324]), - dim_model=512, - dim_feedforward=512, - n_heads=8, - n_layers=2, - dropout=0.25, - - use_alibi=use_alibi, - ground_truth_label=ground_truth_label, - train_patients=train_patients, - valid_patients=valid_patients, - clini_table=clini_table, - slide_table=slide_table, - feature_dir=feature_dir,) - ''' - logger.info( - f"Model 'instantiated with {len(model.state_dict())} parameters." - ) - return model, train_dl, valid_dl - - -def _add_file_handle_(logger: logging.Logger, *, output_dir: Path) -> None: - output_dir.mkdir(exist_ok=True, parents=True) - - file_handler = logging.FileHandler(output_dir / "logfile.log") - file_handler.setLevel(logging.DEBUG) - - formatter = logging.Formatter("%(asctime)s\t%(levelname)s\t%(message)s") - file_handler.setFormatter(formatter) - - logger.addHandler(file_handler) - - -def main(): - '''' - output_dir = os.getenv("TRAINING_OUTPUT_DIR") - print('output_dir:', output_dir) - #_add_file_handle_(_logger, output_dir=Path(output_dir)) - #_logger.info("Using training configuration from environment variables.") - - train_categorical_model_( - output_dir=Path(output_dir), - clini_table=Path(os.getenv("TRAINING_CLINI_TABLE")), - slide_table=Path(os.getenv("TRAINING_SLIDE_TABLE")), - feature_dir=Path(os.getenv("TRAINING_FEATURE_DIR")), - patient_label=os.getenv("TRAINING_PATIENT_LABEL"), - ground_truth_label=os.getenv("TRAINING_GROUND_TRUTH_LABEL"), - filename_label=os.getenv("TRAINING_FILENAME_LABEL"), - categories=os.getenv("TRAINING_CATEGORIES").split(","), - # Dataset and loader parameters - bag_size=int(os.getenv("TRAINING_BAG_SIZE")), - num_workers=int(os.getenv("TRAINING_NUM_WORKERS")), - # Training parameters - batch_size=int(os.getenv("TRAINING_BATCH_SIZE")), - max_epochs=int(os.getenv("TRAINING_MAX_EPOCHS")), - patience=int(os.getenv("TRAINING_PATIENCE")), - accelerator=os.getenv("TRAINING_ACCELERATOR"), - # Experimental features - use_vary_precision_transform=os.getenv("USE_VARY_PRECISION_TRANSFORM", "False").lower() == "true", - use_alibi=os.getenv("USE_ALIBI", "False").lower() == "true", - ) -''' - output_dir = "/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/STAMP_crossval_new" - #logger.info('output_dir:', output_dir) - # _add_file_handle_(_logger, output_dir=Path(output_dir)) - #_logger.info("Using training configuration from environment variables.") - - train_categorical_model_( - output_dir=Path(output_dir), - clini_table=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/TCGA-CRC-DX_clini.xlsx"), - slide_table=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/TCGA-CRC-DX_slide_h5.csv"), - feature_dir=Path("/mnt/swarm_alpha/ECDP2025/pathology_data/TCGA/TCGA-CRC/uni2-02627079"), - patient_label="PATIENT", - ground_truth_label="isMSIH", - filename_label="FILENAME", - categories=None, - # Dataset and loader parameters - bag_size=512, - num_workers=15, - # Training parameters - batch_size=64, - max_epochs=64, # 64 - patience=16, - accelerator="gpu", - # Experimental features - use_vary_precision_transform='true', - use_alibi='false', +def log_total_class_summary( + patient_to_data: Mapping[PatientId, PatientData], + categories: Sequence[Category] | None, +) -> None: + ground_truths = [ + patient_data.ground_truth + for patient_data in patient_to_data.values() + if patient_data.ground_truth is not None + ] + cats = categories or sorted(set(ground_truths)) + counter = Counter(ground_truths) + _logger.info( + f"Total samples: {len(ground_truths)} | " + + " | ".join([f"Class {cls}: {counter.get(cls, 0)}" for cls in cats]) ) -if __name__ == "__main__": - main() diff --git a/application/jobs/stamp/app/custom/modeling/config.py b/application/jobs/stamp/app/custom/modeling/config.py new file mode 100644 index 00000000..34917b27 --- /dev/null +++ b/application/jobs/stamp/app/custom/modeling/config.py @@ -0,0 +1,100 @@ +import os +from collections.abc import Sequence +from pathlib import Path + +import torch +from pydantic import BaseModel, ConfigDict, Field + +from .registry import ModelName +from .types import Category, PandasLabel + + +class TrainConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + output_dir: Path = Field(description="The directory to save the results to") + + clini_table: Path = Field(description="Excel or CSV to read clinical data from") + slide_table: Path | None = Field( + default=None, description="Excel or CSV to read patient-slide associations from" + ) + feature_dir: Path = Field(description="Directory containing feature files") + + ground_truth_label: PandasLabel = Field( + description="Name of categorical column in clinical table to train on" + ) + categories: Sequence[Category] | None = None + + patient_label: PandasLabel = "PATIENT" + filename_label: PandasLabel = "FILENAME" + + params_path: Path | None = Field( + default=None, + description="Optional: Path to a YAML file with advanced training parameters.", + ) + + # Experimental features + use_vary_precision_transform: bool = False + + +class CrossvalConfig(TrainConfig): + n_splits: int = Field(5, ge=2) + + +class DeploymentConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + + output_dir: Path + + checkpoint_paths: list[Path] + clini_table: Path | None = None + slide_table: Path + feature_dir: Path + + ground_truth_label: PandasLabel | None = None + patient_label: PandasLabel = "PATIENT" + filename_label: PandasLabel = "FILENAME" + + num_workers: int = min(os.cpu_count() or 1, 16) + accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + + +class VitModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + dim_model: int = 512 + dim_feedforward: int = 512 + n_heads: int = 8 + n_layers: int = 2 + dropout: float = 0.0 + # Experimental feature: Use ALiBi positional embedding + use_alibi: bool = False + + +class MlpModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + dim_hidden: int = 512 + num_layers: int = 2 + dropout: float = 0.25 + + +class ModelParams(BaseModel): + model_config = ConfigDict(extra="forbid") + vit: VitModelParams + mlp: MlpModelParams + + +class AdvancedConfig(BaseModel): + model_config = ConfigDict(extra="forbid") + bag_size: int = 512 + num_workers: int = min(os.cpu_count() or 1, 16) + batch_size: int = 64 + max_epochs: int = 32 + patience: int = 16 + accelerator: str = "gpu" if torch.cuda.is_available() else "cpu" + max_lr: float = 1e-4 + div_factor: float = 25.0 + model_name: ModelName | None = Field( + default=None, + description='Optional: "vit" or "mlp". Defaults based on feature type.', + ) + model_params: ModelParams diff --git a/docker_config/Dockerfile_stamp b/docker_config/Dockerfile_stamp index 82dd9ed1..b1003e46 100644 --- a/docker_config/Dockerfile_stamp +++ b/docker_config/Dockerfile_stamp @@ -50,4 +50,4 @@ RUN mkdir -p /fl_admin/transfer && ln -s /MediSwarm /fl_admin/transfer/MediSwarm WORKDIR /workspace/ #docker build -f docker_config/Dockerfile_stamp -t stamp-image . -#docker run --rm -it stamp-image:latest-image bash \ No newline at end of file +#docker run --rm -it stamp-image:latest bash \ No newline at end of file