fullwave25 1.4.2.dev3__tar.gz → 1.4.3.dev0__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.
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.bumpversion.toml +1 -1
- fullwave25-1.4.3.dev0/.claude/skills/fullwave25/SKILL.md +253 -0
- fullwave25-1.4.3.dev0/CONTRIBUTING.md +28 -0
- fullwave25-1.4.3.dev0/PKG-INFO +350 -0
- fullwave25-1.4.3.dev0/README.md +305 -0
- fullwave25-1.4.3.dev0/docs/fullwave2_ultra.md +33 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/__init__.py +1 -1
- fullwave25-1.4.3.dev0/fullwave/beamformer/__init__.py +6 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/beamformer/beamformer.py +18 -9
- fullwave25-1.4.3.dev0/fullwave/beamformer/mach_beamformer.py +533 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/grid.py +15 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium.py +8 -7
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/__init__.py +8 -1
- fullwave25-1.4.3.dev0/fullwave/medium_builder/presets/combined_phantom_map.py +146 -0
- fullwave25-1.4.3.dev0/fullwave/medium_builder/presets/domain_combined_phantom.py +316 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/binary_manager.py +1 -1
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/binary_markers.py +29 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/input_file_writer.py +47 -5
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/pml_builder.py +2 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/solver.py +285 -65
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/source_type.py +27 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/source.py +39 -1
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/transducer.py +388 -60
- fullwave25-1.4.3.dev0/fullwave/ultra/attenuation_parameters.py +215 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/backend.py +71 -4
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/backend_2d.py +60 -11
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/binary.py +32 -1
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/dat_io.py +15 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/factory.py +4 -1
- fullwave25-1.4.3.dev0/fullwave/ultra/medium_to_aexp.py +167 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/writer_2d.py +91 -13
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/writer_3d.py +90 -13
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/plot_utils.py +158 -4
- fullwave25-1.4.3.dev0/fullwave/utils/signal_filter.py +126 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/pyproject.toml +7 -1
- fullwave25-1.4.3.dev0/references/2604.06257v1.pdf +0 -0
- fullwave25-1.4.3.dev0/references/signed.gif +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/uv.lock +10 -2
- fullwave25-1.4.2.dev3/.claude/skills/fullwave25/SKILL.md +0 -119
- fullwave25-1.4.2.dev3/CONTRIBUTING.md +0 -24
- fullwave25-1.4.2.dev3/PKG-INFO +0 -486
- fullwave25-1.4.2.dev3/README.md +0 -445
- fullwave25-1.4.2.dev3/fullwave/beamformer/__init__.py +0 -5
- fullwave25-1.4.2.dev3/fullwave/ultra/medium_to_aexp.py +0 -50
- fullwave25-1.4.2.dev3/fullwave/utils/signal_filter.py +0 -161
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/.gitignore +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260911_1813_dead-code.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_1311_commit_message_of_the_input_generation_speedup.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_1311_pull_request_of_the_input_generation_speedup.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_1405_commit_message_of_the_relaxation_memory_estimate.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_1405_pull_request_of_the_relaxation_memory_estimate.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_2152_commit_message_of_the_cuda_version_choice.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_2152_pull_request_of_the_cuda_version_choice.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_2203_commit_message_of_the_cuda_version_tests.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_2213_commit_message_of_the_blackwell_version_choice.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/20260916_2213_pull_request_of_the_blackwell_version_choice.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/CLAUDE.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260909_1844_acquire_the_reflection_matrix.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260909_1900_test_on_a_synthetic_acquisition.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260909_1913_analyze_the_reflection_matrix.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260909_2107_redraw_the_figures_of_an_analysis.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260909_2121_write_the_result_tables.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260910_1126_synthesize_plane_waves_from_the_multistatic_matrix.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260910_1137_scan_the_speed_of_sound.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260910_1141_compare_the_laws_with_the_truth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260910_1146_evaluate_the_reverberation_filter.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/20260910_1208_compare_plane_waves_with_the_synthesis.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/figures.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/ground_truth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/matrix_imaging.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/pipeline.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/reflection_matrix.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/speed_of_sound.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/matrix_imaging/synthetic.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260821_0206_band_anchor_cross_check.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260821_0233_delivered_power_law_end_to_end.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260821_0317_reword_deliver_in_the_library.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260821_1220_relaxation_low_frequency_rolloff.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260825_1937_what_fails_the_pml_golden_tests.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260825_2329_does_the_wrapper_read_the_record.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260826_0002_what_the_new_default_table_holds.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260828_1422_what_the_candidate_tables_hold.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260828_1427_what_the_deployment_needs.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260828_1510_where_the_unstretched_ramp_reaches.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260828_1517_the_blast_radius_of_the_ramp.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1458_the_3d_region_trimmed_kernel_against_the_reference.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1505_the_multi_node_kernel_against_the_multi_gpu_one.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1631_the_3d_multi_node_against_the_multi_gpu_one.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1642_the_selective_absorbing_layer.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1728_the_layer_against_the_taper.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1757_what_the_margin_outside_the_layer_does.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1808_the_layer_in_3d_and_on_the_multi_node_builds.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1832_where_the_3d_residual_sits.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1837_inside_the_layer_at_the_corner.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1840_inside_the_3d_layer_at_the_corner.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1912_both_absorbers_on_one_method.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_1922_the_layer_on_the_reflection_method.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2011_whole_domain_animation.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2032_the_example_source_kind.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2148_drive_scale_of_the_strong_beta_cells.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2159_profile_the_input_map_build.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2201_build_then_extend_equivalence.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2304_the_transmit_methods_launch_what_they_say.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260830_2322_the_unscaled_added_drive_follows_the_courant_number.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0033_the_three_dimensional_arrays.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0054_the_elevation_lens_of_a_linear_array.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0115_run_every_example.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0124_move_the_notebooks_to_the_transmit_interface.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0137_the_air_region_at_four_mechanisms.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0148_the_element_records_its_own_transmit.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0150_the_absorbing_layer_of_a_point_source.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0152_the_exponential_absorbing_layer_against_its_thickness.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0202_the_rebuilt_exponential_absorbing_layer.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0216_where_the_three_dimensional_layer_leaks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_0222_the_absorbing_layer_away_from_the_source.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1037_where_the_near_field_brightness_comes_from.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1040_the_aperture_transit_wedge_in_the_channel_data.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1043_the_transmit_in_the_recorded_trace_and_its_removal.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1048_the_receive_row_against_the_reference_subtraction.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1052_the_backing_behind_the_array_face.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1056_the_transmit_beam_with_the_air_backing.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1105_the_shipped_transducer_stack_in_the_solver.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1119_where_the_stack_puts_the_source_and_what_it_costs_the_depth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1121_the_depth_the_image_reads_with_the_stack.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1123_the_depth_the_beamformer_puts_one_point_at.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1142_the_transmit_offset_the_beamformer_needs.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1145_the_depth_shift_the_offset_fix_makes.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1154_the_two_offset_rules_on_the_same_recording.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1212_what_the_pinned_release_carries.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1228_the_low_cut_against_the_near_field_haze.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1301_run_the_beamforming_notebooks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1432_point_dataset_generator_at_the_local_fullwave.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1437_the_aperture_width_the_reference_geometry_reports.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1454_calibrate_the_ats539_plane_wave.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1508_show_the_b_mode_in_the_hdf5.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1534_the_comet_behind_each_wire.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1547_read_the_calibration_trials.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1605_what_the_medium_holds_in_the_near_field.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1607_sweep_one_generator_knob.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1715_assert_the_ats539_rate_chain.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1717_measure_every_layer_of_the_stack.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1720_where_the_trailing_energy_lands.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1725_offer_the_requested_absorption_to_the_backing.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1727_let_a_sweep_carry_a_switch.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1731_expose_the_exponential_absorbing_layer.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1746_point_the_solver_at_an_older_binary.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1756_measure_the_point_targets.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1802_rederive_the_near_field_bounce.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1810_does_the_shelf_move_with_depth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1812_find_the_repeat_delay_in_the_raw_rf.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1937_give_each_source_row_its_own_launch_delay.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1943_baffle_the_element_plane.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_1952_take_the_cell_size_from_the_grid.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2058_run_the_ats539_through_the_batched_solver.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2105_queue_the_remaining_candidates.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2122_the_simulator_asks_for_a_clamped_source.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2126_rename_the_source_types_to_hard_and_soft.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2140_bind_the_source_type_to_its_entrance.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2225_how_redundant_are_the_absorbing_layer_files.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2239_skip_the_constant_absorbing_layer_maps.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2246_count_the_files_a_run_writes.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2352_run_the_map_tiers_end_to_end.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260831_2355_draw_the_absorbing_layer_map_cost.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1502_can_the_transmit_reach_the_harmonic_band.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1504_contrast_harmonic_imaging.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1507_where_each_band_puts_its_largest_sample.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1519_is_the_harmonic_tail_cumulative.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1537_does_the_cancellation_work.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1612_does_a_sparse_bubble_cloud_localize.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1619_calibrate_the_bubble_scatter_range.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1622_draw_the_sweep_b_modes.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1654_where_the_bright_region_sits.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1701_where_one_bubble_images.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1702_does_the_portable_example_reproduce_the_evaluation.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1753_does_the_ported_solver_still_march_the_same.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1755_do_the_ranks_agree_on_the_bubbles.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1817_does_the_air_region_reach_every_solver.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260901_1818_does_the_air_region_reach_every_rank.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260902_1317_what_the_abdomen_volume_holds.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260902_1319_art_wave_in_the_abdomen.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260902_1324_art_figures_from_the_field.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260902_1744_art_final_animations.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260904_1216_art_wire_visibility.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260904_2327_b_mode_of_the_abdomen.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260905_0038_two_against_three_dimensions.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260909_1934_probe_the_reverberation_filter_on_speckle.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260909_1939_probe_the_ideal_profile_on_speckle.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260909_2044_probe_the_correction_bases_on_a_flat_speckle.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260909_2342_measure_the_wall_delay_screen.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1134_find_the_first_non_finite_value_of_the_plane_wave_correction.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1234_print_the_summaries_of_analyses.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/a_maps_checks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/b_effective_frequency.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/c_composed_steps.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/check_element_centers.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/check_plane_wave_law.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/d2_gate_quarter_period.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/d_gate_cases.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/e_plate_crash.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/f_patch_grid.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/g_frequency_scan.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/gouy_check.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/h_pulse_and_wires.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/i_phase_slope.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a0200910_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a0200910_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a0200910_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a0200910_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a0200910_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_006.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_007.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_008.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_009.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a16f7afd_010.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a3e03828_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a3e03828_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a3e03828_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a3e03828_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a3e03828_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_006.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_007.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_2a8c46c5-7d0_a86c0b02_008.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_47c5d913-2f0_a86c7568_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_006.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_007.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_008.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a09af998_009.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_006.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a3c684a5_007.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_001.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_002.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_003.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_004.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_005.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_006.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_007.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_008.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_009.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_010.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_011.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_012.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_013.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_014.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_015.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/inline_commands/wf_714887dd-b46_a99b1764_016.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/instrumented_guard_run.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/instrumented_plane_wave_nan.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/j_wires_in_wall.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/origin_checks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/origin_das.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/quick_checks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/scan_checks.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/scan_checks_2.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/scan_checks_3.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/scan_checks_4.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1239_matrix_imaging_agent_checks/toy_plane_wave.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1241_check_that_the_records_load.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1241_locate_the_non_finite_values_of_an_analysis.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1241_measure_the_wire_depth_against_the_time_origin.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1241_print_the_time_origin_scans.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1241_test_the_entropy_the_antisymmetry_and_the_filter_linearity.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1247_time_the_phase_reversal_per_patch_against_batched.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1248_compare_the_synthesis_on_the_card_and_the_host.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1250_save_and_compare_the_local_laws_of_a_subset.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1254_analyze_the_2.5mhz_stage_2.sh +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1256_print_the_per_depth_curves_of_a_reverberation_test.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1302_print_the_durations_of_a_chain_log.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1314_print_the_gouy_points_of_a_speed_of_sound_scan.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1445_fit_the_moveout_speed_of_the_point_sources.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1456_fit_the_depth_offset_of_the_reference_arrivals.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1502_print_the_laws_and_gates_of_an_analysis.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1503_analyze_with_the_ramp_removal_off_or_every_law_one.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1504_measure_the_anti_diagonal_share_of_the_clean_clutter.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1508_estimate_the_speed_with_smaller_windows.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1553_focus_the_clutter_records_in_sub_bands.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1553_print_the_band_and_the_screens_of_the_records.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1554_predict_the_phase_speed_of_the_stencil.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1556_compute_the_moveout_truth_with_an_eikonal_solver.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1556_fit_the_height_offset_on_three_grids.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1600_measure_the_flat_and_the_screen_correction_on_point_targets.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1604_predict_the_arrivals_from_the_stencil.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1604_prototype_the_broadband_correction.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1605_scan_the_time_origin_with_other_element_planes.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1607_measure_the_local_far_field_structure_of_the_clutter.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1608_score_the_speed_scans_against_the_eikonal_truth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1610_compare_the_unwrapped_law_delays_with_the_truth.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1611_compare_the_arrival_models_at_the_point_sources.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1613_compare_the_sensor_pixel_with_the_element_center.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1613_measure_the_order_of_the_clutter_from_the_contrast_scaling.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1613_scan_the_speed_of_sound_at_one_time_origin.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1615_locate_the_source_and_sensor_pixels_of_the_array.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1617_check_the_data_level_correction_and_the_pulse_band.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1617_test_local_and_notch_filters_on_the_clutter.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1621_compute_the_moveout_truth_with_a_chosen_arrival_model.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1622_measure_the_tilt_spread_of_the_wall_interfaces.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1623_tabulate_the_broadband_correction_runs.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1626_measure_the_ghost_depths_of_the_plate.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1626_score_the_product_of_the_transducer_laws.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1630_test_the_local_filter_with_padded_windows.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1632_fit_the_time_origin_and_the_speed_of_the_phantom_scans.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1634_fit_the_reference_speed_at_the_sensor_row.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1646_split_the_clutter_into_orders_and_predict_its_floor.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1652_tabulate_the_pulse_band_check.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260910_1659_check_the_plane_wave_transform_of_the_elements.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1103_compare_two_b_mode_roots.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1214_binary_cache_lookup_order.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1253_signal_of_old_against_new.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1300_read_the_numexpr_pool_limit.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1332_gpu_memory_of_the_relaxation_kernel.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1401_relaxation_regions_against_the_kernel.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1420_released_binary_carries_the_fix.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1601_stable_courant_number_of_the_stencil_table.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1602_fast_layer_in_a_small_3d_run.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1719_p4_1_pulse_energy_above_the_trace_nyquist.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1933_l7_4_run_at_8_ppw_stability_and_trace_sampling.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_1946_l7_4_trace_sampling_by_field_modulus.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2004_pulse_bands_of_the_two_abdomen_arrays.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2155_cuda_version_choice_before_and_after.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2155_gpu_architectures_in_cached_binaries.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2155_mutation_check_of_the_cuda_version_tests.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2155_verify_the_cuda_version_test_findings.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260916_2212_mutation_check_of_the_blackwell_version_choice.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260925_1321_selected_cuda_tag.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260925_1326_release_binary_bit_identity.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/scratch/20260925_1331_summarize_release_check.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/settings.local.json +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/convex_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/linear_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/matrix_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/patch.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/ruff.toml +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/superseded/convex_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/superseded/linear_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/superseded/matrix_transducer_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/three_dimensional/test_patch.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/transducer_stack/README.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/transducer_stack/superseded/full_synthetic_aperture_with_stack.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.claude/transducer_stack/superseded/plane_wave_compounding_with_stack.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.gitignore +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.pre-commit-config.yaml +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/.python-version +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/20260120_0844.log +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/CITATION.cff +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/LICENSE +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/Makefile +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/attenuation.png +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/beamformer.m +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/docker/Dockerfile +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/docker/Dockerfile.dockerignore +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/docker/README.md +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/experiments/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/experiments/exp_scatterer_diameter.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/constants/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/constants/material_properties.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/domain.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/medium_builder.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/data/.keep +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/domain_background.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/domain_simple.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/sensor.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513.mat +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513_invalid_cells.json +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456.mat +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456_invalid_cells.json +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/cuda_utils.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/launcher.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/shipped_database.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/solver/utils.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/transmit.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/dat_io_3d.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/errors.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/stencil.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/ultra/time_axis.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/__init__.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/check_functions.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/coordinates.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/gpu_memory.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/memory_tempfile.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/numerical.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/pulse.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/relaxation_parameters.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/scatterer.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/fullwave/utils/signal_process.py +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/logs/20260815_0356_sweep.log +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/ruff.log +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/ruff.toml +0 -0
- {fullwave25-1.4.2.dev3 → fullwave25-1.4.3.dev0}/signal_filter_example.png +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fullwave25
|
|
3
|
+
description: How to set up, run and check a Fullwave 2.5 (fullwave25) ultrasound simulation in the standard way - the basic grid, medium, source, sensor and solver flow first, then transducers, transmit batches, beamforming, multi-GPU runs and the container. Use when writing, reviewing or debugging any code that builds a fullwave.Grid, fullwave.Medium, fullwave.Source, fullwave.Sensor, fullwave.Transducer or fullwave.Solver.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fullwave 2.5 simulations
|
|
7
|
+
|
|
8
|
+
Follow this workflow and these conventions, so every simulation built with fullwave25 reads the same way and avoids the known pitfalls. Start from the basic flow below, or from the closest example in `examples/` (the README lists them), not from an empty file.
|
|
9
|
+
|
|
10
|
+
## Basic flow: grid, medium, source, sensor, solver
|
|
11
|
+
|
|
12
|
+
Every simulation has these five steps. This one sends a plane wave from the top row into a medium with an inclusion and records the pressure in every cell (`examples/simple_plane_wave/simple_plane_wave.py` is the complete version).
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
import fullwave
|
|
18
|
+
from fullwave.utils.coordinates import map_to_coords
|
|
19
|
+
|
|
20
|
+
# 1. Grid: (depth, lateral) in metres; depth is the first axis.
|
|
21
|
+
f0 = 3e6 # Hz
|
|
22
|
+
c0 = 1540 # m/s
|
|
23
|
+
domain_size = (3e-2, 2e-2)
|
|
24
|
+
duration = domain_size[0] / c0 * 2 # s
|
|
25
|
+
grid = fullwave.Grid(domain_size=domain_size, f0=f0, duration=duration, c0=c0)
|
|
26
|
+
|
|
27
|
+
# 2. Medium: one map per property, each with the grid shape.
|
|
28
|
+
shape = (grid.nx, grid.ny)
|
|
29
|
+
sound_speed = np.full(shape, 1540.0) # m/s
|
|
30
|
+
density = np.full(shape, 1000.0) # kg/m^3
|
|
31
|
+
alpha_coeff = np.full(shape, 0.5) # dB/(MHz^y cm)
|
|
32
|
+
alpha_power = np.full(shape, 1.0) # -
|
|
33
|
+
beta = np.zeros(shape) # -
|
|
34
|
+
inclusion = (slice(grid.nx // 3, 2 * grid.nx // 3), slice(grid.ny // 3, 2 * grid.ny // 3))
|
|
35
|
+
sound_speed[inclusion] = 1600.0
|
|
36
|
+
alpha_coeff[inclusion] = 0.75
|
|
37
|
+
alpha_power[inclusion] = 1.1
|
|
38
|
+
medium = fullwave.Medium(
|
|
39
|
+
grid=grid,
|
|
40
|
+
sound_speed=sound_speed,
|
|
41
|
+
density=density,
|
|
42
|
+
alpha_coeff=alpha_coeff,
|
|
43
|
+
alpha_power=alpha_power,
|
|
44
|
+
beta=beta,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 3. Source: the cells that emit, and one signal per cell.
|
|
48
|
+
source_mask = np.zeros(shape, dtype=bool)
|
|
49
|
+
source_mask[0, :] = True
|
|
50
|
+
source_coords = map_to_coords(source_mask)
|
|
51
|
+
pulse = fullwave.utils.pulse.gaussian_modulated_sinusoidal_signal(
|
|
52
|
+
nt=grid.nt,
|
|
53
|
+
duration=duration,
|
|
54
|
+
ncycles=2,
|
|
55
|
+
drop_off=2,
|
|
56
|
+
f0=f0,
|
|
57
|
+
p0=1e5,
|
|
58
|
+
)
|
|
59
|
+
source = fullwave.Source(
|
|
60
|
+
p0=np.tile(pulse, (source_coords.shape[0], 1)),
|
|
61
|
+
coords=source_coords,
|
|
62
|
+
grid_shape=grid.shape,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# 4. Sensor: where and how often the pressure is recorded.
|
|
66
|
+
sensor = fullwave.Sensor(mod_x=1, mod_y=1, sampling_modulus_time=7)
|
|
67
|
+
|
|
68
|
+
# 5. Solver.
|
|
69
|
+
solver = fullwave.Solver(
|
|
70
|
+
work_dir="./outputs/simple_plane_wave",
|
|
71
|
+
grid=grid,
|
|
72
|
+
medium=medium,
|
|
73
|
+
source=source,
|
|
74
|
+
sensor=sensor,
|
|
75
|
+
source_type="soft", # the signal is added to the field
|
|
76
|
+
)
|
|
77
|
+
sensor_output = solver.run()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Plot the medium (`medium.plot(export_path=...)`) before the run, and the pressure field after it (`fullwave.utils.plot_utils`, with `**plot_utils.mm_axes_kwargs(grid)` for mm axes).
|
|
81
|
+
|
|
82
|
+
## The output: shapes and sensor order
|
|
83
|
+
|
|
84
|
+
- `solver.run()` returns `sensor_output` with shape `[n_sensors, n_samples]`. `solver.run_batch(sources)` returns `[n_transmits, n_sensors, n_samples]`.
|
|
85
|
+
- **Time axis.** One sample every `sampling_modulus_time` solver steps: the sample interval is `grid.dt * sampling_modulus_time`, and `n_samples` is `grid.nt / sampling_modulus_time`, rounded.
|
|
86
|
+
- **Sensor axis: the pitfall.** Row `i` is the grid cell `sensor.outcoords[i]`, not the i-th element and not the i-th lateral position. A sensor built from a mask lists its cells in C (row-major) order: all lateral cells of the first depth row, then the next depth row, and in 3D the elevation index changes fastest. A mask that covers more than one depth row therefore interleaves the rows. Use `sensor.outcoords` to place each row, or keep the sensor to one row per depth.
|
|
87
|
+
- **Whole domain.** With `Sensor(mod_x=1, mod_y=1, ...)` every cell is recorded; `fullwave.utils.signal_process.reshape_whole_sensor_to_nt_nx_ny(sensor_output, grid)` turns `sensor_output` into `[n_samples, nx, ny]` (`reshape_whole_sensor_to_nt_nx_ny_nz` in 3D).
|
|
88
|
+
- **Transducer.** The rows of `sensor_output` are the grid cells of the receiving element surfaces, not the elements. `rf_data = transducer.post_process_sensor_output(sensor_output)` sorts and averages them into the RF data, `[n_elements, n_samples]`, in element order; for a batch, call it once per transmit. The beamformers take `rf_data` in this element order.
|
|
89
|
+
|
|
90
|
+
## Scatterers for speckle
|
|
91
|
+
|
|
92
|
+
Tissue speckle comes from sub-resolution scatterers: random variations of the density. `fullwave.utils.generate_scatterer` returns a map of values centred on 1, which multiplies the density map before the `Medium` is built:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
rng = np.random.default_rng(seed=42)
|
|
96
|
+
scatterer, _ = fullwave.utils.generate_scatterer(
|
|
97
|
+
grid=grid,
|
|
98
|
+
ratio_scatterer_to_total_grid=0.38, # fraction of the cells that carry a scatterer
|
|
99
|
+
scatter_value_std=0.02, # standard deviation of the density variation
|
|
100
|
+
rng=rng,
|
|
101
|
+
)
|
|
102
|
+
density *= scatterer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The density standard deviation sets the echo strength of a tissue, and so the speckle brightness of the B-mode image. The values above are a starting point only: calibrate the fraction and the standard deviation of each tissue to your imaging system or target, for example by matching the speckle brightness of a measured phantom or tissue B-mode image. Leave the scatterers out for an anechoic region such as a cyst or a vessel. A `MediumBuilder` domain carries its own scatterers (`ScattererDomain`, or `domain.density *= scatterer` on a preset domain). Fix the seed of `rng` to repeat a speckle pattern.
|
|
106
|
+
|
|
107
|
+
## Debug at a low frequency first
|
|
108
|
+
|
|
109
|
+
Build and check a new simulation at a low `f0`, then raise it. The grid spacing follows the wavelength, so halving `f0` halves the number of points along each axis and the number of time steps: the grid has 1/4 of the cells in 2D and 1/8 in 3D, and the run does 1/8 (2D) or 1/16 (3D) of the cell updates. Check the geometry, the medium and the source at the low frequency, then run the target frequency once. Build a simulation in 2D before its 3D version for the same reason.
|
|
110
|
+
|
|
111
|
+
## Rules
|
|
112
|
+
|
|
113
|
+
- **Grid resolution.** Use `ppw=12`, the default and the recommended value. `ppw=10` also works. `ppw=8` is the minimum: the attenuation and the dispersion are validated at 8; do not go below it. Keep `cfl` at 0.2 unless you have checked another value.
|
|
114
|
+
- **Units.** SI everywhere (m, s, Hz, Pa, kg/m³, m/s), except `alpha_coeff` in dB/(MHz^y cm).
|
|
115
|
+
- **Axis order.** Depth first: (depth, lateral) in 2D and (depth, lateral, elevation) in 3D. Source and sensor coordinates are in C (row-major) order.
|
|
116
|
+
- **Attenuation range.** `alpha_coeff` up to 1.0 dB/(MHz^y cm) and `alpha_power` from 0.4 to 2.0, from 0.1 to 20 MHz.
|
|
117
|
+
- **Attenuation values are discretized.** The relaxation parameters come from a table, not from a fit per voxel. The table holds `alpha_coeff` = 0.0022, then 0.01 to 1.00 in steps of 0.01, and `alpha_power` = 0.4 to 2.0 in steps of 0.1. Each voxel is served the first table level at or above its request, without interpolation: a request of 0.505 runs as 0.51, and an exponent of 1.05 runs as 1.1. Values outside the table are clipped to its ends, with a warning. Pick values on the table levels when the exact value matters, and expect a smooth map to give an attenuation in steps. `Medium(..., scale_to_requested_alpha_coeff=True)` scales the attenuation back to the requested coefficient; the exponent stays on its level.
|
|
118
|
+
- **Source type.** `"soft"` adds the signal to the field; `"hard"` assigns it for the whole run, so the source cells reflect after the drive ends; `"hard_released_after"` assigns it while the drive plays.
|
|
119
|
+
- **Signal processing is a choice.** `run` returns the unfiltered `sensor_output`. Filtering (`highpass_cutoff_mhz`, `bandpass_cutoff_mhz`) and the dynamic range of a B-mode image are decisions to state, not defaults to add without saying so.
|
|
120
|
+
- **Duration and pulse.** Set `duration` to cover the round trip: `2 * depth / c0` for echoes from the bottom of the domain, plus the pulse length. The pulse is set by its center frequency, its number of cycles and its peak pressure (`ncycles` and `p0` of `gaussian_modulated_sinusoidal_signal`, or `fullwave.Pulse(pressure=..., cycles=...)` for a transducer). With `beta = 0` the simulation is linear and the peak pressure only scales the output; with `beta > 0` the peak pressure sets the strength of the nonlinear effects, so give it a physical value.
|
|
121
|
+
- **Reproducibility.** Pin the fullwave25 version in your environment. The solver binaries come from the release pinned in the package (`BINARY_RELEASE_TAG`); `FULLWAVE25_BINARY_TAG` selects another release. Fix the seeds of every random generator (scatterers, phantoms).
|
|
122
|
+
|
|
123
|
+
## Air
|
|
124
|
+
|
|
125
|
+
Cells marked as air hold zero pressure, so they reflect the wave as a pressure-release boundary, like a gas pocket or a probe backed by air. Pass a boolean map with the grid shape. The solver ignores the sound speed and the density of the air cells, so you do not need to set air values in `sound_speed` or `density`:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
air_map = np.zeros(grid.shape, dtype=bool)
|
|
129
|
+
air_map[air_region] = True
|
|
130
|
+
medium = fullwave.Medium(
|
|
131
|
+
grid=grid,
|
|
132
|
+
sound_speed=sound_speed,
|
|
133
|
+
density=density,
|
|
134
|
+
alpha_coeff=alpha_coeff,
|
|
135
|
+
alpha_power=alpha_power,
|
|
136
|
+
beta=beta,
|
|
137
|
+
air_map=air_map,
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`air_coords=` takes the coordinates instead of a map. For the curved `c5_2v`, `transducer.make_surface_reflective_with_air()` returns the air map behind the array face. `examples/simple_plane_wave/simple_plane_wave_air.py` and `examples/wave_3d/simple_plane_wave_3d_with_air.py` scatter single air cells inside an inclusion.
|
|
142
|
+
|
|
143
|
+
## Run in memory for speed
|
|
144
|
+
|
|
145
|
+
`Solver(..., run_on_memory=True)` keeps the input files and the recorded field in a RAM directory (`/run/user/<uid>`) instead of on the disk, which makes the run faster. It needs RAM for the whole record. Before a run, check that the free space of `/run/user/<uid>` and the free memory of the machine exceed the size of the record (next section). With `run_on_memory=False`, the run writes its files, including the record `genout.dat`, under `work_dir`; point `work_dir` at a disk with room for them. A static map (`is_static_map=True`) needs the files on the disk.
|
|
146
|
+
|
|
147
|
+
## Size of the record
|
|
148
|
+
|
|
149
|
+
Estimate the record before a run: `n_sensors * n_samples * 4` bytes, and Python needs one or two more copies of it while it loads and plots. A transducer sensor records one or a few rows of cells. A whole-domain record grows with every cell: the 2D C5-2V abdominal wall example (`examples/convex_transducer/convex_transducer_abdominal_wall.py`) at 1 MHz peaks at 39.6 GB of RAM. To reduce the record:
|
|
150
|
+
|
|
151
|
+
- Record every second or fourth cell: `Sensor(mod_x=2, mod_y=2, sampling_modulus_time=...)` (a sparse grid), or a mask with a stride.
|
|
152
|
+
- Record fewer time samples: a larger `sampling_modulus_time`. A movie needs 50 to 100 frames, not every solver step.
|
|
153
|
+
- Check the setup at a lower frequency first (see above); the record shrinks with the grid.
|
|
154
|
+
|
|
155
|
+
## Advanced: transducers and imaging
|
|
156
|
+
|
|
157
|
+
1. **Transducer.** Take a named array, `fullwave.Transducer.l7_4(grid)`, `p4_1c(grid)` or `c5_2v(grid)`, or build one with `fullwave.TransducerGeometry`. Set the transmit with `plane_wave(angle_deg=...)`, `focus(focus_m=(depth_m, lateral_m))`, `diverging(virtual_source_m=...)` or `synthetic_aperture(element)`. Transmit positions are in metres from the top and the left edge of the grid.
|
|
158
|
+
2. **Medium with a linear array** (`l7_4`, `p4_1c`, `LinearTransducer`): make the grid wider than the array, start the tissue at `transducer.face_depth_m` (for example `start_depth=transducer.face_depth_m` in an `AbdominalWallDomain`), and pass the maps through `transducer.fill_probe_above_face(...)` (or register `transducer.probe_fill_domain()` last in a `MediumBuilder`). Measure target and focus depths from the face. The named arrays use `source_type="hard"`.
|
|
159
|
+
3. **Solver.** `fullwave.Solver(work_dir=..., grid=grid, medium=medium, transducer=transducer)`; the transducer supplies the source and the sensor.
|
|
160
|
+
4. **Transmit sequences** on one medium go through `solver.run_batch(sources)`, not a loop of `run`; it sizes each simulation to the free GPU memory. Convert each transmit's `sensor_output` to `rf_data` with `transducer.post_process_sensor_output(...)`.
|
|
161
|
+
5. **B-mode image.** `fullwave.beamformer.MachBeamformer` on a GPU (`pip install "fullwave25[mach]"`), or `fullwave.beamformer.Beamformer` on the CPU for teaching. Beamformer depths are measured from the probe face.
|
|
162
|
+
|
|
163
|
+
Check `transducer.plot_source_mask(...)` and `plot_sensor_mask(...)` before the first run.
|
|
164
|
+
|
|
165
|
+
### Building the beamformer
|
|
166
|
+
|
|
167
|
+
Build `MachBeamformer` with every argument named, so each choice is visible:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from fullwave.beamformer import MachBeamformer, face_crossing_time_s, receiver_positions_m
|
|
171
|
+
|
|
172
|
+
beamformer = MachBeamformer(
|
|
173
|
+
lateral_position_m=lateral_position_m, # B-mode columns, 0 at the array center
|
|
174
|
+
axial_position_m=axial_position_m, # B-mode rows, depth below the probe face
|
|
175
|
+
receiver_positions_m=receiver_positions_m(transducer),
|
|
176
|
+
sound_speed=grid.c0,
|
|
177
|
+
center_frequency_hz=grid.f0,
|
|
178
|
+
sampling_frequency_hz=1 / (grid.dt * transducer.sampling_modulus_time),
|
|
179
|
+
f_number=1.0, # depth / receive aperture width
|
|
180
|
+
time_zero_s=face_crossing_time_s(transducer),
|
|
181
|
+
tukey_alpha=0.5, # taper of the receive aperture
|
|
182
|
+
)
|
|
183
|
+
rf_data = transducer.post_process_sensor_output(sensor_output)
|
|
184
|
+
tx_arrival_s = beamformer.plane_wave_arrival_s(angle_deg=0.0)
|
|
185
|
+
# complex, [n_depth, n_lateral]
|
|
186
|
+
b_mode_image = beamformer.beamform(rf_data, tx_arrival_s=tx_arrival_s)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`focused_arrival_s(focus_m=...)` and `synthetic_aperture_arrival_s(element=...)` give the transmit arrival for the other transmits. `time_zero_s` is the time the pulse crosses the probe face, so set the transmit before calling `face_crossing_time_s`. Display `20 * log10(abs(b_mode_image) / abs(b_mode_image).max())` over a dynamic range you state.
|
|
190
|
+
|
|
191
|
+
## Media from presets
|
|
192
|
+
|
|
193
|
+
`fullwave.MediumBuilder` combines domains into one medium; a domain registered later overrides an earlier one where they overlap.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from fullwave import MediumBuilder, presets
|
|
197
|
+
|
|
198
|
+
background = presets.BackgroundDomain(grid=grid, background_property_name="liver")
|
|
199
|
+
abdominal_wall = presets.AbdominalWallDomain(grid=grid, start_depth=transducer.face_depth_m)
|
|
200
|
+
mb = MediumBuilder(grid=grid)
|
|
201
|
+
mb.register_domain(background)
|
|
202
|
+
mb.register_domain(abdominal_wall)
|
|
203
|
+
mb.register_domain(transducer.probe_fill_domain()) # last, so the probe fill is not overwritten
|
|
204
|
+
mb.plot_current_map(export_path="medium.png")
|
|
205
|
+
medium = mb.run()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The presets are `BackgroundDomain` (one tissue everywhere), `AbdominalWallDomain` (a layered wall), `CombinedPhantomDomain` (point targets and hypoechoic and hyperechoic cylinders) and `ScattererDomain`, next to `SimpleDomain` for your own maps.
|
|
209
|
+
|
|
210
|
+
## Multiple GPUs
|
|
211
|
+
|
|
212
|
+
- Pass the devices to the solver: `fullwave.Solver(..., cuda_device_id=[0, 1, 2, 3])` or `cuda_device_id="0,1,2,3"`. One integer or string selects one GPU; the default is GPU 0.
|
|
213
|
+
- The solver divides the depth rows of the domain equally among the GPUs (to within one row) and exchanges 8 rows at each boundary between neighbouring GPUs. The memory per GPU therefore falls with the number of GPUs, and the run time scales linearly with the number of GPUs.
|
|
214
|
+
- Every GPU holds the whole source signal matrix, so each source point and each time step of the signal costs memory on every GPU.
|
|
215
|
+
|
|
216
|
+
### Use the GPU memory estimate
|
|
217
|
+
|
|
218
|
+
Before each run, `run` and `run_batch` write an estimate of the memory of each GPU to the log (`gpu_memory_estimate=True`, the default), for example:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
GPU memory estimate [GPU 0] (relaxation mode): 0.52 GiB (depth=2189 +0 halo, lateral=1986)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Compare it with the free memory that `nvidia-smi` or `nvtop` shows before a long run. If it does not fit, reduce the grid (a lower `f0` or a smaller domain), add GPUs, or for a batch lower `max_members_per_run`. `run_batch` already lowers the number of transmits per simulation to fit the free GPU memory.
|
|
225
|
+
|
|
226
|
+
## Container
|
|
227
|
+
|
|
228
|
+
The released solver binaries need glibc 2.34 or newer (for example Ubuntu 22.04). On an older system, run fullwave25 in the container described in `docker/README.md`:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
docker build -f docker/Dockerfile -t fullwave25 .
|
|
232
|
+
docker run --rm --gpus all --user "$(id -u):$(id -g)" -v "$PWD":/work fullwave25 python my_simulation.py
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The host needs an NVIDIA driver and Docker with the NVIDIA Container Toolkit. Select GPUs inside the container with `cuda_device_id`, as outside it. Where Docker is not available, convert the image for Apptainer as `docker/README.md` describes.
|
|
236
|
+
|
|
237
|
+
## Troubleshooting
|
|
238
|
+
|
|
239
|
+
- **`GLIBC_2.34' not found`** at the start of a run: the system's glibc is older than the released binaries need; use the container.
|
|
240
|
+
- **A warning that the driver's CUDA version has no binary for the GPU architecture** and that the closest compatible version is used: the run continues with that binary.
|
|
241
|
+
- **NaN or exploding values** in `sensor_output`: check `cfl` (keep 0.2) and `ppw` (at least 8), and that the maps hold no NaN and no zero sound speed or density outside air cells.
|
|
242
|
+
- **Out of GPU memory:** read the GPU memory estimate in the log (see "Multiple GPUs").
|
|
243
|
+
- **Out of RAM, or a killed process:** see "Size of the record". A killed in-memory run can leave its directory in `/run/user/<uid>`; check the free space there and remove the leftover directories of your own runs.
|
|
244
|
+
|
|
245
|
+
## Checks before trusting a result
|
|
246
|
+
|
|
247
|
+
- The medium plot shows the intended maps (with a linear array, the tissue starts at the probe face).
|
|
248
|
+
- A short run (low frequency, short `duration`, one transmit) finishes before the full run.
|
|
249
|
+
- For a new attenuation setting, `examples/attenuation_dispersion/attenuation_dispersion.py` measures the attenuation and the phase velocity against the power law.
|
|
250
|
+
|
|
251
|
+
## Citation
|
|
252
|
+
|
|
253
|
+
Cite Fullwave 2.5 as given in the README, and also cite mach when you use `MachBeamformer`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Contributing to Fullwave 2.5
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to Fullwave 2.5.
|
|
4
|
+
|
|
5
|
+
## Branches and pull requests
|
|
6
|
+
|
|
7
|
+
- Create a branch named `TYPE/BRANCH_NAME`. `TYPE` is one of `feature`, `bugfix`, `hotfix`, `docs`, `refactor`, `release`, `test` or `experiment`; `BRANCH_NAME` says what the branch does.
|
|
8
|
+
- Base the branch on `develop` and keep it up to date with `develop`.
|
|
9
|
+
- Open the pull request against `develop`. After review, `develop` is merged into `main` for a release.
|
|
10
|
+
- Write clear, concise commit messages.
|
|
11
|
+
|
|
12
|
+
## Code
|
|
13
|
+
|
|
14
|
+
- Add tests for new features and bug fixes.
|
|
15
|
+
- Install the pre-commit hooks; `make install` does this for you:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pre-commit install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- [Ruff](https://docs.astral.sh/ruff/) checks and formats the code before each commit. If a rule gets in the way, say so in your pull request.
|
|
22
|
+
|
|
23
|
+
## New to the tools?
|
|
24
|
+
|
|
25
|
+
- [Visual Studio Code documentation](https://code.visualstudio.com/docs) and [source control in VS Code](https://code.visualstudio.com/docs/sourcecontrol/overview)
|
|
26
|
+
- [Git tutorial (W3Schools)](https://www.w3schools.com/git/default.asp)
|
|
27
|
+
- [uv documentation](https://docs.astral.sh/uv/)
|
|
28
|
+
- [Connecting to GitHub with SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh)
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fullwave25
|
|
3
|
+
Version: 1.4.3-dev0
|
|
4
|
+
Summary: Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities
|
|
5
|
+
Author: Masashi Sode, Gianmarco Pinton
|
|
6
|
+
Maintainer: Masashi Sode
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: fdtd,finite-difference,simulation,ultrasound
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: cupy-cuda12x>=13.6.0
|
|
17
|
+
Requires-Dist: matplotlib>=3.10.7
|
|
18
|
+
Requires-Dist: numba>=0.63.1
|
|
19
|
+
Requires-Dist: numexpr>=2.14.1
|
|
20
|
+
Requires-Dist: numpy>=2.0.0
|
|
21
|
+
Requires-Dist: opencv-python-headless>=4.12.0.88
|
|
22
|
+
Requires-Dist: pillow>=12.3.0
|
|
23
|
+
Requires-Dist: scipy>=1.13.1
|
|
24
|
+
Requires-Dist: tomli>=2.3.0
|
|
25
|
+
Requires-Dist: tqdm>=4.67.1
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: line-profiler>=5.0.1; extra == 'dev'
|
|
28
|
+
Requires-Dist: pre-commit==4.6.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.3.5; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.12.9; extra == 'dev'
|
|
31
|
+
Provides-Extra: examples
|
|
32
|
+
Requires-Dist: cupy-cuda12x>=13.6.0; extra == 'examples'
|
|
33
|
+
Requires-Dist: ipykernel>=6.28.0; extra == 'examples'
|
|
34
|
+
Requires-Dist: jupyter>=1.0.0; extra == 'examples'
|
|
35
|
+
Requires-Dist: mach-beamform>=0.0.4; extra == 'examples'
|
|
36
|
+
Requires-Dist: pymust>=0.1.8; extra == 'examples'
|
|
37
|
+
Provides-Extra: mach
|
|
38
|
+
Requires-Dist: cupy-cuda12x>=13.6.0; extra == 'mach'
|
|
39
|
+
Requires-Dist: mach-beamform>=0.0.4; extra == 'mach'
|
|
40
|
+
Requires-Dist: pymust>=0.1.8; extra == 'mach'
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: pytest; extra == 'test'
|
|
43
|
+
Requires-Dist: tomli>=2.3.0; extra == 'test'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# Fullwave 2.5
|
|
47
|
+
|
|
48
|
+
**Ultrasound wave propagation simulation with heterogeneous power-law attenuation**
|
|
49
|
+
|
|
50
|
+
<a href="https://doi.org/10.5281/zenodo.17625780"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.17625780.svg" alt="DOI"></a>
|
|
51
|
+
<a href="https://pypi.org/project/fullwave25/"><img src="https://img.shields.io/pypi/v/fullwave25" alt="PyPI version"></a>
|
|
52
|
+
<a href="https://pepy.tech/projects/fullwave25"><img src="https://static.pepy.tech/personalized-badge/fullwave25?period=total&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=BLUE&left_text=PyPI+Downloads" alt="PyPI Downloads"></a>
|
|
53
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/pinton-lab/fullwave25" alt="License"></a>
|
|
54
|
+
[](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/simple_plane_wave/example_simple_plane_wave.ipynb)
|
|
55
|
+
|
|
56
|
+
Fullwave 2.5 simulates nonlinear ultrasound propagation in tissue whose attenuation follows a power law $\alpha_0 f^\gamma$ **with a coefficient $\alpha_0$ and an exponent $\gamma$ that can differ in every grid cell**, together with the dispersion that the Kramers-Kronig relations give for each cell. It runs 2D and 3D simulations on multiple GPUs from a Python interface.
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="figs/l7_4_abdomen_3d.gif" width="360" alt="3D simulation of a focused L7-4 transmit through an abdominal wall"/>
|
|
60
|
+
</p>
|
|
61
|
+
<p align="center"><em>3D simulation of a focused transmit from an L7-4 linear array through an abdominal wall. L7-4 CAD model: <a href="https://grabcad.com/library/ultrasound-probes-1">Ultrasound probes</a> by Amirhasan Qadamian (GrabCAD).</em></p>
|
|
62
|
+
|
|
63
|
+
| Medium: abdominal wall and liver (2D) | Focused transmit from an L7-4 array |
|
|
64
|
+
| ---------------------------------------- | -------------------------------------------------------------------------- |
|
|
65
|
+
| <img src="figs/medium.png" width="300"/> | <img src="figs/linear_transducer_focused_abdominal_wall.gif" width="200"/> |
|
|
66
|
+
|
|
67
|
+
## Highlights
|
|
68
|
+
|
|
69
|
+
- **Heterogeneous power-law attenuation.** Multiple relaxation processes give the attenuation and the dispersion of $\alpha_0 f^\gamma$ for a map of $\alpha_0$ and a map of $\gamma$.
|
|
70
|
+
- **High-order FDTD.** Staggered-grid pressure-velocity scheme, 8th order in space and 4th order in time, with a relaxing perfectly matched layer.
|
|
71
|
+
- **2D and 3D, multi-GPU.** The 3D domain is split along depth across GPUs.
|
|
72
|
+
- **Transducers ready to use.** L7-4 linear, C5-2V curved and P4-1C phased arrays, or any geometry, with plane wave, focused, diverging and synthetic aperture transmits.
|
|
73
|
+
- **Transmit batches.** Several transmits on one medium run in one simulation.
|
|
74
|
+
- **Beamforming.** A delay-and-sum `Beamformer` on the CPU, and `MachBeamformer` on the GPU, built on [mach](https://github.com/Forest-Neurotech/mach).
|
|
75
|
+
- **Familiar interface.** The workflow follows [k-Wave](http://www.k-wave.org/) and [k-wave-python](https://github.com/waltsims/k-wave-python).
|
|
76
|
+
|
|
77
|
+
## Attenuation model
|
|
78
|
+
|
|
79
|
+
The solver advances the pressure $p$ and the particle velocity $\mathbf{v}$ with
|
|
80
|
+
|
|
81
|
+
$$\nabla_1 p + \rho \cfrac{\partial {\bf{v}}}{\partial t} = 0, \qquad \nabla_2 \cdot {\bf{v}} + \kappa \cfrac{\partial p}{\partial t} = 0 .$$
|
|
82
|
+
|
|
83
|
+
in which the spatial derivatives $\nabla_1$ and $\nabla_2$ carry a set of relaxation mechanisms. fullwave25 looks up the parameters of these mechanisms in a precomputed table for the $\alpha_0$ and $\gamma$ of each cell, so that the simulated attenuation matches the power law and the phase velocity matches its Kramers-Kronig dispersion. The table spans $\alpha_0$ up to 1.0 dB/(MHz^γ cm) and $\gamma$ from 0.4 to 2.0, for frequencies from 0.1 to 20 MHz.
|
|
84
|
+
|
|
85
|
+
The figure compares the target attenuation $\alpha_0 f^\gamma$ (red lines) with the simulated attenuation (black dots) for $\alpha_0 =$ 0.25, 0.5 and 0.75 dB/(MHz^γ cm) and $\gamma =$ 0.4, 0.7, 1.0, 1.3 and 1.6. Each dot is one simulation of a plane wave through a uniform medium, read at two depths.
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
[attenuation_dispersion.py](examples/attenuation_dispersion/attenuation_dispersion.py) makes these measurements, so you can check the model for your own parameters.
|
|
90
|
+
|
|
91
|
+
## Installation
|
|
92
|
+
|
|
93
|
+
Requirements: Linux (on Windows, use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install)) and an NVIDIA GPU. A 3D simulation can need several GPUs.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
pip install fullwave25
|
|
97
|
+
pip install "fullwave25[mach]" # optional: MachBeamformer (mach-beamform, pymust, cupy)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The released solver binaries need glibc 2.34 or newer (for example Ubuntu 22.04). On an older system, use the container in [docker/README.md](docker/README.md).
|
|
101
|
+
|
|
102
|
+
The plotting functions write videos with `ffmpeg` and OpenCV (`cv2`). If they are missing, install them with your package manager:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
sudo apt install ffmpeg python3-opencv # Ubuntu/Debian; or: pip install opencv-python
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Development installation
|
|
109
|
+
|
|
110
|
+
We use [uv](https://docs.astral.sh/uv/) to manage the environment.
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # if uv is not installed
|
|
114
|
+
git clone git@github.com:pinton-lab/fullwave25.git
|
|
115
|
+
cd fullwave25
|
|
116
|
+
make install-all-extras # core library, examples and development tools
|
|
117
|
+
make test
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Quick start
|
|
121
|
+
|
|
122
|
+
Start with the notebook [example_simple_plane_wave.ipynb](examples/simple_plane_wave/example_simple_plane_wave.ipynb), or run it in Google Colab without a local GPU: [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/simple_plane_wave/example_simple_plane_wave.ipynb)
|
|
123
|
+
|
|
124
|
+
A simulation has five steps:
|
|
125
|
+
|
|
126
|
+
1. Define the computational grid (`fullwave.Grid`).
|
|
127
|
+
2. Define the acoustic medium (`fullwave.Medium`): sound speed, density, attenuation coefficient and exponent, nonlinearity.
|
|
128
|
+
3. Define the source (`fullwave.Source`) or a transducer (`fullwave.Transducer`).
|
|
129
|
+
4. Define the sensor (`fullwave.Sensor`).
|
|
130
|
+
5. Run the solver (`fullwave.Solver`).
|
|
131
|
+
|
|
132
|
+
To start your own simulation, copy [simple_plane_wave.py](examples/simple_plane_wave/simple_plane_wave.py) (2D) or [simple_plane_wave_3d.py](examples/wave_3d/simple_plane_wave_3d.py) (3D) and change it.
|
|
133
|
+
|
|
134
|
+
### Simulation guide
|
|
135
|
+
|
|
136
|
+
[.claude/skills/fullwave25/SKILL.md](.claude/skills/fullwave25/SKILL.md) is a short guide to setting up a simulation the standard way. It covers:
|
|
137
|
+
|
|
138
|
+
- the basic flow, the output shapes and the sensor order;
|
|
139
|
+
- scatterers, air and the discretized attenuation values;
|
|
140
|
+
- grid resolution, duration, the size of the record and running in memory;
|
|
141
|
+
- transducers, presets and beamforming;
|
|
142
|
+
- multi-GPU runs, the container and troubleshooting.
|
|
143
|
+
|
|
144
|
+
Read it before writing a new simulation. The same file is a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill: Claude Code loads it in a clone of this repository, and you can copy the folder to `~/.claude/skills/fullwave25/` to use it in your own projects.
|
|
145
|
+
|
|
146
|
+
## Transducers and transmits
|
|
147
|
+
|
|
148
|
+
A named array places itself on the grid, and one call sets the transmit.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
grid = fullwave.Grid(domain_size=(4.5e-2, 4.5e-2), f0=5.208e6, duration=6.7e-5, c0=1540)
|
|
152
|
+
|
|
153
|
+
transducer = fullwave.Transducer.l7_4(grid)
|
|
154
|
+
transducer.plane_wave(angle_deg=2.5)
|
|
155
|
+
maps = transducer.fill_probe_above_face(
|
|
156
|
+
sound_speed=sound_speed_map, density=density_map, alpha_coeff=alpha_coeff_map, alpha_power=alpha_power_map,
|
|
157
|
+
)
|
|
158
|
+
medium = fullwave.Medium(grid, beta=beta_map, **maps)
|
|
159
|
+
|
|
160
|
+
solver = fullwave.Solver(work_dir="./outputs/my_run", grid=grid, medium=medium, transducer=transducer)
|
|
161
|
+
sensor_output = solver.run() # [n_elements, n_t]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Array | Type | Elements | Aperture | Center frequency |
|
|
165
|
+
| ------- | ------------- | -------- | ------------------------------- | ---------------- |
|
|
166
|
+
| `l7_4` | linear | 128 | 38.09 mm | 5.208 MHz |
|
|
167
|
+
| `p4_1c` | linear phased | 64 | 27 mm | `grid.f0` |
|
|
168
|
+
| `c5_2v` | curved | 128 | 0.508 mm pitch, 49.57 mm radius | 3.7 MHz |
|
|
169
|
+
|
|
170
|
+
`fullwave.TransducerGeometry` builds any other array. The transmits are `plane_wave(angle_deg=...)`, `focus(focus_m=...)`, `diverging(virtual_source_m=...)` and `synthetic_aperture(element)`; each takes `apodization` and `pulse`. To transmit with part of the aperture, set `transducer.active_source_elements` first.
|
|
171
|
+
|
|
172
|
+
### Linear array layout
|
|
173
|
+
|
|
174
|
+
The linear arrays (`l7_4`, `p4_1c` and `LinearTransducer`) share one layout, from the top of the grid:
|
|
175
|
+
|
|
176
|
+
| Rows | Content |
|
|
177
|
+
| ----------------------------- | ----------------------------------------------------------------------------------------- |
|
|
178
|
+
| from row 0 | source: one wavelength of rows (at least 8), with the absorbing layer directly behind it |
|
|
179
|
+
| next row | receiver |
|
|
180
|
+
| `transducer.face_row_px` | probe face, depth 0 (`transducer.face_depth_m`): the medium starts here |
|
|
181
|
+
|
|
182
|
+
The array is centered laterally, so make the grid wider than the array (for example 45 mm for the 38.09 mm L7-4). `transducer.fill_probe_above_face(...)` sets the rows above the face to a probe fill (1540 m/s, 1000 kg/m³, 0.005 dB/(MHz cm), power 1.0) across the array and to water beside it, without scatterers. Pass its result to `fullwave.Medium`, or register `transducer.probe_fill_domain()` last in a `MediumBuilder`.
|
|
183
|
+
|
|
184
|
+
`Transducer.l7_4` gives every gap between two elements exactly one pixel (`kerf_px=1`) and holds those kerf pixels at zero pressure after the drive (`hold_kerf=True`); `kerf_px=None, hold_kerf=False` gives an array without held kerfs. A held kerf needs `source_type="hard"`.
|
|
185
|
+
|
|
186
|
+
Start the tissue at the face (for an `AbdominalWallDomain`, pass `start_depth=transducer.face_depth_m`) and measure target and focus depths from it. The curved `c5_2v` keeps its own layout; `transducer.make_surface_reflective_with_air()` returns the air behind it. `transducer.apply_transducer_stack(...)` paints a backing, a matching layer, a lens and a standoff (`fullwave.TransducerStack`) onto the medium maps before the `Medium` is built.
|
|
187
|
+
|
|
188
|
+
### Source types
|
|
189
|
+
|
|
190
|
+
| `source_type` | Behavior |
|
|
191
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
192
|
+
| `"hard"` (named arrays) | assigns the source pressure for the whole run; after the drive ends the source cells stay at 0 Pa, so the face reflects |
|
|
193
|
+
| `"hard_released_after"` | assigns the source pressure while the drive plays; then the source cells evolve freely |
|
|
194
|
+
| `"soft"` | adds the signal to the field |
|
|
195
|
+
|
|
196
|
+
Pass `source_type=` to `Transducer.l7_4`, `c5_2v`, `p4_1c` or `fullwave.Solver`.
|
|
197
|
+
|
|
198
|
+
### Transmit batches
|
|
199
|
+
|
|
200
|
+
A transmit sequence on one medium, such as plane wave angles, focused lines or synthetic aperture elements, can run as a batch: several transmits in one simulation, which is faster than one `run` per transmit. The transmits of a batch share the medium, the sensor and the source positions, and differ only in their signals; the transmits of one transducer satisfy this.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
sources = []
|
|
204
|
+
for angle in (-10, -5, 0, 5, 10):
|
|
205
|
+
transducer.plane_wave(angle_deg=angle)
|
|
206
|
+
sources.append(transducer.source)
|
|
207
|
+
|
|
208
|
+
sensor_output = solver.run_batch(sources) # [n_transmits, n_sensors, n_t]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`run_batch` puts at most `max_members_per_run` transmits in one simulation: 32 by default, or fewer when the estimated GPU memory does not fit. A longer sequence runs as several simulations of nearly equal size, so 128 transmits run as 4 simulations of 32. In a 2D L7-4 test the time per transmit stopped falling at 16 to 32 transmits per simulation.
|
|
212
|
+
|
|
213
|
+
### Beamforming
|
|
214
|
+
|
|
215
|
+
| Beamformer | Runs on | Purpose |
|
|
216
|
+
| ---------------------------------------- | ------- | ------------------------------------------------------------- |
|
|
217
|
+
| `fullwave.beamformer.Beamformer` | CPU | delay-and-sum for linear arrays, for teaching; not optimized for speed |
|
|
218
|
+
| `fullwave.beamformer.MachBeamformer` | GPU | fast delay-and-sum with [mach](https://github.com/Forest-Neurotech/mach) |
|
|
219
|
+
|
|
220
|
+
mach is an open-source GPU beamformer with an optimized delay-and-sum CUDA kernel: 1.1 trillion points per second on a consumer GPU, more than 10 times faster than other open-source GPU beamformers. `pip install "fullwave25[mach]"` installs mach-beamform, pymust and cupy; mach needs Linux and an NVIDIA driver for CUDA 12.8 or newer.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
beamformer = MachBeamformer.from_transducer(
|
|
224
|
+
transducer, lateral_position_m=lateral_m, axial_position_m=depth_m, f_number=1.0,
|
|
225
|
+
)
|
|
226
|
+
arrival_s = [beamformer.plane_wave_arrival_s(angle_deg=a) for a in angles]
|
|
227
|
+
image = beamformer.beamform(np.stack(traces), tx_arrival_s=np.stack(arrival_s)) # complex [n_depth, n_lateral]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`MachBeamformer` converts the traces to IQ with `pymust.rf2iq` and measures depth from the probe face. `traces` holds one `[n_elements, n_t]` array per transmit (from `post_process_sensor_output`). `focused_arrival_s(focus_m=...)` and `synthetic_aperture_arrival_s(element=...)` give the other transmits. Time zero follows from the drive and the rows between the source and the face, so it holds for every transmit of the named linear arrays. `f_number` is depth / aperture width; `Beamformer` takes the half width as depth / F, so pass `f_number / 2` to it for the same aperture. If you use `MachBeamformer`, please also cite mach (see [Citation](#citation)).
|
|
231
|
+
|
|
232
|
+
### Imaging a phantom
|
|
233
|
+
|
|
234
|
+
`fullwave.presets.CombinedPhantomDomain` builds an imaging phantom: point targets in a column and in rows, and hypoechoic and hyperechoic cylinders, on a scattering background. [plane_wave_phantom.py](examples/linear_transducer/plane_wave_phantom.py) sends one plane wave from an L7-4 array at 5.208 MHz through it and beamforms the echoes into a B-mode with `MachBeamformer`.
|
|
235
|
+
|
|
236
|
+
| Plane wave through the phantom | B-mode, one plane wave (0 to −40 dB) |
|
|
237
|
+
| ------------------------------------------------------ | ------------------------------------------------------------- |
|
|
238
|
+
| <img src="figs/plane_wave_phantom.gif" width="200"/> | <img src="figs/plane_wave_phantom_b_mode.png" width="360"/> |
|
|
239
|
+
|
|
240
|
+
## Examples
|
|
241
|
+
|
|
242
|
+
| Task | Example | Notebook / Colab |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| 2D plane wave from a source built by hand, through an inclusion | [simple_plane_wave.py](examples/simple_plane_wave/simple_plane_wave.py) | [notebook](examples/simple_plane_wave/example_simple_plane_wave.ipynb), [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/simple_plane_wave/example_simple_plane_wave.ipynb) |
|
|
245
|
+
| 2D plane wave through an inclusion with scattered air cells | [simple_plane_wave_air.py](examples/simple_plane_wave/simple_plane_wave_air.py) | [notebook](examples/simple_plane_wave/example_simple_plane_wave_air.ipynb), [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/simple_plane_wave/example_simple_plane_wave_air.ipynb) |
|
|
246
|
+
| Attenuation and phase velocity of a uniform medium against the power law | [attenuation_dispersion.py](examples/attenuation_dispersion/attenuation_dispersion.py) | |
|
|
247
|
+
| Layered medium built with `MediumBuilder` | [medium_builder_example.py](examples/medium_builder/medium_builder_example.py) | [notebook](examples/medium_builder/medium_builder_example.ipynb), [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/medium_builder/medium_builder_example.ipynb) |
|
|
248
|
+
| Abdominal wall built with `MediumBuilder` | [medium_builder_abdominal_example.py](examples/medium_builder/medium_builder_abdominal_example.py) | |
|
|
249
|
+
| L7-4 linear array: focused transmit through an abdominal wall | [linear_transducer_abdominal_wall.py](examples/linear_transducer/linear_transducer_abdominal_wall.py) | |
|
|
250
|
+
| L7-4 linear array: plane wave compounding B-mode | [plane_wave_compounding.py](examples/linear_transducer/plane_wave_compounding.py) | [notebook](examples/linear_transducer/plane_wave_compounding.ipynb), [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/linear_transducer/plane_wave_compounding.ipynb) |
|
|
251
|
+
| L7-4 linear array: plane wave movie and B-mode of an imaging phantom | [plane_wave_phantom.py](examples/linear_transducer/plane_wave_phantom.py) | |
|
|
252
|
+
| L7-4 linear array: plane wave compounding B-mode, all angles in one transmit batch | [plane_wave_compounding_batch.py](examples/linear_transducer/plane_wave_compounding_batch.py) | |
|
|
253
|
+
| L7-4 linear array: plane waves and focused lines as transmit batches | [transmit_batch_basics.py](examples/transmit_batch/transmit_batch_basics.py) | |
|
|
254
|
+
| L7-4 linear array: full synthetic aperture B-mode | [full_synthetic_aperture.py](examples/linear_transducer/full_synthetic_aperture.py) | [notebook](examples/linear_transducer/full_synthetic_aperture.ipynb), [](https://colab.research.google.com/github/pinton-lab/fullwave25/blob/main/examples/linear_transducer/full_synthetic_aperture.ipynb) |
|
|
255
|
+
| L7-4 linear array: high-pass filter on the recorded traces | [plane_wave_with_filter.py](examples/signal_filter/plane_wave_with_filter.py) | |
|
|
256
|
+
| P4-1C phased array: focused transmit from half the aperture, with a movie of the field | [linear_transducer_focused_animation.py](examples/linear_transducer/linear_transducer_focused_animation.py) | |
|
|
257
|
+
| C5-2V curved array: full synthetic aperture acquisition | [convex_transducer_fsa.py](examples/convex_transducer/convex_transducer_fsa.py) | |
|
|
258
|
+
| C5-2V curved array: focused transmit through an abdominal wall | [convex_transducer_abdominal_wall.py](examples/convex_transducer/convex_transducer_abdominal_wall.py) | |
|
|
259
|
+
| 3D plane wave through an inclusion | [simple_plane_wave_3d.py](examples/wave_3d/simple_plane_wave_3d.py) | |
|
|
260
|
+
| 3D plane wave through an inclusion with scattered air cells | [simple_plane_wave_3d_with_air.py](examples/wave_3d/simple_plane_wave_3d_with_air.py) | |
|
|
261
|
+
|
|
262
|
+
### Gallery
|
|
263
|
+
|
|
264
|
+
| Plane wave through an inclusion | P4-1C focused transmit | C5-2V through an abdominal wall |
|
|
265
|
+
| ------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------- |
|
|
266
|
+
| <img src="figs/simple_plane_wave.gif" width="200"/> | <img src="figs/linear_transducer_focused.gif" width="200"/> | <img src="figs/convex_transducer_abdominal_wall.gif" width="200"/> |
|
|
267
|
+
|
|
268
|
+
**3D plane wave through an inclusion, without and with scattered air cells**
|
|
269
|
+
|
|
270
|
+
| | x-y slice | x-z slice |
|
|
271
|
+
| ----------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
|
|
272
|
+
| without air cells | <img src="figs/wave_propagation_x-y.gif" width="200"/> | <img src="figs/wave_propagation_x-z.gif" width="200"/> |
|
|
273
|
+
| with air cells | <img src="figs/wave_propagation_x-y_air.gif" width="200"/> | <img src="figs/wave_propagation_x-z_air.gif" width="200"/> |
|
|
274
|
+
|
|
275
|
+
## Experimental: fullwave2-ultra backend
|
|
276
|
+
|
|
277
|
+
`Solver(..., use_ultra=True)` runs the simulation with the fullwave2-ultra binary, which is optimized for speed. It is experimental, its API may change, and the binaries are not part of fullwave25. See [docs/fullwave2_ultra.md](docs/fullwave2_ultra.md).
|
|
278
|
+
|
|
279
|
+
## Notes
|
|
280
|
+
|
|
281
|
+
- **Axis order.** The grid axes are (x, y, z) = (depth, lateral, elevation). The solver splits the domain along depth across GPUs, so depth comes first.
|
|
282
|
+
- **Index order.** Source and sensor coordinates are in C (row-major) order inside the solver, whatever order you use. Check this when you build a source by hand.
|
|
283
|
+
- **GPU memory.** A 3D simulation needs much GPU memory. Reduce the grid or use more GPUs if it does not fit; `nvidia-smi` or `nvtop` shows the use. `Solver.run` estimates the memory before the run (`gpu_memory_estimate=True`, the default).
|
|
284
|
+
- **Multi-GPU.** 2D and 3D runs can use multiple GPUs (`cuda_device_id`); the run time scales linearly with the number of GPUs.
|
|
285
|
+
- **3D.** Start with the 2D version of a simulation. The 3D code is similar, runs longer, and some plot functions are 2D only.
|
|
286
|
+
|
|
287
|
+
## Citation
|
|
288
|
+
|
|
289
|
+
Fullwave 2.5 is developed and maintained by the [Pinton Lab](https://github.com/pinton-lab) at the University of North Carolina at Chapel Hill. If you use Fullwave 2.5 in your research, please cite:
|
|
290
|
+
|
|
291
|
+
```bibtex
|
|
292
|
+
@article{Sode2026-heterogeneous-attenuation,
|
|
293
|
+
title = {Spatially heterogeneous power-law attenuation with multiple relaxation mechanisms for ultrasound modeling},
|
|
294
|
+
author = {Sode, Masashi and Pinton, Gianmarco},
|
|
295
|
+
journal = {arXiv preprint arXiv:2606.11103},
|
|
296
|
+
year = {2026},
|
|
297
|
+
eprint = {2606.11103},
|
|
298
|
+
archivePrefix = {arXiv},
|
|
299
|
+
primaryClass = {physics.med-ph},
|
|
300
|
+
url = {https://arxiv.org/abs/2606.11103}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@article{Sode2026-fullwave2,
|
|
304
|
+
title = {A fullwave model of the nonlinear wave equation with multiple relaxations and relaxing perfectly matched layers for high-order numerical finite difference solutions},
|
|
305
|
+
author = {Sode, Masashi and Pinton, Gianmarco},
|
|
306
|
+
journal = {Physics in Medicine \& Biology},
|
|
307
|
+
publisher = {IOP Publishing},
|
|
308
|
+
year = {2026},
|
|
309
|
+
doi = {10.1088/1361-6560/ae9116},
|
|
310
|
+
url = {https://doi.org/10.1088/1361-6560/ae9116}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@software{Sode2025-fullwave25,
|
|
314
|
+
title = {Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities},
|
|
315
|
+
author = {Sode, Masashi and Pinton, Gianmarco},
|
|
316
|
+
year = {2025},
|
|
317
|
+
doi = {10.5281/zenodo.17497689},
|
|
318
|
+
url = {https://github.com/pinton-lab/fullwave25}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
If you use `MachBeamformer`, please also cite mach:
|
|
323
|
+
|
|
324
|
+
```bibtex
|
|
325
|
+
@article{mach,
|
|
326
|
+
title = {mach: ultrafast ultrasound beamforming},
|
|
327
|
+
author = {Guan, Charles and Rockhill, Alexander P and Sode, Masashi and Pinton, Gianmarco},
|
|
328
|
+
year = 2026,
|
|
329
|
+
journal = {Journal of Medical Imaging},
|
|
330
|
+
publisher = {Society of Photo-Optical Instrumentation Engineers},
|
|
331
|
+
volume = 13,
|
|
332
|
+
number = 6,
|
|
333
|
+
pages = {062203--062203},
|
|
334
|
+
doi = {10.1117/1.JMI.13.6.062203},
|
|
335
|
+
url = {https://github.com/Forest-Neurotech/mach}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the branch names, the pull request flow and the code checks.
|
|
342
|
+
|
|
343
|
+
## Acknowledgments
|
|
344
|
+
|
|
345
|
+
The design of this repository was inspired by [k-wave-python](https://github.com/waltsims/k-wave-python). We thank the k-wave-python developers; their tools have contributed much to the ultrasound research community.
|
|
346
|
+
|
|
347
|
+
## Authors
|
|
348
|
+
|
|
349
|
+
- Masashi Sode ([MasashiSode](https://github.com/MasashiSode)), maintainer
|
|
350
|
+
- Gianmarco Pinton ([gfpinton](https://github.com/gfpinton))
|