Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 63 additions & 24 deletions bluemath_tk/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@
import pytz


def _coerce_level(level: Union[int, str]) -> int:
"""Return a numeric logging level from an int or level name."""
if isinstance(level, int):
return level
name = str(level).upper()
value = logging.getLevelName(name)
if isinstance(value, int) and value != 0:
return value
raise ValueError(f"Unknown log level: {level!r}")


def _console_handlers(logger: logging.Logger) -> list[logging.Handler]:
return [
handler
for handler in logger.handlers
if isinstance(handler, logging.StreamHandler)
and not isinstance(handler, logging.FileHandler)
]


def _file_handlers(logger: logging.Logger) -> list[logging.FileHandler]:
return [
handler
for handler in logger.handlers
if isinstance(handler, logging.FileHandler)
]


def get_file_logger(
name: str,
logs_path: str = None,
Expand All @@ -23,16 +51,23 @@ def get_file_logger(
logs_path : str, optional
The file path where the log messages will be written. Default is None.
level : Union[int, str], optional
The logging level. Default is "INFO".
The logging level for the logger and file handler. Default is "INFO".
console : bool
Whether to add or not console / terminal logs. Default is True.
console_level : Union[int, str], optional
The logging level for console / terminal logs. Default is "WARNING".

Returns
-------
logging.Logger
Configured logger instance.

Notes
-----
Safe to call more than once for the same *name*: existing handlers are
updated (file level, console level, console on/off) instead of returning
a stale configuration.

Examples
--------
>>> from bluemath_tk.core.logging import get_file_logger
Expand All @@ -48,39 +83,43 @@ def get_file_logger(
>>> # 2023-10-22 14:55:23,458 - my_app_logger - ERROR - This is an error message.
"""

# If a logger with the specified name already exists, return it
if name in logging.Logger.manager.loggerDict:
return logging.getLogger(name)
file_level = _coerce_level(level)
stream_level = _coerce_level(console_level)

# Create a logger with the specified name
logger = logging.getLogger(name)
logger.setLevel(level)
logger.propagate = False # Avoid duplicate logs

# Get current date to append to logs_path
date_str = datetime.now(pytz.timezone("Europe/Madrid")).strftime("%Y-%m-%d")

# Create a file handler to write logs to the specified file
if logs_path is None:
os.makedirs("logs", exist_ok=True)
logs_path = os.path.join("logs", f"{name.strip()}_{date_str}.log")
else:
os.makedirs(os.path.dirname(logs_path))
file_handler = logging.FileHandler(logs_path)
logger.setLevel(file_level)
logger.propagate = False # Avoid duplicate logs via the root logger

# Define a logging format
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(formatter)

# Add the file handler to the logger
logger.addHandler(file_handler)
file_handlers = _file_handlers(logger)
if file_handlers:
for handler in file_handlers:
handler.setLevel(file_level)
if handler.formatter is None:
handler.setFormatter(formatter)
else:
date_str = datetime.now(pytz.timezone("Europe/Madrid")).strftime("%Y-%m-%d")
if logs_path is None:
os.makedirs("logs", exist_ok=True)
logs_path = os.path.join("logs", f"{name.strip()}_{date_str}.log")
else:
log_dir = os.path.dirname(logs_path)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(logs_path)
file_handler.setLevel(file_level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

for handler in _console_handlers(logger):
logger.removeHandler(handler)

# Also ouput logs in the console if requested
if console:
console_handler = logging.StreamHandler()
console_handler.setLevel(console_level)
console_handler.setLevel(stream_level)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

Expand Down
12 changes: 6 additions & 6 deletions bluemath_tk/core/plotting/scatter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import List, Optional, Tuple

import numpy as np
import pandas as pd
from matplotlib.axes import Axes
Expand All @@ -13,7 +11,7 @@

def density_scatter(
x: np.ndarray, y: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
Compute a density scatter for two arrays using gaussian KDE.

Expand All @@ -31,6 +29,8 @@ def density_scatter(
- Sorted x values
- Sorted y values
- Density values corresponding to each point

TODO: check mpl_scatter_density
"""

if len(x) != len(y):
Expand Down Expand Up @@ -122,10 +122,10 @@ def validation_scatter(


def plot_scatters_in_triangle(
dataframes: List[pd.DataFrame],
data_colors: Optional[List[str]] = None,
dataframes: list[pd.DataFrame],
data_colors: list[str] = None,
**kwargs,
) -> Tuple[Figure, np.ndarray]:
) -> tuple[Figure, np.ndarray]:
"""
Plot scatter plots of the dataframes with axes in a triangle arrangement.

Expand Down
15 changes: 12 additions & 3 deletions bluemath_tk/datamining/pca.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,20 @@ def __init__(
else:
self.logger.info(f"Explained variance ratio: {n_components}")
self.n_components = n_components

# try:
# import torch
# from qrpca.decomposition import qrpca

# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# self.logger.warning(f"Using QRPCA with device: {device}")
# self._pca = qrpca(n_component_ratio=self.n_components, device=device)
# except ImportError:
if is_incremental:
self.logger.info("Using Incremental PCA")
self.logger.warning("Using Incremental PCA")
self._pca = IncrementalPCA_(n_components=self.n_components)
else:
self.logger.info("Using PCA")
self.logger.warning("Using PCA")
self._pca = PCA_(n_components=self.n_components)

self.is_fitted: bool = False
Expand Down Expand Up @@ -817,7 +826,7 @@ def plot_eofs(
if map_center:
p_var = eofs[var].plot(
col="n_component",
col_wrap=3,
col_wrap=6,
transform=ccrs.PlateCarree(),
subplot_kws={"projection": ccrs.Orthographic(*map_center)},
)
Expand Down
Loading
Loading