def test_multiview_tfm(use_real_grid): # make probe probe = arim.Probe.make_matrix_probe(5, 0.5e-3, 1, np.nan, 1e6) probe.set_reference_element("first") probe.reset_position() probe.translate([0.0, 0.0, -1e-3]) # make frame tx_arr, rx_arr = arim.ut.fmc(probe.numelements) time = arim.Time(0.5e-6, 1 / 20e6, 100) # use random data but ensure reciprocity scanlines = np.zeros((len(tx_arr), len(time))) for i, (tx, rx) in enumerate(zip(tx_arr, rx_arr)): np.random.seed((tx * rx)**2) # symmetric in tx and rx scanlines[i] = np.random.rand(len(time)) block = arim.Material(6300, 3100) frame = arim.Frame(scanlines, time, tx_arr, rx_arr, probe, arim.ExaminationObject(block)) # prepare view LL-T in contact if use_real_grid: grid = arim.Grid(0.0, 0.0, 0.0, 0.0, 5e-3, 5e-3, np.nan) grid_interface = arim.Interface(*grid.to_oriented_points()) else: grid = arim.Points(np.array([0.0, 0.0, 5e-3]), name="Grid") grid_interface = arim.Interface( *arim.geometry.default_oriented_points(grid.to_1d_points())) backwall = arim.geometry.points_1d_wall_z(-1e-3, 1e-3, 10e-3, 200) backwall_interface = arim.Interface(*backwall) probe_interface = arim.Interface(*probe.to_oriented_points()) path_LL = arim.Path( [probe_interface, backwall_interface, grid_interface], [block, block], ["L", "L"], ) path_T = arim.Path([probe_interface, grid_interface], [block], ["T"]) view = arim.View(path_LL, path_T, "LL-T") arim.ray.ray_tracing([view], convert_to_fortran_order=True) # make TFM tfm = im.tfm.tfm_for_view(frame, grid, view, fillvalue=np.nan) # Check this value is unchanged over time! expected_val = 12.745499105785953 assert tfm.res.shape == grid.shape if use_real_grid: np.testing.assert_array_almost_equal(tfm.res, [[[expected_val]]]) else: np.testing.assert_allclose(tfm.res, expected_val) # Reverse view view_rev = arim.View(path_LL, path_T, "T-LL") tfm_rev = im.tfm.tfm_for_view(frame, grid, view_rev, fillvalue=np.nan) assert tfm.res.shape == grid.shape if use_real_grid: np.testing.assert_array_almost_equal(tfm_rev.res, [[[expected_val]]]) else: np.testing.assert_allclose(tfm_rev.res, expected_val)
def downsample_frame(frame, k): """ Return a Frame obtained by grouping the probe elements by 'k' New scanlines are obtained by averaging original scanlines. Emulates a Frame with a larger pitch. 1D array only. If 'k' is not a divisor of the number of elements, do not use remaining elements. """ probe = frame.probe new_numelements = probe.numelements // k numelements_to_use = k * new_numelements new_locations = np.zeros((new_numelements, 3)) for i in range(new_numelements): new_locations[i] = probe.locations[i * k : (i + 1) * k].mean(axis=0) new_probe = arim.Probe( new_locations, probe.frequency, bandwidth=probe.bandwidth, pcs=probe.pcs.copy() ) # Prepare downsampled FMC: new_scanlines = [] new_tx = [] new_rx = [] elements_idx = np.arange(numelements_to_use) for i in range(new_numelements): for j in range(new_numelements): retained_scanlines_idx = np.logical_and( np.isin(frame.tx, elements_idx[i * k : (i + 1) * k]), np.isin(frame.rx, elements_idx[j * k : (j + 1) * k]), ) if np.sum(retained_scanlines_idx) != k * k: # Missing signals, ignore pass else: new_tx.append(i) new_rx.append(j) new_scanlines.append( frame.scanlines[retained_scanlines_idx].mean(axis=0) ) new_tx = np.array(new_tx) new_rx = np.array(new_rx) new_scanlines = np.array(new_scanlines) new_frame = arim.Frame( new_scanlines, frame.time, new_tx, new_rx, new_probe, frame.examination_object ) return new_frame
def test_contact_tfm(use_hmc): # make probe probe = arim.Probe.make_matrix_probe(5, 0.5e-3, 1, np.nan, 1e6) probe.set_reference_element("first") probe.reset_position() probe.translate([0.0, 0.0, -1e-3]) # make frame if use_hmc: tx_arr, rx_arr = arim.ut.hmc(probe.numelements) else: tx_arr, rx_arr = arim.ut.fmc(probe.numelements) time = arim.Time(0.5e-6, 1 / 20e6, 100) # use random data but ensure reciprocity scanlines = np.zeros((len(tx_arr), len(time))) for i, (tx, rx) in enumerate(zip(tx_arr, rx_arr)): np.random.seed((tx * rx)**2) # symmetric in tx and rx scanlines[i] = np.random.rand(len(time)) # check reciprocity if not use_hmc: for i, (tx, rx) in enumerate(zip(tx_arr, rx_arr)): scanline_1 = scanlines[i] scanline_2 = scanlines[np.logical_and(tx_arr == rx, rx_arr == tx)][0] np.testing.assert_allclose(scanline_1, scanline_2, err_msg="fmc data not symmetric") block = arim.Material(6300, 3100) frame = arim.Frame(scanlines, time, tx_arr, rx_arr, probe, arim.ExaminationObject(block)) # prepare view LL-T in contact grid = arim.Points(np.array([0.0, 0.0, 5e-3]), name="Grid") tfm = im.tfm.contact_tfm(frame, grid, block.longitudinal_vel, fillvalue=np.nan) # Check this value is unchanged over time! expected_val = 12.49925772283528 assert tfm.res.shape == grid.shape np.testing.assert_allclose(tfm.res, expected_val)
def model_full(dataset_name, use_multifreq, full_tfm=True): # %% conf = arim.io.load_conf(dataset_name) result_dir = conf["result_dir"] logger.info(f"dataset_name: {dataset_name}") probe = common.load_probe(conf) examination_object = arim.io.block_in_immersion_from_conf(conf) tx, rx = arim.ut.fmc(probe.numelements) numscanlines = len(tx) scatterers = common.defect_oriented_point(conf) grid = common.grid_near_defect(conf) grid_p = grid.to_oriented_points() probe_p = probe.to_oriented_points() views = bim.make_views( examination_object, probe_p, scatterers, max_number_of_reflection=1, tfm_unique_only=True, ) views_imaging = bim.make_views( examination_object, probe_p, grid_p, max_number_of_reflection=1, tfm_unique_only=True, ) arim.ray.ray_tracing(views.values(), convert_to_fortran_order=True) arim.ray.ray_tracing(views_imaging.values(), convert_to_fortran_order=True) if full_tfm: grid_large = common.make_grid_tfm(conf) grid_large_p = grid_large.to_oriented_points() views_imaging_large = bim.make_views( examination_object, probe_p, grid_large_p, max_number_of_reflection=1, tfm_unique_only=True, ) arim.ray.ray_tracing(views_imaging_large.values(), convert_to_fortran_order=True) if use_multifreq: multifreq_key = "multif" multifreq_key_title = "MultiFreq" else: multifreq_key = "singlef" multifreq_key_title = "SingleFreq" # %% Toneburst and time vector max_delay = max( (view.tx_path.rays.times.max() + view.rx_path.rays.times.max() for view in views.values())) numcycles = conf["model"]["toneburst"]["numcycles"] centre_freq = common.get_centre_freq(conf, probe) dt = 0.25 / centre_freq # to adjust so that the whole toneburst is sampled _tmax = max_delay + 4 * numcycles / centre_freq numsamples = scipy.fftpack.next_fast_len(math.ceil(_tmax / dt)) time = arim.Time(0.0, dt, numsamples) freq_array = np.fft.rfftfreq(len(time), dt) numfreq = len(freq_array) toneburst = arim.model.make_toneburst(numcycles, centre_freq, dt, numsamples, wrap=True) toneburst *= 1.0 / np.abs(hilbert(toneburst)[0]) toneburst_f = np.fft.rfft(toneburst) # plot toneburst plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.plot(1e6 * time.samples, toneburst) plt.title("toneburst (time domain)") plt.xlabel("time (µs)") plt.subplot(1, 2, 2) plt.plot(1e-6 * np.fft.rfftfreq(len(toneburst), dt), abs(toneburst_f)) plt.title("toneburst (frequency domain)") plt.xlabel("frequency (MHz)") # %% Compute transfer function numangles_for_scat_precomp = 180 # 0 to disable model_options = dict( probe_element_width=probe.dimensions.x[0], numangles_for_scat_precomp=numangles_for_scat_precomp, ) scat_obj = arim.scat.scat_factory( material=examination_object.block_material, **conf["scatterer"]["specs"]) scat_angle = np.deg2rad(conf["scatterer"]["angle_deg"]) transfer_function_f = np.zeros((numscanlines, numfreq), np.complex_) tfms = OrderedDict() if full_tfm: tfms_large = OrderedDict() else: tfms_large = None if use_multifreq: # Multi frequency model transfer_function_iterator = bim.multifreq_scat_transfer_functions( views, tx, rx, freq_array=freq_array, scat_obj=scat_obj, scat_angle=scat_angle, **model_options, ) else: # Single frequency model transfer_function_iterator = bim.singlefreq_scat_transfer_functions( views, tx, rx, freq_array=freq_array, scat_obj=scat_obj, scat_angle=scat_angle, **model_options, frequency=common.get_centre_freq(conf, probe), ) with arim.helpers.timeit("Main loop"): for viewname, partial_transfer_func in transfer_function_iterator: transfer_function_f += partial_transfer_func # imaging: partial_response = arim.signal.rfft_to_hilbert( partial_transfer_func * toneburst_f, numsamples) partial_frame = arim.Frame(partial_response, time, tx, rx, probe, examination_object) tfms[viewname] = arim.im.tfm.tfm_for_view( partial_frame, grid, views_imaging[viewname], interpolation=common.TFM_FINE_INTERP, fillvalue=0.0, ) if full_tfm: tfms_large[viewname] = arim.im.tfm.tfm_for_view( partial_frame, grid_large, views_imaging_large[viewname], fillvalue=0.0, ) # %% Save raw TFM results if save: with open(result_dir / f"tfm_{multifreq_key}.pickle", "wb") as f: pickle.dump(tfms, f, pickle.HIGHEST_PROTOCOL) if full_tfm: with open(result_dir / f"tfm_{multifreq_key}_large.pickle", "wb") as f: pickle.dump(tfms_large, f, pickle.HIGHEST_PROTOCOL) # %% Measure TFM intensities tmp = [] scatterer_idx = grid.closest_point(*scatterers.points[0]) for viewname, tfm in tfms.items(): max_tfm_idx = np.argmax(np.abs(tfm.res)) tmp.append(( viewname, np.abs(tfm.res.flat[scatterer_idx]), np.abs(tfm.res.flat[max_tfm_idx]), grid.x.flat[max_tfm_idx], grid.y.flat[max_tfm_idx], grid.z.flat[max_tfm_idx], )) intensities_df = pd.DataFrame( tmp, columns=[ "view", f"Model_{multifreq_key_title}_Centre", f"Model_{multifreq_key_title}_Max", "x_max_intensity", "y_max_intensity", "z_max_intensity", ], ).set_index("view") if save: intensities_df.to_csv( str(result_dir / f"intensities_{multifreq_key}_unscaled.csv")) # %% Plot TFM (defect only) scale_tfm = aplt.common_dynamic_db_scale( [tfm.res for tfm in tfms.values()]) # scale_tfm = itertools.repeat((None, None)) ncols = 6 fig, axes = plt.subplots( ncols=ncols, nrows=math.ceil(len(tfms) / ncols), figsize=(16, 9), sharex=True, sharey=True, ) for (viewname, tfm), ax in zip(tfms.items(), axes.ravel()): ref_db, clim = next(scale_tfm) aplt.plot_tfm( tfm, ax=ax, scale="db", ref_db=ref_db, clim=clim, interpolation="none", savefig=False, ) ax.set_title(viewname) if ax in axes[-1, :]: ax.set_xlabel("x (mm)") else: ax.set_xlabel("") if ax in axes[:, 0]: ax.set_ylabel("z (mm)") else: ax.set_ylabel("") amp = intensities_df.loc[viewname] ax.plot(amp["x_max_intensity"], amp["z_max_intensity"], "1m") ax.plot(scatterers.points.x, scatterers.points.z, "dm") fig.savefig(str(result_dir / f"tfm_model_{multifreq_key}")) # %% return tfms, tfms_large, intensities_df
with arim.helpers.timeit("Main loop for walls:"): for pathname, partial_transfer_func in transfer_function_iterator: transfer_function_wall_f += partial_transfer_func #%% Compute the response in frequency then time domain response_timetraces_f = (transfer_function_f + transfer_function_wall_f) * toneburst_f # response_timetraces_f = transfer_function_f * toneburst_f # response_timetraces_f = transfer_function_wall_f * toneburst_f response_timetraces = arim.signal.rfft_to_hilbert( response_timetraces_f, numsamples, axis=-1 ) real_response_timetraces = np.real(response_timetraces) frame = arim.Frame( response_timetraces, time, tx_list, rx_list, probe, examination_object ) plt.figure() idx = 31 plt.plot( frame.time.samples * 1e6, np.real(frame.timetraces[idx]), label=f"tx={frame.tx[idx]}, rx={frame.rx[idx]}", ) plt.xlabel("time (µs)") plt.title("time-domain response") plt.legend() if aplt.conf["savefig"]: plt.savefig("time_domain_response")
def make_model_walls(conf, use_multifreq=False, wall_keys=WALL_KEYS): """ Generate a FMC containing the walls Parameters ---------- conf : dict wall_keys : set use_multifreq : bool Returns ------- Frame """ probe = common.load_probe(conf) examination_object = arim.io.examination_object_from_conf(conf) tx_list, rx_list = arim.ut.fmc(probe.numelements) wall_keys = set(wall_keys) model_options = dict( probe_element_width=probe.dimensions.x[0], use_directivity=True, use_beamspread=True, use_transrefl=True, use_attenuation=True, ) unknown_wall_keys = wall_keys - WALL_KEYS if unknown_wall_keys: raise ValueError(f"Unknown wall keys: {unknown_wall_keys}") # used paths: wall_paths = [] if "frontwall" in wall_keys: # Frontwall path frontwall_path = bim.frontwall_path( examination_object.couplant_material, examination_object.block_material, *probe.to_oriented_points(), *examination_object.frontwall, ) wall_paths.append(frontwall_path) # Backwall paths backwall_paths = bim.backwall_paths( examination_object.couplant_material, examination_object.block_material, probe.to_oriented_points(), examination_object.frontwall, examination_object.backwall, max_number_of_reflection=2, ) if "backwall_LL" in wall_keys: wall_paths.append(backwall_paths["LL"]) if "backwall_LT" in wall_keys: wall_paths.append(backwall_paths["LT"]) wall_paths.append(backwall_paths["TL"]) if "backwall_LLLL" in wall_keys: wall_paths.append(backwall_paths["LLLL"]) arim.ray.ray_tracing_for_paths(wall_paths) # Toneburst numcycles = conf["model"]["toneburst"]["numcycles"] centre_freq = common.get_centre_freq(conf, probe) max_delay = max(path.rays.times.max() for path in wall_paths) dt = 0.25 / centre_freq # to adjust so that the whole toneburst is sampled _tmax = max_delay + 4 * numcycles / centre_freq numsamples = scipy.fftpack.next_fast_len(math.ceil(_tmax / dt)) time = arim.Time(0.0, dt, numsamples) freq_array = np.fft.rfftfreq(len(time), dt) toneburst = arim.model.make_toneburst(numcycles, centre_freq, dt, numsamples, wrap=True) toneburst_f = np.fft.rfft(toneburst) # convert to dict due to bim API wall_paths_dict = dict(zip(range(len(wall_paths)), wall_paths)) if use_multifreq: transfer_function_iterator = bim.multifreq_wall_transfer_functions( wall_paths_dict, tx_list, rx_list, freq_array, **model_options) else: transfer_function_iterator = bim.singlefreq_wall_transfer_functions( wall_paths_dict, tx_list, rx_list, centre_freq, freq_array, **model_options) scanlines = None for _, transfer_function_wall_f in transfer_function_iterator: tmp_scanlines = arim.signal.rfft_to_hilbert(transfer_function_wall_f * toneburst_f, numsamples, axis=-1) if scanlines is None: scanlines = tmp_scanlines else: scanlines += tmp_scanlines return arim.Frame(scanlines, time, tx_list, rx_list, probe, examination_object)
def test_fulltime_model(use_multifreq, show_plots): # Setup couplant = arim.Material(longitudinal_vel=1480.0, density=1000.0, state_of_matter="liquid") block = arim.Material( longitudinal_vel=6320.0, transverse_vel=3130.0, density=2700.0, state_of_matter="solid", longitudinal_att=arim.material_attenuation_factory("constant", 2.0), transverse_att=arim.material_attenuation_factory("constant", 20.0), ) probe = arim.Probe.make_matrix_probe(20, 1e-3, 1, np.nan, 5e6) probe_element_width = 0.8e-3 probe.set_reference_element("first") probe.reset_position() probe.translate([0.0, 0.0, -5e-3]) probe.rotate(arim.geometry.rotation_matrix_y(np.deg2rad(10))) probe_p = probe.to_oriented_points() frontwall = arim.geometry.points_1d_wall_z(numpoints=1000, xmin=0.0e-3, xmax=40.0e-3, z=0.0, name="Frontwall") backwall = arim.geometry.points_1d_wall_z(numpoints=1000, xmin=0.0e-3, xmax=40.0e-3, z=30.0e-3, name="Backwall") scatterer_p = arim.geometry.default_oriented_points( arim.Points([[35e-3, 0.0, 20e-3]])) all_points = [probe_p, frontwall, backwall, scatterer_p] # if show_plots: # import arim.plot as aplt # aplt.plot_interfaces( # all_points, markers=["o", "o", "o", "d"], show_orientations=True # ) # aplt.plt.show() exam_obj = arim.BlockInImmersion(block, couplant, frontwall, backwall, scatterer_p) scat_obj = arim.scat.scat_factory(material=block, kind="sdh", radius=0.5e-3) scat_funcs = scat_obj.as_angles_funcs(probe.frequency) scat_angle = 0.0 tx_list, rx_list = arim.ut.fmc(probe.numelements) # Toneburst dt = 0.25 / probe.frequency # to adjust so that the whole toneburst is sampled toneburst_time, toneburst, toneburst_t0_idx = arim.model.make_toneburst2( 5, probe.frequency, dt, num_before=1) toneburst_f = np.fft.rfft(toneburst) toneburst_freq = np.fft.rfftfreq(len(toneburst_time), dt) # Allocate a long enough time vector for the timetraces views = bim.make_views( exam_obj, probe_p, scatterer_p, max_number_of_reflection=0, tfm_unique_only=False, ) arim.ray.ray_tracing(views.values()) max_delay = max( (view.tx_path.rays.times.max() + view.rx_path.rays.times.max() for view in views.values())) timetraces_time = arim.Time( 0.0, dt, math.ceil(max_delay / dt) + len(toneburst_time)) timetraces = None # Run model if use_multifreq: model_freq_array = toneburst_freq else: model_freq_array = probe.frequency transfer_function_iterator = bim.scat_unshifted_transfer_functions( views, tx_list, rx_list, model_freq_array, scat_obj, probe_element_width=probe_element_width, use_directivity=True, use_beamspread=True, use_transrefl=True, use_attenuation=True, scat_angle=scat_angle, numangles_for_scat_precomp=120, ) for unshifted_transfer_func, delays in transfer_function_iterator: timetraces = arim.model.transfer_func_to_timetraces( unshifted_transfer_func, delays, timetraces_time, toneburst_time, toneburst_freq, toneburst_f, toneburst_t0_idx, timetraces=timetraces, ) frame = arim.Frame(timetraces, timetraces_time, tx_list, rx_list, probe, exam_obj) if show_plots: import matplotlib.pyplot as plt import arim.plot as aplt aplt.plot_bscan_pulse_echo(frame) plt.title( f"test_fulltime_model - Bscan - use_multifreq={use_multifreq}") tx = 0 rx = probe.numelements - 1 plt.figure() plt.plot(np.real(frame.get_timetrace(tx, rx)), label=f"tx={tx}, rx={rx}") plt.plot(np.real(frame.get_timetrace(rx, tx)), label=f"tx={rx}, rx={tx}") plt.title(f"test_fulltime_model - use_multifreq={use_multifreq}") plt.legend() plt.show()