Coverage for kwave/ktransducer.py: 21%

268 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-24 11:55 -0700

1from kwave.kgrid import kWaveGrid 

2from kwave.ksensor import kSensor 

3from kwave.utils import * 

4 

5 

6# force value to be a positive integer 

7def make_pos_int(val): 

8 return np.abs(val).astype(int) 

9 

10 

11class kWaveTransducerSimple(object): 

12 

13 def __init__( 

14 self, 

15 kgrid: kWaveGrid, 

16 number_elements=128, 

17 element_width=1, 

18 element_length=20, 

19 element_spacing=0, 

20 position=None, 

21 radius=float('inf') 

22 ): 

23 """ 

24 

25 Args: 

26 kgrid: kWaveGrid object 

27 number_elements: the total number of transducer elements 

28 element_width: the width of each element in grid points 

29 element_length: the length of each element in grid points 

30 element_spacing: the spacing (kerf width) between the transducer elements in grid points 

31 position: the position of the corner of the transducer in the grid 

32 radius: the radius of curvature of the transducer [m] 

33 """ 

34 # allocate the grid size and spacing 

35 self.stored_grid_size = [kgrid.Nx, kgrid.Ny, kgrid.Nz] # size of the grid in which the transducer is defined 

36 self.grid_spacing = [kgrid.dx, kgrid.dy, kgrid.dz] # corresponding grid spacing 

37 

38 self.number_elements = make_pos_int(number_elements) 

39 self.element_width = make_pos_int(element_width) 

40 self.element_length = make_pos_int(element_length) 

41 self.element_spacing = make_pos_int(element_spacing) 

42 

43 if position is None: 

44 position = [1, 1, 1] 

45 self.position = make_pos_int(position) 

46 

47 assert np.isinf(radius), 'Only a value of transducer.radius = inf is currently supported' 

48 self.radius = radius 

49 

50 # check the transducer fits into the grid 

51 if np.sum(self.position == 0): 

52 raise ValueError('The defined transducer must be positioned within the grid') 

53 elif (self.position[1] + self.number_elements * self.element_width + ( 

54 self.number_elements - 1) * self.element_spacing) > self.stored_grid_size[1]: 

55 raise ValueError('The defined transducer is too large or positioned outside the grid in the y-direction') 

56 elif (self.position[2] + self.element_length) > self.stored_grid_size[2]: 

57 print(self.position[2]) 

58 print(self.element_length) 

59 print(self.stored_grid_size[2]) 

60 raise ValueError('The defined transducer is too large or positioned outside the grid in the z-direction') 

61 elif self.position[0] > self.stored_grid_size[0]: 

62 raise ValueError('The defined transducer is positioned outside the grid in the x-direction') 

63 

64 @property 

65 def element_pitch(self): 

66 return (self.element_spacing + self.element_width) * self.grid_spacing[1] 

67 

68 @property 

69 def transducer_width(self): 

70 """ 

71 total width of the transducer in grid points 

72 Returns: 

73 the overall length of the transducer 

74 """ 

75 return self.number_elements * self.element_width + (self.number_elements - 1) * self.element_spacing 

76 

77 

78class NotATransducer(kSensor): 

79 

80 def __init__( 

81 self, 

82 transducer: kWaveTransducerSimple, 

83 kgrid: kWaveGrid, 

84 active_elements=None, 

85 focus_distance=float('inf'), 

86 elevation_focus_distance=float('inf'), 

87 receive_apodization='Rectangular', 

88 transmit_apodization='Rectangular', 

89 sound_speed=1540, 

90 input_signal=None, 

91 steering_angle_max=None, 

92 steering_angle=None 

93 ): 

94 """ 

95 

96 'time_reversal_boundary_data' and 'record' fields should not be defined 

97 for the objects of this class 

98 Args: 

99 kgrid: kWaveGrid object 

100 active_elements: the transducer elements that are currently active elements 

101 elevation_focus_distance: the focus depth in the elevation direction [m] 

102 receive_apodization: transmit apodization 

103 transmit_apodization: receive apodization 

104 sound_speed: sound speed used to calculate beamforming delays [m/s] 

105 focus_distance: focus distance used to calculate beamforming delays [m] 

106 input_signal: 

107 steering_angle_max: 

108 steering_angle: 

109 """ 

