├── .gitignore ├── README.md ├── example_us_bmode_sensor_data.h5 ├── py ├── linear_array_beamforming.py └── result.png ├── rs ├── Cargo.lock ├── Cargo.toml ├── opencv-install-notes.org ├── result.png └── src │ └── main.rs └── target geometry.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # Jupyter Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # SageMath parsed files 79 | *.sage.py 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | .venv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # temporary emacs files 96 | *~ 97 | [#]*[#] 98 | .\#* 99 | .#* 100 | 101 | # Pycharm 102 | !/.idea# C++ objects and libs 103 | *.slo 104 | *.lo 105 | *.o 106 | *.a 107 | *.la 108 | *.lai 109 | *.so 110 | *.dll 111 | *.dylib 112 | 113 | # Qt-es 114 | object_script.*.Release 115 | object_script.*.Debug 116 | *_plugin_import.cpp 117 | /.qmake.cache 118 | /.qmake.stash 119 | *.pro.user 120 | *.pro.user.* 121 | *.qbs.user 122 | *.qbs.user.* 123 | *.moc 124 | moc_*.cpp 125 | moc_*.h 126 | qrc_*.cpp 127 | ui_*.h 128 | *.qmlc 129 | *.jsc 130 | Makefile* 131 | *build-* 132 | 133 | # Qt unit tests 134 | target_wrapper.* 135 | 136 | # QtCreator 137 | *.autosave 138 | 139 | # QtCreator Qml 140 | *.qmlproject.user 141 | *.qmlproject.user.* 142 | 143 | # QtCreator CMake 144 | CMakeLists.txt.user* 145 | 146 | # Rust 147 | /target 148 | callgrind.out 149 | perf.data* 150 | 151 | # pics 152 | *.svg 153 | *.png 154 | !result.png 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | This is an example of ultrasound beamforming using a linear array in both Python and Rust. The Python script is written with something of an educational emphasis, and the Rust script is in a minimally-functional state to be refined as Rust's signal and image processing libraries mature. 3 | 4 | # Data Description 5 | RF Data was simulated using a 3rd party MATLAB toolbox called K-Wave. Specifically, the data was generated using `example_us_bmode_linear_transducer.m`, which sets up a linear probe and generates the signals received after pulsing into a 3D scattering phantom. The phantom accounts for nonlinearity, multiple scattering, power law acoustic absorption, and a finite beam width in the elevation direction. One can find the nature of the simulated data as well as a description of the K-wave program [here](http://www.k-wave.org/documentation/example_us_bmode_linear_transducer.php). 6 | 7 | The aforementioned m-file not only simulates recorded data but performs image reconstruction as well. However, I have merely acquired the raw RF data stored in the variable `sensor_data` and written my own image reconstruction routines. You know, for fun. 8 | 9 | # Processing 10 | ## Python 11 | 12 | The conventional steps in an ultrasound signal processing pipeline are conducted, and a comparison is performed between simple B-mode imaging, beamforming with fixed receive focus, and dynamic focusing: 13 | 14 | This program requires `numpy`, `scipy`, `matplotlib`, and `h5py`. 15 | 16 | ## Rust 17 | 18 | The available signal and image processing libraries written in pure Rust are in their infancy at the time of this writing, so this script makes use of C++ libraries such as [`fftw`](https://github.com/rust-math/fftw) and [`opencv`](https://github.com/twistedfall/opencv-rust). The script is centered around [`ndarray`](https://github.com/rust-ndarray/ndarray) and incorporates logging ([`simplelog`](https://github.com/Drakulix/simplelog.rs)) and plot creation/saving ([`plotlib`](https://github.com/milliams/plotlib)) for the checking of intermediate outputs. 19 | 20 | Currently a beamformed image is produced with dynamic focusing, albeit with a slow upsampling step and no signal filtering. 21 | 22 | ### Prerequisites 23 | 24 | You may be required to install fortran/openblas to compile (e.g. `sudo apt-get install gfortran libopenblas-dev` on debian). Notes on installing a version of OpenCV compatible with rust bindings are included. 25 | 26 | py | rs 27 | :-------------------------:|:-------------------------: 28 | ![alt text](./py/result.png) | ![alt text](./rs/result.png) 29 | -------------------------------------------------------------------------------- /example_us_bmode_sensor_data.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csheaff/us-beamform-linarray/cbfd29f7a6a8f226860167c5960cfa6646246f11/example_us_bmode_sensor_data.h5 -------------------------------------------------------------------------------- /py/linear_array_beamforming.py: -------------------------------------------------------------------------------- 1 | import logging 2 | logging.basicConfig(filename='python.log', 3 | filemode='w', 4 | format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', 5 | datefmt='%H:%M:%S', 6 | level=logging.INFO) 7 | logger = logging.getLogger() 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from scipy import signal 12 | from scipy.interpolate import interp2d 13 | import h5py 14 | import pdb 15 | 16 | # constants 17 | n_transmit_beams = 96 18 | n_probe_channels = 32 19 | transmit_freq = 1.5e6 20 | transmit_focal_depth = 20e-3 21 | speed_sound = 1540 22 | array_pitch = 2*1.8519e-4 23 | sample_rate = 27.72e6 24 | time_offset = 1.33e-6 # time at which middle of the transmission pulse occurs 25 | 26 | # 27 | # \ / 28 | # \ / 29 | # \ / 30 | # / \ 31 | # / \ 32 | # / \ 33 | # - - - - - - - - - - - - - - - - - - - 34 | # |---------| 35 | # 32 active chan 36 | # 37 | # \ / 38 | # \ / 39 | # \ / 40 | # / \ 41 | # / \ 42 | # / \ 43 | # - - - - - - - - - - - - - - - - - - - 44 | # |---------| 45 | # 32 active chan 46 | # 47 | # \ / 48 | # \ / 49 | # \ / 50 | # / \ 51 | # / \ 52 | # / \ 53 | # - - - - - - - - - - - - - - - - - - - 54 | # |---------| 55 | # 32 active chan 56 | # 57 | # Beam tranmission moves laterally with different 58 | # groups of 32 active channels. This happens 96 times, 59 | # so we can consider there to be 128 elements in the probe 60 | # all-together. However for each transmission, we will only have 61 | # 32 received waveforms, which correspond to the 32 recivers centered 62 | # upon the transmitting position. 63 | # 64 | # If one considers the transmission to be plane-wave like, 65 | # the distance to an given target is simply that target's depth, z. 66 | # The target is considered a point source that reflects isotropically.. 67 | # The distance from target to receiver is then the euclidean distance from 68 | # the target to the receive element. i.e. 69 | # 70 | # dist = transmit_dist + receive_dist = z + sqrt(((x - x0)**2 + z**2) 71 | # 72 | # where x0 is the lateral position of central active element group and 73 | # x is the lateral position of element. 74 | # 75 | # /| 76 | # / | 77 | # (xd^2 + z^2)^(1/2) / | z 78 | # / | 79 | # / | 80 | # / | 81 | # - - - - - - - - - - - - - - - - - - - - 82 | # x x0 83 | # |-------| 84 | # xd 85 | 86 | def arange2(start, stop=None, step=1): 87 | """#Modified version of numpy.arange which corrects error associated with non 88 | -integer step size""" 89 | if stop is None: 90 | a = np.arange(start) 91 | else: 92 | a = np.arange(start, stop, step) 93 | if a[-1] > stop - step: 94 | a = np.delete(a, -1) 95 | return a 96 | 97 | 98 | def get_tgc(alpha0, prop_dist): 99 | """ Time-gain compensation 100 | The attenuation coefficient of tissue is usually expressed in d_b and 101 | defined as alpha_dB = 20/x*log10[p(0)/p(x)] where x is the propagation 102 | distance, p(0) is the incident pressure, p(x) is the spatially variant 103 | pressure. As a result, p(x)/p(0) = 10^(-alphad_b*x/20). Additionally, 104 | alpha is modeled as alpha_dB = alpha0*f^n where f is frequency and 105 | 0 < n < 1. For tissue, n ~ 1. alpha0 is usually the parameter specified in 106 | units of d_b/(MHz-cm). We can compensate therefore by multiplying each 107 | A-line by 10^(alpha0*f*prop_dist*100/20). Note that this does not take 108 | into account the dissipation of acoustic energy with distance due to 109 | non-plane wave propagation. 110 | 111 | inputs: alpha0 - attenutation coefficient in db/(MHz-cm) 112 | prop_dist - round-trip propagation distance of acoustic pulse in 113 | meters 114 | 115 | outputs: tgc_gain - gain vector for multiplication with A-line """ 116 | 117 | n = 1 # approx. 1 for soft tissue 118 | alpha = alpha0*(transmit_freq*1e-6)**n 119 | tgc_gain = 10**(alpha*prop_dist*100/20) 120 | 121 | return tgc_gain 122 | 123 | 124 | def preproc(data, t, xd): 125 | """Analog time-gain compensation is typically applied followed by an 126 | anti-aliasing filter (low-pass) and then A/D conversion. The input data is 127 | already digitized here, so no need for anti-alias filtering. Following A/D 128 | conversion, one would ideally begin beamforming, however the summing 129 | process in beamforming can produce very high values if low frequencies are 130 | included. This can result in the generation of a dynamic range in the data 131 | that exceeds what's allowable by the number of bits, thereby yielding data 132 | loss. Therefore it's necessary to high-pass filter before beamforming 133 | In addition, beamforming is more accurate with a higher sampling rate 134 | because the calculated beamforming delays are more accurately achieved 135 | Hence interpolation is used to upsample the signal. Finally, apodization 136 | is applied before the beamformer. 137 | 138 | This preprocessing function therefore consists of: 139 | 1) time-gain compensation 140 | 2) filtering 141 | 3) interpolation 142 | 4) apodization 143 | 144 | In the filtering step I've appied a band-pass, as higher frequencies are 145 | also problematic and are usually addressed after beamforming. 146 | 147 | inputs: data - transmission number x receive channel x time index 148 | t - time vector [s] 149 | xd - dector position vector [m] 150 | 151 | outputs: data_apod - processed data 152 | t2 - new time vectors 153 | """ 154 | 155 | sample_rate = 1/(t[1] - t[0]) 156 | record_length = data.shape[2] 157 | a0 = 0.4 158 | 159 | # get time-gain compensation vectors based on estimate for propagation 160 | # distance to each element 161 | zd = t*speed_sound/2 162 | zd2 = zd**2 163 | dist1 = zd 164 | tgc = np.zeros((n_probe_channels, record_length)) 165 | for r in range(n_probe_channels): 166 | dist2 = np.sqrt(xd[r]**2 + zd2) 167 | prop_dist = dist1 + dist2 168 | tgc[r, :] = get_tgc(a0, prop_dist) 169 | 170 | # apply tgc 171 | data_amp = np.zeros(data.shape) 172 | for m in range(n_transmit_beams): 173 | data_amp[m, :, :] = data[m, :, :]*tgc 174 | 175 | # retrieve filter coefficients 176 | filt_ord = 201 177 | lc, hc = 0.5e6, 2.5e6 178 | lc = lc/(sample_rate/2) # normalize to nyquist frequency 179 | hc = hc/(sample_rate/2) 180 | B = signal.firwin(filt_ord, [lc, hc], pass_zero=False) # band-pass filter 181 | 182 | # specify interpolation factor 183 | interp_fact = 4 184 | sample_rate = sample_rate*interp_fact 185 | record_length2 = record_length*interp_fact 186 | 187 | # get apodization window 188 | apod_win = signal.tukey(n_probe_channels) # np.ones(n_probe_channels) 189 | 190 | # process 191 | data_apod = np.zeros((n_transmit_beams, n_probe_channels, record_length2)) 192 | for m in range(n_transmit_beams): 193 | for n in range(n_probe_channels): 194 | w = data_amp[m, n, :] 195 | data_filt = signal.lfilter(B, 1, w) 196 | data_interp = signal.resample_poly(data_filt, interp_fact, 1) 197 | data_apod[m, n, :] = apod_win[n] * data_interp 198 | 199 | # create new time vector based on interpolation and filter delay 200 | freqs, delay = signal.group_delay((B, 1)) 201 | delay = int(delay[0]) * interp_fact 202 | t2 = np.arange(record_length2) / sample_rate + t[0] - delay / sample_rate 203 | 204 | # remove signal before t = 0 205 | f = np.where(t2 < 0)[0] 206 | t2 = np.delete(t2, f) 207 | data_apod = data_apod[:, :, f[-1]+1:] 208 | 209 | return data_apod, t2 210 | 211 | 212 | def beamform(data, t, xd, receive_focus): 213 | """This employs the classic delay-and-sum method of beamforming entailing a 214 | single focus location defined by receive_focus [m]. 215 | inputs: { 216 | data: - RF data (transmission number, receive channel, time index) 217 | t: 1-D time vector, [t] = seconds 218 | xd: horizontal position vector of receive channels relative to center, 219 | [xd] = meters 220 | receive_focus: depth of focus for beamforming, 221 | [receive_focus] = meters} 222 | outputs: { 223 | image: beamformed data (scanline index, depth)} 224 | """ 225 | Rf = receive_focus 226 | fs = 1/(t[1]-t[0]) 227 | delay_ind = np.zeros(n_probe_channels, dtype=int) 228 | for r in range(n_probe_channels): 229 | # difference between propagation time for a central element and that 230 | # for an off-centered element 231 | delay = Rf/speed_sound*(np.sqrt((xd[r]/Rf)**2+1)-1) 232 | delay_ind[r] = int(round(delay*fs)) 233 | max_delay = np.max(delay_ind) 234 | 235 | waveform_length = data.shape[2] 236 | image = np.zeros((n_transmit_beams, waveform_length)) # initialize 237 | for q in range(n_transmit_beams): 238 | scan_line = np.zeros(waveform_length + max_delay) # initialize 239 | for r in range(n_probe_channels): 240 | delay_pad = np.zeros(delay_ind[r]) 241 | fill_pad = np.zeros(len(scan_line)-waveform_length-delay_ind[r]) 242 | waveform = data[q, r, :] 243 | scan_line = scan_line + np.concatenate((fill_pad, waveform, 244 | delay_pad)) 245 | image[q, :] = scan_line[max_delay:] 246 | return image 247 | 248 | 249 | def beamform_df(data, t, xd): 250 | """Ideally we could focus at all depths in receive when beamforming. This 251 | is done in an FPGA by using time delays that are time-varying. To clarify, 252 | suppose we use the above beamform function to focus at some depth z0. Why 253 | not use the delay to achieve this focus merely for the value at that depth? 254 | For some depth z0+dz, we would then have a new delay and use it to generate 255 | the pixel only at z0+dz. So an array of time-dependent delay values can be 256 | generated for each channel that would allow focusing at each depth. 257 | 258 | In order to achieve dynamic focusing offline, digitally, one could find the 259 | time-dependent delays and apply them, but this would require operating a 260 | loop over each time value. One could also use the above beamform function 261 | for each focal point and only keep the value generated for that depth, but 262 | again this would computationally wasteful. An alternative is to fill the 263 | a-line first with values corresponding to the propagation time from 264 | emmission to pixel to receiver. One can then simply index the signal 265 | received by an element at the estimtae for propagation time and add that 266 | to the pixel, followed by summing contributions from other channels 267 | Focusing at all depths is effectively acheived, and this is the method 268 | applied below. 269 | 270 | inputs: { 271 | data: RF data (transmission number, receive channel, time index) 272 | t: time vector associated with RF waveforms, [t] = seconds 273 | xd: horizontal position vector of receive channels relative to 274 | center, [xd] = meters 275 | 276 | outputs: 277 | image - beamformed data, dimensions of (scanline index, depth) 278 | 279 | """ 280 | # We want to create a single beaformed a-line for each tranmission. 281 | # The lateral position of that a-line is aligned with the center of the 282 | # transmission. So, the following will get the acoustic propagation distance 283 | # starting at transmission, to a pixel above the center of the tranmission 284 | # and then to a receiver. This is done across all depths and receivers, which 285 | # can then be used to index the waveforms and perform the beamforming. 286 | # These distances stay the same as we slide across the aperture of 287 | # the full array because they are relative to the center element. 288 | # So only a single 2d matrix is needed. 289 | sample_rate = 1 / (t[2] - t[1]) 290 | zd = t * speed_sound / 2 # can be defined arbitrarily for higher resolution 291 | zd2 = zd**2 292 | prop_dist = np.zeros((n_probe_channels, len(zd))) 293 | for r in range(n_probe_channels): 294 | dist1 = zd 295 | dist2 = np.sqrt(xd[r]**2 + zd2) 296 | prop_dist[r, :] = dist1 + dist2 297 | 298 | # convert distance to sampling index 299 | prop_dist_ind = np.round(prop_dist / speed_sound * sample_rate) 300 | prop_dist_ind = prop_dist_ind.astype('int') 301 | 302 | # Eliminate out-of-bounds indices. 303 | # These indices can occur if the estimated propagation time to a peripheral 304 | # receiver is longer than the acquision time. 305 | # Replace these indices with last index of the waveform (likely to be of low signal 306 | # at that location i.e closest to a null. 307 | oob_inds = np.where(prop_dist_ind >= len(t)) 308 | prop_dist_ind[oob_inds[0], oob_inds[1]] = len(t) - 1 309 | 310 | # perform beamforming 311 | image = np.zeros((n_transmit_beams, len(zd))) 312 | for q in range(n_transmit_beams): # index transmission (96 total) 313 | data_received = data[q, ...] 314 | scan_line = np.zeros(len(zd)) 315 | for r in range(n_probe_channels): # index receiver (32 total) 316 | v = data_received[r, :] 317 | scan_line = scan_line + v[prop_dist_ind[r, :]] 318 | image[q, :] = scan_line 319 | return image 320 | 321 | 322 | def envel_detect(scan_line, t, method='hilbert'): 323 | """Envelope detection. This can be done in a few ways: 324 | (1) Hilbert transform method 325 | - doesn't require knowledge of carrier frequency 326 | - simple - doesn't require filtering 327 | - cannot be implement with analog electronics 328 | - edge effects are undesirable 329 | 330 | (2) Demodulation + Low-pass filtering 331 | - implementable with analog electronics 332 | - requires knowledge of the carrier frequency, which gets smaller with 333 | propagation 334 | - more computational steps involved. 335 | 336 | 'demod' and 'demod2' do exactly the same thing here. The former is merely 337 | the simplest/most intuitive way to look at the operation (multiplying by 338 | complex exponential yields a frequency shift in the fourier domain). 339 | Whereas with the latter, the I and Q components are defined, as is typical. 340 | """ 341 | n = 201 342 | fs = 1/(t[1]-t[0]) 343 | lc = 0.75e6 344 | b = signal.firwin(n, lc/(fs/2)) # low-pass filter 345 | 346 | if method == 'hilbert': 347 | envelope = np.abs(signal.hilbert(scan_line)) 348 | elif method == 'demod': 349 | demodulated = scan_line*np.exp(-1j*2*np.pi*transmit_freq*t) 350 | demod_filt = np.sqrt(2)*signal.filtfilt(b, 1, demodulated) 351 | envelope = np.abs(demod_filt) 352 | elif method == 'demod2': 353 | I = scan_line*np.cos(2*np.pi*transmit_freq*t) 354 | If = np.sqrt(2)*signal.filtfilt(b, 1, I) 355 | Q = scan_line*np.sin(2*np.pi*transmit_freq*t) 356 | Qf = np.sqrt(2)*signal.filtfilt(b, 1, Q) 357 | envelope = np.sqrt(If**2+Qf**2) 358 | return envelope 359 | 360 | 361 | def log_compress(data, dynamic_range, reject_level, bright_gain): 362 | """Dynamic range is defined as the max value of some data divided by the 363 | minimum value, and it is a measure of how spread out the data values are 364 | If the data values have been converted to d_b, then dynamic range is 365 | defined as the max value minus the minimum value. 366 | 367 | One could interpret there being two stages of compression in the standard 368 | log compression process. The first is the simple conversion to d_b. The 369 | second is in selecting to display a certain range of d_b 370 | 371 | inputs: 372 | data - envelope-detected data having values >= 0. Dimensions should 373 | be scanline x depth/time index 374 | dynamic_range - desired dynamic range of data to present [d_b] 375 | reject_level - level of rejection [d_b] 376 | bright_gain - brightness gain [d_b] 377 | output: 378 | xd_b3 - processed image, dimensions of scanline x depth/time index 379 | """ 380 | 381 | # compress to dynamic range chosen 382 | xd_b = 20*np.log10(data+1) 383 | xd_b2 = xd_b - np.max(xd_b) # shift such that max is 0 d_b 384 | xd_b3 = xd_b2 + dynamic_range # shift such that max is dynamic_range value 385 | xd_b3[xd_b3 < 0] = 0 # eliminate data outside of dynamic range 386 | 387 | # rejection 388 | xd_b3[xd_b3 <= reject_level] = 0 389 | 390 | # add brightness gain, keep max value = dynamic_range 391 | xd_b3 = xd_b3 + bright_gain 392 | xd_b3[xd_b3 > dynamic_range] = dynamic_range 393 | 394 | return xd_b3 395 | 396 | 397 | def scan_convert(data, xb, zb): 398 | """create 512x512 pixel image 399 | inputs: data - scanline x depth/time 400 | xb - horizontal distance vector 401 | zb - depth vector 402 | outputs: image_sC - scanline x depth/time 403 | znew - new depth vector 404 | xnew - new horizontal distance vector""" 405 | 406 | # decimate in depth dimensions 407 | decim_fact = 8 408 | data = data[:, 0:-1:decim_fact] 409 | zb = zb[0:-1:decim_fact] 410 | 411 | # interpolation 412 | interp_func = interp2d(zb, xb, data, kind='linear') 413 | dz = zb[1]-zb[0] 414 | xnew = arange2(xb[0], xb[-1]+dz, dz) # make pixel square by making dx = dz 415 | znew = zb 416 | image_sC = interp_func(znew, xnew) 417 | 418 | return image_sC, znew, xnew 419 | 420 | 421 | def get_proc_rfdata(): 422 | h5f = h5py.File('example_us_bmode_sensor_data.h5', 'r') 423 | sensor_data = h5f['dataset_1'][:] 424 | 425 | # data get info 426 | record_length = sensor_data.shape[2] 427 | 428 | # time vector for data 429 | time = np.arange(record_length) / sample_rate - time_offset 430 | 431 | # transducer locations relative to the a-line, which is always centered 432 | xd = np.arange(n_probe_channels) * array_pitch 433 | xd = xd - np.max(xd) / 2 434 | 435 | # preprocessing - signal filtering, interpolation, and apodization 436 | preproc_data, time_shifted = preproc(sensor_data, time, xd) 437 | 438 | return preproc_data, time_shifted, xd 439 | 440 | 441 | def plot(images_proc, x_sc, z_sc): 442 | fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(10, 10)) 443 | 444 | ax1.imshow(images_proc[0], extent=[x_sc[0]*1e3, x_sc[-1]*1e3, 445 | z_sc[-1]*1e3, z_sc[0]*1e3], cmap='gray', 446 | interpolation='none') 447 | ax1.set_ylabel('Depth(mm)') 448 | ax1.set_xlabel('x(mm)') 449 | ax1.set_title('No beamforming') 450 | 451 | ax2.imshow(images_proc[1], extent=[x_sc[0]*1e3, x_sc[-1]*1e3, z_sc[-1]*1e3, 452 | z_sc[0]*1e3], cmap='gray', 453 | interpolation='none') 454 | ax2.set_ylabel('Depth(mm)') 455 | ax2.set_xlabel('x(mm)') 456 | ax2.set_title('Fixed Receive Focus at 15 mm') 457 | 458 | ax3.imshow(images_proc[2], extent=[x_sc[0]*1e3, x_sc[-1]*1e3, z_sc[-1]*1e3, 459 | z_sc[0]*1e3], cmap='gray', 460 | interpolation='none') 461 | ax3.set_ylabel('Depth(mm)') 462 | ax3.set_xlabel('x(mm)') 463 | ax3.set_title('Fixed Receive Focus at 30 mm') 464 | 465 | ax4.imshow(images_proc[3], extent=[x_sc[0]*1e3, x_sc[-1]*1e3, z_sc[-1]*1e3, 466 | z_sc[0]*1e3], cmap='gray', 467 | interpolation='none') 468 | ax4.set_ylabel('Depth(mm)') 469 | ax4.set_xlabel('x(mm)') 470 | ax4.set_title('Dynamic Focusing') 471 | 472 | plt.tight_layout() 473 | fig.savefig('./result.png') 474 | 475 | 476 | def main(): 477 | 478 | h5f = h5py.File('../example_us_bmode_sensor_data.h5', 'r') 479 | sensor_data = h5f['dataset_1'][:] 480 | 481 | logger.info(f'Data shape = {sensor_data.shape}') 482 | 483 | # data get info 484 | record_length = sensor_data.shape[2] 485 | 486 | # time vector for data 487 | time = np.arange(record_length) / sample_rate - time_offset 488 | 489 | # transducer locations relative to the a-line, which is always centered 490 | xd = np.arange(n_probe_channels) * array_pitch 491 | xd = xd - np.max(xd) / 2 492 | 493 | # preprocessing - signal filtering, interpolation, and apodization 494 | preproc_data, time_shifted = preproc(sensor_data, time, xd) 495 | 496 | logger.info(f'Preproc Data shape = {preproc_data.shape}') 497 | plot_steps = True 498 | if plot_steps: 499 | aline_ind = 15 500 | fig, ax = plt.subplots(figsize=(8, 6)) 501 | ax.plot(time, sensor_data[45, aline_ind, :]) 502 | ax.plot(time_shifted, preproc_data[45, aline_ind, :]) 503 | ax.set_ylim([-5000, 5000]) 504 | fig.savefig('./preproc.png', dpi=150) 505 | 506 | # B-mode image w/o beamforming (only use waveform from central element) 507 | image = preproc_data[:, 15, :] 508 | 509 | # beamforming with different receive focii 510 | receive_focus = 15e-3 511 | image_bf1 = beamform(preproc_data, time_shifted, xd, receive_focus) 512 | 513 | receive_focus = 30e-3 514 | image_bf2 = beamform(preproc_data, time_shifted, xd, receive_focus) 515 | 516 | # beamforming with dynamic focusing 517 | image_df = beamform_df(preproc_data, time_shifted, xd) 518 | 519 | logger.info(f'Beamformed Data shape = {image_df.shape}') 520 | logger.info(f'Beamformed data sum = {np.sum(image_df[0, :])}') 521 | 522 | images = (image, image_bf1, image_bf2, image_df) 523 | z = time_shifted*speed_sound/2 524 | 525 | # lateral locations of beamformed a-lines 526 | xd2 = np.arange(n_transmit_beams) * array_pitch 527 | xd2 = xd2 - np.max(xd2)/2 528 | 529 | # post process all images generated 530 | images_proc = [] 531 | for n, im in enumerate(images): 532 | 533 | # define portion of image you want to display 534 | # includes nullifying beginning of image containing transmission pulse 535 | f = np.where(z < 5e-3)[0] 536 | z_trunc = np.delete(z, f) 537 | im_trunc = im[:, f[-1]+1:] 538 | 539 | # envelope detection 540 | im_trunc_orig = im_trunc.copy() 541 | for m in range(n_transmit_beams): 542 | im_trunc[m, :] = envel_detect(im_trunc[m, :], 2*z_trunc/speed_sound, 543 | method='hilbert') 544 | 545 | # log compression and scan conversion 546 | DR = 35 # dynamic range - units of dB 547 | reject = 0 # rejection level - units of dB 548 | BG = 0 # brightness gain - units of dB 549 | image_log = log_compress(im_trunc, DR, reject, BG) 550 | 551 | # convert to 512x512 image 552 | image_sc, z_sc, x_sc = scan_convert(image_log, xd2, z_trunc) 553 | 554 | if n == 3: 555 | if plot_steps: 556 | # plot some intermediate steps for the beamformed image 557 | # plot some intermediate steps 558 | fig, ax = plt.subplots(); 559 | ax.plot(im_trunc_orig[aline_ind, :]); 560 | ax.plot(im_trunc[aline_ind, :]); 561 | fig.savefig('./envelope.png') 562 | logger.info(f'Envelope detected Data shape = {im_trunc.shape}') 563 | 564 | fig, ax = plt.subplots(); 565 | ax.plot(image_log[aline_ind, :]) 566 | fig.savefig('./img_log_slice.png') 567 | logger.info(f'Scan converted Data shape = {image_sc.shape}') 568 | 569 | image_sc2 = np.round(255*image_sc/DR) # convert to 8-bit grayscale 570 | image_sc3 = image_sc2.astype('int') 571 | images_proc.append(np.transpose(image_sc3)) 572 | 573 | plot(images_proc, x_sc, z_sc) 574 | 575 | 576 | if __name__ == '__main__': 577 | main() 578 | -------------------------------------------------------------------------------- /py/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csheaff/us-beamform-linarray/cbfd29f7a6a8f226860167c5960cfa6646246f11/py/result.png -------------------------------------------------------------------------------- /rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.14.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "adler" 14 | version = "0.2.3" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 17 | 18 | [[package]] 19 | name = "adler32" 20 | version = "1.1.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" 23 | 24 | [[package]] 25 | name = "aho-corasick" 26 | version = "0.5.3" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 29 | dependencies = [ 30 | "memchr 0.1.11", 31 | ] 32 | 33 | [[package]] 34 | name = "aho-corasick" 35 | version = "0.7.13" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 38 | dependencies = [ 39 | "memchr 2.3.3", 40 | ] 41 | 42 | [[package]] 43 | name = "anyhow" 44 | version = "1.0.35" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" 47 | 48 | [[package]] 49 | name = "approx" 50 | version = "0.3.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" 53 | dependencies = [ 54 | "num-traits", 55 | ] 56 | 57 | [[package]] 58 | name = "arrayref" 59 | version = "0.3.6" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 62 | 63 | [[package]] 64 | name = "arrayvec" 65 | version = "0.5.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 68 | 69 | [[package]] 70 | name = "ascii" 71 | version = "1.0.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" 74 | 75 | [[package]] 76 | name = "autocfg" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 80 | 81 | [[package]] 82 | name = "backtrace" 83 | version = "0.3.55" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" 86 | dependencies = [ 87 | "addr2line", 88 | "cfg-if 1.0.0", 89 | "libc", 90 | "miniz_oxide 0.4.3", 91 | "object", 92 | "rustc-demangle", 93 | ] 94 | 95 | [[package]] 96 | name = "base-x" 97 | version = "0.2.8" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" 100 | 101 | [[package]] 102 | name = "base64" 103 | version = "0.13.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 106 | 107 | [[package]] 108 | name = "basic_dsp" 109 | version = "0.9.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "460d458f6d0db1dc7016b397b8a0ea715d485b6ba5e0ea05b27f66515b9479ad" 112 | dependencies = [ 113 | "basic_dsp_matrix", 114 | "basic_dsp_vector", 115 | ] 116 | 117 | [[package]] 118 | name = "basic_dsp_matrix" 119 | version = "0.9.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "6071b80ed880f219b36f64d3758e755badd42d2998a436713b3ec74a18a77f75" 122 | dependencies = [ 123 | "basic_dsp_vector", 124 | ] 125 | 126 | [[package]] 127 | name = "basic_dsp_vector" 128 | version = "0.9.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "bb9517050b9e71aae25f338decb55a3561e582da9ec4038a24066846f172c5e8" 131 | dependencies = [ 132 | "arrayvec", 133 | "crossbeam", 134 | "lazy_static 1.4.0", 135 | "linreg", 136 | "num-complex 0.3.1", 137 | "num-traits", 138 | "num_cpus", 139 | "rustfft", 140 | "time 0.2.23", 141 | ] 142 | 143 | [[package]] 144 | name = "bitflags" 145 | version = "1.2.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 148 | 149 | [[package]] 150 | name = "blake2b_simd" 151 | version = "0.5.11" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 154 | dependencies = [ 155 | "arrayref", 156 | "arrayvec", 157 | "constant_time_eq", 158 | ] 159 | 160 | [[package]] 161 | name = "blas-src" 162 | version = "0.2.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "a7b894d1cc14af76750a80f64387f7986fe93a9d3786a9a8926a340ae50b2c51" 165 | dependencies = [ 166 | "openblas-src", 167 | ] 168 | 169 | [[package]] 170 | name = "blas-src" 171 | version = "0.6.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a25f136d1bcc6fc28330077511a0646aaf75bc894e5532b1a33a93d814272328" 174 | 175 | [[package]] 176 | name = "bumpalo" 177 | version = "3.4.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 180 | 181 | [[package]] 182 | name = "bytemuck" 183 | version = "1.2.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" 186 | 187 | [[package]] 188 | name = "byteorder" 189 | version = "1.3.4" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 192 | 193 | [[package]] 194 | name = "bzip2" 195 | version = "0.3.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" 198 | dependencies = [ 199 | "bzip2-sys", 200 | "libc", 201 | ] 202 | 203 | [[package]] 204 | name = "bzip2-sys" 205 | version = "0.1.9+1.0.8" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "ad3b39a260062fca31f7b0b12f207e8f2590a67d32ec7d59c20484b07ea7285e" 208 | dependencies = [ 209 | "cc", 210 | "libc", 211 | "pkg-config", 212 | ] 213 | 214 | [[package]] 215 | name = "cauchy" 216 | version = "0.2.2" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "9c505375a294ec7cf79430c4ccb91ee1fab5f6390da3a264932fd303832a2de7" 219 | dependencies = [ 220 | "num-complex 0.2.4", 221 | "num-traits", 222 | "rand 0.5.6", 223 | "serde", 224 | ] 225 | 226 | [[package]] 227 | name = "cblas-sys" 228 | version = "0.1.4" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b6feecd82cce51b0204cf063f0041d69f24ce83f680d87514b004248e7b0fa65" 231 | dependencies = [ 232 | "libc", 233 | ] 234 | 235 | [[package]] 236 | name = "cc" 237 | version = "1.0.66" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 240 | dependencies = [ 241 | "jobserver", 242 | ] 243 | 244 | [[package]] 245 | name = "cfg-if" 246 | version = "0.1.10" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 249 | 250 | [[package]] 251 | name = "cfg-if" 252 | version = "1.0.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 255 | 256 | [[package]] 257 | name = "chrono" 258 | version = "0.2.25" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" 261 | dependencies = [ 262 | "num", 263 | "time 0.1.44", 264 | ] 265 | 266 | [[package]] 267 | name = "chrono" 268 | version = "0.4.19" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 271 | dependencies = [ 272 | "libc", 273 | "num-integer", 274 | "num-traits", 275 | "time 0.1.44", 276 | "winapi 0.3.9", 277 | ] 278 | 279 | [[package]] 280 | name = "clang" 281 | version = "1.0.3" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "34c6913be3a1c94f52fb975cdec7ef5a7b69de10a55de66dcbc30d7046b85fa1" 284 | dependencies = [ 285 | "clang-sys", 286 | "libc", 287 | ] 288 | 289 | [[package]] 290 | name = "clang-sys" 291 | version = "1.0.3" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c" 294 | dependencies = [ 295 | "glob", 296 | "libc", 297 | ] 298 | 299 | [[package]] 300 | name = "cloudabi" 301 | version = "0.0.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 304 | dependencies = [ 305 | "bitflags", 306 | ] 307 | 308 | [[package]] 309 | name = "color_quant" 310 | version = "1.0.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 313 | 314 | [[package]] 315 | name = "const_fn" 316 | version = "0.4.3" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" 319 | 320 | [[package]] 321 | name = "constant_time_eq" 322 | version = "0.1.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 325 | 326 | [[package]] 327 | name = "crc32fast" 328 | version = "1.2.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 331 | dependencies = [ 332 | "cfg-if 0.1.10", 333 | ] 334 | 335 | [[package]] 336 | name = "crossbeam" 337 | version = "0.7.3" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" 340 | dependencies = [ 341 | "cfg-if 0.1.10", 342 | "crossbeam-channel", 343 | "crossbeam-deque", 344 | "crossbeam-epoch", 345 | "crossbeam-queue", 346 | "crossbeam-utils 0.7.2", 347 | ] 348 | 349 | [[package]] 350 | name = "crossbeam-channel" 351 | version = "0.4.4" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" 354 | dependencies = [ 355 | "crossbeam-utils 0.7.2", 356 | "maybe-uninit", 357 | ] 358 | 359 | [[package]] 360 | name = "crossbeam-deque" 361 | version = "0.7.3" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 364 | dependencies = [ 365 | "crossbeam-epoch", 366 | "crossbeam-utils 0.7.2", 367 | "maybe-uninit", 368 | ] 369 | 370 | [[package]] 371 | name = "crossbeam-epoch" 372 | version = "0.8.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 375 | dependencies = [ 376 | "autocfg", 377 | "cfg-if 0.1.10", 378 | "crossbeam-utils 0.7.2", 379 | "lazy_static 1.4.0", 380 | "maybe-uninit", 381 | "memoffset", 382 | "scopeguard", 383 | ] 384 | 385 | [[package]] 386 | name = "crossbeam-queue" 387 | version = "0.2.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 390 | dependencies = [ 391 | "cfg-if 0.1.10", 392 | "crossbeam-utils 0.7.2", 393 | "maybe-uninit", 394 | ] 395 | 396 | [[package]] 397 | name = "crossbeam-utils" 398 | version = "0.7.2" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 401 | dependencies = [ 402 | "autocfg", 403 | "cfg-if 0.1.10", 404 | "lazy_static 1.4.0", 405 | ] 406 | 407 | [[package]] 408 | name = "crossbeam-utils" 409 | version = "0.8.1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" 412 | dependencies = [ 413 | "autocfg", 414 | "cfg-if 1.0.0", 415 | "lazy_static 1.4.0", 416 | ] 417 | 418 | [[package]] 419 | name = "deflate" 420 | version = "0.8.4" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" 423 | dependencies = [ 424 | "adler32", 425 | "byteorder", 426 | ] 427 | 428 | [[package]] 429 | name = "dirs" 430 | version = "2.0.2" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 433 | dependencies = [ 434 | "cfg-if 0.1.10", 435 | "dirs-sys", 436 | ] 437 | 438 | [[package]] 439 | name = "dirs-sys" 440 | version = "0.3.5" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 443 | dependencies = [ 444 | "libc", 445 | "redox_users", 446 | "winapi 0.3.9", 447 | ] 448 | 449 | [[package]] 450 | name = "discard" 451 | version = "1.0.4" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 454 | 455 | [[package]] 456 | name = "displaydoc" 457 | version = "0.1.7" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "syn", 464 | ] 465 | 466 | [[package]] 467 | name = "dunce" 468 | version = "1.0.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "b2641c4a7c0c4101df53ea572bffdc561c146f6c2eb09e4df02bc4811e3feeb4" 471 | 472 | [[package]] 473 | name = "either" 474 | version = "1.5.3" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 477 | 478 | [[package]] 479 | name = "failure" 480 | version = "0.1.8" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 483 | dependencies = [ 484 | "backtrace", 485 | "failure_derive", 486 | ] 487 | 488 | [[package]] 489 | name = "failure_derive" 490 | version = "0.1.8" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 493 | dependencies = [ 494 | "proc-macro2", 495 | "quote", 496 | "syn", 497 | "synstructure", 498 | ] 499 | 500 | [[package]] 501 | name = "fftw" 502 | version = "0.6.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "9a61025ae24e60113038885a9ad24ef67b3f4df0b4300c98678c63e8ce3297f8" 505 | dependencies = [ 506 | "bitflags", 507 | "failure", 508 | "fftw-sys", 509 | "lazy_static 1.4.0", 510 | "ndarray 0.12.1", 511 | "num-complex 0.2.4", 512 | "num-traits", 513 | ] 514 | 515 | [[package]] 516 | name = "fftw-src" 517 | version = "0.3.3" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "c08962470ab0e91e74ec7d338c8731476c28ed4e503a3080b0f001692e395a7c" 520 | dependencies = [ 521 | "anyhow", 522 | "cc", 523 | "fs_extra", 524 | "ftp", 525 | "zip", 526 | ] 527 | 528 | [[package]] 529 | name = "fftw-sys" 530 | version = "0.5.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "9489c013030134b2e49cdda9bee90ff9c3c23ae5330650cb84e10b6927b9ec66" 533 | dependencies = [ 534 | "fftw-src", 535 | "libc", 536 | "num-complex 0.2.4", 537 | ] 538 | 539 | [[package]] 540 | name = "flate2" 541 | version = "1.0.14" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" 544 | dependencies = [ 545 | "cfg-if 0.1.10", 546 | "crc32fast", 547 | "libc", 548 | "miniz_oxide 0.3.7", 549 | ] 550 | 551 | [[package]] 552 | name = "fs_extra" 553 | version = "1.2.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" 556 | 557 | [[package]] 558 | name = "ftp" 559 | version = "3.0.1" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "542951aad0071952c27409e3bd7cb62d1a3ad419c4e7314106bf994e0083ad5d" 562 | dependencies = [ 563 | "chrono 0.2.25", 564 | "lazy_static 0.1.16", 565 | "regex 0.1.80", 566 | ] 567 | 568 | [[package]] 569 | name = "fuchsia-cprng" 570 | version = "0.1.1" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 573 | 574 | [[package]] 575 | name = "getrandom" 576 | version = "0.1.14" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 579 | dependencies = [ 580 | "cfg-if 0.1.10", 581 | "libc", 582 | "wasi 0.9.0+wasi-snapshot-preview1", 583 | ] 584 | 585 | [[package]] 586 | name = "gif" 587 | version = "0.10.3" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 590 | dependencies = [ 591 | "color_quant", 592 | "lzw", 593 | ] 594 | 595 | [[package]] 596 | name = "gimli" 597 | version = "0.23.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" 600 | 601 | [[package]] 602 | name = "glob" 603 | version = "0.3.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 606 | 607 | [[package]] 608 | name = "hdf5" 609 | version = "0.6.1" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "0f12248857f9a2b720a15a722aa16e078a90fcadc8427c1139ce803db3ab375e" 612 | dependencies = [ 613 | "bitflags", 614 | "hdf5-derive", 615 | "hdf5-sys", 616 | "hdf5-types", 617 | "lazy_static 1.4.0", 618 | "libc", 619 | "ndarray 0.13.1", 620 | "num-integer", 621 | "num-traits", 622 | "parking_lot", 623 | ] 624 | 625 | [[package]] 626 | name = "hdf5-derive" 627 | version = "0.6.1" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "93c8c8de0275de9d1d60283cba92148e5fa3f500b281be1540b06c030b7b4819" 630 | dependencies = [ 631 | "proc-macro2", 632 | "quote", 633 | "syn", 634 | ] 635 | 636 | [[package]] 637 | name = "hdf5-sys" 638 | version = "0.6.1" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "8687f1f9a4c9f2a3e791f1515e264459288f64bc743db95c077d6e968cdca3ca" 641 | dependencies = [ 642 | "libc", 643 | "libloading", 644 | "pkg-config", 645 | "regex 1.3.9", 646 | "serde", 647 | "serde_derive", 648 | "winreg", 649 | ] 650 | 651 | [[package]] 652 | name = "hdf5-types" 653 | version = "0.6.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "dd18651c0898fd7fb0da9e16095a98e7cc49bfa2d3fb6b6d4ed704e68393e5ba" 656 | dependencies = [ 657 | "ascii", 658 | "libc", 659 | ] 660 | 661 | [[package]] 662 | name = "hermit-abi" 663 | version = "0.1.14" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" 666 | dependencies = [ 667 | "libc", 668 | ] 669 | 670 | [[package]] 671 | name = "image" 672 | version = "0.23.6" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4" 675 | dependencies = [ 676 | "bytemuck", 677 | "byteorder", 678 | "gif", 679 | "jpeg-decoder", 680 | "num-iter", 681 | "num-rational", 682 | "num-traits", 683 | "png", 684 | "scoped_threadpool", 685 | "tiff", 686 | ] 687 | 688 | [[package]] 689 | name = "indexmap" 690 | version = "1.4.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" 693 | dependencies = [ 694 | "autocfg", 695 | ] 696 | 697 | [[package]] 698 | name = "itertools" 699 | version = "0.7.11" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" 702 | dependencies = [ 703 | "either", 704 | ] 705 | 706 | [[package]] 707 | name = "itertools" 708 | version = "0.8.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" 711 | dependencies = [ 712 | "either", 713 | ] 714 | 715 | [[package]] 716 | name = "itoa" 717 | version = "0.4.6" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 720 | 721 | [[package]] 722 | name = "jobserver" 723 | version = "0.1.21" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" 726 | dependencies = [ 727 | "libc", 728 | ] 729 | 730 | [[package]] 731 | name = "jpeg-decoder" 732 | version = "0.1.19" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "5b47b4c4e017b01abdc5bcc126d2d1002e5a75bbe3ce73f9f4f311a916363704" 735 | dependencies = [ 736 | "byteorder", 737 | "rayon", 738 | ] 739 | 740 | [[package]] 741 | name = "kernel32-sys" 742 | version = "0.2.2" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 745 | dependencies = [ 746 | "winapi 0.2.8", 747 | "winapi-build", 748 | ] 749 | 750 | [[package]] 751 | name = "lapack-src" 752 | version = "0.6.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "92b348b4b770b1092add976188242941b92272f3bad95cfbacadbfcb0c8923eb" 755 | 756 | [[package]] 757 | name = "lapacke" 758 | version = "0.2.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "4bce9d3464fe3be72376bcbe3dc4df22dd368ca228b62e8415d2c7b09bb58677" 761 | dependencies = [ 762 | "lapacke-sys", 763 | "libc", 764 | "num-complex 0.2.4", 765 | ] 766 | 767 | [[package]] 768 | name = "lapacke-sys" 769 | version = "0.1.4" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "2f7d0817c6f4a6029f3b153de01d6498dcf9df659a7536c58bd8df5cd3ccaa6e" 772 | dependencies = [ 773 | "libc", 774 | ] 775 | 776 | [[package]] 777 | name = "lazy_static" 778 | version = "0.1.16" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" 781 | 782 | [[package]] 783 | name = "lazy_static" 784 | version = "1.4.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 787 | 788 | [[package]] 789 | name = "libc" 790 | version = "0.2.71" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 793 | 794 | [[package]] 795 | name = "libloading" 796 | version = "0.6.2" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" 799 | dependencies = [ 800 | "winapi 0.3.9", 801 | ] 802 | 803 | [[package]] 804 | name = "linreg" 805 | version = "0.2.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "f8c6fd2f0b9338b6d298dbcee4b4ec2a6ec2e88dc0a5f6f92d5cecd65afdf445" 808 | dependencies = [ 809 | "displaydoc", 810 | "num-traits", 811 | ] 812 | 813 | [[package]] 814 | name = "lock_api" 815 | version = "0.3.4" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 818 | dependencies = [ 819 | "scopeguard", 820 | ] 821 | 822 | [[package]] 823 | name = "log" 824 | version = "0.4.11" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 827 | dependencies = [ 828 | "cfg-if 0.1.10", 829 | ] 830 | 831 | [[package]] 832 | name = "lzw" 833 | version = "0.10.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 836 | 837 | [[package]] 838 | name = "maplit" 839 | version = "1.0.2" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 842 | 843 | [[package]] 844 | name = "matrixmultiply" 845 | version = "0.1.15" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" 848 | dependencies = [ 849 | "rawpointer 0.1.0", 850 | ] 851 | 852 | [[package]] 853 | name = "matrixmultiply" 854 | version = "0.2.3" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "d4f7ec66360130972f34830bfad9ef05c6610a43938a467bcc9ab9369ab3478f" 857 | dependencies = [ 858 | "rawpointer 0.2.1", 859 | ] 860 | 861 | [[package]] 862 | name = "maybe-uninit" 863 | version = "2.0.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 866 | 867 | [[package]] 868 | name = "memchr" 869 | version = "0.1.11" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 872 | dependencies = [ 873 | "libc", 874 | ] 875 | 876 | [[package]] 877 | name = "memchr" 878 | version = "2.3.3" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 881 | 882 | [[package]] 883 | name = "memoffset" 884 | version = "0.5.4" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 887 | dependencies = [ 888 | "autocfg", 889 | ] 890 | 891 | [[package]] 892 | name = "miniz_oxide" 893 | version = "0.3.7" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 896 | dependencies = [ 897 | "adler32", 898 | ] 899 | 900 | [[package]] 901 | name = "miniz_oxide" 902 | version = "0.4.3" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" 905 | dependencies = [ 906 | "adler", 907 | "autocfg", 908 | ] 909 | 910 | [[package]] 911 | name = "ndarray" 912 | version = "0.12.1" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "7cf380a8af901ad627594013a3bbac903ae0a6f94e176e47e46b5bbc1877b928" 915 | dependencies = [ 916 | "itertools 0.7.11", 917 | "matrixmultiply 0.1.15", 918 | "num-complex 0.2.4", 919 | "num-traits", 920 | ] 921 | 922 | [[package]] 923 | name = "ndarray" 924 | version = "0.13.1" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" 927 | dependencies = [ 928 | "approx", 929 | "blas-src 0.2.1", 930 | "cblas-sys", 931 | "matrixmultiply 0.2.3", 932 | "num-complex 0.2.4", 933 | "num-integer", 934 | "num-traits", 935 | "rawpointer 0.2.1", 936 | ] 937 | 938 | [[package]] 939 | name = "ndarray-linalg" 940 | version = "0.12.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "6892e51e159690d3d3d9fe2b052ff80b638c7d0f7ce634818c84ab7aea09e01c" 943 | dependencies = [ 944 | "blas-src 0.6.1", 945 | "cauchy", 946 | "lapack-src", 947 | "lapacke", 948 | "ndarray 0.13.1", 949 | "num-complex 0.2.4", 950 | "num-traits", 951 | "rand 0.5.6", 952 | ] 953 | 954 | [[package]] 955 | name = "ndarray-stats" 956 | version = "0.3.0" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "0e3da4b73160713dbd0f05af78d31e398b9a9edf70b8c94551f668477dc70618" 959 | dependencies = [ 960 | "indexmap", 961 | "itertools 0.8.2", 962 | "ndarray 0.13.1", 963 | "noisy_float", 964 | "num-integer", 965 | "num-traits", 966 | "rand 0.7.3", 967 | ] 968 | 969 | [[package]] 970 | name = "noisy_float" 971 | version = "0.1.12" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "b1b3e1bd47a70dc23b4b1d4c7dabe09135cf86a5eb2ad5a60c853cb69c64d32e" 974 | dependencies = [ 975 | "num-traits", 976 | ] 977 | 978 | [[package]] 979 | name = "num" 980 | version = "0.1.42" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 983 | dependencies = [ 984 | "num-integer", 985 | "num-iter", 986 | "num-traits", 987 | ] 988 | 989 | [[package]] 990 | name = "num-complex" 991 | version = "0.2.4" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" 994 | dependencies = [ 995 | "autocfg", 996 | "num-traits", 997 | "rand 0.5.6", 998 | "serde", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "num-complex" 1003 | version = "0.3.1" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" 1006 | dependencies = [ 1007 | "num-traits", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "num-integer" 1012 | version = "0.1.43" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 1015 | dependencies = [ 1016 | "autocfg", 1017 | "num-traits", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "num-iter" 1022 | version = "0.1.41" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" 1025 | dependencies = [ 1026 | "autocfg", 1027 | "num-integer", 1028 | "num-traits", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "num-rational" 1033 | version = "0.3.0" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" 1036 | dependencies = [ 1037 | "autocfg", 1038 | "num-integer", 1039 | "num-traits", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "num-traits" 1044 | version = "0.2.12" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 1047 | dependencies = [ 1048 | "autocfg", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "num_cpus" 1053 | version = "1.13.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 1056 | dependencies = [ 1057 | "hermit-abi", 1058 | "libc", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "object" 1063 | version = "0.22.0" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" 1066 | 1067 | [[package]] 1068 | name = "once_cell" 1069 | version = "1.5.2" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 1072 | 1073 | [[package]] 1074 | name = "openblas-src" 1075 | version = "0.6.1" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "af5265a3d795f0624d3e3756cb484da70f8bfbf409ce23d9035ffc8574297aad" 1078 | 1079 | [[package]] 1080 | name = "opencv" 1081 | version = "0.46.3" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "aaceb178970cea3be8e8d7a1554d496092be90847cf1cccc846bfa118a63f9ce" 1084 | dependencies = [ 1085 | "cc", 1086 | "clang", 1087 | "dunce", 1088 | "glob", 1089 | "jobserver", 1090 | "libc", 1091 | "num-traits", 1092 | "once_cell", 1093 | "opencv-binding-generator", 1094 | "pkg-config", 1095 | "semver 0.10.0", 1096 | "shlex", 1097 | "vcpkg", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "opencv-binding-generator" 1102 | version = "0.21.3" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "a2f60ec2ee0b06d1266f37d7c083417412858410fd6e91b1353fb69ac797d8cf" 1105 | dependencies = [ 1106 | "clang", 1107 | "clang-sys", 1108 | "dunce", 1109 | "maplit", 1110 | "once_cell", 1111 | "percent-encoding", 1112 | "regex 1.3.9", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "parking_lot" 1117 | version = "0.10.2" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 1120 | dependencies = [ 1121 | "lock_api", 1122 | "parking_lot_core", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "parking_lot_core" 1127 | version = "0.7.2" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 1130 | dependencies = [ 1131 | "cfg-if 0.1.10", 1132 | "cloudabi", 1133 | "libc", 1134 | "redox_syscall", 1135 | "smallvec", 1136 | "winapi 0.3.9", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "percent-encoding" 1141 | version = "2.1.0" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1144 | 1145 | [[package]] 1146 | name = "pkg-config" 1147 | version = "0.3.17" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 1150 | 1151 | [[package]] 1152 | name = "plotlib" 1153 | version = "0.5.1" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "9462104f987d8d0f6625f0c7764f1c8b890bd1dc8584d8293e031f25c5a0d242" 1156 | dependencies = [ 1157 | "failure", 1158 | "svg", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "png" 1163 | version = "0.16.6" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "c150bf7479fafe3dd8740dbe48cc33b2a3efb7b0fe3483aced8bbc39f6d0238d" 1166 | dependencies = [ 1167 | "bitflags", 1168 | "crc32fast", 1169 | "deflate", 1170 | "miniz_oxide 0.3.7", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "ppv-lite86" 1175 | version = "0.2.8" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 1178 | 1179 | [[package]] 1180 | name = "proc-macro-hack" 1181 | version = "0.5.19" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 1184 | 1185 | [[package]] 1186 | name = "proc-macro2" 1187 | version = "1.0.24" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 1190 | dependencies = [ 1191 | "unicode-xid", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "quote" 1196 | version = "1.0.7" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1199 | dependencies = [ 1200 | "proc-macro2", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "rand" 1205 | version = "0.5.6" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" 1208 | dependencies = [ 1209 | "cloudabi", 1210 | "fuchsia-cprng", 1211 | "libc", 1212 | "rand_core 0.3.1", 1213 | "winapi 0.3.9", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "rand" 1218 | version = "0.7.3" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1221 | dependencies = [ 1222 | "getrandom", 1223 | "libc", 1224 | "rand_chacha", 1225 | "rand_core 0.5.1", 1226 | "rand_hc", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "rand_chacha" 1231 | version = "0.2.2" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1234 | dependencies = [ 1235 | "ppv-lite86", 1236 | "rand_core 0.5.1", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "rand_core" 1241 | version = "0.3.1" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1244 | dependencies = [ 1245 | "rand_core 0.4.2", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "rand_core" 1250 | version = "0.4.2" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1253 | 1254 | [[package]] 1255 | name = "rand_core" 1256 | version = "0.5.1" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1259 | dependencies = [ 1260 | "getrandom", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "rand_hc" 1265 | version = "0.2.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1268 | dependencies = [ 1269 | "rand_core 0.5.1", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "rawpointer" 1274 | version = "0.1.0" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" 1277 | 1278 | [[package]] 1279 | name = "rawpointer" 1280 | version = "0.2.1" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 1283 | 1284 | [[package]] 1285 | name = "rayon" 1286 | version = "1.3.1" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" 1289 | dependencies = [ 1290 | "autocfg", 1291 | "crossbeam-deque", 1292 | "either", 1293 | "rayon-core", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "rayon-core" 1298 | version = "1.7.1" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" 1301 | dependencies = [ 1302 | "crossbeam-deque", 1303 | "crossbeam-queue", 1304 | "crossbeam-utils 0.7.2", 1305 | "lazy_static 1.4.0", 1306 | "num_cpus", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "redox_syscall" 1311 | version = "0.1.56" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1314 | 1315 | [[package]] 1316 | name = "redox_users" 1317 | version = "0.3.5" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 1320 | dependencies = [ 1321 | "getrandom", 1322 | "redox_syscall", 1323 | "rust-argon2", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "regex" 1328 | version = "0.1.80" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 1331 | dependencies = [ 1332 | "aho-corasick 0.5.3", 1333 | "memchr 0.1.11", 1334 | "regex-syntax 0.3.9", 1335 | "thread_local 0.2.7", 1336 | "utf8-ranges", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "regex" 1341 | version = "1.3.9" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1344 | dependencies = [ 1345 | "aho-corasick 0.7.13", 1346 | "memchr 2.3.3", 1347 | "regex-syntax 0.6.18", 1348 | "thread_local 1.0.1", 1349 | ] 1350 | 1351 | [[package]] 1352 | name = "regex-syntax" 1353 | version = "0.3.9" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 1356 | 1357 | [[package]] 1358 | name = "regex-syntax" 1359 | version = "0.6.18" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1362 | 1363 | [[package]] 1364 | name = "rust-argon2" 1365 | version = "0.8.3" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" 1368 | dependencies = [ 1369 | "base64", 1370 | "blake2b_simd", 1371 | "constant_time_eq", 1372 | "crossbeam-utils 0.8.1", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "rustc-demangle" 1377 | version = "0.1.18" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" 1380 | 1381 | [[package]] 1382 | name = "rustc_version" 1383 | version = "0.2.3" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1386 | dependencies = [ 1387 | "semver 0.9.0", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "rustfft" 1392 | version = "4.0.0" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "b422f7370bf3092b1a5f10b68f7b4719c964879836a4bd026a410f11dde2ad0d" 1395 | dependencies = [ 1396 | "num-complex 0.3.1", 1397 | "num-integer", 1398 | "num-traits", 1399 | "strength_reduce", 1400 | "transpose", 1401 | ] 1402 | 1403 | [[package]] 1404 | name = "ryu" 1405 | version = "1.0.5" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1408 | 1409 | [[package]] 1410 | name = "scoped_threadpool" 1411 | version = "0.1.9" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 1414 | 1415 | [[package]] 1416 | name = "scopeguard" 1417 | version = "1.1.0" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1420 | 1421 | [[package]] 1422 | name = "semver" 1423 | version = "0.9.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1426 | dependencies = [ 1427 | "semver-parser", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "semver" 1432 | version = "0.10.0" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "394cec28fa623e00903caf7ba4fa6fb9a0e260280bb8cdbbba029611108a0190" 1435 | dependencies = [ 1436 | "semver-parser", 1437 | ] 1438 | 1439 | [[package]] 1440 | name = "semver-parser" 1441 | version = "0.7.0" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1444 | 1445 | [[package]] 1446 | name = "serde" 1447 | version = "1.0.114" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 1450 | 1451 | [[package]] 1452 | name = "serde_derive" 1453 | version = "1.0.114" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 1456 | dependencies = [ 1457 | "proc-macro2", 1458 | "quote", 1459 | "syn", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "serde_json" 1464 | version = "1.0.59" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 1467 | dependencies = [ 1468 | "itoa", 1469 | "ryu", 1470 | "serde", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "sha1" 1475 | version = "0.6.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 1478 | 1479 | [[package]] 1480 | name = "shlex" 1481 | version = "0.1.1" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 1484 | 1485 | [[package]] 1486 | name = "simplelog" 1487 | version = "0.7.6" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "3cf9a002ccce717d066b3ccdb8a28829436249867229291e91b25d99bd723f0d" 1490 | dependencies = [ 1491 | "chrono 0.4.19", 1492 | "log", 1493 | "term", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "smallvec" 1498 | version = "1.4.0" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 1501 | 1502 | [[package]] 1503 | name = "standback" 1504 | version = "0.2.13" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" 1507 | dependencies = [ 1508 | "version_check", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "stdweb" 1513 | version = "0.4.20" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" 1516 | dependencies = [ 1517 | "discard", 1518 | "rustc_version", 1519 | "stdweb-derive", 1520 | "stdweb-internal-macros", 1521 | "stdweb-internal-runtime", 1522 | "wasm-bindgen", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "stdweb-derive" 1527 | version = "0.5.3" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" 1530 | dependencies = [ 1531 | "proc-macro2", 1532 | "quote", 1533 | "serde", 1534 | "serde_derive", 1535 | "syn", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "stdweb-internal-macros" 1540 | version = "0.2.9" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" 1543 | dependencies = [ 1544 | "base-x", 1545 | "proc-macro2", 1546 | "quote", 1547 | "serde", 1548 | "serde_derive", 1549 | "serde_json", 1550 | "sha1", 1551 | "syn", 1552 | ] 1553 | 1554 | [[package]] 1555 | name = "stdweb-internal-runtime" 1556 | version = "0.1.5" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 1559 | 1560 | [[package]] 1561 | name = "strength_reduce" 1562 | version = "0.2.3" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254" 1565 | 1566 | [[package]] 1567 | name = "svg" 1568 | version = "0.7.2" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "3685c82a045a6af0c488f0550b0f52b4c77d2a52b0ca8aba719f9d268fa96965" 1571 | 1572 | [[package]] 1573 | name = "syn" 1574 | version = "1.0.52" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "6c1e438504729046a5cfae47f97c30d6d083c7d91d94603efdae3477fc070d4c" 1577 | dependencies = [ 1578 | "proc-macro2", 1579 | "quote", 1580 | "unicode-xid", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "synstructure" 1585 | version = "0.12.4" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 1588 | dependencies = [ 1589 | "proc-macro2", 1590 | "quote", 1591 | "syn", 1592 | "unicode-xid", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "term" 1597 | version = "0.6.1" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" 1600 | dependencies = [ 1601 | "dirs", 1602 | "winapi 0.3.9", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "thiserror" 1607 | version = "1.0.22" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" 1610 | dependencies = [ 1611 | "thiserror-impl", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "thiserror-impl" 1616 | version = "1.0.22" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" 1619 | dependencies = [ 1620 | "proc-macro2", 1621 | "quote", 1622 | "syn", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "thread-id" 1627 | version = "2.0.0" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 1630 | dependencies = [ 1631 | "kernel32-sys", 1632 | "libc", 1633 | ] 1634 | 1635 | [[package]] 1636 | name = "thread_local" 1637 | version = "0.2.7" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 1640 | dependencies = [ 1641 | "thread-id", 1642 | ] 1643 | 1644 | [[package]] 1645 | name = "thread_local" 1646 | version = "1.0.1" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1649 | dependencies = [ 1650 | "lazy_static 1.4.0", 1651 | ] 1652 | 1653 | [[package]] 1654 | name = "tiff" 1655 | version = "0.5.0" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" 1658 | dependencies = [ 1659 | "byteorder", 1660 | "lzw", 1661 | "miniz_oxide 0.3.7", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "time" 1666 | version = "0.1.44" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1669 | dependencies = [ 1670 | "libc", 1671 | "wasi 0.10.0+wasi-snapshot-preview1", 1672 | "winapi 0.3.9", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "time" 1677 | version = "0.2.23" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" 1680 | dependencies = [ 1681 | "const_fn", 1682 | "libc", 1683 | "standback", 1684 | "stdweb", 1685 | "time-macros", 1686 | "version_check", 1687 | "winapi 0.3.9", 1688 | ] 1689 | 1690 | [[package]] 1691 | name = "time-macros" 1692 | version = "0.1.1" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" 1695 | dependencies = [ 1696 | "proc-macro-hack", 1697 | "time-macros-impl", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "time-macros-impl" 1702 | version = "0.1.1" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" 1705 | dependencies = [ 1706 | "proc-macro-hack", 1707 | "proc-macro2", 1708 | "quote", 1709 | "standback", 1710 | "syn", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "transpose" 1715 | version = "0.2.0" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "c3311ef71dea6a1fd6bf5bfc10ec5b4bef6174048f6b481dbc6ce915ff48c0a0" 1718 | dependencies = [ 1719 | "num-integer", 1720 | "strength_reduce", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "unicode-xid" 1725 | version = "0.2.1" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1728 | 1729 | [[package]] 1730 | name = "us-beamform-linarray" 1731 | version = "0.1.0" 1732 | dependencies = [ 1733 | "basic_dsp", 1734 | "blas-src 0.2.1", 1735 | "fftw", 1736 | "fftw-src", 1737 | "hdf5", 1738 | "image", 1739 | "log", 1740 | "ndarray 0.13.1", 1741 | "ndarray-linalg", 1742 | "ndarray-stats", 1743 | "num-integer", 1744 | "openblas-src", 1745 | "opencv", 1746 | "plotlib", 1747 | "simplelog", 1748 | ] 1749 | 1750 | [[package]] 1751 | name = "utf8-ranges" 1752 | version = "0.1.3" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 1755 | 1756 | [[package]] 1757 | name = "vcpkg" 1758 | version = "0.2.11" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" 1761 | 1762 | [[package]] 1763 | name = "version_check" 1764 | version = "0.9.2" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1767 | 1768 | [[package]] 1769 | name = "wasi" 1770 | version = "0.9.0+wasi-snapshot-preview1" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1773 | 1774 | [[package]] 1775 | name = "wasi" 1776 | version = "0.10.0+wasi-snapshot-preview1" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1779 | 1780 | [[package]] 1781 | name = "wasm-bindgen" 1782 | version = "0.2.68" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" 1785 | dependencies = [ 1786 | "cfg-if 0.1.10", 1787 | "wasm-bindgen-macro", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "wasm-bindgen-backend" 1792 | version = "0.2.68" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" 1795 | dependencies = [ 1796 | "bumpalo", 1797 | "lazy_static 1.4.0", 1798 | "log", 1799 | "proc-macro2", 1800 | "quote", 1801 | "syn", 1802 | "wasm-bindgen-shared", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "wasm-bindgen-macro" 1807 | version = "0.2.68" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" 1810 | dependencies = [ 1811 | "quote", 1812 | "wasm-bindgen-macro-support", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "wasm-bindgen-macro-support" 1817 | version = "0.2.68" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" 1820 | dependencies = [ 1821 | "proc-macro2", 1822 | "quote", 1823 | "syn", 1824 | "wasm-bindgen-backend", 1825 | "wasm-bindgen-shared", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "wasm-bindgen-shared" 1830 | version = "0.2.68" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" 1833 | 1834 | [[package]] 1835 | name = "winapi" 1836 | version = "0.2.8" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1839 | 1840 | [[package]] 1841 | name = "winapi" 1842 | version = "0.3.9" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1845 | dependencies = [ 1846 | "winapi-i686-pc-windows-gnu", 1847 | "winapi-x86_64-pc-windows-gnu", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "winapi-build" 1852 | version = "0.1.1" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1855 | 1856 | [[package]] 1857 | name = "winapi-i686-pc-windows-gnu" 1858 | version = "0.4.0" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1861 | 1862 | [[package]] 1863 | name = "winapi-x86_64-pc-windows-gnu" 1864 | version = "0.4.0" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1867 | 1868 | [[package]] 1869 | name = "winreg" 1870 | version = "0.7.0" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1873 | dependencies = [ 1874 | "serde", 1875 | "winapi 0.3.9", 1876 | ] 1877 | 1878 | [[package]] 1879 | name = "zip" 1880 | version = "0.5.9" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "cc2896475a242c41366941faa27264df2cb935185a92e059a004d0048feb2ac5" 1883 | dependencies = [ 1884 | "byteorder", 1885 | "bzip2", 1886 | "crc32fast", 1887 | "flate2", 1888 | "thiserror", 1889 | "time 0.1.44", 1890 | ] 1891 | -------------------------------------------------------------------------------- /rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "us-beamform-linarray" 3 | version = "0.1.0" 4 | authors = ["Clay Sheaff "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ndarray = { version = "0.13.1", features = ["blas"] } 11 | ndarray-linalg = { version = "0.12"} 12 | ndarray-stats = { version = "0.3"} 13 | num-integer = "0.1" 14 | blas-src = { version = "0.2.0", default-features = false, features = ["openblas"] } 15 | openblas-src = { version = "0.6.0", default-features = false, features = ["cblas", "system"] } 16 | fftw-src = { version = "0.3.0"} # had to set CC=/usr/bin/gcc followed by 'cargo clean' to compile correctly (anaconda conflict) 17 | fftw = { version = "0.6.0"} 18 | image = { version = "0.23.4"} 19 | hdf5 = { version = "0.6.1"} # requires debian package libhdf5-dev 20 | basic_dsp = "*" 21 | log = "0.4" 22 | simplelog = "^0.7.6" 23 | opencv = {version = "0.46", default-features = false, features = ["opencv-4", "buildtime-bindgen", "contrib"]} 24 | plotlib = '*' 25 | 26 | [profile.release] 27 | debug = true -------------------------------------------------------------------------------- /rs/opencv-install-notes.org: -------------------------------------------------------------------------------- 1 | #+title: opencv-install-notes 2 | 3 | * Installation 4 | 5 | The following was completed successfully on Pop!_OS 20.10. 6 | 7 | ** From source 8 | 9 | NOTE: Disable all python virtual envs before proceeding. 10 | 11 | NOTE: I'm choosing to install v4.3.0, as this is the newest version supported by opencv-rust. 12 | 13 | System prereqs: 14 | 15 | #+begin_src sh 16 | 17 | sudo apt install build-essential cmake git pkg-config libgtk-3-dev \ 18 | libavcodec-dev libavformat-dev libswscale-dev libv4l-dev \ 19 | libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev \ 20 | gfortran openexr libatlas-base-dev python3-dev python3-numpy \ 21 | libtbb2 libtbb-dev libdc1394-22-dev libopenexr-dev \ 22 | libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev 23 | 24 | #+end_src 25 | 26 | Create dirs, clone repos. Checking out v4.3.0 for compatability with opencv-rust. 27 | 28 | #+begin_src 29 | 30 | mkdir ~/opencv_build 31 | cd ~/opencv_build 32 | git clone https://github.com/opencv/opencv.git 33 | cd opencv 34 | git checkout tags/4.3.0 35 | cd .. 36 | git clone https://github.com/opencv/opencv_contrib.git 37 | cd opencv_contrib 38 | git checkout tags/4.3.0 39 | cd .. 40 | 41 | cd ~/opencv_build/opencv 42 | mkdir -p build && cd build 43 | 44 | #+end_src 45 | 46 | Configure build 47 | 48 | #+begin_src sh 49 | 50 | cmake -D CMAKE_BUILD_TYPE=RELEASE \ 51 | -D CMAKE_INSTALL_PREFIX=/usr/local \ 52 | -D INSTALL_C_EXAMPLES=ON \ 53 | -D INSTALL_PYTHON_EXAMPLES=ON \ 54 | -D OPENCV_GENERATE_PKGCONFIG=ON \ 55 | -D OPENCV_EXTRA_MODULES_PATH=~/opencv_build/opencv_contrib/modules \ 56 | -D BUILD_EXAMPLES=ON .. 57 | 58 | #+end_src 59 | 60 | Make build and install. Note the number following =j= should be the output of =nproc= command 61 | 62 | #+begin_src sh 63 | 64 | make -j16 65 | 66 | sudo make install 67 | 68 | #+end_src 69 | 70 | Verify 71 | 72 | #+begin_src sh 73 | 74 | pkg-config --modversion opencv4 75 | 76 | #+end_src 77 | 78 | ** Rust bindings 79 | 80 | Use the following to allow usage of Rust bindings. Again, note that only opencv 4.3.0. is currently supported. 81 | 82 | Add the following environmental variables to =variables.bash= 83 | 84 | #+begin_src sh 85 | 86 | CV_DIR="HOME/opencv_build/opencv" 87 | export OpenCV_DIR="$CV_DIR/cmake" 88 | export LD_LIBRARY_PATH="$CV_DIR/build/lib" 89 | export LLVM_CONFIG_PATH="/usr/bin/llvm-config-11" 90 | export LIBCLANG_PATH="/usr/lib/x86_64-linux-gnu" 91 | 92 | #+end_src 93 | 94 | Install the additional system dependencies: 95 | 96 | #+begin_src sh 97 | 98 | sudo apt install ninja-build clang 99 | 100 | #+end_src 101 | 102 | Put the following in your =Cargo.toml= 103 | 104 | #+begin_src rs 105 | 106 | opencv = {version = "0.46", default-features = false, features = ["opencv-4", "buildtime-bindgen", "contrib"]} 107 | 108 | #+end_src 109 | 110 | And try this in =main.rs= 111 | 112 | #+begin_src rs 113 | 114 | use opencv::prelude::*; 115 | 116 | #+end_src 117 | 118 | Now run =cargo build=. 119 | -------------------------------------------------------------------------------- /rs/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csheaff/us-beamform-linarray/cbfd29f7a6a8f226860167c5960cfa6646246f11/rs/result.png -------------------------------------------------------------------------------- /rs/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate log; 2 | extern crate simplelog; 3 | use simplelog::*; 4 | use std::fs::File; 5 | 6 | use opencv::{ 7 | core::{self, MatConstIterator, Point, Point2d, Rect, Size, Vec2d, Vec2b, Vec3d, Vec3f, Vec4w}, 8 | Error, 9 | prelude::*, 10 | Result, 11 | types::{VectorOff64, VectorOfi32, VectorOfMat}, 12 | imgproc::{self, resize}, 13 | }; 14 | 15 | extern crate hdf5; 16 | extern crate basic_dsp; 17 | use basic_dsp::conv_types::*; 18 | use basic_dsp::*; 19 | 20 | use ndarray::{prelude::*, stack, Zip}; 21 | use ndarray_linalg::{norm::Norm, types::Scalar}; 22 | use ndarray_stats::QuantileExt; // this adds basic stat methods to your arrays 23 | use fftw::array::AlignedVec; 24 | use fftw::plan::*; 25 | use fftw::types::*; 26 | 27 | use std::f64::consts::PI; 28 | use std::vec::Vec; 29 | use std::path::Path; 30 | 31 | use plotlib::page::Page; 32 | use plotlib::repr::Plot; 33 | use plotlib::view::ContinuousView; 34 | use plotlib::style::LineStyle; 35 | 36 | use std::time::Instant; 37 | 38 | const SAMPLE_RATE: f64 = 27.72e6; 39 | const TIME_OFFSET: f64 = 1.33e-6; 40 | const SPEED_SOUND: f64 = 1540.0; 41 | const N_TRANSMIT_BEAMS: u32 = 96; 42 | const N_PROBE_CHANNELS: u32 = 32; 43 | const TRANSMIT_FREQ: f64 = 1.6e6; 44 | const TRANSMIT_FOCAL_DEPTH: f64 = 20e-3; 45 | const ARRAY_PITCH: f64 = 2.0 * 1.8519e-4; 46 | const REC_LEN: u32 = 1585; 47 | const UPSAMP_FACT: u32 = 4; 48 | const DECIM_FACT: u32 = 8; 49 | 50 | 51 | fn plotlib_zip(x: &Array1, y: &Array1) -> Vec<(f64, f64)> { 52 | // recreate's python's zip() for two 1-d arrays, resulting in 53 | // a vector that can be digested by plotlib's Plot::new() 54 | let x = x.clone().into_raw_vec(); 55 | let y = y.clone().into_raw_vec(); 56 | let d: Vec<(f64, f64)> = x.into_iter().zip(y).collect(); 57 | d 58 | } 59 | 60 | 61 | fn fft_priv(x: &Array1, n: usize, sign: Sign) -> Array1 { 62 | let mut xfft = AlignedVec::new(n); 63 | let mut xs_aligned = AlignedVec::new(n); 64 | for (x_aligned, &x) in xs_aligned.iter_mut().zip(x) { 65 | *x_aligned = x; 66 | } 67 | 68 | let mut plan: C2CPlan64 = C2CPlan::aligned(&[n], sign, Flag::MEASURE).unwrap(); 69 | 70 | plan.c2c(&mut xs_aligned, &mut xfft).unwrap(); 71 | Array1::from(Vec::from(xfft.as_slice())) 72 | } 73 | 74 | 75 | fn fft(x: &Array1, n: usize) -> Array1 { 76 | // this is unnormalized, just like scipy.fftpack.fft 77 | fft_priv(x, n, Sign::Forward) 78 | } 79 | 80 | 81 | fn ifft(x: &Array1) -> Array1 { 82 | // this will normalize, just like scipy.fftpack.ifft 83 | fft_priv(x, x.len(), Sign::Backward) / c64::new(x.len() as f64, 0.0) 84 | } 85 | 86 | 87 | fn get_data(data_path: &Path) -> Array3 { 88 | let file = hdf5::File::open(data_path).unwrap(); 89 | let data = file.dataset("dataset_1").unwrap(); 90 | let data: Array3 = data.read().unwrap(); 91 | return data 92 | } 93 | 94 | 95 | fn preproc(data: &Array3, t: &Array1, xd: &Array1) -> (Array3, Array1) { 96 | // Preprocessing. right now this only does upsampling/interpolation. 97 | // TODO: filtering, apodization, replace interpolation b/c 98 | // it's slow 99 | let filt_ord = 201; 100 | let lc = 0.5e6; 101 | let uc = 2.5e6; 102 | let lc = lc / (SAMPLE_RATE / 2.0); 103 | let uc = uc / (SAMPLE_RATE / 2.0); 104 | 105 | let rec_len_interp = REC_LEN * UPSAMP_FACT; 106 | let mut data_interp = Array3::::zeros(( 107 | N_TRANSMIT_BEAMS as usize, 108 | N_PROBE_CHANNELS as usize, 109 | rec_len_interp as usize, 110 | )); 111 | let mut buffer = SingleBuffer::new(); 112 | for n in 0..N_TRANSMIT_BEAMS { 113 | for m in 0..N_PROBE_CHANNELS { 114 | // get waveform and convert to DspVec 115 | let waveform = data.slice(s![n as usize, m as usize, ..]); 116 | let mut dsp_vec = waveform.to_owned().into_raw_vec().to_real_time_vec(); 117 | 118 | // interpolate - currently a bug(ish) requiring truncation. See https://github.com/liebharc/basic_dsp/issues/46 119 | dsp_vec.interpolatei(&mut buffer, &RaisedCosineFunction::new(0.1), UPSAMP_FACT).unwrap(); 120 | let (mut dsp_vec_data, points) = dsp_vec.get(); 121 | dsp_vec_data.truncate(points); 122 | // let vec: Vec = dsp_vec.into(); // This also works but, what if you still need to operate on dsp_vec? 123 | 124 | // plug into new array 125 | let mut waveform_interp = data_interp.slice_mut(s![n as usize, m as usize, ..]); 126 | waveform_interp.assign(&Array1::from(dsp_vec_data)); 127 | } 128 | } 129 | let sample_rate = SAMPLE_RATE * UPSAMP_FACT as f64; 130 | let t_interp = Array::range(0.0, rec_len_interp as f64, 1.0) / sample_rate + t[0]; 131 | 132 | // remove transmission pulse. truncating before 5 ms would be best, 133 | let trunc_ind = 350 as usize; 134 | let data_preproc = data_interp.slice(s![.., .., trunc_ind..]).into_owned(); 135 | let t_interp = t_interp.slice(s![trunc_ind..]).into_owned(); 136 | 137 | (data_preproc, t_interp) 138 | } 139 | 140 | 141 | fn where_2D(bools: Array2) -> Vec<(usize, usize)> { 142 | // This is designed to behave like np.where. Currently ndarray does not 143 | // provide this function natively. See https://github.com/rust-ndarray/ndarray/issues/466 144 | let x: Vec<_> = bools 145 | .indexed_iter() 146 | .filter_map(|(index, &item)| if item { Some(index) } else { None }) 147 | .collect(); 148 | return x 149 | } 150 | 151 | 152 | fn array_indexing_1d(x: &Array1, ind: &Array1) -> Array1 { 153 | Zip::from(ind).apply_collect(|idx| x[*idx]) 154 | } 155 | 156 | 157 | fn beamform_df(data: &Array3, time: &Array1, xd: &Array1) -> Array2 { 158 | // acoustic propagation distance from transmission to reception for each 159 | // element. Note: transmission is consdiered to arise from the center 160 | // of the array. 161 | let zd = time * SPEED_SOUND / 2.0; 162 | let zd2 = zd.mapv(|x| x.powi(2)); 163 | let mut prop_dist = Array2::::zeros((N_PROBE_CHANNELS as usize, zd.len())); 164 | for r in 0..N_PROBE_CHANNELS { 165 | let dist = (xd[r as usize].powi(2) + &zd2).mapv(::sqrt) + &zd; 166 | let mut slice = prop_dist.slice_mut(s![r as usize, ..]); 167 | slice.assign(&dist); 168 | } 169 | 170 | let sample_rate = SAMPLE_RATE * UPSAMP_FACT as f64; 171 | let prop_dist_ind = (prop_dist / SPEED_SOUND * sample_rate).mapv(|x| x.round() as usize); 172 | 173 | // replace out-of-bounds indices 174 | let prop_dist_ind = prop_dist_ind.mapv(|x| x.min(time.len() - 1)); 175 | 176 | // beamform 177 | let mut image = Array2::::zeros((N_TRANSMIT_BEAMS as usize, zd.len())); 178 | for n in 0..N_TRANSMIT_BEAMS { 179 | let mut scan_line = Array1::::zeros(zd.len()); 180 | for m in 0..N_PROBE_CHANNELS { 181 | let waveform = data.slice(s![n as usize, m as usize, ..]).into_owned(); 182 | let inds = prop_dist_ind.slice(s![m as usize, ..]).into_owned(); 183 | let waveform_indexed = array_indexing_1d(&waveform, &inds); 184 | scan_line += &waveform_indexed; 185 | } 186 | let mut image_slice = image.slice_mut(s![n as usize, ..]); 187 | image_slice.assign(&scan_line); 188 | } 189 | return image 190 | } 191 | 192 | 193 | fn analytic(waveform: &Array1, nfft: usize) -> Array1 { 194 | // Discrete-time analytic signal 195 | // This mimics scipy.signal.hilbert 196 | 197 | let waveform = waveform.mapv(|x| c64::new(x, 0.0)); // convert to complex 198 | let waveform_fft = fft(&waveform, nfft); 199 | 200 | // currently only working if nfft is even 201 | let mut h1 = Array1::::ones(nfft); 202 | let h2 = Array1::::ones(((nfft / 2) - 1) as usize) * 2.0; 203 | let mut slice = h1.slice_mut(s![1..(nfft / 2)]); 204 | slice.assign(&h2); 205 | let h0 = Array1::::zeros((nfft / 2 - 1) as usize); 206 | let mut slice = h1.slice_mut(s![(nfft / 2) + 1..]); 207 | slice.assign(&h0); 208 | 209 | let analytic_fft = waveform_fft * h1.mapv(|x| c64::new(x, 0.0)); 210 | let analytic = ifft(&analytic_fft); 211 | 212 | analytic 213 | } 214 | 215 | 216 | fn envelope(waveform: &Array1) -> Array1 { 217 | let nfft = 6340; // length of data 218 | let env = analytic(&waveform, nfft).mapv(|x| x.abs()); 219 | let env = env.slice(s![..waveform.len()]).to_owned(); 220 | 221 | env 222 | } 223 | 224 | 225 | fn log_compress(data: &Array2, dr: f64) -> Array2 { 226 | let data_max = *(data.max().unwrap()); 227 | let data_log = 20.0 * data.mapv(|x| (x / data_max).log10()); 228 | let data_log = data_log.mapv(|x| x.max(-dr)); 229 | let data_log = (data_log + dr) / dr; 230 | data_log 231 | } 232 | 233 | 234 | fn ndarray2mat_2d(x: &Array2) -> Mat { 235 | // covert a 2-d ndarray to single channel Mat object 236 | let n_rows = x.shape()[0]; 237 | let x = x.clone().into_raw_vec(); 238 | let mat = Mat::from_slice(&x).unwrap(); 239 | let mat = mat.reshape(1, n_rows as i32).unwrap(); 240 | mat 241 | } 242 | 243 | 244 | fn resize_ndarray(img_src: &Array2, fx: f64, fy: f64) -> Array2 { 245 | // perform opencv's resize but using ndarray as input and output types 246 | // note: Array2::shape yields (height, width), but Mat::size yields (width, height) 247 | 248 | let n_rows_resize = (img_src.shape()[0] as f64 * fy) as i32; 249 | let n_cols_resize = (img_src.shape()[1] as f64 * fx) as i32; 250 | 251 | let img_src = ndarray2mat_2d(&img_src); 252 | let mut img_dst = Mat::default().unwrap(); 253 | let size = Size::new(n_cols_resize, n_rows_resize); 254 | resize(&img_src, &mut img_dst, size, 0., 0., imgproc::INTER_LINEAR).unwrap(); 255 | 256 | let img_dst = img_dst.data_typed::().unwrap().to_vec(); 257 | let img_dst = Array2::::from_shape_vec((n_rows_resize as usize, n_cols_resize as usize), img_dst).unwrap(); 258 | 259 | img_dst 260 | } 261 | 262 | 263 | fn scan_convert(img: &Array2, x: &Array1, z: &Array1) 264 | -> (Array2, Array1, Array1) { 265 | 266 | // decimate in depth dimensions 267 | let img_decim = img.slice(s![.., ..;DECIM_FACT]).into_owned(); 268 | let z_sc = z.slice(s![..;DECIM_FACT]).into_owned(); 269 | 270 | info!("Decimated imape shape = {:?}", img_decim.shape()); 271 | 272 | // make pixels square by resampling in x-direction 273 | let dz = z_sc[1] - z_sc[0]; 274 | let dx = x[1] - x[0]; 275 | let img_sc = resize_ndarray(&img_decim, 1., dx / dz); 276 | let x_sc = Array1::::linspace(x[0], x[x.len() - 1], img_sc.shape()[0]); 277 | 278 | (img_sc, x_sc, z_sc) 279 | } 280 | 281 | 282 | fn transpose(a: Array2) -> Array2 { 283 | // transpose a 2-d array while maintining c-order layout 284 | let a_t = a.t(); 285 | let mut a_t_owned = Array2::zeros(a_t.raw_dim()); 286 | a_t_owned.assign(&a_t); 287 | a_t_owned 288 | } 289 | 290 | 291 | fn img_save(img: &Array2, img_save_path: &Path) { 292 | // save grayscale image from Array2 293 | let img = img.clone(); 294 | let img = 255.0 * img; 295 | let img = img.mapv(|x| x as u8); 296 | let imgx = img.shape()[0] as u32; 297 | let imgy = img.shape()[1] as u32; 298 | let imgbuf = image::GrayImage::from_vec(imgy, imgx, img.into_raw_vec()); 299 | imgbuf.unwrap().save(img_save_path).unwrap(); 300 | } 301 | 302 | 303 | fn logger_init(filename: &str) { 304 | CombinedLogger::init( 305 | vec![ 306 | TermLogger::new(LevelFilter::Warn, Config::default(), TerminalMode::Mixed).unwrap(), 307 | WriteLogger::new(LevelFilter::Info, Config::default(), File::create(filename).unwrap()), 308 | ] 309 | ).unwrap(); 310 | } 311 | 312 | 313 | fn main() { 314 | // logger and timer init 315 | let before = Instant::now(); 316 | logger_init("rust.log"); 317 | 318 | // data loading 319 | let data_path = Path::new("../example_us_bmode_sensor_data.h5"); 320 | let data = get_data(&data_path); 321 | 322 | info!("Data shape = {:?}", data.shape()); 323 | 324 | let t = Array::range(0., REC_LEN as f64, 1.) / SAMPLE_RATE - TIME_OFFSET; 325 | let xd = Array::range(0., N_PROBE_CHANNELS as f64, 1.) * ARRAY_PITCH; 326 | let xd_max = *xd.max().unwrap(); 327 | let xd = xd - xd_max / 2.; 328 | 329 | // preprocessing 330 | let (preproc_data, t_interp) = preproc(&data, &t, &xd); 331 | let zd = &t_interp * SPEED_SOUND / 2.; 332 | 333 | info!("Preprocess Data shape = {:?}", preproc_data.shape()); 334 | 335 | // beamforming 336 | let data_beamformed = beamform_df(&preproc_data, &t_interp, &xd); 337 | info!("Beamformed Data shape = {:?}", data_beamformed.shape()); 338 | let m = data_beamformed.slice(s![0, ..]).sum(); 339 | info!("Beamformed Data sum = {:?}", m); 340 | 341 | // lateral locations of beamformed a-lines 342 | let xd2 = Array1::::range(0., N_TRANSMIT_BEAMS as f64, 1.) * ARRAY_PITCH; 343 | let xd2_max = *xd.max().unwrap(); 344 | let xd2 = xd2 - xd2_max / 2.; 345 | 346 | // envelope detection 347 | let mut img = Array2::::zeros(data_beamformed.raw_dim()); 348 | for n in 0..N_TRANSMIT_BEAMS { 349 | let a_line = data_beamformed.slice(s![n as usize, ..]).into_owned(); 350 | let env = envelope(&a_line); 351 | let mut img_slice = img.slice_mut(s![n as usize, ..]); 352 | img_slice.assign(&env); 353 | } 354 | info!("Envelope detected Data shape = {:?}", img.shape()); 355 | 356 | // log compression 357 | let dr = 35.0; 358 | let img_log = log_compress(&img, dr); 359 | 360 | // scan conversion 361 | let (img_sc, x_sc, z_sc) = scan_convert(&img_log, &xd2, &zd); 362 | info!("Length of z vector after scan conversion {:?}", z_sc.len()); 363 | info!("Length of x vector after scan conversion {:?}", x_sc.len()); 364 | info!("Scan converted imape shape = {:?}", img_sc.shape()); 365 | 366 | // image save 367 | let img_save_path = Path::new("./result.png"); 368 | let img_sc = transpose(img_sc); 369 | img_save(&img_sc, &img_save_path); 370 | 371 | // optional plotting of intermediate steps 372 | let should_plot_steps = true; 373 | if should_plot_steps { 374 | 375 | // A-line vs pre-processed A-line 376 | let aline_ind = 15; 377 | let data_ex = data.slice(s![45, aline_ind, ..]).into_owned(); 378 | let pp_ex = preproc_data.slice(s![45, aline_ind, ..]).into_owned(); 379 | let xy = plotlib_zip(&t, &data_ex); 380 | let s1: Plot = Plot::new(xy).line_style(LineStyle::new()).legend(String::from("Waveform")); 381 | let xy = plotlib_zip(&t_interp, &pp_ex); 382 | let s2: Plot = Plot::new(xy).line_style(LineStyle::new().colour("#35C788")).legend(String::from("Preproc")); 383 | let v = ContinuousView::new().add(s1).add(s2).x_label("Time (s)").y_range(-5000., 5000.); 384 | Page::single(&v).save("./preproc.svg").unwrap(); 385 | 386 | // Demo of Envelope detection 387 | let a_line = data_beamformed.slice(s![aline_ind, ..]).into_owned(); 388 | let env = envelope(&a_line); 389 | let xy = plotlib_zip(&t_interp, &a_line); 390 | let s1: Plot = Plot::new(xy).line_style(LineStyle::new()).legend(String::from("Beamformed Waveform")); 391 | let xy = plotlib_zip(&t_interp, &env); 392 | let s2: Plot = Plot::new(xy).line_style(LineStyle::new().colour("#35C788")).legend(String::from("Envelope")); 393 | let v = ContinuousView::new().add(s1).add(s2).x_label("Time (s)"); 394 | Page::single(&v).save("./envelope.svg").unwrap(); 395 | 396 | // of log compression 397 | let img_log_slice = img_log.slice(s![aline_ind, ..]).into_owned(); 398 | let xy = plotlib_zip(&zd, &img_log_slice); 399 | let s1: Plot = Plot::new(xy).line_style(LineStyle::new()); 400 | let v = ContinuousView::new().add(s1).x_label("Depth (m)"); 401 | Page::single(&v).save("./img_log_slice.svg").unwrap(); 402 | 403 | } 404 | 405 | info!("Elapsed time: {:.2?} s", before.elapsed()); 406 | 407 | // TODO: filtering 408 | // TODO: replace basic_dsp interpolation (very slow) 409 | 410 | } 411 | -------------------------------------------------------------------------------- /target geometry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csheaff/us-beamform-linarray/cbfd29f7a6a8f226860167c5960cfa6646246f11/target geometry.png --------------------------------------------------------------------------------