// 2D phase unwrapping, modified for inclusion in scipy by Gregor Thalhammer // Original file name: Miguel_2D_unwrapper_with_mask_and_wrap_around_option.c //This program was written by Munther Gdeisat and Miguel Arevallilo Herraez to program the two-dimensional unwrapper //entitled "Fast two-dimensional phase-unwrapping algorithm based on sorting by //reliability following a noncontinuous path" //by Miguel Arevallilo Herraez, David R. Burton, Michael J. Lalor, and Munther A. Gdeisat //published in the Journal Applied Optics, Vol. 41, No. 35, pp. 7437, 2002. //This program was written by Munther Gdeisat, Liverpool John Moores University, United Kingdom. //Date 26th August 2007 //The wrapped phase map is assumed to be of floating point data type. The resultant unwrapped phase map is also of floating point type. //The mask is of byte data type. //When the mask is 255 this means that the pixel is valid //When the mask is 0 this means that the pixel is invalid (noisy or corrupted pixel) //This program takes into consideration the image wrap around problem encountered in MRI imaging. #include #include #include #include #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif #define PI M_PI #define TWOPI (2 * M_PI) //TODO: remove global variables //TODO: make thresholds independent #define NOMASK 0 #define MASK 1 typedef struct { double mod; int x_connectivity; int y_connectivity; int no_of_edges; } params_t; //PIXELM information struct PIXELM { int increment; //No. of 2*pi to add to the pixel to unwrap it int number_of_pixels_in_group;//No. of pixel in the pixel group double value; //value of the pixel double reliability; unsigned char input_mask; //0 pixel is masked. NOMASK pixel is not masked unsigned char extended_mask; //0 pixel is masked. NOMASK pixel is not masked int group; //group No. int new_group; struct PIXELM *head; //pointer to the first pixel in the group in the linked list struct PIXELM *last; //pointer to the last pixel in the group struct PIXELM *next; //pointer to the next pixel in the group }; typedef struct PIXELM PIXELM; //the EDGE is the line that connects two pixels. //if we have S pixels, then we have S horizontal edges and S vertical edges struct EDGE { double reliab; //reliabilty of the edge and it depends on the two pixels PIXELM *pointer_1; //pointer to the first pixel PIXELM *pointer_2; //pointer to the second pixel int increment; //No. of 2*pi to add to one of the pixels to //unwrap it with respect to the second }; typedef struct EDGE EDGE; //---------------start quicker_sort algorithm -------------------------------- #define swap(x,y) {EDGE t; t=x; x=y; y=t;} #define order(x,y) if (x.reliab > y.reliab) swap(x,y) #define o2(x,y) order(x,y) #define o3(x,y,z) o2(x,y); o2(x,z); o2(y,z) typedef enum {yes, no} yes_no; yes_no find_pivot(EDGE *left, EDGE *right, double *pivot_ptr) { EDGE a, b, c, *p; a = *left; b = *(left + (right - left) / 2); c = *right; o3(a, b, c); if (a.reliab < b.reliab) { *pivot_ptr = b.reliab; return yes; } if (b.reliab < c.reliab) { *pivot_ptr = c.reliab; return yes; } for (p = left + 1; p <= right; ++p) { if (p->reliab != left->reliab) { *pivot_ptr = (p->reliab < left->reliab) ? left->reliab : p->reliab; return yes; } } return no; } EDGE *partition(EDGE *left, EDGE *right, double pivot) { while (left <= right) { while (left->reliab < pivot) ++left; while (right->reliab >= pivot) --right; if (left < right) { swap (*left, *right); ++left; --right; } } return left; } void quicker_sort(EDGE *left, EDGE *right) { EDGE *p; double pivot; if (find_pivot(left, right, &pivot) == yes) { p = partition(left, right, pivot); quicker_sort(left, p - 1); quicker_sort(p, right); } } //--------------end quicker_sort algorithm ----------------------------------- //--------------------start initialize pixels ---------------------------------- //initialize pixels. See the explination of the pixel class above. //initially every pixel is assumed to belong to a group consisting of only itself void initialisePIXELs(double *wrapped_image, unsigned char *input_mask, unsigned char *extended_mask, PIXELM *pixel, int image_width, int image_height) { PIXELM *pixel_pointer = pixel; double *wrapped_image_pointer = wrapped_image; unsigned char *input_mask_pointer = input_mask; unsigned char *extended_mask_pointer = extended_mask; int i, j; for (i=0; i < image_height; i++) { for (j=0; j < image_width; j++) { pixel_pointer->increment = 0; pixel_pointer->number_of_pixels_in_group = 1; pixel_pointer->value = *wrapped_image_pointer; pixel_pointer->reliability = 9999999. + rand(); pixel_pointer->input_mask = *input_mask_pointer; pixel_pointer->extended_mask = *extended_mask_pointer; pixel_pointer->head = pixel_pointer; pixel_pointer->last = pixel_pointer; pixel_pointer->next = NULL; pixel_pointer->new_group = 0; pixel_pointer->group = -1; pixel_pointer++; wrapped_image_pointer++; input_mask_pointer++; extended_mask_pointer++; } } } //-------------------end initialize pixels ----------- //gamma function in the paper double wrap(double pixel_value) { double wrapped_pixel_value; if (pixel_value > PI) wrapped_pixel_value = pixel_value - TWOPI; else if (pixel_value < -PI) wrapped_pixel_value = pixel_value + TWOPI; else wrapped_pixel_value = pixel_value; return wrapped_pixel_value; } // pixelL_value is the left pixel, pixelR_value is the right pixel int find_wrap(double pixelL_value, double pixelR_value) { double difference; int wrap_value; difference = pixelL_value - pixelR_value; if (difference > PI) wrap_value = -1; else if (difference < -PI) wrap_value = 1; else wrap_value = 0; return wrap_value; } void extend_mask(unsigned char *input_mask, unsigned char *extended_mask, int image_width, int image_height, params_t *params) { int i,j; int image_width_plus_one = image_width + 1; int image_width_minus_one = image_width - 1; unsigned char *IMP = input_mask + image_width + 1; //input mask pointer unsigned char *EMP = extended_mask + image_width + 1; //extended mask pointer //extend the mask for the image except borders for (i=1; i < image_height - 1; ++i) { for (j=1; j < image_width - 1; ++j) { if ( (*IMP) == NOMASK && (*(IMP + 1) == NOMASK) && (*(IMP - 1) == NOMASK) && (*(IMP + image_width) == NOMASK) && (*(IMP - image_width) == NOMASK) && (*(IMP - image_width_minus_one) == NOMASK) && (*(IMP - image_width_plus_one) == NOMASK) && (*(IMP + image_width_minus_one) == NOMASK) && (*(IMP + image_width_plus_one) == NOMASK) ) { *EMP = NOMASK; } ++EMP; ++IMP; } EMP += 2; IMP += 2; } if (params->x_connectivity == 1) { //extend the mask for the right border of the image IMP = input_mask + 2 * image_width - 1; EMP = extended_mask + 2 * image_width -1; for (i=1; i < image_height - 1; ++ i) { if ( (*IMP) == NOMASK && (*(IMP - 1) == NOMASK) && (*(IMP + 1) == NOMASK) && (*(IMP + image_width) == NOMASK) && (*(IMP - image_width) == NOMASK) && (*(IMP - image_width - 1) == NOMASK) && (*(IMP - image_width + 1) == NOMASK) && (*(IMP + image_width - 1) == NOMASK) && (*(IMP - 2 * image_width + 1) == NOMASK) ) { *EMP = NOMASK; } EMP += image_width; IMP += image_width; } //extend the mask for the left border of the image IMP = input_mask + image_width; EMP = extended_mask + image_width; for (i=1; i < image_height - 1; ++i) { if ( (*IMP) == NOMASK && (*(IMP - 1) == NOMASK) && (*(IMP + 1) == NOMASK) && (*(IMP + image_width) == NOMASK) && (*(IMP - image_width) == NOMASK) && (*(IMP - image_width + 1) == NOMASK) && (*(IMP + image_width + 1) == NOMASK) && (*(IMP + image_width - 1) == NOMASK) && (*(IMP + 2 * image_width - 1) == NOMASK) ) { *EMP = NOMASK; } EMP += image_width; IMP += image_width; } } if (params->y_connectivity == 1) { //extend the mask for the top border of the image IMP = input_mask + 1; EMP = extended_mask + 1; for (i=1; i < image_width - 1; ++i) { if ( (*IMP) == NOMASK && (*(IMP - 1) == NOMASK) && (*(IMP + 1) == NOMASK) && (*(IMP + image_width) == NOMASK) && (*(IMP + image_width * (image_height - 1)) == NOMASK) && (*(IMP + image_width + 1) == NOMASK) && (*(IMP + image_width - 1) == NOMASK) && (*(IMP + image_width * (image_height - 1) - 1) == NOMASK) && (*(IMP + image_width * (image_height - 1) + 1) == NOMASK) ) { *EMP = NOMASK; } EMP++; IMP++; } //extend the mask for the bottom border of the image IMP = input_mask + image_width * (image_height - 1) + 1; EMP = extended_mask + image_width * (image_height - 1) + 1; for (i=1; i < image_width - 1; ++i) { if ( (*IMP) == NOMASK && (*(IMP - 1) == NOMASK) && (*(IMP + 1) == NOMASK) && (*(IMP - image_width) == NOMASK) && (*(IMP - image_width - 1) == NOMASK) && (*(IMP - image_width + 1) == NOMASK) && (*(IMP - image_width * (image_height - 1) ) == NOMASK) && (*(IMP - image_width * (image_height - 1) - 1) == NOMASK) && (*(IMP - image_width * (image_height - 1) + 1) == NOMASK) ) { *EMP = NOMASK; } EMP++; IMP++; } } } void calculate_reliability(double *wrappedImage, PIXELM *pixel, int image_width, int image_height, params_t *params) { int image_width_plus_one = image_width + 1; int image_width_minus_one = image_width - 1; PIXELM *pixel_pointer = pixel + image_width_plus_one; double *WIP = wrappedImage + image_width_plus_one; //WIP is the wrapped image pointer double H, V, D1, D2; int i, j; for (i = 1; i < image_height -1; ++i) { for (j = 1; j < image_width - 1; ++j) { if (pixel_pointer->extended_mask == NOMASK) { H = wrap(*(WIP - 1) - *WIP) - wrap(*WIP - *(WIP + 1)); V = wrap(*(WIP - image_width) - *WIP) - wrap(*WIP - *(WIP + image_width)); D1 = wrap(*(WIP - image_width_plus_one) - *WIP) - wrap(*WIP - *(WIP + image_width_plus_one)); D2 = wrap(*(WIP - image_width_minus_one) - *WIP) - wrap(*WIP - *(WIP + image_width_minus_one)); pixel_pointer->reliability = H*H + V*V + D1*D1 + D2*D2; } pixel_pointer++; WIP++; } pixel_pointer += 2; WIP += 2; } if (params->x_connectivity == 1) { //calculating the reliability for the left border of the image PIXELM *pixel_pointer = pixel + image_width; double *WIP = wrappedImage + image_width; for (i = 1; i < image_height - 1; ++i) { if (pixel_pointer->extended_mask == NOMASK) { H = wrap(*(WIP + image_width - 1) - *WIP) - wrap(*WIP - *(WIP + 1)); V = wrap(*(WIP - image_width) - *WIP) - wrap(*WIP - *(WIP + image_width)); D1 = wrap(*(WIP - 1) - *WIP) - wrap(*WIP - *(WIP + image_width_plus_one)); D2 = wrap(*(WIP - image_width_minus_one) - *WIP) - wrap(*WIP - *(WIP + 2* image_width - 1)); pixel_pointer->reliability = H*H + V*V + D1*D1 + D2*D2; } pixel_pointer += image_width; WIP += image_width; } //calculating the reliability for the right border of the image pixel_pointer = pixel + 2 * image_width - 1; WIP = wrappedImage + 2 * image_width - 1; for (i = 1; i < image_height - 1; ++i) { if (pixel_pointer->extended_mask == NOMASK) { H = wrap(*(WIP - 1) - *WIP) - wrap(*WIP - *(WIP - image_width_minus_one)); V = wrap(*(WIP - image_width) - *WIP) - wrap(*WIP - *(WIP + image_width)); D1 = wrap(*(WIP - image_width_plus_one) - *WIP) - wrap(*WIP - *(WIP + 1)); D2 = wrap(*(WIP - 2 * image_width - 1) - *WIP) - wrap(*WIP - *(WIP + image_width_minus_one)); pixel_pointer->reliability = H*H + V*V + D1*D1 + D2*D2; } pixel_pointer += image_width; WIP += image_width; } } if (params->y_connectivity == 1) { //calculating the reliability for the top border of the image PIXELM *pixel_pointer = pixel + 1; double *WIP = wrappedImage + 1; for (i = 1; i < image_width - 1; ++i) { if (pixel_pointer->extended_mask == NOMASK) { H = wrap(*(WIP - 1) - *WIP) - wrap(*WIP - *(WIP + 1)); V = wrap(*(WIP + image_width*(image_height - 1)) - *WIP) - wrap(*WIP - *(WIP + image_width)); D1 = wrap(*(WIP + image_width*(image_height - 1) - 1) - *WIP) - wrap(*WIP - *(WIP + image_width_plus_one)); D2 = wrap(*(WIP + image_width*(image_height - 1) + 1) - *WIP) - wrap(*WIP - *(WIP + image_width_minus_one)); pixel_pointer->reliability = H*H + V*V + D1*D1 + D2*D2; } pixel_pointer++; WIP++; } //calculating the reliability for the bottom border of the image pixel_pointer = pixel + (image_height - 1) * image_width + 1; WIP = wrappedImage + (image_height - 1) * image_width + 1; for (i = 1; i < image_width - 1; ++i) { if (pixel_pointer->extended_mask == NOMASK) { H = wrap(*(WIP - 1) - *WIP) - wrap(*WIP - *(WIP + 1)); V = wrap(*(WIP - image_width) - *WIP) - wrap(*WIP - *(WIP -(image_height - 1) * (image_width))); D1 = wrap(*(WIP - image_width_plus_one) - *WIP) - wrap(*WIP - *(WIP - (image_height - 1) * (image_width) + 1)); D2 = wrap(*(WIP - image_width_minus_one) - *WIP) - wrap(*WIP - *(WIP - (image_height - 1) * (image_width) - 1)); pixel_pointer->reliability = H*H + V*V + D1*D1 + D2*D2; } pixel_pointer++; WIP++; } } } //calculate the reliability of the horizontal edges of the image //it is calculated by adding the reliability of pixel and the relibility of //its right-hand neighbour //edge is calculated between a pixel and its next neighbour void horizontalEDGEs(PIXELM *pixel, EDGE *edge, int image_width, int image_height, params_t *params) { int i, j; EDGE *edge_pointer = edge; PIXELM *pixel_pointer = pixel; int no_of_edges = params->no_of_edges; for (i = 0; i < image_height; i++) { for (j = 0; j < image_width - 1; j++) { if (pixel_pointer->input_mask == NOMASK && (pixel_pointer + 1)->input_mask == NOMASK) { edge_pointer->pointer_1 = pixel_pointer; edge_pointer->pointer_2 = (pixel_pointer+1); edge_pointer->reliab = pixel_pointer->reliability + (pixel_pointer + 1)->reliability; edge_pointer->increment = find_wrap(pixel_pointer->value, (pixel_pointer + 1)->value); edge_pointer++; no_of_edges++; } pixel_pointer++; } pixel_pointer++; } //construct edges at the right border of the image if (params->x_connectivity == 1) { pixel_pointer = pixel + image_width - 1; for (i = 0; i < image_height; i++) { if (pixel_pointer->input_mask == NOMASK && (pixel_pointer - image_width + 1)->input_mask == NOMASK) { edge_pointer->pointer_1 = pixel_pointer; edge_pointer->pointer_2 = (pixel_pointer - image_width + 1); edge_pointer->reliab = pixel_pointer->reliability + (pixel_pointer - image_width + 1)->reliability; edge_pointer->increment = find_wrap(pixel_pointer->value, (pixel_pointer - image_width + 1)->value); edge_pointer++; no_of_edges++; } pixel_pointer+=image_width; } } params->no_of_edges = no_of_edges; } //calculate the reliability of the vertical edges of the image //it is calculated by adding the reliability of pixel and the relibility of //its lower neighbour in the image. void verticalEDGEs(PIXELM *pixel, EDGE *edge, int image_width, int image_height, params_t *params) { int i, j; int no_of_edges = params->no_of_edges; PIXELM *pixel_pointer = pixel; EDGE *edge_pointer = edge + no_of_edges; for (i=0; i < image_height - 1; i++) { for (j=0; j < image_width; j++) { if (pixel_pointer->input_mask == NOMASK && (pixel_pointer + image_width)->input_mask == NOMASK) { edge_pointer->pointer_1 = pixel_pointer; edge_pointer->pointer_2 = (pixel_pointer + image_width); edge_pointer->reliab = pixel_pointer->reliability + (pixel_pointer + image_width)->reliability; edge_pointer->increment = find_wrap(pixel_pointer->value, (pixel_pointer + image_width)->value); edge_pointer++; no_of_edges++; } pixel_pointer++; } //j loop } // i loop //construct edges that connect at the bottom border of the image if (params->y_connectivity == 1) { pixel_pointer = pixel + image_width *(image_height - 1); for (i = 0; i < image_width; i++) { if (pixel_pointer->input_mask == NOMASK && (pixel_pointer - image_width *(image_height - 1))->input_mask == NOMASK) { edge_pointer->pointer_1 = pixel_pointer; edge_pointer->pointer_2 = (pixel_pointer - image_width *(image_height - 1)); edge_pointer->reliab = pixel_pointer->reliability + (pixel_pointer - image_width *(image_height - 1))->reliability; edge_pointer->increment = find_wrap(pixel_pointer->value, (pixel_pointer - image_width *(image_height - 1))->value); edge_pointer++; no_of_edges++; } pixel_pointer++; } } params->no_of_edges = no_of_edges; } //gather the pixels of the image into groups void gatherPIXELs(EDGE *edge, params_t *params) { int k; PIXELM *PIXEL1; PIXELM *PIXEL2; PIXELM *group1; PIXELM *group2; EDGE *pointer_edge = edge; int incremento; for (k = 0; k < params->no_of_edges; k++) { PIXEL1 = pointer_edge->pointer_1; PIXEL2 = pointer_edge->pointer_2; //PIXELM 1 and PIXELM 2 belong to different groups //initially each pixel is a group by it self and one pixel can construct a group //no else or else if to this if if (PIXEL2->head != PIXEL1->head) { //PIXELM 2 is alone in its group //merge this pixel with PIXELM 1 group and find the number of 2 pi to add //to or subtract to unwrap it if ((PIXEL2->next == NULL) && (PIXEL2->head == PIXEL2)) { PIXEL1->head->last->next = PIXEL2; PIXEL1->head->last = PIXEL2; (PIXEL1->head->number_of_pixels_in_group)++; PIXEL2->head=PIXEL1->head; PIXEL2->increment = PIXEL1->increment-pointer_edge->increment; } //PIXELM 1 is alone in its group //merge this pixel with PIXELM 2 group and find the number of 2 pi to add //to or subtract to unwrap it else if ((PIXEL1->next == NULL) && (PIXEL1->head == PIXEL1)) { PIXEL2->head->last->next = PIXEL1; PIXEL2->head->last = PIXEL1; (PIXEL2->head->number_of_pixels_in_group)++; PIXEL1->head = PIXEL2->head; PIXEL1->increment = PIXEL2->increment+pointer_edge->increment; } //PIXELM 1 and PIXELM 2 both have groups else { group1 = PIXEL1->head; group2 = PIXEL2->head; //if the no. of pixels in PIXELM 1 group is larger than the //no. of pixels in PIXELM 2 group. Merge PIXELM 2 group to //PIXELM 1 group and find the number of wraps between PIXELM 2 //group and PIXELM 1 group to unwrap PIXELM 2 group with respect //to PIXELM 1 group. the no. of wraps will be added to PIXELM 2 //group in the future if (group1->number_of_pixels_in_group > group2->number_of_pixels_in_group) { //merge PIXELM 2 with PIXELM 1 group group1->last->next = group2; group1->last = group2->last; group1->number_of_pixels_in_group = group1->number_of_pixels_in_group + group2->number_of_pixels_in_group; incremento = PIXEL1->increment-pointer_edge->increment - PIXEL2->increment; //merge the other pixels in PIXELM 2 group to PIXELM 1 group while (group2 != NULL) { group2->head = group1; group2->increment += incremento; group2 = group2->next; } } //if the no. of pixels in PIXELM 2 group is larger than the //no. of pixels in PIXELM 1 group. Merge PIXELM 1 group to //PIXELM 2 group and find the number of wraps between PIXELM 2 //group and PIXELM 1 group to unwrap PIXELM 1 group with respect //to PIXELM 2 group. the no. of wraps will be added to PIXELM 1 //group in the future else { //merge PIXELM 1 with PIXELM 2 group group2->last->next = group1; group2->last = group1->last; group2->number_of_pixels_in_group = group2->number_of_pixels_in_group + group1->number_of_pixels_in_group; incremento = PIXEL2->increment + pointer_edge->increment - PIXEL1->increment; //merge the other pixels in PIXELM 2 group to PIXELM 1 group while (group1 != NULL) { group1->head = group2; group1->increment += incremento; group1 = group1->next; } // while } // else } //else } //if pointer_edge++; } } //unwrap the image void unwrapImage(PIXELM *pixel, int image_width, int image_height) { int i; int image_size = image_width * image_height; PIXELM *pixel_pointer=pixel; for (i = 0; i < image_size; i++) { pixel_pointer->value += TWOPI * (double)(pixel_pointer->increment); pixel_pointer++; } } //set the masked pixels (mask = 0) to the minimum of the unwrapper phase void maskImage(PIXELM *pixel, unsigned char *input_mask, int image_width, int image_height) { int image_width_plus_one = image_width + 1; int image_height_plus_one = image_height + 1; int image_width_minus_one = image_width - 1; int image_height_minus_one = image_height - 1; PIXELM *pointer_pixel = pixel; unsigned char *IMP = input_mask; //input mask pointer double min=99999999; int i; int image_size = image_width * image_height; //find the minimum of the unwrapped phase for (i = 0; i < image_size; i++) { if ((pointer_pixel->value < min) && (*IMP == NOMASK)) min = pointer_pixel->value; pointer_pixel++; IMP++; } pointer_pixel = pixel; IMP = input_mask; //set the masked pixels to minimum for (i = 0; i < image_size; i++) { if ((*IMP) == MASK) { pointer_pixel->value = min; } pointer_pixel++; IMP++; } } //the input to this unwrapper is an array that contains the wrapped //phase map. copy the image on the buffer passed to this unwrapper to //over-write the unwrapped phase map on the buffer of the wrapped //phase map. void returnImage(PIXELM *pixel, double *unwrapped_image, int image_width, int image_height) { int i; int image_size = image_width * image_height; double *unwrapped_image_pointer = unwrapped_image; PIXELM *pixel_pointer = pixel; for (i=0; i < image_size; i++) { *unwrapped_image_pointer = pixel_pointer->value; pixel_pointer++; unwrapped_image_pointer++; } } //the main function of the unwrapper void unwrap2D(double* wrapped_image, double* UnwrappedImage, unsigned char* input_mask, int image_width, int image_height, int wrap_around_x, int wrap_around_y) { params_t params = {TWOPI, wrap_around_x, wrap_around_y, 0}; unsigned char *extended_mask; PIXELM *pixel; EDGE *edge; int image_size = image_height * image_width; int No_of_Edges_initially = 2 * image_width * image_height; extended_mask = (unsigned char *) calloc(image_size, sizeof(unsigned char)); pixel = (PIXELM *) calloc(image_size, sizeof(PIXELM)); edge = (EDGE *) calloc(No_of_Edges_initially, sizeof(EDGE)); extend_mask(input_mask, extended_mask, image_width, image_height, ¶ms); initialisePIXELs(wrapped_image, input_mask, extended_mask, pixel, image_width, image_height); calculate_reliability(wrapped_image, pixel, image_width, image_height, ¶ms); horizontalEDGEs(pixel, edge, image_width, image_height, ¶ms); verticalEDGEs(pixel, edge, image_width, image_height, ¶ms); //sort the EDGEs depending on their reiability. The PIXELs with higher //relibility (small value) first quicker_sort(edge, edge + params.no_of_edges - 1); //gather PIXELs into groups gatherPIXELs(edge, ¶ms); unwrapImage(pixel, image_width, image_height); maskImage(pixel, input_mask, image_width, image_height); //copy the image from PIXELM structure to the unwrapped phase array //passed to this function //TODO: replace by (cython?) function to directly write into numpy array ? returnImage(pixel, UnwrappedImage, image_width, image_height); free(edge); free(pixel); free(extended_mask); }