def test_paths(self): """ Test that if the path of the directory where the file will be saved does not exist the system will raise an exception """ Fiber_Density_Calculation.fiber_density_and_distribution(3, 12) dimensional_measurements = [1, 2, 3, 4, 5, 6, 7, 8] Data_Management_Module.set_dimensional_measurements( dimensional_measurements) diameters = [] for i in range(12): diameters.append([1, 2]) Data_Management_Module.set_diameters(diameters) path = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folde' units = 'cm' name = "Test" self.assertRaises(Exception, Data_Management_Module.save_fiber_density_csv, name, path) self.assertRaises( Exception, Data_Management_Module.save_dimensional_measurements_csv, name, path, units) self.assertRaises(Exception, Data_Management_Module.save_graph_fiber_vs_wedges, name, path) self.assertRaises(Exception, Data_Management_Module.save_graph_fiber_vs_rings, name, path) self.assertRaises(Exception, Data_Management_Module.save_graphs, name, path) self.assertRaises(Exception, Data_Management_Module.save_diameter_csv, path, units)
def progress_change(self): """ Signals the user interface when process is completed or wen error occurs. :return: None """ if self.progressBar.value() == 2: self.stop_button_func() self.warning_message_box(str(self.error_message)) elif self.progressBar.value() == 100: self.densities = DM.get_fiber_density_average() self.measurement_data = DM.get_dimensional_measurements() self.stop_button_func()
def fiber_density_and_distribution(number_rings, number_wedges, dictionary): """ Main Fiber Density Module function to be called by the UI controller. This function will generate the fiber density list, with the averages, and send them to the Data Management Module. :param number_rings: the number of rings, specified by the user, for the sample cross-section analysis. :param number_wedges: the number of wedges, specified by the user, for the sample cross-section analysis. :param dictionary: dictionary containing raw image data :return: None """ fiber_density_list = fiber_density_calculation(number_rings, number_wedges, dictionary) Data_Management_Module.set_fiber_density(fiber_density_list) fiber_density_with_average = fiber_density_averages(fiber_density_list) Data_Management_Module.set_fiber_density_average( fiber_density_with_average)
def test_units(self): """ Test that if units provided are not supported system will throw an exception """ Fiber_Density_Calculation.fiber_density_and_distribution(3, 12) dimensional_measurements = [1, 2, 3, 4, 5, 6, 7, 8] Data_Management_Module.set_dimensional_measurements( dimensional_measurements) diameters = [] for i in range(12): diameters.append([1, 2]) Data_Management_Module.set_diameters(diameters) path = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder' units = 'm' name = "Test" self.assertRaises( Exception, Data_Management_Module.save_dimensional_measurements_csv, name, path, units) self.assertRaises(Exception, Data_Management_Module.save_diameter_csv, path, units)
def test_sample_1(self): parameters = { 'img_path': 'C:/Users/jeano/PycharmProjects/ICOM5047/Images/R_1.1.1.jpg', 'intermediate_path': "Run2", 'num_measurement': 400, 'num_wedges': 400, 'units': 'in', 'num_rings': 25, 'img_dpi': 1800, 'enhance': 0 } controller = SproutController(parameters) controller.run() density = Data_Management_Module.get_fiber_density_average()[-1][-1] print(density) measurements = Data_Management_Module.get_dimensional_measurements() print(measurements) self.assertLessEqual(calculate_error(measurements[0], 3.902), 3.5, "Area calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[1], 2.955), 3.5, "Outer diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[2], 1.956), 3.5, "Inner diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[3], 1.5011), 3.5, "Centroid X axis has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[4], 1.4572), 3.5, "Centroid Y axis has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[5], 2.3856), 3.5, "X-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[6], 2.2770), 3.5, "Y-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[7], 0.13438), 3.5, "Product of inertia calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(density, 0.45573), 3.5, "Fiber density has more than 3.5% of error")
def test_sample_0(self): parameters = { 'img_path': 'C:/Users/jeano/PycharmProjects/ICOM5047/Images/R_0.0.0.jpg', 'intermediate_path': "Run1", 'num_measurement': 12, 'num_wedges': 24, 'units': 'cm', 'num_rings': 3, 'img_dpi': 1800, 'enhance': 0 } controller = SproutController(parameters) controller.run() density = Data_Management_Module.get_fiber_density_average()[-1][-1] print(density) measurements = Data_Management_Module.get_dimensional_measurements() print(measurements) self.assertLessEqual(calculate_error(measurements[0], 16.2273), 3.5, "Area calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[1], 6.6696), 3.5, "Outer diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[2], 4.8683), 3.5, "Inner diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[3], 3.2406), 3.5, "Centroid X axis has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[4], 3.4100), 3.5, "Centroid Y axis has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[5], 25.9764), 3.5, "X-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[6], 34.5381), 3.5, "Y-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[7], -1.2249), 3.5, "Product of inertia calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(density, 0.56394), 3.5, "Fiber density has more than 3.5% of error")
def test_graph_files_saved_correctly(self): """ Test that graph images are being stored with correct name, correct file type and in the correct directory """ Fiber_Density_Calculation.fiber_density_and_distribution(3, 12) path = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder' Data_Management_Module.save_graphs('Graph', path) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Graph_RingGraph.jpg' expected_output1 = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Graph_WedgeGraph.jpg' self.assertTrue(os.path.exists(expected_output), "Graph was not properly saved") self.assertTrue(os.path.exists(expected_output1), "Graph was not properly saved") Data_Management_Module.save_graphs('Graph', path) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Graph1_RingGraph.jpg' expected_output1 = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Graph1_WedgeGraph.jpg' self.assertTrue(os.path.exists(expected_output), "Graph was not properly saved") self.assertTrue(os.path.exists(expected_output1), "Graph was not properly saved")
def test_sample_5(self): parameters = { 'img_path': '../../Images/1200dpi.jpg', 'intermediate_path': "Run5", 'num_measurement': 12, 'num_wedges': 12, 'units': 'mm', 'num_rings': 3, 'img_dpi': 1200, 'enhance': 1 } controller = SproutController(parameters) controller.run() density = Data_Management_Module.get_fiber_density_average()[-1][-1] print(density) measurements = Data_Management_Module.get_dimensional_measurements() self.assertLessEqual(calculate_error(measurements[0], 1084.53), 3.5, "Area calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[1], 63.172), 3.5, "Outer diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[2], 51.043), 3.5, "Inner diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[3], 31.533), 3.5, "Centroid X axis has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[4], 31.467), 3.5, "Centroid Y axis has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[5], 317354), 3.5, "X-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[6], 344873), 3.5, "Y-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[7], 7478.9), 3.5, "Product of inertia calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(density, 0.48649), 3.5, "Fiber density calculation has more than 3.5% of error")
def test_sample_4(self): parameters = { 'img_path': '../../Images/1200dpi.jpg', 'intermediate_path': "Run5", 'num_measurement': 12, 'num_wedges': 12, 'units': 'cm', 'num_rings': 3, 'img_dpi': 1200, 'enhance': 0 } controller = SproutController(parameters) controller.run() density = Data_Management_Module.get_fiber_density_average()[-1][-1] print(density) measurements = Data_Management_Module.get_dimensional_measurements() self.assertLessEqual(calculate_error(measurements[0], 10.7992), 3.5, "Area calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[1], 6.3140), 3.5, "Outer diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[2], 5.1075), 3.5, "Inner diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[3], 3.1784), 3.5, "Centroid X axis has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[4], 3.1662), 3.5, "Centroid Y axis has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[5], 27.3069), 3.5, "X-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[6], 29.2833), 3.5, "Y-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[7], 0.56596), 3.5, "Product of inertia calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(density, 0.52648), 3.5, "Fiber density calculation has more than 3.5% of error")
def test_sample_3(self): parameters = { 'img_path': 'C:/Users/jeano/PycharmProjects/ICOM5047/Images/Sample2.tif', 'intermediate_path': "Run4", 'num_measurement': 12, 'num_wedges': 12, 'units': 'cm', 'num_rings': 3, 'img_dpi': 4800, 'enhance': 0 } controller = SproutController(parameters) controller.run() density = Data_Management_Module.get_fiber_density_average()[-1][-1] print(density) measurements = Data_Management_Module.get_dimensional_measurements() self.assertLessEqual(calculate_error(measurements[0], 10.8029), 3.5, "Area calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[1], 6.3156), 3.5, "Outer diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[2], 5.1197), 3.5, "Inner diam. has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[3], 3.1696), 3.5, "Centroid X axis has more than 3.5% of error") self.assertLessEqual(calculate_error(measurements[4], 3.1571), 3.5, "Centroid Y axis has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[5], 30.6929), 3.5, "X-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[6], 33.1669), 3.5, "Y-axis moment calculation has more than 3.5% of error") self.assertLessEqual( calculate_error(measurements[7], 0.64975), 3.5, "Product of inertia calculation has more than 3.5% of error") self.assertLessEqual(calculate_error(density, 0.52648), 3.5, "Fiber density has more than 3.5% of error")
def save_graph_data(self): """ Starts the process of saving the graphs and/or data calling the respective function in the data management module. :return: None """ if not (self.checkBox_graphs.isChecked() or self.checkBox_data.isChecked()): QMessageBox.critical(self, "Warning!", "Please make sure to have at least \n" " one checkbox selected.") elif self.lineEdit_fileName.text().strip() is "": QMessageBox.critical(self, "Warning!", "Please make sure to provide a valid file name. Do not \n" " leave blank and do not use special character (<, >, :, \n" " /, , |, ?, *, \"). ") elif self.lineEdit_filePath.text() is "": QMessageBox.critical(self, "Warning!", "Please make sure to provide a folder path. ") else: save_folder_file_path = self.lineEdit_filePath.text() save_file_name = self.lineEdit_fileName.text().strip() special_characters = ['<', '>', ':', '/', '\\', '|', '?', '*', '"'] for c in special_characters: if c in save_file_name: QMessageBox.critical(self, "Warning!", "Please make sure to provide a valid file name. Do not \n" " leave blank and do not use special character (<, >, :, \n" " /, , |, ?, *, \"). ") return try: if self.checkBox_graphs.isChecked(): DM.save_graphs(save_file_name, save_folder_file_path) if self.checkBox_data.isChecked(): DM.save_fiber_density_csv(save_file_name, save_folder_file_path) DM.save_dimensional_measurements_csv(save_file_name, save_folder_file_path, in_data['units']) if in_data['pixelMap'] is True: DM.save_pixel_table(save_file_name, save_folder_file_path) # Close save window self.close() # Open save location try: os.startfile(save_folder_file_path) except OSError: raise Exception("Unable to save \"File Path\" location.") except Exception as e: QMessageBox.critical(self, "Warning!", str(e))
def test_csv_files_saved_correctly(self): """ Test that csv files are being stored with correct name, correct file type and in the correct directory """ Fiber_Density_Calculation.fiber_density_and_distribution(3, 12) dimensional_measurements = [1, 2, 3, 4, 5, 6, 7, 8] Data_Management_Module.set_dimensional_measurements( dimensional_measurements) diameters = [] for i in range(12): diameters.append([1, 2]) Data_Management_Module.set_diameters(diameters) path = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder' units = 'cm' Data_Management_Module.save_fiber_density_csv('FDTest', path) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\FDTest_RegionFiberDensity.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved") Data_Management_Module.save_fiber_density_csv('FDTest', path) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\FDTest1_RegionFiberDensity.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved") Data_Management_Module.save_dimensional_measurements_csv( 'DTest', path, 'cm') expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\DTest_AdditionalData.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved") Data_Management_Module.save_dimensional_measurements_csv( 'DTest', path, 'cm') expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\DTest1_AdditionalData.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved") Data_Management_Module.save_diameter_csv(path, units) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Bamboo_Diameters.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved") Data_Management_Module.save_diameter_csv(path, units) expected_output = 'C:\\Users\\israe\\Desktop\\Capstone\\Test_Folder\\Bamboo_Diameters1.csv' self.assertTrue(os.path.exists(expected_output), "File was not properly saved")
def pre_process_image(num_of_measurements: int, image_dpi: int, units: str, image_path: str = None, enhanced_image: object = None, t: object = None, pixel_map: bool = False): """ Takes a bamboo image and binarizes it then bounds it and determines its area, inner and outer diameters, centroid coordinates, and moment of inertia with respect to the x and y axes :param t: Qt thread :param num_of_measurements: number of measurements to use for determining average inner and outer diameters :param image_dpi: DPI of the input image :param units: units used for displaying measurements (cm, in, or mm) :param image_path: path of the input image to be used :param enhanced_image: data of the input image (for when image enhancement is used) :return: bounded input image and bounded binarized input image """ def binarize_image(source_image: object, blur_intensity: int, save_images=False, kernel_type=0) -> object: """ Converts the input image into a binary image :param kernel_type: type of kernel for Gaussian Blur :param save_images: bolean value that determines if images will be saved or not :param blur_intensity: size of the kernel for blurring the image :param source_image: image to be binarized :return: binarized image data """ # Convert RGB image to grayscale gray_image = cv.cvtColor(source_image, cv.COLOR_BGR2GRAY) if save_images: cv.imwrite(path + '/grayscale_image.jpg', gray_image) # Use Gaussian Blur to remove noise from the image if blur_intensity is not 0: gray_image = cv.GaussianBlur(gray_image, (blur_intensity, blur_intensity), kernel_type) # Calculate the global threshold value and binarize the image ret, gray_image = cv.threshold(gray_image, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) if save_images: cv.imwrite(path + '/binarized_image.jpg', gray_image) return gray_image def find_largest_contours(contour_list: list) -> object: ''' Finds the two largest contours of the image, these are those of the inner and outer bamboo ring :param contour_list: list containing all of the contours of an input image :return: tuple containing the index of the largest and second largest contours ''' largest_area = 0 largest_contour = 100 second_largest = 0 second_largest_area = 0 # Find largest and second largest contours in image (rings of the bamboo) for i in range(len(contour_list)): contour_area = cv.contourArea(contour_list[i]) if contour_area > largest_area: largest_area = contour_area second_largest = largest_contour largest_contour = i elif contour_area > second_largest_area: second_largest_area = contour_area second_largest = i return largest_contour, second_largest def rotate_image(image, angle): """ Function to rotate the input image by a specified angle :param image: input image to rotate :param angle: number of degrees the image will be rotated by :return: rotated input image """ # determine center of the image (h, w) = image.shape[:2] (cX, cY) = (w // 2, h // 2) # get the rotation matrix (applying the negative of the # angle to rotate clockwise), then get the sine and cosine M = cv.getRotationMatrix2D((cX, cY), -angle, 1.0) cos = np.abs(M[0, 0]) sin = np.abs(M[0, 1]) # Calculate bounding dimensions of the image nW = int((h * sin) + (w * cos)) nH = int((h * cos) + (w * sin)) M[0, 2] += (nW / 2) - cX M[1, 2] += (nH / 2) - cY return cv.warpAffine(image, M, (nW, nH)) def unit_converter(): """ Lambda function to convert values to corresponding units :return: lambda function """ return lambda z: z / image_dpi * units_multiplier def image_contours(binary_source_image: object, t: object): """ Find the contours of the bamboo, bound the image and make all dimensional measurements :param binary_source_image: binarized input image :return: bounded image, bounded binarized image, area, outer diameter, inner diameter, centroid, x moment, y moment, inner diameter measurements, and outer diameter lists """ # Erode and dilate image to eroded = cv.dilate(binary_source_image, None, iterations=7) eroded = cv.erode(eroded, None, iterations=7) # Find contours of the image and select those belonging to the bamboo contours, hierarchy = cv.findContours(eroded, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE) largest_contour_index, second_largest_index = find_largest_contours( contours) # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) # Draw bamboo contours on the image and save the new image contoured = img.copy() cv.drawContours(contoured, contours[largest_contour_index], -1, (0, 255, 0), 7) cv.drawContours(contoured, contours[second_largest_index], -1, (0, 255, 0), 7) cv.imwrite(path + '/contour_image.jpg', contoured) # Find the bounding box of the largest contour and crop both the input and binarized image x, y, w, h = cv.boundingRect(contours[largest_contour_index]) bounded_image = img[y:y + h, x:x + w] binarized_bounded_image = binary_source_image[y:y + h, x:x + w] cv.imwrite(path + '/bounded_image.jpg', bounded_image) cv.imwrite(path + '/binarized_bounded_image.jpg', binarized_bounded_image) # Dilate and erode the bounded image to fill the fibers. Then save the image num_of_iterations = int((30 / 3600) * image_dpi) + 1 eroded = binarized_bounded_image eroded = cv.dilate(eroded, None, iterations=num_of_iterations) eroded = cv.erode(eroded, None, iterations=num_of_iterations + 1) bounded_filled_image = eroded cv.imwrite(path + '/filled_image.jpg', bounded_filled_image) # Find the bounding box of the inner bamboo ring and store the height and width x2, y2, w2, h2 = cv.boundingRect(contours[second_largest_index]) outer_diameter_measurements = [w, h] inner_diameter_measurements = [w2, h2] bamboo_thickness = [(w - w2) / 2, (h - h2) / 2] pre_rotated_image = bounded_filled_image.copy() # Calculate the degrees between each measurement radius_steps = int(360 / ((num_of_measurements - 2) / 2)) # Make multiple inner and outer diameter measurements for angle in range(radius_steps, int(radius_steps * (num_of_measurements / 2)), radius_steps): # Rotate image rotated_image = rotate_image(pre_rotated_image, angle) # Find contours of rotated image and find inner and outer ring new_contours, new_hierarchy = cv.findContours( rotated_image, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE) largest_contour_index, second_largest_index = find_largest_contours( new_contours) # Find bounding box of both rings, take height and width as new measurements x2, y2, w2, h2 = cv.boundingRect( new_contours[largest_contour_index]) outer_diameter_measurements.append(w2) outer_diameter_measurements.append(h2) x2, y2, w2, h2 = cv.boundingRect( new_contours[second_largest_index]) inner_diameter_measurements.append(w2) inner_diameter_measurements.append(h2) # Calculate thickness of each measurement bamboo_thickness.append((outer_diameter_measurements[-1] - inner_diameter_measurements[-1]) / 2) bamboo_thickness.append((outer_diameter_measurements[-2] - inner_diameter_measurements[-2]) / 2) # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) # Convert the radial measurements to user input units outer_diameter_measurements = list( map(unit_converter(), outer_diameter_measurements)) inner_diameter_measurements = list( map(unit_converter(), inner_diameter_measurements)) bamboo_thickness = list(map(unit_converter(), bamboo_thickness)) # Calculate the average inner and outer diameters meas_outer_diameter = sum(outer_diameter_measurements) / len( outer_diameter_measurements) meas_inner_diameter = sum(inner_diameter_measurements) / len( inner_diameter_measurements) meas_thickness = sum(bamboo_thickness) / len(bamboo_thickness) # Find the statistics of the filled ring and convert the area to specified units output = cv.connectedComponentsWithStats(bounded_filled_image, 4, cv.CV_32S) meas_area = (output[2][1][4] / (image_dpi**2)) * (units_multiplier**2) # Find the coordinates of the centroid and convert them to the specified units new_binarized_bounded_image = binarize_image(img, 0)[y:y + h, x:x + w] meas_centroid = [0, 0] new_binarized_bounded_image = new_binarized_bounded_image & bounded_filled_image M = cv.moments(new_binarized_bounded_image, binaryImage=True) centroid_coordinates = (M['m10'] / M['m00'], M['m01'] / M['m00']) meas_centroid[ 0] = centroid_coordinates[0] * units_multiplier / image_dpi meas_centroid[ 1] = centroid_coordinates[1] * units_multiplier / image_dpi # Create a pixel map that has the coordinate of every pixel, distance to centroid and if its a fiber if pixel_map: pixel_table = [] for i in range(new_binarized_bounded_image.shape[0]): for j in range(new_binarized_bounded_image.shape[1]): if bounded_filled_image[i][j] == 255: pixel_table.append([ i, j, abs(i - int(centroid_coordinates[0])), abs(j - int(centroid_coordinates[1])), 0 if new_binarized_bounded_image[i, j] == 255 else 1 ]) Data_Management_Module.set_pixel_table(pixel_table) pixel_table = None # Draw the centroid on the image and save it cv.circle(bounded_image, (int(centroid_coordinates[0]), int(centroid_coordinates[1])), 10, (0, 255, 255), 10) cv.imwrite(path + "/centroid.jpg", bounded_image) # Calculate the moments of the image and obtain the second moments to get the moments of inertia x_moment = (M['m20'] - centroid_coordinates[0] * M['m01']) * ( (units_multiplier / image_dpi)**4) * 0.995 y_moment = (M['m02'] - centroid_coordinates[1] * M['m01']) * ( (units_multiplier / image_dpi)**4) * 1.001 moment_product = (M['m11'] - centroid_coordinates[0] * M['m01']) * ( (units_multiplier / image_dpi)**4) return bounded_image, binarized_bounded_image, [meas_area, meas_outer_diameter, meas_inner_diameter, meas_thickness, meas_centroid[0], meas_centroid[1], x_moment, y_moment, moment_product], \ [outer_diameter_measurements, inner_diameter_measurements, bamboo_thickness] # Validate inputs assert image_path is not None, "Image path must be given as input." assert type(num_of_measurements ) is int, "Dimensional measurements number is not an integer." assert type(image_dpi) is int, "Image DPI is not an integer." assert type(units) is str, "Units of measurement is not a string." assert 400 >= num_of_measurements >= 12, "Number of dimensional measurements is not in allowed range." assert 4800 >= image_dpi >= 1200, "Image DPI should be between 1200 and 4800." assert units in ( 'cm', 'in', 'mm' ), "Supported units are only inches(in), centimeters(cm), and milimeters(mm)" assert os.path.exists(image_path), "Input image was not found." assert image_path[-4:] in ('.bmp', '.jpg', 'jpeg', '.tif'), "Image format is not supported." path = "Pre_Processing" global TESTING # Create folder for images if it does not exist if not os.path.exists(path): try: os.makedirs(path) except: raise Exception("Unable to create Pre-Processing sub-directory") # Dictionary to store the conversion value of each unit of measurement units_dict = {'cm': 2.54, 'mm': 25.4, 'in': 1} units_multiplier = units_dict[units] # Check if input is the image path or the image data img = None if enhanced_image is None: try: img = cv.imread(image_path) except Exception: raise Exception("Unable to open input image") else: img = enhanced_image # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return # Binarize the input image try: binarized_image = binarize_image(img, 5, True) # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) except: raise Exception("Unable to binarize input image.") # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return try: # Find contours of the image and make dimensional measurements image, binarized_image, measurements_list, diameters_list = image_contours( binarized_image, t) Data_Management_Module.set_dimensional_measurements(measurements_list) Data_Management_Module.set_diameters(diameters_list) binarized_image = binarize_image(image.copy(), 0, kernel_type=cv.BORDER_ISOLATED) # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) except Exception: raise Exception( "Unable to calculate dimensional measurements of bamboo") # If user is testing module, send all the parameters if TESTING: return binarized_image, image, measurements_list[0], measurements_list[1], measurements_list[2], \ measurements_list[3], measurements_list[4], measurements_list[5], measurements_list[6], \ measurements_list[7], measurements_list[8], diameters_list[0], diameters_list[1] else: return binarized_image, image
def image_contours(binary_source_image: object, t: object): """ Find the contours of the bamboo, bound the image and make all dimensional measurements :param binary_source_image: binarized input image :return: bounded image, bounded binarized image, area, outer diameter, inner diameter, centroid, x moment, y moment, inner diameter measurements, and outer diameter lists """ # Erode and dilate image to eroded = cv.dilate(binary_source_image, None, iterations=7) eroded = cv.erode(eroded, None, iterations=7) # Find contours of the image and select those belonging to the bamboo contours, hierarchy = cv.findContours(eroded, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE) largest_contour_index, second_largest_index = find_largest_contours( contours) # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) # Draw bamboo contours on the image and save the new image contoured = img.copy() cv.drawContours(contoured, contours[largest_contour_index], -1, (0, 255, 0), 7) cv.drawContours(contoured, contours[second_largest_index], -1, (0, 255, 0), 7) cv.imwrite(path + '/contour_image.jpg', contoured) # Find the bounding box of the largest contour and crop both the input and binarized image x, y, w, h = cv.boundingRect(contours[largest_contour_index]) bounded_image = img[y:y + h, x:x + w] binarized_bounded_image = binary_source_image[y:y + h, x:x + w] cv.imwrite(path + '/bounded_image.jpg', bounded_image) cv.imwrite(path + '/binarized_bounded_image.jpg', binarized_bounded_image) # Dilate and erode the bounded image to fill the fibers. Then save the image num_of_iterations = int((30 / 3600) * image_dpi) + 1 eroded = binarized_bounded_image eroded = cv.dilate(eroded, None, iterations=num_of_iterations) eroded = cv.erode(eroded, None, iterations=num_of_iterations + 1) bounded_filled_image = eroded cv.imwrite(path + '/filled_image.jpg', bounded_filled_image) # Find the bounding box of the inner bamboo ring and store the height and width x2, y2, w2, h2 = cv.boundingRect(contours[second_largest_index]) outer_diameter_measurements = [w, h] inner_diameter_measurements = [w2, h2] bamboo_thickness = [(w - w2) / 2, (h - h2) / 2] pre_rotated_image = bounded_filled_image.copy() # Calculate the degrees between each measurement radius_steps = int(360 / ((num_of_measurements - 2) / 2)) # Make multiple inner and outer diameter measurements for angle in range(radius_steps, int(radius_steps * (num_of_measurements / 2)), radius_steps): # Rotate image rotated_image = rotate_image(pre_rotated_image, angle) # Find contours of rotated image and find inner and outer ring new_contours, new_hierarchy = cv.findContours( rotated_image, cv.RETR_CCOMP, cv.CHAIN_APPROX_NONE) largest_contour_index, second_largest_index = find_largest_contours( new_contours) # Find bounding box of both rings, take height and width as new measurements x2, y2, w2, h2 = cv.boundingRect( new_contours[largest_contour_index]) outer_diameter_measurements.append(w2) outer_diameter_measurements.append(h2) x2, y2, w2, h2 = cv.boundingRect( new_contours[second_largest_index]) inner_diameter_measurements.append(w2) inner_diameter_measurements.append(h2) # Calculate thickness of each measurement bamboo_thickness.append((outer_diameter_measurements[-1] - inner_diameter_measurements[-1]) / 2) bamboo_thickness.append((outer_diameter_measurements[-2] - inner_diameter_measurements[-2]) / 2) # Check if has an interrupt request (Stop Button Interrupt) if t is not None and t.isInterruptionRequested(): return # Update the progress bar if t is not None: t.update_module_progress(t.p_img_pre_processing, 25) # Convert the radial measurements to user input units outer_diameter_measurements = list( map(unit_converter(), outer_diameter_measurements)) inner_diameter_measurements = list( map(unit_converter(), inner_diameter_measurements)) bamboo_thickness = list(map(unit_converter(), bamboo_thickness)) # Calculate the average inner and outer diameters meas_outer_diameter = sum(outer_diameter_measurements) / len( outer_diameter_measurements) meas_inner_diameter = sum(inner_diameter_measurements) / len( inner_diameter_measurements) meas_thickness = sum(bamboo_thickness) / len(bamboo_thickness) # Find the statistics of the filled ring and convert the area to specified units output = cv.connectedComponentsWithStats(bounded_filled_image, 4, cv.CV_32S) meas_area = (output[2][1][4] / (image_dpi**2)) * (units_multiplier**2) # Find the coordinates of the centroid and convert them to the specified units new_binarized_bounded_image = binarize_image(img, 0)[y:y + h, x:x + w] meas_centroid = [0, 0] new_binarized_bounded_image = new_binarized_bounded_image & bounded_filled_image M = cv.moments(new_binarized_bounded_image, binaryImage=True) centroid_coordinates = (M['m10'] / M['m00'], M['m01'] / M['m00']) meas_centroid[ 0] = centroid_coordinates[0] * units_multiplier / image_dpi meas_centroid[ 1] = centroid_coordinates[1] * units_multiplier / image_dpi # Create a pixel map that has the coordinate of every pixel, distance to centroid and if its a fiber if pixel_map: pixel_table = [] for i in range(new_binarized_bounded_image.shape[0]): for j in range(new_binarized_bounded_image.shape[1]): if bounded_filled_image[i][j] == 255: pixel_table.append([ i, j, abs(i - int(centroid_coordinates[0])), abs(j - int(centroid_coordinates[1])), 0 if new_binarized_bounded_image[i, j] == 255 else 1 ]) Data_Management_Module.set_pixel_table(pixel_table) pixel_table = None # Draw the centroid on the image and save it cv.circle(bounded_image, (int(centroid_coordinates[0]), int(centroid_coordinates[1])), 10, (0, 255, 255), 10) cv.imwrite(path + "/centroid.jpg", bounded_image) # Calculate the moments of the image and obtain the second moments to get the moments of inertia x_moment = (M['m20'] - centroid_coordinates[0] * M['m01']) * ( (units_multiplier / image_dpi)**4) * 0.995 y_moment = (M['m02'] - centroid_coordinates[1] * M['m01']) * ( (units_multiplier / image_dpi)**4) * 1.001 moment_product = (M['m11'] - centroid_coordinates[0] * M['m01']) * ( (units_multiplier / image_dpi)**4) return bounded_image, binarized_bounded_image, [meas_area, meas_outer_diameter, meas_inner_diameter, meas_thickness, meas_centroid[0], meas_centroid[1], x_moment, y_moment, moment_product], \ [outer_diameter_measurements, inner_diameter_measurements, bamboo_thickness]