110 super().__init__() 

111 assert isinstance(transducer, kWaveTransducerSimple) 

112 self.transducer = transducer 

113 # time index to start recording if transducer is used as a sensor 

114 self.record_start_index = 1 

115 # stored value of appended_zeros (accessed using get and set methods). 

116 # This is used to set the number of zeros that are appended and prepended to the input signal. 

117 self.stored_appended_zeros = 'auto' 

118 # stored value of the minimum beamforming delay. 

119 # This is used to offset the delay mask so that all the delays are >= 0 

120 self.stored_beamforming_delays_offset = 'auto' 

121 # stored value of the steering_angle_max (accessed using get and set methods). 

122 # This can be set by the user and is used to derive the two parameters above. 

123 self.stored_steering_angle_max = 'auto' 

124 # stored value of the steering_angle (accessed using get and set methods) 

125 self.stored_steering_angle = 0 

126 

127 #################################### 

128 # if the sensor is a transducer, check that the simulation is in 3D 

129 assert kgrid.dim == 3, 'Transducer inputs are only compatible with 3D simulations.' 

130 

131 #################################### 

132 

133 # allocate the temporal spacing 

134 if is_number(kgrid.dt): 

135 self.dt = kgrid.dt 

136 elif kgrid.t_array is not None: 

137 self.dt = kgrid.t_array[1] - kgrid.t_array[0] 

138 else: 

139 raise ValueError('kgrid.dt or kgrid.t_array must be explicitly defined') 

140 

141 if active_elements is None: 

142 active_elements = np.ones((transducer.number_elements, 1)) 

143 self.active_elements = active_elements 

144 

145 self.elevation_focus_distance = elevation_focus_distance 

146 

147 # check the length of the input 

148 assert not is_number(receive_apodization) or len(receive_apodization) == self.number_active_elements, \ 

149 'The length of the receive apodization input must match the number of active elements' 

150 self.receive_apodization = receive_apodization 

151 

152 # check the length of the input 

153 assert not is_number(transmit_apodization) or len(transmit_apodization) == self.number_active_elements, \ 

154 'The length of the transmit apodization input must match the number of active elements' 

155 self.transmit_apodization = transmit_apodization 

156 

157 # check to see the sound_speed is positive 

158 assert sound_speed > 0, 'transducer.sound_speed must be greater than 0' 

159 self.sound_speed = sound_speed 

160 

161 self.focus_distance = focus_distance 

162 

163 if input_signal is not None: 

164 input_signal = np.squeeze(input_signal) 

165 assert input_signal.ndim == 1, 'transducer.input_signal must be a one-dimensional array' 

166 self.stored_input_signal = np.atleast_2d(input_signal).T # force the input signal to be a column vector 

167 

168 if steering_angle_max is not None: 

169 # set the maximum steering angle using the set method (this avoids having to duplicate error checking) 

170 self.steering_angle_max = steering_angle_max 

171 

172 if steering_angle is not None: 

173 # set the maximum steering angle using the set method (this 

174 # avoids having to duplicate error checking) 

175 self.steering_angle = steering_angle 

176 

177 # assign the data type for the transducer matrix based on the 

178 # number of different elements (uint8 supports 255 numbers so 

179 # most of the time this data type will be used) 

180 mask_type = get_smallest_possible_type(transducer.number_elements, 'uint') 

181 

182 # create an empty transducer mask (the grid points within 

183 # element 'n' are all given the value 'n') 

184 assert transducer.stored_grid_size is not None 

185 self.indexed_mask = np.zeros(transducer.stored_grid_size, dtype=mask_type) 

186 

187 # create a second empty mask used for the elevation beamforming 

188 # delays (the grid points across each element are numbered 1 to 

189 # M, where M is the number of grid points in the elevation 

190 # direction) 

191 self.indexed_element_voxel_mask = np.zeros(transducer.stored_grid_size, dtype=mask_type) 

192 

193 # create the corresponding indexing variable 1:M 

194 element_voxel_index = np.tile(np.arange(transducer.element_length) + 1, (transducer.element_width, 1)) 

195 

196 # for each transducer element, calculate the grid point indices 

