diff --git a/.github/workflows/Dockerfile b/.github/workflows/Dockerfile index a8bb2227ab..46b0379500 100644 --- a/.github/workflows/Dockerfile +++ b/.github/workflows/Dockerfile @@ -1,31 +1,33 @@ -# Based on old ubuntu to create more compatible binaries +# Based on manylinux_2_28 (AlmaLinux 8, GLIBC 2.28) for maximum HPC compatibility +# Binaries built here will run on RHEL/Rocky/Alma 8+, Ubuntu 20.04+, and most HPC clusters # To build (e.g. for ShapeWorks 6.7): -# docker build --progress=plain -t akenmorris/ubuntu-build-box-jammy-sw67 . +# docker build --progress=plain -t akenmorris/manylinux-build-box-sw67 . # To publish: -# docker push akenmorris/ubuntu-build-box-jammy-sw67 +# docker push akenmorris/manylinux-build-box-sw67 -FROM ubuntu:jammy-20250819 AS env -MAINTAINER akenmorris@gmail.com +FROM quay.io/pypa/manylinux_2_28_x86_64 AS env +LABEL maintainer="akenmorris@gmail.com" # Set environment variables -ENV PATH=/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV PATH=/opt/rh/gcc-toolset-13/root/usr/bin:/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -ARG DEBIAN_FRONTEND=noninteractive -ENV TZ=Etc/UTC +# Enable PowerTools repo for additional development packages +RUN dnf install -y dnf-plugins-core && dnf config-manager --set-enabled powertools -# Update -RUN apt-get update -y && apt-get upgrade -y && apt-get dist-upgrade -y && apt-get install build-essential software-properties-common -y && add-apt-repository ppa:ubuntu-toolchain-r/test -y && apt-get update -y +# Update and install build tools +# gcc-toolset-13 provides GCC 13 with GLIBCXX_3.4.31, required by conda Qt5/ICU packages. +# This keeps GLIBC 2.28 for binary compatibility while providing modern C++ library symbols. +RUN dnf update -y && dnf groupinstall -y "Development Tools" \ + && dnf install -y gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-binutils gcc-toolset-13-libstdc++-devel # Install git and git-lfs -RUN add-apt-repository ppa:git-core/ppa -RUN apt-get update -RUN apt-get install git curl -y -RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash -RUN apt-get install git-lfs -y +RUN dnf install -y git curl +RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.rpm.sh | bash +RUN dnf install -y git-lfs # Install other dependencies -RUN apt-get install rsync freeglut3-dev libgl1-mesa-dev libegl1-mesa zip libcups2 pigz wget ccache -y +RUN dnf install -y rsync freeglut-devel mesa-libGL-devel mesa-libEGL-devel zip cups-libs pigz wget ccache # Install conda RUN curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -o /tmp/Miniconda3-latest-Linux-x86_64.sh \ @@ -38,6 +40,15 @@ RUN curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh - && echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc \ && echo "conda activate base" >> ~/.bashrc +# Install modern libstdc++ with GLIBCXX_3.4.30+ required by conda Qt5/ICU packages +# gcc-toolset-13 only provides static libstdc++, so we use conda-forge's libstdcxx-ng +RUN /opt/conda/bin/conda install -y -c conda-forge libstdcxx-ng=13 \ + && LIBSTDCXX=$(ls /opt/conda/lib/libstdc++.so.6.0.* | head -1) \ + && cp -f "$LIBSTDCXX" /usr/lib64/ \ + && rm -f /usr/lib64/libstdc++.so.6 \ + && ln -s $(basename "$LIBSTDCXX") /usr/lib64/libstdc++.so.6 \ + && ldconfig + # Get and decompress linuxdeployqt, it's complicated to use fuse with docker due to the kernel module RUN curl -L -o $HOME/linuxdeployqt.AppImage https://github.com/probonopd/linuxdeployqt/releases/download/5/linuxdeployqt-5-x86_64.AppImage && chmod +x $HOME/linuxdeployqt.AppImage ; cd $HOME ; ./linuxdeployqt.AppImage --appimage-extract RUN ln -s /root/squashfs-root/usr/bin/linuxdeployqt /usr/bin diff --git a/.github/workflows/build-linux-debug.yml b/.github/workflows/build-linux-debug.yml index 84a107ae73..5cdc52f247 100644 --- a/.github/workflows/build-linux-debug.yml +++ b/.github/workflows/build-linux-debug.yml @@ -24,7 +24,7 @@ jobs: build: runs-on: ubuntu-latest - container: akenmorris/ubuntu-build-box-jammy-sw67 + container: akenmorris/manylinux-build-box-sw67 steps: @@ -66,7 +66,7 @@ jobs: uses: actions/cache/restore@v3 with: path: /github/home/install - key: ${{ runner.os }}-deps-debug-${{ hashFiles('.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} + key: ${{ runner.os }}-deps-debug-${{ hashFiles('.github/workflows/Dockerfile', '.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} - name: Check space3.5 run: df -h @@ -107,7 +107,7 @@ jobs: uses: actions/cache/save@v3 with: path: /github/home/install - key: ${{ runner.os }}-deps-debug-${{ hashFiles('.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} + key: ${{ runner.os }}-deps-debug-${{ hashFiles('.github/workflows/Dockerfile', '.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} - name: Check space5 run: df -h diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index a0c2fa6efd..994ee9482b 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -25,7 +25,7 @@ jobs: build: runs-on: ubuntu-latest - container: akenmorris/ubuntu-build-box-jammy-sw67 + container: akenmorris/manylinux-build-box-sw67 steps: @@ -64,7 +64,7 @@ jobs: uses: actions/cache/restore@v3 with: path: /github/home/install - key: ${{ runner.os }}-deps-${{ hashFiles('.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} + key: ${{ runner.os }}-deps-${{ hashFiles('.github/workflows/Dockerfile', '.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} - name: try import vtk shell: bash -l {0} @@ -81,7 +81,7 @@ jobs: uses: actions/cache/save@v3 with: path: /github/home/install - key: ${{ runner.os }}-deps-${{ hashFiles('.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} + key: ${{ runner.os }}-deps-${{ hashFiles('.github/workflows/Dockerfile', '.github/workflows/gha_deps.sh', 'install_shapeworks.sh', 'python_requirements.txt', 'build_dependencies.sh') }} - name: Check space4 run: df -h diff --git a/.github/workflows/gha_conda.sh b/.github/workflows/gha_conda.sh index 846941378c..99fcbe4467 100755 --- a/.github/workflows/gha_conda.sh +++ b/.github/workflows/gha_conda.sh @@ -13,7 +13,7 @@ else cd ${GITHUB_WORKSPACE} # run install - source ./install_shapeworks.sh --developer + source ./install_shapeworks.sh --developer || exit 1 conda clean -p -t -y pip cache info pip cache purge diff --git a/Libs/Python/CMakeLists.txt b/Libs/Python/CMakeLists.txt index d01de1a59b..44120688e0 100644 --- a/Libs/Python/CMakeLists.txt +++ b/Libs/Python/CMakeLists.txt @@ -1,4 +1,5 @@ -pybind11_add_module(shapeworks_py +# NO_EXTRAS disables LTO which can cause GCC internal compiler errors +pybind11_add_module(shapeworks_py NO_EXTRAS ShapeworksPython.cpp PythonAnalyze.cpp PythonGroom.cpp diff --git a/Python/shapeworks/shapeworks/network_analysis.py b/Python/shapeworks/shapeworks/network_analysis.py index d5d926ba3d..46e79c7ca0 100644 --- a/Python/shapeworks/shapeworks/network_analysis.py +++ b/Python/shapeworks/shapeworks/network_analysis.py @@ -4,7 +4,6 @@ import trimesh import itertools -import open3d as o3d import scipy import spm1d import vtk @@ -19,6 +18,8 @@ import shapeworks as sw np.random.seed(0) +# open3d is imported lazily when needed (not available on all platforms) + class NetworkAnalysis: def __init__(self, project): @@ -85,6 +86,16 @@ def compute_mean_shape(self): return mesh_points, mesh_normals, mean_shape, surface def run(self): + # Import open3d here (lazy import - not available on all platforms) + try: + import open3d as o3d + except ImportError: + raise ImportError( + "open3d is required for network analysis but is not installed. " + "This feature may not be available on your platform/Python version. " + "Try: pip install open3d-cpu (Linux) or pip install open3d (macOS/Windows)" + ) + project = self.project analyze = self.analyze num_pts = analyze.get_num_particles() diff --git a/Python/shapeworks/shapeworks/network_analysis_figures.py b/Python/shapeworks/shapeworks/network_analysis_figures.py index d05b37f960..970303c2fe 100644 --- a/Python/shapeworks/shapeworks/network_analysis_figures.py +++ b/Python/shapeworks/shapeworks/network_analysis_figures.py @@ -10,7 +10,6 @@ import trimesh # import itertools # import operator -import open3d as o3d import scipy # import spm1d import vtk @@ -24,11 +23,23 @@ import vedo import cv2 +# open3d is imported lazily when needed (not available on all platforms) + class NetworkAnalysisFigures: def __init__(self, network_analysis): self.network_analysis = network_analysis def run(self): + # Import open3d here (lazy import - not available on all platforms) + try: + import open3d as o3d + except ImportError: + raise ImportError( + "open3d is required for network analysis figures but is not installed. " + "This feature may not be available on your platform/Python version. " + "Try: pip install open3d-cpu (Linux) or pip install open3d (macOS/Windows)" + ) + # In[4]: analyze = self.network_analysis.analyze diff --git a/Support/package.sh b/Support/package.sh index e6e52fe158..34a93ebf90 100755 --- a/Support/package.sh +++ b/Support/package.sh @@ -138,7 +138,8 @@ else linuxdeployqt ShapeWorksStudio -verbose=2 cd .. - rm lib/libxcb* lib/libX* lib/libfont* lib/libfreetype* + # Keep libfreetype as harfbuzz depends on it (FT_Get_Transform requires freetype 2.11+) + rm lib/libxcb* lib/libX* lib/libfont* rm -rf geometry-central doc fi diff --git a/install_shapeworks.sh b/install_shapeworks.sh index 3002ac6cdf..cdb64bfcb0 100644 --- a/install_shapeworks.sh +++ b/install_shapeworks.sh @@ -179,22 +179,34 @@ function install_conda() { if ! python -m light_the_torch install torch==2.8.0 torchaudio==2.8.0 torchvision==0.23.0; then return 1; fi fi - # for network analysis + # for network analysis (optional - not available on all platforms/Python versions) # open3d needs to be installed differently on each platform so it's not part of python_requirements.txt + OPEN3D_INSTALLED=NO if [[ "$(uname)" == "Linux" ]]; then - if ! pip install open3d-cpu==0.19.0; then return 1; fi + if pip install open3d-cpu==0.19.0; then + OPEN3D_INSTALLED=YES + else + echo "WARNING: open3d-cpu could not be installed. Network analysis features will not be available." + fi elif [[ "$(uname)" == "Darwin" ]]; then - if ! pip install open3d==0.19.0; then return 1; fi - - if [[ "$(uname -m)" == "arm64" ]]; then - pushd $CONDA_PREFIX/lib/python3.12/site-packages/open3d/cpu - install_name_tool -change /opt/homebrew/opt/libomp/lib/libomp.dylib @rpath/libomp.dylib pybind.cpython-312-darwin.so - install_name_tool -add_rpath @loader_path/../../../ pybind.cpython-312-darwin.so - popd - ln -sf "$CONDA_PREFIX/lib/libomp.dylib" "$CONDA_PREFIX/lib/python3.12/site-packages/open3d/cpu/../../../libomp.dylib" + if pip install open3d==0.19.0; then + OPEN3D_INSTALLED=YES + if [[ "$(uname -m)" == "arm64" ]]; then + pushd $CONDA_PREFIX/lib/python3.12/site-packages/open3d/cpu + install_name_tool -change /opt/homebrew/opt/libomp/lib/libomp.dylib @rpath/libomp.dylib pybind.cpython-312-darwin.so + install_name_tool -add_rpath @loader_path/../../../ pybind.cpython-312-darwin.so + popd + ln -sf "$CONDA_PREFIX/lib/libomp.dylib" "$CONDA_PREFIX/lib/python3.12/site-packages/open3d/cpu/../../../libomp.dylib" + fi + else + echo "WARNING: open3d could not be installed. Network analysis features will not be available." fi else - if ! pip install open3d==0.19.0; then return 1; fi + if pip install open3d==0.19.0; then + OPEN3D_INSTALLED=YES + else + echo "WARNING: open3d could not be installed. Network analysis features will not be available." + fi fi for package in DataAugmentationUtilsPackage DatasetUtilsPackage MONAILabelPackage DeepSSMUtilsPackage DocumentationUtilsPackage ShapeCohortGenPackage shapeworks ; do