import logging
import os
import click
from rich_click import RichGroup
import exosim.recipes as recipes
from exosim import __version__
from exosim.log import addLogFile, setLogLevel
from exosim.utils import RunConfig
# Logger configuration
[docs]logger = logging.getLogger("exosim")
[docs]code_name_and_version = "ExoSim {}".format(__version__)
# Common options decorator for shared command-line arguments
[docs]def common_options(func):
"""Decorator to add common options to commands."""
options = [
click.option(
"-c",
"--configuration",
"conf",
required=True,
type=click.Path(exists=True),
help="Input configuration file to pass.",
),
click.option(
"-o",
"--output",
"output",
required=False,
type=click.Path(),
default=None,
help="Output file.",
),
click.option(
"--nThreads",
"numberOfThreads",
required=False,
type=int,
default=None,
help="Number of threads for parallel processing.",
),
click.option(
"-d",
"--debug",
is_flag=True,
help="Enable debug mode.",
),
click.option(
"-l",
"--logger",
is_flag=True,
help="Save log file.",
),
click.option(
"-P",
"--plot",
is_flag=True,
help="Save plots.",
),
]
for option in reversed(options): # Add options in correct order
func = option(func)
return func
@click.group(cls=RichGroup)
@click.version_option(
version=__version__,
prog_name="ExoSim",
message="%(prog)s v%(version)s",
)
[docs]def cli():
"""ExoSim CLI - A simulation toolkit for exoplanet characterisation."""
@cli.command()
@common_options
@click.option(
"--plot-scale",
default="linear",
type=click.Choice(["linear", "dB"], case_sensitive=False),
show_default=True,
help="Plot scale. Can be 'linear' or 'dB'.",
)
[docs]def focalplane(conf, output, numberOfThreads, debug, logger, plot, plot_scale):
"""Create and plot the focal plane."""
_set_log(debug, logger, output)
_set_threads(numberOfThreads)
recipes.CreateFocalPlane(options_file=conf, output_file=output)
if plot:
_plot_focal_plane(output, plot_scale)
@cli.command()
@common_options
[docs]def radiometric(conf, output, numberOfThreads, debug, logger, plot):
"""Create a radiometric model."""
_set_log(debug, logger, output)
_set_threads(numberOfThreads)
recipes.RadiometricModel(options_file=conf, input_file=output)
if plot:
_plot_radiometric(output)
@cli.command()
@common_options
@click.option(
"-i",
"--input",
"input",
required=True,
type=click.Path(exists=True),
help="Input file.",
)
@click.option(
"--chunk-size",
default=2,
type=float,
show_default=True,
help="H5 file chunk size.",
)
[docs]def subexposures(
conf, input, output, numberOfThreads, debug, logger, plot, chunk_size
):
"""Create and plot sub-exposures."""
_set_log(debug, logger, output)
_set_threads(numberOfThreads)
RunConfig.chunk_size = chunk_size
recipes.CreateSubExposures(
options_file=conf, input_file=input, output_file=output
)
if plot:
_plot_subexposures(output)
@cli.command()
@common_options
@click.option(
"-i",
"--input",
"input",
required=True,
type=click.Path(exists=True),
help="Input file.",
)
@click.option(
"--chunk-size",
default=2,
type=float,
show_default=True,
help="H5 file chunk size.",
)
[docs]def ndrs(
conf, input, output, numberOfThreads, debug, logger, plot, chunk_size
):
"""Create and plot NDRs."""
_set_log(debug, logger, output)
_set_threads(numberOfThreads)
RunConfig.chunk_size = chunk_size
recipes.CreateNDRs(options_file=conf, input_file=input, output_file=output)
if plot:
_plot_ndrs(output)
# Support functions for shared behaviour
def _set_log(debug, log, output):
"""Configure logging based on options."""
if debug:
setLogLevel(logging.DEBUG)
if log:
log_file = "exosim.log"
if output:
log_dir = os.path.dirname(output)
log_file = os.path.join(log_dir, "exosim.log")
try:
addLogFile(fname=log_file)
except PermissionError:
addLogFile(fname="exosim.log")
def _set_threads(numberOfThreads):
"""Set the number of threads for parallel processing."""
if numberOfThreads:
RunConfig.n_job = numberOfThreads
# Plot functions for specific outputs
def _plot_focal_plane(output, scale):
"""Generate focal plane plots."""
import exosim.plots as plots
dir_name = os.path.join(os.path.dirname(output), "plots")
os.makedirs(dir_name, exist_ok=True)
focal_plane_plotter = plots.FocalPlanePlotter(input=output)
focal_plane_plotter.plot_focal_plane(time_step=0, scale=scale)
focal_plane_plotter.save_fig(os.path.join(dir_name, "focal_plane.png"))
focal_plane_plotter.plot_efficiency()
focal_plane_plotter.save_fig(os.path.join(dir_name, "efficiency.png"))
def _plot_radiometric(output):
"""Generate radiometric model plots."""
import exosim.plots as plots
dir_name = os.path.join(os.path.dirname(output), "plots")
os.makedirs(dir_name, exist_ok=True)
radiometric_plotter = plots.RadiometricPlotter(input=output)
radiometric_plotter.plot_table()
radiometric_plotter.save_fig(os.path.join(dir_name, "radiometric.png"))
radiometric_plotter.plot_apertures()
radiometric_plotter.save_fig(os.path.join(dir_name, "apertures.png"))
def _plot_subexposures(output):
"""Generate sub-exposures plots."""
import exosim.plots as plots
dir_name = os.path.join(os.path.dirname(output), "plots")
os.makedirs(dir_name, exist_ok=True)
sub_exposures_plotter = plots.SubExposuresPlotter(input=output)
sub_exposures_plotter.plot(dir_name)
def _plot_ndrs(output):
"""Generate NDRs plots."""
import exosim.plots as plots
dir_name = os.path.join(os.path.dirname(output), "plots")
os.makedirs(dir_name, exist_ok=True)
ndrs_plotter = plots.NDRsPlotter(input=output)
ndrs_plotter.plot(dir_name)
if __name__ == "__main__":
cli()