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.
Files changed (43) hide show
  1. zea/__init__.py +1 -1
  2. zea/backend/tensorflow/dataloader.py +0 -4
  3. zea/beamform/pixelgrid.py +1 -1
  4. zea/data/__init__.py +0 -9
  5. zea/data/augmentations.py +221 -28
  6. zea/data/convert/__init__.py +1 -6
  7. zea/data/convert/__main__.py +123 -0
  8. zea/data/convert/camus.py +99 -39
  9. zea/data/convert/echonet.py +183 -82
  10. zea/data/convert/echonetlvh/README.md +2 -3
  11. zea/data/convert/echonetlvh/{convert_raw_to_usbmd.py → __init__.py} +173 -102
  12. zea/data/convert/echonetlvh/manual_rejections.txt +73 -0
  13. zea/data/convert/echonetlvh/precompute_crop.py +43 -64
  14. zea/data/convert/picmus.py +37 -40
  15. zea/data/convert/utils.py +86 -0
  16. zea/data/convert/{matlab.py → verasonics.py} +33 -61
  17. zea/data/data_format.py +124 -4
  18. zea/data/dataloader.py +12 -7
  19. zea/data/datasets.py +109 -70
  20. zea/data/file.py +91 -82
  21. zea/data/file_operations.py +496 -0
  22. zea/data/preset_utils.py +1 -1
  23. zea/display.py +7 -8
  24. zea/internal/checks.py +6 -12
  25. zea/internal/operators.py +4 -0
  26. zea/io_lib.py +108 -160
  27. zea/models/__init__.py +1 -1
  28. zea/models/diffusion.py +62 -11
  29. zea/models/lv_segmentation.py +2 -0
  30. zea/ops.py +398 -158
  31. zea/scan.py +18 -8
  32. zea/tensor_ops.py +82 -62
  33. zea/tools/fit_scan_cone.py +90 -160
  34. zea/tracking/__init__.py +16 -0
  35. zea/tracking/base.py +94 -0
  36. zea/tracking/lucas_kanade.py +474 -0
  37. zea/tracking/segmentation.py +110 -0
  38. zea/utils.py +11 -2
  39. {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/METADATA +3 -1
  40. {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/RECORD +43 -35
  41. {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/WHEEL +0 -0
  42. {zea-0.0.7.dist-info → zea-0.0.8.dist-info}/entry_points.txt +0 -0
  43. {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
- # Set Keras backend to numpy for best CPU performance
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
- """Load splits from MeasurementsList.csv and return avi filenames"""
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
- """Find AVI file in the specified batch directory or any batch if not specified."""
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
- source_path = Path(args.source)
134
- output_path = Path(args.output)
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
- print(f"Parameters already exist at {cone_params_csv}. Use --force to recompute.")
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.source, base_filename, batch=args.batch)
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
- print(
159
- f"Warning: Could not find AVI file for {base_filename} in batch "
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
- print(f"Limited to processing {args.max_files} files due to max_files parameter")
156
+ log.info(f"Limited to processing {args.max_files} files due to max_files parameter")
167
157
 
168
- print(f"Computing cone parameters for {len(files_to_process)} files")
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
- print(f"Error processing {avi_file}: {str(e)}")
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
- print(f"Cone parameters saved to {cone_params_csv} and {cone_params_json}")
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.")
@@ -1,15 +1,11 @@
1
1
  """
2
2
  Script to convert the PICMUS database to the zea format.
3
3
 
4
- Example usage:
5
- ```bash
6
- python zea/data/convert/picmus.py \
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 convert_picmus(source_path, output_path, overwrite=False):
26
- """Converts the PICMUS database to the zea format.
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 get_args():
116
- """Parse command line arguments."""
117
- parser = argparse.ArgumentParser(
118
- description=(
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.src_dir)
141
- output_dir = Path(args.output_dir)
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
- output_dir.mkdir(parents=True, exist_ok=False)
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
- if (
155
- not str_file.endswith("iq.hdf5") or not str_file.endswith("rf.hdf5")
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 = output_dir / file.relative_to(base_dir)
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
- convert_picmus(file, output_file, overwrite=True)
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 matlab raw files to the zea format.
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 in python:
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.data_format.zea_from_matlab_raw import zea_from_matlab_raw
15
+ from zea.data.convert.verasonics import zea_from_verasonics_workspace
16
16
 
17
- zea_from_matlab_raw("C:/path/to/raw_data.mat", "C:/path/to/output.hdf5")
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/matlab.py "C:/path/to/directory"
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
- ``zea_from_matlab_raw`` function as a list.
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
- zea_from_matlab_raw(
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 zea_from_matlab_raw(input_path, output_path, additional_functions=None, frames="all"):
960
- """Converts a Verasonics matlab raw file to the zea format. The MATLAB file
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
- if __name__ == "__main__":
1087
- args = parse_args()
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.input_path is None:
1094
- log.info("Select a directory containing Verasonics matlab raw files.")
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.input_path
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.output_path is None:
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.output_path)
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
- zea_from_matlab_raw(selected_path, output_path, frames=frames)
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
- zea_from_matlab_raw(full_path, file_output_path, frames=frames)
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}")