zea 0.0.7__py3-none-any.whl → 0.0.8__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.
- zea/__init__.py +1 -1
- zea/backend/tensorflow/dataloader.py +0 -4
- zea/beamform/pixelgrid.py +1 -1
- zea/data/__init__.py +0 -9
- zea/data/augmentations.py +221 -28
- zea/data/convert/__init__.py +1 -6
- zea/data/convert/__main__.py +123 -0
- zea/data/convert/camus.py +99 -39
- zea/data/convert/echonet.py +183 -82
- zea/data/convert/echonetlvh/README.md +2 -3
- zea/data/convert/echonetlvh/{convert_raw_to_usbmd.py → __init__.py} +173 -102
- zea/data/convert/echonetlvh/manual_rejections.txt +73 -0
- zea/data/convert/echonetlvh/precompute_crop.py +43 -64
- zea/data/convert/picmus.py +37 -40
- zea/data/convert/utils.py +86 -0
- zea/data/convert/{matlab.py → verasonics.py} +33 -61
- zea/data/data_format.py +124 -4
- zea/data/dataloader.py +12 -7
- zea/data/datasets.py +109 -70
- zea/data/file.py +91 -82
- zea/data/file_operations.py +496 -0
- zea/data/preset_utils.py +1 -1
- zea/display.py +7 -8
- zea/internal/checks.py +6 -12
- zea/internal/operators.py +4 -0
- zea/io_lib.py +108 -160
- zea/models/__init__.py +1 -1
- zea/models/diffusion.py +62 -11
- zea/models/lv_segmentation.py +2 -0
- zea/ops.py +398 -158
- zea/scan.py +18 -8
- zea/tensor_ops.py +82 -62
- zea/tools/fit_scan_cone.py +90 -160
- zea/tracking/__init__.py +16 -0
- zea/tracking/base.py +94 -0
- zea/tracking/lucas_kanade.py +474 -0
- zea/tracking/segmentation.py +110 -0
- zea/utils.py +11 -2
- {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/METADATA +3 -1
- {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/RECORD +43 -35
- {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/WHEEL +0 -0
- {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/entry_points.txt +0 -0
- {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,58 +3,27 @@ Script to precompute cone parameters for the EchoNet-LVH dataset.
|
|
|
3
3
|
This script should be run separately before the main conversion process.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import argparse
|
|
7
6
|
import csv
|
|
8
7
|
import json
|
|
9
|
-
import os
|
|
10
8
|
from pathlib import Path
|
|
11
9
|
|
|
12
10
|
from tqdm import tqdm
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
os.environ["KERAS_BACKEND"] = "numpy"
|
|
16
|
-
|
|
12
|
+
from zea import log
|
|
17
13
|
from zea.tools.fit_scan_cone import fit_and_crop_around_scan_cone
|
|
18
14
|
|
|
19
15
|
|
|
20
|
-
def get_args():
|
|
21
|
-
"""Parse command line arguments."""
|
|
22
|
-
parser = argparse.ArgumentParser(
|
|
23
|
-
description="Precompute cone parameters for EchoNet-LVH dataset"
|
|
24
|
-
)
|
|
25
|
-
parser.add_argument(
|
|
26
|
-
"--source",
|
|
27
|
-
type=str,
|
|
28
|
-
required=True,
|
|
29
|
-
)
|
|
30
|
-
parser.add_argument(
|
|
31
|
-
"--output",
|
|
32
|
-
type=str,
|
|
33
|
-
required=True,
|
|
34
|
-
)
|
|
35
|
-
parser.add_argument(
|
|
36
|
-
"--batch",
|
|
37
|
-
type=str,
|
|
38
|
-
help="Specify which BatchX directory to process, e.g. --batch=Batch2",
|
|
39
|
-
)
|
|
40
|
-
parser.add_argument(
|
|
41
|
-
"--max_files",
|
|
42
|
-
type=int,
|
|
43
|
-
default=None,
|
|
44
|
-
help="Maximum number of files to process (for testing)",
|
|
45
|
-
)
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--force",
|
|
48
|
-
action="store_true",
|
|
49
|
-
help="Force recomputation even if parameters already exist",
|
|
50
|
-
)
|
|
51
|
-
return parser.parse_args()
|
|
52
|
-
|
|
53
|
-
|
|
54
16
|
def load_splits(source_dir):
|
|
55
|
-
"""
|
|
17
|
+
"""
|
|
18
|
+
Load splits from MeasurementsList.csv and return avi filenames
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
source_dir: Source directory containing MeasurementsList.csv
|
|
22
|
+
Returns:
|
|
23
|
+
Dictionary with keys 'train', 'val', 'test', 'rejected' and values as lists of avi filenames
|
|
24
|
+
"""
|
|
56
25
|
csv_path = Path(source_dir) / "MeasurementsList.csv"
|
|
57
|
-
splits = {"train": [], "val": [], "test": []}
|
|
26
|
+
splits = {"train": [], "val": [], "test": [], "rejected": []}
|
|
58
27
|
# Read CSV using built-in csv module
|
|
59
28
|
with open(csv_path, newline="", encoding="utf-8") as csvfile:
|
|
60
29
|
reader = csv.DictReader(csvfile)
|
|
@@ -71,7 +40,17 @@ def load_splits(source_dir):
|
|
|
71
40
|
|
|
72
41
|
|
|
73
42
|
def find_avi_file(source_dir, hashed_filename, batch=None):
|
|
74
|
-
"""
|
|
43
|
+
"""
|
|
44
|
+
Find AVI file in the specified batch directory or any batch if not specified.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
source_dir: Source directory containing BatchX subdirectories
|
|
48
|
+
hashed_filename: Hashed filename (with or without .avi extension)
|
|
49
|
+
batch: Specific batch directory to search in (e.g., "Batch2"), or None to search all batches
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Path to the AVI file if found, else None
|
|
53
|
+
"""
|
|
75
54
|
# If filename already has .avi extension, strip it
|
|
76
55
|
if hashed_filename.endswith(".avi"):
|
|
77
56
|
hashed_filename = hashed_filename[:-4]
|
|
@@ -98,7 +77,7 @@ def load_first_frame(avi_file):
|
|
|
98
77
|
avi_file: Path to the video file
|
|
99
78
|
|
|
100
79
|
Returns:
|
|
101
|
-
First frame as numpy array
|
|
80
|
+
First frame as numpy array of shape (H, W) and dtype np.uint8 (grayscale)
|
|
102
81
|
"""
|
|
103
82
|
try:
|
|
104
83
|
import cv2
|
|
@@ -129,9 +108,20 @@ def precompute_cone_parameters(args):
|
|
|
129
108
|
This function loads the first frame from each AVI file, applies fit_scan_cone
|
|
130
109
|
to determine cropping parameters, and saves these parameters to a CSV file
|
|
131
110
|
for later use during the actual data conversion.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
args: Argument parser namespace with the following attributes:
|
|
114
|
+
src: Source directory containing EchoNet-LVH data
|
|
115
|
+
dst: Destination directory to save cone parameters
|
|
116
|
+
batch: Specific batch to process (e.g., "Batch2") or None for all
|
|
117
|
+
max_files: Maximum number of files to process (or None for all)
|
|
118
|
+
force: Whether to recompute parameters if they already exist
|
|
119
|
+
Returns:
|
|
120
|
+
Path to the CSV file containing cone parameters
|
|
132
121
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
122
|
+
|
|
123
|
+
source_path = Path(args.src)
|
|
124
|
+
output_path = Path(args.dst)
|
|
135
125
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
136
126
|
|
|
137
127
|
# Output file for cone parameters
|
|
@@ -140,7 +130,7 @@ def precompute_cone_parameters(args):
|
|
|
140
130
|
|
|
141
131
|
# Check if parameters already exist
|
|
142
132
|
if cone_params_csv.exists() and not args.force:
|
|
143
|
-
|
|
133
|
+
log.warning(f"Parameters already exist at {cone_params_csv}. Use --force to recompute.")
|
|
144
134
|
return cone_params_csv
|
|
145
135
|
|
|
146
136
|
# Get list of files to process
|
|
@@ -151,21 +141,21 @@ def precompute_cone_parameters(args):
|
|
|
151
141
|
for avi_filename in split_files:
|
|
152
142
|
# Strip .avi if present
|
|
153
143
|
base_filename = avi_filename[:-4] if avi_filename.endswith(".avi") else avi_filename
|
|
154
|
-
avi_file = find_avi_file(args.
|
|
144
|
+
avi_file = find_avi_file(args.src, base_filename, batch=args.batch)
|
|
155
145
|
if avi_file:
|
|
156
146
|
files_to_process.append((avi_file, avi_filename))
|
|
157
147
|
else:
|
|
158
|
-
|
|
159
|
-
f"
|
|
148
|
+
log.warning(
|
|
149
|
+
f"Could not find AVI file for {base_filename} in batch "
|
|
160
150
|
f"{args.batch if args.batch else 'any'}"
|
|
161
151
|
)
|
|
162
152
|
|
|
163
153
|
# Limit files if max_files is specified
|
|
164
154
|
if args.max_files is not None:
|
|
165
155
|
files_to_process = files_to_process[: args.max_files]
|
|
166
|
-
|
|
156
|
+
log.info(f"Limited to processing {args.max_files} files due to max_files parameter")
|
|
167
157
|
|
|
168
|
-
|
|
158
|
+
log.info(f"Computing cone parameters for {len(files_to_process)} files")
|
|
169
159
|
|
|
170
160
|
# Dictionary to store parameters for each file
|
|
171
161
|
all_cone_params = {}
|
|
@@ -217,7 +207,7 @@ def precompute_cone_parameters(args):
|
|
|
217
207
|
all_cone_params[avi_filename] = essential_params
|
|
218
208
|
|
|
219
209
|
except Exception as e:
|
|
220
|
-
|
|
210
|
+
log.error(f"Error processing {avi_file}: {str(e)}")
|
|
221
211
|
|
|
222
212
|
# Write failure record
|
|
223
213
|
failure_record = {
|
|
@@ -236,16 +226,5 @@ def precompute_cone_parameters(args):
|
|
|
236
226
|
with open(cone_params_json, "w", encoding="utf-8") as jsonfile:
|
|
237
227
|
json.dump(all_cone_params, jsonfile)
|
|
238
228
|
|
|
239
|
-
|
|
229
|
+
log.info(f"Cone parameters saved to {cone_params_csv} and {cone_params_json}")
|
|
240
230
|
return cone_params_csv
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if __name__ == "__main__":
|
|
244
|
-
args = get_args()
|
|
245
|
-
print("Using Keras backend: numpy (forced for best performance)")
|
|
246
|
-
|
|
247
|
-
# Precompute cone parameters
|
|
248
|
-
cone_params_csv = precompute_cone_parameters(args)
|
|
249
|
-
|
|
250
|
-
print(f"Precomputation completed. Parameters saved to {cone_params_csv}")
|
|
251
|
-
print("You can now run the main conversion script.")
|
zea/data/convert/picmus.py
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Script to convert the PICMUS database to the zea format.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
--src_dir /mnt/data/PICMUS \
|
|
8
|
-
--output_dir converted_PICMUS_dir
|
|
9
|
-
```
|
|
4
|
+
For more information about the dataset, resort to the following links:
|
|
5
|
+
|
|
6
|
+
- The original dataset can be found at `this link <https://www.creatis.insa-lyon.fr/Challenge/IEEE_IUS_2016/download>`_.
|
|
10
7
|
"""
|
|
11
8
|
|
|
12
|
-
import argparse
|
|
13
9
|
import logging
|
|
14
10
|
import os
|
|
15
11
|
from pathlib import Path
|
|
@@ -19,11 +15,13 @@ import numpy as np
|
|
|
19
15
|
|
|
20
16
|
from zea import log
|
|
21
17
|
from zea.beamform.delays import compute_t0_delays_planewave
|
|
18
|
+
from zea.data.convert.utils import unzip
|
|
22
19
|
from zea.data.data_format import generate_zea_dataset
|
|
23
20
|
|
|
24
21
|
|
|
25
|
-
def
|
|
26
|
-
"""
|
|
22
|
+
def convert(source_path, output_path, overwrite=False):
|
|
23
|
+
"""
|
|
24
|
+
Converts and writes a single PICMUS file to the zea format.
|
|
27
25
|
|
|
28
26
|
Args:
|
|
29
27
|
source_path (str, pathlike): The path to the original PICMUS file.
|
|
@@ -112,37 +110,37 @@ def convert_picmus(source_path, output_path, overwrite=False):
|
|
|
112
110
|
)
|
|
113
111
|
|
|
114
112
|
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"Converts the PICMUS database to the zea format. The "
|
|
120
|
-
"src_dir is scanned for hdf5 files ending in iq or rf. These files are"
|
|
121
|
-
"converted and stored in output_dir under the same relative path as "
|
|
122
|
-
"they came from in src_dir."
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
parser.add_argument(
|
|
126
|
-
"--src_dir",
|
|
127
|
-
type=str,
|
|
128
|
-
help="Source directory where the original PICMUS data is stored.",
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
parser.add_argument("--output_dir", type=str, help="Output directory of the converted database")
|
|
132
|
-
return parser.parse_args()
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if __name__ == "__main__":
|
|
136
|
-
# Parse the arguments
|
|
137
|
-
args = get_args()
|
|
113
|
+
def convert_picmus(args):
|
|
114
|
+
"""
|
|
115
|
+
Convert PICMUS HDF5 files under a source directory into the zea dataset format,
|
|
116
|
+
preserving relative paths in the destination.
|
|
138
117
|
|
|
118
|
+
Args:
|
|
119
|
+
args (argparse.Namespace): An object with the following attributes.
|
|
120
|
+
|
|
121
|
+
- src (str or Path): Path to the PICMUS source directory or archive.
|
|
122
|
+
- dst (str or Path): Path to the output directory where converted .hdf5 files
|
|
123
|
+
will be written.
|
|
124
|
+
|
|
125
|
+
Note:
|
|
126
|
+
- Scans `src` (after unzipping if needed) for `.hdf5` files containing IQ/RF data and
|
|
127
|
+
converts each to the zea format.
|
|
128
|
+
- Preserves the relative directory structure under `dst` and places each converted
|
|
129
|
+
file in its own subdirectory named after the file stem.
|
|
130
|
+
- Fails fast if `src` does not exist or if `dst` already exists.
|
|
131
|
+
"""
|
|
139
132
|
# Get the source and output directories
|
|
140
|
-
base_dir = Path(args.
|
|
141
|
-
|
|
133
|
+
base_dir = Path(args.src)
|
|
134
|
+
dst = Path(args.dst)
|
|
142
135
|
|
|
143
136
|
# Check if the source directory exists and create the output directory
|
|
144
137
|
assert base_dir.exists(), f"Source directory {base_dir} does not exist."
|
|
145
|
-
|
|
138
|
+
|
|
139
|
+
assert not dst.exists(), f"Destination directory {dst} already exists, Exiting."
|
|
140
|
+
|
|
141
|
+
# Unzip the PICMUS dataset if necessary
|
|
142
|
+
base_dir = unzip(base_dir, "picmus")
|
|
143
|
+
dst.mkdir(parents=True, exist_ok=False)
|
|
146
144
|
|
|
147
145
|
# Traverse the source directory and convert all files
|
|
148
146
|
for file in base_dir.rglob("*.hdf5"):
|
|
@@ -151,9 +149,8 @@ if __name__ == "__main__":
|
|
|
151
149
|
# Select only the data files that actually contain rf or iq data
|
|
152
150
|
# (There are also files containing the geometry of the phantoms or
|
|
153
151
|
# images)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
) and "img" in str_file:
|
|
152
|
+
is_data_file = str_file.endswith("iq.hdf5") or str_file.endswith("rf.hdf5")
|
|
153
|
+
if not is_data_file or "img" in str_file:
|
|
157
154
|
log.info("Skipping %s", file.name)
|
|
158
155
|
continue
|
|
159
156
|
|
|
@@ -161,7 +158,7 @@ if __name__ == "__main__":
|
|
|
161
158
|
|
|
162
159
|
# Find the folder relative to the base directory to retain the
|
|
163
160
|
# folder structure in the output directory
|
|
164
|
-
output_file =
|
|
161
|
+
output_file = dst / file.relative_to(base_dir)
|
|
165
162
|
|
|
166
163
|
# Define the output path
|
|
167
164
|
# NOTE: I added output_file.stem to put each file in its own
|
|
@@ -175,7 +172,7 @@ if __name__ == "__main__":
|
|
|
175
172
|
# Create the output directory if it does not exist already
|
|
176
173
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
177
174
|
|
|
178
|
-
|
|
175
|
+
convert(file, output_file, overwrite=True)
|
|
179
176
|
except Exception:
|
|
180
177
|
output_file.parent.rmdir()
|
|
181
178
|
log.error("Failed to convert %s", str_file)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import zipfile
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import imageio
|
|
5
|
+
import numpy as np
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
from zea import log
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_avi(file_path, mode="L"):
|
|
12
|
+
"""Load a .avi file and return a numpy array of frames.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
filename (str): The path to the video file.
|
|
16
|
+
mode (str, optional): Color mode: "L" (grayscale) or "RGB".
|
|
17
|
+
Defaults to "L".
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
numpy.ndarray: Array of frames (num_frames, H, W) or (num_frames, H, W, C)
|
|
21
|
+
"""
|
|
22
|
+
frames = []
|
|
23
|
+
with imageio.get_reader(file_path) as reader:
|
|
24
|
+
for frame in reader:
|
|
25
|
+
img = Image.fromarray(frame)
|
|
26
|
+
img = img.convert(mode)
|
|
27
|
+
img = np.array(img)
|
|
28
|
+
frames.append(img)
|
|
29
|
+
return np.stack(frames)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def unzip(src: str | Path, dataset: str) -> Path:
|
|
33
|
+
"""
|
|
34
|
+
Checks if data folder exist in src.
|
|
35
|
+
Otherwise, unzip dataset.zip in src.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
src (str | Path): The source directory containing the zip file or unzipped folder.
|
|
39
|
+
dataset (str): The name of the dataset to unzip.
|
|
40
|
+
Options are "picmus", "camus", "echonet", "echonetlvh".
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Path: The path to the unzipped dataset directory.
|
|
44
|
+
"""
|
|
45
|
+
src = Path(src)
|
|
46
|
+
if dataset == "picmus":
|
|
47
|
+
zip_name = "picmus.zip"
|
|
48
|
+
folder_name = "archive_to_download"
|
|
49
|
+
unzip_dir = src / folder_name
|
|
50
|
+
elif dataset == "camus":
|
|
51
|
+
zip_name = "CAMUS_public.zip"
|
|
52
|
+
folder_name = "CAMUS_public"
|
|
53
|
+
unzip_dir = src / folder_name
|
|
54
|
+
elif dataset == "echonet":
|
|
55
|
+
zip_name = "EchoNet-Dynamic.zip"
|
|
56
|
+
folder_name = "EchoNet-Dynamic"
|
|
57
|
+
unzip_dir = src / folder_name / "Videos"
|
|
58
|
+
elif dataset == "echonetlvh":
|
|
59
|
+
zip_name = "EchoNet-LVH.zip"
|
|
60
|
+
folder_name = "Batch1"
|
|
61
|
+
unzip_dir = src
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError(f"Dataset {dataset} not recognized for unzip.")
|
|
64
|
+
|
|
65
|
+
if (src / folder_name).exists():
|
|
66
|
+
if dataset == "echonetlvh":
|
|
67
|
+
# EchoNetLVH dataset unzips into four folders. Check they all exist.
|
|
68
|
+
assert (src / "Batch2").exists(), f"Missing Batch2 folder in {src}."
|
|
69
|
+
assert (src / "Batch3").exists(), f"Missing Batch3 folder in {src}."
|
|
70
|
+
assert (src / "Batch4").exists(), f"Missing Batch4 folder in {src}."
|
|
71
|
+
assert (src / "MeasurementsList.csv").exists(), (
|
|
72
|
+
f"Missing MeasurementsList.csv in {src}."
|
|
73
|
+
)
|
|
74
|
+
log.info(f"Found Batch1, Batch2, Batch3, Batch4 and MeasurementsList.csv in {src}.")
|
|
75
|
+
return unzip_dir
|
|
76
|
+
|
|
77
|
+
zip_path = src / zip_name
|
|
78
|
+
if not zip_path.exists():
|
|
79
|
+
raise FileNotFoundError(f"Could not find {zip_name} or {folder_name} folder in {src}.")
|
|
80
|
+
|
|
81
|
+
log.info(f"Unzipping {zip_path} to {src}...")
|
|
82
|
+
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
83
|
+
zip_ref.extractall(src)
|
|
84
|
+
log.info("Unzipping completed.")
|
|
85
|
+
log.info(f"Starting conversion from {src / folder_name}.")
|
|
86
|
+
return unzip_dir
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""Functionality to convert Verasonics
|
|
1
|
+
"""Functionality to convert Verasonics MATLAB workspace to the zea format.
|
|
2
2
|
|
|
3
|
-
Example (MATLAB):
|
|
3
|
+
Example of saving the entire workspace to a .mat file (MATLAB):
|
|
4
4
|
|
|
5
5
|
.. code-block:: matlab
|
|
6
6
|
|
|
@@ -8,19 +8,19 @@ Example (MATLAB):
|
|
|
8
8
|
>> VSX;
|
|
9
9
|
>> save_raw('C:/path/to/raw_data.mat');
|
|
10
10
|
|
|
11
|
-
Then
|
|
11
|
+
Then convert the saved `raw_data.mat` file to zea format using the following code (Python):
|
|
12
12
|
|
|
13
13
|
.. code-block:: python
|
|
14
14
|
|
|
15
|
-
from zea.
|
|
15
|
+
from zea.data.convert.verasonics import zea_from_verasonics_workspace
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
zea_from_verasonics_workspace("C:/path/to/raw_data.mat", "C:/path/to/output.hdf5")
|
|
18
18
|
|
|
19
19
|
Or alternatively, use the script below to convert all .mat files in a directory:
|
|
20
20
|
|
|
21
21
|
.. code-block:: bash
|
|
22
22
|
|
|
23
|
-
python zea/data/convert/
|
|
23
|
+
python zea/data/convert/verasonics.py "C:/path/to/directory"
|
|
24
24
|
|
|
25
25
|
or without the directory argument, the script will prompt you to select a directory
|
|
26
26
|
using a file dialog.
|
|
@@ -72,7 +72,7 @@ Adding additional elements
|
|
|
72
72
|
|
|
73
73
|
You can add additional elements to the dataset by defining a function that reads the
|
|
74
74
|
data from the file and returns a ``DatasetElement``. Then pass the function to the
|
|
75
|
-
``
|
|
75
|
+
``zea_from_verasonics_workspace`` function as a list.
|
|
76
76
|
|
|
77
77
|
.. code-block:: python
|
|
78
78
|
|
|
@@ -91,14 +91,13 @@ data from the file and returns a ``DatasetElement``. Then pass the function to t
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
zea_from_verasonics_workspace(
|
|
95
95
|
"C:/path/to/raw_data.mat",
|
|
96
96
|
"C:/path/to/output.hdf5",
|
|
97
97
|
[read_high_voltage_func],
|
|
98
98
|
)
|
|
99
99
|
""" # noqa: E501
|
|
100
100
|
|
|
101
|
-
import argparse
|
|
102
101
|
import os
|
|
103
102
|
import sys
|
|
104
103
|
import traceback
|
|
@@ -150,11 +149,6 @@ def dereference_index(file, dataset, index, event=None, subindex=None):
|
|
|
150
149
|
else:
|
|
151
150
|
return file[reference][subindex]
|
|
152
151
|
else:
|
|
153
|
-
if index > 0:
|
|
154
|
-
log.warning(
|
|
155
|
-
f"index {index} is not a reference. You are probably "
|
|
156
|
-
"incorrectly indexing a dataset."
|
|
157
|
-
)
|
|
158
152
|
return dataset
|
|
159
153
|
|
|
160
154
|
|
|
@@ -935,7 +929,7 @@ def get_frame_indices(file, frames):
|
|
|
935
929
|
frame_indices (np.ndarray): The frame indices.
|
|
936
930
|
"""
|
|
937
931
|
# Read the number of frames from the file
|
|
938
|
-
n_frames = int(file["Resource"]["RcvBuffer"]["numFrames"][0][0])
|
|
932
|
+
n_frames = int(dereference_index(file, file["Resource"]["RcvBuffer"]["numFrames"], 0)[0][0])
|
|
939
933
|
|
|
940
934
|
if isinstance(frames, str) and frames == "all":
|
|
941
935
|
# Create an array of all frame-indices
|
|
@@ -956,8 +950,8 @@ def get_frame_indices(file, frames):
|
|
|
956
950
|
return frame_indices
|
|
957
951
|
|
|
958
952
|
|
|
959
|
-
def
|
|
960
|
-
"""Converts a Verasonics
|
|
953
|
+
def zea_from_verasonics_workspace(input_path, output_path, additional_functions=None, frames="all"):
|
|
954
|
+
"""Converts a Verasonics MATLAB workspace file (.mat) to the zea format. The .mat file
|
|
961
955
|
should be created using the `save_raw` function and be stored in "v7.3" format.
|
|
962
956
|
|
|
963
957
|
Args:
|
|
@@ -970,6 +964,7 @@ def zea_from_matlab_raw(input_path, output_path, additional_functions=None, fram
|
|
|
970
964
|
a list of integers, a range of integers (e.g. 4-8), or 'all'. Defaults to
|
|
971
965
|
'all'.
|
|
972
966
|
"""
|
|
967
|
+
|
|
973
968
|
# Create the output directory if it does not exist
|
|
974
969
|
input_path = Path(input_path)
|
|
975
970
|
output_path = Path(output_path)
|
|
@@ -1004,6 +999,7 @@ def zea_from_matlab_raw(input_path, output_path, additional_functions=None, fram
|
|
|
1004
999
|
# convert dict of events to dict of lists
|
|
1005
1000
|
data = {key: [data[event][key] for event in data] for key in data[0]}
|
|
1006
1001
|
description = ["Verasonics data with multiple events"] * num_events
|
|
1002
|
+
|
|
1007
1003
|
# Generate the zea dataset
|
|
1008
1004
|
generate_zea_dataset(
|
|
1009
1005
|
path=output_path,
|
|
@@ -1024,40 +1020,6 @@ def zea_from_matlab_raw(input_path, output_path, additional_functions=None, fram
|
|
|
1024
1020
|
log.success(f"Converted {log.yellow(input_path)} to {log.yellow(output_path)}")
|
|
1025
1021
|
|
|
1026
1022
|
|
|
1027
|
-
def parse_args():
|
|
1028
|
-
"""Parse command line arguments."""
|
|
1029
|
-
parser = argparse.ArgumentParser(
|
|
1030
|
-
description="Convert Verasonics matlab raw files to the zea format."
|
|
1031
|
-
"Example usage: python zea/data/convert/matlab.py raw_file.mat output.hdf5 --frames 1-5 7"
|
|
1032
|
-
)
|
|
1033
|
-
parser.add_argument(
|
|
1034
|
-
"input_path",
|
|
1035
|
-
default=None,
|
|
1036
|
-
type=str,
|
|
1037
|
-
nargs="?",
|
|
1038
|
-
help="The path to a file or directory containing raw Verasonics data.",
|
|
1039
|
-
)
|
|
1040
|
-
|
|
1041
|
-
parser.add_argument(
|
|
1042
|
-
"output_path",
|
|
1043
|
-
default=None,
|
|
1044
|
-
type=str,
|
|
1045
|
-
nargs="?",
|
|
1046
|
-
help="The path to the output file or directory.",
|
|
1047
|
-
)
|
|
1048
|
-
|
|
1049
|
-
parser.add_argument(
|
|
1050
|
-
"--frames",
|
|
1051
|
-
default=["all"],
|
|
1052
|
-
type=str,
|
|
1053
|
-
nargs="+",
|
|
1054
|
-
help="The frames to add to the file. This can be a list of integers, a range "
|
|
1055
|
-
"of integers (e.g. 4-8), or 'all'.",
|
|
1056
|
-
)
|
|
1057
|
-
|
|
1058
|
-
return parser.parse_args()
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
1023
|
def get_answer(prompt, additional_options=None):
|
|
1062
1024
|
"""Get a yes or no answer from the user. There is also the option to provide
|
|
1063
1025
|
additional options. In case yes or no is selected, the function returns a boolean.
|
|
@@ -1083,15 +1045,25 @@ def get_answer(prompt, additional_options=None):
|
|
|
1083
1045
|
log.warning("Invalid input.")
|
|
1084
1046
|
|
|
1085
1047
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1048
|
+
def convert_verasonics(args):
|
|
1049
|
+
"""
|
|
1050
|
+
Converts a Verasonics MATLAB workspace file (.mat) or a directory containing multiple
|
|
1051
|
+
such files to the zea format.
|
|
1052
|
+
|
|
1053
|
+
Args:
|
|
1054
|
+
args (argparse.Namespace): An object with attributes:
|
|
1055
|
+
|
|
1056
|
+
- src (str): Source folder path.
|
|
1057
|
+
- dst (str): Destination folder path.
|
|
1058
|
+
- frames (list[str]): MATLAB frames spec (e.g., ["all"], integers, or ranges like "4-8")
|
|
1059
|
+
"""
|
|
1088
1060
|
|
|
1089
1061
|
# Variable to indicate what to do with existing files.
|
|
1090
1062
|
# Is set by the user in case these are found.
|
|
1091
1063
|
existing_file_policy = None
|
|
1092
1064
|
|
|
1093
|
-
if args.
|
|
1094
|
-
log.info("Select a directory containing Verasonics
|
|
1065
|
+
if args.src is None:
|
|
1066
|
+
log.info("Select a directory containing Verasonics MATLAB workspace files.")
|
|
1095
1067
|
# Create a Tkinter root window
|
|
1096
1068
|
try:
|
|
1097
1069
|
import tkinter as tk
|
|
@@ -1115,7 +1087,7 @@ if __name__ == "__main__":
|
|
|
1115
1087
|
)
|
|
1116
1088
|
) from e
|
|
1117
1089
|
else:
|
|
1118
|
-
selected_path = args.
|
|
1090
|
+
selected_path = args.src
|
|
1119
1091
|
|
|
1120
1092
|
# Exit when no path is selected
|
|
1121
1093
|
if not selected_path:
|
|
@@ -1128,14 +1100,14 @@ if __name__ == "__main__":
|
|
|
1128
1100
|
|
|
1129
1101
|
# Set the output path to be next to the input directory with _zea appended
|
|
1130
1102
|
# to the name
|
|
1131
|
-
if args.
|
|
1103
|
+
if args.dst is None:
|
|
1132
1104
|
if selected_path_is_directory:
|
|
1133
1105
|
output_path = selected_path.parent / (Path(selected_path).name + "_zea")
|
|
1134
1106
|
else:
|
|
1135
1107
|
output_path = str(selected_path.with_suffix("")) + "_zea.hdf5"
|
|
1136
1108
|
output_path = Path(output_path)
|
|
1137
1109
|
else:
|
|
1138
|
-
output_path = Path(args.
|
|
1110
|
+
output_path = Path(args.dst)
|
|
1139
1111
|
if selected_path.is_file() and output_path.suffix not in (".hdf5", ".h5"):
|
|
1140
1112
|
log.error(
|
|
1141
1113
|
"When converting a single file, the output path should have the .hdf5 "
|
|
@@ -1145,7 +1117,7 @@ if __name__ == "__main__":
|
|
|
1145
1117
|
elif selected_path.is_dir() and output_path.is_file():
|
|
1146
1118
|
log.error("When converting a directory, the output path should be a directory.")
|
|
1147
1119
|
sys.exit()
|
|
1148
|
-
|
|
1120
|
+
|
|
1149
1121
|
if output_path.is_dir() and not selected_path_is_directory:
|
|
1150
1122
|
output_path = output_path / (selected_path.name + "_zea.hdf5")
|
|
1151
1123
|
|
|
@@ -1180,7 +1152,7 @@ if __name__ == "__main__":
|
|
|
1180
1152
|
else:
|
|
1181
1153
|
log.info("Aborting...")
|
|
1182
1154
|
sys.exit()
|
|
1183
|
-
|
|
1155
|
+
zea_from_verasonics_workspace(selected_path, output_path, frames=frames)
|
|
1184
1156
|
else:
|
|
1185
1157
|
# Continue with the rest of your code...
|
|
1186
1158
|
for root, dirs, files in os.walk(selected_path):
|
|
@@ -1227,7 +1199,7 @@ if __name__ == "__main__":
|
|
|
1227
1199
|
file_output_path.unlink(missing_ok=False)
|
|
1228
1200
|
|
|
1229
1201
|
try:
|
|
1230
|
-
|
|
1202
|
+
zea_from_verasonics_workspace(full_path, file_output_path, frames=frames)
|
|
1231
1203
|
except Exception:
|
|
1232
1204
|
# Print error message without raising it
|
|
1233
1205
|
log.error(f"Failed to convert {mat_file}")
|