changeset 0:2b1759ccaa8b draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit f28a8cb73a7f3053eac92166867a48b3d4af28fd
author astroteam
date Fri, 25 Apr 2025 21:48:27 +0000
parents
children
files light_curve.py plot_tools_astro_tool.xml sky_plot.py spectrum.py
diffstat 4 files changed, 839 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/light_curve.py	Fri Apr 25 21:48:27 2025 +0000
@@ -0,0 +1,240 @@
+#!/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
+
+from oda_api.json import CustomJSONEncoder
+
+fn = "data.tsv"  # oda:POSIXPath
+skiprows = 0  # http://odahub.io/ontology#Integer
+sep = "whitespace"  # http://odahub.io/ontology#String ; oda:allowed_value "comma", "tab", "space", "whitespace", "semicolon"
+column = "T"  # http://odahub.io/ontology#String
+weight_col = ""  # http://odahub.io/ontology#String
+binning = "logarithmic"  # http://odahub.io/ontology#String ; oda:allowed_value "linear","logarithmic"
+minval = 0  # http://odahub.io/ontology#Float
+maxval = 0  # http://odahub.io/ontology#Float
+use_quantile_values = False  # http://odahub.io/ontology#Boolean
+nbins = 15  # http://odahub.io/ontology#Integer
+xlabel = "time, s"  # http://odahub.io/ontology#String
+ylabel = "Ncounts"  # http://odahub.io/ontology#String
+plot_mode = "flux"  # http://odahub.io/ontology#String ; oda:allowed_value "counts", "flux"
+
+_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
+fn = str(inp_pdic["fn"])
+skiprows = int(inp_pdic["skiprows"])
+sep = str(inp_pdic["sep"])
+column = str(inp_pdic["column"])
+weight_col = str(inp_pdic["weight_col"])
+binning = str(inp_pdic["binning"])
+minval = float(inp_pdic["minval"])
+maxval = float(inp_pdic["maxval"])
+use_quantile_values = bool(inp_pdic["use_quantile_values"])
+nbins = int(inp_pdic["nbins"])
+xlabel = str(inp_pdic["xlabel"])
+ylabel = str(inp_pdic["ylabel"])
+plot_mode = str(inp_pdic["plot_mode"])
+
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+
+if plot_mode != "counts" and ylabel == "Ncounts":
+    ylabel = plot_mode  # replace default value
+
+assert minval >= 0 or not use_quantile_values
+assert maxval >= 0 or not use_quantile_values
+assert minval <= 1 or not use_quantile_values
+assert maxval <= 1 or not use_quantile_values
+assert minval < maxval or minval == 0 or maxval == 0
+
+separators = {
+    "tab": "\t",
+    "comma": ",",
+    "semicolon": ";",
+    "whitespace": "\s+",
+    "space": " ",
+}
+
+df = None
+
+if sep == "auto":
+    for name, s in separators.items():
+        try:
+            df = pd.read_csv(fn, sep=s, index_col=False, skiprows=skiprows)
+            if len(df.columns) > 2:
+                sep = s
+                print("Detected separator: ", name)
+                break
+        except Exception as e:
+            print("Separator ", s, " failed", e)
+    assert sep != "auto", "Failed to find valid separator"
+
+if df is None:
+    df = pd.read_csv(fn, sep=separators[sep], index_col=False)
+
+df.columns
+
+def weighted_quantile(
+    values, quantiles, sample_weight=None, values_sorted=False, old_style=False
+):
+    """Very close to numpy.percentile, but supports weights.
+    NOTE: quantiles should be in [0, 1]!
+    :param values: numpy.array with data
+    :param quantiles: array-like with many quantiles needed
+    :param sample_weight: array-like of the same length as `array`
+    :param values_sorted: bool, if True, then will avoid sorting of initial array
+    :param old_style: if True, will correct output to be consistent with numpy.percentile.
+    :return: numpy.array with computed quantiles.
+    """
+    values = np.array(values)
+    quantiles = np.array(quantiles)
+    if sample_weight is None:
+        sample_weight = np.ones(len(values))
+    sample_weight = np.array(sample_weight)
+    assert np.all(quantiles >= 0) and np.all(
+        quantiles <= 1
+    ), "quantiles should be in [0, 1]"
+
+    if not values_sorted:
+        sorter = np.argsort(values)
+        values = values[sorter]
+        sample_weight = sample_weight[sorter]
+
+    weighted_quantiles = np.cumsum(sample_weight) - 0.5 * sample_weight
+    if old_style:
+        # To be convenient with np.percentile
+        weighted_quantiles -= weighted_quantiles[0]
+        weighted_quantiles /= weighted_quantiles[-1]
+    else:
+        weighted_quantiles /= np.sum(sample_weight)
+    return np.interp(quantiles, weighted_quantiles, values)
+
+def read_data(df, colname, optional=False):
+    for i, c in enumerate(df.columns):
+        if colname == f"c{i+1}":
+            print(colname, c)
+            return df[c].values
+        elif colname == c:
+            print(colname, c)
+            return df[c].values
+
+    assert optional, colname + " column not found"
+    return None
+
+delays = read_data(df, column)
+weights = read_data(df, weight_col, optional=True)
+if weights is None:
+    weights = np.ones_like(delays)
+
+if binning != "linear":
+    min_positive_val = np.min(delays[delays > 0])
+    delays[delays <= 0] = (
+        min_positive_val  # replace zero delays with minimal positive value
+    )
+
+if use_quantile_values:
+    minval, maxval = weighted_quantile(
+        delays, [minval, maxval], sample_weight=weights
+    )
+    if minval == maxval:
+        print("ignoreing minval and maxval (empty range)")
+        minval = np.min(delays)
+        maxval = np.max(delays)
+else:
+    if minval == 0:
+        minval = np.min(delays)
+    if maxval == 0:
+        maxval = np.max(delays)
+
+if minval == maxval:
+    print("correcting minval and maxval (empty range)")
+    maxval = minval * 1.1 if minval > 0 else 1e-100
+
+from numpy import log10
+
+if binning == "linear":
+    bins = np.linspace(minval, maxval, nbins + 1)
+else:
+    bins = np.logspace(log10(minval), log10(maxval), nbins + 1)
+bins
+
+if plot_mode == "flux" and binning == "logarithmic":
+    weights = weights / delays
+
+plt.figure()
+h = plt.hist(delays, weights=weights, bins=bins)
+
+if binning == "logarithmic":
+    plt.xscale("log")
+    plt.yscale("log")
+plt.xlabel(xlabel)
+plt.ylabel(ylabel)
+plt.savefig("Histogram.png", format="png", dpi=150)
+hist_counts = h[0]
+hist_bins = h[1]
+hist_mins = hist_bins[:-1]
+hist_maxs = hist_bins[1:]
+
+from astropy.table import Table
+from oda_api.data_products import ODAAstropyTable, PictureProduct
+
+names = ("bins_min", "bins_max", "counts")
+res = ODAAstropyTable(Table([hist_mins, hist_maxs, hist_counts], names=names))
+
+plot = PictureProduct.from_file("Histogram.png")
+
+histogram_data = res  # http://odahub.io/ontology#ODAAstropyTable
+histogram_picture = plot  # http://odahub.io/ontology#ODAPictureProduct
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(
+    (
+        "out_light_curve_histogram_data",
+        "histogram_data_galaxy.output",
+        histogram_data,
+    )
+)
+_oda_outs.append(
+    (
+        "out_light_curve_histogram_picture",
+        "histogram_picture_galaxy.output",
+        histogram_picture,
+    )
+)
+
+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"}
+
+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/plot_tools_astro_tool.xml	Fri Apr 25 21:48:27 2025 +0000
@@ -0,0 +1,270 @@
+<tool id="plot_tools_astro_tool" name="Plot Tools" version="0.0.1+galaxy0" profile="24.0">
+  <requirements>
+    <requirement type="package" version="2.2.3">pandas</requirement>
+    <requirement type="package" version="3.9.2">matplotlib</requirement>
+    <requirement type="package" version="5.3">astropy</requirement>
+    <requirement type="package" version="1.2.37">oda-api</requirement>
+    <requirement type="package" version="1.2">gammapy</requirement>
+    <requirement type="package" version="9.1.0">ipython</requirement>
+  </requirements>
+  <command detect_errors="exit_code">python '$__tool_directory__/${C_data_product_.DPselector_}.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>
+    <conditional name="C_data_product_">
+      <param name="DPselector_" type="select" label="Data Product">
+        <option value="light_curve" selected="true">light_curve</option>
+        <option value="spectrum" selected="false">spectrum</option>
+        <option value="sky_plot" selected="false">sky_plot</option>
+      </param>
+      <when value="light_curve">
+        <param name="fn" type="data" label="fn" format="data" optional="false" />
+        <param name="skiprows" type="integer" value="0" label="skiprows" optional="false" />
+        <param name="sep" type="select" label="sep" optional="false">
+          <option value="comma">comma</option>
+          <option value="semicolon">semicolon</option>
+          <option value="space">space</option>
+          <option value="tab">tab</option>
+          <option value="whitespace" selected="true">whitespace</option>
+        </param>
+        <param name="column" type="text" value="T" label="column" optional="false" />
+        <param name="weight_col" type="text" value="" label="weight_col" optional="false" />
+        <param name="binning" type="select" label="binning" optional="false">
+          <option value="linear">linear</option>
+          <option value="logarithmic" selected="true">logarithmic</option>
+        </param>
+        <param name="minval" type="float" value="0" label="minval" optional="false" />
+        <param name="maxval" type="float" value="0" label="maxval" optional="false" />
+        <param name="use_quantile_values" type="boolean" label="use_quantile_values" optional="false" />
+        <param name="nbins" type="integer" value="15" label="nbins" optional="false" />
+        <param name="xlabel" type="text" value="time, s" label="xlabel" optional="false" />
+        <param name="ylabel" type="text" value="Ncounts" label="ylabel" optional="false" />
+        <param name="plot_mode" type="select" label="plot_mode" optional="false">
+          <option value="counts">counts</option>
+          <option value="flux" selected="true">flux</option>
+        </param>
+      </when>
+      <when value="spectrum">
+        <param name="fn" type="data" label="fn" format="data" optional="false" />
+        <param name="skiprows" type="integer" value="0" label="skiprows" optional="false" />
+        <param name="sep" type="select" label="sep" optional="false">
+          <option value="auto">auto</option>
+          <option value="comma">comma</option>
+          <option value="semicolon">semicolon</option>
+          <option value="tab">tab</option>
+          <option value="whitespace" selected="true">whitespace</option>
+        </param>
+        <param name="column" type="text" value="c1" label="column" optional="false" />
+        <param name="weight_col" type="text" value="" label="weight_col" optional="false" />
+        <param name="binning" type="select" label="binning" optional="false">
+          <option value="linear">linear</option>
+          <option value="logarithmic" selected="true">logarithmic</option>
+        </param>
+        <param name="minval" type="float" value="0" label="minval" optional="false" />
+        <param name="maxval" type="float" value="0" label="maxval" optional="false" />
+        <param name="nbins" type="integer" value="15" label="nbins" optional="false" />
+        <param name="xlabel" type="text" value="Energy, [eV]" label="xlabel" optional="false" />
+        <param name="ylabel" type="text" value="Flux E^2, [eV]" label="ylabel" optional="false" />
+        <param name="spec_power" type="float" value="2.0" label="spec_power" optional="false" />
+      </when>
+      <when value="sky_plot">
+        <param name="fn" type="data" label="fn" format="data" optional="false" />
+        <param name="skiprows" type="integer" value="0" label="skiprows" optional="false" />
+        <param name="sep" type="select" label="sep" optional="false">
+          <option value="auto">auto</option>
+          <option value="comma">comma</option>
+          <option value="semicolon">semicolon</option>
+          <option value="tab">tab</option>
+          <option value="whitespace" selected="true">whitespace</option>
+        </param>
+        <param name="ra_col" type="text" value="c3" label="ra_col" optional="false" />
+        <param name="dec_col" type="text" value="c4" label="dec_col" optional="false" />
+        <param name="weight_col" type="text" value="" label="weight_col" optional="false" />
+        <param name="binsz" type="float" value="0.02" label="binsz" optional="false" />
+        <param name="window_size_RA" type="float" value="2.0" label="window_size_RA" optional="false" />
+        <param name="window_size_DEC" type="float" value="2.0" label="window_size_DEC" optional="false" />
+      </when>
+    </conditional>
+  </inputs>
+  <outputs>
+    <data label="${tool.name} -&gt; light_curve histogram_data" name="out_light_curve_histogram_data" format="auto" from_work_dir="histogram_data_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'light_curve'</filter>
+    </data>
+    <data label="${tool.name} -&gt; light_curve histogram_picture" name="out_light_curve_histogram_picture" format="auto" from_work_dir="histogram_picture_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'light_curve'</filter>
+    </data>
+    <data label="${tool.name} -&gt; spectrum histogram_data" name="out_spectrum_histogram_data" format="auto" from_work_dir="histogram_data_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'spectrum'</filter>
+    </data>
+    <data label="${tool.name} -&gt; spectrum histogram_picture" name="out_spectrum_histogram_picture" format="auto" from_work_dir="histogram_picture_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'spectrum'</filter>
+    </data>
+    <data label="${tool.name} -&gt; sky_plot plot" name="out_sky_plot_plot" format="auto" from_work_dir="plot_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'sky_plot'</filter>
+    </data>
+    <data label="${tool.name} -&gt; sky_plot fits_image" name="out_sky_plot_fits_image" format="auto" from_work_dir="fits_image_galaxy.output">
+      <filter>C_data_product_['DPselector_'] == 'sky_plot'</filter>
+    </data>
+  </outputs>
+  <tests>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="light_curve" />
+        <param name="fn" location="https://gitlab.renkulab.io/astronomy/mmoda/plot-tools/-/raw/57cc7b8180fa6c1a286114a9e7b903923f786631/data.tsv" />
+        <param name="skiprows" value="0" />
+        <param name="sep" value="whitespace" />
+        <param name="column" value="T" />
+        <param name="weight_col" value="" />
+        <param name="binning" value="logarithmic" />
+        <param name="minval" value="0" />
+        <param name="maxval" value="0" />
+        <param name="use_quantile_values" value="False" />
+        <param name="nbins" value="15" />
+        <param name="xlabel" value="time, s" />
+        <param name="ylabel" value="Ncounts" />
+        <param name="plot_mode" value="flux" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="spectrum" />
+        <param name="fn" location="https://gitlab.renkulab.io/astronomy/mmoda/plot-tools/-/raw/57cc7b8180fa6c1a286114a9e7b903923f786631/data.tsv" />
+        <param name="skiprows" value="0" />
+        <param name="sep" value="whitespace" />
+        <param name="column" value="c1" />
+        <param name="weight_col" value="" />
+        <param name="binning" value="logarithmic" />
+        <param name="minval" value="0" />
+        <param name="maxval" value="0" />
+        <param name="nbins" value="15" />
+        <param name="xlabel" value="Energy, [eV]" />
+        <param name="ylabel" value="Flux E^2, [eV]" />
+        <param name="spec_power" value="2.0" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+    <test expect_num_outputs="2">
+      <conditional name="C_data_product_">
+        <param name="DPselector_" value="sky_plot" />
+        <param name="fn" location="https://gitlab.renkulab.io/astronomy/mmoda/plot-tools/-/raw/57cc7b8180fa6c1a286114a9e7b903923f786631/data.tsv" />
+        <param name="skiprows" value="0" />
+        <param name="sep" value="whitespace" />
+        <param name="ra_col" value="c3" />
+        <param name="dec_col" value="c4" />
+        <param name="weight_col" value="" />
+        <param name="binsz" value="0.02" />
+        <param name="window_size_RA" value="2.0" />
+        <param name="window_size_DEC" value="2.0" />
+      </conditional>
+      <assert_stdout>
+        <has_text text="*** Job finished successfully ***" />
+      </assert_stdout>
+    </test>
+  </tests>
+  <help>Plot Tools
+==========
+
+Set of tools for astronomical data visualization
+
+- sky_plot - visualize sky map using set of events as input
+- light_cure - calculate and visualize light curve from a set of evebts
+- spectrum - calculate energy spectrum using set of events as input
+
+Parameters
+----------
+
+common parameters
+~~~~~~~~~~~~~~~~~
+
+fn - input data file
+
+skiprows - number of rows to skip
+
+sep - separator value
+
+sky_plot parameters
+~~~~~~~~~~~~~~~~~~~
+
+ra_col - RA column (either column name of string cXX, where XX &gt;= 1 is
+column number)
+
+dec_col - DEC column (either column name of string cXX, where XX &gt;= 1 is
+column number)
+
+weight_col - weight column (either column name of string cXX, where XX
+&gt;= 1 is column number, or empty string if data doesn&#8217;t contain weights)
+
+binsz - bin size in degrees
+
+window_size_RA - size of window (RA) in degrees
+
+window_size_DEC - size of window (DEC) in degrees
+
+light_cure parameters
+~~~~~~~~~~~~~~~~~~~~~
+
+column - time column (either column name of string cXX, where XX &gt;= 1 is
+column number)
+
+weight_col - weight column (either column name of string cXX, where XX
+&gt;= 1 is column number, or empty string if data doesn&#8217;t contain weights)
+
+binning - binning type (&#8216;logarithmic&#8217; or &#8220;linear&#8221;)
+
+minval - minimal value (use 0 to infer from data)
+
+maxval - maximal value (use 0 to infer from data)
+
+use_quantile_values - interpret minval and maxval as quantiles
+
+nbins=15 - number of bins
+
+xlabel - plot x-label
+
+ylabel - plot y-label
+
+plot_mode - plotting mode (&#8220;counts&#8221; or &#8220;flux&#8221;)
+
+spectrum parameters
+~~~~~~~~~~~~~~~~~~~
+
+column - energy column (either column name of string cXX, where XX &gt;= 1
+is column number)
+
+weight_col- weight column (either column name of string cXX, where XX &gt;=
+1 is column number, or empty string if data doesn&#8217;t contain weights)
+
+binning - binning type (&#8216;logarithmic&#8217; or &#8220;linear&#8221;)
+
+minval - minimal value (use 0 to infer from data)
+
+maxval - maximal value (use 0 to infer from data)
+
+nbins - number of bins
+
+xlabel - plot x-label
+
+ylabel - plot y-label
+
+spec_power - multiply spectrum by energy in this power on plot
+</help>
+  <citations>
+    <citation type="bibtex">@misc{label,
+			title = {Tool repository},
+			url = {https://renkulab.io/projects/astronomy/mmoda/plot-tools},
+			author = {Oleg Kalashev},
+			year = {2024},
+			note = {}
+		}</citation>
+  </citations>
+</tool>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sky_plot.py	Fri Apr 25 21:48:27 2025 +0000
@@ -0,0 +1,147 @@
+#!/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
+
+from oda_api.json import CustomJSONEncoder
+
+fn = "data.tsv"  # oda:POSIXPath
+skiprows = 0  # http://odahub.io/ontology#Integer
+sep = "whitespace"  # http://odahub.io/ontology#String ; oda:allowed_value "auto", "comma", "tab", "whitespace", "semicolon"
+
+ra_col = "c3"  # http://odahub.io/ontology#String
+dec_col = "c4"  # http://odahub.io/ontology#String
+weight_col = ""  # http://odahub.io/ontology#String
+binsz = 0.02  # http://odahub.io/ontology#Float
+window_size_RA = 2.0  # http://odahub.io/ontology#Degree
+window_size_DEC = 2.0  # http://odahub.io/ontology#Degree
+
+_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
+fn = str(inp_pdic["fn"])
+skiprows = int(inp_pdic["skiprows"])
+sep = str(inp_pdic["sep"])
+ra_col = str(inp_pdic["ra_col"])
+dec_col = str(inp_pdic["dec_col"])
+weight_col = str(inp_pdic["weight_col"])
+binsz = float(inp_pdic["binsz"])
+window_size_RA = float(inp_pdic["window_size_RA"])
+window_size_DEC = float(inp_pdic["window_size_DEC"])
+
+import astropy.units as u
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+from astropy.coordinates import SkyCoord
+from gammapy.maps import Map
+from oda_api.data_products import ImageDataProduct, PictureProduct
+
+separators = {
+    "tab": "\t",
+    "comma": ",",
+    "semicolon": ";",
+    "whitespace": "\s+",
+    "space": " ",
+}
+
+df = None
+
+if sep == "auto":
+    for name, s in separators.items():
+        try:
+            df = pd.read_csv(fn, sep=s, index_col=False, skiprows=skiprows)
+            if len(df.columns) > 2:
+                sep = s
+                print("Detected separator: ", name)
+                break
+        except Exception as e:
+            print("Separator ", s, " failed", e)
+    assert sep != "auto", "Failed to find valid separator"
+
+if df is None:
+    df = pd.read_csv(fn, sep=separators[sep], index_col=False)
+
+df.columns
+
+def read_data(df, colname, optional=False):
+    for i, c in enumerate(df.columns):
+        if colname == f"c{i+1}":
+            print(colname, c)
+            return df[c].values
+        elif colname == c:
+            print(colname, c)
+            return df[c].values
+
+    assert optional, colname + " column not found"
+    return None
+
+ra = read_data(df, ra_col)
+dec = read_data(df, dec_col)
+w = read_data(df, weight_col, optional=True)
+if w is None:
+    w = np.ones_like(ra)
+
+source = SkyCoord(ra=np.mean(ra) * u.deg, dec=np.mean(dec) * u.deg)
+
+map = Map.create(
+    binsz=binsz,
+    width=(window_size_RA * u.deg, window_size_DEC * u.deg),
+    frame="icrs",
+    axes=[],
+    skydir=SkyCoord(source),
+)
+
+map.fill_by_coord({"lat": dec * u.deg, "lon": ra * u.deg}, weights=w)
+
+map.plot()
+plt.savefig("map.png")
+
+map.write("map.fits", overwrite=True)
+fits_image = ImageDataProduct.from_fits_file("map.fits")
+
+plot = PictureProduct.from_file("map.png")
+
+plot = plot  # http://odahub.io/ontology#ODAPictureProduct
+fits_image = fits_image  # http://odahub.io/ontology#Image
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(("out_sky_plot_plot", "plot_galaxy.output", plot))
+_oda_outs.append(
+    ("out_sky_plot_fits_image", "fits_image_galaxy.output", fits_image)
+)
+
+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"}
+
+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/spectrum.py	Fri Apr 25 21:48:27 2025 +0000
@@ -0,0 +1,182 @@
+#!/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
+
+from oda_api.json import CustomJSONEncoder
+
+fn = "data.tsv"  # oda:POSIXPath
+skiprows = 0  # http://odahub.io/ontology#Integer
+sep = "whitespace"  # http://odahub.io/ontology#String ; oda:allowed_value "auto", "comma", "tab", "whitespace", "semicolon"
+column = "c1"  # http://odahub.io/ontology#String
+weight_col = ""  # http://odahub.io/ontology#String
+binning = "logarithmic"  # http://odahub.io/ontology#String ; oda:allowed_value "linear","logarithmic"
+minval = 0  # http://odahub.io/ontology#Float
+maxval = 0  # http://odahub.io/ontology#Float
+nbins = 15  # http://odahub.io/ontology#Integer
+xlabel = "Energy, [eV]"  # http://odahub.io/ontology#String
+ylabel = "Flux E^2, [eV]"  # http://odahub.io/ontology#String
+spec_power = 2.0  # http://odahub.io/ontology#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
+fn = str(inp_pdic["fn"])
+skiprows = int(inp_pdic["skiprows"])
+sep = str(inp_pdic["sep"])
+column = str(inp_pdic["column"])
+weight_col = str(inp_pdic["weight_col"])
+binning = str(inp_pdic["binning"])
+minval = float(inp_pdic["minval"])
+maxval = float(inp_pdic["maxval"])
+nbins = int(inp_pdic["nbins"])
+xlabel = str(inp_pdic["xlabel"])
+ylabel = str(inp_pdic["ylabel"])
+spec_power = float(inp_pdic["spec_power"])
+
+import matplotlib.pyplot as plt
+import numpy as np
+import pandas as pd
+
+separators = {
+    "tab": "\t",
+    "comma": ",",
+    "semicolon": ";",
+    "whitespace": "\s+",
+    "space": " ",
+}
+
+df = None
+
+if sep == "auto":
+    for name, s in separators.items():
+        try:
+            df = pd.read_csv(fn, sep=s, index_col=False, skiprows=skiprows)
+            if len(df.columns) > 2:
+                sep = s
+                print("Detected separator: ", name)
+                break
+        except Exception as e:
+            print("Separator ", s, " failed", e)
+    assert sep != "auto", "Failed to find valid separator"
+
+if df is None:
+    df = pd.read_csv(fn, sep=separators[sep], index_col=False)
+
+df.columns
+
+def read_data(df, colname, optional=False):
+    for i, c in enumerate(df.columns):
+        if colname == f"c{i+1}":
+            print(colname, c)
+            return df[c].values
+        elif colname == c:
+            print(colname, c)
+            return df[c].values
+
+    assert optional, colname + " column not found"
+    return None
+
+values = read_data(df, column)
+weights = read_data(df, weight_col, optional=True)
+if weights is None:
+    weights = np.ones_like(values)
+
+values, weights
+
+from numpy import log10
+
+if minval == 0:
+    minval = np.min(values)
+
+if maxval == 0:
+    maxval = np.max(values)
+
+if binning == "linear":
+    bins = np.linspace(minval, maxval, nbins + 1)
+else:
+    bins = np.logspace(log10(minval), log10(maxval), nbins + 1)
+bins
+
+bin_val, _ = np.histogram(values, weights=weights, bins=bins)
+len(bin_val), len(bins)
+bin_width = bins[1:] - bins[:-1]
+flux = bin_val / bin_width
+if binning == "linear":
+    spec_point = 0.5 * (bins[1:] + bins[:-1])
+else:
+    spec_point = np.sqrt(bins[1:] * bins[:-1])
+
+plt.figure()
+h = plt.plot(spec_point, flux * spec_point**spec_power)
+
+if binning == "logarithmic":
+    plt.xscale("log")
+    plt.yscale("log")
+
+plt.xlabel(xlabel)
+plt.ylabel(ylabel)
+plt.savefig("spectrum.png", format="png", dpi=150)
+
+from astropy.table import Table
+from oda_api.data_products import ODAAstropyTable, PictureProduct
+
+names = ("bins_min", "bins_max", "flux")
+
+res = ODAAstropyTable(Table([bins[:-1], bins[1:], flux], names=names))
+
+plot = PictureProduct.from_file("spectrum.png")
+
+histogram_data = res  # http://odahub.io/ontology#ODAAstropyTable
+histogram_picture = plot  # http://odahub.io/ontology#ODAPictureProduct
+
+# output gathering
+_galaxy_meta_data = {}
+_oda_outs = []
+_oda_outs.append(
+    (
+        "out_spectrum_histogram_data",
+        "histogram_data_galaxy.output",
+        histogram_data,
+    )
+)
+_oda_outs.append(
+    (
+        "out_spectrum_histogram_picture",
+        "histogram_picture_galaxy.output",
+        histogram_picture,
+    )
+)
+
+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"}
+
+with open(os.path.join(_galaxy_wd, "galaxy.json"), "w") as fd:
+    json.dump(_galaxy_meta_data, fd)
+print("*** Job finished successfully ***")