poulet-py 0.1.2__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.
poulet_py/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from lazy_loader import attach_stub
2
+
3
+ __getattr__, __dir__, __all__ = attach_stub(__name__, __file__)
poulet_py/__init__.pyi ADDED
@@ -0,0 +1,46 @@
1
+ # ruff: noqa TID252
2
+ from .config import LOGGER, SETTINGS, Settings, setup_logging
3
+ from .converters import Seq
4
+ from .hardware import (
5
+ TCS,
6
+ Arduino,
7
+ BaslerCamera,
8
+ JulaboChiller,
9
+ TCSCommand,
10
+ TCSStimulus,
11
+ ThermalCamera,
12
+ )
13
+ from .tools import (
14
+ check_or_create,
15
+ define_folder_name,
16
+ generate_stimulus_sequence,
17
+ go_to,
18
+ json_serializer,
19
+ sanitize_path,
20
+ save_metadata_exp,
21
+ )
22
+ from .utils import Oscilloscope, TCSInterface
23
+
24
+ __all__ = [
25
+ "LOGGER",
26
+ "SETTINGS",
27
+ "TCS",
28
+ "Arduino",
29
+ "BaslerCamera",
30
+ "JulaboChiller",
31
+ "Oscilloscope",
32
+ "Seq",
33
+ "Settings",
34
+ "TCSCommand",
35
+ "TCSInterface",
36
+ "TCSStimulus",
37
+ "ThermalCamera",
38
+ "check_or_create",
39
+ "define_folder_name",
40
+ "generate_stimulus_sequence",
41
+ "go_to",
42
+ "json_serializer",
43
+ "sanitize_path",
44
+ "save_metadata_exp",
45
+ "setup_logging",
46
+ ]
@@ -0,0 +1,3 @@
1
+ from lazy_loader import attach_stub
2
+
3
+ __getattr__, __dir__, __all__ = attach_stub(__name__, __file__)
@@ -0,0 +1,5 @@
1
+ # ruff: noqa TID252
2
+ from .logging import LOGGER, setup_logging
3
+ from .settings import SETTINGS, Settings
4
+
5
+ __all__ = ["LOGGER", "SETTINGS", "Settings", "setup_logging"]
@@ -0,0 +1,94 @@
1
+ from logging import FileHandler, Formatter, Logger, getLogger
2
+
3
+ from rich.console import Console
4
+ from rich.logging import RichHandler
5
+
6
+ from poulet_py.config.settings import SETTINGS
7
+
8
+
9
+ def setup_logging(
10
+ logger: Logger,
11
+ *,
12
+ terminal_width: int | None = None,
13
+ show_time: bool = False,
14
+ show_path: bool = True,
15
+ markup: bool = True,
16
+ rich_tracebacks: bool = True,
17
+ tracebacks_extra_lines: int = 4,
18
+ tracebacks_word_wrap: bool = True,
19
+ tracebacks_show_locals: bool = True,
20
+ level: int | str = "warning",
21
+ file: str | None = None,
22
+ ) -> None:
23
+ """
24
+ Configure logging for the provided logger with optional rich formatting
25
+ and file logging.
26
+
27
+ Parameters
28
+ ----------
29
+ logger : Logger
30
+ The logger instance to configure.
31
+ terminal_width : int, optional
32
+ The width of the terminal for rich console output.
33
+ If None, the default width is used.
34
+ show_time : bool, optional
35
+ Whether to show the time in the log output. Default is False.
36
+ show_path : bool, optional
37
+ Whether to show the path in the log output. Default is True.
38
+ markup : bool, optional
39
+ Whether to enable markup in the log output. Default is True.
40
+ rich_tracebacks : bool, optional
41
+ Whether to enable rich tracebacks. Default is True.
42
+ tracebacks_extra_lines : int, optional
43
+ Number of extra lines to show in tracebacks. Default is 4.
44
+ tracebacks_word_wrap : bool, optional
45
+ Whether to enable word wrap in tracebacks. Default is True.
46
+ tracebacks_show_locals : bool, optional
47
+ Whether to show local variables in tracebacks. Default is True.
48
+ level : int or str, optional
49
+ The logging level to set for the logger. Default is warning.
50
+ file : str, optional
51
+ The file to log to. If None, logs are output to the console.
52
+
53
+ Returns
54
+ -------
55
+ None
56
+ """
57
+ if file is not None:
58
+ file_handler = FileHandler(file)
59
+ file_handler.setFormatter(Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
60
+ logger.addHandler(file_handler)
61
+ else:
62
+ console = Console(width=terminal_width) if terminal_width else Console()
63
+ rich_handler = RichHandler(
64
+ show_time=show_time,
65
+ show_level=True,
66
+ rich_tracebacks=rich_tracebacks,
67
+ tracebacks_show_locals=tracebacks_show_locals,
68
+ tracebacks_word_wrap=tracebacks_word_wrap,
69
+ tracebacks_extra_lines=tracebacks_extra_lines,
70
+ markup=markup,
71
+ show_path=show_path,
72
+ console=console,
73
+ )
74
+ rich_handler.setFormatter(Formatter("%(message)s"))
75
+ logger.addHandler(rich_handler)
76
+
77
+ logger.setLevel(level.upper())
78
+ logger.propagate = False
79
+
80
+
81
+ # Global instance of the `logger` object
82
+ LOGGER = getLogger()
83
+ setup_logging(LOGGER, level=SETTINGS.log.level, file=SETTINGS.log.file)
84
+ """
85
+ An instance of the `logger` object.
86
+
87
+ This instance holds can be imported and used throughout the application
88
+ for logging.
89
+
90
+ Example
91
+ -------
92
+ >>> from poulet_py.config.logging import LOGGER
93
+ >>> LOGGER.warning("This is a warning message.")
94
+ """
@@ -0,0 +1,90 @@
1
+ """
2
+ Configuration settings.
3
+
4
+ This module defines the application's configuration settings using Pydantic's
5
+ `BaseSettings` and `BaseModel`. It supports loading configuration from environment
6
+ variables and a `.env` file.
7
+
8
+ Usage Examples
9
+ --------------
10
+ 1. Accessing Default Settings:
11
+ >>> from poulet_py.config.settings import settings
12
+ >>> print(settings.log.level) # Output: "info"
13
+ >>> print(settings.log.file) # Output: None
14
+
15
+ 2. Overriding Settings with Environment Variables:
16
+ Set environment variables before running the application:
17
+ ```bash
18
+ export LOG__LEVEL="debug"
19
+ export LOG__FILE="/var/log/myapp.log"
20
+ ```
21
+ Then access the settings in your code:
22
+ >>> from poulet_py.config.settings import settings
23
+ >>> print(settings.log.level) # Output: "debug"
24
+ >>> print(settings.log.file) # Output: "/var/log/myapp.log"
25
+
26
+ 3. Loading Settings from a `.env` File:
27
+ Create a `.env` file in the project root:
28
+ ```env
29
+ LOG__LEVEL=warning
30
+ LOG__FILE=/tmp/app.log
31
+ ```
32
+ Then access the settings in your code:
33
+ >>> from poulet_py.config.settings import settings
34
+ >>> print(settings.log.level) # Output: "warning"
35
+ >>> print(settings.log.file) # Output: "/tmp/app.log"
36
+
37
+ 4. Programmatically Updating Settings:
38
+ >>> from poulet_py.config.settings import Settings
39
+ >>> custom_settings = Settings(log={"level": "error", "file": "/custom/path.log"})
40
+ >>> print(custom_settings.log.level) # Output: "error"
41
+ >>> print(custom_settings.log.file) # Output: "/custom/path.log"
42
+ """
43
+
44
+ from dotenv import find_dotenv
45
+ from pydantic import BaseModel, Field
46
+ from pydantic_settings import BaseSettings, SettingsConfigDict
47
+
48
+
49
+ class Log(BaseModel):
50
+ level: str = Field("info", description="The logging level")
51
+ file: str | None = Field(
52
+ None,
53
+ description="""The file path for logging. If `None`,
54
+ logging is done to the console.
55
+ """,
56
+ )
57
+
58
+
59
+ class Settings(BaseSettings):
60
+ """
61
+ This class loads configuration from environment variables and a `.env` file.
62
+ It supports nested configuration using the `env_nested_delimiter`
63
+ (e.g., `LOG__LEVEL`).
64
+ """
65
+
66
+ model_config = SettingsConfigDict(
67
+ env_nested_delimiter="__",
68
+ env_file=find_dotenv(),
69
+ env_file_encoding="utf-8",
70
+ extra="ignore",
71
+ )
72
+ log: Log = Field(Log(), description="Logging configuration settings")
73
+
74
+
75
+ # Global instance of the `Settings` class
76
+ SETTINGS = Settings()
77
+ """
78
+ An instance of the `Settings` class.
79
+
80
+ This instance holds the application's configuration, loaded from environment
81
+ variables and a `.env` file.
82
+ It can be imported and used throughout the application to access
83
+ configuration settings.
84
+
85
+ Example
86
+ -------
87
+ To access the logging level:
88
+ >>> from poulet_py.config.settings import SETTINGS
89
+ >>> print(SETTINGS.log.level)
90
+ """
@@ -0,0 +1,3 @@
1
+ from lazy_loader import attach_stub
2
+
3
+ __getattr__, __dir__, __all__ = attach_stub(__name__, __file__)
@@ -0,0 +1,4 @@
1
+ # ruff: noqa TID252
2
+ from .seq import Seq
3
+
4
+ __all__ = ["Seq"]
@@ -0,0 +1,147 @@
1
+ try:
2
+ from pathlib import Path
3
+ from pickle import HIGHEST_PROTOCOL
4
+ from typing import Any
5
+
6
+ from flirpy.io.fff import Fff
7
+ from flirpy.io.seq import Seq as SeqBase
8
+ from h5py import File
9
+ from numpy import array, ndarray
10
+ from pandas._typing import CompressionOptions
11
+ from pandas.io.pickle import to_pickle as pd_to_pickle
12
+ except ImportError as e:
13
+ msg = """
14
+ Missing 'seq' module. Install options:
15
+ - Dedicated: pip install poulet_py[seq]
16
+ - Module: pip install poulet_py[converters]
17
+ - Full: pip install poulet_py[all]
18
+ """
19
+ raise ImportError(msg) from e
20
+
21
+
22
+ class Seq(SeqBase):
23
+ """A sequence handler for FLIR thermal data with export capabilities.
24
+
25
+ Extends `flirpy.io.seq.Seq` to add NumPy, HDF5, and pickle serialization.
26
+
27
+ Parameters
28
+ ----------
29
+ input_file : str | Path
30
+ Path to the input SEQ file.
31
+ height : int, optional
32
+ Force a specific image height. If None, uses the file's metadata.
33
+ width : int, optional
34
+ Force a specific image width. If None, uses the file's metadata.
35
+ raw : bool, default=False
36
+ If True, returns the raw bytes not Fff class.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ input_file: str | Path,
42
+ *,
43
+ height: int | None = None,
44
+ width: int | None = None,
45
+ raw: bool = False,
46
+ ):
47
+ super().__init__(input_file, height=height, width=width, raw=raw)
48
+
49
+ def to_list(self) -> list:
50
+ """Convert the sequence to a list of frames.
51
+
52
+ Returns
53
+ -------
54
+ list
55
+ List of frames (as ndarray or bytes if unprocessed).
56
+ """
57
+ return [frame.get_image() if isinstance(frame, Fff) else frame for frame in self]
58
+
59
+ def to_numpy(self, *, dtype: type | None = None) -> ndarray:
60
+ """Convert the sequence to a NumPy array.
61
+
62
+ Parameters
63
+ ----------
64
+ dtype : type, optional
65
+ Desired data type for the output array (e.g., `np.float32`).
66
+
67
+ Returns
68
+ -------
69
+ ndarray
70
+ Stacked frames as a 3D array (n_frames, height, width).
71
+ """
72
+ return array(self.to_list(), dtype=dtype)
73
+
74
+ def to_hdf(
75
+ self,
76
+ path: str | Path,
77
+ *,
78
+ key,
79
+ mode="w",
80
+ dtype: type | None = None,
81
+ complib=None,
82
+ complevel=None,
83
+ meta: dict[str, Any] | None = None,
84
+ ):
85
+ """Save the sequence to an HDF5 file.
86
+
87
+ Parameters
88
+ ----------
89
+ path : str | Path
90
+ Output file path.
91
+ key : str
92
+ HDF5 dataset name.
93
+ mode : str, default="w"
94
+ File mode ('w', 'a', 'r+', etc.).
95
+ dtype : type, optional
96
+ Data type for the stored array.
97
+ complib : str, optional
98
+ Compression library (e.g., 'gzip', 'lzf').
99
+ complevel : int, optional
100
+ Compression level (1-9 for gzip).
101
+ meta : dict, optional
102
+ Metadata to store as HDF5 attributes.
103
+ """
104
+ data = self.to_numpy(dtype=dtype)
105
+ with File(path, mode) as f:
106
+ ds = f.create_dataset(key, data=data, compression=complib, compression_opts=complevel)
107
+
108
+ if meta:
109
+ for k, v in meta.items():
110
+ ds.attrs[k] = v
111
+
112
+ def to_pickle(
113
+ self,
114
+ path: str | Path,
115
+ *,
116
+ dtype: type | None = None,
117
+ compression: CompressionOptions = "infer",
118
+ protocol: int = HIGHEST_PROTOCOL,
119
+ ):
120
+ """Save the sequence to a pickle file using pandas' I/O handler.
121
+
122
+ Parameters
123
+ ----------
124
+ path : str | Path
125
+ Output file path.
126
+ dtype : type, optional
127
+ Data type for the array.
128
+ compression : str or dict, default 'infer'
129
+ For on-the-fly compression of the output data.
130
+ If 'infer' and 'path' is path-like,
131
+ then detect compression from the following extensions:
132
+ '.gz', '.bz2', '.zip', '.xz', '.zst', '.tar', '.tar.gz', '.tar.xz' or '.tar.bz2'
133
+ (otherwise no compression).
134
+ Set to None for no compression.
135
+ Can also be a dict with key 'method'
136
+ set to one of {'zip', 'gzip', 'bz2', 'zstd', 'xz', 'tar'}
137
+ and other key-value pairs are forwarded to zipfile.ZipFile,
138
+ gzip.GzipFile, bz2.BZ2File, zstandard.ZstdCompressor,
139
+ lzma.LZMAFile or tarfile.TarFile, respectively.
140
+ As an example, the following could be passed for faster compression
141
+ and to create a reproducible gzip archive:
142
+ compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}
143
+ protocol : int, default=HIGHEST_PROTOCOL
144
+ Pickle protocol version.
145
+ """
146
+ data = self.to_numpy(dtype=dtype)
147
+ pd_to_pickle(data, path, compression=compression, protocol=protocol)
@@ -0,0 +1,3 @@
1
+ from lazy_loader import attach_stub
2
+
3
+ __getattr__, __dir__, __all__ = attach_stub(__name__, __file__)
@@ -0,0 +1,13 @@
1
+ # ruff: noqa TID252
2
+ from .camera import BaslerCamera, ThermalCamera
3
+ from .stimulator import TCS, Arduino, JulaboChiller, TCSCommand, TCSStimulus
4
+
5
+ __all__ = [
6
+ "TCS",
7
+ "Arduino",
8
+ "BaslerCamera",
9
+ "JulaboChiller",
10
+ "TCSCommand",
11
+ "TCSStimulus",
12
+ "ThermalCamera",
13
+ ]
@@ -0,0 +1,3 @@
1
+ from lazy_loader import attach_stub
2
+
3
+ __getattr__, __dir__, __all__ = attach_stub(__name__, __file__)
@@ -0,0 +1,5 @@
1
+ # ruff: noqa TID252
2
+ from .basler import BaslerCamera
3
+ from .thermal_camera import ThermalCamera
4
+
5
+ __all__ = ["BaslerCamera", "ThermalCamera"]