ogl_beamforming

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

das.glsl (15302B)


      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 layout(std430, buffer_reference) buffer F16 { f16 x[]; };
     47 
     48 #define RX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 0, 4)
     49 #define TX_ORIENTATION(tx_rx) bitfieldExtract((tx_rx), 4, 4)
     50 
     51 #define C_SPLINE 0.5
     52 
     53 #if InputDataKind == DataKind_Float32Complex
     54 vec2 rotate_iq(const vec2 iq, const float time)
     55 {
     56 	float arg    = radians(360) * DemodulationFrequency * time;
     57 	mat2  phasor = mat2( cos(arg), sin(arg),
     58 	                    -sin(arg), cos(arg));
     59 	vec2 result = phasor * iq;
     60 	return result;
     61 }
     62 #else
     63   #define rotate_iq(a, b) (a)
     64 #endif
     65 
     66 /* NOTE: See: https://cubic.org/docs/hermite.htm */
     67 SAMPLE_TYPE cubic(const int offset, const float t)
     68 {
     69 	const mat4 h = mat4(
     70 		 2, -3,  0, 1,
     71 		-2,  3,  0, 0,
     72 		 1, -2,  1, 0,
     73 		 1, -1,  0, 0
     74 	);
     75 
     76 	SAMPLE_TYPE samples[4] = {
     77 		rf[offset + 0],
     78 		rf[offset + 1],
     79 		rf[offset + 2],
     80 		rf[offset + 3],
     81 	};
     82 
     83 	vec4        S  = vec4(t * t * t, t * t, t, 1);
     84 	SAMPLE_TYPE P1 = samples[1];
     85 	SAMPLE_TYPE P2 = samples[2];
     86 	SAMPLE_TYPE T1 = C_SPLINE * (P2 - samples[0]);
     87 	SAMPLE_TYPE T2 = C_SPLINE * (samples[3] - P1);
     88 
     89 	#if   InputDataKind == DataKind_Float32
     90 	vec4 C = vec4(P1.x, P2.x, T1.x, T2.x);
     91 	SAMPLE_TYPE result = dot(S, h * C);
     92 	#elif InputDataKind == DataKind_Float32Complex
     93 	mat2x4 C = mat2x4(vec4(P1.x, P2.x, T1.x, T2.x), vec4(P1.y, P2.y, T1.y, T2.y));
     94 	SAMPLE_TYPE result = S * h * C;
     95 	#endif
     96 	return result;
     97 }
     98 
     99 SAMPLE_TYPE sample_rf(const int rf_offset, const float index)
    100 {
    101 	SAMPLE_TYPE result = SAMPLE_TYPE(0);
    102 
    103 	switch (InterpolationMode) {
    104 	case InterpolationMode_Nearest:{
    105 		if (index >= 0.f && index < (f32(SampleCount) - 0.5f))
    106 			result = rotate_iq(rf[rf_offset + int(round(index))], index / SamplingFrequency);
    107 	}break;
    108 	case InterpolationMode_Linear:{
    109 		if (index >= 0.f && index < f32(SampleCount - 1)) {
    110 			float tk, t = modf(index, tk);
    111 			int n = rf_offset + int(tk);
    112 			result = (1 - t) * rf[n] + t * rf[n + 1];
    113 			result = rotate_iq(result, index / SamplingFrequency);
    114 		}
    115 	}break;
    116 	case InterpolationMode_Cubic:{
    117 		if (index >= 1.f && index < f32(SampleCount - 2)) {
    118 			float tk, t = modf(index, tk);
    119 			result = rotate_iq(cubic(rf_offset + int(index), t), index / SamplingFrequency);
    120 		}
    121 	}break;
    122 	}
    123 	return result;
    124 }
    125 
    126 float sample_index(const float distance)
    127 {
    128 	float  time = distance / SpeedOfSound + TimeOffset;
    129 	return time * SamplingFrequency;
    130 }
    131 
    132 u32 output_index(const u32 x, const u32 y, const u32 z)
    133 {
    134 	u32 result = OutputSizeX * OutputSizeY * z + OutputSizeX * y + x;
    135 	return result;
    136 }
    137 
    138 float apodize(const float arg)
    139 {
    140 	/* IMPORTANT: do not move calculation of arg into this function. It will generate a
    141 	 * conditional move resulting in cos always being evaluated causing a slowdown */
    142 
    143 	/* NOTE: constant F# dynamic receive apodization. This is implemented as:
    144 	 *
    145 	 *                  /        |x_e - x_i|\
    146 	 *    a(x, z) = cos(F# * π * ----------- ) ^ 2
    147 	 *                  \        |z_e - z_i|/
    148 	 *
    149 	 * where x,z_e are transducer element positions and x,z_i are image positions. */
    150 	float a = cos(radians(180) * arg);
    151 	return a * a;
    152 }
    153 
    154 vec2 rca_plane_projection(const vec3 point, const bool rows)
    155 {
    156 	vec2 result = vec2(point[int(rows)], point[2]);
    157 	return result;
    158 }
    159 
    160 float plane_wave_transmit_distance(const vec3 point, const float transmit_angle, const bool tx_rows)
    161 {
    162 	return dot(rca_plane_projection(point, tx_rows), vec2(sin(transmit_angle), cos(transmit_angle)));
    163 }
    164 
    165 float cylindrical_wave_transmit_distance(const vec3 point, const float focal_depth,
    166                                          const float transmit_angle, const bool tx_rows)
    167 {
    168 	vec2 f = focal_depth * vec2(sin(transmit_angle), cos(transmit_angle));
    169 	return distance(rca_plane_projection(point, tx_rows), f);
    170 }
    171 
    172 u8 tx_rx_orientation_for_acquisition(const s32 acquisition)
    173 {
    174 	u8 result = u8(TransmitReceiveOrientation);
    175 	ComputeArrayParametersReference dp = ComputeArrayParametersReference(ArrayParameters);
    176 	if (!SingleOrientation) result = dp.transmit_receive_orientations[acquisition];
    177 	return result;
    178 }
    179 
    180 f32vec2 focal_vector_for_acquisition(const s32 acquisition)
    181 {
    182 	ComputeArrayParametersReference dp = ComputeArrayParametersReference(ArrayParameters);
    183 	f32vec2 result = SingleFocus ? f32vec2(TransmitAngle, FocusDepth) : dp.focal_vectors[acquisition];
    184 	return result;
    185 }
    186 
    187 f32 rca_transmit_distance(const vec3 world_point, const vec2 focal_vector, const u8 transmit_receive_orientation)
    188 {
    189 	float result = 0;
    190 	if (TX_ORIENTATION(transmit_receive_orientation) != RCAOrientation_None) {
    191 		bool  tx_rows        = TX_ORIENTATION(transmit_receive_orientation) == RCAOrientation_Rows;
    192 		float transmit_angle = radians(focal_vector.x);
    193 		float focal_depth    = focal_vector.y;
    194 
    195 		if (isinf(focal_depth)) {
    196 			result = plane_wave_transmit_distance(world_point, transmit_angle, tx_rows);
    197 		} else {
    198 			result = cylindrical_wave_transmit_distance(world_point, focal_depth, transmit_angle, tx_rows);
    199 		}
    200 	}
    201 	return result;
    202 }
    203 
    204 RESULT_TYPE RCA(const vec3 world_point)
    205 {
    206 	RESULT_TYPE result = RESULT_TYPE(0);
    207 	for (s32 acquisition = 0; acquisition < s32(AcquisitionCount); acquisition++) {
    208 		const u8   tx_rx_orientation = tx_rx_orientation_for_acquisition(acquisition);
    209 		const bool rx_rows           = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Rows;
    210 		const vec2 focal_vector      = focal_vector_for_acquisition(acquisition);
    211 		vec2  xdc_world_point   = rca_plane_projection((xdc_transform * vec4(world_point, 1)).xyz, rx_rows);
    212 		float transmit_distance = rca_transmit_distance(world_point, focal_vector, tx_rx_orientation);
    213 
    214 		int rf_offset  = int(rf_element_offset) + acquisition * SampleCount;
    215 		rf_offset     -= int(InterpolationMode == InterpolationMode_Cubic);
    216 		for (f32 chunk_channel = 0.f; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.f) {
    217 			f32  rx_channel     = f32(channel_offset) + chunk_channel;
    218 			vec3 rx_center      = vec3(rx_channel * xdc_element_pitch, 0);
    219 			vec2 receive_vector = xdc_world_point - rca_plane_projection(rx_center, rx_rows);
    220 			f32  a_arg          = abs(FNumber * receive_vector.x / abs(xdc_world_point.y));
    221 
    222 			if (a_arg < 0.5f) {
    223 				float       sidx  = sample_index(transmit_distance + length(receive_vector));
    224 				SAMPLE_TYPE value = apodize(a_arg) * sample_rf(rf_offset, sidx);
    225 				result += RESULT_STORE(value);
    226 			}
    227 			rf_offset += SampleCount * AcquisitionCount;
    228 		}
    229 	}
    230 	return result;
    231 }
    232 
    233 RESULT_TYPE HERCULES(const vec3 world_point)
    234 {
    235 	ComputeArrayParametersReference dp = ComputeArrayParametersReference(ArrayParameters);
    236 
    237 	const u8   tx_rx_orientation = tx_rx_orientation_for_acquisition(0);
    238 	const bool rx_cols           = RX_ORIENTATION(tx_rx_orientation) == RCAOrientation_Columns;
    239 	const vec2 focal_vector      = focal_vector_for_acquisition(0);
    240 	const vec3 xdc_world_point   = (xdc_transform * vec4(world_point, 1)).xyz;
    241 
    242 	const float transmit_index   = sample_index(rca_transmit_distance(world_point, focal_vector, tx_rx_orientation));
    243 	const float z_delta_squared  = xdc_world_point.z * xdc_world_point.z;
    244 	const float f_number_over_z  = abs(FNumber / xdc_world_point.z);
    245 	const vec2  xy_world_point   = xdc_world_point.xy;
    246 	const float apodization_test = 0.25f / (f_number_over_z * f_number_over_z);
    247 
    248 	RESULT_TYPE result = RESULT_TYPE(0);
    249 	for (f32 chunk_channel = 0.f; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.f) {
    250 		f32 rx_channel  = f32(channel_offset) + chunk_channel;
    251 		s32 rf_offset   = s32(rf_element_offset) + s32(chunk_channel) * SampleCount * AcquisitionCount + s32(Sparse) * SampleCount;
    252 		rf_offset      -= s32(InterpolationMode == InterpolationMode_Cubic);
    253 
    254 		// NOTE(rnp): this wouldn't be so messy if we just forced an orientation like with FORCES
    255 		vec2 element_receive_delta_squared = xy_world_point;
    256 		if (rx_cols) element_receive_delta_squared.x -= rx_channel * xdc_element_pitch.x;
    257 		else         element_receive_delta_squared.y -= rx_channel * xdc_element_pitch.y;
    258 
    259 		if (rx_cols) element_receive_delta_squared.x *= element_receive_delta_squared.x;
    260 		else         element_receive_delta_squared.y *= element_receive_delta_squared.y;
    261 
    262 		for (f32 transmit = f32(Sparse); transmit < f32(AcquisitionCount); transmit += 1.f) {
    263 			f32 tx_channel = Sparse ? f32(dp.sparse_elements[s32(transmit) - s32(Sparse)]) : transmit;
    264 
    265 			if (rx_cols) element_receive_delta_squared.y  = xy_world_point.y - tx_channel * xdc_element_pitch.y;
    266 			else         element_receive_delta_squared.x  = xy_world_point.x - tx_channel * xdc_element_pitch.x;
    267 
    268 			if (rx_cols) element_receive_delta_squared.y *= element_receive_delta_squared.y;
    269 			else         element_receive_delta_squared.x *= element_receive_delta_squared.x;
    270 
    271 			float element_delta_squared = element_receive_delta_squared.x + element_receive_delta_squared.y;
    272 			if (element_delta_squared < apodization_test) {
    273 				/* NOTE: tribal knowledge */
    274 				float apodization = transmit == 0 ? inversesqrt(float(AcquisitionCount)) : 1.0f;
    275 				apodization *= apodize(f_number_over_z * sqrt(element_delta_squared));
    276 
    277 				float index = transmit_index + sqrt(z_delta_squared + element_delta_squared) * SamplingFrequency / SpeedOfSound;
    278 				SAMPLE_TYPE value = apodization * sample_rf(rf_offset, index);
    279 				result += RESULT_STORE(value);
    280 			}
    281 
    282 			rf_offset += SampleCount;
    283 		}
    284 	}
    285 	return result;
    286 }
    287 
    288 RESULT_TYPE FORCES(const vec3 xdc_world_point)
    289 {
    290 	RESULT_TYPE result = RESULT_TYPE(0);
    291 
    292 	ComputeArrayParametersReference dp = ComputeArrayParametersReference(ArrayParameters);
    293 
    294 	float z_delta_squared     = xdc_world_point.z * xdc_world_point.z;
    295 	float transmit_y_delta    = xdc_world_point.y - xdc_element_pitch.y * ChannelCount / 2;
    296 	float transmit_yz_squared = transmit_y_delta * transmit_y_delta + z_delta_squared;
    297 
    298 	for (f32 chunk_channel = 0; chunk_channel < f32(ChunkChannelCount); chunk_channel += 1.f) {
    299 		float rx_channel      = f32(channel_offset) + chunk_channel;
    300 		float receive_x_delta = xdc_world_point.x - rx_channel * xdc_element_pitch.x;
    301 		float a_arg           = abs(FNumber * receive_x_delta / xdc_world_point.z);
    302 
    303 		if (a_arg < 0.5f) {
    304 			s32 rf_offset  = s32(rf_element_offset) + s32(chunk_channel) * SampleCount * AcquisitionCount + s32(Sparse) * SampleCount;
    305 			rf_offset     -= s32(InterpolationMode == InterpolationMode_Cubic);
    306 
    307 			f32 receive_index = sample_index(sqrt(receive_x_delta * receive_x_delta + z_delta_squared));
    308 			f32 apodization   = apodize(a_arg);
    309 			for (f32 transmit = f32(Sparse); transmit < f32(AcquisitionCount); transmit += 1.f) {
    310 				f32 tx_channel = Sparse ? f32(dp.sparse_elements[s32(transmit) - s32(Sparse)]) : transmit;
    311 				f32 transmit_x_delta = xdc_world_point.x - xdc_element_pitch.x * tx_channel;
    312 				f32 transmit_index   = sqrt(transmit_yz_squared + transmit_x_delta * transmit_x_delta) * SamplingFrequency / SpeedOfSound;
    313 
    314 				SAMPLE_TYPE value = apodization * sample_rf(rf_offset, receive_index + transmit_index);
    315 				result    += RESULT_STORE(value);
    316 				rf_offset += SampleCount;
    317 			}
    318 		}
    319 	}
    320 	return result;
    321 }
    322 
    323 RESULT_TYPE READI_FORCES(const vec3 xdc_world_point)
    324 {
    325 	RESULT_TYPE result = RESULT_TYPE(0);
    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.f) {
    335 		f32 rx_channel      = f32(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 * F16(Hadamard).x[hadamard_offset + tx_group];
    351 				s32 rf_offset = channel_rf_offset;
    352 
    353 				for (f32 tx_event = 0; tx_event < f32(AcquisitionCount); tx_event += 1.f) {
    354 					f32 tx_element       = f32(tx_group) * f32(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(OutputSizeX, OutputSizeY, OutputSizeZ))))
    372 		return;
    373 
    374 	vec3 image_points = vec3(OutputSizeX, OutputSizeY, OutputSizeZ) - 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(IncoherentFrame).x[out_index] += RESULT_INCOHERENT_CAST(sum);
    404 	#endif
    405 
    406 	Output(output_frame).x[out_index] += RESULT_COHERENT_CAST(sum);
    407 }