Coverage for kwave/ktransducer.py: 21%

1 from kwave.kgrid import kWaveGrid

2 from kwave.ksensor import kSensor

3 from kwave.utils import *

4

5

6# force value to be a positive integer

7 def make_pos_int(val):

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

9

10

11 class 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

78 class 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