197 for element_index in range(0, transducer.number_elements): 

198 # assign the current transducer position 

199 element_pos_x = transducer.position[0] 

200 element_pos_y = transducer.position[1] + (transducer.element_width + transducer.element_spacing) * element_index 

201 element_pos_z = transducer.position[2] 

202 

203 element_pos_x = element_pos_x - 1 

204 element_pos_y = element_pos_y - 1 

205 element_pos_z = element_pos_z - 1 

206 

207 # assign the grid points within the current element the 

208 # index of the element 

209 self.indexed_mask[element_pos_x, element_pos_y:element_pos_y + transducer.element_width, 

210 element_pos_z:element_pos_z + transducer.element_length] = element_index + 1 

211 

212 # assign the individual grid points an index corresponding 

213 # to their order across the element 

214 self.indexed_element_voxel_mask[element_pos_x, element_pos_y:element_pos_y + transducer.element_width, 

215 element_pos_z:element_pos_z + transducer.element_length] = element_voxel_index 

216 

217 # double check the transducer fits within the desired grid size 

218 assert (np.array(self.indexed_mask.shape) == transducer.stored_grid_size).all(), \ 

219 'Desired transducer is larger than the input grid_size' 

220 

221 self.sxx = self.syy = self.szz = self.sxy = self.sxz = self.syz = None 

222 self.u_mode = self.p_mode = None 

223 self.ux = self.uy = self.uz = None 

224 

225 @staticmethod 

226 def isfield(_): 

227 # return field_name in dir(self) 

228 # return eng.isfield(self.transducer, field_name) 

229 return False # this method call was returning false always because Matlab 'isfield' calls are false for classes 

230 

231 def __contains__(self, item): 

232 return self.isfield(item) 

233 

234 @property 

235 def beamforming_delays(self): 

236 """ 

237 calculate the beamforming delays based on the focus and steering settings 

238 Returns: 

239 

240 """ 

241 # calculate the element pitch in [m] 

242 element_pitch = self.transducer.element_pitch 

243 

244 # create indexing variable 

245 element_index = np.arange(-(self.number_active_elements - 1) / 2, (self.number_active_elements + 1) / 2) 

246 

247 # check for focus depth 

248 if np.isinf(self.focus_distance): 

249 

250 # calculate time delays for a steered beam 

251 delay_times = element_pitch * element_index * \ 

252 np.sin(self.steering_angle * np.pi / 180) / self.sound_speed # [s] 

253 

254 else: 

255 

256 # calculate time delays for a steered and focussed beam 

257 delay_times = self.focus_distance / self.sound_speed * ( 

258 1 - np.sqrt(1 + (element_index * element_pitch / self.focus_distance) ** 2 

259 - 2 * (element_index * element_pitch / self.focus_distance) * np.sin( 

260 self.steering_angle * np.pi / 180))) # [s] 

261 

262 # convert the delays to be in units of time points 

263 delay_times = (delay_times / self.dt).round().astype(int) 

264 return delay_times 

265 

266 @property 

267 def beamforming_delays_offset(self): 

268 """ 

269 offset used to make all the delays in the delay_mask positive (either set to 'auto' or based on the setting for steering_angle_max) 

270 Returns: 

271 the stored value of the offset used to force the values in delay_mask to be >= 0 

272 """ 

273 return self.stored_beamforming_delays_offset 

274 

275 @property 

276 def mask(self): 

277 """ 

278 allow mask query to allow compatability with regular sensor structure - return the active sensor mask 

279 Returns: 

280 

281 """ 

282 return self.active_elements_mask 

283 

284 @property 

285 def indexed_active_elements_mask(self): 

286 # copy the indexed elements mask 

287 mask = self.indexed_mask 

288 if mask is None: 

289 return None 

290 

291 mask = np.copy(mask) 

292 

293 # remove inactive elements from the mask 

294 for element_index in range(self.transducer.number_elements): 

295 if not self.active_elements[element_index]: 

296 mask[mask == (element_index + 1)] = 0 # +1 compatibility 

297 

298 # force the lowest element index to be 1 

299 lowest_active_element_index = matlab_find(self.active_elements)[0][0] 

