pyqt5 code with astroalign not working after pyinstaller

91 views
Skip to first unread message

skyjudith JAI

unread,
Mar 16, 2025, 10:02:43 AMMar 16
to PyInstaller
I have tried to fix this problem for days with help from chatgpt, but without success.  I wonder if anyone has ideas?

I'm on ubuntu working from a virtual environment.  The code, Astro-FLEX.py, works fine when I just type python Astro-FLEX.py.  The code opens a window, displays FITS files using astropy, and then runs astroalign to align the images.  So all is well.

Now, when I run pyinstaller to create an executable, I cannot do the astroalign part. The code runs through the steps and creates a median at the end, but the actual shifting is not done. In the logger I get a message
Error registering image filename.fit Input type for source not supported.

I am using
pyinstaller --onedir --add-data "/home/irwin/.local/lib/python3.10/site-packages/PyQt5/Qt5/plugins:PyQt5/Qt5/plugins" Astro-FLEX.py 
but whether or not I include plugins, it still doesn't work.

I then booted on windows and get the same behaviour but no error message. Works fine from python but not after pyinstaller. 

anyone have insight?  thanks!

Judith




bwoodsend

unread,
Mar 16, 2025, 2:52:33 PMMar 16
to PyInstaller
Presumably whatever submodule/package/data file is responsible for supporting these FITS files is not being collected. I can't help beyond that without either a minimal example + FITS file to play with or a some clue as to what package/file is issuing that error message which will tell us where to look for what exactly is missing.

skyjudith JAI

unread,
Mar 16, 2025, 6:55:57 PMMar 16
to PyInstaller
My complete code is very long but I've made a 'minimumtest.py' which shows the same behaviour, i.e. works in python but fails after pyinstaller.  The minimum code is below (sorry I don't see where I can include it as an attachment).  I was aligning 9 fits images but each is 7.4 Mb.  Is there somewhere I can put the fits files?
Judith

import sys
import os
import numpy as np
from astropy.io import fits
import astroalign as aa
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QFileDialog, QLabel
)
from PyQt5.QtCore import QTimer
import pyqtgraph as pg
import warnings
warnings.simplefilter("ignore", DeprecationWarning)

class FITSAlignmentApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("FITS Alignment Test")
        self.setGeometry(100, 100, 800, 600)

        # Main widget
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)

        # Layout
        layout = QVBoxLayout(central_widget)

        # Load button
        self.load_button = QPushButton("Load FITS Files", self)
        self.load_button.clicked.connect(self.load_fits_files)
        layout.addWidget(self.load_button)

        # Status label
        self.status_label = QLabel("Select FITS files to align", self)
        layout.addWidget(self.status_label)

        # PyQtGraph ImageView
        self.image_view = pg.ImageView()
        layout.addWidget(self.image_view)

        # Timer for sequential loading
        self.timer = QTimer()
        self.timer.timeout.connect(self.show_next_image)
        self.image_index = 0
        self.images = []
        self.aligned_images = []
        self.transforms = []

    def load_fits_files(self):
        """ Open file dialog and load multiple FITS files with delay """
        files, _ = QFileDialog.getOpenFileNames(
            self, "Select FITS Files", os.getcwd(),  # Start in the current working directory
            "FITS Files (*.fits *.fit *.fts *.FITS *.FIT *.FTS);;All Files (*)"
        )

        if not files:
            self.status_label.setText("No files selected.")
            return

        # Load images
        self.images = []
        for file in files:
            try:
                with fits.open(file) as hdul:
                    data = hdul[0].data
                    if data is not None:
                        self.images.append(data.astype(np.float64))
            except Exception as e:
                self.status_label.setText(f"Error loading {file}: {e}")
                return

        if len(self.images) < 2:
            self.status_label.setText("Need at least 2 images to align.")
            return

        self.status_label.setText(f"Loaded {len(self.images)} images. Aligning...")

        # Use the first image as the reference
        self.reference_image = self.images[0]
        self.aligned_images = [self.reference_image]  # First image is already "aligned"

        # Align each image to the reference
        self.transforms = []
        for i, img in enumerate(self.images[1:], start=1):
            try:
                # Compute the transformation parameters
                transform, footprint = aa.find_transform(img, self.reference_image)

                dx, dy = transform.translation  # Extract x, y shifts
                rotation_rad = transform.rotation  # Extract rotation in radians
                rotation_deg = np.degrees(rotation_rad)  # Convert to degrees

                # Apply the transformation to align the image
                print(f"Original Image {i} Shape: {img.shape}")
                aligned_img, footprint = aa.apply_transform(transform, img, self.reference_image)
                # Ensure aligned_img is a NumPy array
                aligned_img = np.array(aligned_img, dtype=np.float64)
                print(f"Transformed Image {i} Shape: {aligned_img.shape}")
                aligned_img = np.array(aligned_img, dtype=np.float64)
               
                print(f"Image {i}: Δx = {dx:.2f}, Δy = {dy:.2f}, Δrot = {rotation_deg:.2f}°")

                # Ensure aligned image has the same shape as the reference image
                aligned_img = np.array(aligned_img, dtype=np.float64)

                if aligned_img.shape != self.reference_image.shape:
                    print(f"Warning: Shape mismatch in Image {i}. Resizing...")
                    min_shape = (
                        min(aligned_img.shape[0], self.reference_image.shape[0]),
                        min(aligned_img.shape[1], self.reference_image.shape[1]),
                    )
                    aligned_img = aligned_img[:min_shape[0], :min_shape[1]]
                    self.reference_image = self.reference_image[:min_shape[0], :min_shape[1]]

                self.aligned_images.append(aligned_img)
                self.transforms.append(transform)


            except Exception as e:
                print(f"Error aligning image {i}: {e}")
                self.aligned_images.append(img)  # If alignment fails, keep original

        self.status_label.setText("Alignment complete. Displaying images...")
        self.image_index = 0
        self.timer.start(1000)  # 1-second delay between images

    def extract_transform(self, transform):
        """ Extract translation and rotation from transformation matrix. """
        dx = transform[0, 2]  # Translation in x
        dy = transform[1, 2]  # Translation in y
        rotation_radians = np.arctan2(transform[1, 0], transform[0, 0])
        rotation_degrees = np.degrees(rotation_radians)  # Convert to degrees
        return dx, dy, rotation_degrees

    def show_next_image(self):
        """ Show aligned images one by one before displaying the median """
        if self.image_index < len(self.aligned_images):
            current_image = self.aligned_images[self.image_index]
            focus_min, focus_max = self.compute_focus_range(current_image)
            self.image_view.setImage(current_image, levels=(focus_min, focus_max))
            self.status_label.setText(f"Showing aligned image {self.image_index + 1}/{len(self.aligned_images)}")
            self.image_index += 1
        else:
            # Stop the timer and compute the median
            self.timer.stop()
            self.display_median_image()

    def display_median_image(self):
        """ Compute and display the median-combined image """
        self.status_label.setText("Computing median image...")
        median_image = np.nanmedian(np.stack(self.aligned_images), axis=0)
        focus_min, focus_max = self.compute_focus_range(median_image)
        self.image_view.setImage(median_image, levels=(focus_min, focus_max))
        self.status_label.setText("Showing median image.")

    def compute_focus_range(self, data):
        """ Compute focus_min and focus_max dynamically """
        valid_data = data[data != 0]  # Exclude exact zero values
        if valid_data.size > 0:
            median = np.nanmedian(valid_data)
            std = np.nanstd(valid_data)
            min_val = np.nanmin(valid_data)
            max_val = np.nanmax(valid_data)
        else:
            # Fallback if all values are zero
            median = np.nanmedian(data)
            std = np.nanstd(data)
            min_val = np.nanmin(data)
            max_val = np.nanmax(data)

        # Expand range slightly for better visualization
        focus_min = max(median - std, min_val)
        focus_max = min(median + std, max_val)

        return focus_min, focus_max

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = FITSAlignmentApp()
    window.show()
    sys.exit(app.exec_())



Chris Barker

unread,
Mar 17, 2025, 5:06:00 PMMar 17
to pyins...@googlegroups.com
Just a suggestion- to make it even easier to diagnose, try a simple script only processes a fits file, without  QT.

That will be a far simpler build, and it may be more obvious what’s missing.

-CHB


Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris....@noaa.gov


--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pyinstaller/4da01cd9-0d2f-47ef-96f3-05b87ee1989bn%40googlegroups.com.

skyjudith JAI

unread,
Mar 18, 2025, 9:30:44 AMMar 18
to pyins...@googlegroups.com
so far no success.  I've pared everything down to the simplest possible code.  The code is attached.
typing
python simple_align.py BG1.FIT BG2.FIT
gives output indicating that the alignment works fine.

however, after
pyinstaller --onedir --clean \
  --hidden-import=numpy \
  --hidden-import=astropy \
  --hidden-import=astroalign \
  --hidden-import=skimage \
  --collect-all sep \
  --copy-metadata sep \
  --exclude-module=cv2 \
  simple_align.py

then going to the dist directory
./simple_align BG1.FIT BG2.FIT
I get
Error during alignment: Input type for source not supported.

It looks like I need to send the 2 fits files in 2 more posts, since they are too large for one message. 

Judith






simple_align.py

skyjudith JAI

unread,
Mar 18, 2025, 9:31:53 AMMar 18
to pyins...@googlegroups.com
Here is the first fits file.

On Mon, Mar 17, 2025 at 5:05 PM 'Chris Barker' via PyInstaller <pyins...@googlegroups.com> wrote:
BG1.FIT

skyjudith JAI

unread,
Mar 18, 2025, 9:32:31 AMMar 18
to pyins...@googlegroups.com
Here is the second fits file.

