pymagnetos 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. pymagnetos/__init__.py +15 -0
  2. pymagnetos/cli.py +40 -0
  3. pymagnetos/core/__init__.py +19 -0
  4. pymagnetos/core/_config.py +340 -0
  5. pymagnetos/core/_data.py +132 -0
  6. pymagnetos/core/_processor.py +905 -0
  7. pymagnetos/core/config_models.py +57 -0
  8. pymagnetos/core/gui/__init__.py +6 -0
  9. pymagnetos/core/gui/_base_mainwindow.py +819 -0
  10. pymagnetos/core/gui/widgets/__init__.py +19 -0
  11. pymagnetos/core/gui/widgets/_batch_processing.py +319 -0
  12. pymagnetos/core/gui/widgets/_configuration.py +167 -0
  13. pymagnetos/core/gui/widgets/_files.py +129 -0
  14. pymagnetos/core/gui/widgets/_graphs.py +93 -0
  15. pymagnetos/core/gui/widgets/_param_content.py +20 -0
  16. pymagnetos/core/gui/widgets/_popup_progressbar.py +29 -0
  17. pymagnetos/core/gui/widgets/_text_logger.py +32 -0
  18. pymagnetos/core/signal_processing.py +1004 -0
  19. pymagnetos/core/utils.py +85 -0
  20. pymagnetos/log.py +126 -0
  21. pymagnetos/py.typed +0 -0
  22. pymagnetos/pytdo/__init__.py +6 -0
  23. pymagnetos/pytdo/_config.py +24 -0
  24. pymagnetos/pytdo/_config_models.py +59 -0
  25. pymagnetos/pytdo/_tdoprocessor.py +1052 -0
  26. pymagnetos/pytdo/assets/config_default.toml +84 -0
  27. pymagnetos/pytdo/gui/__init__.py +26 -0
  28. pymagnetos/pytdo/gui/_worker.py +106 -0
  29. pymagnetos/pytdo/gui/main.py +617 -0
  30. pymagnetos/pytdo/gui/widgets/__init__.py +8 -0
  31. pymagnetos/pytdo/gui/widgets/_buttons.py +66 -0
  32. pymagnetos/pytdo/gui/widgets/_configuration.py +78 -0
  33. pymagnetos/pytdo/gui/widgets/_graphs.py +280 -0
  34. pymagnetos/pytdo/gui/widgets/_param_content.py +137 -0
  35. pymagnetos/pyuson/__init__.py +7 -0
  36. pymagnetos/pyuson/_config.py +26 -0
  37. pymagnetos/pyuson/_config_models.py +71 -0
  38. pymagnetos/pyuson/_echoprocessor.py +1901 -0
  39. pymagnetos/pyuson/assets/config_default.toml +92 -0
  40. pymagnetos/pyuson/gui/__init__.py +26 -0
  41. pymagnetos/pyuson/gui/_worker.py +135 -0
  42. pymagnetos/pyuson/gui/main.py +767 -0
  43. pymagnetos/pyuson/gui/widgets/__init__.py +7 -0
  44. pymagnetos/pyuson/gui/widgets/_buttons.py +95 -0
  45. pymagnetos/pyuson/gui/widgets/_configuration.py +85 -0
  46. pymagnetos/pyuson/gui/widgets/_graphs.py +248 -0
  47. pymagnetos/pyuson/gui/widgets/_param_content.py +193 -0
  48. pymagnetos-0.1.0.dist-info/METADATA +23 -0
  49. pymagnetos-0.1.0.dist-info/RECORD +51 -0
  50. pymagnetos-0.1.0.dist-info/WHEEL +4 -0
  51. pymagnetos-0.1.0.dist-info/entry_points.txt +7 -0
@@ -0,0 +1,85 @@
1
+ """Utility functions."""
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+
7
+ def merge_dict_nested(
8
+ dic_user: Mapping[str, Any], dic_default: dict[str, Any]
9
+ ) -> dict[str, Any]:
10
+ """
11
+ Merge two dictionaries.
12
+
13
+ Keep elements from `dic_user` if it exists, with a fallback on elements in
14
+ `dic_default`. All nested dictionaries are also treated like this.
15
+
16
+ Parameters
17
+ ----------
18
+ dic_user : dict-like
19
+ Dict being merged, its values have priority over `dic_default`.
20
+ dic_default : dict-like
21
+ Dict being merged, its values are used if they are not in `dic_user`.
22
+
23
+ Returns
24
+ -------
25
+ dic : dict
26
+ Merged dict.
27
+ """
28
+ dic_copy = dic_default.copy()
29
+ for key, value in dic_user.items():
30
+ if key in dic_default:
31
+ if isinstance(value, Mapping):
32
+ dic_copy[key] = merge_dict_nested(value, dic_default[key])
33
+ else:
34
+ if key in dic_user:
35
+ dic_copy[key] = dic_user[key]
36
+
37
+ else:
38
+ dic_copy[key] = dic_user[key]
39
+
40
+ return dic_copy
41
+
42
+
43
+ def cast_type_dict(
44
+ dic: dict[str, Any], in_type: type, out_type: type
45
+ ) -> dict[str, Any]:
46
+ """
47
+ Convert `in_type` value to `out_type` values in a dictionary.
48
+
49
+ Nested dictionaries are supported, so the casted types can't be `dict`.
50
+
51
+ Parameters
52
+ ----------
53
+ dic : dict
54
+ Dict in which values are casted.
55
+ in_type, out_type : type
56
+ `in_type` types are casted to `out_type`. Can't be `dict`.
57
+ """
58
+ dic_copy = dic.copy()
59
+ for key, value in dic.items():
60
+ if isinstance(value, dict):
61
+ dic_copy[key] = cast_type_dict(value, in_type, out_type)
62
+ else:
63
+ if isinstance(dic_copy[key], in_type):
64
+ dic_copy[key] = out_type(dic[key])
65
+
66
+ return dic_copy
67
+
68
+
69
+ def strip_none_dict(dic: dict) -> dict:
70
+ """
71
+ Strip elements that are None from `dic`.
72
+
73
+ Parameters
74
+ ----------
75
+ dic : dict
76
+ Dict in which None values are removed.
77
+ """
78
+ dic_copy = dic.copy()
79
+ for key, value in dic.items():
80
+ if isinstance(value, dict):
81
+ dic_copy[key] = strip_none_dict(value)
82
+ elif value is None:
83
+ dic_copy.pop(key)
84
+
85
+ return dic_copy
pymagnetos/log.py ADDED
@@ -0,0 +1,126 @@
1
+ """Logging utilities."""
2
+
3
+ import logging
4
+ import sys
5
+ import warnings
6
+ from logging.handlers import RotatingFileHandler
7
+ from pathlib import Path
8
+ from types import TracebackType
9
+ from typing import Literal
10
+
11
+ from rich.logging import RichHandler
12
+
13
+ __all__ = ["configure_logger"]
14
+
15
+ # Setup logging directory
16
+ APP_DIR = Path.home() / ".pymagnetos"
17
+ APP_DIR.mkdir(parents=True, exist_ok=True)
18
+
19
+ APP_NAME = "pymagnetos"
20
+ LOGGING_FORMAT = "{asctime}.{msecs:3g} [{levelname}] : {message}"
21
+ CONSOLE_HANDLER = RichHandler(log_time_format="%H:%m:%S.%f")
22
+ FILE_FORMATTER = logging.Formatter(
23
+ LOGGING_FORMAT, style="{", datefmt="%Y-%m-%d %H:%M:%S"
24
+ )
25
+
26
+
27
+ def configure_logger(
28
+ logger: logging.Logger, filename: str, log_level: str = "INFO"
29
+ ) -> None:
30
+ """
31
+ Configure logger handlers.
32
+
33
+ Parameters
34
+ ----------
35
+ filename : str
36
+ Name of the file in which log will be written to. The file will be created under
37
+ $HOME/.pymagnetos/.
38
+ log_level : str, optional
39
+ Logging level to choose from ("DEBUG", "INFO", "WARNING", "ERROR"). Default is
40
+ "INFO".
41
+ """
42
+ # Define the file handler
43
+ log_file = APP_DIR / filename
44
+ file_handler = RotatingFileHandler(
45
+ log_file, mode="a", encoding="utf-8", maxBytes=int(1e6), backupCount=1
46
+ )
47
+ file_handler.setLevel(log_level)
48
+ file_handler.setFormatter(FILE_FORMATTER)
49
+
50
+ # Add handlers and set log levels
51
+ remove_file_handlers(logger, log_file, mode="same")
52
+ CONSOLE_HANDLER.setLevel(log_level)
53
+ logger.addHandler(CONSOLE_HANDLER)
54
+ logger.addHandler(file_handler)
55
+ logger.setLevel(log_level)
56
+
57
+ # Get the core logger and add the same handlers
58
+ core_logger = logging.getLogger(APP_NAME + ".core")
59
+ remove_file_handlers(core_logger, str(log_file), mode="all")
60
+ core_logger.addHandler(CONSOLE_HANDLER)
61
+ core_logger.addHandler(file_handler)
62
+ core_logger.setLevel(log_level)
63
+
64
+ # Set the logger to log exceptions
65
+ log_uncaugth_exceptions(logger)
66
+
67
+ logger.info(f"Logging to {log_file}")
68
+
69
+
70
+ def remove_file_handlers(
71
+ logger: logging.Logger,
72
+ file: str | Path,
73
+ mode: Literal["same", "all"] = "same",
74
+ warn: bool = True,
75
+ ) -> None:
76
+ """
77
+ Remove file handlers from a logger.
78
+
79
+ In mode "same", only handlers that log to the same file will be removed, in mode
80
+ "all", all file handlers are removed.
81
+
82
+ Parameters
83
+ ----------
84
+ logger : logging.Logger
85
+ Logger instance.
86
+ file : str or Path
87
+ Path to the log file.
88
+ mode : {"same", "all"}, optional
89
+ "same" : remove the handler only if it logs to the same file (default).
90
+ "all": remove all file handlers.
91
+ warn : bool, optional
92
+ Issue a warning when removing a handler set to a different file. Default is
93
+ True.
94
+ """
95
+ file = str(file)
96
+ for idx in range(len(logger.handlers)):
97
+ handler = logger.handlers[idx]
98
+ if isinstance(handler, logging.FileHandler):
99
+ if handler.baseFilename == file:
100
+ logger.removeHandler(handler)
101
+ else:
102
+ if mode == "all":
103
+ if warn:
104
+ warnings.warn(
105
+ f"The logger was writing to {handler.baseFilename}, "
106
+ "it will stop"
107
+ )
108
+ logger.removeHandler(handler)
109
+
110
+
111
+ def log_uncaugth_exceptions(logger: logging.Logger) -> None:
112
+ """Make uncaugth exceptions to be logged by the logger."""
113
+
114
+ def handle_exception(
115
+ exc_type: type[BaseException],
116
+ exc_value: BaseException,
117
+ exc_traceback: TracebackType | None,
118
+ ) -> None:
119
+ if not issubclass(exc_type, KeyboardInterrupt):
120
+ logger.critical(
121
+ "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
122
+ )
123
+
124
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
125
+
126
+ sys.excepthook = handle_exception
pymagnetos/py.typed ADDED
File without changes
@@ -0,0 +1,6 @@
1
+ """The `pytdo` module, an app for TDO experiments."""
2
+
3
+ from ._config import TDOConfig
4
+ from ._tdoprocessor import TDOProcessor
5
+
6
+ __all__ = ["TDOConfig", "TDOProcessor"]
@@ -0,0 +1,24 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+ from ..core import BaseConfig
5
+ from ._config_models import TDOConfiguration
6
+
7
+ CONFIG_DEFAULT = Path(__file__).parent / "assets" / "config_default.toml"
8
+
9
+
10
+ class TDOConfig(BaseConfig):
11
+ def __init__(
12
+ self,
13
+ user_file: str | Path | None = None,
14
+ default_file: str | Path | None = None,
15
+ **overrides: Any,
16
+ ) -> None:
17
+ if not default_file:
18
+ default_file = CONFIG_DEFAULT
19
+
20
+ super().__init__(TDOConfiguration, user_file, default_file, **overrides)
21
+
22
+ @classmethod
23
+ def loads(cls, json_data: str | bytes, **kwargs) -> "TDOConfig":
24
+ return super()._loads(TDOConfiguration, json_data, **kwargs)
@@ -0,0 +1,59 @@
1
+ """Pydantic models for the TDOProcessor Config."""
2
+
3
+ from collections.abc import Sequence
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from ..core.config_models import File, Metadata, Nexus, Parameters
10
+
11
+
12
+ class Settings(BaseModel):
13
+ """Settings for signal extraction, analysis and plotting."""
14
+
15
+ # Extraction
16
+ max_time: float
17
+
18
+ spectro_nperseg: int
19
+ spectro_win_size: int
20
+ spectro_noverlap: int
21
+
22
+ barycenters_fwindow: float
23
+ barycenters_fast: bool
24
+
25
+ time_offset: float
26
+
27
+ # Analysis
28
+ poly_window: Sequence[float]
29
+ poly_deg: int
30
+ npoints_interp_inverse: int
31
+ fft_window: Sequence[float]
32
+ fft_pad_mult: int
33
+ max_bfreq: float
34
+
35
+ # Display
36
+ offset: float
37
+
38
+
39
+ class TDOConfiguration(BaseModel):
40
+ """A model specialized for TDO experiments."""
41
+
42
+ expid: str
43
+ data_directory: Path
44
+
45
+ filenames: dict[str, str | Path] = dict()
46
+
47
+ files: dict[str, File]
48
+
49
+ measurements: dict[str, int]
50
+
51
+ parameters: Parameters
52
+ settings: Settings
53
+
54
+ metadata: Metadata
55
+
56
+ nx: dict[str, dict[str, Any]]
57
+ nexus: Nexus
58
+
59
+ model_config = ConfigDict(validate_assignment=True)