zea 0.0.6__py3-none-any.whl → 0.0.7__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 (61) hide show
  1. zea/__init__.py +54 -19
  2. zea/agent/__init__.py +12 -12
  3. zea/agent/masks.py +2 -1
  4. zea/backend/tensorflow/dataloader.py +2 -1
  5. zea/beamform/beamformer.py +100 -50
  6. zea/beamform/lens_correction.py +9 -2
  7. zea/beamform/pfield.py +9 -2
  8. zea/config.py +34 -25
  9. zea/data/__init__.py +22 -16
  10. zea/data/convert/camus.py +2 -1
  11. zea/data/convert/echonet.py +4 -4
  12. zea/data/convert/echonetlvh/convert_raw_to_usbmd.py +1 -1
  13. zea/data/convert/matlab.py +11 -4
  14. zea/data/data_format.py +31 -30
  15. zea/data/datasets.py +7 -5
  16. zea/data/file.py +104 -2
  17. zea/data/layers.py +3 -3
  18. zea/datapaths.py +16 -4
  19. zea/display.py +7 -5
  20. zea/interface.py +14 -16
  21. zea/internal/_generate_keras_ops.py +6 -7
  22. zea/internal/cache.py +2 -49
  23. zea/internal/config/validation.py +1 -2
  24. zea/internal/core.py +69 -6
  25. zea/internal/device.py +6 -2
  26. zea/internal/dummy_scan.py +330 -0
  27. zea/internal/operators.py +114 -2
  28. zea/internal/parameters.py +101 -70
  29. zea/internal/setup_zea.py +5 -6
  30. zea/internal/utils.py +282 -0
  31. zea/io_lib.py +247 -19
  32. zea/keras_ops.py +74 -4
  33. zea/log.py +9 -7
  34. zea/metrics.py +15 -7
  35. zea/models/__init__.py +30 -20
  36. zea/models/base.py +30 -14
  37. zea/models/carotid_segmenter.py +19 -4
  38. zea/models/diffusion.py +173 -12
  39. zea/models/echonet.py +22 -8
  40. zea/models/echonetlvh.py +31 -7
  41. zea/models/lpips.py +19 -2
  42. zea/models/lv_segmentation.py +28 -11
  43. zea/models/preset_utils.py +5 -5
  44. zea/models/regional_quality.py +30 -10
  45. zea/models/taesd.py +21 -5
  46. zea/models/unet.py +15 -1
  47. zea/ops.py +390 -196
  48. zea/probes.py +6 -6
  49. zea/scan.py +109 -49
  50. zea/simulator.py +24 -21
  51. zea/tensor_ops.py +406 -302
  52. zea/tools/hf.py +1 -1
  53. zea/tools/selection_tool.py +47 -86
  54. zea/utils.py +92 -480
  55. zea/visualize.py +177 -39
  56. {zea-0.0.6.dist-info → zea-0.0.7.dist-info}/METADATA +4 -2
  57. zea-0.0.7.dist-info/RECORD +114 -0
  58. zea-0.0.6.dist-info/RECORD +0 -112
  59. {zea-0.0.6.dist-info → zea-0.0.7.dist-info}/WHEEL +0 -0
  60. {zea-0.0.6.dist-info → zea-0.0.7.dist-info}/entry_points.txt +0 -0
  61. {zea-0.0.6.dist-info → zea-0.0.7.dist-info}/licenses/LICENSE +0 -0
zea/probes.py CHANGED
@@ -16,14 +16,14 @@ Example usage
16
16
 
17
17
  We can initialize a generic probe with the following code:
18
18
 
19
- .. code-block:: python
19
+ .. doctest::
20
20
 
21
- import zea
21
+ >>> from zea import Probe
22
22
 
23
- probe = zea.Probe.from_name("generic")
24
- print(probe.get_parameters())
25
-
26
- """
23
+ >>> probe = Probe.from_name("generic")
24
+ >>> print(probe.get_parameters())
25
+ {'probe_geometry': None, 'center_frequency': None, 'sampling_frequency': None, 'xlims': None, 'zlims': None}
26
+ """ # noqa: E501
27
27
 
28
28
  import numpy as np
29
29
 
zea/scan.py CHANGED
@@ -43,38 +43,38 @@ Comparison to ``zea.Config`` and ``zea.Probe``
43
43
  Example Usage
44
44
  ^^^^^^^^^^^^^
45
45
 
46
- .. code-block:: python
47
-
48
- from zea import Config, Probe, Scan
49
-
50
- # Initialize Scan from a Probe's parameters
51
- probe = Probe.from_name("verasonics_l11_4v")
52
- scan = Scan(**probe.get_parameters(), grid_size_z=256)
53
-
54
- # Or initialize from a Config object
55
- config = Config.from_hf("zeahub/configs", "config_picmus_rf.yaml", repo_type="dataset")
56
- scan = Scan(**config.scan, n_tx=11)
57
-
58
- # Or manually specify parameters
59
- scan = Scan(
60
- grid_size_x=128,
61
- grid_size_z=256,
62
- xlims=(-0.02, 0.02),
63
- zlims=(0.0, 0.06),
64
- center_frequency=6.25e6,
65
- sound_speed=1540.0,
66
- sampling_frequency=25e6,
67
- n_el=128,
68
- n_tx=11,
69
- )
70
-
71
- # Access a derived property (computed lazily)
72
- grid = scan.grid # shape: (grid_size_z, grid_size_x, 3)
73
-
74
- # Select a subset of transmit events
75
- scan.set_transmits(3) # Use 3 evenly spaced transmits
76
- scan.set_transmits([0, 2, 4]) # Use specific transmit indices
77
- scan.set_transmits("all") # Use all transmits
46
+ .. doctest::
47
+
48
+ >>> from zea import Config, Probe, Scan
49
+
50
+ >>> # Initialize Scan from a Probe's parameters
51
+ >>> probe = Probe.from_name("verasonics_l11_4v")
52
+ >>> scan = Scan(**probe.get_parameters(), grid_size_z=256, n_tx=11)
53
+
54
+ >>> # Or initialize from a Config object
55
+ >>> config = Config.from_hf("zeahub/configs", "config_picmus_rf.yaml", repo_type="dataset")
56
+ >>> scan = Scan(n_tx=11, **config.scan)
57
+
58
+ >>> # Or manually specify parameters
59
+ >>> scan = Scan(
60
+ ... grid_size_x=128,
61
+ ... grid_size_z=256,
62
+ ... xlims=(-0.02, 0.02),
63
+ ... zlims=(0.0, 0.06),
64
+ ... center_frequency=6.25e6,
65
+ ... sound_speed=1540.0,
66
+ ... sampling_frequency=25e6,
67
+ ... n_el=128,
68
+ ... n_tx=11,
69
+ ... )
70
+
71
+ >>> # Access a derived property (computed lazily)
72
+ >>> grid = scan.grid # shape: (grid_size_z, grid_size_x, 3)
73
+
74
+ >>> # Select a subset of transmit events
75
+ >>> _ = scan.set_transmits(3) # Use 3 evenly spaced transmits
76
+ >>> _ = scan.set_transmits([0, 2, 4]) # Use specific transmit indices
77
+ >>> _ = scan.set_transmits("all") # Use all transmits
78
78
 
79
79
  """
80
80
 
@@ -83,7 +83,11 @@ from keras import ops
83
83
 
84
84
  from zea import log
85
85
  from zea.beamform.pfield import compute_pfield
86
- from zea.beamform.pixelgrid import cartesian_pixel_grid, check_for_aliasing, polar_pixel_grid
86
+ from zea.beamform.pixelgrid import (
87
+ cartesian_pixel_grid,
88
+ check_for_aliasing,
89
+ polar_pixel_grid,
90
+ )
87
91
  from zea.display import (
88
92
  compute_scan_convert_2d_coordinates,
89
93
  compute_scan_convert_3d_coordinates,
@@ -130,6 +134,15 @@ class Scan(Parameters):
130
134
  demodulation_frequency (float, optional): Demodulation frequency in Hz.
131
135
  time_to_next_transmit (np.ndarray): The time between subsequent
132
136
  transmit events of shape (n_frames, n_tx).
137
+ tgc_gain_curve (np.ndarray): Time gain compensation (TGC) curve of shape (n_ax,).
138
+ waveforms_one_way (np.ndarray): The one-way transmit waveforms of shape
139
+ (n_waveforms, n_samples).
140
+ waveforms_two_way (np.ndarray): The two-way transmit waveforms of shape
141
+ (n_waveforms, n_samples).
142
+ tx_waveform_indices (np.ndarray): Indices of the waveform used for each
143
+ transmit event of shape (n_tx,).
144
+ t_peak (np.ndarray, optional): The time of the peak of the pulse of every transmit waveform
145
+ of shape (n_waveforms,).
133
146
  pixels_per_wavelength (int, optional): Number of pixels per wavelength.
134
147
  Defaults to 4.
135
148
  element_width (float, optional): Width of each transducer element in meters.
@@ -160,6 +173,7 @@ class Scan(Parameters):
160
173
  Can be "cartesian" or "polar". Defaults to "cartesian".
161
174
  dynamic_range (tuple, optional): Dynamic range for image display.
162
175
  Defined in dB as (min_dB, max_dB). Defaults to (-60, 0).
176
+
163
177
  """
164
178
 
165
179
  VALID_PARAMS = {
@@ -200,6 +214,11 @@ class Scan(Parameters):
200
214
  "focus_distances": {"type": np.ndarray},
201
215
  "initial_times": {"type": np.ndarray},
202
216
  "time_to_next_transmit": {"type": np.ndarray},
217
+ "tgc_gain_curve": {"type": np.ndarray},
218
+ "waveforms_one_way": {"type": np.ndarray},
219
+ "waveforms_two_way": {"type": np.ndarray},
220
+ "tx_waveform_indices": {"type": np.ndarray},
221
+ "t_peak": {"type": np.ndarray},
203
222
  # scan conversion parameters
204
223
  "theta_range": {"type": (tuple, list)},
205
224
  "phi_range": {"type": (tuple, list)},
@@ -209,8 +228,9 @@ class Scan(Parameters):
209
228
  }
210
229
 
211
230
  def __init__(self, **kwargs):
212
- # Store the current selection state before initialization
213
- selected_transmits_input = kwargs.pop("selected_transmits", None)
231
+ # Ensure that selected_transmits is present and set to None by default
232
+ selected_transmits_input = kwargs.get("selected_transmits", None)
233
+ kwargs["selected_transmits"] = None
214
234
 
215
235
  # Initialize parent class
216
236
  super().__init__(**kwargs)
@@ -240,7 +260,10 @@ class Scan(Parameters):
240
260
  )
241
261
  elif self.grid_type == "cartesian":
242
262
  return cartesian_pixel_grid(
243
- self.xlims, self.zlims, grid_size_z=self.grid_size_z, grid_size_x=self.grid_size_x
263
+ self.xlims,
264
+ self.zlims,
265
+ grid_size_z=self.grid_size_z,
266
+ grid_size_x=self.grid_size_x,
244
267
  )
245
268
  else:
246
269
  raise ValueError(
@@ -301,7 +324,10 @@ class Scan(Parameters):
301
324
  radius * np.cos(-np.pi / 2 + self.polar_limits[1]),
302
325
  )
303
326
  xlims_plane = (self.probe_geometry[0, 0], self.probe_geometry[-1, 0])
304
- xlims = min(xlims_polar[0], xlims_plane[0]), max(xlims_polar[1], xlims_plane[1])
327
+ xlims = (
328
+ min(xlims_polar[0], xlims_plane[0]),
329
+ max(xlims_polar[1], xlims_plane[1]),
330
+ )
305
331
  return xlims
306
332
 
307
333
  @cache_with_dependencies("sound_speed", "sampling_frequency", "n_ax")
@@ -344,6 +370,11 @@ class Scan(Parameters):
344
370
  """The total number of transmits in the full dataset."""
345
371
  return self._params["n_tx"]
346
372
 
373
+ @property
374
+ def n_tx_selected(self):
375
+ """The number of currently selected transmits."""
376
+ return len(self.selected_transmits)
377
+
347
378
  @cache_with_dependencies("selected_transmits")
348
379
  def n_tx(self):
349
380
  """The number of currently selected transmits."""
@@ -387,14 +418,12 @@ class Scan(Parameters):
387
418
  if selection is None or selection == "all":
388
419
  self._selected_transmits = None
389
420
  self._invalidate("selected_transmits")
390
- self._invalidate_dependents("selected_transmits")
391
421
  return self
392
422
 
393
423
  # Handle "center" - use center transmit
394
424
  if selection == "center":
395
425
  self._selected_transmits = [n_tx_total // 2]
396
426
  self._invalidate("selected_transmits")
397
- self._invalidate_dependents("selected_transmits")
398
427
  return self
399
428
 
400
429
  # Handle integer - select evenly spaced transmits
@@ -416,7 +445,6 @@ class Scan(Parameters):
416
445
  self._selected_transmits = list(np.rint(tx_indices).astype(int))
417
446
 
418
447
  self._invalidate("selected_transmits")
419
- self._invalidate_dependents("selected_transmits")
420
448
  return self
421
449
 
422
450
  # Handle slice - convert to list of indices
@@ -436,7 +464,6 @@ class Scan(Parameters):
436
464
  int(i) for i in selection
437
465
  ] # Convert numpy integers to Python ints
438
466
  self._invalidate("selected_transmits")
439
- self._invalidate_dependents("selected_transmits")
440
467
  return self
441
468
 
442
469
  # Aliasing check
@@ -481,7 +508,7 @@ class Scan(Parameters):
481
508
  value = self._params.get("azimuth_angles")
482
509
  if value is None:
483
510
  log.warning("No azimuth angles provided, using zeros")
484
- value = np.zeros(self.n_tx_total)
511
+ value = np.zeros(self.n_tx_selected)
485
512
 
486
513
  return value[self.selected_transmits]
487
514
 
@@ -492,7 +519,7 @@ class Scan(Parameters):
492
519
  value = self._params.get("t0_delays")
493
520
  if value is None:
494
521
  log.warning("No transmit delays provided, using zeros")
495
- return np.zeros((self.n_tx_total, self.n_el))
522
+ return np.zeros((self.n_tx_selected, self.n_el))
496
523
 
497
524
  return value[self.selected_transmits]
498
525
 
@@ -502,7 +529,7 @@ class Scan(Parameters):
502
529
  value = self._params.get("tx_apodizations")
503
530
  if value is None:
504
531
  log.warning("No transmit apodizations provided, using ones")
505
- value = np.ones((self.n_tx_total, self.n_el))
532
+ value = np.ones((self.n_tx_selected, self.n_el))
506
533
 
507
534
  return value[self.selected_transmits]
508
535
 
@@ -512,7 +539,7 @@ class Scan(Parameters):
512
539
  value = self._params.get("focus_distances")
513
540
  if value is None:
514
541
  log.warning("No focus distances provided, using zeros")
515
- value = np.zeros(self.n_tx_total)
542
+ value = np.zeros(self.n_tx_selected)
516
543
 
517
544
  return value[self.selected_transmits]
518
545
 
@@ -522,10 +549,19 @@ class Scan(Parameters):
522
549
  value = self._params.get("initial_times")
523
550
  if value is None:
524
551
  log.warning("No initial times provided, using zeros")
525
- value = np.zeros(self.n_tx_total)
552
+ value = np.zeros(self.n_tx_selected)
526
553
 
527
554
  return value[self.selected_transmits]
528
555
 
556
+ @property
557
+ def t_peak(self):
558
+ """The time of the peak of the pulse in seconds of shape (n_waveforms,)."""
559
+ t_peak = self._params.get("t_peak")
560
+ if t_peak is None:
561
+ t_peak = np.array([1 / self.center_frequency])
562
+
563
+ return t_peak
564
+
529
565
  @cache_with_dependencies("selected_transmits")
530
566
  def time_to_next_transmit(self):
531
567
  """The time between subsequent transmit events of shape (n_frames, n_tx)."""
@@ -536,6 +572,23 @@ class Scan(Parameters):
536
572
  selected = self.selected_transmits
537
573
  return value[:, selected]
538
574
 
575
+ @cache_with_dependencies("n_ax")
576
+ def tgc_gain_curve(self):
577
+ """Time gain compensation (TGC) curve of shape (n_ax,)."""
578
+ value = self._params.get("tgc_gain_curve")
579
+ if value is None:
580
+ return np.ones(self.n_ax)
581
+ return value[: self.n_ax]
582
+
583
+ @cache_with_dependencies("selected_transmits")
584
+ def tx_waveform_indices(self):
585
+ """Indices of the waveform used for each transmit event of shape (n_tx,)."""
586
+ value = self._params.get("tx_waveform_indices")
587
+ if value is None:
588
+ return np.zeros(self.n_tx_selected, dtype=int)
589
+
590
+ return value[self.selected_transmits]
591
+
539
592
  @cache_with_dependencies(
540
593
  "sound_speed",
541
594
  "center_frequency",
@@ -545,8 +598,9 @@ class Scan(Parameters):
545
598
  "tx_apodizations",
546
599
  "grid",
547
600
  "t0_delays",
601
+ "pfield_kwargs",
548
602
  )
549
- def pfield(self):
603
+ def pfield(self) -> np.ndarray:
550
604
  """Compute or return the pressure field (pfield) for weighting."""
551
605
  pfield = compute_pfield(
552
606
  sound_speed=self.sound_speed,
@@ -596,7 +650,12 @@ class Scan(Parameters):
596
650
  return coords
597
651
 
598
652
  @cache_with_dependencies(
599
- "rho_range", "theta_range", "phi_range", "resolution", "grid_size_z", "grid_size_x"
653
+ "rho_range",
654
+ "theta_range",
655
+ "phi_range",
656
+ "resolution",
657
+ "grid_size_z",
658
+ "grid_size_x",
600
659
  )
601
660
  def coordinates_3d(self):
602
661
  """The coordinates for scan conversion."""
@@ -644,6 +703,7 @@ class Scan(Parameters):
644
703
  """The width of each transducer element in meters."""
645
704
  value = self._params.get("element_width")
646
705
  if value is None:
706
+ # assume uniform spacing
647
707
  return np.linalg.norm(self.probe_geometry[1] - self.probe_geometry[0])
648
708
  return value
649
709
 
@@ -651,5 +711,5 @@ class Scan(Parameters):
651
711
  if key == "selected_transmits":
652
712
  # If setting selected_transmits, call set_transmits to handle logic
653
713
  self.set_transmits(value)
654
- return
714
+ return super().__setattr__(key, self.selected_transmits)
655
715
  return super().__setattr__(key, value)
zea/simulator.py CHANGED
@@ -14,27 +14,30 @@ Example usage
14
14
  A simple example of simulating RF data with a single scatterer at the center of the probe. For a
15
15
  more in depth example see the notebook: :doc:`../notebooks/data/zea_simulation_example`.
16
16
 
17
- .. code-block:: python
18
-
19
- raw_data = simulate_rf(
20
- scatterer_positions=np.array([[0, 0, 20e-3]]),
21
- scatterer_magnitudes=np.array([1.0]),
22
- probe_geometry=np.stack(
23
- [np.linspace(-20e-3, 20e-3, 64), np.zeros(64), np.zeros(64)], axis=-1
24
- ),
25
- apply_lens_correction=True,
26
- lens_thickness=1e-3,
27
- lens_sound_speed=1000,
28
- sound_speed=1540,
29
- n_ax=1024,
30
- center_frequency=5e6,
31
- sampling_frequency=20e6,
32
- t0_delays=np.zeros((1, 64)),
33
- initial_times=np.zeros(1),
34
- element_width=0.2e-3,
35
- attenuation_coef=0.5,
36
- tx_apodizations=np.ones((1, 64)),
37
- )
17
+ .. doctest::
18
+
19
+ >>> from zea.simulator import simulate_rf
20
+ >>> import numpy as np
21
+
22
+ >>> raw_data = simulate_rf(
23
+ ... scatterer_positions=np.array([[0, 0, 20e-3]]),
24
+ ... scatterer_magnitudes=np.array([1.0]),
25
+ ... probe_geometry=np.stack(
26
+ ... [np.linspace(-20e-3, 20e-3, 64), np.zeros(64), np.zeros(64)], axis=-1
27
+ ... ),
28
+ ... apply_lens_correction=True,
29
+ ... lens_thickness=1e-3,
30
+ ... lens_sound_speed=1000,
31
+ ... sound_speed=1540,
32
+ ... n_ax=1024,
33
+ ... center_frequency=5e6,
34
+ ... sampling_frequency=20e6,
35
+ ... t0_delays=np.zeros((1, 64)),
36
+ ... initial_times=np.zeros(1),
37
+ ... element_width=0.2e-3,
38
+ ... attenuation_coef=0.5,
39
+ ... tx_apodizations=np.ones((1, 64)),
40
+ ... )
38
41
 
39
42
  """
40
43