changeset 0:f4648bd69d48 draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit 89e20bd2ec941d1d362da85bd81230419a1a4b2a
author astroteam
date Fri, 18 Jul 2025 14:23:35 +0000
parents
children
files source_extraction.py source_extractor_astro_tool.xml
diffstat 2 files changed, 539 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source_extraction.py	Fri Jul 18 14:23:35 2025 +0000
@@ -0,0 +1,367 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+#!/usr/bin/env python
+
+# This script is generated with nb2galaxy
+
+# flake8: noqa
+
+import json
+import os
+import shutil
+
+import matplotlib.pyplot as plt
+import numpy as np
+import sep
+import tifffile
+from astropy.io import fits
+from astropy.table import Table
+from matplotlib import rcParams
+from matplotlib.patches import Ellipse
+from oda_api.json import CustomJSONEncoder
+
+get_ipython().run_line_magic("matplotlib", "inline")   # noqa: F821
+
+rcParams["figure.figsize"] = [10.0, 8.0]
+
+input_file = "./input.fits"  # oda:POSIXPath; oda:label "Input file"
+
+### These params are for both functions
+mask_file = None  # oda:POSIXPath, oda:optional; oda:label "Mask file"
+
+### These params are for sep.extract()
+thresh = 1.5  # oda:Float
+err_option = "float_globalrms"  # oda:String; oda:allowed_value 'float_globalrms','array_rms', 'none'
+# gain = None
+maskthresh = 0.0  # oda:Float
+minarea = 5  # oda:Integer
+filter_case = "default"  # oda:String; oda:label "Filter Case"; oda:allowed_value 'none', 'default', 'file'
+filter_file = None  # oda:POSIXPath, oda:optional; oda:label "Filter file"
+filter_type = "matched"  # oda:String; oda:allowed_value 'matched','conv'
+deblend_nthresh = 32  # oda:Integer
+deblend_cont = 0.005  # oda:Float
+clean = True  # oda:Boolean
+clean_param = 1.0  # oda:Float
+
+### These params are for sep.Background()
+# maskthresh = 0.0
+bw = 64  # oda:Integer
+bh = 64  # oda:Integer
+fw = 3  # oda:Integer
+fh = 3  # oda:Integer
+fthresh = 0.0  # oda:Float
+
+_galaxy_wd = os.getcwd()
+
+with open("inputs.json", "r") as fd:
+    inp_dic = json.load(fd)
+if "C_data_product_" in inp_dic.keys():
+    inp_pdic = inp_dic["C_data_product_"]
+else:
+    inp_pdic = inp_dic
+input_file = str(inp_pdic["input_file"])
+
+mask_file = (
+    str(inp_pdic["mask_file"])
+    if inp_pdic.get("mask_file", None) is not None
+    else None
+)
+
+thresh = float(inp_pdic["thresh"])
+err_option = str(inp_pdic["err_option"])
+maskthresh = float(inp_pdic["maskthresh"])
+minarea = int(inp_pdic["minarea"])
+filter_case = str(inp_pdic["filter_case"])
+
+filter_file = (
+    str(inp_pdic["filter_file"])
+    if inp_pdic.get("filter_file", None) is not None
+    else None
+)
+
+filter_type = str(inp_pdic["filter_type"])
+deblend_nthresh = int(inp_pdic["deblend_nthresh"])
+deblend_cont = float(inp_pdic["deblend_cont"])
+clean = bool(inp_pdic["clean"])
+clean_param = float(inp_pdic["clean_param"])
+bw = int(inp_pdic["bw"])
+bh = int(inp_pdic["bh"])
+fw = int(inp_pdic["fw"])
+fh = int(inp_pdic["fh"])
+fthresh = float(inp_pdic["fthresh"])
+
+try:
+    hdul = fits.open(input_file)
+    data = hdul[0].data
+    data = data.astype(data.dtype.newbyteorder("=")).astype(float)
+except:
+    try:
+        data = tifffile.imread(input_file).astype(float)
+    except:
+        raise RuntimeError(
+            "The input file should have the FITS or TIFF format."
+        )
+
+print("INFO: Data shape:", data.shape)
+
+if mask_file is not None:
+    try:
+        hdul = fits.open(mask_file)
+        mask = hdul[0].data
+        mask = mask.astype(mask.dtype.newbyteorder("="))
+    except:
+        try:
+            mask = tifffile.imread(mask_file)
+        except:
+            raise RuntimeError(
+                "The mask file should have the FITS or TIFF format."
+            )
+else:
+    mask = None
+
+print("INFO: Mask type:", type(mask))
+
+filter_kernel = None
+if filter_case == "none":
+    filter_kernel = None
+elif filter_case == "default":
+    filter_kernel = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]])
+elif filter_case == "file":
+    try:
+        filter_kernel = np.loadtxt(filter_file)
+    except:
+        raise RuntimeError(
+            "The filter file should be a text file that is loaded with numpy.loadtxt"
+        )
+
+print("INFO: Filter kernel:", filter_kernel)
+
+# measure a spatially varying background on the image
+bkg = sep.Background(
+    data,
+    mask=mask,
+    maskthresh=maskthresh,
+    bw=bw,
+    bh=bh,
+    fw=fw,
+    fh=fh,
+    fthresh=fthresh,
+)
+
+# evaluate background as 2-d array, same size as original image
+bkg_array = bkg.back()
+hdu_bkg = fits.PrimaryHDU(bkg_array)
+hdu_bkg.writeto("bkg_array.fits")
+
+# evaluate the background noise as 2-d array, same size as original image
+bkg_rms = bkg.rms()
+hdu_rms = fits.PrimaryHDU(bkg_rms)
+hdu_rms.writeto("bkg_rms.fits")
+
+# subtract the background
+data_sub = data - bkg
+
+if err_option == "float_globalrms":
+    err = bkg.globalrms
+elif err_option == "array_rms":
+    err = bkg_rms
+else:
+    err = None
+
+# extract sources:
+objects, segmap = sep.extract(
+    data_sub,
+    thresh,
+    err=err,
+    gain=None,
+    mask=mask,
+    maskthresh=maskthresh,
+    minarea=minarea,
+    filter_kernel=filter_kernel,
+    filter_type=filter_type,
+    deblend_nthresh=deblend_nthresh,
+    deblend_cont=deblend_cont,
+    clean=clean,
+    clean_param=clean_param,
+    segmentation_map=True,
+)
+
+# show the background
+fig, ax = plt.subplots()
+im = ax.imshow(bkg_array, interpolation="nearest", cmap="gray", origin="lower")
+fig.colorbar(im)
+fig.savefig("./bkg_image.png", format="png", bbox_inches="tight")
+
+# show the background noise
+fig, ax = plt.subplots()
+im = ax.imshow(bkg_rms, interpolation="nearest", cmap="gray", origin="lower")
+ax.set_title(f"This is array_rms. While float_globalrms={bkg.globalrms}")
+fig.colorbar(im)
+fig.savefig("./bkg_rms.png", format="png", bbox_inches="tight")
+
+# plot image
+fig, ax = plt.subplots()
+m, s = np.mean(data), np.std(data)
+im = ax.imshow(
+    data,
+    interpolation="nearest",
+    cmap="gray",
+    vmin=m - s,
+    vmax=m + s,
+    origin="lower",
+)
+fig.colorbar(im)
+fig.savefig("./fits2image.png", format="png", bbox_inches="tight")
+
+# show the segmentation map
+fig, ax = plt.subplots()
+im = ax.imshow(
+    segmap,
+    interpolation="nearest",
+    cmap="gray",
+    origin="lower",
+    vmin=0,
+    vmax=1,
+)
+fig.colorbar(im)
+fig.savefig("./segmap.png", format="png", bbox_inches="tight")
+
+# plot background-subtracted image
+fig, ax = plt.subplots()
+m, s = np.mean(data_sub), np.std(data_sub)
+im = ax.imshow(
+    data_sub,
+    interpolation="nearest",
+    cmap="gray",
+    vmin=m - s,
+    vmax=m + s,
+    origin="lower",
+)
+
+# plot an ellipse for each object
+for i in range(len(objects)):
+    e = Ellipse(
+        xy=(objects["x"][i], objects["y"][i]),
+        width=6 * objects["a"][i],
+        height=6 * objects["b"][i],
+        angle=objects["theta"][i] * 180.0 / np.pi,
+    )
+    e.set_facecolor("none")
+    e.set_edgecolor("red")
+    ax.add_artist(e)
+
+fig.savefig("./sources.png", format="png", bbox_inches="tight")
+
+plt.show()
+
+from oda_api.data_products import ODAAstropyTable
+
+cat = ODAAstropyTable(Table(data=objects))
+hdu_rms = fits.PrimaryHDU(segmap.astype("uint32"))
+hdu_rms.writeto("segmentation_map.fits")
+
+bkg_picture = "./bkg_image.png"  # oda:POSIXPath
+rms_picture = "./bkg_rms.png"  # oda:POSIXPath
+data_picture = "./fits2image.png"  # oda:POSIXPath
+sources_picture = "./sources.png"  # oda:POSIXPath
+segmentation_map_picture = "./segmap.png"  # oda:POSIXPath
+segmentation_map = "./segmentation_map.fits"  # oda:POSIXPath
+bkg_array = "./bkg_array.fits"  # oda:POSIXPath
+rms_array = "./bkg_rms.fits"  # oda:POSIXPath
+catalog_table = cat  # oda:ODAAstropyTable
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(
+    (
+        "out_source_extraction_catalog_table",
+        "catalog_table_galaxy.output",
+        catalog_table,
+    )
+)
+
+for _outn, _outfn, _outv in _oda_outs:
+    _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
+    if isinstance(_outv, str) and os.path.isfile(_outv):
+        shutil.move(_outv, _galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    elif getattr(_outv, "write_fits_file", None):
+        _outv.write_fits_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "fits"}
+    elif getattr(_outv, "write_file", None):
+        _outv.write_file(_galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    else:
+        with open(_galaxy_outfile_name, "w") as fd:
+            json.dump(_outv, fd, cls=CustomJSONEncoder)
+        _galaxy_meta_data[_outn] = {"ext": "json"}
+_simple_outs = []
+_simple_outs.append(
+    (
+        "out_source_extraction_bkg_picture",
+        "bkg_picture_galaxy.output",
+        bkg_picture,
+    )
+)
+_simple_outs.append(
+    (
+        "out_source_extraction_rms_picture",
+        "rms_picture_galaxy.output",
+        rms_picture,
+    )
+)
+_simple_outs.append(
+    (
+        "out_source_extraction_data_picture",
+        "data_picture_galaxy.output",
+        data_picture,
+    )
+)
+_simple_outs.append(
+    (
+        "out_source_extraction_sources_picture",
+        "sources_picture_galaxy.output",
+        sources_picture,
+    )
+)
+_simple_outs.append(
+    (
+        "out_source_extraction_segmentation_map_picture",
+        "segmentation_map_picture_galaxy.output",
+        segmentation_map_picture,
+    )
+)
+_simple_outs.append(
+    (
+        "out_source_extraction_segmentation_map",
+        "segmentation_map_galaxy.output",
+        segmentation_map,
+    )
+)
+_simple_outs.append(
+    ("out_source_extraction_bkg_array", "bkg_array_galaxy.output", bkg_array)
+)
+_simple_outs.append(
+    ("out_source_extraction_rms_array", "rms_array_galaxy.output", rms_array)
+)
+_numpy_available = True
+
+for _outn, _outfn, _outv in _simple_outs:
+    _galaxy_outfile_name = os.path.join(_galaxy_wd, _outfn)
+    if isinstance(_outv, str) and os.path.isfile(_outv):
+        shutil.move(_outv, _galaxy_outfile_name)
+        _galaxy_meta_data[_outn] = {"ext": "_sniff_"}
+    elif _numpy_available and isinstance(_outv, np.ndarray):
+        with open(_galaxy_outfile_name, "wb") as fd:
+            np.savez(fd, _outv)
+        _galaxy_meta_data[_outn] = {"ext": "npz"}
+    else:
+        with open(_galaxy_outfile_name, "w") as fd:
+            json.dump(_outv, fd)
+        _galaxy_meta_data[_outn] = {"ext": "expression.json"}
+
+with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
+    json.dump(_galaxy_meta_data, fd)
+print("*** Job finished successfully ***")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source_extractor_astro_tool.xml	Fri Jul 18 14:23:35 2025 +0000
@@ -0,0 +1,172 @@
+<tool id="source_extractor_astro_tool" name="source-extractor" version="0.0.1+galaxy0" profile="24.0">
+  <requirements>
+    <requirement type="package" version="6.1.4">astropy</requirement>
+    <requirement type="package" version="3.10.3">matplotlib</requirement>
+    <requirement type="package" version="1.21.4">wget</requirement>
+    <requirement type="package" version="1.4.1">sep</requirement>
+    <requirement type="package" version="1.2.33">oda-api</requirement>
+    <requirement type="package" version="2025.6.11">tifffile</requirement>
+    <requirement type="package" version="9.4.0">ipython</requirement>
+    <!--Requirements string 'nb2workflow>=1.3.30' can't be converted automatically. Please add the galaxy/conda requirement manually or modify the requirements file!-->
+  </requirements>
+  <command detect_errors="exit_code">ipython '$__tool_directory__/source_extraction.py'</command>
+  <environment_variables>
+    <environment_variable name="BASEDIR">$__tool_directory__</environment_variable>
+    <environment_variable name="GALAXY_TOOL_DIR">$__tool_directory__</environment_variable>
+  </environment_variables>
+  <configfiles>
+    <inputs name="inputs" filename="inputs.json" data_style="paths" />
+  </configfiles>
+  <inputs>
+    <param name="input_file" type="data" label="Input file" format="data" optional="false" />
+    <param name="mask_file" type="data" label="Mask file" format="data" optional="true" />
+    <param name="thresh" type="float" value="1.5" label="thresh" optional="false" />
+    <param name="err_option" type="select" label="err_option" optional="false">
+      <option value="array_rms">array_rms</option>
+      <option value="float_globalrms" selected="true">float_globalrms</option>
+      <option value="none">none</option>
+    </param>
+    <param name="maskthresh" type="float" value="0.0" label="maskthresh" optional="false" />
+    <param name="minarea" type="integer" value="5" label="minarea" optional="false" />
+    <param name="filter_case" type="select" label="Filter Case" optional="false">
+      <option value="default" selected="true">default</option>
+      <option value="file">file</option>
+      <option value="none">none</option>
+    </param>
+    <param name="filter_file" type="data" label="Filter file" format="data" optional="true" />
+    <param name="filter_type" type="select" label="filter_type" optional="false">
+      <option value="conv">conv</option>
+      <option value="matched" selected="true">matched</option>
+    </param>
+    <param name="deblend_nthresh" type="integer" value="32" label="deblend_nthresh" optional="false" />
+    <param name="deblend_cont" type="float" value="0.005" label="deblend_cont" optional="false" />
+    <param name="clean" type="boolean" checked="true" label="clean" optional="false" />
+    <param name="clean_param" type="float" value="1.0" label="clean_param" optional="false" />
+    <param name="bw" type="integer" value="64" label="bw" optional="false" />
+    <param name="bh" type="integer" value="64" label="bh" optional="false" />
+    <param name="fw" type="integer" value="3" label="fw" optional="false" />
+    <param name="fh" type="integer" value="3" label="fh" optional="false" />
+    <param name="fthresh" type="float" value="0.0" label="fthresh" optional="false" />
+  </inputs>
+  <outputs>
+    <data label="${tool.name} -&gt; source_extraction bkg_picture" name="out_source_extraction_bkg_picture" format="auto" from_work_dir="bkg_picture_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction rms_picture" name="out_source_extraction_rms_picture" format="auto" from_work_dir="rms_picture_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction data_picture" name="out_source_extraction_data_picture" format="auto" from_work_dir="data_picture_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction sources_picture" name="out_source_extraction_sources_picture" format="auto" from_work_dir="sources_picture_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction segmentation_map_picture" name="out_source_extraction_segmentation_map_picture" format="auto" from_work_dir="segmentation_map_picture_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction segmentation_map" name="out_source_extraction_segmentation_map" format="auto" from_work_dir="segmentation_map_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction bkg_array" name="out_source_extraction_bkg_array" format="auto" from_work_dir="bkg_array_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction rms_array" name="out_source_extraction_rms_array" format="auto" from_work_dir="rms_array_galaxy.output" />
+    <data label="${tool.name} -&gt; source_extraction catalog_table" name="out_source_extraction_catalog_table" format="auto" from_work_dir="catalog_table_galaxy.output" />
+  </outputs>
+  <tests>
+    <test expect_num_outputs="9">
+      <param name="input_file" location="https://gitlab.renkulab.io/astronomy/mmoda/source-extractor/-/raw/5680fc685979c56c69f98eec8153673422abfa72/input.fits" />
+      <param name="thresh" value="1.5" />
+      <param name="err_option" value="float_globalrms" />
+      <param name="maskthresh" value="0.0" />
+      <param name="minarea" value="5" />
+      <param name="filter_case" value="default" />
+      <param name="filter_type" value="matched" />
+      <param name="deblend_nthresh" value="32" />
+      <param name="deblend_cont" value="0.005" />
+      <param name="clean" value="True" />
+      <param name="clean_param" value="1.0" />
+      <param name="bw" value="64" />
+      <param name="bh" value="64" />
+      <param name="fw" value="3" />
+      <param name="fh" value="3" />
+      <param name="fthresh" value="0.0" />
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+  </tests>
+  <help>Source extractor
+================
+
+This tool can be used to extract luminous sources from sky images. It is
+entirely based on the
+`sep &lt;https://sep.readthedocs.io/en/stable/index.html&gt;`__ package, which
+is built on `Source
+Extractor &lt;https://sextractor.readthedocs.io/en/latest/Introduction.html&gt;`__.
+
+Input
+-----
+
+Important input parameters:
+
+1. **Input file** (``.fits`` or ``.tiff``) - A single-channel sky image
+   to analyze.
+
+2. **Mask file** (``.fits`` or ``.tiff``; optional) - A 2D numpy array.
+   True values, or numeric values greater than **maskthresh**, are
+   considered masked. Masking a pixel is equivalent to setting data to
+   zero and noise (if present) to infinity.
+
+3. **thresh** - Threshold pixel value for detection:
+   ``thresh * err[j, i]``, where ``err[j, i]`` is given by the
+   **err_option** parameter; ``j`` and ``i`` represent the pixel indices
+
+4. **err_option** - Sets the error that is taken into account into the
+   detection of sources:
+
+   - ``none`` - The value of **thresh** is taken as an absolute
+     threshold.
+   - ``array_rms`` - An array of the background RMS, i.e.&#160;for each
+     individual pixel.
+   - ``float_globalrms`` - A float value of the global background RMS.
+
+The rest of the parameters are described in the documentations of
+`sep.Background &lt;https://sep.readthedocs.io/en/stable/api/sep.Background.html#sep.Background&gt;`__
+and
+`sep.extract &lt;https://sep.readthedocs.io/en/stable/api/sep.extract.html#sep.extract&gt;`__.
+
+Output
+------
+
+Source Catalog
+~~~~~~~~~~~~~~
+
+The catalogue of sources is explained
+`here &lt;https://sep.readthedocs.io/en/stable/api/sep.extract.html#sep.extract&gt;`__.
+
+Images
+~~~~~~
+
+There are 4 images as output:
+
+- **Background**:
+
+  - ``.fits``: The output of ``sep.Background`` function, i.e.&#160;the
+    estimated 2D background of the input image.
+  - ``.png``: The gray-scale image of the previously described array
+
+- **Background noise**:
+
+  - ``.fits``: The RMS of the background image.
+  - ``.png``: The gray-scale image of the previously described array
+
+- **Input image** - The gray-scale input image.
+- **Sources** - The sources on the background subtracted input image
+- **Segmentation map**:
+
+  - ``.fits``: Each pixel is labeled with 0 or object ID (``0`` =
+    background; ``i+1`` = object ``i``).
+  - ``.png``: Binary mask (``1`` = source, ``0`` = background).
+
+Acknowledgement
+---------------
+
+Bertin, E. &amp; Arnouts, S. 1996: `SExtractor: Software for source
+extraction &lt;https://ui.adsabs.harvard.edu/abs/1996A%26AS..117..393B/abstract&gt;`__,
+Astronomy &amp; Astrophysics Supplement 317, 393
+
+Barbary, (2016), SEP: Source Extractor as a library, Journal of Open
+Source Software, 1(6), 58, doi:10.21105/joss.00058
+</help>
+  <citations>
+    <citation type="doi">10.1051/aas:1996164</citation>
+    <citation type="doi">10.21105/joss.00058</citation>
+  </citations>
+</tool>
\ No newline at end of file