|
| 1 | +# coding: utf-8 |
| 2 | +from __future__ import absolute_import, division, print_function |
| 3 | + |
| 4 | +import matplotlib.pyplot as plt |
| 5 | +import numpy as np |
| 6 | +import pyeit.eit.jac as jac |
| 7 | +import pyeit.mesh as mesh |
| 8 | +from pyeit.eit.fem import EITForward |
| 9 | +import pyeit.eit.protocol as protocol |
| 10 | +from pyeit.mesh.wrapper import PyEITAnomaly_Circle |
| 11 | +from pyeit.mesh.external import place_electrodes_equal_spacing |
| 12 | +from numpy.random import default_rng |
| 13 | +from pyeit.quality.eit_system import ( |
| 14 | + calc_signal_to_noise_ratio, |
| 15 | + calc_accuracy, |
| 16 | + calc_drift, |
| 17 | + calc_detectability, |
| 18 | +) |
| 19 | +from pyeit.eit.render import render_2d_mesh |
| 20 | +from pyeit.visual.plot import colorbar |
| 21 | + |
| 22 | + |
| 23 | +def main(): |
| 24 | + # Configuration |
| 25 | + # ------------------------------------------------------------------------------------------------------------------ |
| 26 | + n_el = 16 |
| 27 | + render_resolution = (64, 64) |
| 28 | + background_value = 1 |
| 29 | + anomaly_value = 2 |
| 30 | + noise_magnitude = 2e-4 |
| 31 | + drift_rate = 2e-7 # Per frame |
| 32 | + |
| 33 | + n_background_measurements = 10 |
| 34 | + n_drift_measurements = 1800 |
| 35 | + measurement_frequency = 1 |
| 36 | + |
| 37 | + detectability_r = 0.5 |
| 38 | + distinguishability_r = 0.35 |
| 39 | + |
| 40 | + # Initialization |
| 41 | + # ------------------------------------------------------------------------------------------------------------------ |
| 42 | + drift_period_hours = n_drift_measurements / (60 * 60 * measurement_frequency) |
| 43 | + conductive_target = True if anomaly_value - background_value > 0 else False |
| 44 | + det_center_range = np.arange(0, 0.667, 0.067) |
| 45 | + dist_distance_range = np.arange(0.35, 1.2, 0.09) |
| 46 | + rng = default_rng(0) |
| 47 | + |
| 48 | + # Problem setup |
| 49 | + # ------------------------------------------------------------------------------------------------------------------ |
| 50 | + sim_mesh = mesh.create(n_el, h0=0.05) |
| 51 | + electrode_nodes = place_electrodes_equal_spacing(sim_mesh, n_electrodes=n_el) |
| 52 | + sim_mesh.el_pos = np.array(electrode_nodes) |
| 53 | + protocol_obj = protocol.create(n_el, dist_exc=1, step_meas=1, parser_meas="std") |
| 54 | + fwd = EITForward(sim_mesh, protocol_obj) |
| 55 | + |
| 56 | + recon_mesh = mesh.create(n_el, h0=0.1) |
| 57 | + electrode_nodes = place_electrodes_equal_spacing(recon_mesh, n_electrodes=n_el) |
| 58 | + recon_mesh.el_pos = np.array(electrode_nodes) |
| 59 | + eit = jac.JAC(recon_mesh, protocol_obj) |
| 60 | + eit.setup( |
| 61 | + p=0.5, lamb=0.03, method="kotre", perm=background_value, jac_normalized=True |
| 62 | + ) |
| 63 | + |
| 64 | + # Simulate background |
| 65 | + # ------------------------------------------------------------------------------------------------------------------ |
| 66 | + v0 = fwd.solve_eit(perm=background_value) |
| 67 | + d1 = np.array(range(n_drift_measurements)) * drift_rate # Create drift |
| 68 | + n1 = noise_magnitude * rng.standard_normal( |
| 69 | + (n_drift_measurements, len(v0)) |
| 70 | + ) # Create noise |
| 71 | + |
| 72 | + v0_dn = np.tile(v0, (n_drift_measurements, 1)) + np.tile(d1, (len(v0), 1)).T + n1 |
| 73 | + |
| 74 | + # Calculate background performance measures |
| 75 | + # ------------------------------------------------------------------------------------------------------------------ |
| 76 | + snr = calc_signal_to_noise_ratio(v0_dn[:n_background_measurements], method="db") |
| 77 | + accuracy = calc_accuracy(v0_dn[:n_background_measurements], v0, method="EIDORS") |
| 78 | + t2, adevs = calc_drift(v0_dn, method="Allan") |
| 79 | + |
| 80 | + drifts_delta = calc_drift(v0_dn, sample_period=10, method="Delta") |
| 81 | + start = np.average(v0_dn[0:10], axis=0) |
| 82 | + drifts_percent = 100 * drifts_delta / start |
| 83 | + |
| 84 | + # Simulate detectability test |
| 85 | + # ------------------------------------------------------------------------------------------------------------------ |
| 86 | + detectabilities = [] |
| 87 | + detectability_renders = [] |
| 88 | + for c in det_center_range: |
| 89 | + anomaly = PyEITAnomaly_Circle( |
| 90 | + center=[c, 0], r=detectability_r, perm=anomaly_value |
| 91 | + ) |
| 92 | + sim_mesh_new = mesh.set_perm( |
| 93 | + sim_mesh, anomaly=anomaly, background=background_value |
| 94 | + ) |
| 95 | + v1 = fwd.solve_eit(perm=sim_mesh_new.perm) |
| 96 | + n = noise_magnitude * rng.standard_normal(len(v1)) |
| 97 | + v1_n = v1 + n |
| 98 | + ds = eit.solve(v1_n, v0_dn[0], normalize=True) |
| 99 | + solution = np.real(ds) |
| 100 | + image = render_2d_mesh(recon_mesh, solution, resolution=render_resolution) |
| 101 | + detectability = calc_detectability( |
| 102 | + image, conductive_target=conductive_target, method="db" |
| 103 | + ) |
| 104 | + detectabilities.append(detectability) |
| 105 | + detectability_renders.append(image) |
| 106 | + |
| 107 | + # Simulate distinguishability test |
| 108 | + # ------------------------------------------------------------------------------------------------------------------ |
| 109 | + anomaly = PyEITAnomaly_Circle(center=[0, 0], r=detectability_r, perm=anomaly_value) |
| 110 | + sim_mesh_new = mesh.set_perm(sim_mesh, anomaly=anomaly, background=background_value) |
| 111 | + dist_v0 = fwd.solve_eit(perm=sim_mesh_new.perm) |
| 112 | + dist_v0n = dist_v0 + noise_magnitude * rng.standard_normal(len(dist_v0)) |
| 113 | + |
| 114 | + distinguishabilities = [] |
| 115 | + distinguishabilitiy_renders = [] |
| 116 | + for d in dist_distance_range: |
| 117 | + a1 = PyEITAnomaly_Circle( |
| 118 | + center=[d / 2, 0], r=distinguishability_r, perm=anomaly_value |
| 119 | + ) |
| 120 | + a2 = PyEITAnomaly_Circle( |
| 121 | + center=[-d / 2, 0], r=distinguishability_r, perm=anomaly_value |
| 122 | + ) |
| 123 | + sim_mesh_new = mesh.set_perm( |
| 124 | + sim_mesh, anomaly=[a1, a2], background=background_value |
| 125 | + ) |
| 126 | + v1 = fwd.solve_eit(perm=sim_mesh_new.perm) |
| 127 | + v1_n = v1 + noise_magnitude * rng.standard_normal(len(v1)) |
| 128 | + ds = eit.solve(v1_n, dist_v0n, normalize=True) |
| 129 | + solution = np.real(ds) |
| 130 | + image = render_2d_mesh(recon_mesh, solution, resolution=render_resolution) |
| 131 | + # Distinguishability is detectability but with a target as the background. |
| 132 | + distinguishability = calc_detectability( |
| 133 | + image, conductive_target=conductive_target, method="db" |
| 134 | + ) |
| 135 | + distinguishabilities.append(distinguishability) |
| 136 | + distinguishabilitiy_renders.append(image) |
| 137 | + |
| 138 | + # Plot results |
| 139 | + # ------------------------------------------------------------------------------------------------------------------ |
| 140 | + fig, axs = plt.subplots(2, 2) |
| 141 | + |
| 142 | + axs[0, 0].plot(snr) |
| 143 | + axs[0, 0].set_xlabel("Channel Number") |
| 144 | + axs[0, 0].set_ylabel("Signal to Noise Ratio\n(dB)") |
| 145 | + axs[0, 0].title.set_text(f"Signal to Noise Ratio for {len(snr)} channels") |
| 146 | + |
| 147 | + axs[0, 1].plot(accuracy) |
| 148 | + axs[0, 1].set_xlabel("Channel Number") |
| 149 | + axs[0, 1].set_ylabel("Accuracy") |
| 150 | + axs[0, 1].title.set_text(f"Accuracy for {len(snr)} channels") |
| 151 | + |
| 152 | + axs[1, 0].set_xlabel("Averaging Window (s)") |
| 153 | + axs[1, 0].set_ylabel("Allan Deviation") |
| 154 | + axs[1, 0].title.set_text(f"Allan Deviation for {len(snr)} channels") |
| 155 | + for adev in adevs: |
| 156 | + axs[1, 0].plot(t2, adev) |
| 157 | + |
| 158 | + axs[1, 1].plot(drifts_percent) |
| 159 | + axs[1, 1].title.set_text( |
| 160 | + f"Drift percentage on all channels.\nDrift period (hours): {drift_period_hours}" |
| 161 | + ) |
| 162 | + axs[1, 1].set_xlabel("Channel number") |
| 163 | + axs[1, 1].set_ylabel("Drift (% of starting value)") |
| 164 | + |
| 165 | + fig.tight_layout() |
| 166 | + fig.set_size_inches((10, 6)) |
| 167 | + |
| 168 | + fig, axs = plt.subplots(1, 2) |
| 169 | + axs[0].plot(det_center_range, detectabilities, ".-", label="-x axis") |
| 170 | + axs[0].legend() |
| 171 | + axs[0].set_xlabel("Target position (radius fraction)") |
| 172 | + axs[0].set_ylabel("Detectability (dB)") |
| 173 | + axs[0].title.set_text("Detectability vs radial position") |
| 174 | + |
| 175 | + axs[1].plot(dist_distance_range, distinguishabilities) |
| 176 | + axs[1].set_xlabel("Separation distance (radius fraction)") |
| 177 | + axs[1].set_ylabel("Distinguishability (dB)") |
| 178 | + axs[1].title.set_text("Distinguishability vs separation distance") |
| 179 | + |
| 180 | + fig.set_size_inches((10, 4)) |
| 181 | + fig.tight_layout() |
| 182 | + |
| 183 | + fig, axs = plt.subplots(1, len(det_center_range)) |
| 184 | + for i, c in enumerate(det_center_range): |
| 185 | + axs[i].imshow(detectability_renders[i]) |
| 186 | + axs[i].xaxis.set_ticks([]) |
| 187 | + axs[i].yaxis.set_ticks([]) |
| 188 | + |
| 189 | + fig.set_size_inches((14, 2)) |
| 190 | + fig.suptitle("Detectability Renders") |
| 191 | + fig.tight_layout() |
| 192 | + |
| 193 | + fig, axs = plt.subplots(1, len(dist_distance_range)) |
| 194 | + for i, d in enumerate(dist_distance_range): |
| 195 | + img = axs[i].imshow(distinguishabilitiy_renders[i]) |
| 196 | + axs[i].xaxis.set_ticks([]) |
| 197 | + axs[i].yaxis.set_ticks([]) |
| 198 | + colorbar(img) |
| 199 | + |
| 200 | + fig.set_size_inches((18, 2)) |
| 201 | + fig.suptitle("Distinguishability Renders") |
| 202 | + fig.tight_layout() |
| 203 | + plt.show() |
| 204 | + |
| 205 | + |
| 206 | +if __name__ == "__main__": |
| 207 | + main() |
0 commit comments