From 96ad8419687221ebb53126402d24e47ec46e6414 Mon Sep 17 00:00:00 2001 From: Rolf Verberg Date: Wed, 21 Oct 2026 12:03:46 -0400 Subject: [PATCH 1/3] ci: update github workflows to Python 3.11 --- .github/workflows/manual-pages-deploy.yml | 2 +- .github/workflows/pages-deploy-from-docs.yml | 2 +- .github/workflows/pages-deploy.yml | 2 +- .github/workflows/pypi-pyblish.yaml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/manual-pages-deploy.yml b/.github/workflows/manual-pages-deploy.yml index af1840dc..0b103506 100644 --- a/.github/workflows/manual-pages-deploy.yml +++ b/.github/workflows/manual-pages-deploy.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Miniconda uses: conda-incubator/setup-miniconda@v3 with: - python-version: '3.10' + python-version: '3.11' environment-file: docs/environment.yml auto-activate: true activate-environment: sphinx diff --git a/.github/workflows/pages-deploy-from-docs.yml b/.github/workflows/pages-deploy-from-docs.yml index 56e58af2..621d69c5 100644 --- a/.github/workflows/pages-deploy-from-docs.yml +++ b/.github/workflows/pages-deploy-from-docs.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Miniconda uses: conda-incubator/setup-miniconda@v3 with: - python-version: '3.10' + python-version: '3.11' environment-file: docs/environment.yml auto-activate: true activate-environment: sphinx diff --git a/.github/workflows/pages-deploy.yml b/.github/workflows/pages-deploy.yml index d4c09044..422ebaca 100644 --- a/.github/workflows/pages-deploy.yml +++ b/.github/workflows/pages-deploy.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Miniconda uses: conda-incubator/setup-miniconda@v3 with: - python-version: '3.10' + python-version: '3.11' environment-file: docs/environment.yml auto-activate: true activate-environment: sphinx diff --git a/.github/workflows/pypi-pyblish.yaml b/.github/workflows/pypi-pyblish.yaml index f0370b8b..d503e78e 100644 --- a/.github/workflows/pypi-pyblish.yaml +++ b/.github/workflows/pypi-pyblish.yaml @@ -20,10 +20,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python 3.11 uses: actions/setup-python@v3 with: - python-version: '3.8' + python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip From d26fce749db121e9801c5d750347da13cb98435f Mon Sep 17 00:00:00 2001 From: Rolf Verberg Date: Wed, 21 Oct 2026 12:05:45 -0400 Subject: [PATCH 2/3] build: Update all CHAP enviroments to python 3.11 --- CHAP/edd/environment.yml | 23 ++++++++++------------- CHAP/giwaxs/environment.yml | 17 +++++++++-------- CHAP/saxswaxs/environment.yml | 23 +++++++++++++---------- CHAP/tomo/environment.yml | 24 ++++++++++-------------- environment.yml | 8 ++++---- requirements.txt | 6 +++--- 6 files changed, 49 insertions(+), 52 deletions(-) diff --git a/CHAP/edd/environment.yml b/CHAP/edd/environment.yml index 5a9ddf88..fb417cc1 100644 --- a/CHAP/edd/environment.yml +++ b/CHAP/edd/environment.yml @@ -4,20 +4,17 @@ channels: - hexrd - nodefaults dependencies: - - python>=3.9,<3.11 - - asteval==1.0.8 - - chessanalysispipeline - - hexrd==0.9.4 + - python==3.11 + - numpy==2.4.6 + - pydantic==2.13.5 + - pyyaml==6.0.3 + - hexrd==0.10.6 - lmfit==1.3.4 - - matplotlib==3.10.8 - - mpi4py==4.1.1 - - nexpy==2.0.0 - - nexusformat==2.0.0 - - pip==25.3 - - pydantic==2.12.5 - - requests==2.32.5 - - scipy==1.15.2 + - nexpy==2.1.0 + - nexusformat==2.1.0 + - pip==26.2.1 + - scipy==1.17.1 - sympy==1.14.0 - pip: - - certif-pyspec==1.5.3 - chess-scanparsers + - chessanalysispipeline diff --git a/CHAP/giwaxs/environment.yml b/CHAP/giwaxs/environment.yml index 5d9263ec..98fba960 100644 --- a/CHAP/giwaxs/environment.yml +++ b/CHAP/giwaxs/environment.yml @@ -3,13 +3,14 @@ channels: - conda-forge - nodefaults dependencies: - - python>=3.9,<3.11 - - chessanalysispipeline - - nexpy==2.0.0 - - nexusformat==2.0.0 - - pip==25.3 - - pyfai==2026.2.0 - - pydantic==2.12.5 + - python==3.11 + - numpy==2.4.6 + - pydantic==2.13.5 + - pyyaml==6.0.3 + - nexpy==2.1.0 + - nexusformat==2.1.0 + - pip==26.2.1 + - pyfai==2026.9.0 - pip: - - certif-pyspec==1.5.3 - chess-scanparsers + - chessanalysispipeline diff --git a/CHAP/saxswaxs/environment.yml b/CHAP/saxswaxs/environment.yml index 7dc253a4..3f4b4460 100644 --- a/CHAP/saxswaxs/environment.yml +++ b/CHAP/saxswaxs/environment.yml @@ -4,14 +4,17 @@ channels: - nodefaults dependencies: - python==3.11 -# - chessanalysispipeline - - nexpy==1.0.9 - - nexusformat==1.0.6 - - pandas==2.3.3 - - pip==24.2 - - pydantic==2.7.3 - - pyfai==2025.1.0 - - zarr==3.0.6 + - numpy==2.4.6 + - pydantic==2.13.5 + - pyyaml==6.0.3 + - lmfit==1.3.4 + - nexpy==2.1.0 + - nexusformat==2.1.0 + - pip==26.2.1 + - pyfai==2026.9.0 + - scipy==1.17.1 + - sympy==1.14.0 + - zarr==3.1.6 - pip: - - certif-pyspec==1.5.3 -# - chess-scanparsers + - chess-scanparsers + - chessanalysispipeline diff --git a/CHAP/tomo/environment.yml b/CHAP/tomo/environment.yml index 00a4b7f2..f01d3e0e 100644 --- a/CHAP/tomo/environment.yml +++ b/CHAP/tomo/environment.yml @@ -4,22 +4,18 @@ channels: - astra-toolbox - nodefaults dependencies: - - python>=3.9,<3.11 - - astra-toolbox==2.3.1 - - chessanalysispipeline - - fabio==2025.10.0 + - python==3.11 + - numpy==2.4.6 + - pydantic==2.13.5 + - pyyaml==6.0.3 + - astra-toolbox==2.5.0 - lmfit==1.3.4 - - matplotlib==3.10.8 - - mpi4py==4.1.1 - - nexpy==2.0.0 - - nexusformat==2.0.0 - - pip==25.3 - - pyfai==2025.12.1 - - pydantic==2.12.5 - - requests==2.32.5 - - scipy==1.15.2 + - nexpy==2.1.0 + - nexusformat==2.1.0 + - pip==26.2.1 + - scipy==1.17.1 - sympy==1.14.0 - tomopy==1.15.3 - pip: - - certif-pyspec==1.5.3 - chess-scanparsers + - chessanalysispipeline diff --git a/environment.yml b/environment.yml index 87717f88..5d1fddf4 100644 --- a/environment.yml +++ b/environment.yml @@ -3,7 +3,7 @@ channels: - conda-forge - nodefaults dependencies: - - python==3.10.19 - - numpy - - pydantic - - pyyaml + - python==3.11 + - numpy==2.4.6 + - pydantic==2.13.5 + - pyyaml==6.0.3 diff --git a/requirements.txt b/requirements.txt index f1e1abb9..79585d03 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -numpy -pydantic -pyyaml +numpy==2.4.6 +pydantic==2.13.5 +pyyaml==6.0.3 From c6dd9eb878795cd8cf036811e9973490fa66b1cc Mon Sep 17 00:00:00 2001 From: Rolf Verberg Date: Wed, 21 Oct 2026 12:06:39 -0400 Subject: [PATCH 3/3] fix: fixes crash in Tkinter due to plt.close order issue --- CHAP/common/processor.py | 26 ++++++++------ CHAP/edd/processor.py | 22 +++++++----- CHAP/edd/utils.py | 48 +++++++++++++++----------- CHAP/utils/general.py | 73 +++++++++++++++++++++++++--------------- 4 files changed, 104 insertions(+), 65 deletions(-) diff --git a/CHAP/common/processor.py b/CHAP/common/processor.py index 4e1f6aa6..e3c0faeb 100755 --- a/CHAP/common/processor.py +++ b/CHAP/common/processor.py @@ -390,6 +390,19 @@ def confirm(event): """Callback function for the "Confirm" button.""" plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + lambda_box.disconnect(lambda_cid) + continue_btn.disconnect(continue_cid) + confirm_btn.disconnect(confirm_cid) + + # ... and remove the buttons before returning the figure + lambda_box.ax.remove() + continue_btn.ax.remove() + confirm_btn.ax.remove() + + def get_baseline( y, *, mask=None, w=None, tol=1.e-6, lam=1.6, max_iter=20): """Get a baseline. @@ -484,19 +497,12 @@ def get_baseline( plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) + # Show figure for user interaction plt.show() - # Disconnect all widget callbacks when figure is closed - lambda_box.disconnect(lambda_cid) - continue_btn.disconnect(continue_cid) - confirm_btn.disconnect(confirm_cid) - - # ... and remove the buttons before returning the figure - lambda_box.ax.remove() - continue_btn.ax.remove() - confirm_btn.ax.remove() - if return_buf: fig_title.set_in_layout(True) fig_subtitles[-1].set_in_layout(True) diff --git a/CHAP/edd/processor.py b/CHAP/edd/processor.py index a5a3b859..334ff088 100755 --- a/CHAP/edd/processor.py +++ b/CHAP/edd/processor.py @@ -1580,6 +1580,17 @@ def confirm(event): error_texts.pop() plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + reset_btn.disconnect(reset_cid) + confirm_btn.disconnect(confirm_cid) + + # ... and remove the buttons before returning the figure + reset_btn.ax.remove() + confirm_btn.ax.remove() + + def find_peaks(min_height=0.05, min_width=5, tolerance=10): """Find the peaks. @@ -1739,15 +1750,10 @@ def select_peaks(): plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) - plt.show() - - # Disconnect all widget callbacks when figure is closed - reset_btn.disconnect(reset_cid) - confirm_btn.disconnect(confirm_cid) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) - # ... and remove the buttons before returning the figure - reset_btn.ax.remove() - confirm_btn.ax.remove() + plt.show() if return_buf: fig_title[0].set_in_layout(True) diff --git a/CHAP/edd/utils.py b/CHAP/edd/utils.py index b342bb4a..7be3867e 100755 --- a/CHAP/edd/utils.py +++ b/CHAP/edd/utils.py @@ -217,6 +217,16 @@ def confirm(event): """Callback function for the "Confirm" button.""" plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + tth_input.disconnect(cid_update_tth) + confirm_btn.disconnect(confirm_cid) + + # ...and remove the buttons before returning the figure + tth_input.ax.remove() + confirm_btn.ax.remove() + fig_title = [] error_texts = [] @@ -268,17 +278,12 @@ def confirm(event): confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) + # Show figure for user interaction plt.show() - # Disconnect all widget callbacks when figure is closed - tth_input.disconnect(cid_update_tth) - confirm_btn.disconnect(confirm_cid) - - # ...and remove the buttons before returning the figure - tth_input.ax.remove() - confirm_btn.ax.remove() - # Save the figures if requested and close if return_buf: if interactive: @@ -1100,6 +1105,19 @@ def confirm(event): f'detector {detector_id}') plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + add_span_btn.disconnect(add_span_cid) + fig.canvas.mpl_disconnect(pick_hkl_cid) + reset_btn.disconnect(reset_cid) + confirm_btn.disconnect(confirm_cid) + + # ...and remove the buttons before returning the figure + add_span_btn.ax.remove() + confirm_btn.ax.remove() + reset_btn.ax.remove() + if detector_id is None: _change_fig_title('Select data and HKLs to use in fitting') else: @@ -1125,19 +1143,11 @@ def confirm(event): confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) + # Show figure for user interaction plt.show() - - # Disconnect all widget callbacks when figure is closed - add_span_btn.disconnect(add_span_cid) - fig.canvas.mpl_disconnect(pick_hkl_cid) - reset_btn.disconnect(reset_cid) - confirm_btn.disconnect(confirm_cid) - - # ...and remove the buttons before returning the figure - add_span_btn.ax.remove() - confirm_btn.ax.remove() - reset_btn.ax.remove() plt.subplots_adjust(bottom=0.0) if return_buf: diff --git a/CHAP/utils/general.py b/CHAP/utils/general.py index 6cd36fd6..61d8e61a 100755 --- a/CHAP/utils/general.py +++ b/CHAP/utils/general.py @@ -2006,6 +2006,18 @@ def update_index_ranges(mask): selected_index_ranges[-1] = (selected_index_ranges[-1], num_data-1) return selected_index_ranges + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + add_span_btn.disconnect(add_span_cid) + reset_btn.disconnect(reset_cid) + confirm_btn.disconnect(confirm_cid) + + # ...and remove the buttons before returning the figure + add_span_btn.ax.remove() + reset_btn.ax.remove() + confirm_btn.ax.remove() + # Check inputs y = np.asarray(y) if y.ndim > 1: @@ -2112,18 +2124,11 @@ def update_index_ranges(mask): confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) + # Show figure for user interaction plt.show() - - # Disconnect all widget callbacks when figure is closed - add_span_btn.disconnect(add_span_cid) - reset_btn.disconnect(reset_cid) - confirm_btn.disconnect(confirm_cid) - - # ...and remove the buttons before returning the figure - add_span_btn.ax.remove() - reset_btn.ax.remove() - confirm_btn.ax.remove() plt.subplots_adjust(bottom=0.0) selected_index_ranges = _get_selected_index_ranges() @@ -2295,6 +2300,17 @@ def confirm(event): _change_fig_title(f'Selected ROI: {roi}') plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + reset_btn.disconnect(reset_cid) + confirm_btn.disconnect(confirm_cid) + + # ... and remove the buttons before returning the figure + reset_btn.ax.remove() + confirm_btn.ax.remove() + + # Check inputs a = np.asarray(a) if a.ndim != 2: @@ -2361,17 +2377,12 @@ def confirm(event): confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) + # Show figure for user interaction plt.show() - # Disconnect all widget callbacks when figure is closed - reset_btn.disconnect(reset_cid) - confirm_btn.disconnect(confirm_cid) - - # ... and remove the buttons before returning the figure - reset_btn.ax.remove() - confirm_btn.ax.remove() - buf = None if filename is not None or return_buf: if fig_title: @@ -2566,6 +2577,19 @@ def confirm(event): get_selected_indices(_change_fig_title) plt.close() + def on_close(event): + """Callback function to disconnect and remove all widgets.""" + # Disconnect all widget callbacks when figure is closed + index_input.disconnect(indices_cid) + reset_btn.disconnect(reset_cid) + confirm_btn.disconnect(confirm_cid) + + # ... and remove the buttons before returning the figure + index_input.ax.remove() + reset_btn.ax.remove() + confirm_btn.ax.remove() + + # Check inputs a = np.asarray(a) if a.ndim != 2: @@ -2670,17 +2694,10 @@ def confirm(event): confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm') confirm_cid = confirm_btn.on_clicked(confirm) - plt.show() + # Connect the close event callback to the canvas + fig.canvas.mpl_connect('close_event', on_close) - # Disconnect all widget callbacks when figure is closed - index_input.disconnect(indices_cid) - reset_btn.disconnect(reset_cid) - confirm_btn.disconnect(confirm_cid) - - # ... and remove the buttons before returning the figure - index_input.ax.remove() - reset_btn.ax.remove() - confirm_btn.ax.remove() + plt.show() fig_title[0].set_in_layout(True) fig.tight_layout(rect=(0, 0, 1, 0.95))