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 +3 -0
- poulet_py/__init__.pyi +46 -0
- poulet_py/config/__init__.py +3 -0
- poulet_py/config/__init__.pyi +5 -0
- poulet_py/config/logging.py +94 -0
- poulet_py/config/settings.py +90 -0
- poulet_py/converters/__init__.py +3 -0
- poulet_py/converters/__init__.pyi +4 -0
- poulet_py/converters/seq.py +147 -0
- poulet_py/hardware/__init__.py +3 -0
- poulet_py/hardware/__init__.pyi +13 -0
- poulet_py/hardware/camera/__init__.py +3 -0
- poulet_py/hardware/camera/__init__.pyi +5 -0
- poulet_py/hardware/camera/basler.py +315 -0
- poulet_py/hardware/camera/thermal_camera.py +632 -0
- poulet_py/hardware/camera/uvctypes.py +502 -0
- poulet_py/hardware/stimulator/__init__.py +3 -0
- poulet_py/hardware/stimulator/__init__.pyi +6 -0
- poulet_py/hardware/stimulator/arduino.py +130 -0
- poulet_py/hardware/stimulator/julabo.py +199 -0
- poulet_py/hardware/stimulator/qst.py +592 -0
- poulet_py/tools/__init__.py +3 -0
- poulet_py/tools/__init__.pyi +14 -0
- poulet_py/tools/generators.py +89 -0
- poulet_py/tools/organizational.py +137 -0
- poulet_py/tools/serializers.py +88 -0
- poulet_py/utils/__init__.py +3 -0
- poulet_py/utils/__init__.pyi +5 -0
- poulet_py/utils/oscilloscope.py +306 -0
- poulet_py/utils/qst.py +283 -0
- poulet_py-0.1.2.dist-info/METADATA +187 -0
- poulet_py-0.1.2.dist-info/RECORD +34 -0
- poulet_py-0.1.2.dist-info/WHEEL +4 -0
- poulet_py-0.1.2.dist-info/licenses/LICENSE +674 -0
poulet_py/__init__.py
ADDED
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,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,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,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
|
+
]
|