viernes, 27 de febrero de 2026

Selección de imágenes antes de procesado en Siril

 
Aunque durante el apilado los métodos de rechazo suelen filtrar las imágenes de peor calidad, siempre realizo una inspección visual para descartar aquellas que no deben ser procesadas.

Siril dispone de un script que permite hacerlo de una forma gráfica y sin tener que registrar las imágenes previamente.
También comento al final del post otro método más 'manual' que incluye un pequeño script escrito por mí.


Blink/browse/filter/sort


Esta herramienta la encontraremos dentro del apartado de scripts, en utilidades:


Necesitamos indicarle al script la carpeta donde están nuestras lights y después debemos pulsar en el botón Analyse files.
Dependiendo del número de imágenes, el proceso puede tardar un tiempo, al final del cual nos mostrará el total de elementos analizados


Podemos desplazarnos por las diferentes imágenes usando los botones Next/Prev y marcar/desmarcar las imágenes usando los botones Toggle Include/Select All, Unselect All e Invert selection.
El único problema que veo es que la imagen no se le hace el debayer

Otra opción muy interesante es la posibilidad de filtrar de forma automática en función de ciertos parámetros. Para ello, si desplegamos el menú Image Filtering:


Podremos ver gráficamente el resultado del análisis e incluso filtrar según diferentes criterios


En este ejemplo selecciono filtrar únicamente aquellas imágenes cuyo FWHM es inferior a 0.5 y cuyas estrellas tengan una redondez superior a 0.910 en valores absolutos


Si pulsamos el botón Create sequence nos generará una secuencia con las imágenes filtradas lista para que Siril pueda procesarlas


Además, nos ha creado también una carpeta rejected con las imágenes que no han pasado el filtro


Manual


Este proceso es más manual, pero realiza un debayer de la imagen, por lo que visualmente es más sencillo decidir la calidad.
Requiere el uso de un script hecho por mí el cual os dejo al final del post.

Creamos una carpeta process y la seleccionamos como carpeta de trabajo en Siril.
En la pestaña de conversión añadimos las imágenes Light de nuestra sesión, ponemos nombre a la secuencia, marcamos a casilla Debayer y pulsamos en Convertir.


Una vez finalizado el proceso, aparecerá la primera imágen de las capturadas y por defecto estará en modo lineal con los canales de color enlazados. Normalmente. en este modo solo veremos las estrellas más brillantes y el color será tintado a verde.
Para poder analizar las imágenes con detalle seleccionaremos el modo de pantalla AutoExtender y desmarcaremos el enlace de colores


A continuación pulsaremos en el botón de la esquina inferior derecha para abrir la lista de imágenes


Desde aquí podemos navegar fácilmente por cada imagen y revisar en la vista principal si debemos mantenerla o descartarla.
Por defecto aparecen todas marcadas y podemos marcar/desmarcar individualmente o agrupando o seleccionar/desmarcar todas.

Así por ejemplo, la segunda imagen aparece algo que cada vez es más común, como son satélites artificiales o, como es el caso, aviones. En este caso la desmarcamos para que no sea tenida en cuenta durante el procesado apilado


Debe indicarse, no obstante que existen herramientas que permiten eliminar estas trazas y que algunos métodos de apilado permiten minimizar el impacto de estos artefactos en la imagen final

Una vez hemos desmarcado aquellas que no nos interesan y desde Scripts > Python Scripts > Scripts, seleccionamos mi script unselected_file_discard el cual dejo al final

Pulsamos en Browse y seleccionamos la secuencia que habíamos creado, y seguidamente hacemos clic en el botón Process unselected subs.


 
Si no marcamos la casilla Remove unselected... el script creará una subcarpeta denominada discarded y moverá los archivos no seleccionado allí, dejando la carpeta Lights solo con los archivos seleccionados.
Si marcamos esa casilla, directamente los borrará -no hay opción de deshacer-

Si vamos a utilizar scripts para el apilado de imágenes, podemos borrar la carpeta process creada al principio

