ogl_beamforming

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

das.glsl (8953B)


      1 /* See LICENSE for license details. */
      2 layout(std430, binding = 1) readonly restrict buffer buffer_1 {
      3 	vec2 rf_data[];
      4 };
      5 
      6 layout(rg32f, binding = 0) writeonly uniform image3D u_out_data_tex;
      7 
      8 layout(location = 2) uniform ivec3 u_voxel_offset;
      9 layout(location = 3) uniform uint  u_cycle_t;
     10 
     11 #define C_SPLINE 0.5
     12 
     13 #define TX_ROWS 0
     14 #define TX_COLS 1
     15 
     16 #define TX_MODE_TX_COLS(a) (((a) & 2) != 0)
     17 #define TX_MODE_RX_COLS(a) (((a) & 1) != 0)
     18 
     19 /* NOTE: See: https://cubic.org/docs/hermite.htm */
     20 vec2 cubic(uint ridx, float x)
     21 {
     22 	mat4 h = mat4(
     23 		 2, -3,  0, 1,
     24 		-2,  3,  0, 0,
     25 		 1, -2,  1, 0,
     26 		 1, -1,  0, 0
     27 	);
     28 
     29 	uint  xk = uint(floor(x));
     30 	float t  = (x  - float(xk));
     31 	vec4  S  = vec4(t * t * t, t * t, t, 1);
     32 
     33 	vec2 P1 = rf_data[ridx + xk];
     34 	vec2 P2 = rf_data[ridx + xk + 1];
     35 	vec2 T1 = C_SPLINE * (P2 - rf_data[ridx + xk - 1]);
     36 	vec2 T2 = C_SPLINE * (rf_data[ridx + xk + 2] - P1);
     37 
     38 	vec4 C1 = vec4(P1.x, P2.x, T1.x, T2.x);
     39 	vec4 C2 = vec4(P1.y, P2.y, T1.y, T2.y);
     40 	return vec2(dot(S, h * C1), dot(S, h * C2));
     41 }
     42 
     43 vec2 sample_rf(uint ridx, float t)
     44 {
     45 	vec2 result;
     46 	if (interpolate) result = cubic(ridx, t);
     47 	else             result = rf_data[ridx + uint(floor(t))];
     48 	return result;
     49 }
     50 
     51 vec3 calc_image_point(vec3 voxel)
     52 {
     53 	ivec3 out_data_dim = imageSize(u_out_data_tex);
     54 	vec4 output_size   = abs(output_max_coordinate - output_min_coordinate);
     55 	vec3 image_point   = output_min_coordinate.xyz + voxel * output_size.xyz / out_data_dim;
     56 
     57 	switch (das_shader_id) {
     58 	case DAS_ID_FORCES:
     59 	case DAS_ID_UFORCES:
     60 		/* TODO: fix the math so that the image plane can be aritrary */
     61 		image_point.y = 0;
     62 		break;
     63 	case DAS_ID_HERCULES:
     64 	case DAS_ID_RCA_TPW:
     65 	case DAS_ID_RCA_VLS:
     66 		/* TODO(rnp): this can be removed when we use an abitrary plane transform */
     67 		if (!all(greaterThan(out_data_dim, vec3(1, 1, 1))))
     68 			image_point.y = off_axis_pos;
     69 		break;
     70 	}
     71 
     72 	return image_point;
     73 }
     74 
     75 vec2 apodize(vec2 value, float apodization_arg, float distance)
     76 {
     77 	/* NOTE: apodization value for this transducer element */
     78 	float a  = cos(clamp(abs(apodization_arg * distance), 0, 0.25 * radians(360)));
     79 	return value * a * a;
     80 }
     81 
     82 vec3 orientation_projection(vec3 point, bool rows)
     83 {
     84 	return point * vec3(!rows, rows, 1);
     85 }
     86 
     87 vec3 world_space_to_rca_space(vec3 image_point, bool rx_rows)
     88 {
     89 	return orientation_projection((xdc_transform * vec4(image_point, 1)).xyz, rx_rows);
     90 }
     91 
     92 float sample_index(float distance)
     93 {
     94 	float  time = distance / speed_of_sound + time_offset;
     95 	return time * sampling_frequency;
     96 }
     97 
     98 float planewave_transmit_distance(vec3 point, float transmit_angle, bool tx_rows)
     99 {
    100 	return dot(orientation_projection(point, tx_rows),
    101 	           vec3(sin(transmit_angle), sin(transmit_angle), cos(transmit_angle)));
    102 }
    103 
    104 float cylindricalwave_transmit_distance(vec3 point, float focal_depth, float transmit_angle, bool tx_rows)
    105 {
    106 	vec3 f = focal_depth * vec3(sin(transmit_angle), sin(transmit_angle), cos(transmit_angle));
    107 	return length(orientation_projection(point - f, tx_rows));
    108 }
    109 
    110 vec2 RCA(vec3 image_point, vec3 delta, float apodization_arg)
    111 {
    112 	uint ridx      = 0;
    113 	int  direction = beamform_plane;
    114 	if (direction != TX_ROWS) image_point = image_point.yxz;
    115 
    116 	bool tx_col = TX_MODE_TX_COLS(transmit_mode);
    117 	bool rx_col = TX_MODE_RX_COLS(transmit_mode);
    118 
    119 	vec3 receive_point = world_space_to_rca_space(image_point, !rx_col);
    120 	delta = orientation_projection(delta, !rx_col);
    121 
    122 	vec2 sum = vec2(0);
    123 	/* NOTE: For Each Acquistion in Raw Data */
    124 	// uint i = (dec_data_dim.z - 1) * uint(clamp(u_cycle_t, 0, 1)); {
    125 	for (uint i = 0; i < dec_data_dim.z; i++) {
    126 		uint base_idx = i / 4;
    127 		uint sub_idx  = i % 4;
    128 
    129 		float focal_depth    = focal_depths[base_idx][sub_idx];
    130 		float transmit_angle = radians(transmit_angles[base_idx][sub_idx]);
    131 
    132 		float transmit_distance;
    133 		if (isinf(focal_depth)) {
    134 			transmit_distance = planewave_transmit_distance(image_point, transmit_angle,
    135 			                                                !tx_col);
    136 		} else {
    137 			transmit_distance = cylindricalwave_transmit_distance(image_point, focal_depth,
    138 			                                                      transmit_angle,
    139 			                                                      !tx_col);
    140 		}
    141 
    142 		vec3 receive_distance = receive_point;
    143 		/* NOTE: For Each Receiver */
    144 		// uint j = (dec_data_dim.z - 1) * uint(clamp(u_cycle_t, 0, 1)); {
    145 		for (uint j = 0; j < dec_data_dim.y; j++) {
    146 			float sidx  = sample_index(transmit_distance + length(receive_distance));
    147 			vec2 valid  = vec2(sidx >= 0) * vec2(sidx < dec_data_dim.x);
    148 			sum        += apodize(sample_rf(ridx, sidx), apodization_arg, length(receive_distance.xy)) * valid;
    149 			receive_distance   -= delta;
    150 			ridx       += dec_data_dim.x;
    151 		}
    152 	}
    153 	return sum;
    154 }
    155 
    156 vec2 HERCULES(vec3 image_point, vec3 delta, float apodization_arg)
    157 {
    158 	uint uhercules = uint(das_shader_id == DAS_ID_UHERCULES);
    159 	uint ridx      = dec_data_dim.y * dec_data_dim.x * uhercules;
    160 	int  direction = beamform_plane;
    161 	if (direction != TX_ROWS) image_point = image_point.yxz;
    162 
    163 	bool tx_col = TX_MODE_TX_COLS(transmit_mode);
    164 	bool rx_col = TX_MODE_RX_COLS(transmit_mode);
    165 	float focal_depth    = focal_depths[0][0];
    166 	float transmit_angle = radians(transmit_angles[0][0]);
    167 
    168 	vec3 receive_point = (xdc_transform * vec4(image_point, 1)).xyz;
    169 
    170 	float transmit_distance;
    171 	if (isinf(focal_depth)) {
    172 		transmit_distance = planewave_transmit_distance(image_point, transmit_angle,
    173 		                                                !tx_col);
    174 	} else {
    175 		transmit_distance = cylindricalwave_transmit_distance(image_point, focal_depth,
    176 		                                                      transmit_angle,
    177 		                                                      !tx_col);
    178 	}
    179 
    180 	vec2 sum = vec2(0);
    181 	/* NOTE: For Each Acquistion in Raw Data */
    182 	for (uint i = uhercules; i < dec_data_dim.z; i++) {
    183 		uint base_idx = ((i - uhercules) / 8);
    184 		uint sub_idx  = ((i - uhercules) % 8) / 2;
    185 		uint shift    = (~(i - uhercules) & 1u) * 16u;
    186 		uint channel  = (uforces_channels[base_idx][sub_idx] << shift) >> 16u;
    187 
    188 		/* NOTE: For Each Virtual Source */
    189 		for (uint j = 0; j < dec_data_dim.y; j++) {
    190 			vec3 element_position;
    191 			if (rx_col) element_position = vec3(j, channel, 0) * delta;
    192 			else        element_position = vec3(channel, j, 0) * delta;
    193 			vec3 receive_distance = receive_point - element_position;
    194 			float sidx  = sample_index(transmit_distance + length(receive_distance));
    195 			vec2 valid  = vec2(sidx >= 0) * vec2(sidx < dec_data_dim.x);
    196 
    197 			/* NOTE: tribal knowledge */
    198 			if (i == 0) valid *= inversesqrt(dec_data_dim.z);
    199 
    200 			sum  += apodize(sample_rf(ridx, sidx), apodization_arg,
    201 			                length(receive_distance.xy)) * valid;
    202 			ridx += dec_data_dim.x;
    203 		}
    204 	}
    205 	return sum;
    206 }
    207 
    208 vec2 uFORCES(vec3 image_point, vec3 delta, float apodization_arg)
    209 {
    210 	/* NOTE: skip first acquisition in uforces since its garbage */
    211 	uint uforces = uint(das_shader_id == DAS_ID_UFORCES);
    212 	uint ridx    = dec_data_dim.y * dec_data_dim.x * uforces;
    213 
    214 	image_point  = (xdc_transform * vec4(image_point, 1)).xyz;
    215 
    216 	vec3 focal_point_offset = vec3(0, delta.y * floor(dec_data_dim.y / 2), 0);
    217 	delta.y = 0;
    218 
    219 	vec2 sum = vec2(0);
    220 	for (uint i = uforces; i < dec_data_dim.z; i++) {
    221 		uint base_idx = ((i - uforces) / 8);
    222 		uint sub_idx  = ((i - uforces) % 8) / 2;
    223 		uint shift    = (~(i - uforces) & 1u) * 16u;
    224 		uint channel  = (uforces_channels[base_idx][sub_idx] << shift) >> 16u;
    225 
    226 		vec2  rdist         = vec2(image_point.x, image_point.z);
    227 		vec3  focal_point   = channel * delta + focal_point_offset;
    228 		float transmit_dist = distance(image_point, focal_point);
    229 
    230 		for (uint j = 0; j < dec_data_dim.y; j++) {
    231 			float sidx  = sample_index(transmit_dist + length(rdist));
    232 			vec2 valid  = vec2(sidx >= 0) * vec2(sidx < dec_data_dim.x);
    233 			sum        += apodize(sample_rf(ridx, sidx), apodization_arg, rdist.x) * valid;
    234 			rdist.x    -= delta.x;
    235 			ridx       += dec_data_dim.x;
    236 		}
    237 	}
    238 	return sum;
    239 }
    240 
    241 void main()
    242 {
    243 	/* NOTE: Convert voxel to physical coordinates */
    244 	ivec3 out_coord   = ivec3(gl_GlobalInvocationID) + u_voxel_offset;
    245 	vec3  image_point = calc_image_point(vec3(out_coord));
    246 
    247 	/* NOTE: used for constant F# dynamic receive apodization. This is implemented as:
    248 	 *
    249 	 *                  /        |x_e - x_i|\
    250 	 *    a(x, z) = cos(F# * π * ----------- ) ^ 2
    251 	 *                  \        |z_e - z_i|/
    252 	 *
    253 	 * where x,z_e are transducer element positions and x,z_i are image positions. */
    254 	float apod_arg = f_number * radians(180) / abs(image_point.z);
    255 
    256 	/* NOTE: skip over channels corresponding to other arrays */
    257 	vec2 sum;
    258 	switch (das_shader_id) {
    259 	case DAS_ID_FORCES:
    260 	case DAS_ID_UFORCES:
    261 		sum = uFORCES(image_point, vec3(xdc_element_pitch, 0), apod_arg);
    262 		break;
    263 	case DAS_ID_HERCULES:
    264 	case DAS_ID_UHERCULES:
    265 		sum = HERCULES(image_point, vec3(xdc_element_pitch, 0), apod_arg);
    266 		break;
    267 	case DAS_ID_RCA_TPW:
    268 	case DAS_ID_RCA_VLS:
    269 		sum = RCA(image_point, vec3(xdc_element_pitch, 0), apod_arg);
    270 		break;
    271 	}
    272 
    273 	imageStore(u_out_data_tex, out_coord, vec4(sum, 0, 0));
    274 }