300 mask[mask != 0] = mask[mask != 0] - lowest_active_element_index + 1 

301 return mask 

302 

303 @property 

304 def indexed_elements_mask(self): # nr 

305 return self.indexed_mask 

306 

307 @property 

308 def steering_angle(self): # nr 

309 return self.stored_steering_angle 

310 

311 # set the stored value of the steering angle 

312 @steering_angle.setter 

313 def steering_angle(self, steering_angle): 

314 # force to be scalar 

315 steering_angle = float(steering_angle) 

316 

317 # check if the steering angle is between -90 and 90 

318 assert -90 < steering_angle < 90, 'Input for steering_angle must be betweeb -90 and 90 degrees.' 

319 

320 # check if the steering angle is less than the maximum steering angle 

321 if self.stored_steering_angle_max != 'auto' and (abs(steering_angle) > self.stored_steering_angle_max): 

322 raise ValueError('Input for steering_angle cannot be greater than steering_angle_max.') 

323 

324 # update the stored value 

325 self.stored_steering_angle = steering_angle 

326 

327 @property 

328 def steering_angle_max(self): 

329 return self.stored_steering_angle_max 

330 

331 @steering_angle_max.setter 

332 def steering_angle_max(self, steering_angle_max): 

333 # force input to be scalar and positive 

334 steering_angle_max = float(steering_angle_max) 

335 

336 # check the steering angle is within range 

337 assert -90 < steering_angle_max < 90, 'Input for steering_angle_max must be between -90 and 90.' 

338 

339 # check the maximum steering angle is greater than the current steering angle 

340 assert self.stored_steering_angle_max == 'auto' or abs(self.stored_steering_angle) <= steering_angle_max, \ 

341 'Input for steering_angle_max cannot be less than the current steering_angle.' 

342 

343 # overwrite the stored value 

344 self.stored_steering_angle_max = steering_angle_max 

345 

346 # store a copy of the current value for the steering angle 

347 current_steering_angle = self.stored_steering_angle 

348 

349 # overwrite with the user defined maximum value 

350 self.stored_steering_angle = steering_angle_max 

351 

352 # set the beamforming delay offset to zero (this means the delays will contain negative 

353 # values which we can use to derive the new values for the offset) 

354 self.stored_appended_zeros = 0 

355 self.stored_beamforming_delays_offset = 0 

356 

357 # get the element beamforming delays and reverse 

358 delay_times = -self.beamforming_delays 

359 

360 # get the maximum and minimum beamforming delays 

361 min_delay, max_delay = delay_times.min(), delay_times.max() 

362 

363 # add the maximum and minimum elevation delay if the elevation focus is not set to infinite 

364 if not np.isinf(self.elevation_focus_distance): 

365 max_delay = max_delay + max(self.elevation_beamforming_delays) 

366 min_delay = min_delay + min(self.elevation_beamforming_delays) 

367 

368 # set the beamforming offset to the difference between the 

369 # maximum and minimum delay 

370 self.stored_appended_zeros = max_delay - min_delay 

371 

372 # set the minimum beamforming delay (converting to a positive number) 

373 self.stored_beamforming_delays_offset = - min_delay 

374 

375 # reset the previous value of the steering angle 

376 self.stored_steering_angle = current_steering_angle 

377 

378 @property 

379 def elevation_beamforming_mask(self): # nr 

380 # get elevation beamforming mask 

381 delay_mask = self.delay_mask(2) 

382 

383 # extract the active elements 

384 delay_mask = delay_mask[self.active_elements_mask != 0] 

385 

386 # force delays to start from zero 

387 delay_mask = delay_mask - delay_mask.min() 

388 

389 # create an empty output mask 

390 mask = np.zeros((delay_mask.size, delay_mask.max() + 1)) 

391 

392 # populate the mask by setting 1's at the index given by the delay time 

393 for index in range(delay_mask.size): 

394 mask[index, delay_mask[index]] = 1 

395 

396 # flip the mask so the shortest delays are at the right 

397 return np.fliplr(mask) 

398 

399 @property 

400 def input_signal(self): 

401 signal = self.stored_input_signal 

402 

403 # check the signal is not empty 

404 assert signal is not None, 'Transducer input signal is not defined' 

