Photometric error stage demo

author: Tianqing Zhang, John-Franklin Crenshaw

This notebook demonstrate the use of rail.creation.degraders.photometric_errors, which adds column for the photometric noise to the catalog based on the package PhotErr developed by John-Franklin Crenshaw. The RAIL stage PhotoErrorModel inherit from the Noisifier base classes, and the LSST, Roman, Euclid child classes inherit from the PhotoErrorModel.

If you’re interested in running this in pipeline mode, see 02_Photometric_Realization_with_Other_Surveys.ipynb in the pipeline_examples/core_examples/ folder.

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from rail.core.data import PqHandle
from rail.interactive.creation.degraders import photometric_errors
Install FSPS with the following commands:
pip uninstall fsps
git clone --recursive https://github.com/dfm/python-fsps.git
cd python-fsps
python -m pip install .
export SPS_HOME=$(pwd)/src/fsps/libfsps

LEPHAREDIR is being set to the default cache directory:
/home/runner/.cache/lephare/data
More than 1Gb may be written there.
LEPHAREWORK is being set to the default cache directory:
/home/runner/.cache/lephare/work
Default work cache is already linked.
This is linked to the run directory:
/home/runner/.cache/lephare/runs/20260601T134643
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.2.6 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.

Traceback (most recent call last):  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel_launcher.py", line 18, in <module>
    app.launch_new_instance()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/traitlets/config/application.py", line 1082, in launch_instance
    app.start()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/kernelapp.py", line 758, in start
    self.io_loop.start()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/tornado/platform/asyncio.py", line 211, in start
    self.asyncio_loop.run_forever()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/asyncio/base_events.py", line 603, in run_forever
    self._run_once()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/asyncio/base_events.py", line 1909, in _run_once
    handle._run()
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/utils.py", line 71, in preserve_context
    return await f(*args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/kernelbase.py", line 621, in shell_main
    await self.dispatch_shell(msg, subshell_id=subshell_id)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/kernelbase.py", line 478, in dispatch_shell
    await result
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/ipkernel.py", line 372, in execute_request
    await super().execute_request(stream, ident, parent)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/kernelbase.py", line 834, in execute_request
    reply_content = await reply_content
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/ipkernel.py", line 464, in do_execute
    res = shell.run_cell(
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/ipykernel/zmqshell.py", line 663, in run_cell
    return super().run_cell(*args, **kwargs)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3077, in run_cell
    result = self._run_cell(
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3132, in _run_cell
    result = runner(coro)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/async_helpers.py", line 128, in _pseudo_sync_runner
    coro.send(None)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3336, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3519, in run_ast_nodes
    if await self.run_code(code, result, async_=asy):
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3579, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/tmp/ipykernel_4040/2313627096.py", line 5, in <module>
    from rail.interactive.creation.degraders import photometric_errors
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/interactive/__init__.py", line 3, in <module>
    from . import calib, creation, estimation, evaluation, tools
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/interactive/calib/__init__.py", line 3, in <module>
    from rail.utils.interactive.initialize_utils import _initialize_interactive_module
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/utils/interactive/initialize_utils.py", line 17, in <module>
    from rail.utils.interactive.base_utils import (
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/utils/interactive/base_utils.py", line 10, in <module>
    rail.stages.import_and_attach_all(silent=True)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/stages/__init__.py", line 74, in import_and_attach_all
    RailEnv.import_all_packages(silent=silent)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/core/introspection.py", line 541, in import_all_packages
    _imported_module = importlib.import_module(pkg)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/som/__init__.py", line 1, in <module>
    from rail.creation.degraders.specz_som import *
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/rail/creation/degraders/specz_som.py", line 15, in <module>
    from somoclu import Somoclu
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/somoclu/__init__.py", line 11, in <module>
    from .train import Somoclu
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/somoclu/train.py", line 25, in <module>
    from .somoclu_wrap import train as wrap_train
  File "/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/somoclu/somoclu_wrap.py", line 11, in <module>
    import _somoclu_wrap
---------------------------------------------------------------------------

ImportError                               Traceback (most recent call last)

File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/numpy/core/_multiarray_umath.py:44, in __getattr__(attr_name)
     39     # Also print the message (with traceback).  This is because old versions
     40     # of NumPy unfortunately set up the import to replace (and hide) the
     41     # error.  The traceback shouldn't be needed, but e.g. pytest plugins
     42     # seem to swallow it and we should be failing anyway...
     43     sys.stderr.write(msg + tb_msg)
---> 44     raise ImportError(msg)
     46 ret = getattr(_multiarray_umath, attr_name, None)
     47 if ret is None:


ImportError:
A module that was compiled using NumPy 1.x cannot be run in
NumPy 2.2.6 as it may crash. To support both 1.x and 2.x
versions of NumPy, modules must be compiled with NumPy 2.0.
Some module may need to rebuild instead e.g. with 'pybind11>=2.12'.

If you are a user of the module, the easiest solution will be to
downgrade to 'numpy<2' or try to upgrade the affected module.
We expect that some modules will need time to support NumPy 2.
Warning: the binary library cannot be imported. You cannot train maps, but you can load and analyze ones that you have already saved.
The problem occurs because either compilation failed when you installed Somoclu or a path is missing from the dependencies when you are trying to import it. Please refer to the documentation to see your options.

Create a random catalog with ugrizy+YJHF bands as the the true input

data = np.random.normal(23, 3, size=(1000, 9))

data_df = pd.DataFrame(
    data=data, columns=["u", "g", "r", "i", "z", "y", "Y", "J", "H"]  # values
)
data_truth = PqHandle("input")
data_truth.set_data(data_df)
data_df
u g r i z y Y J H
0 27.597138 23.410746 24.332932 21.738095 25.762221 22.901580 22.856230 24.178991 21.208914
1 19.492572 24.849699 21.856477 28.348929 24.810721 24.516700 25.210521 25.491282 26.925990
2 29.528775 24.695894 19.489144 22.534017 22.105033 28.391906 27.913236 23.519688 23.033575
3 23.001564 25.114046 25.460844 13.027881 29.276571 16.347359 18.821405 25.464906 28.687989
4 18.741741 19.055497 20.419327 21.201373 19.648462 22.229420 20.813919 24.404762 23.080636
... ... ... ... ... ... ... ... ... ...
995 20.784500 22.244543 22.330757 23.481757 25.612422 25.472825 23.737939 20.384226 22.815083
996 24.693647 27.168555 26.823348 23.964394 19.816125 22.723520 25.730922 23.142357 26.550749
997 21.858594 22.220576 20.974283 20.229863 21.850657 26.088833 11.442345 20.401696 28.636086
998 21.300828 26.489568 23.922031 26.914451 21.419059 22.900724 20.922308 25.153080 23.993964
999 21.449172 24.369495 18.990733 24.942895 22.184028 22.599188 22.904663 23.185487 21.214893

1000 rows × 9 columns

The LSST error model adds noise to the optical bands

samples_w_errs = photometric_errors.lsst_error_model(sample=data_truth)

samples_w_errs["output"]
Inserting handle into data store.  input: None, LSSTErrorModel
Inserting handle into data store.  output: inprogress_output.pq, LSSTErrorModel
u u_err g g_err r r_err i i_err z z_err y y_err Y J H
0 30.102427 2.737282 23.407993 0.010137 24.338807 0.018368 21.726908 0.005746 25.986121 0.235627 22.916062 0.036208 22.856230 24.178991 21.208914
1 19.503096 0.005148 24.840171 0.032171 21.854077 0.005376 27.906369 0.590702 24.872526 0.090710 24.621745 0.162039 25.210521 25.491282 26.925990
2 27.652200 0.855331 24.713740 0.028803 19.490800 0.005012 22.535723 0.007619 22.094043 0.008986 inf inf 27.913236 23.519688 23.033575
3 23.004021 0.019162 25.043191 0.038465 25.430378 0.047588 13.028362 0.005000 27.492445 0.736039 16.353453 0.005004 18.821405 25.464906 28.687989
4 18.743195 0.005062 19.063674 0.005013 20.416982 0.005042 21.203254 0.005320 19.656611 0.005091 22.246399 0.020219 20.813919 24.404762 23.080636
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
995 20.793837 0.005809 22.258692 0.005972 22.324416 0.005802 23.488054 0.014406 25.744867 0.192635 25.422898 0.314775 23.737939 20.384226 22.815083
996 24.753537 0.087319 27.681906 0.367843 26.867078 0.167602 23.946306 0.021044 19.814546 0.005116 22.737282 0.030929 25.730922 23.142357 26.550749
997 21.859575 0.008522 22.219286 0.005915 20.977609 0.005095 20.233290 0.005071 21.852857 0.007828 26.458192 0.680951 11.442345 20.401696 28.636086
998 21.297655 0.006636 26.470775 0.135045 23.907867 0.012979 27.309042 0.378543 21.412526 0.006457 22.878675 0.035032 20.922308 25.153080 23.993964
999 21.443869 0.007004 24.384983 0.021691 18.989867 0.005007 24.970121 0.051696 22.195766 0.009585 22.572717 0.026779 22.904663 23.185487 21.214893

1000 rows × 15 columns

fig, ax = plt.subplots(figsize=(5, 4), dpi=100)

for band in "ugrizy":
    # pull out the magnitudes and errors
    mags = samples_w_errs["output"][band].to_numpy()
    errs = samples_w_errs["output"][band + "_err"].to_numpy()

    # sort them by magnitude
    mags, errs = mags[mags.argsort()], errs[mags.argsort()]

    # plot errs vs mags
    ax.plot(mags, errs, label=band)

ax.legend()
ax.set(xlabel="Magnitude (AB)", ylabel="Error (mags)")
plt.show()
../../../_images/Photometric_Realization_with_Other_Surveys_7_0.png

The Roman error model adds noise to the infrared bands

samples_w_errs_roman = photometric_errors.roman_error_model(
    sample=data_truth,
    m5={"Y": 27.0, "J": 26.4, "H": 26.4},
    theta={"Y": 27.0, "J": 0.106, "H": 0.128},
)

samples_w_errs_roman["output"]
Inserting handle into data store.  input: None, RomanErrorModel
Inserting handle into data store.  output: inprogress_output.pq, RomanErrorModel
u g r i z y Y Y_err J J_err H H_err
0 27.597138 23.410746 24.332932 21.738095 25.762221 22.901580 22.859942 0.006913 24.170842 0.027951 21.215713 0.005323
1 19.492572 24.849699 21.856477 28.348929 24.810721 24.516700 25.243721 0.042524 25.510416 0.091837 26.652818 0.244428
2 29.528775 24.695894 19.489144 22.534017 22.105033 28.391906 27.673376 0.343298 23.528140 0.016092 23.053220 0.011088
3 23.001564 25.114046 25.460844 13.027881 29.276571 16.347359 18.820148 0.005001 25.431686 0.085678 27.705607 0.553997
4 18.741741 19.055497 20.419327 21.201373 19.648462 22.229420 20.823225 0.005053 24.411337 0.034579 23.087848 0.011375
... ... ... ... ... ... ... ... ... ... ... ... ...
995 20.784500 22.244543 22.330757 23.481757 25.612422 25.472825 23.718735 0.011639 20.387234 0.005072 22.819517 0.009422
996 24.693647 27.168555 26.823348 23.964394 19.816125 22.723520 25.839393 0.072270 23.134508 0.011778 26.416626 0.200789
997 21.858594 22.220576 20.974283 20.229863 21.850657 26.088833 11.443304 0.005000 20.400411 0.005074 28.217478 0.788172
998 21.300828 26.489568 23.922031 26.914451 21.419059 22.900724 20.925271 0.005064 25.230300 0.071689 23.972169 0.023486
999 21.449172 24.369495 18.990733 24.942895 22.184028 22.599188 22.900142 0.007039 23.200419 0.012382 21.219162 0.005325

1000 rows × 12 columns

fig, ax = plt.subplots(figsize=(5, 4), dpi=100)

for band in "YJH":
    # pull out the magnitudes and errors
    mags = samples_w_errs_roman["output"][band].to_numpy()
    errs = samples_w_errs_roman["output"][band + "_err"].to_numpy()

    # sort them by magnitude
    mags, errs = mags[mags.argsort()], errs[mags.argsort()]

    # plot errs vs mags
    ax.plot(mags, errs, label=band)

ax.legend()
ax.set(xlabel="Magnitude (AB)", ylabel="Error (mags)")
plt.show()
../../../_images/Photometric_Realization_with_Other_Surveys_10_0.png

The Euclid error model adds noise to YJH bands

samples_w_errs_Euclid = photometric_errors.euclid_error_model(sample=data_truth)

samples_w_errs_Euclid["output"]
Inserting handle into data store.  input: None, EuclidErrorModel
Inserting handle into data store.  output: inprogress_output.pq, EuclidErrorModel
u g r i z y Y Y_err J J_err H H_err
0 27.597138 23.410746 24.332932 21.738095 25.762221 22.901580 22.855500 0.056147 24.108074 0.141762 21.225567 0.012625
1 19.492572 24.849699 21.856477 28.348929 24.810721 24.516700 24.716920 0.279554 25.484755 0.436889 26.171335 0.764601
2 29.528775 24.695894 19.489144 22.534017 22.105033 28.391906 inf inf 23.512650 0.084249 23.200052 0.069790
3 23.001564 25.114046 25.460844 13.027881 29.276571 16.347359 18.825782 0.005192 24.969218 0.291646 inf inf
4 18.741741 19.055497 20.419327 21.201373 19.648462 22.229420 20.820811 0.010090 24.393330 0.180941 23.025741 0.059771
... ... ... ... ... ... ... ... ... ... ... ... ...
995 20.784500 22.244543 22.330757 23.481757 25.612422 25.472825 23.698714 0.118230 20.381447 0.006980 22.800650 0.048909
996 24.693647 27.168555 26.823348 23.964394 19.816125 22.723520 25.294257 0.440046 23.145421 0.060827 inf inf
997 21.858594 22.220576 20.974283 20.229863 21.850657 26.088833 11.446687 0.005000 20.399652 0.007037 27.027379 1.279362
998 21.300828 26.489568 23.922031 26.914451 21.419059 22.900724 20.899159 0.010663 24.773929 0.248716 23.755097 0.113818
999 21.449172 24.369495 18.990733 24.942895 22.184028 22.599188 22.891534 0.057978 23.334772 0.071974 21.228030 0.012649

1000 rows × 12 columns

fig, ax = plt.subplots(figsize=(5, 4), dpi=100)

for band in "YJH":
    # pull out the magnitudes and errors
    mags = samples_w_errs_Euclid["output"][band].to_numpy()
    errs = samples_w_errs_Euclid["output"][band + "_err"].to_numpy()

    # sort them by magnitude
    mags, errs = mags[mags.argsort()], errs[mags.argsort()]

    # plot errs vs mags
    ax.plot(mags, errs, label=band)

ax.legend()
ax.set(xlabel="Magnitude (AB)", ylabel="Error (mags)")
plt.show()
../../../_images/Photometric_Realization_with_Other_Surveys_13_0.png