El script es este:
import os
import re
import sys
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QLineEdit,
    QTextEdit,
    QFileDialog,
    QMessageBox,
    QCheckBox,
)
from PyQt6.QtCore import Qt


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

        self.setWindowTitle("Discard Unselected Siril Subs")
        self.setMinimumSize(600, 440)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # Seq file input
        seq_layout = QHBoxLayout()
        seq_label = QLabel("Siril Sequence File (.seq):")
        self.seq_edit = QLineEdit()
        self.seq_browse_button = QPushButton("Browse...")
        self.seq_browse_button.clicked.connect(self.browse_seq_file)

        seq_layout.addWidget(seq_label)
        seq_layout.addWidget(self.seq_edit)
        seq_layout.addWidget(self.seq_browse_button)
        layout.addLayout(seq_layout)

        # Move instead of delete checkbox
        self.move_checkbox = QCheckBox("Remove unselected frames to 'discarded' folder instead of deleting")
        layout.addWidget(self.move_checkbox)

        # Remove button
        self.remove_button = QPushButton("Process unselected subs")
        self.remove_button.clicked.connect(self.remove_unselected)
        self.remove_button.setStyleSheet("background-color: red; color: white; font-weight: bold;")
        layout.addWidget(self.remove_button)

        # Log window
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setStyleSheet("background-color: black; color: white; font-family: Consolas; font-size: 10pt;")
        layout.addWidget(self.log_text)

    def log_message(self, message):
        self.log_text.append(message)
        self.log_text.ensureCursorVisible()

    def browse_seq_file(self):
        seq_path, _ = QFileDialog.getOpenFileName(self, "Select Siril Sequence File (.seq)", "", "Siril Sequence Files (*.seq)")
        if seq_path:
            self.seq_edit.setText(seq_path)
            self.log_message(f"Selected sequence file: {seq_path}")

    def remove_unselected(self):
        seq_path = self.seq_edit.text().strip()
        if not seq_path or not os.path.isfile(seq_path):
            QMessageBox.critical(self, "Error", "Please select a valid sequence (.seq) file.")
            self.log_message("Error: Invalid or missing sequence file path.")
            return

        base_dir = os.path.dirname(seq_path)  # e.g. M_74/process
        seq_name = os.path.basename(seq_path)  # e.g. m74.seq
        seq_name_base = seq_name[:-4]  # remove .seq extension
        conversion_name = f"{seq_name_base}conversion.txt"
        conversion_path = os.path.join(base_dir, conversion_name)

        if not os.path.isfile(conversion_path):
            QMessageBox.critical(self, "Error", f"Conversion file not found:\n{conversion_path}")
            self.log_message(f"Error: Conversion file not found: {conversion_path}")
            return

        conv_map = {}
        lights_dir = None
        try:
            with open(conversion_path, 'r') as f:
                for line in f:
                    m = re.match(r"'(.*?)'\s*->\s*'(.*?)'", line.strip())
                    if m:
                        orig_path, conv_name = m.groups()
                        conv_map[conv_name] = orig_path
                        if lights_dir is None:
                            lights_dir = os.path.dirname(orig_path)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed reading conversion file:\n{e}")
            self.log_message(f"Error reading conversion file: {e}")
            return

        self.log_message(f"Inferred lights folder: {lights_dir}")

        if not lights_dir or not os.path.isdir(lights_dir):
            QMessageBox.critical(self, "Error", f"Lights folder not found:\n{lights_dir}")
            self.log_message(f"Error: Lights folder not found: {lights_dir}")
            return

        # Setup discarded folder path one level above lights folder (M_74/discarded)
        discarded_dir = os.path.join(os.path.dirname(lights_dir), "discarded")
        if not self.move_checkbox.isChecked() and not os.path.exists(discarded_dir):
            try:
                os.makedirs(discarded_dir)
                self.log_message(f"Created discarded folder: {discarded_dir}")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to create discarded folder:\n{e}")
                self.log_message(f"Error creating discarded folder: {e}")
                return

        idx_enabled = {}
        try:
            with open(seq_path, 'r') as f:
                for line in f:
                    if line.startswith("I "):
                        parts = line.strip().split()
                        if len(parts) >= 3:
                            idx = int(parts[1])
                            enabled = parts[2] == "1"
                            idx_enabled[idx] = enabled
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Failed reading sequence file:\n{e}")
            self.log_message(f"Error reading sequence file: {e}")
            return

        to_process = []
        pattern = re.escape(seq_name_base) + r"(\d+)"
        for conv_name, orig_file in conv_map.items():
            m_idx = re.search(pattern, conv_name)
            if m_idx:
                idx = int(m_idx.group(1))
                if idx_enabled.get(idx, True) is False:
                    to_process.append(orig_file)
            else:
                self.log_message(f"Warning: Could not find index in file {conv_name}")

        if not to_process:
            QMessageBox.information(self, "No files to process", "No unselected frames found to move or delete.")
            self.log_message("No unselected frames detected in sequence.")
            return

        processed_count = 0
        for f in to_process:
            if os.path.exists(f):
                try:
                    if not self.move_checkbox.isChecked():
                        # Move file to discarded folder
                        basename = os.path.basename(f)
                        target_path = os.path.join(discarded_dir, basename)
                        os.rename(f, target_path)
                        self.log_message(f"Moved to discarded: {target_path}")
                    else:
                        # Delete file
                        os.remove(f)
                        self.log_message(f"Deleted: {f}")
                    processed_count += 1
                except Exception as e:
                    self.log_message(f"Failed to process {f}: {e}")
            else:
                self.log_message(f"File not found, skipping: {f}")

        action = "moved to 'discarded'" if not self.move_checkbox.isChecked() else "deleted"
        QMessageBox.information(self, "Done", f"{processed_count} unselected light frames {action}.")
        self.log_message(f"Operation complete. {processed_count} files {action}.")


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

En la carpeta que tengáis de scripts de siril, crear un archivo llamado unselected_file_discard.py y con un editor pegáis el código de arriba. Guardáis y cerráis.
Para que Siril lo reconozca, la primera vez deberéis ir a Scripts > Obtener scripts aseguraros de que la carpeta donde habéis guardado el archivo está en la lista de Directorio de almacenamiento de scripts y pulsar el botón de refrescar 

El script lo veréis en Scripts > Python Scripts > Scripts

No hay comentarios:

Publicar un comentario

Cómo limpiar la lente objetivo de tu refractor

Con el uso, las condiciones climáticas -viento, humedad- y entorno -polvo- nuestra lente objetivo acaba por acumular suciedad. En este post ...