405 

406 # automatically prepend and append zeros if the beamforming 

407 # delay offset is set 

408 

409 # check if the beamforming delay offset is set. If so, use this 

410 # number to prepend and append this number of zeros to the 

411 # input signal. Otherwise, calculate how many zeros are needed 

412 # and prepend and append these. 

413 stored_appended_zeros = self.stored_appended_zeros 

414 if stored_appended_zeros != 'auto': 

415 

416 # use the current value of the beamforming offset to add 

417 # zeros to the input signal 

418 signal = np.vstack([np.zeros((stored_appended_zeros, 1)), signal, np.zeros((stored_appended_zeros, 1))]) 

419 

420 else: 

421 # get the current delay beam forming 

422 delay_mask = self.delay_mask() 

423 

424 # find the maximum delay 

425 delay_max = delay_mask.max() 

426 

427 # count the number of leading zeros in the input signal 

428 leading_zeros = matlab_find(signal)[0, 0] - 1 

429 

430 # count the number of trailing zeros in the input signal 

431 trailing_zeros = matlab_find(np.flipud(signal))[0, 0] - 1 

432 

433 # check the number of leading zeros is sufficient given the 

434 # maximum delay 

435 if leading_zeros < delay_max + 1: 

436 

437 print(f' prepending transducer.input_signal with {delay_max - leading_zeros + 1} leading zeros') 

438 

439 # prepend extra leading zeros 

440 signal = np.vstack([np.zeros((delay_max - leading_zeros + 1, 1)), signal]) 

441 

442 # check the number of leading zeros is sufficient given the 

443 # maximum delay 

444 if trailing_zeros < delay_max + 1: 

445 

446 print(f' appending transducer.input_signal with {delay_max - trailing_zeros + 1} trailing zeros') 

447 

448 # append extra trailing zeros 

449 signal = np.vstack([signal, np.zeros((delay_max - trailing_zeros + 1, 1))]) 

450 

451 return signal 

452 

453 @property 

454 def number_active_elements(self): 

455 return self.active_elements.sum() 

456 

457 @property 

458 def appended_zeros(self): 

459 """ 

460 number of zeros appended to input signal to allow a single time series to be used 

461 within kspaceFirstOrder3D (either set to 'auto' or based on the setting for steering_angle_max) 

462 Returns: 

463 

464 """ 

465 return self.stored_appended_zeros 

466 

467 @property 

468 def grid_size(self): 

469 """ 

470 return the size of the grid 

471 Returns: grid size 

472 

473 """ 

474 return self.transducer.stored_grid_size 

475 

476 @property 

477 def active_elements_mask(self): 

478 """ 

479 return a binary mask showing the locations of the active elements 

480 Returns: 

481 

482 """ 

483 indexed_mask = np.copy(self.indexed_mask) 

484 active_elements = self.active_elements.squeeze() 

485 number_elements = int(self.transducer.number_elements) 

486 

487 # copy the indexed elements mask 

488 mask = indexed_mask 

489 

490 # remove inactive elements from the mask 

491 for element_index in range(1, number_elements + 1): 

492 mask[mask == element_index] = active_elements[element_index - 1] 

493 

494 # convert remaining mask to binary 

495 mask[mask != 0] = 1 

496 

497 return mask 

498 

499 @property 

500 def all_elements_mask(self): 

501 """ 

502 binary mask of all the transducer elements (both active and inactive) 

503 Returns: 

504 a binary mask showing the locations of all the elements (both active and inactive) 

505 """ 

506 mask = np.copy(self.indexed_mask) 

507 mask[mask != 0] = 1 

508 return mask 

509 

510 def expand_grid(self, expand_size): 

511 self.indexed_mask = expand_matrix(self.indexed_mask, expand_size, 0) 

512 

513 def retract_grid(self, retract_size): 

514 indexed_mask = self.indexed_mask 

515 retract_size = np.array(retract_size[0]).astype(np.int) 

516 

517 self.indexed_mask = indexed_mask[retract_size[0]:-retract_size[0], retract_size[1]:-retract_size[1], retract_size[2]:-retract_size[2]] 

518 

519 @property 

520 def transmit_apodization_mask(self): 