On Mon, Mar 17, 2025 at 5:05 PM 'Chris Barker' via PyInstaller <pyins...@googlegroups.com> wrote:
BG2.FIT

Chris Barker

unread,
Mar 18, 2025, 11:03:44 AMMar 18
to pyins...@googlegroups.com
If seems the first decider us not being included by installer-. I would ask the astropy folks where/how the decoders are discovered. 

In the meantime, maybe try setting astropy.io or even astropy.io.fits as a hidden import. 

Though if you aren’t getting an import error, then the (python) module is there — but it seems to rely on a data file, or library or something that isn’t being included. 


-CHB

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris....@noaa.gov

bwoodsend

unread,
Mar 18, 2025, 3:04:45 PMMar 18
to PyInstaller

If you remove the try/except, the cause is fairly standard.

Traceback (most recent call last): File "astroalign.py", line 537, in _find_sources File "sep_pjw.pyx", line 21, in init sep_pjw ModuleNotFoundError: No module named '_version' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "simple_align.py", line 25, in <module> transform, footprint = aa.find_transform(target_image, ref_image) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "astroalign.py", line 314, in find_transform TypeError: Input type for source not supported. [PYI-1439842:ERROR] Failed to execute script 'simple_align' due to unhandled exception!

The fix is just to add --hiddenimport=_version to the build command. (I’d call it a bug in sep_pjw that it’s keeping its version in a top level module rather than say sep_pjw._version but that’s another matter.)

Please never use blanket exception handling like that. We’d have fixed this in about 2 seconds if you hadn’t thrown away the stacktrace.

Chris Barker

unread,
Mar 18, 2025, 3:34:13 PMMar 18
to pyins...@googlegroups.com
he fix is just to add --hiddenimport=_version to the build command. (I’d call it a bug in sep_pjw that it’s keeping its version in a top level module rather than say sep_pjw._version but that’s another matter.)

good point -- I took a look at sep_pjw to see if I could report this, but:

The sep-pjw package was deprecated on 03/02/2025, and is now published under the original sep name. Please install sep>=1.4.0 instead. Future development will take place at sep-developers/sep.

So maybe give sep a try ... it may not have this issue.

-CHB





 

Please never use blanket exception handling like that. We’d have fixed this in about 2 seconds if you hadn’t thrown away the stacktrace.

--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.

skyjudith JAI

unread,
Mar 19, 2025, 9:50:20 AMMar 19
to pyins...@googlegroups.com
Thank you!  Brilliant.  That worked. It's something that I (and my on-again off-again friend chatgpt) did not figure out.  Could you explain what you mean by:
"Please never use blanket exception handling like that."  I"m not sure what the alternative should be.

Judith


--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.

Chris Barker

unread,
Mar 19, 2025, 12:09:20 PMMar 19
to pyins...@googlegroups.com
On Wed, Mar 19, 2025 at 6:50 AM skyjudith JAI <skyla...@gmail.com> wrote:
Thank you!  Brilliant.  That worked. It's something that I (and my on-again off-again friend chatgpt) did not figure out.  Could you explain what you mean by:
"Please never use blanket exception handling like that."  I"m not sure what the alternative should be.

What he means is that:

except Exception as e:
    print("Error during alignment:", e)

Will catch ANY exception, and then swallow it up to make it go away, leaving you with no idea what went wrong.

So better to maybe catch, print a helpful message and then re-raise the exception so you'll get the full stack trace.

In practice, particularly behind a GUI, there may be a need for your program not to crash out. In that cse, you want to capture and log or print, or something, the full traceback. Google for how, but here's a hint:

https://docs.python.org/3/library/traceback.html#module-traceback

Meanwhile, when debugging, you definitely simply want the Exception to bubble up :-)

-CHB





 

Judith


On Tue, Mar 18, 2025 at 3:04 PM bwoodsend <bwoo...@gmail.com> wrote:

If you remove the try/except, the cause is fairly standard.

Traceback (most recent call last): File "astroalign.py", line 537, in _find_sources File "sep_pjw.pyx", line 21, in init sep_pjw ModuleNotFoundError: No module named '_version' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "simple_align.py", line 25, in <module> transform, footprint = aa.find_transform(target_image, ref_image) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "astroalign.py", line 314, in find_transform TypeError: Input type for source not supported. [PYI-1439842:ERROR] Failed to execute script 'simple_align' due to unhandled exception!

The fix is just to add --hiddenimport=_version to the build command. (I’d call it a bug in sep_pjw that it’s keeping its version in a top level module rather than say sep_pjw._version but that’s another matter.)

Please never use blanket exception handling like that. We’d have fixed this in about 2 seconds if you hadn’t thrown away the stacktrace.

--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pyinstaller/e7b80820-9007-4996-bf86-53caf60ebdc5n%40googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "PyInstaller" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyinstaller...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages