aboutsummaryrefslogtreecommitdiffstats
path: root/fg21sim
diff options
context:
space:
mode:
Diffstat (limited to 'fg21sim')
-rw-r--r--fg21sim/configs/00-general.conf.spec34
-rw-r--r--fg21sim/configs/manager.py44
-rw-r--r--fg21sim/utils/__init__.py1
-rw-r--r--fg21sim/utils/healpix.py30
-rw-r--r--fg21sim/utils/logging.py85
5 files changed, 182 insertions, 12 deletions
diff --git a/fg21sim/configs/00-general.conf.spec b/fg21sim/configs/00-general.conf.spec
new file mode 100644
index 0000000..c1a0eb3
--- /dev/null
+++ b/fg21sim/configs/00-general.conf.spec
@@ -0,0 +1,34 @@
+# Configurations for "fg21sim"
+# -*- mode: conf -*-
+#
+# Syntax: `ConfigObj`, https://github.com/DiffSK/configobj
+#
+# This file contains the general configurations, which control the general
+# behaviors, or will be used in other configuration sections.
+
+[logging]
+# DEBUG: Detailed information, typically of interest only when diagnosing
+# problems.
+# INFO: Confirmation that things are working as expected.
+# WARNING: An dinciation that something unexpected happended, or indicative
+# of some problem in the near future (e.g., "disk space low").
+# The software is still working as expected.
+# ERROR: Due to a more serious problem, the software has not been able to
+# perform some function.
+# CRITICAL: A serious error, indicating that the program itself may be unable
+# to continue running.
+level = option("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", default="INFO")
+
+# Set the format of displayed messages
+format = string(default="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
+
+# Set the date/time format in messages (default: ISO8601)
+datefmt = string(default="%Y-%m-%dT%H:%M:%S")
+
+# Set the logging filename (will create a `FileHandler`)
+filename = string(default="")
+# Set the mode to open the above logging file
+filemode = option("w", "a", default="a")
+
+# Set the stream used to initialize the `StreamHandler`
+stream = option("stderr", "stdout", "", default="stderr")
diff --git a/fg21sim/configs/manager.py b/fg21sim/configs/manager.py
index 933a63d..f5465a9 100644
--- a/fg21sim/configs/manager.py
+++ b/fg21sim/configs/manager.py
@@ -10,13 +10,19 @@ Configuration manager.
"""
import os
+import sys
from glob import glob
-from errors import ConfigError
+import logging
from configobj import ConfigObj, ConfigObjError, flatten_errors
from validate import Validator
+class ConfigError(Exception):
+ """Could not parse user configurations"""
+ pass
+
+
CONFIGS_PATH = os.path.dirname(__file__)
@@ -30,21 +36,23 @@ class ConfigManager:
spec = "\n".join([open(f).read() for f in configs_spec]).split("\n")
self._configspec = ConfigObj(spec, interpolation=False,
list_values=False, _inspec=True)
- self._validator = Validator()
- configs_default = ConfigObj(configspec=self._configspec)
+ configs_default = ConfigObj(interpolation=False,
+ configspec=self._configspec)
self._config = self.validate(configs_default)
if configs:
for config in configs:
self.read_config(config)
def read_config(self, config):
- newconfig = ConfigObj(config, configspec=self._configspec)
+ newconfig = ConfigObj(config, interpolation=False,
+ configspec=self._configspec)
newconfig = self.validate(newconfig)
self._config.merge(newconfig)
def validate(self, config):
+ validator = Validator()
try:
- results = config.validate(self._validator, preserve_errors=True)
+ results = config.validate(validator, preserve_errors=True)
except ConfigObjError as e:
raise ConfigError(e.message)
if not results:
@@ -72,3 +80,29 @@ class ConfigManager:
def set(self, key, value):
self._config[key] = value
+
+ @property
+ def logging(self):
+ """Get and prepare the logging configurations for
+ `logging.basicConfig()`
+ """
+ from logging import FileHandler, StreamHandler
+ conf = self.get("logging")
+ # logging handlers
+ handlers = []
+ stream = conf["stream"]
+ if stream:
+ handlers.append(StreamHandler(getattr(sys, stream)))
+ logfile = conf["filename"]
+ filemode = conf["filemode"]
+ if logfile:
+ handlers.append(FileHandler(logfile, mode=filemode))
+ #
+ logconf = {
+ "level": getattr(logging, conf["level"]),
+ "format": conf["format"],
+ "datefmt": conf["datefmt"],
+ "filemode": filemode,
+ "handlers": handlers,
+ }
+ return logconf
diff --git a/fg21sim/utils/__init__.py b/fg21sim/utils/__init__.py
index 3548b3f..650ab37 100644
--- a/fg21sim/utils/__init__.py
+++ b/fg21sim/utils/__init__.py
@@ -2,3 +2,4 @@
# MIT license
from .healpix import healpix2hpx, hpx2healpix
+from .logging import setup_logging
diff --git a/fg21sim/utils/healpix.py b/fg21sim/utils/healpix.py
index af0df59..acd85ef 100644
--- a/fg21sim/utils/healpix.py
+++ b/fg21sim/utils/healpix.py
@@ -33,7 +33,7 @@ from astropy.io import fits
from .. import logging
-logger = logging.getLogger()
+logger = logging.getLogger(__name__)
def healpix2hpx(data, header=None, append_history=None, append_comment=None):
@@ -61,11 +61,16 @@ def healpix2hpx(data, header=None, append_history=None, append_comment=None):
hp_data, hp_header = hp.read_map(data, nest=False, h=True,
verbose=False)
hp_header = fits.header.Header(hp_header)
+ logger.info("Read HEALPix data from file or HDU")
else:
hp_data, hp_header = np.asarray(data), fits.header.Header(header)
+ logger.info("Read HEALPix data from array and header")
+ logger.info("HEALPix index ordering: %s" % hp_header["ORDERING"])
if hp_header["ORDERING"] != "RING":
raise ValueError("only 'RING' ordering currently supported")
- nside = hp.npix2nside(len(hp_data))
+ npix = len(hp_data)
+ nside = hp.npix2nside(npix)
+ logger.info("HEALPix data: Npix=%d, Nside=%d" % (npix, nside))
if nside != hp_header["NSIDE"]:
raise ValueError("HEALPix data Nside does not match the header")
hp_data = np.concatenate([hp_data, [np.nan]])
@@ -103,20 +108,24 @@ def hpx2healpix(data, header=None, append_history=None, append_comment=None):
if isinstance(data, str):
hpx_hdu = fits.open(data)[0]
hpx_data, hpx_header = hpx_hdu.data, hpx_hdu.header
+ logger.info("Read HPX image from file")
elif isinstance(data, fits.PrimaryHDU):
hpx_data, hpx_header = data.data, data.header
+ logger.info("Read HPX image from HDU")
else:
hpx_data, hpx_header = np.asarray(data), fits.header.Header(header)
+ logger.info("Read HPX image from array and header")
+ logger.info("HPX coordinate system: ({0}, {1})".format(
+ hpx_header["CTYPE1"], hpx_header["CTYPE2"]))
if ((hpx_header["CTYPE1"], hpx_header["CTYPE2"]) !=
("GLON-HPX", "GLAT-HPX")):
- logger.debug("Input coordinate system: ({0}, {1})".format(
- hpx_header["CTYPE1"], hpx_header["CTYPE2"]))
raise ValueError("only Galactic 'HPX' projection currently supported")
# calculate Nside
nside = round(hpx_header["NAXIS1"] / 5)
nside2 = round(90 / np.sqrt(2) / hpx_header["CDELT2"])
if nside != nside2:
raise ValueError("Cannot determine the Nside value")
+ logger.info("Determined HEALPix Nside=%d" % nside)
#
npix = hp.nside2npix(nside)
hpx_idx = _calc_hpx_indices(nside).flatten()
@@ -150,7 +159,7 @@ def _calc_hpx_indices(nside):
----
* The indices are 0-based;
* Currently only HEALPix RING ordering supported;
- * The null/empty elements in the HPX projection are filled with '-1'.
+ * The null/empty elements in the HPX projection are filled with "-1".
"""
# number of horizontal/vertical facet
nfacet = 5
@@ -166,6 +175,7 @@ def _calc_hpx_indices(nside):
#
shape = (nfacet*nside, nfacet*nside)
indices = -np.ones(shape).astype(np.int)
+ logger.info("HPX indices matrix shape: {0}".format(shape))
#
# Loop vertically facet-by-facet
for jfacet in range(nfacet):
@@ -285,6 +295,7 @@ def _make_hpx_header(header, append_history=None, append_comment=None):
"[deg] Galactic latitude at the reference point")
header["PV2_1"] = (4, "HPX H parameter (longitude)")
header["PV2_2"] = (3, "HPX K parameter (latitude)")
+ logger.info("Made HPX FITS header")
#
header["DATE"] = (
datetime.now(timezone.utc).astimezone().isoformat(),
@@ -301,9 +312,11 @@ def _make_hpx_header(header, append_history=None, append_comment=None):
header.add_comment(comment)
#
if append_history is not None:
+ logger.info("HPX FITS header: append history")
for history in append_history:
header.add_history(history)
- if append_history is not None:
+ if append_comment is not None:
+ logger.info("HPX FITS header: append comments")
for comment in append_comment:
header.add_comment(comment)
return header
@@ -323,6 +336,7 @@ def _make_healpix_header(header, nside,
header["NPIX"] = (npix, "Total number of pixels")
header["FIRSTPIX"] = (0, "First pixel # (0 based)")
header["LASTPIX"] = (npix-1, "Last pixel # (0 based)")
+ logger.info("Made HEALPix FITS header")
#
header["DATE"] = (
datetime.now(timezone.utc).astimezone().isoformat(),
@@ -330,9 +344,11 @@ def _make_healpix_header(header, nside,
)
#
if append_history is not None:
+ logger.info("HEALPix FITS header: append history")
for history in append_history:
header.add_history(history)
- if append_history is not None:
+ if append_comment is not None:
+ logger.info("HEALPix FITS header: append comments")
for comment in append_comment:
header.add_comment(comment)
return header
diff --git a/fg21sim/utils/logging.py b/fg21sim/utils/logging.py
new file mode 100644
index 0000000..0abf4d3
--- /dev/null
+++ b/fg21sim/utils/logging.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2016 Weitian LI <liweitianux@live.com>
+# MIT license
+
+"""
+Logging utilities.
+"""
+
+import sys
+import logging
+from logging import FileHandler, StreamHandler
+
+
+def setup_logging(dict_config=None, level=None, stream=None, logfile=None):
+ """Setup the logging.
+ This will override the logging configurations in the config file
+ if specified (e.g., by command line arguments).
+
+ Parameters
+ ----------
+ dict_config : dict
+ Dict of logging configurations specified in the config file.
+ level : string;
+ Override the existing log level
+ stream : string; "stderr", "stdout", or ""
+ This controls where the log messages go to.
+ If not None, then override the old ``StreamHandler`` settings;
+ if ``stream=""``, then disable the ``StreamHandler``.
+ logfile : string
+ Specify the file where the log messages go to.
+ If ``logfile=""``, then disable the ``FileHandler``.
+
+ NOTE
+ ----
+ If the logging already has ``StreamHandler`` or ``FileHandler``
+ configured, then the old handler will be *replaced* (i.e., remove
+ the old one, then add the new one).
+ """
+ # default file open mode for logging to file
+ filemode = "a"
+ #
+ if dict_config:
+ # XXX:
+ # "basicConfig()" does NOT accept paramter "filemode" if the
+ # corresponding parameter "filename" NOT specified.
+ filemode = dict_config.pop("filemode", filemode)
+ logging.basicConfig(**dict_config)
+ #
+ root_logger = logging.getLogger()
+ #
+ if level is not None:
+ level_int = getattr(logging, level.upper(), None)
+ if not isinstance(level_int, int):
+ raise ValueError("invalid log level: %s" % level)
+ root_logger.setLevel(level_int)
+ #
+ if stream is None:
+ pass
+ elif stream in ["", "stderr", "stdout"]:
+ for handler in root_logger.handlers:
+ if isinstance(handler, StreamHandler):
+ # remove old ``StreamHandler``
+ root_logger.removeHandler(handler)
+ if stream == "":
+ # disable ``StreamHandler``
+ pass
+ else:
+ # add new ``StreamHandler``
+ handler = StreamHandler(getattr(sys, stream))
+ root_logger.addHandler(handler)
+ else:
+ raise ValueError("invalid stream: %s" % stream)
+ #
+ if logfile is not None:
+ for handler in root_logger.handlers:
+ if isinstance(handler, FileHandler):
+ filemode = handler.mode
+ # remove old ``FileHandler``
+ root_logger.removeHandler(handler)
+ if logfile == "":
+ # disable ``FileHandler``
+ pass
+ else:
+ # add new ``FileHandler``
+ handler = FileHandler(logfile, mode=filemode)
+ root_logger.addHandler(handler)