521 """ 

522 % convert the transmit apodization into the form of a element mask, 

523 % where the apodization values are placed at the grid points 

524 % belonging to the active transducer elements. These values are 

525 % then extracted in the correct order within 

526 % kspaceFirstOrder_inputChecking using apodization = 

527 % transmit_apodization_mask(active_elements_mask ~= 0) 

528 Returns: 

529 

530 """ 

531 # get transmit apodization 

532 apodization = self.get_transmit_apodization() 

533 

534 # create an empty mask; 

535 mask = np.zeros(self.transducer.stored_grid_size) 

536 

537 # assign the apodization values to every grid point in the transducer 

538 mask_index = self.indexed_active_elements_mask 

539 mask_index = mask_index[mask_index != 0] 

540 mask[self.active_elements_mask == 1] = apodization[mask_index - 1, 0] # -1 for conversion 

541 return mask 

542 

543 def get_transmit_apodization(self): 

544 """ 

545 return the transmit apodization, converting strings of window 

546 type to actual numbers using getWin 

547 Returns: 

548 

549 """ 

550 

551 # check if a user defined apodization is given and whether this 

552 # is still the correct size (in case the number of active 

553 # elements has changed) 

554 if is_number(self.transmit_apodization): 

555 assert self.transmit_apodization.size == self.number_active_elements, \ 

556 'The length of the transmit apodization input must match the number of active elements' 

557 

558 # assign apodization 

559 apodization = self.transmit_apodization 

560 else: 

561 

562 # if the number of active elements is greater than 1, 

563 # create apodization using getWin, otherwise, assign 1 

564 if self.number_active_elements > 1: 

565 apodization, _ = get_win(int(self.number_active_elements), type_=self.transmit_apodization) 

566 else: 

567 apodization = 1 

568 apodization = np.array(apodization) 

569 return apodization 

570 

571 def delay_mask(self, mode=None): 

572 """ 

573 % mode == 1: both delays 

574 % mode == 2: elevation only 

575 % mode == 3: azimuth only 

576 Returns: 

577 

578 """ 

579 # assign the delays to a new mask using the indexed_element_mask 

580 indexed_active_elements_mask_copy = self.indexed_active_elements_mask 

581 mask = np.zeros(self.transducer.stored_grid_size, dtype=np.float32) 

582 

583 if indexed_active_elements_mask_copy is None: 

584 return mask 

585 

586 active_elements_index = matlab_find(indexed_active_elements_mask_copy) 

587 

588 # calculate azimuth focus delay times provided they are not all zero 

589 if (not np.isinf(self.focus_distance) or (self.steering_angle != 0)) and (mode is None or mode != 2): 

590 

591 # get the element beamforming delays and reverse 

592 delay_times = -self.beamforming_delays 

593 

594 # add delay times 

595 # mask[active_elements_index] = delay_times[indexed_active_elements_mask_copy[active_elements_index]] 

596 mask[unflatten_matlab_mask(mask, active_elements_index, diff=-1)] = matlab_mask(delay_times, matlab_mask(indexed_active_elements_mask_copy, active_elements_index, diff=-1), diff=-1).squeeze() 

597 

598 # calculate elevation focus time delays provided each element is longer than one grid point 

599 if not np.isinf(self.elevation_focus_distance) and (self.transducer.element_length > 1) and (mode is None or mode != 3): 

600 

601 # get elevation beamforming delays 

602 elevation_delay_times = self.elevation_beamforming_delays 

603 

604 # get current mask 

605 element_voxel_mask = self.indexed_element_voxel_mask 

606 

607 # add delay times 

608 mask[unflatten_matlab_mask(mask, active_elements_index - 1)] += matlab_mask(elevation_delay_times, matlab_mask(element_voxel_mask, active_elements_index - 1) - 1)[:, 0] # -1s compatibility 

609 

610 # shift delay times (these should all be >= 0, where a value of 0 means no delay) 

611 if self.stored_appended_zeros == 'auto': 

612 mask[unflatten_matlab_mask(mask, active_elements_index - 1)] -= mask[unflatten_matlab_mask(mask, active_elements_index - 1)].min() # -1s compatibility 

613 else: 

