ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

das.glsl (15340B)


      1 /* See LICENSE for license details. */
      2 #if   InputDataKind == DataKind_Float32
      3   #if CoherencyWeighting
      4     #define RESULT_TYPE               vec2
      5     #define RESULT_COHERENT_CAST(a)   (a).x
      6     #define RESULT_INCOHERENT_CAST(a) (a).y
      7   #endif
      8   #define SAMPLE_TYPE f32
      9 #elif InputDataKind == DataKind_Float32Complex
     10   #if CoherencyWeighting
     11     #define RESULT_TYPE               vec3
     12     #define RESULT_COHERENT_CAST(a)   (a).xy
     13     #define RESULT_INCOHERENT_CAST(a) (a).z
     14   #endif
     15   #define SAMPLE_TYPE f32vec2
     16 #else
     17   #error InputDataKind unsupported for DAS
     18 #endif
     19 
     20 #ifndef RESULT_TYPE
     21   #define RESULT_TYPE SAMPLE_TYPE
     22 #endif
     23 
     24 #ifndef RESULT_COHERENT_CAST
     25   #define RESULT_COHERENT_CAST(a) (a)
     26 #endif
     27 
     28 #if CoherencyWeighting
     29   #define RESULT_STORE(a) RESULT_TYPE(RESULT_COHERENT_CAST(a), length(a))
     30 #else
     31   #define RESULT_STORE(a) (a)
     32 #endif
     33 
     34 layout(set = ShaderResourceKind_Buffer, binding = ShaderBufferSlot_PingPong) readonly buffer RF {
     35 	InputDataType rf[];
     36 };
     37 
     38 layout(std430, buffer_reference) buffer Output {
     39 	OutputDataType x[];
     40 };
     41 
     42 layout(std430, buffer_reference) buffer IncoherentOutput {
     43 	f32 x[];
     44 };
     45 
     46 #define RX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 0, 4)
     47 #define TX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 4, 4)
     48 
     49 #define C_SPLINE 0.5
     50 
     51 #if InputDataKind == DataKind_Float32Complex
     52 vec2 rotate_iq(const vec2 iq, const float time)
     53 {
     54 	float arg    = radians(360) * DemodulationFrequency * time;
     55 	mat2  phasor = mat2( cos(arg), sin(arg),
     56 	                    -sin(arg), cos(arg));
     57 	vec2 result = phasor * iq;
     58 	return result;
     59 }
     60 #else
     61   #define rotate_iq(a, b) (a)
     62 #endif
     63 
     64 /* NOTE: See: https://cubic.org/docs/hermite.htm */
     65 SAMPLE_TYPE cubic(const int offset, const float t)
     66 {
     67 	const mat4 h = mat4(
     68 		 2, -3,  0, 1,
     69 		-2,  3,  0, 0,
     70 		 1, -2,  1, 0,
     71 		 1, -1,  0, 0
     72 	);
     73 
     74 	SAMPLE_TYPE samples[4] = {
     75 		rf[offset + 0],
     76 		rf[offset + 1],
     77 		rf[offset + 2],
     78 		rf[offset + 3],
     79 	};
     80 
     81 	vec4        S  = vec4(t * t * t, t * t, t, 1);
     82 	SAMPLE_TYPE P1 = samples[1];
     83 	SAMPLE_TYPE P2 = samples[2];
     84 	SAMPLE_TYPE T1 = C_SPLINE * (P2 - samples[0]);
     85 	SAMPLE_TYPE T2 = C_SPLINE * (samples[3] - P1);
     86 
     87 	#if   InputDataKind == DataKind_Float32
     88 	vec4 C = vec4(P1.x, P2.x, T1.x, T2.x);
     89 	SAMPLE_TYPE result = dot(S, h * C);
     90 	#elif InputDataKind == DataKind_Float32Complex
     91 	mat2x4 C = mat2x4(vec4(P1.x, P2.x, T1.x, T2.x), vec4(P1.y, P2.y, T1.y, T2.y));
     92 	SAMPLE_TYPE result = S * h * C;
     93 	#endif
     94 	return result;
     95 }
     96 
     97 SAMPLE_TYPE sample_rf(const int rf_offset, const float index)
     98 {
     99 	SAMPLE_TYPE result = SAMPLE_TYPE(0);
    100 
    101 	switch (InterpolationMode) {
    102 	case InterpolationMode_Nearest:{
    103 		if (int(index) >= 0 && int(round(index)) < SampleCount)
    104 			result = rotate_iq(rf[rf_offset + int(round(index))], index / SamplingFrequency);
    105 	}break;
    106 	case InterpolationMode_Linear:{
    107 		if (int(index) >= 0 && int(index) < SampleCount - 1) {
    108 			float tk, t = modf(index, tk);
    109 			int n = rf_offset + int(tk);
    110 			result = (1 - t) * rf[n] + t * rf[n + 1];
    111 			result = rotate_iq(result, index / SamplingFrequency);
    112 		}
    113 	}break;
    114 	case InterpolationMode_Cubic:{
    115 		if (int(index) > 0 && int(index) < SampleCount - 2) {
    116 			float tk, t = modf(index, tk);
    117 			result = rotate_iq(cubic(rf_offset + int(index), t), index / SamplingFrequency);
    118 		}
    119 	}break;
    120 	}
    121 	return result;
    122 }
    123 
    124 float sample_index(const float distance)
    125 {
    126 	float  time = distance / SpeedOfSound + TimeOffset;
    127 	return time * SamplingFrequency;
    128 }
    129 
    130 uint32_t output_index(uint32_t x, uint32_t y, uint32_t z)
    131 {
    132 	uint32_t result = output_size_x * output_size_y * z + output_size_x * y + x;
    133 	return result;
    134 }
    135 
    136 float apodize(const float arg)
    137 {
    138 	/* IMPORTANT: do not move calculation of arg into this function. It will generate a
    139 	 * conditional move resulting in cos always being evaluated causing a slowdown */
    140 
    141 	/* NOTE: constant F# dynamic receive apodization. This is implemented as:
    142 	 *
    143 	 *                  /        |x_e - x_i|\
    144 	 *    a(x, z) = cos(F# * π * ----------- ) ^ 2
    145 	 *                  \        |z_e - z_i|/
    146 	 *
    147 	 * where x,z_e are transducer element positions and x,z_i are image positions. */
    148 	float a = cos(radians(180) * arg);
    149 	return a * a;
    150 }
    151 
    152 vec2 rca_plane_projection(const vec3 point, const bool rows)
    153 {
    154 	vec2 result = vec2(point[int(rows)], point[2]);
    155 	return result;
    156 }
    157 
    158 float plane_wave_transmit_distance(const vec3 point, const float transmit_angle, const bool tx_rows)
    159 {
    160 	return dot(rca_plane_projection(point, tx_rows), vec2(sin(transmit_angle), cos(transmit_angle)));
    161 }
    162 
    163 float cylindrical_wave_transmit_distance(const vec3 point, const float focal_depth,
    164                                          const float transmit_angle, const bool tx_rows)
    165 {
    166 	vec2 f = focal_depth * vec2(sin(transmit_angle), cos(transmit_angle));
    167 	return distance(rca_plane_projection(point, tx_rows), f);
    168 }
    169 
    170 u16 tx_rx_orientation_for_acquisition(const s16 acquisition)
    171 {
    172 	u16 result = u16(TransmitReceiveOrientation);
    173 	DASArrayParametersReference dp = DASArrayParametersReference(array_parameters);
    174 	if (!SingleOrientation) result = dp.transmit_receive_orientations[acquisition];
    175 	return result;
    176 }
    177 
    178 f32vec2 focal_vector_for_acquisition(const s16 acquisition)
    179 {
    180 	DASArrayParametersReference dp = DASArrayParametersReference(array_parameters);
    181 	f32vec2 result = SingleFocus ? f32vec2(TransmitAngle, FocusDepth) : dp.focal_vectors[acquisition];
    182 	return result;
    183 }
    184 
    185 float rca_transmit_distance(const vec3 world_point, const vec2 focal_vector, const uint16_t transmit_receive_orientation)
    186 {
    187 	float result = 0;
    188 	if (TX_ORIENTATION(transmit_receive_orientation) != RCAOrientation_None) {
    189 		bool  tx_rows        = TX_ORIENTATION(transmit_receive_orientation) == RCAOrientation_Rows;
    190 		float transmit_angle = radians(focal_vector.x);
    191 		float focal_depth    = focal_vector.y;
    192 
    193 		if (isinf(focal_depth)) {
    194 			result = plane_wave_transmit_distance(world_point, transmit_angle, tx_rows);
    195 		} else {
    196 			result = cylindrical_wave_transmit_distance(world_point, focal_depth, transmit_angle, tx_rows);
    197 		}
    198 	}
    199 	return result;
    200 }
    201 
    202 RESULT_TYPE RCA(const vec3 world_point)
    203 {
    204 	RESULT_TYPE result = RESULT_TYPE(0);
    205 	for (int16_t acquisition = int16_t(0); acquisition < int16_t(AcquisitionCount); acquisition++) {
    206 		const uint16_t tx_rx_orientation = tx_rx_orientation_for_acquisition(acquisition);
    207 		const bool     rx_rows           = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows;
    208 		const vec2     focal_vector      = focal_vector_for_acquisition(acquisition);
    209 		vec2  xdc_world_point   = rca_plane_projection((xdc_transform * vec4(world_point, 1)).xyz, rx_rows);
    210 		float transmit_distance = rca_transmit_distance(world_point, focal_vector, tx_rx_orientation);
    211 
    212 		int rf_offset  = int(rf_element_offset) + acquisition * SampleCount;
    213 		rf_offset     -= int(InterpolationMode == InterpolationMode_Cubic);
    214 		for (int chunk_channel = 0; chunk_channel < ChunkChannelCount; chunk_channel++) {
    215 			int   rx_channel     = channel_offset + chunk_channel;
    216 			vec3  rx_center      = vec3(rx_channel * xdc_element_pitch, 0);
    217 			vec2  receive_vector = xdc_world_point - rca_plane_projection(rx_center, rx_rows);
    218 			float a_arg          = abs(FNumber * receive_vector.x / abs(xdc_world_point.y));
    219 
    220 			if (a_arg < 0.5f) {
    221 				float       sidx  = sample_index(transmit_distance + length(receive_vector));
    222 				SAMPLE_TYPE value = apodize(a_arg) * sample_rf(rf_offset, sidx);
    223 				result += RESULT_STORE(value);
    224 			}
    225 			rf_offset += SampleCount * AcquisitionCount;
    226 		}
    227 	}
    228 	return result;
    229 }
    230 
    231 RESULT_TYPE HERCULES(const vec3 world_point)
    232 {
    233 	DASArrayParametersReference dp = DASArrayParametersReference(array_parameters);
    234 
    235 	const uint16_t tx_rx_orientation = tx_rx_orientation_for_acquisition(int16_t(0));
    236 	const bool     rx_cols           = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Columns;
    237 	const vec2     focal_vector      = focal_vector_for_acquisition(int16_t(0));
    238 	const vec3     xdc_world_point   = (xdc_transform * vec4(world_point, 1)).xyz;
    239 
    240 	const float transmit_index   = sample_index(rca_transmit_distance(world_point, focal_vector, tx_rx_orientation));
    241 	const float z_delta_squared  = xdc_world_point.z * xdc_world_point.z;
    242 	const float f_number_over_z  = abs(FNumber / xdc_world_point.z);
    243 	const vec2  xy_world_point   = xdc_world_point.xy;
    244 	const float apodization_test = 0.25f / (f_number_over_z * f_number_over_z);
    245 
    246 	RESULT_TYPE result = RESULT_TYPE(0);
    247 	for (f32 chunk_channel = 0; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.0f) {
    248 		f32 rx_channel  = f32(channel_offset) + chunk_channel;
    249 		s32 rf_offset   = s32(rf_element_offset) + s32(chunk_channel) * SampleCount * AcquisitionCount + s32(Sparse) * SampleCount;
    250 		rf_offset      -= s32(InterpolationMode == InterpolationMode_Cubic);
    251 
    252 		// NOTE(rnp): this wouldn't be so messy if we just forced an orientation like with FORCES
    253 		vec2 element_receive_delta_squared = xy_world_point;
    254 		if (rx_cols) element_receive_delta_squared.x -= rx_channel * xdc_element_pitch.x;
    255 		else         element_receive_delta_squared.y -= rx_channel * xdc_element_pitch.y;
    256 
    257 		if (rx_cols) element_receive_delta_squared.x *= element_receive_delta_squared.x;
    258 		else         element_receive_delta_squared.y *= element_receive_delta_squared.y;
    259 
    260 		for (s32 transmit = s32(Sparse); transmit < AcquisitionCount; transmit++) {
    261 			s32 tx_channel = Sparse ? dp.sparse_elements[transmit - s32(Sparse)] : transmit;
    262 
    263 			if (rx_cols) element_receive_delta_squared.y  = xy_world_point.y - tx_channel * xdc_element_pitch.y;
    264 			else         element_receive_delta_squared.x  = xy_world_point.x - tx_channel * xdc_element_pitch.x;
    265 
    266 			if (rx_cols) element_receive_delta_squared.y *= element_receive_delta_squared.y;
    267 			else         element_receive_delta_squared.x *= element_receive_delta_squared.x;
    268 
    269 			float element_delta_squared = element_receive_delta_squared.x + element_receive_delta_squared.y;
    270 			if (element_delta_squared < apodization_test) {
    271 				/* NOTE: tribal knowledge */
    272 				float apodization = transmit == 0 ? inversesqrt(float(AcquisitionCount)) : 1.0f;
    273 				apodization *= apodize(f_number_over_z * sqrt(element_delta_squared));
    274 
    275 				float index = transmit_index + sqrt(z_delta_squared + element_delta_squared) * SamplingFrequency / SpeedOfSound;
    276 				SAMPLE_TYPE value = apodization * sample_rf(rf_offset, index);
    277 				result += RESULT_STORE(value);
    278 			}
    279 
    280 			rf_offset += SampleCount;
    281 		}
    282 	}
    283 	return result;
    284 }
    285 
    286 RESULT_TYPE FORCES(const vec3 xdc_world_point)
    287 {
    288 	RESULT_TYPE result = RESULT_TYPE(0);
    289 
    290 	DASArrayParametersReference dp = DASArrayParametersReference(array_parameters);
    291 
    292 	float z_delta_squared     = xdc_world_point.z * xdc_world_point.z;
    293 	float transmit_y_delta    = xdc_world_point.y - xdc_element_pitch.y * ChannelCount / 2;
    294 	float transmit_yz_squared = transmit_y_delta * transmit_y_delta + z_delta_squared;
    295 
    296 	for (f32 chunk_channel = 0; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.0f) {
    297 		float rx_channel      = float(channel_offset) + chunk_channel;
    298 		float receive_x_delta = xdc_world_point.x - rx_channel * xdc_element_pitch.x;
    299 		float a_arg           = abs(FNumber * receive_x_delta / xdc_world_point.z);
    300 
    301 		if (a_arg < 0.5f) {
    302 			s32 rf_offset  = s32(rf_element_offset) + s32(chunk_channel) * SampleCount * AcquisitionCount + s32(Sparse) * SampleCount;
    303 			rf_offset     -= s32(InterpolationMode == InterpolationMode_Cubic);
    304 
    305 			float receive_index = sample_index(sqrt(receive_x_delta * receive_x_delta + z_delta_squared));
    306 			float apodization   = apodize(a_arg);
    307 			for (s32 transmit = s32(Sparse); transmit < AcquisitionCount; transmit++) {
    308 				s32 tx_channel = Sparse ? dp.sparse_elements[transmit - s32(Sparse)] : transmit;
    309 				float transmit_x_delta = xdc_world_point.x - xdc_element_pitch.x * tx_channel;
    310 				float transmit_index   = sqrt(transmit_yz_squared + transmit_x_delta * transmit_x_delta) * SamplingFrequency / SpeedOfSound;
    311 
    312 				SAMPLE_TYPE value = apodization * sample_rf(rf_offset, receive_index + transmit_index);
    313 				result    += RESULT_STORE(value);
    314 				rf_offset += SampleCount;
    315 			}
    316 		}
    317 	}
    318 	return result;
    319 }
    320 
    321 RESULT_TYPE READI_FORCES(const vec3 xdc_world_point)
    322 {
    323 	RESULT_TYPE result = RESULT_TYPE(0);
    324 
    325 	DASArrayParametersReference dp = DASArrayParametersReference(array_parameters);
    326 
    327 	float z_delta_squared     = xdc_world_point.z * xdc_world_point.z;
    328 	float transmit_y_delta    = xdc_world_point.y - xdc_element_pitch.y * ChannelCount / 2;
    329 	float transmit_yz_squared = transmit_y_delta * transmit_y_delta + z_delta_squared;
    330 
    331 	// NOTE(tkh): The row we use matches the acquisition group, the column is the element group we are beamforming.
    332 	s32 hadamard_offset = s32(readi_group) * s32(ReadiGroupCount);
    333 
    334 	for (f32 chunk_channel = 0; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.0f) {
    335 		f32 rx_channel      = float(channel_offset) + chunk_channel;
    336 		f32 receive_x_delta = xdc_world_point.x - rx_channel * xdc_element_pitch.x;
    337 		f32 a_arg           = abs(FNumber * receive_x_delta / xdc_world_point.z);
    338 
    339 		if (a_arg < 0.5f) {
    340 			s32 channel_rf_offset  = s32(rf_element_offset) + s32(chunk_channel) * SampleCount * AcquisitionCount;
    341 			channel_rf_offset     -= s32(InterpolationMode == InterpolationMode_Cubic);
    342 
    343 			f32 receive_index = sample_index(sqrt(receive_x_delta * receive_x_delta + z_delta_squared));
    344 			f32 apodization   = apodize(a_arg);
    345 
    346 			// NOTE(tkh): Iterating over groups of tx elements, each group is AcquisitionCount
    347 			// sequential elements. The first element in each group is beamformed using the first
    348 			// acquisition, the second element in each group is beamformed using the second acquisition, etc.
    349 			for (s32 tx_group = 0; tx_group < s32(ReadiGroupCount); tx_group++) {
    350 				f32 group_apodization = apodization * dp.hadamard_matrix[hadamard_offset + tx_group];
    351 				s32 rf_offset = channel_rf_offset;
    352 
    353 				for (s32 tx_event = 0; tx_event < AcquisitionCount; tx_event++) {
    354 					s32 tx_element = tx_group * AcquisitionCount + tx_event;
    355 					f32 transmit_x_delta = xdc_world_point.x - xdc_element_pitch.x * tx_element;
    356 					f32 transmit_index   = sqrt(transmit_yz_squared + transmit_x_delta * transmit_x_delta) * SamplingFrequency / SpeedOfSound;
    357 
    358 					SAMPLE_TYPE value = group_apodization * sample_rf(rf_offset, receive_index + transmit_index);
    359 					result    += RESULT_STORE(value);
    360 					rf_offset += SampleCount;
    361 				}
    362 			}
    363 		}
    364 	}
    365 	return result;
    366 }
    367 
    368 void main()
    369 {
    370 	uvec3 out_voxel = gl_GlobalInvocationID;
    371 	if (!all(lessThan(out_voxel, uvec3(output_size_x, output_size_y, output_size_z))))
    372 		return;
    373 
    374 	vec3 image_points = vec3(output_size_x, output_size_y, output_size_z) - 1.0f;
    375 	vec3 point        = vec3(out_voxel) / max(vec3(1.0f), image_points);
    376 	vec3 world_point  = (voxel_transform * vec4(point, 1)).xyz;
    377 
    378 	uint32_t out_index = output_index(out_voxel.x, out_voxel.y, out_voxel.z);
    379 
    380 	RESULT_TYPE sum = RESULT_TYPE(0);
    381 	switch (AcquisitionKind) {
    382 	case AcquisitionKind_FORCES:
    383 	case AcquisitionKind_UFORCES:
    384 	{
    385 		sum = ReadiGroupCount > 1 ? READI_FORCES(world_point)
    386 		                          : FORCES(world_point);
    387 	}break;
    388 	case AcquisitionKind_HERCULES:
    389 	case AcquisitionKind_UHERCULES:
    390 	case AcquisitionKind_HERO_PA:
    391 	{
    392 		sum = HERCULES(world_point);
    393 	}break;
    394 	case AcquisitionKind_Flash:
    395 	case AcquisitionKind_RCA_TPW:
    396 	case AcquisitionKind_RCA_VLS:
    397 	{
    398 		sum = RCA(world_point);
    399 	}break;
    400 	}
    401 
    402 	#if CoherencyWeighting
    403 	IncoherentOutput(incoherent_frame).x[out_index] += RESULT_INCOHERENT_CAST(sum);
    404 	#endif
    405 
    406 	Output(output_frame).x[out_index] += RESULT_COHERENT_CAST(sum);
    407 }