lvpyioTools 2.4.5__tar.gz
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.
- lvpyiotools-2.4.5/LICENSE +19 -0
- lvpyiotools-2.4.5/PKG-INFO +78 -0
- lvpyiotools-2.4.5/README.md +60 -0
- lvpyiotools-2.4.5/pyproject.toml +27 -0
- lvpyiotools-2.4.5/setup.cfg +4 -0
- lvpyiotools-2.4.5/src/lvpyioTools/__init__.py +0 -0
- lvpyiotools-2.4.5/src/lvpyioTools/attribute.py +105 -0
- lvpyiotools-2.4.5/src/lvpyioTools/calibration.py +43 -0
- lvpyiotools-2.4.5/src/lvpyioTools/frame.py +132 -0
- lvpyiotools-2.4.5/src/lvpyioTools/set.py +397 -0
- lvpyiotools-2.4.5/src/lvpyioTools/setParser.py +104 -0
- lvpyiotools-2.4.5/src/lvpyioTools/setProperties.py +113 -0
- lvpyiotools-2.4.5/src/lvpyioTools/viewer.py +30 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/PKG-INFO +78 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/SOURCES.txt +17 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/dependency_links.txt +1 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/entry_points.txt +2 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/requires.txt +4 -0
- lvpyiotools-2.4.5/src/lvpyioTools.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Simon Cadet
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lvpyioTools
|
|
3
|
+
Version: 2.4.5
|
|
4
|
+
Summary: A set of tools to handle LabView files and data via the lvpyio library.
|
|
5
|
+
Author-email: SimKDT <simon.cadet79+github@proton.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SimKDT/lvpyio-tools
|
|
8
|
+
Project-URL: Issues, https://github.com/SimKDT/lvpyio-tools/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Requires-Python: >=3.12.3
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Requires-Dist: Pillow
|
|
16
|
+
Requires-Dist: lvpyio>=1.4.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# lvpyio-tools
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
A collection of tools to manipulate lvpyio objects such as Sets.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
<details open>
|
|
31
|
+
<summary>PyPi</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install lvpyio-tools
|
|
35
|
+
```
|
|
36
|
+
</details>
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary>Git</summary>
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install git+https://github.com/SimKDT/lvpyio-tools.git
|
|
43
|
+
```
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
<details>
|
|
47
|
+
<summary>Local</summary>
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install .
|
|
51
|
+
```
|
|
52
|
+
</details>
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
from lvpyioTools.set import LVSet
|
|
59
|
+
|
|
60
|
+
set_file = Path("path/to/your/set.set")
|
|
61
|
+
with LVSet(set_file) as lv_set:
|
|
62
|
+
print(lv_set)
|
|
63
|
+
print(f"Number of frames: {len(lv_set)}")
|
|
64
|
+
print(f"Properties: {lv_set.properties}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Doc (build)
|
|
68
|
+
```bash
|
|
69
|
+
make html
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Serving the documentation locally:
|
|
73
|
+
```bash
|
|
74
|
+
make serve
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# lvpyio-tools
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
A collection of tools to manipulate lvpyio objects such as Sets.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
<details open>
|
|
13
|
+
<summary>PyPi</summary>
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install lvpyio-tools
|
|
17
|
+
```
|
|
18
|
+
</details>
|
|
19
|
+
|
|
20
|
+
<details>
|
|
21
|
+
<summary>Git</summary>
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install git+https://github.com/SimKDT/lvpyio-tools.git
|
|
25
|
+
```
|
|
26
|
+
</details>
|
|
27
|
+
|
|
28
|
+
<details>
|
|
29
|
+
<summary>Local</summary>
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install .
|
|
33
|
+
```
|
|
34
|
+
</details>
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from lvpyioTools.set import LVSet
|
|
41
|
+
|
|
42
|
+
set_file = Path("path/to/your/set.set")
|
|
43
|
+
with LVSet(set_file) as lv_set:
|
|
44
|
+
print(lv_set)
|
|
45
|
+
print(f"Number of frames: {len(lv_set)}")
|
|
46
|
+
print(f"Properties: {lv_set.properties}")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Doc (build)
|
|
50
|
+
```bash
|
|
51
|
+
make html
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Serving the documentation locally:
|
|
55
|
+
```bash
|
|
56
|
+
make serve
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lvpyioTools"
|
|
3
|
+
description = "A set of tools to handle LabView files and data via the lvpyio library."
|
|
4
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
5
|
+
requires-python = ">=3.12.3"
|
|
6
|
+
version = "2.4.5"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "SimKDT", email = "simon.cadet79+github@proton.me" },
|
|
11
|
+
]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy",
|
|
14
|
+
"matplotlib",
|
|
15
|
+
"Pillow",
|
|
16
|
+
"lvpyio>=1.4.0",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/SimKDT/lvpyio-tools"
|
|
24
|
+
Issues = "https://github.com/SimKDT/lvpyio-tools/issues"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
lvpyioShow = "lvpyioTools.viewer:main"
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
class FrameAttribute(StrEnum):
|
|
4
|
+
"""
|
|
5
|
+
Enumeration of the different attributes of an ImageFrame object.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
ACQ_ATTRIBUTES_TRANSFORMED = "Acq.AttributesTransformed"
|
|
9
|
+
ACQ_CAMERA_ID = "Acq.Camera.ID"
|
|
10
|
+
"""The ID of the camera used for the acquisition."""
|
|
11
|
+
ACQ_CAMERA_INDEX = "Acq.Camera.Index"
|
|
12
|
+
"""Index of the camera used for the acquisition. This is an array."""
|
|
13
|
+
ACQ_CAMERA_LABEL = "Acq.Camera.Label"
|
|
14
|
+
"""Label of the camera (i.e. Camera 1, Camera 2)."""
|
|
15
|
+
ACQ_TIME = "Acq.Time"
|
|
16
|
+
"""Time of the acquisition of this frame. This is an array."""
|
|
17
|
+
ACQ_TIME_TO_TRIGGER = "Acq.TimeToTrigger"
|
|
18
|
+
"""Time to trigger of the acquisition of this frame. This is an array."""
|
|
19
|
+
ACQ_TIME_SERIES = "AcqTimeSeries"
|
|
20
|
+
CCD_EXPOSURE_TIME = "CCDExposureTime"
|
|
21
|
+
"""Exposure time of the camera. This is a string value with the unit."""
|
|
22
|
+
CAM_PIXEL_SIZE = "CamPixelSize"
|
|
23
|
+
"""Pixel size of the camera. This is a string value with the unit."""
|
|
24
|
+
CAMERA_MAX_INTENSITY = "CameraMaxIntensity"
|
|
25
|
+
"""Maximum intensity of the pixel values. This is a string value."""
|
|
26
|
+
CAMERA_MAX_NX = "CameraMaxNx"
|
|
27
|
+
"""Maximum number of pixels in the x-direction. This is a string value."""
|
|
28
|
+
CAMERA_MAX_NY = "CameraMaxNy"
|
|
29
|
+
"""Maximum number of pixels in the y-direction. This is a string value."""
|
|
30
|
+
CAMERA_NAME = "CameraName"
|
|
31
|
+
"""Name of the camera."""
|
|
32
|
+
FRAME_PROCESSING = "FrameProcessing"
|
|
33
|
+
RGB_FRAME = "RGBFrame"
|
|
34
|
+
"""Whenever this is a RGB frame or not. This is a string value of 0 or 1."""
|
|
35
|
+
|
|
36
|
+
class BufferAttribute(StrEnum):
|
|
37
|
+
"""
|
|
38
|
+
Enumeration of the different attributes of a Buffer object.
|
|
39
|
+
"""
|
|
40
|
+
ACQ_INPUT_SPEED_SELECT = "Acq.Input.SpeedSelect"
|
|
41
|
+
ACQ_INPUT_START_TRIGGER = "Acq.Input.StartTrigger"
|
|
42
|
+
ACQ_STATUS_RECORD_POST = "Acq.Status.RecordPost"
|
|
43
|
+
ACQ_TIME_SERIES = "AcqTimeSeries"
|
|
44
|
+
CUSTOM_IMAGE_TAG_COUNT = "CustomImageTag_Count"
|
|
45
|
+
DEV_DATA_ALIAS0 = "DevDataAlias0"
|
|
46
|
+
DEV_DATA_ALIAS1 = "DevDataAlias1"
|
|
47
|
+
DEV_DATA_ALIAS2 = "DevDataAlias2"
|
|
48
|
+
DEV_DATA_ALIAS3 = "DevDataAlias3"
|
|
49
|
+
DEV_DATA_ALIAS4 = "DevDataAlias4"
|
|
50
|
+
DEV_DATA_CHANNEL0 = "DevDataChannel0"
|
|
51
|
+
DEV_DATA_CHANNEL1 = "DevDataChannel1"
|
|
52
|
+
DEV_DATA_CHANNEL2 = "DevDataChannel2"
|
|
53
|
+
DEV_DATA_CHANNEL3 = "DevDataChannel3"
|
|
54
|
+
DEV_DATA_CHANNEL4 = "DevDataChannel4"
|
|
55
|
+
DEV_DATA_CLASS0 = "DevDataClass0"
|
|
56
|
+
DEV_DATA_CLASS1 = "DevDataClass1"
|
|
57
|
+
DEV_DATA_CLASS2 = "DevDataClass2"
|
|
58
|
+
DEV_DATA_CLASS3 = "DevDataClass3"
|
|
59
|
+
DEV_DATA_CLASS4 = "DevDataClass4"
|
|
60
|
+
DEV_DATA_NAME0 = "DevDataName0"
|
|
61
|
+
DEV_DATA_NAME1 = "DevDataName1"
|
|
62
|
+
DEV_DATA_NAME2 = "DevDataName2"
|
|
63
|
+
DEV_DATA_NAME3 = "DevDataName3"
|
|
64
|
+
DEV_DATA_NAME4 = "DevDataName4"
|
|
65
|
+
DEV_DATA_SCALE0 = "DevDataScale0"
|
|
66
|
+
DEV_DATA_SCALE1 = "DevDataScale1"
|
|
67
|
+
DEV_DATA_SCALE2 = "DevDataScale2"
|
|
68
|
+
DEV_DATA_SCALE3 = "DevDataScale3"
|
|
69
|
+
DEV_DATA_SCALE4 = "DevDataScale4"
|
|
70
|
+
DEV_DATA_SCALE_I0 = "DevDataScaleI0"
|
|
71
|
+
DEV_DATA_SCALE_I1 = "DevDataScaleI1"
|
|
72
|
+
DEV_DATA_SCALE_I2 = "DevDataScaleI2"
|
|
73
|
+
DEV_DATA_SCALE_I3 = "DevDataScaleI3"
|
|
74
|
+
DEV_DATA_SCALE_I4 = "DevDataScaleI4"
|
|
75
|
+
DEV_DATA_X0 = "DevDataX0"
|
|
76
|
+
DEV_DATA_X1 = "DevDataX1"
|
|
77
|
+
DEV_DATA_X2 = "DevDataX2"
|
|
78
|
+
DEV_DATA_X3 = "DevDataX3"
|
|
79
|
+
DEV_DATA_X4 = "DevDataX4"
|
|
80
|
+
DEV_DATA_SOURCES = "DevDataSources"
|
|
81
|
+
DEV_DATA_TRACE0 = "DevDataTrace0"
|
|
82
|
+
DEV_DATA_TRACE1 = "DevDataTrace1"
|
|
83
|
+
DEV_DATA_TRACE2 = "DevDataTrace2"
|
|
84
|
+
DEV_DATA_TRACE3 = "DevDataTrace3"
|
|
85
|
+
DEV_DATA_TRACE4 = "DevDataTrace4"
|
|
86
|
+
EX_PATH_VALUE_F = "ExPathValue_f"
|
|
87
|
+
LOAD_FILE = "LoadFile"
|
|
88
|
+
"""Source file of the buffer attributes and data. For `.im7` files this is the original `.im7` file for example."""
|
|
89
|
+
LOAD_SET = "LoadSet"
|
|
90
|
+
"""Original set file from which this buffer was loaded. The extension (.set) is omitted."""
|
|
91
|
+
LOAD_SET_INDEX = "LoadSetIndex"
|
|
92
|
+
LOAD_SET_TYPE = "LoadSetType"
|
|
93
|
+
TIMESTAMP = "Timestamp"
|
|
94
|
+
"""Timestamp in ISO format.
|
|
95
|
+
|
|
96
|
+
See: https://en.wikipedia.org/wiki/ISO_8601
|
|
97
|
+
"""
|
|
98
|
+
DAVIS_VERSION = "_DaVisVersion"
|
|
99
|
+
"""DaVis version used to create the buffer."""
|
|
100
|
+
DATE = "_Date"
|
|
101
|
+
"""Can be empty."""
|
|
102
|
+
HEADER_PACK_TYPE = "_Header_PackType"
|
|
103
|
+
TIME = "_Time"
|
|
104
|
+
"""Can be empty. See Timestamp instead."""
|
|
105
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Literal
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
|
|
5
|
+
from lvpyio.types.scale import Scale, Scales
|
|
6
|
+
|
|
7
|
+
def _get_for_axis(scales, id: Literal['LinearScaleX', 'LinearScaleY', 'LinearScaleZ', 'LinearScaleI']):
|
|
8
|
+
linearScale = scales.find(id)
|
|
9
|
+
if linearScale is None:
|
|
10
|
+
raise ValueError(f"Could not find {id} in the calibration file.")
|
|
11
|
+
|
|
12
|
+
factor = linearScale.get("FactorMmPerPixel")
|
|
13
|
+
offset = linearScale.get("OffsetMm")
|
|
14
|
+
unit = linearScale.get("Unit")
|
|
15
|
+
description = linearScale.get("Description")
|
|
16
|
+
if factor is None or offset is None or unit is None:
|
|
17
|
+
raise ValueError(f"Could not find FactorMmPerPixel, OffsetMm, or Unit in the calibration file for {id}.")
|
|
18
|
+
|
|
19
|
+
return Scale(slope=float(factor), offset=float(offset), unit=unit, description=description)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_calibration(calibration_file: Path) -> Scales:
|
|
23
|
+
"""
|
|
24
|
+
Get the calibration settings from the given calibration file which should be a XML file. Values are rounded to fit the calibration application of DaVis.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
calibration_file (Path): Calibration file in XML format.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Scales: A `Scales` object containing the calibration settings for x, y, z, and i axes.
|
|
31
|
+
"""
|
|
32
|
+
# Read and parse the XML file
|
|
33
|
+
tree = ET.parse(calibration_file)
|
|
34
|
+
root = tree.getroot()
|
|
35
|
+
# Search for "Scales" in the XML file
|
|
36
|
+
scales = root.find(".//Scales")
|
|
37
|
+
|
|
38
|
+
x = _get_for_axis(scales, id='LinearScaleX')
|
|
39
|
+
y = _get_for_axis(scales, id='LinearScaleY')
|
|
40
|
+
z = _get_for_axis(scales, id='LinearScaleZ')
|
|
41
|
+
i = _get_for_axis(scales, id='LinearScaleI')
|
|
42
|
+
|
|
43
|
+
return Scales(x, y, z, i)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper class for working lvpyio frames retrieved from a set.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
|
|
8
|
+
from lvpyio.types.frame import ImageFrame
|
|
9
|
+
from lvpyio.types.scale import Scales
|
|
10
|
+
|
|
11
|
+
from .attribute import FrameAttribute
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .set import LVSet
|
|
14
|
+
|
|
15
|
+
class LVFrame():
|
|
16
|
+
"""
|
|
17
|
+
Wrapper class for working with lvpyio frames retrieved from a set. This class provides a convenient interface to access frame attributes, image data, and scale information.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, frame: ImageFrame, set: 'LVSet'):
|
|
20
|
+
self.frame = frame
|
|
21
|
+
self.set = set
|
|
22
|
+
|
|
23
|
+
def replace_frame(self, new_frame: ImageFrame):
|
|
24
|
+
"""
|
|
25
|
+
Update the current frame with a new frame. Notably used for performance reasons to not create a new LVFrame object for each frame in a set.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
new_frame (ImageFrame): The new frame to update with.
|
|
29
|
+
"""
|
|
30
|
+
self.frame = new_frame
|
|
31
|
+
|
|
32
|
+
def __len__(self):
|
|
33
|
+
return len(self.frame.images)
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f"<LVFrame: {len(self.frame.images)} images, shape={self.frame.shape}>"
|
|
37
|
+
|
|
38
|
+
def shape(self):
|
|
39
|
+
"""
|
|
40
|
+
Get the images and masks shapes.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
tuple: A tuple containing the shapes of the images and masks.
|
|
44
|
+
"""
|
|
45
|
+
return self.frame.shape
|
|
46
|
+
|
|
47
|
+
def get(self, image_number: int = 0):
|
|
48
|
+
"""
|
|
49
|
+
Read the image data from a specific frame and image number.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
image_number (int, optional): The index of the image to retrieve. Defaults to 0.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
IndexError: If the image_number is out of range.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
numpy.ndarray: The image data as a NumPy array.
|
|
59
|
+
"""
|
|
60
|
+
if image_number < 0 or image_number >= len(self.frame.images):
|
|
61
|
+
raise IndexError(f"Image number {image_number} is out of range. Available images: 0 to {len(self.frame.images)-1}.")
|
|
62
|
+
return self.frame.images[image_number]
|
|
63
|
+
|
|
64
|
+
def show(self, image_number: int = 0,
|
|
65
|
+
vmin: float | None = None, vmax: float | None = None,
|
|
66
|
+
cmap: str = 'gray',
|
|
67
|
+
_show: bool = True):
|
|
68
|
+
"""
|
|
69
|
+
Display the image data from a specific frame and image number using the default image viewer.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
image_number (int, optional): The index of the image to display. Defaults to 0.
|
|
73
|
+
"""
|
|
74
|
+
import matplotlib.pyplot as plt
|
|
75
|
+
image_data = self.get(image_number)
|
|
76
|
+
fig, ax = plt.subplots()
|
|
77
|
+
ax.imshow(image_data, cmap=cmap, vmin=vmin, vmax=vmax)
|
|
78
|
+
ax.axis('off')
|
|
79
|
+
plt.tight_layout()
|
|
80
|
+
if _show:
|
|
81
|
+
plt.show()
|
|
82
|
+
return fig, ax
|
|
83
|
+
|
|
84
|
+
def scale(self) -> Scales:
|
|
85
|
+
"""
|
|
86
|
+
Read the scale information from the frame, that is a class containing the following attributes:
|
|
87
|
+
- `x`: The scale in the x-direction (in meters per pixel).
|
|
88
|
+
- `y`: The scale in the y-direction (in meters per pixel).
|
|
89
|
+
- `z`: The scale in the z-direction (in meters per pixel).
|
|
90
|
+
- `i`: The scale in the intensity direction (generally with a slope of 1).
|
|
91
|
+
|
|
92
|
+
Each attributes are `Scale` objects, which contain the following attributes:
|
|
93
|
+
- `slope`: The slope of the scale
|
|
94
|
+
- `offset`: The offset of the scale
|
|
95
|
+
- `unit`: The unit of the scale (mm, counts, etc.)
|
|
96
|
+
- `description`: Mostly just empty
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Scales: A `Scales` object containing the scale information for the frame.
|
|
100
|
+
"""
|
|
101
|
+
return self.frame.scales
|
|
102
|
+
|
|
103
|
+
def get_attribute(self, attribute: FrameAttribute):
|
|
104
|
+
"""
|
|
105
|
+
Get a specific attribute from the frame.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
attribute (FrameAttribute): The attribute to retrieve.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Any: The value of the requested attribute, or `None` if the attribute is not found in the frame.
|
|
112
|
+
"""
|
|
113
|
+
return self.frame.attributes.get(attribute.value, None)
|
|
114
|
+
|
|
115
|
+
def get_max_intensity(self):
|
|
116
|
+
"""
|
|
117
|
+
Get the maximum intensity of the pixel values from the frame.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If the CAMERA_MAX_INTENSITY attribute is not found or is invalid.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
float: The maximum intensity value of the pixel values from the frame.
|
|
124
|
+
"""
|
|
125
|
+
max_intensity = self.get_attribute(FrameAttribute.CAMERA_MAX_INTENSITY)
|
|
126
|
+
if max_intensity is not None:
|
|
127
|
+
try:
|
|
128
|
+
return float(max_intensity)
|
|
129
|
+
except ValueError:
|
|
130
|
+
raise ValueError(f"Invalid CAMERA_MAX_INTENSITY value: {max_intensity}")
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError("CAMERA_MAX_INTENSITY attribute not found in frame.")
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper class for working with lvpyio sets.
|
|
3
|
+
"""
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
import lvpyio as lv
|
|
11
|
+
# from lvpyio.types
|
|
12
|
+
from lvpyio.types.frame import ImageFrame
|
|
13
|
+
from lvpyio.types.buffer import Buffer
|
|
14
|
+
from lvpyio.types.scale import Scales
|
|
15
|
+
from lvpyio.io.set import Set
|
|
16
|
+
|
|
17
|
+
from . import setParser, calibration
|
|
18
|
+
from .frame import LVFrame
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LVSet(): # numpydoc ignore=SA01
|
|
24
|
+
"""
|
|
25
|
+
Helper class for working with DaVis sets.
|
|
26
|
+
|
|
27
|
+
Set files are simple text files that contain some generic information that were saved on creation. They don't give any information about the actual data, where it is stored, or how to read it but the folder placed in the same directory as the set file contains all the data.
|
|
28
|
+
|
|
29
|
+
This provides a simple interface to easily manipulate and read the set files.
|
|
30
|
+
|
|
31
|
+
A set of images is handled this way in DaVis:
|
|
32
|
+
1. buffer (set[buffer_frame])
|
|
33
|
+
2. frame (set[buffer_frame][frame_number])
|
|
34
|
+
3. image (set[buffer_frame][frame_number].images[image_number])
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
file (Path): The path to the .set file.
|
|
39
|
+
|
|
40
|
+
Examples
|
|
41
|
+
--------
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from lvpyioTools.sets import LVSet
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
set_file = Path("example/example.set")
|
|
48
|
+
with LVSet(set_file) as set:
|
|
49
|
+
set.show()
|
|
50
|
+
print(f"Number of frames in the set: {len(set)}")
|
|
51
|
+
```
|
|
52
|
+
"""
|
|
53
|
+
set: Set | None = None
|
|
54
|
+
frames: tuple[LVFrame, ...] | None = None
|
|
55
|
+
|
|
56
|
+
def __init__(self, file: Path):
|
|
57
|
+
# verify provided file
|
|
58
|
+
if not file.exists():
|
|
59
|
+
raise FileNotFoundError(f"File {file} does not exist.")
|
|
60
|
+
if not file.is_file():
|
|
61
|
+
raise ValueError(f"Provided path {file} is not a file.")
|
|
62
|
+
if not file.suffix in [".set", ".exp"]:
|
|
63
|
+
raise ValueError(f"Provided file {file} is not a .set or .exp file.")
|
|
64
|
+
|
|
65
|
+
self.file = file
|
|
66
|
+
self.properties = self.get_properties()
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
if self.is_experiment():
|
|
70
|
+
return f"<LVSet: {self.file.name}, experiment set, properties={len(self.properties)}>"
|
|
71
|
+
if self.is_open():
|
|
72
|
+
return f"<LVSet: {self.file.name}, {len(self)} frames, properties={len(self.properties)}>"
|
|
73
|
+
return f"<LVSet: {self.file.name}, closed, properties={len(self.properties)}>"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## LOADER / SAVER
|
|
77
|
+
|
|
78
|
+
def __enter__(self):
|
|
79
|
+
"""
|
|
80
|
+
Context manager entry point.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
LVSet: The current instance of LVSet.
|
|
84
|
+
"""
|
|
85
|
+
self.open()
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def __exit__(self, *args):
|
|
89
|
+
"""
|
|
90
|
+
Safeguard to make sure the set gets closed.
|
|
91
|
+
"""
|
|
92
|
+
self.close()
|
|
93
|
+
|
|
94
|
+
def __del__(self):
|
|
95
|
+
"""
|
|
96
|
+
Safeguard to make sure the set gets closed.
|
|
97
|
+
"""
|
|
98
|
+
self.close()
|
|
99
|
+
|
|
100
|
+
def is_open(self) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Check if the set is currently open.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if the set is open, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
return self.set is not None
|
|
108
|
+
|
|
109
|
+
def open(self):
|
|
110
|
+
"""
|
|
111
|
+
Load the set with lvpyio.
|
|
112
|
+
"""
|
|
113
|
+
# safeguard to ensure we properly close the set
|
|
114
|
+
self.close()
|
|
115
|
+
if self.is_experiment():
|
|
116
|
+
raise ValueError(f"Cannot open an experiment set (`.exp`) directly.")
|
|
117
|
+
self.set = lv.read_set(self.file)
|
|
118
|
+
|
|
119
|
+
def close(self):
|
|
120
|
+
"""
|
|
121
|
+
Close the currently opened set. If no set is open, this method does nothing.
|
|
122
|
+
"""
|
|
123
|
+
if self.set is None:
|
|
124
|
+
return
|
|
125
|
+
self.set.close()
|
|
126
|
+
self.set = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## PARENTS / CHILDREN
|
|
130
|
+
|
|
131
|
+
def is_experiment(self) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Check if the set is an experiment set (`.exp`).
|
|
134
|
+
"""
|
|
135
|
+
return self.file.suffix == ".exp"
|
|
136
|
+
|
|
137
|
+
def get_parent(self) -> 'LVSet | None':
|
|
138
|
+
"""
|
|
139
|
+
Retrieve parent set holding this current set if exists. If the current set is an experiment set, it has no parent and this method will return None.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
LVSet | None: The parent set if it exists, otherwise None.
|
|
143
|
+
"""
|
|
144
|
+
isParent = self.is_experiment()
|
|
145
|
+
if isParent:
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
# get parent theorical path
|
|
149
|
+
set_dir = self.file.parent
|
|
150
|
+
parent_dir = set_dir.parent
|
|
151
|
+
|
|
152
|
+
# find .set or .exp file if exists
|
|
153
|
+
for suffix in [".set", ".exp"]:
|
|
154
|
+
# try to access the set file
|
|
155
|
+
parent_set_file = parent_dir / (set_dir.name + suffix)
|
|
156
|
+
if parent_set_file.exists():
|
|
157
|
+
return LVSet(parent_set_file)
|
|
158
|
+
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def get_experiment(self, max_iteration: int = 100) -> 'LVSet | None':
|
|
162
|
+
"""
|
|
163
|
+
Retrieve the experiment set holding this current set if exists.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
max_iteration (int, optional): Maximum number of iterations to search for the experiment set. Defaults to 100.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
LVSet | None: The experiment set if it exists, otherwise None.
|
|
170
|
+
"""
|
|
171
|
+
current_set = self
|
|
172
|
+
iteration = 0
|
|
173
|
+
while current_set is not None:
|
|
174
|
+
if current_set.is_experiment():
|
|
175
|
+
return current_set
|
|
176
|
+
current_set = current_set.get_parent()
|
|
177
|
+
|
|
178
|
+
# stop after too many iterations to avoid infinite loops
|
|
179
|
+
iteration += 1
|
|
180
|
+
if iteration > max_iteration:
|
|
181
|
+
warnings.warn(f"Reached maximum iteration ({max_iteration}) while searching for experiment set. Stopping search.")
|
|
182
|
+
break
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def get_children(self) -> list['LVSet']:
|
|
186
|
+
"""
|
|
187
|
+
Retrieve all child sets of the current set.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
list[LVSet]: A list of child sets.
|
|
191
|
+
"""
|
|
192
|
+
children = []
|
|
193
|
+
set_dir = self.file.parent
|
|
194
|
+
for child_dir in set_dir.iterdir():
|
|
195
|
+
if child_dir.is_dir():
|
|
196
|
+
for suffix in [".set", ".exp"]:
|
|
197
|
+
child_set_file = child_dir / (child_dir.name + suffix)
|
|
198
|
+
if child_set_file.exists():
|
|
199
|
+
children.append(LVSet(child_set_file))
|
|
200
|
+
return children
|
|
201
|
+
|
|
202
|
+
def get_calibration(self) -> 'Scales | None':
|
|
203
|
+
"""
|
|
204
|
+
Retrieve the calibration settings from the experiment set if it exists.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Scales | None: The calibration settings if they exist, otherwise None.
|
|
208
|
+
"""
|
|
209
|
+
experiment = self.get_experiment()
|
|
210
|
+
if experiment is None:
|
|
211
|
+
warnings.warn(f"No experiment set found for {self.file}. Cannot retrieve calibration.")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
# get calibration file
|
|
215
|
+
calibration_file = experiment.file.with_suffix("") / "Properties" / "Calibration" / "Calibration.xml"
|
|
216
|
+
if not calibration_file.exists():
|
|
217
|
+
warnings.warn(f"Calibration file {calibration_file} does not exist. Cannot retrieve calibration.")
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
return calibration.get_calibration(calibration_file)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
## GENERIC INFORMATION ABOUT THE SET
|
|
224
|
+
|
|
225
|
+
def __len__(self):
|
|
226
|
+
"""
|
|
227
|
+
Return the number of frames in the set.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
int: The number of frames in the set.
|
|
231
|
+
|
|
232
|
+
Raises:
|
|
233
|
+
RuntimeError: Set is not open.
|
|
234
|
+
"""
|
|
235
|
+
if self.set is None:
|
|
236
|
+
raise RuntimeError("Set is not open. Please call 'open()' before accessing the length.")
|
|
237
|
+
return len(self.set)
|
|
238
|
+
|
|
239
|
+
def read(self) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Read the set file and display its content.
|
|
242
|
+
"""
|
|
243
|
+
with open(self.file, 'r') as f:
|
|
244
|
+
return f.read().strip()
|
|
245
|
+
|
|
246
|
+
def get_properties(self) -> dict[setParser.SetProperty, Any]:
|
|
247
|
+
"""
|
|
248
|
+
Read the set file and return its properties as a dictionary.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
dict[SetProperty, Any]: A dictionary containing the set properties and their values.
|
|
252
|
+
"""
|
|
253
|
+
return setParser.read(self.file)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
## READERS
|
|
257
|
+
|
|
258
|
+
def get_buffer(self, buffer_frame: int) -> Buffer:
|
|
259
|
+
"""
|
|
260
|
+
Get a specific buffer from the set.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
buffer_frame (int): The index of the buffer frame to retrieve.
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
RuntimeError: Set is not open.
|
|
267
|
+
IndexError: Buffer frame index is out of range.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Buffer: The requested buffer object.
|
|
271
|
+
"""
|
|
272
|
+
# verify set is open
|
|
273
|
+
if self.set is None:
|
|
274
|
+
raise RuntimeError("Set is not open. Please call `open()` before accessing buffers.")
|
|
275
|
+
|
|
276
|
+
size = len(self)
|
|
277
|
+
if buffer_frame < 0 or buffer_frame >= size:
|
|
278
|
+
raise IndexError(f"Buffer frame index {buffer_frame} is out of range. Valid range is 0 to {size - 1}.")
|
|
279
|
+
return self.set[buffer_frame]
|
|
280
|
+
|
|
281
|
+
def get_frames(self, buffer_frame: int) -> tuple[LVFrame, ...]:
|
|
282
|
+
buffer = self.get_buffer(buffer_frame)
|
|
283
|
+
|
|
284
|
+
# init LVFrame instances for each frame in the buffer
|
|
285
|
+
if self.frames is None:
|
|
286
|
+
frames_count = len(buffer)
|
|
287
|
+
self.frames = tuple(LVFrame(buffer[i], self) for i in range(frames_count))
|
|
288
|
+
|
|
289
|
+
# if already exists, then simply replace the frames in the existing LVFrame instances
|
|
290
|
+
else:
|
|
291
|
+
for i in range(len(buffer)):
|
|
292
|
+
self.frames[i].replace_frame(buffer[i])
|
|
293
|
+
|
|
294
|
+
return self.frames
|
|
295
|
+
|
|
296
|
+
def get_frame(self, buffer_frame: int, frame_number: int = 0) -> LVFrame:
|
|
297
|
+
"""
|
|
298
|
+
Get a specific frame from a buffer in the set.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
buffer_frame (int): The index of the buffer frame to retrieve the frame from.
|
|
302
|
+
frame_number (int, optional): The index of the frame within the buffer. Defaults to 0.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
LVFrame: The requested frame object.
|
|
306
|
+
"""
|
|
307
|
+
frames = self.get_frames(buffer_frame)
|
|
308
|
+
if frame_number < 0 or frame_number >= len(frames):
|
|
309
|
+
raise IndexError(f"Frame number {frame_number} is out of range. Valid range is 0 to {len(frames) - 1}.")
|
|
310
|
+
return frames[frame_number]
|
|
311
|
+
|
|
312
|
+
def get_image(self, buffer_frame: int,
|
|
313
|
+
frame_number: int = 0,
|
|
314
|
+
image_number: int = 0):
|
|
315
|
+
"""
|
|
316
|
+
Get a specific image from a frame in a buffer in the set.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
buffer_frame (int): The index of the buffer frame to retrieve the image from.
|
|
320
|
+
frame_number (int, optional): The index of the frame within the buffer. Defaults to 0.
|
|
321
|
+
image_number (int, optional): The index of the image within the frame. Defaults to 0.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Image: The requested image object.
|
|
325
|
+
"""
|
|
326
|
+
frame = self.get_frame(buffer_frame, frame_number)
|
|
327
|
+
return frame.get(image_number)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
## EXPORTS
|
|
331
|
+
|
|
332
|
+
def export(self, output_dir: Path, extension: str = ".tif"):
|
|
333
|
+
if not self.is_open():
|
|
334
|
+
raise RuntimeError("Set is not open. Please call `open()` before exporting.")
|
|
335
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
for buffer_frame in range(len(self)):
|
|
337
|
+
image = self.get_image(buffer_frame)
|
|
338
|
+
output_file = output_dir / f"buffer_{buffer_frame:05d}{extension}"
|
|
339
|
+
img = Image.fromarray(image)
|
|
340
|
+
img.save(output_file)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
## TEST SCRIPTS
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
from pprint import pprint
|
|
347
|
+
# Example usage
|
|
348
|
+
set_file = Path("example/example.set")
|
|
349
|
+
print(set_file)
|
|
350
|
+
with LVSet(set_file) as set:
|
|
351
|
+
pprint(set.get_properties())
|
|
352
|
+
print(f"Number of frames in the set: {len(set)}")
|
|
353
|
+
|
|
354
|
+
print()
|
|
355
|
+
|
|
356
|
+
outside_set = Path("/media/scadet03/CADET_MAIN/Manips/2025-10/data.2025-10.piv/DaVis/Upstream/jonc_2/f=0.7, S0=0.05, d=0.06, N=5.0/1/1.set")
|
|
357
|
+
print(outside_set)
|
|
358
|
+
with LVSet(outside_set) as set:
|
|
359
|
+
pprint(set.get_properties())
|
|
360
|
+
print(f"Number of frames in the set: {len(set)}")
|
|
361
|
+
|
|
362
|
+
frame = set.get_frame(0)
|
|
363
|
+
print(frame)
|
|
364
|
+
|
|
365
|
+
print()
|
|
366
|
+
|
|
367
|
+
outside_set = Path("/media/scadet03/CADET_MAIN/Manips/2025-10/data.2025-10.piv/temporary_calibration_ref_data/jonc_1/f=0.8, S0=0.05, d=0.06, N=1.0/Scale.set")
|
|
368
|
+
print(outside_set)
|
|
369
|
+
with LVSet(outside_set) as set:
|
|
370
|
+
pprint(set.get_properties())
|
|
371
|
+
print(f"Number of frames in the set: {len(set)}")
|
|
372
|
+
|
|
373
|
+
buffer = set.get_buffer(0)
|
|
374
|
+
frame = set.get_frame(0)
|
|
375
|
+
print(frame)
|
|
376
|
+
|
|
377
|
+
print()
|
|
378
|
+
|
|
379
|
+
outside_set = Path("/media/scadet03/CADET_MAIN/Manips/2025-10/data.2025-10.piv/DaVis/Upstream/jonc_1/f=0.7, S0=0.05, d=0.06, N=1.0/1/1.set")
|
|
380
|
+
print(outside_set)
|
|
381
|
+
with LVSet(outside_set) as set:
|
|
382
|
+
print(set)
|
|
383
|
+
print(f"Number of frames in the set: {len(set)}")
|
|
384
|
+
|
|
385
|
+
buffer = set.get_buffer(0)
|
|
386
|
+
frame = set.get_frame(0)
|
|
387
|
+
print(frame)
|
|
388
|
+
|
|
389
|
+
parent = set.get_parent()
|
|
390
|
+
print(parent.file if parent is not None else "No parent set found.")
|
|
391
|
+
|
|
392
|
+
experiment = set.get_experiment()
|
|
393
|
+
print(experiment)
|
|
394
|
+
print(experiment.file if experiment is not None else "No experiment set found.")
|
|
395
|
+
|
|
396
|
+
calib = set.get_calibration()
|
|
397
|
+
print(calib)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from .setProperties import SetProperty, property_types
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_PROPERTY_PATTERN = r"(?P<property>\w+) = \"?(?P<value>[\S\s]*?)\"?;"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_property(key: str, value: str):
|
|
12
|
+
"""
|
|
13
|
+
Read a property from the set file and convert it to the appropriate type and property key.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
key (str): The property key from the set file.
|
|
17
|
+
value (str): The property value from the set file.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
tuple[SetProperty | None, Any]: A tuple containing the property key as a `SetProperty` enum member (or `None` if unknown) and the converted property value (or `None` if conversion failed).
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
prop = SetProperty(key)
|
|
24
|
+
except ValueError:
|
|
25
|
+
warnings.warn(f"Unknown property '{key}' found in the set file.")
|
|
26
|
+
return None, None
|
|
27
|
+
|
|
28
|
+
# convert to type
|
|
29
|
+
prop_type = property_types.get(prop, None)
|
|
30
|
+
if prop_type is None:
|
|
31
|
+
warnings.warn(f"Unknown property '{key}' found in the set file.")
|
|
32
|
+
else:
|
|
33
|
+
try:
|
|
34
|
+
value = prop_type(value) # convert to the appropriate type
|
|
35
|
+
except Exception as e:
|
|
36
|
+
warnings.warn(f"Error converting property '{key}' with value '{value}': {e}")
|
|
37
|
+
return None, None
|
|
38
|
+
|
|
39
|
+
return prop, value
|
|
40
|
+
|
|
41
|
+
def read(file: Path) -> dict[SetProperty, Any]:
|
|
42
|
+
"""
|
|
43
|
+
Read the contents of a file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
file (Path): The path to the file to read.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
dict[SetProperty, Any]: A dictionary containing the set properties and their values.
|
|
50
|
+
"""
|
|
51
|
+
with open(file, 'r') as f:
|
|
52
|
+
content = f.read().strip()
|
|
53
|
+
lines = content.splitlines()
|
|
54
|
+
|
|
55
|
+
# identify line with "#GROUP Sets"
|
|
56
|
+
group_line_index = None
|
|
57
|
+
for i, line in enumerate(lines):
|
|
58
|
+
if line.strip() == "#GROUP Sets":
|
|
59
|
+
group_line_index = i
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
if group_line_index is None:
|
|
63
|
+
raise ValueError("The file does not contain a '#GROUP Sets' line.")
|
|
64
|
+
|
|
65
|
+
# extract lines after "#GROUP Sets"
|
|
66
|
+
set_lines = lines[group_line_index + 1:]
|
|
67
|
+
|
|
68
|
+
import re
|
|
69
|
+
set_dict = {}
|
|
70
|
+
for line in set_lines:
|
|
71
|
+
line = line.strip()
|
|
72
|
+
if not line or line.startswith("#"):
|
|
73
|
+
continue # skip empty lines and comments
|
|
74
|
+
match = re.match(_PROPERTY_PATTERN, line)
|
|
75
|
+
if match:
|
|
76
|
+
key = match.group('property')
|
|
77
|
+
value = match.group('value')
|
|
78
|
+
|
|
79
|
+
prop, value = read_property(key, value)
|
|
80
|
+
if prop is None:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
set_dict[prop] = value
|
|
84
|
+
return set_dict
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
from pprint import pprint
|
|
90
|
+
examples = Path("example")
|
|
91
|
+
|
|
92
|
+
for set_file in examples.rglob("*.set"):
|
|
93
|
+
print(set_file)
|
|
94
|
+
set_dict = read(set_file)
|
|
95
|
+
pprint(set_dict)
|
|
96
|
+
|
|
97
|
+
if SetProperty.SetTime in set_dict:
|
|
98
|
+
print(f"SetTime: {set_dict[SetProperty.SetTime].isoformat()}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
outside_set = Path("/media/scadet03/CADET_MAIN/Manips/2025-10/data.2025-10.piv/temporary_calibration_ref_data/jonc_1/f=0.8, S0=0.05, d=0.06, N=1.0/Scale.set")
|
|
102
|
+
print(outside_set)
|
|
103
|
+
set_dict = read(outside_set)
|
|
104
|
+
pprint(set_dict)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from enum import Enum, StrEnum
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import locale, warnings
|
|
4
|
+
|
|
5
|
+
def _read_date(value: str) -> datetime | None:
|
|
6
|
+
"""
|
|
7
|
+
Read the .set file date format and convert into datetime object.
|
|
8
|
+
The date uses english format, e.g. "Fri Sep 26 11:25:26 2025",
|
|
9
|
+
so conversion to a specific locale is needed to ensure correct parsing regardless of the system locale.
|
|
10
|
+
|
|
11
|
+
See:
|
|
12
|
+
https://stackoverflow.com/questions/38303217/datetime-strptime-unexpected-behavior-locale-issue
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
value (str): The date string from the .set file.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
datetime | None: The corresponding datetime object, or `None` if parsing failed.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
# use 'C' locale to ensure parsing works regardless of system locale
|
|
22
|
+
old_locale = locale.setlocale(locale.LC_TIME)
|
|
23
|
+
try:
|
|
24
|
+
locale.setlocale(locale.LC_TIME, 'C')
|
|
25
|
+
return datetime.strptime(value, "%a %b %d %H:%M:%S %Y")
|
|
26
|
+
finally:
|
|
27
|
+
# restore original locale
|
|
28
|
+
try:
|
|
29
|
+
locale.setlocale(locale.LC_TIME, old_locale)
|
|
30
|
+
except locale.Error:
|
|
31
|
+
# restoration fails, just proceed
|
|
32
|
+
pass
|
|
33
|
+
except ValueError:
|
|
34
|
+
warnings.warn(f"Could not parse date from value '{value}'")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SetType(Enum):
|
|
40
|
+
"""
|
|
41
|
+
Classifications of the different values of the `SetType` property.
|
|
42
|
+
"""
|
|
43
|
+
IMAGE = 256
|
|
44
|
+
"""`.im7` images"""
|
|
45
|
+
VECTORS = 512
|
|
46
|
+
"""`.vc7` files"""
|
|
47
|
+
CINE = 4352
|
|
48
|
+
"""`.cine` file"""
|
|
49
|
+
PROPERTIES = 8192
|
|
50
|
+
"""`Properties` folder"""
|
|
51
|
+
FOLDER = 16384
|
|
52
|
+
"""Simple folder"""
|
|
53
|
+
CALIBRATION = 131072
|
|
54
|
+
"""Calibration set"""
|
|
55
|
+
|
|
56
|
+
def _read_set_type(value: str) -> SetType | None:
|
|
57
|
+
"""
|
|
58
|
+
Convert a string value to a `SetType` enum member.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
value (str): The string representation of the set type, which is expected to be an integer in string format.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
SetType: The corresponding `SetType` enum member.
|
|
65
|
+
"""
|
|
66
|
+
# convert to int
|
|
67
|
+
try:
|
|
68
|
+
value_int = int(value)
|
|
69
|
+
except ValueError:
|
|
70
|
+
warnings.warn(f"Could not convert value '{value}' to int for SetType")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
# try to convert to SetType
|
|
74
|
+
try:
|
|
75
|
+
return SetType(value_int)
|
|
76
|
+
except ValueError:
|
|
77
|
+
warnings.warn(f"Unknown SetType value '{value_int}'")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
class SetProperty(StrEnum):
|
|
81
|
+
"""
|
|
82
|
+
Definitions of the different properties that can be found in a set file.
|
|
83
|
+
"""
|
|
84
|
+
SetType = "SetType"
|
|
85
|
+
SetGroups = "SetGroups"
|
|
86
|
+
SetTime = "SetTime"
|
|
87
|
+
SetComments = "SetComments"
|
|
88
|
+
SetStart = "SetStart"
|
|
89
|
+
SetInc = "SetInc"
|
|
90
|
+
SetSourceSet = "SetSourceSet"
|
|
91
|
+
SetViewCallback = "SetViewCallback"
|
|
92
|
+
SetLoadCallback = "SetLoadCallback"
|
|
93
|
+
bpInfoString = "bpInfoString"
|
|
94
|
+
SetIdentifier = "SetIdentifier"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
property_types = {
|
|
98
|
+
SetProperty.SetType: _read_set_type,
|
|
99
|
+
SetProperty.SetGroups: str,
|
|
100
|
+
SetProperty.SetTime: _read_date,
|
|
101
|
+
SetProperty.SetComments: str,
|
|
102
|
+
SetProperty.SetStart: int,
|
|
103
|
+
SetProperty.SetInc: int,
|
|
104
|
+
SetProperty.SetSourceSet: str,
|
|
105
|
+
SetProperty.SetViewCallback: str,
|
|
106
|
+
SetProperty.SetLoadCallback: str,
|
|
107
|
+
SetProperty.bpInfoString: str,
|
|
108
|
+
SetProperty.SetIdentifier: str,
|
|
109
|
+
}
|
|
110
|
+
"""Associate each SetProperty to a function or type for convertion to a specific type."""
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
print(SetType(256))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Show a specific image from a DaVis set file.
|
|
3
|
+
"""
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .set import LVSet
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
parser = argparse.ArgumentParser(description="LVPyIO Tools Viewer")
|
|
12
|
+
parser.add_argument("set", type=str, help="Path to the DaVis set file")
|
|
13
|
+
parser.add_argument("--buffer", type=int, default=0, help="Buffer frame number to display (default: 0)")
|
|
14
|
+
parser.add_argument("--frame", type=int, default=0, help="Frame number to display (default: 0)")
|
|
15
|
+
parser.add_argument("--image", type=int, default=0, help="Image number to display (default: 0)")
|
|
16
|
+
parser.add_argument("--vmin", type=float, default=None, help="Minimum value for image display (default: None)")
|
|
17
|
+
parser.add_argument("--vmax", type=float, default=None, help="Maximum value for image display (default: None)")
|
|
18
|
+
parser.add_argument("--cmap", type=str, default='gray', help="Colormap for image display (default: 'gray')")
|
|
19
|
+
args = parser.parse_args()
|
|
20
|
+
|
|
21
|
+
with LVSet(Path(args.set)) as lv_set:
|
|
22
|
+
try:
|
|
23
|
+
frame = lv_set.get_frame(args.buffer, args.frame)
|
|
24
|
+
frame.show(image_number=args.image,
|
|
25
|
+
vmin=args.vmin, vmax=args.vmax,
|
|
26
|
+
cmap=args.cmap)
|
|
27
|
+
except IndexError as e:
|
|
28
|
+
print(f"Error: {e}")
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"An unexpected error occurred: {e}")
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lvpyioTools
|
|
3
|
+
Version: 2.4.5
|
|
4
|
+
Summary: A set of tools to handle LabView files and data via the lvpyio library.
|
|
5
|
+
Author-email: SimKDT <simon.cadet79+github@proton.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SimKDT/lvpyio-tools
|
|
8
|
+
Project-URL: Issues, https://github.com/SimKDT/lvpyio-tools/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Requires-Python: >=3.12.3
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Requires-Dist: Pillow
|
|
16
|
+
Requires-Dist: lvpyio>=1.4.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# lvpyio-tools
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
A collection of tools to manipulate lvpyio objects such as Sets.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
<details open>
|
|
31
|
+
<summary>PyPi</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install lvpyio-tools
|
|
35
|
+
```
|
|
36
|
+
</details>
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary>Git</summary>
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install git+https://github.com/SimKDT/lvpyio-tools.git
|
|
43
|
+
```
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
<details>
|
|
47
|
+
<summary>Local</summary>
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install .
|
|
51
|
+
```
|
|
52
|
+
</details>
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
from lvpyioTools.set import LVSet
|
|
59
|
+
|
|
60
|
+
set_file = Path("path/to/your/set.set")
|
|
61
|
+
with LVSet(set_file) as lv_set:
|
|
62
|
+
print(lv_set)
|
|
63
|
+
print(f"Number of frames: {len(lv_set)}")
|
|
64
|
+
print(f"Properties: {lv_set.properties}")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Doc (build)
|
|
68
|
+
```bash
|
|
69
|
+
make html
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Serving the documentation locally:
|
|
73
|
+
```bash
|
|
74
|
+
make serve
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/lvpyioTools/__init__.py
|
|
5
|
+
src/lvpyioTools/attribute.py
|
|
6
|
+
src/lvpyioTools/calibration.py
|
|
7
|
+
src/lvpyioTools/frame.py
|
|
8
|
+
src/lvpyioTools/set.py
|
|
9
|
+
src/lvpyioTools/setParser.py
|
|
10
|
+
src/lvpyioTools/setProperties.py
|
|
11
|
+
src/lvpyioTools/viewer.py
|
|
12
|
+
src/lvpyioTools.egg-info/PKG-INFO
|
|
13
|
+
src/lvpyioTools.egg-info/SOURCES.txt
|
|
14
|
+
src/lvpyioTools.egg-info/dependency_links.txt
|
|
15
|
+
src/lvpyioTools.egg-info/entry_points.txt
|
|
16
|
+
src/lvpyioTools.egg-info/requires.txt
|
|
17
|
+
src/lvpyioTools.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lvpyioTools
|