614 mask[unflatten_matlab_mask(mask, active_elements_index - 1)] += self.stored_beamforming_delays_offset # -1s compatibility 

615 return mask.astype(np.uint8) 

616 

617 @property 

618 def elevation_beamforming_delays(self): 

619 """ 

620 calculate the elevation beamforming delays based on the focus setting 

621 Returns: 

622 """ 

623 if not np.isinf(self.elevation_focus_distance): 

624 # create indexing variable 

625 

626 element_index = np.arange(-(self.transducer.element_length - 1) / 2, (self.transducer.element_length + 1) / 2) 

627 

628 # calculate time delays for a focussed beam 

629 delay_times = (self.elevation_focus_distance - np.sqrt((element_index * self.transducer.grid_spacing[2]) ** 2 + self.elevation_focus_distance ** 2)) / self.sound_speed 

630 

631 # convert the delays to be in units of time points and then reverse 

632 delay_times = -np.round(delay_times / self.dt).astype(np.int32) 

633 

634 else: 

635 # create an empty array 

636 delay_times = np.zeros((1, self.transducer.element_length)) 

637 return delay_times 

638 

639 # # function to create a scan line based on the input sensor data and the current apodization and beamforming setting 

640 # def scan_line(obj, sensor_data): 

641 # 

642 # # get the current apodization setting 

643 # apodization = obj.get_receive_apodization 

644 # 

645 # # get the current beamforming weights and reverse 

646 # delays = -obj.beamforming_delays 

647 # 

648 # # offset the received sensor_data by the beamforming delays and apply receive apodization 

649 # for element_index in np.arange(0, obj.number_active_elements): 

650 # if delays(element_index) > 0: 

651 # # shift element data forwards 

652 # sensor_data[element_index, :] = apodization[element_index] *[sensor_data(element_index, 1 + delays(element_index):end), zeros(1, delays(element_index))]; 

653 # 

654 # elif delays(element_index) < 0 

655 # # shift element data backwards 

656 # sensor_data[element_index, :] = apodization[element_index] *[zeros(1, -delays(element_index)), sensor_data(element_index, 1:end + delays(element_index))]; 

657 # 

658 # # form the a-line summing across the elements 

659 # line = sum(sensor_data) 

660 # return lin 

661 

662 def combine_sensor_data(self, sensor_data): 

663 # check the data is the correct size 

664 if sensor_data.shape[0] != (self.number_active_elements * self.transducer.element_width * self.transducer.element_length): 

665 raise ValueError('The number of time series in the input sensor_data must match the number of grid points in the active tranducer elements.') 

666 

667 # get index of which element each time series belongs to 

668 # Tricky things going on here 

669 ind = self.indexed_active_elements_mask[0].T[self.indexed_active_elements_mask[0].T > 0] 

670 

671 # create empty output 

672 sensor_data_sum = np.zeros((int(self.number_active_elements), sensor_data.shape[1])) 

673 

674 # check if an elevation focus is set 

675 if np.isinf(self.elevation_focus_distance): 

676 

677 raise NotImplementedError 

678 

679 # # loop over time series and sum 

680 # for ii = 1:length(ind) 

681 # sensor_data_sum(ind(ii), :) = sensor_data_sum(ind(ii), :) + sensor_data(ii, :); 

682 # end 

683 

684 else: 

685 

686 # get the elevation delay for each grid point of each 

687 # active transducer element (this is given in units of grid 

688 # points) 

689 dm = self.delay_mask(2) 

690 # dm = dm[self.active_elements_mask != 0] 

691 dm = dm[0].T[self.active_elements_mask[0].T != 0] 

692 dm = dm.astype(np.int32) 

693 

694 # loop over time series, shift and sum 

695 for ii in range(len(ind)): 

696 # FARID: something nasty can be here 

697 end = -dm[ii] if dm[ii] != 0 else sensor_data_sum.shape[-1] 

698 sensor_data_sum[ind[ii]-1, 0:end] = sensor_data_sum[ind[ii]-1, 0:end] + sensor_data[ii, dm[ii]:] 

699 

700 # divide by number of time series in each element 

701 sensor_data_sum = sensor_data_sum * (1 / (self.transducer.element_width * self.transducer.element_length)) 

702 return sensor_data_sum