def catphan_helperf_analyze(args): phantom = args["phantom"] general_functions.set_configuration(args["config"]) # Transfer to this process hu_tolerance = args["hu_tolerance"] scaling_tolerance = args["scaling_tolerance"] thickness_tolerance = args["thickness_tolerance"] low_contrast_tolerance = args["low_contrast_tolerance"] cnr_threshold = args["cnr_threshold"] path = args["path"] try: if phantom == "Catphan 503": mycbct_temp = pylinac_ct.CatPhan503(path) elif phantom == "Catphan 504": mycbct_temp = pylinac_ct.CatPhan504(path) elif phantom == "Catphan 600": mycbct_temp = pylinac_ct.CatPhan600(path) elif phantom == "Catphan 604": mycbct_temp = pylinac_ct.CatPhan604(path) mycbct_temp.analyze(hu_tolerance=hu_tolerance, scaling_tolerance=scaling_tolerance, thickness_tolerance=thickness_tolerance, low_contrast_tolerance=low_contrast_tolerance, cnr_threshold=cnr_threshold) except Exception as e: return e else: return mycbct_temp
def fieldrot_helperf(args): test_type = args["test_type"] direction = args["direction"] direction2 = args["direction2"] number_samples = args["number_samples"] margin = args["margin"] clipbox = args["clipbox"] invert = args["invert"] w1 = args["w1"] w2 = args["w2"] colormap = args["colormap"] med_filter = args["med_filter"] general_functions.set_configuration(args["config"]) imgdescription = args["imgdescription"] station = args["station"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] high_contrast = args["high_contrast"] # Collect data for "save results" dicomenergy = general_functions.get_energy_from_imgdescription( imgdescription) user_machine, user_energy = general_functions.get_user_machine_and_energy( station, dicomenergy) machines_and_energies = general_functions.get_machines_and_energies( general_functions.get_treatmentunits_fieldrotation()) tolerances = general_functions.get_tolerance_user_machine_fieldrotation( user_machine) # If user_machne has specific tolerance if not tolerances: tt = general_functions.get_settings_fieldrotation() else: tt = tolerances[0] (tolerance_collabs, tolerance_collrel, tolerance_couchrel) = tt tolerance_collabs = float(tolerance_collabs) tolerance_collrel = float(tolerance_collrel) tolerance_couchrel = float(tolerance_couchrel) save_results = { "user_machine": user_machine, "user_energy": user_energy, "machines_and_energies": machines_and_energies, "displayname": displayname, "nominal_angle": np.linspace(360, -360, 49).tolist() } try: temp_folder1, file_path1 = RestToolbox.GetSingleDcm( config.ORTHANC_URL, w1) temp_folder2, file_path2 = RestToolbox.GetSingleDcm( config.ORTHANC_URL, w2) except: return template("error_template", {"error_message": "Cannot read images."}) # Load first image try: img1 = pylinac_image.DicomImage(file_path1) # Here we force pixels to background outside of box: if clipbox != 0: try: img1.check_inversion_by_histogram(percentiles=[ 4, 50, 96 ]) # Check inversion otherwise this might not work general_functions.clip_around_image(img1, clipbox) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) else: img1.remove_edges(pixels=2) if invert: img1.invert() else: img1.check_inversion() img1.flipud() except: return template("error_template", {"error_message": "Cannot read image."}) try: img2 = pylinac_image.DicomImage(file_path2) if clipbox != 0: try: img2.check_inversion_by_histogram(percentiles=[ 4, 50, 96 ]) # Check inversion otherwise this might not work general_functions.clip_around_image(img2, clipbox) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) else: img2.remove_edges(pixels=2) if invert: img2.invert() else: img2.check_inversion() img2.flipud() except: return template("error_template", {"error_message": "Cannot read image."}) # Apply some filtering if med_filter > 0: img1.filter(med_filter) img2.filter(med_filter) # Get radiation field box: try: center_cax1, rad_field_bounding_box1, field_corners1 = field_rotation._find_field_centroid( img1) center_cax2, rad_field_bounding_box2, field_corners2 = field_rotation._find_field_centroid( img2) except Exception as e: return template("error_template", {"error_message": str(e)}) field_corners1 = field_corners1.astype(int) field_corners2 = field_corners2.astype(int) # Set colormap cmap = matplotlib.cm.get_cmap(colormap) if test_type == "Collimator absolute": # Get BBs try: if high_contrast: bbs1 = field_rotation._find_bb2(img1, rad_field_bounding_box1) bbs2 = field_rotation._find_bb2(img2, rad_field_bounding_box2) else: bbs1 = field_rotation._find_bb(img1, rad_field_bounding_box1) bbs2 = field_rotation._find_bb(img2, rad_field_bounding_box2) except Exception as e: return template("error_template", {"error_message": str(e)}) bb_coord1_1, bw_bb_im1_1 = bbs1[0] bb_coord1_2, bw_bb_im1_2 = bbs1[1] bb_coord2_1, bw_bb_im2_1 = bbs2[0] bb_coord2_2, bw_bb_im2_2 = bbs2[1] center1_1 = (bb_coord1_1[0], bb_coord1_1[1]) center1_2 = (bb_coord1_2[0], bb_coord1_2[1]) center2_1 = (bb_coord2_1[0], bb_coord2_1[1]) center2_2 = (bb_coord2_2[0], bb_coord2_2[1]) # Line between BBs: bb_angle1 = np.arctan(np.inf if bb_coord1_1[0] - bb_coord1_2[0] == 0 else (bb_coord1_1[1] - bb_coord1_2[1]) / (bb_coord1_1[0] - bb_coord1_2[0])) * 180 / np.pi bb_angle2 = np.arctan(np.inf if bb_coord2_1[0] - bb_coord2_2[0] == 0 else (bb_coord2_1[1] - bb_coord2_2[1]) / (bb_coord2_1[0] - bb_coord2_2[0])) * 180 / np.pi img1_filled = np.copy(img1.array) img2_filled = np.copy(img2.array) # Fill BBs area with neighbouring values field_rotation.fill_BB_hole(bb_coord1_1, bw_bb_im1_1, img1_filled) field_rotation.fill_BB_hole(bb_coord1_2, bw_bb_im1_2, img1_filled) field_rotation.fill_BB_hole(bb_coord2_1, bw_bb_im2_1, img2_filled) field_rotation.fill_BB_hole(bb_coord2_2, bw_bb_im2_2, img2_filled) # Get penumbra points try: samples_left1, samples_right1, p_left1, p_right1 = field_rotation.find_penumbra_points( direction, number_samples, field_corners1, margin, img1_filled) samples_left2, samples_right2, p_left2, p_right2 = field_rotation.find_penumbra_points( direction2, number_samples, field_corners2, margin, img2_filled) except Exception as e: return template("error_template", {"error_message": str(e)}) # Calculate field edge slopes pmin = 0 pmax = np.max( img1.shape) # Maksimum point for drawing regression lines if direction == "X": left_slope1, left_P1, left_err1 = field_rotation.calculate_regression( p_left1, samples_left1, pmin, pmax) right_slope1, right_P1, right_err1 = field_rotation.calculate_regression( p_right1, samples_right1, pmin, pmax) else: left_slope1, left_P1, left_err1 = field_rotation.calculate_regression( samples_left1, p_left1, pmin, pmax) right_slope1, right_P1, right_err1 = field_rotation.calculate_regression( samples_right1, p_right1, pmin, pmax) if direction2 == "X": left_slope2, left_P2, left_err2 = field_rotation.calculate_regression( p_left2, samples_left2, pmin, pmax) right_slope2, right_P2, right_err2 = field_rotation.calculate_regression( p_right2, samples_right2, pmin, pmax) else: left_slope2, left_P2, left_err2 = field_rotation.calculate_regression( samples_left2, p_left2, pmin, pmax) right_slope2, right_P2, right_err2 = field_rotation.calculate_regression( samples_right2, p_right2, pmin, pmax) left_edge_angle1 = np.arctan(left_slope1) * 180 / np.pi left_edge_angle2 = np.arctan(left_slope2) * 180 / np.pi right_edge_angle1 = np.arctan(right_slope1) * 180 / np.pi right_edge_angle2 = np.arctan(right_slope2) * 180 / np.pi # First plot: field and penumbra points fig1 = Figure(figsize=(10.5, 5.5), tight_layout={"w_pad": 3, "pad": 3}) ax1 = fig1.add_subplot(1, 2, 1) ax2 = fig1.add_subplot(1, 2, 2) # Plot error bars and goodness of fit fig2 = Figure(figsize=(10, 4), tight_layout={"w_pad": 0, "pad": 1}) ax3 = fig2.add_subplot(1, 2, 1) ax4 = fig2.add_subplot(1, 2, 2) # Plot angled lines fig3 = Figure(figsize=(10, 4), tight_layout={"w_pad": 0, "pad": 1}) ax5 = fig3.add_subplot(1, 2, 1) ax6 = fig3.add_subplot(1, 2, 2) ax1.imshow(img1.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax1.set_title('Image 1') ax1.axis('off') ax2.imshow(img2.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax2.set_title('Image 2') ax2.axis('off') #Plot field corners ax1.plot(field_corners1[:, 1], field_corners1[:, 0], "mo", markersize=5, markeredgewidth=0) ax2.plot(field_corners2[:, 1], field_corners2[:, 0], "mo", markersize=5, markeredgewidth=0) # Plot penumbra points if direction == "X": ax1.plot(p_left1, samples_left1, "bx", markersize=5, markeredgewidth=2) ax1.plot(p_right1, samples_right1, "yx", markersize=5, markeredgewidth=2) else: ax1.plot(samples_left1, p_left1, "bx", markersize=5, markeredgewidth=2) ax1.plot(samples_right1, p_right1, "yx", markersize=5, markeredgewidth=2) ax1.plot(left_P1[0], left_P1[1], "b--") ax1.plot(right_P1[0], right_P1[1], "y--") if direction2 == "X": ax2.plot(p_left2, samples_left2, "bx", markersize=5, markeredgewidth=2) ax2.plot(p_right2, samples_right2, "yx", markersize=5, markeredgewidth=2) else: ax2.plot(samples_left2, p_left2, "bx", markersize=5, markeredgewidth=2) ax2.plot(samples_right2, p_right2, "yx", markersize=5, markeredgewidth=2) ax2.plot(left_P2[0], left_P2[1], "b--") ax2.plot(right_P2[0], right_P2[1], "y--") # Plot errors: ax3.plot(samples_left1, left_err1, "bx", markeredgewidth=2) ax3.plot(samples_right1, right_err1, "yx", markeredgewidth=2) ax4.plot(samples_left2, left_err2, "bx", markeredgewidth=2) ax4.plot(samples_right2, right_err2, "yx", markeredgewidth=2) limits_max = np.amax([ np.amax(left_err1), np.amax(right_err1), np.amax(left_err2), np.amax(right_err2) ]) * 1.05 limits_min = np.amin([ np.amin(left_err1), np.amin(right_err1), np.amin(left_err2), np.amin(right_err2) ]) * 0.95 limits = np.amax([abs(limits_max), abs(limits_min)]) limits = limits if limits > 1 else 1 ax3.set_ylim([-limits, limits]) ax4.set_ylim([-limits, limits]) ax3.set_ylabel("Deviation from fit [px]") ax4.set_ylabel("Deviation from fit [px]") ax3.set_xlabel("Field edge [px]") ax4.set_xlabel("Field edge [px]") ax3.set_title('Image 1 - Regression error') ax4.set_title('Image 2 - Regression error') # Plot angled lines: # If angles are negative, convert to [pi, 2pi] if abs(bb_angle1) > 80 and abs(bb_angle1) <= 90: B1 = left_edge_angle1 if left_edge_angle1 >= 0 else 180 + left_edge_angle1 Y1 = right_edge_angle1 if right_edge_angle1 >= 0 else 180 + right_edge_angle1 BB1 = bb_angle1 if bb_angle1 >= 0 else 180 + bb_angle1 ref_angle_plot = PI / 2 # Reference angle for drawing (either 0 or 90) else: B1 = left_edge_angle1 Y1 = right_edge_angle1 BB1 = bb_angle1 ref_angle_plot = 0 if abs(bb_angle2) > 80 and abs(bb_angle2) <= 90: B2 = left_edge_angle2 if left_edge_angle2 >= 0 else 180 + left_edge_angle2 Y2 = right_edge_angle2 if right_edge_angle2 >= 0 else 180 + right_edge_angle2 BB2 = bb_angle2 if bb_angle2 >= 0 else 180 + bb_angle2 else: B2 = left_edge_angle2 Y2 = right_edge_angle2 BB2 = bb_angle2 a = 2 x_bb1 = [-a * np.cos(BB1 * PI / 180), a * np.cos(BB1 * PI / 180)] y_bb1 = [-a * np.sin(BB1 * PI / 180), a * np.sin(BB1 * PI / 180)] x_b1 = [-a * np.cos((B1) * PI / 180), a * np.cos((B1) * PI / 180)] y_b1 = [-a * np.sin((B1) * PI / 180), a * np.sin((B1) * PI / 180)] x_b2 = [ -a * np.cos((BB1 - (B2 - BB2)) * PI / 180), a * np.cos( (BB1 - (B2 - BB2)) * PI / 180) ] y_b2 = [ -a * np.sin((BB1 - (B2 - BB2)) * PI / 180), a * np.sin( (BB1 - (B2 - BB2)) * PI / 180) ] x_y1 = [-a * np.cos((Y1) * PI / 180), a * np.cos((Y1) * PI / 180)] y_y1 = [-a * np.sin((Y1) * PI / 180), a * np.sin((Y1) * PI / 180)] x_y2 = [ -a * np.cos((BB1 - (Y2 - BB2)) * PI / 180), a * np.cos( (BB1 - (Y2 - BB2)) * PI / 180) ] y_y2 = [ -a * np.sin((BB1 - (Y2 - BB2)) * PI / 180), a * np.sin( (BB1 - (Y2 - BB2)) * PI / 180) ] ax5.plot(x_bb1, y_bb1, "g-", label="BB") ax5.plot(x_b1, y_b1, "b-", label="Gantry 0") ax5.plot(x_b2, y_b2, "b--", label="Gantry 180") ax6.plot(x_bb1, y_bb1, "g-", label="BB") ax6.plot(x_y1, y_y1, "y-", label="Gantry 0") ax6.plot(x_y2, y_y2, "y--", label="Gantry 180") if ref_angle_plot == PI / 2: max_xb = np.amax([np.abs(x_b1), np.abs(x_b2)]) max_xy = np.amax([np.abs(x_y1), np.abs(x_y2)]) ax5.set_xlim([-2 * max_xb, 2 * max_xb]) ax5.set_ylim([-1, 1]) ax6.set_xlim([-2 * max_xy, 2 * max_xy]) ax6.set_ylim([-1, 1]) else: max_y = np.amax([np.abs(y_b1), np.abs(y_b2)]) max_yy = np.amax([np.abs(y_y1), np.abs(y_y2)]) ax5.set_ylim([-2 * max_y, 2 * max_y]) ax5.set_xlim([-1, 1]) ax6.set_ylim([-2 * max_yy, 2 * max_yy]) ax6.set_xlim([-1, 1]) ax5.legend(loc='upper right', fontsize=10, edgecolor="none") ax5.set_title("Blue edge") ax5.set_xlabel("LAT [px]") ax5.set_ylabel("LONG [px]") ax6.legend(loc='upper right', fontsize=10, edgecolor="none") ax6.set_title("Yellow edge") ax6.set_xlabel("LAT [px]") ax6.set_ylabel("LONG [px]") # Plot BB line and crosses ax1.plot([bb_coord1_1[0], bb_coord1_2[0]], [bb_coord1_1[1], bb_coord1_2[1]], "g-") ax2.plot([bb_coord2_1[0], bb_coord2_2[0]], [bb_coord2_1[1], bb_coord2_2[1]], "g-") ax1.plot(center1_1[0], center1_1[1], 'r+', markersize=10, markeredgewidth=2) ax1.plot(center1_2[0], center1_2[1], 'r+', markersize=10, markeredgewidth=2) ax2.plot(center2_1[0], center2_1[1], 'r+', markersize=10, markeredgewidth=2) ax2.plot(center2_2[0], center2_2[1], 'r+', markersize=10, markeredgewidth=2) ax1.set_xlim([0, img1.shape[1]]) ax1.set_ylim([0, img1.shape[0]]) ax2.set_xlim([0, img2.shape[1]]) ax2.set_ylim([0, img2.shape[0]]) script1 = mpld3.fig_to_html(fig1, d3_url=D3_URL, mpld3_url=MPLD3_URL) script2 = mpld3.fig_to_html(fig2, d3_url=D3_URL, mpld3_url=MPLD3_URL) script3 = mpld3.fig_to_html(fig3, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script1": script1, "script2": script2, "script3": script3, "left_edge_angle1": left_edge_angle1, "left_edge_angle2": left_edge_angle2, "right_edge_angle1": right_edge_angle1, "right_edge_angle2": right_edge_angle2, "bb_angle1": bb_angle1, "bb_angle2": bb_angle2, "test_type": test_type, "tolerance": tolerance_collabs } elif test_type == "Collimator relative": # Get penumbra points try: samples_left1, samples_right1, p_left1, p_right1 = field_rotation.find_penumbra_points( direction, number_samples, field_corners1, margin, img1.array) samples_left2, samples_right2, p_left2, p_right2 = field_rotation.find_penumbra_points( direction2, number_samples, field_corners2, margin, img2.array) except Exception as e: return template("error_template", {"error_message": str(e)}) # Calculate field edge slopes pmin = 0 pmax = np.max( img1.shape) # Maksimum point for drawing regression lines if direction == "X": left_slope1, left_P1, left_err1 = field_rotation.calculate_regression( p_left1, samples_left1, pmin, pmax) right_slope1, right_P1, right_err1 = field_rotation.calculate_regression( p_right1, samples_right1, pmin, pmax) else: left_slope1, left_P1, left_err1 = field_rotation.calculate_regression( samples_left1, p_left1, pmin, pmax) right_slope1, right_P1, right_err1 = field_rotation.calculate_regression( samples_right1, p_right1, pmin, pmax) if direction2 == "X": left_slope2, left_P2, left_err2 = field_rotation.calculate_regression( p_left2, samples_left2, pmin, pmax) right_slope2, right_P2, right_err2 = field_rotation.calculate_regression( p_right2, samples_right2, pmin, pmax) else: left_slope2, left_P2, left_err2 = field_rotation.calculate_regression( samples_left2, p_left2, pmin, pmax) right_slope2, right_P2, right_err2 = field_rotation.calculate_regression( samples_right2, p_right2, pmin, pmax) left_edge_angle1 = np.arctan(left_slope1) * 180 / np.pi left_edge_angle2 = np.arctan(left_slope2) * 180 / np.pi right_edge_angle1 = np.arctan(right_slope1) * 180 / np.pi right_edge_angle2 = np.arctan(right_slope2) * 180 / np.pi # First plot: field and penumbra points fig1 = Figure(figsize=(10.5, 5.5), tight_layout={"w_pad": 3, "pad": 3}) ax1 = fig1.add_subplot(1, 2, 1) ax2 = fig1.add_subplot(1, 2, 2) # Plot error bars and goodness of fit fig2 = Figure(figsize=(10, 4), tight_layout={"w_pad": 0, "pad": 1}) ax3 = fig2.add_subplot(1, 2, 1) ax4 = fig2.add_subplot(1, 2, 2) ax1.imshow(img1.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax1.set_title('Image 1') ax1.axis('off') ax2.imshow(img2.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax2.set_title('Image 2') ax2.axis('off') #Plot field corners ax1.plot(field_corners1[:, 1], field_corners1[:, 0], "mo", markersize=5, markeredgewidth=0) ax2.plot(field_corners2[:, 1], field_corners2[:, 0], "mo", markersize=5, markeredgewidth=0) # Plot penumbra points if direction == "X": ax1.plot(p_left1, samples_left1, "bx", markersize=5, markeredgewidth=2) ax1.plot(p_right1, samples_right1, "yx", markersize=5, markeredgewidth=2) else: ax1.plot(samples_left1, p_left1, "bx", markersize=5, markeredgewidth=2) ax1.plot(samples_right1, p_right1, "yx", markersize=5, markeredgewidth=2) ax1.plot(left_P1[0], left_P1[1], "b--") ax1.plot(right_P1[0], right_P1[1], "y--") if direction2 == "X": ax2.plot(p_left2, samples_left2, "bx", markersize=5, markeredgewidth=2) ax2.plot(p_right2, samples_right2, "yx", markersize=5, markeredgewidth=2) else: ax2.plot(samples_left2, p_left2, "bx", markersize=5, markeredgewidth=2) ax2.plot(samples_right2, p_right2, "yx", markersize=5, markeredgewidth=2) ax2.plot(left_P2[0], left_P2[1], "b--") ax2.plot(right_P2[0], right_P2[1], "y--") ax1.set_xlim([0, img1.shape[1]]) ax1.set_ylim([0, img1.shape[0]]) ax2.set_xlim([0, img2.shape[1]]) ax2.set_ylim([0, img2.shape[0]]) # Plot errors: ax3.plot(samples_left1, left_err1, "bx", markeredgewidth=2) ax3.plot(samples_right1, right_err1, "yx", markeredgewidth=2) ax4.plot(samples_left2, left_err2, "bx", markeredgewidth=2) ax4.plot(samples_right2, right_err2, "yx", markeredgewidth=2) limits_max = np.amax([ np.amax(left_err1), np.amax(right_err1), np.amax(left_err2), np.amax(right_err2) ]) * 1.05 limits_min = np.amin([ np.amin(left_err1), np.amin(right_err1), np.amin(left_err2), np.amin(right_err2) ]) * 0.95 limits = np.amax([abs(limits_max), abs(limits_min)]) limits = limits if limits > 1 else 1 ax3.set_ylim([-limits, limits]) ax4.set_ylim([-limits, limits]) ax3.set_ylabel("Deviation from fit [px]") ax4.set_ylabel("Deviation from fit [px]") ax3.set_xlabel("Field edge [px]") ax4.set_xlabel("Field edge [px]") ax3.set_title('Image 1 - Regression error') ax4.set_title('Image 2 - Regression error') script1 = mpld3.fig_to_html(fig1, d3_url=D3_URL, mpld3_url=MPLD3_URL) script2 = mpld3.fig_to_html(fig2, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script1": script1, "script2": script2, "script3": "", "left_edge_angle1": left_edge_angle1, "left_edge_angle2": left_edge_angle2, "right_edge_angle1": right_edge_angle1, "right_edge_angle2": right_edge_angle2, "test_type": test_type, "tolerance": tolerance_collrel } else: # Couch rotation # Get BBs try: if high_contrast: bbs1 = field_rotation._find_bb2(img1, rad_field_bounding_box1) bbs2 = field_rotation._find_bb2(img2, rad_field_bounding_box2) else: bbs1 = field_rotation._find_bb(img1, rad_field_bounding_box1) bbs2 = field_rotation._find_bb(img2, rad_field_bounding_box2) except Exception as e: return template("error_template", {"error_message": str(e)}) bb_coord1_1, bw_bb_im1_1 = bbs1[0] bb_coord1_2, bw_bb_im1_2 = bbs1[1] bb_coord2_1, bw_bb_im2_1 = bbs2[0] bb_coord2_2, bw_bb_im2_2 = bbs2[1] center1_1 = [bb_coord1_1[0], bb_coord1_1[1]] center1_2 = [bb_coord1_2[0], bb_coord1_2[1]] center2_1 = [bb_coord2_1[0], bb_coord2_1[1]] center2_2 = [bb_coord2_2[0], bb_coord2_2[1]] bb_line_center1 = [ np.average([center1_1[0], center1_2[0]]), np.average([center1_1[1], center1_2[1]]) ] bb_line_center2 = [ np.average([center2_1[0], center2_2[0]]), np.average([center2_1[1], center2_2[1]]) ] # Line between BBs: bb_angle1 = np.arctan(np.inf if bb_coord1_1[0] - bb_coord1_2[0] == 0 else (bb_coord1_1[1] - bb_coord1_2[1]) / (bb_coord1_1[0] - bb_coord1_2[0])) * 180 / np.pi bb_angle2 = np.arctan(np.inf if bb_coord2_1[0] - bb_coord2_2[0] == 0 else (bb_coord2_1[1] - bb_coord2_2[1]) / (bb_coord2_1[0] - bb_coord2_2[0])) * 180 / np.pi fig1 = Figure(figsize=(10.5, 5.5), tight_layout={"w_pad": 3, "pad": 3}) ax1 = fig1.add_subplot(1, 2, 1) ax2 = fig1.add_subplot(1, 2, 2) ax1.imshow(img1.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax1.set_title('Image 1') ax1.axis('off') ax2.imshow(img2.array, cmap=cmap, interpolation="none", aspect="equal", origin='lower') ax2.set_title('Image 2') ax2.axis('off') # Plot BB line and crosses ax1.plot([bb_coord1_1[0], bb_coord1_2[0]], [bb_coord1_1[1], bb_coord1_2[1]], "g-") ax2.plot([bb_coord2_1[0], bb_coord2_2[0]], [bb_coord2_1[1], bb_coord2_2[1]], "g-") ax1.plot(center1_1[0], center1_1[1], 'r+', markersize=10, markeredgewidth=2) ax1.plot(center1_2[0], center1_2[1], 'r+', markersize=10, markeredgewidth=2) ax2.plot(center2_1[0], center2_1[1], 'r+', markersize=10, markeredgewidth=2) ax2.plot(center2_2[0], center2_2[1], 'r+', markersize=10, markeredgewidth=2) ax1.set_xlim([0, img1.shape[1]]) ax1.set_ylim([0, img1.shape[0]]) ax2.set_xlim([0, img2.shape[1]]) ax2.set_ylim([0, img2.shape[0]]) script1 = mpld3.fig_to_html(fig1, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script1": script1, "script2": "", "script3": "", "bb_angle1": bb_angle1, "bb_angle2": bb_angle2, "bb_line_center1": bb_line_center1, "bb_line_center2": bb_line_center2, "test_type": test_type, "tolerance": tolerance_couchrel } variables["acquisition_datetime"] = acquisition_datetime variables["save_results"] = save_results general_functions.delete_files_in_subfolders([temp_folder1, temp_folder2 ]) # Delete image return template("fieldrot_results", variables)
def flatsym_helper(args): calc_definition = args["calc_definition"] center_definition = args["center_definition"] center_x = args["center_x"] center_y = args["center_y"] invert = args["invert"] w = args["instance"] station = args["station"] imgdescription = args["imgdescription"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] general_functions.set_configuration( args["config"]) # Transfer to this process # Collect data for "save results" dicomenergy = general_functions.get_energy_from_imgdescription( imgdescription) user_machine, user_energy = general_functions.get_user_machine_and_energy( station, dicomenergy) machines_and_energies = general_functions.get_machines_and_energies( general_functions.get_treatmentunits_flatsym()) tolerances = general_functions.get_tolerance_user_machine_flatsym( user_machine) # If user_machne has specific tolerance if not tolerances: tolerance_flat, tolerance_sym, pdf_report_enable = general_functions.get_settings_flatsym( ) else: tolerance_flat, tolerance_sym, pdf_report_enable = tolerances[0] tolerance_flat = float(tolerance_flat) tolerance_sym = float(tolerance_sym) save_results = { "user_machine": user_machine, "user_energy": user_energy, "machines_and_energies": machines_and_energies, "displayname": displayname } temp_folder, file_path = RestToolbox.GetSingleDcm(config.ORTHANC_URL, w) try: flatsym = FlatSym(file_path) except Exception as e: return template("error_template", { "error_message": "The FlatSym module cannot calculate. " + str(e) }) # Define the center pixel where to get profiles: def find_field_centroid(img): '''taken from pylinac WL module''' min, max = np.percentile(img.image.array, [5, 99.9]) # min, max = np.amin(img.array), np.max(img.array) threshold_img = img.image.as_binary((max - min) / 2 + min) # clean single-pixel noise from outside field coords = ndimage.measurements.center_of_mass(threshold_img) return (int(coords[-1]), int(coords[0])) flatsym.image.crop(pixels=2) flatsym.image.check_inversion() if invert: flatsym.image.invert() flatsym.image.array = np.flipud(flatsym.image.array) vmax = flatsym.image.array.max() if center_definition == "Automatic": center_int = [ int(flatsym.image.array.shape[1] / 2), int(flatsym.image.array.shape[0] / 2) ] center = [0.5, 0.5] elif center_definition == "CAX": center_int = find_field_centroid( flatsym) # Define as mechanical isocenter center = [ int(center_int[0]) / flatsym.image.array.shape[1], int(center_int[1]) / flatsym.image.array.shape[0] ] else: center_int = [int(center_x), int(center_y)] center = [ int(center_x) / flatsym.image.array.shape[1], int(center_y) / flatsym.image.array.shape[0] ] fig = Figure(figsize=(9, 9), tight_layout={"w_pad": 1}) ax = fig.add_subplot(1, 1, 1) ax.imshow(flatsym.image.array, cmap=matplotlib.cm.jet, interpolation="none", vmin=0.9 * vmax, vmax=vmax, aspect="equal", origin='lower') ax.autoscale(tight=True) # Plot lines along which the profiles are calculated: ax.plot([0, flatsym.image.array.shape[1]], [center_int[1], center_int[1]], "b-", linewidth=2) ax.plot([center_int[0], center_int[0]], [0, flatsym.image.array.shape[0]], c="darkgreen", linestyle="-", linewidth=2) ax.set_xlim([0, flatsym.image.array.shape[1]]) ax.set_ylim([0, flatsym.image.array.shape[0]]) # Get profiles crossplane = PylinacSingleProfile(flatsym.image.array[center_int[1], :]) inplane = PylinacSingleProfile(flatsym.image.array[:, center_int[0]]) # Do some filtering: crossplane.filter(kind='median', size=0.01) inplane.filter(kind='median', size=0.01) # Normalize profiles norm_val_crossplane = crossplane.values[center_int[0]] norm_val_inplane = inplane.values[center_int[1]] crossplane.normalize(norm_val=norm_val_crossplane) inplane.normalize(norm_val=norm_val_inplane) # Get index of CAX of both profiles(different than center) to be used for mirroring: fwhm_crossplane = crossplane.fwxm_center(interpolate=True) fwhm_inplane = inplane.fwxm_center(interpolate=True) # Plot profiles divider = make_axes_locatable(ax) ax_crossplane = divider.append_axes("bottom", size="40%", pad=0.25, sharex=ax) ax_crossplane.set_xlim([0, flatsym.image.array.shape[1]]) ax_crossplane.set_xticks([]) ax_crossplane.set_title("Crossplane") ax_inplane = divider.append_axes("right", size="40%", pad=0.25, sharey=ax) ax_inplane.set_ylim([0, flatsym.image.array.shape[0]]) ax_inplane.set_yticks([]) ax_inplane.set_title("Inplane") ax_crossplane.plot(crossplane._indices, crossplane, "b-") ax_crossplane.plot(2 * fwhm_crossplane - crossplane._indices, crossplane, "b--") ax_inplane.plot(inplane, inplane._indices, c="darkgreen", linestyle="-") ax_inplane.plot(inplane, 2 * fwhm_inplane - inplane._indices, c="darkgreen", linestyle="--") ax_inplane.grid(alpha=0.5) ax_crossplane.grid(alpha=0.5) mpld3.plugins.connect(fig, mpld3.plugins.MousePosition(fontsize=14, fmt=".2f")) script = mpld3.fig_to_html(fig, d3_url=D3_URL, mpld3_url=MPLD3_URL) if calc_definition == "Elekta": method = "elekta" else: method = "varian" try: flatsym.analyze(flatness_method=method, symmetry_method=method, vert_position=center[0], horiz_position=center[1]) except Exception as e: return template("error_template", {"error_message": "Analysis failed. " + str(e)}) symmetry_hor = round(flatsym.symmetry['horizontal']['value'], 2) symmetry_vrt = round(flatsym.symmetry['vertical']['value'], 2) flatness_hor = round(flatsym.flatness['horizontal']['value'], 2) flatness_vrt = round(flatsym.flatness['vertical']['value'], 2) horizontal_width = round( flatsym.symmetry['horizontal']['profile'].fwxm(interpolate=True) / flatsym.image.dpmm, 2) vertical_width = round( flatsym.symmetry['vertical']['profile'].fwxm(interpolate=True) / flatsym.image.dpmm, 2) horizontal_penumbra_width = round( flatsym.symmetry['horizontal']['profile'].penumbra_width( interpolate=True) / flatsym.image.dpmm, 2) vertical_penumbra_width = round( flatsym.symmetry['vertical']['profile'].penumbra_width( interpolate=True) / flatsym.image.dpmm, 2) # Check if passed if method == "varian": if (flatness_hor < tolerance_flat) and (flatness_vrt < tolerance_flat) and ( symmetry_hor < tolerance_sym) and (symmetry_vrt < tolerance_sym): passed = True else: passed = False else: if (abs(flatness_hor - 100) < tolerance_flat) and ( abs(flatness_vrt - 100) < tolerance_flat) and ( abs(symmetry_hor - 100) < tolerance_sym) and ( abs(symmetry_vrt - 100) < tolerance_sym): passed = True else: passed = False variables = { "symmetry_hor": symmetry_hor, "symmetry_vrt": symmetry_vrt, "flatness_hor": flatness_hor, "flatness_vrt": flatness_vrt, "horizontal_width": horizontal_width, "vertical_width": vertical_width, "horizontal_penumbra_width": horizontal_penumbra_width, "vertical_penumbra_width": vertical_penumbra_width, "passed": passed, "pdf_report_enable": pdf_report_enable, "script": script, "save_results": save_results, "acquisition_datetime": acquisition_datetime, "calc_definition": calc_definition } # Generate pylinac report: if pdf_report_enable == "True": pdf_file = tempfile.NamedTemporaryFile(delete=False, prefix="FlatSym", suffix=".pdf", dir=config.PDF_REPORT_FOLDER) flatsym.publish_pdf(pdf_file) variables["pdf_report_filename"] = os.path.basename(pdf_file.name) general_functions.delete_files_in_subfolders([temp_folder]) # Delete image return template("flatsym_results", variables)
def catphan_calculate_helperf(args): use_reference = args["use_reference"] phantom = args["phantom"] machine = args["machine"] beam = args["beam"] HU_delta = args["HU_delta"] colormap = args["colormap"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] s = args["s"] general_functions.set_configuration(args["config"]) # Transfer to this process save_results = { "machine": machine, "beam": beam, "phantom": phantom, "displayname": displayname } # Set colormap cmap = matplotlib.cm.get_cmap(colormap) # Collect data for "save results" tolerances = general_functions.get_tolerance_user_machine_catphan(machine, beam, phantom) # If user_machne has specific tolerance if not tolerances: hu, lcv, scaling, thickness, lowcontrast, cnr, mtf, uniformityidx, pdf_report_enable = "100", "2", "0.5", "0.25", "1", "10", "10", "3", "False" else: hu, lcv, scaling, thickness, lowcontrast, cnr, mtf, uniformityidx, pdf_report_enable = tolerances hu_tolerance = float(hu) lcv_tolerance = float(lcv) scaling_tolerance = float(scaling) thickness_tolerance = float(thickness) low_contrast_tolerance = float(lowcontrast) cnr_threshold = float(cnr) mtf_tolerance = float(mtf) uniformityidx_tolerance = float(uniformityidx) ref_path = general_functions.get_referenceimagepath_catphan(machine, beam, phantom) if ref_path is not None: ref_path = os.path.join(config.REFERENCE_IMAGES_FOLDER, ref_path[0]) if os.path.exists(ref_path): ref_exists = True else: ref_exists = False else: ref_exists = False folder_path = RestToolbox.GetSeries2Folder2(config.ORTHANC_URL, s) # Use two threads to speedup the calculation (if ref exists) args_current = {"hu_tolerance": hu_tolerance, "scaling_tolerance": scaling_tolerance, "thickness_tolerance": thickness_tolerance, "cnr_threshold": cnr_threshold, "path": folder_path, "phantom": phantom, "low_contrast_tolerance": low_contrast_tolerance, "config": general_functions.get_configuration()} args_ref = {"hu_tolerance": hu_tolerance, "scaling_tolerance": scaling_tolerance, "thickness_tolerance": thickness_tolerance, "cnr_threshold": cnr_threshold, "path": ref_path, "phantom": phantom, "low_contrast_tolerance": low_contrast_tolerance, "config": general_functions.get_configuration()} if use_reference and ref_exists: try: p = ThreadPool(2) [mycbct, mycbct_ref] = p.map(catphan_helperf_analyze, [args_current, args_ref]) finally: p.close() p.join() else: mycbct = catphan_helperf_analyze(args_current) if use_reference and ref_exists: if isinstance(mycbct_ref, Exception): return template("error_template", {"error_message": "Unable to analyze reference image. " + str(mycbct_ref) }) if isinstance(mycbct, Exception): general_functions.delete_files_in_subfolders([folder_path]) # Delete temporary images return template("error_template", {"error_message": "Unable to analyze image. " + str(mycbct) }) try: # add this to prevent memory problems when threads with exceptions are still alive # ######################### CTP528 - Resolution ################################### fig_dcm = Figure(figsize=(10.5, 5), tight_layout={"w_pad":0, "pad": 1.5}) ax1 = fig_dcm.add_subplot(1,2,1) ax2 = fig_dcm.add_subplot(1,2,2) # Reference image array if use_reference and ref_exists: ax1.imshow(mycbct_ref.ctp528.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') ax1.autoscale(enable=False) else: ax1.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') # Analysed current array ax2.imshow(mycbct.ctp528.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') ax2.set_title('CTP528 current image') ax1.set_title('CTP528 reference image') ax2.autoscale(enable=False) # Plot rMTF and gather some data fig_mtf = Figure(figsize=(5, 5), tight_layout={"w_pad":2, "pad": 1}) ax_mtf = fig_mtf.add_subplot(1,1,1) msize = 8 if use_reference and ref_exists: ax_mtf.plot(list(mycbct_ref.ctp528.mtf.norm_mtfs.keys()), list(mycbct_ref.ctp528.mtf.norm_mtfs.values()), marker='o', color="blue", markersize=msize, markerfacecolor="None", linestyle="--") ax_mtf.plot(list(mycbct.ctp528.mtf.norm_mtfs.keys()), list(mycbct.ctp528.mtf.norm_mtfs.values()), marker='o', color="blue", markersize=msize) ax_mtf.margins(0.05) ax_mtf.grid('on') ax_mtf.set_xlabel('Line pairs / mm') ax_mtf.set_ylabel("Relative MTF") ax_mtf.set_title('Modulation transfer function') script_ctp528 = mpld3.fig_to_html(fig_dcm, d3_url=D3_URL, mpld3_url=MPLD3_URL) script_ctp528mtf = mpld3.fig_to_html(fig_mtf, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Some data: mtf30_ref = mycbct_ref.ctp528.mtf.relative_resolution(30) if use_reference and ref_exists else np.nan mtf30 = mycbct.ctp528.mtf.relative_resolution(30) mtf50_ref = mycbct_ref.ctp528.mtf.relative_resolution(50) if use_reference and ref_exists else np.nan mtf50 = mycbct.ctp528.mtf.relative_resolution(50) mtf80_ref = mycbct_ref.ctp528.mtf.relative_resolution(80) if use_reference and ref_exists else np.nan mtf80 = mycbct.ctp528.mtf.relative_resolution(80) if use_reference and ref_exists: mtf_passing = True if abs(100*(mtf50-mtf50_ref)/mtf50_ref)<=mtf_tolerance else False else: mtf_passing = None # ####################### CTP404 - GEOMETRY HU LINEARITY #################### fig_404 = Figure(figsize=(10.5, 5), tight_layout={"w_pad":0, "pad": 1.5}) ax404_1 = fig_404.add_subplot(1,2,1) ax404_2 = fig_404.add_subplot(1,2,2) def ctp404_plotROI(mycbct, fig, axis): # Plot lines and circles - taken from pylinac # plot HU linearity ROIs for roi in mycbct.ctp404.hu_rois.values(): axis.add_patch(matplotlib.patches.Circle((roi.center.x, roi.center.y), edgecolor=roi.plot_color, radius=roi.radius, fill=False)) for roi in mycbct.ctp404.bg_hu_rois.values(): axis.add_patch(matplotlib.patches.Circle((roi.center.x, roi.center.y), edgecolor='blue', radius=roi.radius, fill=False)) # plot thickness ROIs for roi in mycbct.ctp404.thickness_rois.values(): axis.add_patch(matplotlib.patches.Rectangle((roi.bl_corner.x, roi.bl_corner.y), width=roi.width, height=roi.height, angle=0, edgecolor="blue", alpha=1, facecolor="g", fill=False)) # plot geometry lines for line in mycbct.ctp404.lines.values(): axis.plot((line.point1.x, line.point2.x), (line.point1.y, line.point2.y), linewidth=1, color=line.pass_fail_color) # Plot tooltips for patches names = [] hu_rois_centers_x = [] hu_rois_centers_y = [] hu_rois_radius = [] for name, roi in mycbct.ctp404.hu_rois.items(): names.append(name) hu_rois_centers_x.append(roi.center.x) hu_rois_centers_y.append(roi.center.y) hu_rois_radius.append((roi.radius)**2) hu_rois_ttip = axis.scatter(hu_rois_centers_x, hu_rois_centers_y, s = hu_rois_radius, alpha=0) labels = [names[i] for i in range(len(names))] tooltip = mpld3.plugins.PointLabelTooltip(hu_rois_ttip, labels=labels) mpld3.plugins.connect(fig, tooltip) # Add tooltips for 4 lines inc = 1 for line in mycbct.ctp404.lines.values(): hu_lines_ttip = axis.plot((line.point1.x, line.point2.x), (line.point1.y, line.point2.y), alpha=0, lw=7) tooltip2 = mpld3.plugins.LineLabelTooltip(hu_lines_ttip[0], label="Line "+str(inc)) mpld3.plugins.connect(fig, tooltip2) inc += 1 # Reference image if use_reference and ref_exists: ax404_1.imshow(mycbct_ref.ctp404.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') #mycbct_ref.ctp404.plot_rois(ax404_1) ctp404_plotROI(mycbct_ref, fig_404, ax404_1) # alternative ax404_1.autoscale(enable=False) ax404_1.set_xlim([0, mycbct_ref.ctp404.image.shape[1]]) ax404_1.set_ylim([mycbct_ref.ctp404.image.shape[0], 0]) else: ax404_1.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') # Current image ax404_2.imshow(mycbct.ctp404.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') ctp404_plotROI(mycbct, fig_404, ax404_2) # alternative #mycbct.ctp404.plot_rois(ax404_2) ax404_2.set_xlim([0, mycbct.ctp404.image.shape[1]]) ax404_2.set_ylim([mycbct.ctp404.image.shape[0], 0]) ax404_1.set_title('CTP404 reference image') ax404_2.set_title('CTP404 current image') ax404_2.autoscale(enable=False) # Draw HU linearity plot def plot_linearity(mycbct, fig, axis, plot_delta): '''Taken from pylinac''' nominal_x_values = [roi.nominal_val for roi in mycbct.ctp404.hu_rois.values()] actual_values = [] diff_values = [] if plot_delta: values = [] names = [] for name, roi in mycbct.ctp404.hu_rois.items(): names.append(name) values.append(roi.value_diff) actual_values.append(roi.pixel_value) diff_values.append(roi.value_diff) nominal_measurements = [0]*len(values) ylabel = 'HU Delta' else: values = [] names = [] for name, roi in mycbct.ctp404.hu_rois.items(): names.append(name) values.append(roi.pixel_value) actual_values.append(roi.pixel_value) diff_values.append(roi.value_diff) nominal_measurements = nominal_x_values ylabel = 'Measured Values' points = axis.plot(nominal_x_values, values, 'g+', markersize=15, mew=2) axis.plot(nominal_x_values, nominal_measurements) axis.plot(nominal_x_values, np.array(nominal_measurements) + mycbct.ctp404.hu_tolerance, 'r--') axis.plot(nominal_x_values, np.array(nominal_measurements) - mycbct.ctp404.hu_tolerance, 'r--') axis.margins(0.07) axis.grid(True, alpha=0.35) axis.set_xlabel("Nominal Values") axis.set_ylabel(ylabel) axis.set_title("HU linearity") labels = [names[i]+" -- Nom.={:.1f}, Act.={:.1f}, Diff.={:.1f}".format(nominal_x_values[i], actual_values[i], diff_values[i]) for i in range(len(names))] tooltip = mpld3.plugins.PointLabelTooltip(points[0], labels=labels, location="top right") mpld3.plugins.connect(fig, tooltip) fig_404_HU = Figure(figsize=(10.5, 5), tight_layout={"w_pad":1}) ax_HU_ref = fig_404_HU.add_subplot(1,2,1) ax_HU = fig_404_HU.add_subplot(1,2,2) # Reference HU linearity if use_reference and ref_exists: plot_linearity(mycbct_ref, fig_404_HU, ax_HU_ref, plot_delta=HU_delta) else: ax_HU_ref.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') ax_HU_ref.set_title("HU linearity") # Current HU linearity plot_linearity(mycbct, fig_404_HU, ax_HU, plot_delta=HU_delta) # Gather data from HU holes: if use_reference and ref_exists: HU_values_ref = [] HU_std_ref = [] HU_diff_ref = [] cnrs404_ref = [] for key, value in mycbct_ref.ctp404.hu_rois.items(): HU_values_ref.append(value.pixel_value) HU_std_ref.append(round(value.std, 1)) HU_diff_ref.append(value.value_diff) cnrs404_ref.append(round(value.cnr, 1)) # Background HU ROIs for key, value in mycbct_ref.ctp404.bg_hu_rois.items(): HU_values_ref.append(value.pixel_value) HU_std_ref.append(round(value.std, 1)) HU_diff_ref.append(np.nan) cnrs404_ref.append(np.nan) lcv_ref = round(mycbct_ref.ctp404.lcv, 2) slice_thickness_ref = round(mycbct_ref.ctp404.meas_slice_thickness, 2) lines_ref = [] # Line length for l in mycbct_ref.ctp404.lines.values(): lines_ref.append(round(l.length_mm, 2)) lines_avg_ref = round(mycbct_ref.ctp404.avg_line_length, 2) phantom_roll_ref = round(mycbct_ref.ctp404.catphan_roll, 2) dicom_slice_thickness_ref = round(mycbct_ref.ctp404.slice_thickness, 2) else: length = len(list(mycbct.ctp404.hu_rois.values())+list(mycbct.ctp404.bg_hu_rois.values())) HU_values_ref = [np.nan]*length HU_std_ref = [np.nan]*length HU_diff_ref = [np.nan]*length cnrs404_ref = [np.nan]*length lcv_ref = np.nan slice_thickness_ref = np.nan lines_ref = [np.nan]*len(mycbct.ctp404.lines.values()) lines_avg_ref = np.nan phantom_roll_ref = np.nan dicom_slice_thickness_ref = np.nan HU_values = [] HU_std = [] HU_diff = [] HU_nominal = [] HU_names = [] cnrs404 = [] HU_CNR_values_dict = {} for key, value in mycbct.ctp404.hu_rois.items(): HU_values.append(value.pixel_value) HU_std.append(round(value.std, 1)) HU_diff.append(value.value_diff) HU_nominal.append(value.nominal_val) HU_names.append(key) cnrs404.append(round(value.cnr, 1)) HU_CNR_values_dict[key] = [value.pixel_value, round(value.cnr, 1)] # Background HU ROIs for key, value in mycbct.ctp404.bg_hu_rois.items(): HU_values.append(value.pixel_value) HU_std.append(round(value.std, 1)) HU_diff.append(np.nan) HU_nominal.append(0) HU_names.append("Background "+str(key)) cnrs404.append(np.nan) HU_CNR_values_dict[key] = [value.pixel_value, "nan"] # For easier acces of values in results save_results["HU_CNR_values_dict"] = HU_CNR_values_dict lcv = mycbct.ctp404.lcv slice_thickness = round(mycbct.ctp404.meas_slice_thickness, 2) phantom_roll = round(mycbct.ctp404.catphan_roll, 2) dicom_slice_thickness = round(mycbct.ctp404.slice_thickness, 2) # Get origin slice and phantom center and slice number of other modules if use_reference and ref_exists: mm_per_pixel_ref = round(mycbct_ref.mm_per_pixel, 2) origin_slice_ref = mycbct_ref.origin_slice ctp528_slice_ref = mycbct_ref.ctp528.slice_num ctp486_slice_ref = mycbct_ref.ctp486.slice_num ctp515_slice_ref = mycbct_ref.ctp515.slice_num if phantom != "Catphan 503" else np.nan phantom_center_ref = [round(mycbct_ref.ctp404.phan_center.x, 2), round(mycbct_ref.ctp404.phan_center.y, 2)] else: mm_per_pixel_ref = np.nan origin_slice_ref = np.nan ctp528_slice_ref = np.nan ctp486_slice_ref = np.nan ctp515_slice_ref = np.nan phantom_center_ref = [np.nan, np.nan] mm_per_pixel = round(mycbct.mm_per_pixel, 2) origin_slice = mycbct.origin_slice ctp528_slice = mycbct.ctp528.slice_num ctp486_slice = mycbct.ctp486.slice_num ctp515_slice = mycbct.ctp515.slice_num if phantom != "Catphan 503" else np.nan phantom_center = [round(mycbct.ctp404.phan_center.x, 2), round(mycbct.ctp404.phan_center.y, 2)] lines = [] # Line length for l in mycbct.ctp404.lines.values(): lines.append(round(l.length_mm, 2)) lines_avg = round(mycbct.ctp404.avg_line_length, 2) passed_HU = mycbct.ctp404.passed_hu passed_thickness = mycbct.ctp404.passed_thickness passed_geometry = mycbct.ctp404.passed_geometry passed_lcv = True if lcv >= lcv_tolerance else False passed_404 = passed_HU and passed_thickness and passed_geometry and passed_lcv script_404 = mpld3.fig_to_html(fig_404, d3_url=D3_URL, mpld3_url=MPLD3_URL) script_404_HU = mpld3.fig_to_html(fig_404_HU, d3_url=D3_URL, mpld3_url=MPLD3_URL) # ############################## CTP486 - UNIFORMITY #################### fig_486 = Figure(figsize=(10.5, 5), tight_layout={"w_pad":0, "pad": 1.5}) ax486_1 = fig_486.add_subplot(1,2,1) ax486_2 = fig_486.add_subplot(1,2,2) if use_reference and ref_exists: ax486_1.imshow(mycbct_ref.ctp486.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') mycbct_ref.ctp486.plot_rois(ax486_1) # Add text inside ROI for reference to uniformity index: for ind, roi in enumerate(mycbct_ref.ctp486.rois.values()): ax486_1.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') else: ax486_1.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') ax486_2.imshow(mycbct.ctp486.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') mycbct.ctp486.plot_rois(ax486_2) # Add text inside ROI for reference to uniformity index: for ind, roi in enumerate(mycbct.ctp486.rois.values()): ax486_2.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') ax486_1.set_title('CTP486 reference image') ax486_1.autoscale(enable=False) ax486_2.set_title('CTP486 current image') ax486_2.autoscale(enable=False) script_486 = mpld3.fig_to_html(fig_486, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Draw orthogonal profiles: fig_486_profile = Figure(figsize=(10.5, 5), tight_layout={"w_pad":0, "pad": 1.5}) ax486_profile_ref = fig_486_profile.add_subplot(1,2,1) ax486_profile = fig_486_profile.add_subplot(1,2,2) if use_reference and ref_exists: mycbct_ref.ctp486.plot_profiles(ax486_profile_ref) else: ax486_profile_ref.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') mycbct.ctp486.plot_profiles(ax486_profile) ax486_profile_ref.set_title('Reference uniformity profiles') ax486_profile.set_title('Current uniformity profiles') script_486_profile = mpld3.fig_to_html(fig_486_profile, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Get mean pixel values and uniformity index: # take into account slope and intercept HU = slope * px + intercept if use_reference and ref_exists: hvalues_ref = [roi.pixel_value for roi in mycbct_ref.ctp486.rois.values()] uidx_ref = round(mycbct_ref.ctp486.uniformity_index, 2) else: hvalues_ref = [np.nan]*len(mycbct.ctp486.rois.values()) uidx_ref = np.nan hvalues = [roi.pixel_value for roi in mycbct.ctp486.rois.values()] passed_uniformity = mycbct.ctp486.overall_passed uidx = round(mycbct.ctp486.uniformity_index, 2) passed_uniformity_index = True if abs(uidx)<=uniformityidx_tolerance else False # ############################## CTP515 - LOW CONTRAST #################### if phantom != "Catphan 503": show_ctp515 = True fig_515 = Figure(figsize=(10.5, 5), tight_layout={"w_pad":0, "pad": 1.5}) ax515_1 = fig_515.add_subplot(1,2,1) ax515_2 = fig_515.add_subplot(1,2,2) if use_reference and ref_exists: ax515_1.imshow(mycbct_ref.ctp515.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') mycbct_ref.ctp515.plot_rois(ax515_1) else: ax515_1.text(0.5, 0.5 ,"Reference image not available", horizontalalignment='center', verticalalignment='center') ax515_2.imshow(mycbct.ctp515.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') mycbct.ctp515.plot_rois(ax515_2) ax515_1.set_title('CTP515 reference image') ax515_1.autoscale(enable=False) ax515_2.set_title('CTP515 current image') ax515_2.autoscale(enable=False) script_515 = mpld3.fig_to_html(fig_515, d3_url=D3_URL, mpld3_url=MPLD3_URL) fig_515_contrast = Figure(figsize=(10, 5), tight_layout={"w_pad":1, "pad": 1}) ax515_contrast = fig_515_contrast.add_subplot(1,2,1) ax515_cnr = fig_515_contrast.add_subplot(1,2,2) cnrs_names = [] contrasts_515 = [] cnrs515 = [] for key, value in mycbct.ctp515.rois.items(): cnrs_names.append(key) contrasts_515.append(value.contrast_constant) cnrs515.append(round(value.cnr_constant, 2)) sizes_515 = np.array(cnrs_names, dtype=int) ax515_contrast.plot(sizes_515, contrasts_515, marker='o', color="blue", markersize=8, markerfacecolor="None", linestyle="-") ax515_cnr.plot(sizes_515, cnrs515, marker='o', color="blue", markersize=8, markerfacecolor="None", linestyle="-") if use_reference and ref_exists: contrasts_515_ref = [] cnrs515_ref = [] cnrs_names_ref = [] for key, value in mycbct_ref.ctp515.rois.items(): cnrs_names_ref.append(key) contrasts_515_ref.append(value.contrast_constant) cnrs515_ref.append(round(value.cnr_constant, 2)) sizes_515_ref = np.array(cnrs_names_ref, dtype=int) ax515_contrast.plot(sizes_515_ref, contrasts_515_ref, marker='o', color="blue", markersize=8, markerfacecolor="None", linestyle="--") ax515_cnr.plot(sizes_515_ref, cnrs515_ref, marker='o', color="blue", markersize=8, markerfacecolor="None", linestyle="--") ctp515_visible_ref = mycbct_ref.ctp515.rois_visible else: ctp515_visible_ref = np.nan cnrs515_ref = [np.nan]*len(mycbct.ctp515.rois.values()) ax515_contrast.margins(0.05) ax515_contrast.grid(True) ax515_contrast.set_xlabel('ROI size (mm)') ax515_contrast.set_ylabel("Contrast * Diameter") ax515_cnr.margins(0.05) ax515_cnr.grid(True) ax515_cnr.set_xlabel('ROI size (mm)') ax515_cnr.set_ylabel("CNR * Diameter") script_515_contrast = mpld3.fig_to_html(fig_515_contrast, d3_url=D3_URL, mpld3_url=MPLD3_URL) ctp515_passed = mycbct.ctp515.overall_passed #ctp515_passed = None ctp515_visible = mycbct.ctp515.rois_visible else: show_ctp515 = False script_515_contrast = None script_515 = None ctp515_visible_ref = np.nan ctp515_passed = None ctp515_visible = np.nan cnrs515_ref = None cnrs515 = None cnrs_names = None general_functions.delete_files_in_subfolders([folder_path]) # Delete temporary images variables = { "script_ctp528": script_ctp528, "script_ctp528mtf": script_ctp528mtf, "mtf30_ref": round(mtf30_ref, 2), "mtf30": round(mtf30, 2), "mtf50_ref": round(mtf50_ref, 2), "mtf50": round(mtf50, 2), "mtf80_ref": round(mtf80_ref, 2), "mtf80": round(mtf80, 2), "mtf_passing": mtf_passing, "script_404": script_404, "script_404_HU": script_404_HU, "HU_values_ref": HU_values_ref, "HU_std_ref": HU_std_ref, "HU_values": HU_values, "HU_std": HU_std, "HU_nominal": HU_nominal, "HU_names": HU_names, "HU_diff_ref": HU_diff_ref, "HU_diff": HU_diff, "passed_HU": passed_HU, "passed_thickness": passed_thickness, "passed_geometry": passed_geometry, "passed_lcv": passed_lcv, "passed_404": passed_404, "lcv_ref": lcv_ref, "lcv": round(lcv, 2), "slice_thickness": slice_thickness, "slice_thickness_ref": slice_thickness_ref, "dicom_slice_thickness": dicom_slice_thickness, "dicom_slice_thickness_ref": dicom_slice_thickness_ref, "lines_ref": lines_ref, "lines": lines, "lines_avg": lines_avg, "lines_avg_ref": lines_avg_ref, "phantom_roll": phantom_roll, "phantom_roll_ref": phantom_roll_ref, "origin_slice_ref": origin_slice_ref, "origin_slice": origin_slice, "ctp528_slice": ctp528_slice, "ctp486_slice": ctp486_slice, "ctp515_slice": ctp515_slice, "ctp528_slice_ref": ctp528_slice_ref, "ctp486_slice_ref": ctp486_slice_ref, "ctp515_slice_ref": ctp515_slice_ref, "phantom_center_ref": phantom_center_ref, "phantom_center": phantom_center, "mm_per_pixel": mm_per_pixel, "mm_per_pixel_ref": mm_per_pixel_ref, "cnrs404_ref" : cnrs404_ref, "cnrs404": cnrs404, "script_486": script_486, "script_486_profile": script_486_profile, "hvalues_ref": hvalues_ref, "hvalues": hvalues, "passed_uniformity": passed_uniformity, "passed_uniformity_index": passed_uniformity_index, "uidx": uidx, "uidx_ref": uidx_ref, "script_515": script_515, "script_515_contrast": script_515_contrast, "show_ctp515": show_ctp515, "ctp515_passed": ctp515_passed, "ctp515_visible": ctp515_visible, "ctp515_visible_ref": ctp515_visible_ref, "cnrs515_ref": cnrs515_ref, "cnrs515": cnrs515, "cnrs_names": cnrs_names, "save_results": save_results, "acquisition_datetime": acquisition_datetime, "pdf_report_enable": pdf_report_enable } # Generate pylinac report: if pdf_report_enable == "True": pdf_file = tempfile.NamedTemporaryFile(delete=False, prefix="Catphan", suffix=".pdf", dir=config.PDF_REPORT_FOLDER) mycbct.publish_pdf(pdf_file) variables["pdf_report_filename"] = os.path.basename(pdf_file.name) except Exception as e: general_functions.delete_files_in_subfolders([folder_path]) # Delete temporary images return template("error_template", {"error_message": "Cannot analyze image. "+str(e) }) else: return template("catphan_results", variables)
def planar_imaging_helperf(args): # This function is used in order to prevent memory problems clip_box = args["clip_box"] phantom = args["phantom"] machine = args["machine"] beam = args["beam"] leedsrot1 = args["leedsrot1"] leedsrot2 = args["leedsrot2"] inv = args["inv"] bbox = args["bbox"] w1 = args["w1"] use_reference = args["use_reference"] colormap = args["colormap"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] general_functions.set_configuration( args["config"]) # Transfer to this process # Collect data for "save results" tolerances = general_functions.get_tolerance_user_machine_planarimaging( machine, beam, phantom) # If user_machne has specific tolerance if not tolerances: lowtresh, hightresh, generate_pdf = 0.05, 0.1, "True" else: lowtresh, hightresh, generate_pdf = tolerances lowtresh = float(lowtresh) hightresh = float(hightresh) # Set colormap cmap = matplotlib.cm.get_cmap(colormap) try: temp_folder1, file_path1 = RestToolbox.GetSingleDcm( config.ORTHANC_URL, w1) except: return template("error_template", {"error_message": "Cannot read image."}) ref_path1 = general_functions.get_referenceimagepath_planarimaging( machine, beam, phantom) if ref_path1 is not None: ref_path1 = os.path.join(config.REFERENCE_IMAGES_FOLDER, ref_path1[0]) if os.path.exists(ref_path1): ref1_exists = True else: ref1_exists = False else: ref1_exists = False if not use_reference: ref1_exists = False # First analyze first image try: if phantom == "QC3": pi1 = StandardImagingQC3(file_path1) elif phantom == "LeedsTOR": pi1 = LeedsTOR(file_path1) elif phantom == "Las Vegas": pi1 = LasVegas(file_path1) elif phantom == "DoselabMC2MV": pi1 = DoselabMC2MV(file_path1) else: pi1 = DoselabMC2kV(file_path1) if clip_box != 0: try: #pi1.image.check_inversion_by_histogram() general_functions.clip_around_image(pi1.image, clip_box) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) pi1.analyze(low_contrast_threshold=lowtresh, high_contrast_threshold=hightresh, invert=inv, angle_override=None if leedsrot2 == 0 else leedsrot2) except Exception as e: return template("error_template", {"error_message": "Cannot analyze image 1. " + str(e)}) # Analyze reference images if they exists if ref1_exists: try: if phantom == "QC3": ref1 = StandardImagingQC3(ref_path1) elif phantom == "LeedsTOR": ref1 = LeedsTOR(ref_path1) elif phantom == "Las Vegas": ref1 = LasVegas(ref_path1) elif phantom == "DoselabMC2MV": ref1 = DoselabMC2MV(ref_path1) else: ref1 = DoselabMC2kV(ref_path1) if clip_box != 0: try: #ref1.image.check_inversion_by_histogram() general_functions.clip_around_image(ref1.image, clip_box) except Exception as e: return template("error_template", { "error_message": "Unable to apply clipbox. " + str(e) }) ref1.analyze(low_contrast_threshold=lowtresh, high_contrast_threshold=hightresh, invert=inv, angle_override=None if leedsrot1 == 0 else leedsrot1) except: return template("error_template", {"error_message": "Cannot analyze reference image."\ " Check that the image in the database is valid."}) save_results = { "machine": machine, "beam": beam, "phantom": phantom, "displayname": displayname } fig = Figure(figsize=(10.5, 5), tight_layout={"w_pad": 0, "pad": 1.5}) ax_ref = fig.add_subplot(1, 2, 1) ax_pi = fig.add_subplot(1, 2, 2) # Plot reference image and regions if phantom == "QC3": low_contrast_rois_pi1 = pi1.low_contrast_rois[ 1:] # Exclude first point which is background else: low_contrast_rois_pi1 = pi1.low_contrast_rois if ref1_exists: if phantom == "QC3": low_contrast_rois_ref1 = ref1.low_contrast_rois[ 1:] # Exclude first point which is background else: low_contrast_rois_ref1 = ref1.low_contrast_rois if ref1_exists: ax_ref.imshow(ref1.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') ax_ref.set_title(phantom + ' Reference Image') ax_ref.axis('off') # plot the background ROIS for roi in ref1.low_contrast_background_rois: roi.plot2axes(ax_ref, edgecolor='yellow') ax_ref.text(roi.center.x, roi.center.y, "B", horizontalalignment='center', verticalalignment='center') # low contrast ROIs for ind, roi in enumerate(low_contrast_rois_ref1): roi.plot2axes(ax_ref, edgecolor=roi.plot_color) ax_ref.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') # plot the high-contrast ROIs if phantom != "Las Vegas": mtf_temp_ref = list(ref1.mtf.norm_mtfs.values()) for ind, roi in enumerate(ref1.high_contrast_rois): color = 'b' if mtf_temp_ref[ ind] > ref1._high_contrast_threshold else 'r' roi.plot2axes(ax_ref, edgecolor=color) ax_ref.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') else: ax_ref.text(0.5, 0.5, "Reference image not available", horizontalalignment='center', verticalalignment='center') # Plot current image and regions ax_pi.imshow(pi1.image.array, cmap=cmap, interpolation="none", aspect="equal", origin='upper') ax_pi.axis('off') ax_pi.set_title(phantom + ' Current Image') # plot the background ROIS for roi in pi1.low_contrast_background_rois: roi.plot2axes(ax_pi, edgecolor='yellow') ax_pi.text(roi.center.x, roi.center.y, "B", horizontalalignment='center', verticalalignment='center') # low contrast ROIs for ind, roi in enumerate(low_contrast_rois_pi1): roi.plot2axes(ax_pi, edgecolor=roi.plot_color) ax_pi.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') # plot the high-contrast ROIs if phantom != "Las Vegas": mtf_temp_pi = list(pi1.mtf.norm_mtfs.values()) for ind, roi in enumerate(pi1.high_contrast_rois): color = 'b' if mtf_temp_pi[ ind] > pi1._high_contrast_threshold else 'r' roi.plot2axes(ax_pi, edgecolor=color) ax_pi.text(roi.center.x, roi.center.y, str(ind), horizontalalignment='center', verticalalignment='center') # Zoom on phantom if requested: if bbox: if phantom == "QC3" or phantom == "DoselabMC2MV" or phantom == "DoselabMC2kV": pad = 15 # Additional space between cyan bbox and plot if ref1_exists: bounding_box_ref = ref1.phantom_ski_region.bbox bbox_center_ref = ref1.phantom_center if abs(bounding_box_ref[1] - bounding_box_ref[3]) >= abs(bounding_box_ref[2] - bounding_box_ref[0]): dist = abs(bounding_box_ref[1] - bounding_box_ref[3]) / 2 ax_ref.set_ylim(bbox_center_ref.y + dist + pad, bbox_center_ref.y - dist - pad) ax_ref.set_xlim(bbox_center_ref.x - dist - pad, bbox_center_ref.x + dist + pad) else: dist = abs(bounding_box_ref[2] - bounding_box_ref[0]) / 2 ax_ref.set_ylim(bbox_center_ref.y + dist + pad, bbox_center_ref.y - dist - pad) ax_ref.set_xlim(bbox_center_ref.x - dist - pad, bbox_center_ref.x + dist + pad) ax_ref.plot([ bounding_box_ref[1], bounding_box_ref[1], bounding_box_ref[3], bounding_box_ref[3], bounding_box_ref[1] ], [ bounding_box_ref[2], bounding_box_ref[0], bounding_box_ref[0], bounding_box_ref[2], bounding_box_ref[2] ], c="cyan") ax_ref.autoscale(False) bounding_box_pi = pi1.phantom_ski_region.bbox bbox_center_pi = pi1.phantom_center if abs(bounding_box_pi[1] - bounding_box_pi[3]) >= abs(bounding_box_pi[2] - bounding_box_pi[0]): dist = abs(bounding_box_pi[1] - bounding_box_pi[3]) / 2 ax_pi.set_ylim(bbox_center_pi.y + dist + pad, bbox_center_pi.y - dist - pad) ax_pi.set_xlim(bbox_center_pi.x - dist - pad, bbox_center_pi.x + dist + pad) else: dist = abs(bounding_box_pi[2] - bounding_box_pi[0]) / 2 ax_pi.set_ylim(bbox_center_pi.y + dist + pad, bbox_center_pi.y - dist - pad) ax_pi.set_xlim(bbox_center_pi.x - dist - pad, bbox_center_pi.x + dist + pad) ax_pi.plot([ bounding_box_pi[1], bounding_box_pi[1], bounding_box_pi[3], bounding_box_pi[3], bounding_box_pi[1] ], [ bounding_box_pi[2], bounding_box_pi[0], bounding_box_pi[0], bounding_box_pi[2], bounding_box_pi[2] ], c="cyan") ax_pi.autoscale(False) elif phantom == "Las Vegas": # For some reason phantom_ski_regio has an underscore pad = 15 # Additional space between cyan bbox and plot if ref1_exists: bounding_box_ref = ref1._phantom_ski_region.bbox bbox_center_ref = ref1.phantom_center if abs(bounding_box_ref[1] - bounding_box_ref[3]) >= abs(bounding_box_ref[2] - bounding_box_ref[0]): dist = abs(bounding_box_ref[1] - bounding_box_ref[3]) / 2 ax_ref.set_ylim(bbox_center_ref.y + dist + pad, bbox_center_ref.y - dist - pad) ax_ref.set_xlim(bbox_center_ref.x - dist - pad, bbox_center_ref.x + dist + pad) else: dist = abs(bounding_box_ref[2] - bounding_box_ref[0]) / 2 ax_ref.set_ylim(bbox_center_ref.y + dist + pad, bbox_center_ref.y - dist - pad) ax_ref.set_xlim(bbox_center_ref.x - dist - pad, bbox_center_ref.x + dist + pad) ax_ref.plot([ bounding_box_ref[1], bounding_box_ref[1], bounding_box_ref[3], bounding_box_ref[3], bounding_box_ref[1] ], [ bounding_box_ref[2], bounding_box_ref[0], bounding_box_ref[0], bounding_box_ref[2], bounding_box_ref[2] ], c="cyan") ax_ref.autoscale(False) bounding_box_pi = pi1._phantom_ski_region.bbox bbox_center_pi = pi1.phantom_center if abs(bounding_box_pi[1] - bounding_box_pi[3]) >= abs(bounding_box_pi[2] - bounding_box_pi[0]): dist = abs(bounding_box_pi[1] - bounding_box_pi[3]) / 2 ax_pi.set_ylim(bbox_center_pi.y + dist + pad, bbox_center_pi.y - dist - pad) ax_pi.set_xlim(bbox_center_pi.x - dist - pad, bbox_center_pi.x + dist + pad) else: dist = abs(bounding_box_pi[2] - bounding_box_pi[0]) / 2 ax_pi.set_ylim(bbox_center_pi.y + dist + pad, bbox_center_pi.y - dist - pad) ax_pi.set_xlim(bbox_center_pi.x - dist - pad, bbox_center_pi.x + dist + pad) ax_pi.plot([ bounding_box_pi[1], bounding_box_pi[1], bounding_box_pi[3], bounding_box_pi[3], bounding_box_pi[1] ], [ bounding_box_pi[2], bounding_box_pi[0], bounding_box_pi[0], bounding_box_pi[2], bounding_box_pi[2] ], c="cyan") ax_pi.autoscale(False) elif phantom == "LeedsTOR": pad = 15 # Additional space between cyan bbox and plot if ref1_exists: big_circle_idx = np.argsort([ ref1._regions[roi].major_axis_length for roi in ref1._blobs ])[-1] circle_roi = ref1._regions[ref1._blobs[big_circle_idx]] bounding_box_ref = circle_roi.bbox bbox_center_ref = bbox_center(circle_roi) max_xy = max([ abs(bounding_box_ref[1] - bounding_box_ref[3]) / 2, abs(bounding_box_ref[0] - bounding_box_ref[2]) / 2 ]) ax_ref.set_ylim(bbox_center_ref.y + max_xy + pad, bbox_center_ref.y - max_xy - pad) ax_ref.set_xlim(bbox_center_ref.x - max_xy - pad, bbox_center_ref.x + max_xy + pad) ax_ref.plot([ bounding_box_ref[1], bounding_box_ref[1], bounding_box_ref[3], bounding_box_ref[3], bounding_box_ref[1] ], [ bounding_box_ref[2], bounding_box_ref[0], bounding_box_ref[0], bounding_box_ref[2], bounding_box_ref[2] ], c="cyan") ax_ref.autoscale(False) big_circle_idx = np.argsort([ pi1._regions[roi].major_axis_length for roi in pi1._blobs ])[-1] circle_roi = pi1._regions[pi1._blobs[big_circle_idx]] bounding_box_pi = circle_roi.bbox bbox_center_pi = bbox_center(circle_roi) max_xy = max([ abs(bounding_box_pi[1] - bounding_box_pi[3]) / 2, abs(bounding_box_pi[0] - bounding_box_pi[2]) / 2 ]) ax_pi.set_ylim(bbox_center_pi.y + max_xy + pad, bbox_center_pi.y - max_xy - pad) ax_pi.set_xlim(bbox_center_pi.x - max_xy - pad, bbox_center_pi.x + max_xy + pad) ax_pi.plot([ bounding_box_pi[1], bounding_box_pi[1], bounding_box_pi[3], bounding_box_pi[3], bounding_box_pi[1] ], [ bounding_box_pi[2], bounding_box_pi[0], bounding_box_pi[0], bounding_box_pi[2], bounding_box_pi[2] ], c="cyan") ax_pi.autoscale(False) # Add phantom outline: outline_obj_pi1, settings_pi1 = pi1._create_phantom_outline_object() outline_obj_pi1.plot2axes(ax_pi, edgecolor='g', **settings_pi1) if ref1_exists: outline_obj_ref1, settings_ref1 = ref1._create_phantom_outline_object() outline_obj_ref1.plot2axes(ax_ref, edgecolor='g', **settings_ref1) # Plot low frequency contrast, CNR and rMTF fig2 = Figure(figsize=(10.5, 10), tight_layout={"w_pad": 1}) ax_lfc = fig2.add_subplot(2, 2, 1) ax_lfcnr = fig2.add_subplot(2, 2, 2) ax_rmtf = fig2.add_subplot(2, 2, 3) # lfc ax_lfc.plot([abs(roi.contrast) for roi in low_contrast_rois_pi1], marker='o', markersize=8, color='r') if ref1_exists: ax_lfc.plot([abs(roi.contrast) for roi in low_contrast_rois_ref1], marker='o', color='r', markersize=8, markerfacecolor="None", linestyle="--") ax_lfc.plot([], [], color='r', linestyle="--", label='Reference') ax_lfc.plot([], [], color='r', label='Current') ax_lfc.plot([0, len(low_contrast_rois_pi1) - 1], [lowtresh, lowtresh], "-g") ax_lfc.grid(True) ax_lfc.set_title('Low-frequency Contrast') ax_lfc.set_xlabel('ROI #') ax_lfc.set_ylabel('Contrast') ax_lfc.set_xticks(np.arange(0, len(low_contrast_rois_pi1), 1)) ax_lfc.legend(loc='upper right', ncol=2, columnspacing=0, fontsize=12, handletextpad=0) ax_lfc.margins(0.05) # CNR ax_lfcnr.plot( [abs(roi.contrast_to_noise) for roi in low_contrast_rois_pi1], marker='^', markersize=8, color='r') if ref1_exists: ax_lfcnr.plot( [abs(roi.contrast_to_noise) for roi in low_contrast_rois_ref1], marker='^', color='r', markersize=8, markerfacecolor="None", linestyle="--") ax_lfcnr.plot([], [], color='r', linestyle="--", label='Reference') ax_lfcnr.plot([], [], color='r', label='Current') ax_lfcnr.grid(True) ax_lfcnr.set_title('Contrast-Noise Ratio') ax_lfcnr.set_xlabel('ROI #') ax_lfcnr.set_ylabel('CNR') ax_lfcnr.set_xticks(np.arange(0, len(low_contrast_rois_pi1), 1)) ax_lfcnr.legend(loc='upper right', ncol=2, columnspacing=0, fontsize=12, handletextpad=0) ax_lfcnr.margins(0.05) # rMTF if phantom != "Las Vegas": mtfs_pi1 = list(pi1.mtf.norm_mtfs.values()) if ref1_exists: mtfs_ref1 = list(ref1.mtf.norm_mtfs.values()) else: mtfs_ref1 = [np.nan] * len(mtfs_pi1) lppmm = pi1.mtf.spacings ax_rmtf.plot(lppmm, mtfs_pi1, marker='D', markersize=8, color='b') ax_rmtf.plot([min(lppmm), max(lppmm)], [hightresh, hightresh], "-g") if ref1_exists: ax_rmtf.plot(lppmm, mtfs_ref1, marker='D', color='b', markersize=8, markerfacecolor="None", linestyle="--") ax_rmtf.plot([], [], color='b', linestyle="--", label='Reference') ax_rmtf.plot([], [], color='b', label='Current') ax_rmtf.grid(True) ax_rmtf.set_title('High-frequency rMTF') ax_rmtf.set_xlabel('Line pairs / mm') ax_rmtf.set_ylabel('relative MTF') ax_rmtf.legend(loc='upper right', ncol=2, columnspacing=0, fontsize=12, handletextpad=0) ax_rmtf.margins(0.05) f30 = [ ref1.mtf.relative_resolution(30) if ref1_exists else np.nan, pi1.mtf.relative_resolution(30) ] f40 = [ ref1.mtf.relative_resolution(40) if ref1_exists else np.nan, pi1.mtf.relative_resolution(40) ] f50 = [ ref1.mtf.relative_resolution(50) if ref1_exists else np.nan, pi1.mtf.relative_resolution(50) ] f80 = [ ref1.mtf.relative_resolution(80) if ref1_exists else np.nan, pi1.mtf.relative_resolution(80) ] else: ax_rmtf.text(0.5, 0.5, "MTF not available", horizontalalignment='center', verticalalignment='center') f30 = [np.nan, np.nan] f40 = [np.nan, np.nan] f50 = [np.nan, np.nan] f80 = [np.nan, np.nan] if ref1_exists: median_contrast = [ np.median([roi.contrast for roi in low_contrast_rois_ref1]), np.median([roi.contrast for roi in low_contrast_rois_pi1]) ] median_CNR = [ np.median( [roi.contrast_to_noise for roi in low_contrast_rois_ref1]), np.median([roi.contrast_to_noise for roi in low_contrast_rois_pi1]) ] phantom_angle = [ref1.phantom_angle, pi1.phantom_angle] else: median_contrast = [ np.nan, np.median([roi.contrast for roi in low_contrast_rois_pi1]) ] median_CNR = [ np.nan, np.median([roi.contrast_to_noise for roi in low_contrast_rois_pi1]) ] phantom_angle = [np.nan, pi1.phantom_angle] script = mpld3.fig_to_html(fig, d3_url=D3_URL, mpld3_url=MPLD3_URL) script2 = mpld3.fig_to_html(fig2, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script": script, "script2": script2, "f30": f30, "f40": f40, "f50": f50, "f80": f80, "median_contrast": median_contrast, "median_CNR": median_CNR, "pdf_report_enable": generate_pdf, "save_results": save_results, "acquisition_datetime": acquisition_datetime, "phantom_angle": phantom_angle } if generate_pdf == "True": pdf_file = tempfile.NamedTemporaryFile(delete=False, prefix="PlanarImaging_", suffix=".pdf", dir=config.PDF_REPORT_FOLDER) metadata = RestToolbox.GetInstances(config.ORTHANC_URL, [w1]) try: patient = metadata[0]["PatientName"] except: patient = "" try: stationname = metadata[0]["StationName"] except: stationname = "" try: date_time = RestToolbox.get_datetime(metadata[0]) date_var = datetime.datetime.strptime( date_time[0], "%Y%m%d").strftime("%d/%m/%Y") except: date_var = "" pi1.publish_pdf(pdf_file, notes=[ "Date = " + date_var, "Patient = " + patient, "Station = " + stationname ]) variables["pdf_report_filename"] = os.path.basename(pdf_file.name) general_functions.delete_figure([fig, fig2]) general_functions.delete_files_in_subfolders([temp_folder1 ]) # Delete image #gc.collect() return template("planar_imaging_results", variables)
def starshot_helperf(args): imgtype = args["imgtype"] w = args["w"] clip_box = args["clip_box"] radius = args["radius"] min_peak_height = args["min_peak_height"] start_x = args["start_x"] start_y = args["start_y"] dpi = args["dpi"] sid = args["sid"] fwhm = args["fwhm"] recursive = args["recursive"] invert = args["invert"] temp_folder = args["temp_folder"] file_path = args["file_path"] imgdescription = args["imgdescription"] station = args["station"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] general_functions.set_configuration(args["config"]) # Collect data for "save results" dicomenergy = general_functions.get_energy_from_imgdescription(imgdescription) user_machine, user_energy = general_functions.get_user_machine_and_energy(station, dicomenergy) machines_and_energies = general_functions.get_machines_and_energies(general_functions.get_treatmentunits_starshot()) tolerances = general_functions.get_tolerance_user_machine_starshot(user_machine) # If user_machne has specific tolerance if not tolerances: tolerance, pdf_report_enable = general_functions.get_settings_starshot() else: tolerance, pdf_report_enable = tolerances[0] tolerance = float(tolerance) # If more than this, the test is "borderline", but not "failed" save_results = { "user_machine": user_machine, "user_energy": user_energy, "machines_and_energies": machines_and_energies, "testtype": ["Collimator", "Couch", "Gantry"], "displayname": displayname } if start_x==0 or start_y==0: start_point=None else: start_point=(start_x, start_y) if sid==0.0 and dpi==0: try: star = Starshot(file_path) except Exception as e: return template("error_template", {"error_message": "The Starshot module cannot calculate. "+str(e)}) elif sid==0.0 and dpi!=0: try: star = Starshot(file_path, dpi=dpi) except Exception as e: return template("error_template", {"error_message": "The Starshot module cannot calculate. "+str(e)}) elif sid!=0.0 and dpi==0: try: star = Starshot(file_path, sid=sid) except Exception as e: return template("error_template", {"error_message": "The Starshot module cannot calculate. "+str(e)}) else: try: star = Starshot(file_path, dpi=dpi, sid=sid) except Exception as e: return template("error_template", {"error_message": "The Starshot module cannot calculate. "+str(e)}) # Here we force pixels to background outside of box: if clip_box != 0: try: star.image.check_inversion_by_histogram(percentiles=[4, 50, 96]) # Check inversion otherwise this might not work general_functions.clip_around_image(star.image, clip_box) except Exception as e: return template("error_template", {"error_message": "Unable to apply clipbox. "+str(e)}) # If inversion is selected: if invert: star.image.invert() # Now we try to analyse try: star.analyze(radius=radius, min_peak_height=min_peak_height, tolerance=tolerance, start_point=start_point, fwhm=fwhm, recursive=recursive) except Exception as e: return template("error_template", {"error_message": "Module Starshot cannot calculate. "+str(e)}) fig_ss = Figure(figsize=(10, 6), tight_layout={"w_pad":4}) img_ax = fig_ss.add_subplot(1,2,1) wobble_ax = fig_ss.add_subplot(1,2,2) img_ax.imshow(star.image.array, cmap=matplotlib.cm.gray, interpolation="none", aspect="equal", origin='upper') star.lines.plot(img_ax) star.wobble.plot2axes(img_ax, edgecolor='green') star.circle_profile.plot2axes(img_ax, edgecolor='green') img_ax.axis('off') img_ax.autoscale(tight=True) img_ax.set_aspect(1) img_ax.set_xticks([]) img_ax.set_yticks([]) star.lines.plot(wobble_ax) star.wobble.plot2axes(wobble_ax, edgecolor='green') star.circle_profile.plot2axes(wobble_ax, edgecolor='green') wobble_ax.axis('off') xlims = [star.wobble.center.x + star.wobble.diameter, star.wobble.center.x - star.wobble.diameter] ylims = [star.wobble.center.y + star.wobble.diameter, star.wobble.center.y - star.wobble.diameter] wobble_ax.set_xlim(xlims) wobble_ax.set_ylim(ylims) wobble_ax.axis('on') wobble_ax.set_aspect(1) script = mpld3.fig_to_html(fig_ss, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script": script, "passed": star.passed, "radius": star.wobble.radius_mm, "tolerance": star.tolerance, "circle_center": star.wobble.center, "pdf_report_enable": pdf_report_enable, "save_results": save_results, "acquisition_datetime": acquisition_datetime } # Generate pylinac report: if pdf_report_enable == "True": pdf_file = tempfile.NamedTemporaryFile(delete=False, prefix="Starshot_", suffix=".pdf", dir=config.PDF_REPORT_FOLDER) if imgtype == "dicom": metadata = RestToolbox.GetInstances(config.ORTHANC_URL, [w]) try: patient = metadata[0]["PatientName"] except: patient = "" try: stationname = metadata[0]["StationName"] except: stationname = "" try: date_time = RestToolbox.get_datetime(metadata[0]) date_var = datetime.datetime.strptime(date_time[0], "%Y%m%d").strftime("%d/%m/%Y") except: date_var = "" star.publish_pdf(pdf_file, notes=["Date = "+date_var, "Patient = "+patient, "Station = "+stationname]) else: star.publish_pdf(pdf_file) variables["pdf_report_filename"] = os.path.basename(pdf_file.name) general_functions.delete_figure([fig_ss]) general_functions.delete_files_in_subfolders([temp_folder]) # Delete image return template("starshot_results", variables)
def picket_fence_helperf(args): '''This function is used in order to prevent memory problems''' temp_folder = args["temp_folder"] file_path = args["file_path"] clip_box = args["clip_box"] py_filter = args["py_filter"] num_pickets = args["num_pickets"] sag = args["sag"] mlc = args["mlc"] invert = args["invert"] orientation = args["orientation"] w = args["w"] imgdescription = args["imgdescription"] station = args["station"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] general_functions.set_configuration( args["config"]) # Transfer to this process # Chose module: if mlc in ["Varian_80", "Elekta_80", "Elekta_160"]: use_original_pylinac = "False" else: use_original_pylinac = "True" # Collect data for "save results" dicomenergy = general_functions.get_energy_from_imgdescription( imgdescription) user_machine, user_energy = general_functions.get_user_machine_and_energy( station, dicomenergy) machines_and_energies = general_functions.get_machines_and_energies( general_functions.get_treatmentunits_picketfence()) tolerances = general_functions.get_tolerance_user_machine_picketfence( user_machine) # If user_machne has specific tolerance if not tolerances: action_tolerance, tolerance, generate_pdf_report = general_functions.get_settings_picketfence( ) else: action_tolerance, tolerance, generate_pdf_report = tolerances[0] tolerance = float(tolerance) action_tol = float(action_tolerance) save_results = { "user_machine": user_machine, "user_energy": user_energy, "machines_and_energies": machines_and_energies, "displayname": displayname } # Import either original pylinac module or the modified module if use_original_pylinac == "True": from pylinac import PicketFence as PicketFence # Original pylinac analysis else: if __name__ == '__main__' or parent_module.__name__ == '__main__': from python_packages.pylinac.picketfence_modified import PicketFence as PicketFence else: from .python_packages.pylinac.picketfence_modified import PicketFence as PicketFence try: pf = PicketFence(file_path, filter=py_filter) except Exception as e: return template("error_template", { "error_message": "Module PicketFence cannot calculate. " + str(e) }) # Here we force pixels to background outside of box: if clip_box != 0: try: pf.image.check_inversion_by_histogram(percentiles=[ 4, 50, 96 ]) # Check inversion otherwise this might not work general_functions.clip_around_image(pf.image, clip_box) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) # Now invert if needed if invert: try: pf.image.invert() except Exception as e: return template( "error_template", {"error_message": "Unable to invert the image. " + str(e)}) # Now analyze try: if use_original_pylinac == "True": hdmlc = True if mlc == "Varian_120HD" else False pf.analyze(tolerance=tolerance, action_tolerance=action_tol, hdmlc=hdmlc, sag_adjustment=float(sag), num_pickets=num_pickets, orientation=orientation) else: pf.analyze(tolerance=tolerance, action_tolerance=action_tol, mlc_type=mlc, sag_adjustment=float(sag), num_pickets=num_pickets, orientation=orientation) except Exception as e: return template( "error_template", {"error_message": "Picket fence module cannot analyze. " + str(e)}) # Added an if clause to tell if num of mlc's are not the same on all pickets: num_mlcs = len(pf.pickets[0].mlc_meas) for p in pf.pickets: if len(p.mlc_meas) != num_mlcs: return template( "error_template", { "error_message": "Not all pickets have the same number of leaves. " + "Probably your image si too skewed. Rotate your collimator a bit " + "and try again. Use the jaws perpendicular to MLCs to set the right " + "collimator angle." }) error_array = np.array([]) max_error = [] max_error_leaf = [] passed_tol = [] picket_offsets = [] picket_nr = pf.num_pickets for k in pf.pickets.pickets: error_array = np.concatenate((error_array, k.error_array)) max_error.append(k.max_error) max_err_leaf_ind = np.argmax(k.error_array) max_error_leaf.append(max_err_leaf_ind) passed_tol.append("Passed" if k.passed else "Failed") picket_offsets.append(k.dist2cax) # Plot images if pf.settings.orientation == "Left-Right": fig_pf = Figure(figsize=(9, 10), tight_layout={"w_pad": 0}) else: fig_pf = Figure(figsize=(9.5, 7), tight_layout={"w_pad": 0}) img_ax = fig_pf.add_subplot(1, 1, 1) img_ax.imshow(pf.image.array, cmap=matplotlib.cm.gray, interpolation="none", aspect="equal", origin='upper') # Taken from pylinac: leaf_error_subplot: tol_line_height = [pf.settings.tolerance, pf.settings.tolerance] tol_line_width = [0, max(pf.image.shape)] # make the new axis divider = make_axes_locatable(img_ax) if pf.settings.orientation == 'Up-Down': axtop = divider.append_axes('right', 1.75, pad=0.2, sharey=img_ax) else: axtop = divider.append_axes('bottom', 1.75, pad=0.5, sharex=img_ax) # get leaf positions, errors, standard deviation, and leaf numbers pos, vals, err, leaf_nums = pf.pickets.error_hist() # Changed leaf_nums to sequential numbers: leaf_nums = list(np.arange(0, len(leaf_nums), 1)) # plot the leaf errors as a bar plot if pf.settings.orientation == 'Up-Down': axtop.barh(pos, vals, xerr=err, height=pf.pickets[0].sample_width * 2, alpha=0.4, align='center') # plot the tolerance line(s) axtop.plot(tol_line_height, tol_line_width, 'r-', linewidth=3) if pf.settings.action_tolerance is not None: tol_line_height_action = [ pf.settings.action_tolerance, pf.settings.action_tolerance ] tol_line_width_action = [0, max(pf.image.shape)] axtop.plot(tol_line_height_action, tol_line_width_action, 'y-', linewidth=3) # reset xlims to comfortably include the max error or tolerance value axtop.set_xlim([0, max(max(vals), pf.settings.tolerance) + 0.1]) else: axtop.bar(pos, vals, yerr=err, width=pf.pickets[0].sample_width * 2, alpha=0.4, align='center') axtop.plot(tol_line_width, tol_line_height, 'r-', linewidth=3) if pf.settings.action_tolerance is not None: tol_line_height_action = [ pf.settings.action_tolerance, pf.settings.action_tolerance ] tol_line_width_action = [0, max(pf.image.shape)] axtop.plot(tol_line_width_action, tol_line_height_action, 'y-', linewidth=3) axtop.set_ylim([0, max(max(vals), pf.settings.tolerance) + 0.1]) # add formatting to axis axtop.grid(True) axtop.set_title("Average Error (mm)") # add tooltips if interactive # Copied this from previous version of pylinac interactive = True if interactive: if pf.settings.orientation == 'Up-Down': labels = [[ 'Leaf pair: {0} <br> Avg Error: {1:3.3f} mm <br> Stdev: {2:3.3f} mm' .format(leaf_num, err, std) ] for leaf_num, err, std in zip(leaf_nums, vals, err)] voffset = 0 hoffset = 20 else: labels = [[ 'Leaf pair: {0}, Avg Error: {1:3.3f} mm, Stdev: {2:3.3f} mm'. format(leaf_num, err, std) ] for leaf_num, err, std in zip(leaf_nums, vals, err)] if pf.settings.orientation == 'Up-Down': for num, patch in enumerate(axtop.axes.patches): ttip = mpld3.plugins.PointHTMLTooltip(patch, labels[num], voffset=voffset, hoffset=hoffset) mpld3.plugins.connect(fig_pf, ttip) mpld3.plugins.connect(fig_pf, mpld3.plugins.MousePosition(fontsize=14)) else: for num, patch in enumerate(axtop.axes.patches): ttip = mpld3.plugins.PointLabelTooltip(patch, labels[num], location='top left') mpld3.plugins.connect(fig_pf, ttip) mpld3.plugins.connect(fig_pf, mpld3.plugins.MousePosition(fontsize=14)) for p_num, picket in enumerate(pf.pickets): picket.add_guards_to_axes(img_ax.axes) for idx, mlc_meas in enumerate(picket.mlc_meas): mlc_meas.plot2axes(img_ax.axes, width=1.5) # plot CAX img_ax.plot(pf.settings.image_center.x, pf.settings.image_center.y, 'r+', ms=12, markeredgewidth=3) # tighten up the plot view img_ax.set_xlim([0, pf.image.shape[1]]) img_ax.set_ylim([pf.image.shape[0], 0]) img_ax.axis('off') img_ax.set_xticks([]) img_ax.set_yticks([]) # Histogram of all errors and average profile plot upper_bound = pf.settings.tolerance upper_outliers = np.sum(error_array.flatten() >= upper_bound) fig_pf2 = Figure(figsize=(10, 4), tight_layout={"w_pad": 2}) ax2 = fig_pf2.add_subplot(1, 2, 1) ax3 = fig_pf2.add_subplot(1, 2, 2) n, bins = np.histogram(error_array.flatten(), density=False, bins=10, range=(0, upper_bound)) ax2.bar(bins[0:-1], n, width=np.diff(bins)[0], facecolor='green', alpha=0.75) ax2.bar([upper_bound, upper_bound * 1.1], upper_outliers, width=0.1 * upper_bound, facecolor='red', alpha=0.75) ax2.plot([pf.settings.action_tolerance, pf.settings.action_tolerance], [0, max(n) / 2], color="orange") ax2.annotate("Action Tol.", (pf.settings.action_tolerance, 1.05 * max(n) / 2), color='black', fontsize=6, ha='center', va='bottom') ax2.plot([pf.settings.tolerance, pf.settings.tolerance], [0, max(n) / 2], color="darkred") ax2.annotate("Tol.", (pf.settings.tolerance, 1.05 * max(n) / 2), color='black', fontsize=6, ha='center', va='bottom') # Plot mean inplane profile and calculate FWHM: mlc_mean_profile = pf.pickets.image_mlc_inplane_mean_profile ax3.plot(mlc_mean_profile.values, "b-") picket_fwhm = [] fwhm_mean = 0 try: peaks = mlc_mean_profile.find_peaks(max_number=picket_nr, min_distance=0.02, threshold=0.5) peaks = np.sort(peaks) ax3.plot(peaks, mlc_mean_profile[peaks], "ro") separation = int(np.mean(np.diff(peaks)) / 3) mmpd = 1 / pf.image.dpmm # Get valleys valleys = [] for p in np.arange(0, len(peaks) - 1, 1): prof_partial = mlc_mean_profile[peaks[p]:peaks[p + 1]] valleys.append(peaks[p] + np.argmin(prof_partial)) edge_points = [peaks[0] - separation ] + valleys + [peaks[-1] + separation] ax3.plot(edge_points, mlc_mean_profile[edge_points], "yo") for k in np.arange(0, len(edge_points) - 1, 1): pr = PylinacSingleProfile( mlc_mean_profile[edge_points[k]:edge_points[k + 1]]) left = pr[0] right = pr[-1] amplitude = mlc_mean_profile[peaks[k]] if left < right: x = 100 * ((amplitude - left) * 0.5 + left - right) / (amplitude - right) a = pr._penumbra_point(x=50, side="left", interpolate=True) b = pr._penumbra_point(x=x, side="right", interpolate=True) else: x = 100 * ((amplitude - right) * 0.5 + right - left) / (amplitude - left) a = pr._penumbra_point(x=x, side="left", interpolate=True) b = pr._penumbra_point(x=50, side="right", interpolate=True) left_point = edge_points[k] + a right_point = edge_points[k] + b ax3.plot([left_point, right_point], [ np.interp(left_point, np.arange(0, len(mlc_mean_profile.values), 1), mlc_mean_profile.values), np.interp(right_point, np.arange(0, len(mlc_mean_profile.values), 1), mlc_mean_profile.values) ], "-k", alpha=0.5) picket_fwhm.append(np.abs(a - b) * mmpd) fwhm_mean = np.mean(picket_fwhm) except: picket_fwhm = [np.nan] * picket_nr fwhm_mean = np.nan if len(picket_fwhm) != picket_nr: fwhm_mean = np.mean(picket_fwhm) picket_fwhm = [np.nan] * picket_nr ax2.set_xlim([-0.025, pf.settings.tolerance * 1.15]) ax3.set_xlim([0, pf.image.shape[1]]) ax2.set_title("Leaf error") ax3.set_title("MLC mean profile") ax2.set_xlabel("Error [mm]") ax2.set_ylabel("Counts") ax3.set_xlabel("Pixel") ax3.set_ylabel("Grey value") passed = "Passed" if pf.passed else "Failed" script = mpld3.fig_to_html(fig_pf, d3_url=D3_URL, mpld3_url=MPLD3_URL) script2 = mpld3.fig_to_html(fig_pf2, d3_url=D3_URL, mpld3_url=MPLD3_URL) variables = { "script": script, "script2": script2, "passed": passed, "max_error": max_error, "max_error_leaf": max_error_leaf, "passed_tol": passed_tol, "picket_nr": picket_nr, "tolerance": pf.settings.tolerance, "perc_passing": pf.percent_passing, "max_error_all": pf.max_error, "max_error_picket_all": pf.max_error_picket, "max_error_leaf_all": pf.max_error_leaf, "median_error": pf.abs_median_error, "spacing": pf.pickets.mean_spacing, "picket_offsets": picket_offsets, "fwhm_mean": fwhm_mean, "picket_fwhm": picket_fwhm, "pdf_report_enable": generate_pdf_report, "save_results": save_results, "acquisition_datetime": acquisition_datetime } # Generate pylinac report: if generate_pdf_report == "True": pdf_file = tempfile.NamedTemporaryFile(delete=False, prefix="PicketFence_", suffix=".pdf", dir=config.PDF_REPORT_FOLDER) metadata = RestToolbox.GetInstances(config.ORTHANC_URL, [w]) try: patient = metadata[0]["PatientName"] except: patient = "" try: stationname = metadata[0]["StationName"] except: stationname = "" try: date_time = RestToolbox.get_datetime(metadata[0]) date_var = datetime.datetime.strptime( date_time[0], "%Y%m%d").strftime("%d/%m/%Y") except: date_var = "" pf.publish_pdf(pdf_file, notes=[ "Date = " + date_var, "Patient = " + patient, "Station = " + stationname ]) variables["pdf_report_filename"] = os.path.basename(pdf_file.name) #gc.collect() general_functions.delete_files_in_subfolders([temp_folder]) # Delete image return template("picket_fence_results", variables)
def fieldsize_helperf(args): mlc_type = args["mlc_type"] iso_method = args["iso_method"] mlc_direction = args["mlc_direction"] mlc_points = args["mlc_points"] jaw_points = args["jaw_points"] mmpd = args["mmpd"] cax_x = args["cax_x"] cax_y = args["cax_y"] clipbox = args["clipbox"] invert = args["invert"] imgdescription = args["imgdescription"] station = args["station"] displayname = args["displayname"] acquisition_datetime = args["acquisition_datetime"] w1 = args["w1"] w2 = args["w2"] general_functions.set_configuration(args["config"]) high_contrast = args["high_contrast"] filter_size = args["filter_size"] # Collect data for "save results" dicomenergy = general_functions.get_energy_from_imgdescription( imgdescription) user_machine, user_energy = general_functions.get_user_machine_and_energy( station, dicomenergy) machines_and_energies = general_functions.get_machines_and_energies( general_functions.get_treatmentunits_fieldsize()) tolerances = general_functions.get_tolerance_user_machine_fieldsize( user_machine) # If user_machne has specific tolerance if not tolerances: tt = general_functions.get_settings_fieldsize() else: tt = tolerances[0] (small_nominal, medium_nominal, large_nominal, small_exp_mlc, medium_exp_mlc, large_exp_mlc, small_exp_jaw, medium_exp_jaw, large_exp_jaw, tolerance_small_mlc, tolerance_medium_mlc, tolerance_large_mlc, tolerance_small_jaw, tolerance_medium_jaw, tolerance_large_jaw, tolerance_iso) = tt small_exp_mlc = float(small_exp_mlc) medium_exp_mlc = float(medium_exp_mlc) large_exp_mlc = float(large_exp_mlc) small_exp_jaw = float(small_exp_jaw) medium_exp_jaw = float(medium_exp_jaw) large_exp_jaw = float(large_exp_jaw) tolerance_small_mlc = float(tolerance_small_mlc) tolerance_medium_mlc = float(tolerance_medium_mlc) tolerance_large_mlc = float(tolerance_large_mlc) tolerance_small_jaw = float(tolerance_small_jaw) tolerance_medium_jaw = float(tolerance_medium_jaw) tolerance_large_jaw = float(tolerance_large_jaw) tolerance_iso = float(tolerance_iso) save_results = { "user_machine": user_machine, "user_energy": user_energy, "machines_and_energies": machines_and_energies, "displayname": displayname, "testtype": ["MLC and Jaws", "Jaws only", "MLC only"] } try: temp_folder1, file_path1 = RestToolbox.GetSingleDcm( config.ORTHANC_URL, w1) temp_folder2, file_path2 = RestToolbox.GetSingleDcm( config.ORTHANC_URL, w2) except: return template("error_template", {"error_message": "Cannot read images."}) # Load first image try: img1 = pylinac_image.DicomImage(file_path1) # Here we force pixels to background outside of box: if clipbox != 0: try: img1.check_inversion_by_histogram(percentiles=[ 4, 50, 96 ]) # Check inversion otherwise this might not work general_functions.clip_around_image(img1, clipbox) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) else: img1.remove_edges(pixels=2) if invert: img1.invert() else: img1.check_inversion() img1.flipud() if filter_size != 0: img1.filter(filter_size) except: return template("error_template", {"error_message": "Cannot read image."}) try: img2 = pylinac_image.DicomImage(file_path2) if clipbox != 0: try: img2.check_inversion_by_histogram(percentiles=[ 4, 50, 96 ]) # Check inversion otherwise this might not work general_functions.clip_around_image(img2, clipbox) except Exception as e: return template( "error_template", {"error_message": "Unable to apply clipbox. " + str(e)}) else: img2.remove_edges(pixels=2) if invert: img2.invert() else: img2.check_inversion() img2.flipud() if filter_size != 0: img1.filter(filter_size) except: return template("error_template", {"error_message": "Cannot read image."}) # FIRST IMAGE (to get isocenter): if iso_method == "Manual": center = (float(cax_x), float(cax_y)) elif iso_method == "Plate (Elekta)": img1_copy = copy.copy( img1) # Copy because you will need the original later reg = MLC_fieldsize._get_canny_regions(img1) size = [r.area for r in reg] sort = np.argsort(size)[::-1] # Get the region with the right area: for s in range(0, len(reg), 1): if 0.9 < (reg[sort[s]].image.size / (img1.dpmm * img1.dpmm) / 31420) < 1.1: break max_area_index = sort[s] bb_box = reg[ max_area_index].bbox # (min_row, min_col, max_row, max_col) margin = 15 # pixels img1.array = img1.array[bb_box[0] + margin:bb_box[2] - margin, bb_box[1] + margin:bb_box[3] - margin] center = MLC_fieldsize._find_plate_center( img1) # This is chosen as the mechanical center!!! filter_size = 0.05 # Don't go higher! sample_length = 15 # mm to both sides from the center! sample_box = 5 # Half the number of lines per averaged profile minus one(must be odd number) hor_margin = 50 vrt_margin = 30 # Vertical profiles (two regions): samples_vertical = np.rint(center[0] - sample_length * img1.dpmm + np.arange(1, 2 * sample_length * img1.dpmm + 1, 2 * sample_box + 1)).astype(int) up_end = np.rint(center[1] - vrt_margin * img1.dpmm).astype(int) down_start = np.rint(center[1] + vrt_margin * img1.dpmm).astype(int) width_vert_up, fwhm_center_vert_up, width_vert_down, fwhm_center_vert_down = MLC_fieldsize.plate_ud_width( img1, samples_vertical, filter_size, up_end, down_start, sample_box) # Horizontal profiles (two regions): samples_horizontal = np.rint( center[1] - sample_length * img1.dpmm + np.arange(1, 2 * sample_length * img1.dpmm + 1, 2 * sample_box + 1)).astype(int) left_end = np.rint(center[0] - hor_margin * img1.dpmm).astype(int) right_start = np.rint(center[0] + hor_margin * img1.dpmm).astype(int) width_hor_left, fwhm_center_hor_left, width_hor_right, fwhm_center_hor_right = MLC_fieldsize.plate_lr_width( img1, samples_horizontal, filter_size, left_end, right_start, sample_box) vrt_px = (np.average(width_vert_up) + np.average(width_vert_down)) / 2 hor_px = (np.average(width_hor_left) + np.average(width_hor_right)) / 2 center_fwhm_x = (np.average(fwhm_center_hor_left) + right_start + np.average(fwhm_center_hor_right)) / 2 center_fwhm_y = (np.average(fwhm_center_vert_up) + down_start + np.average(fwhm_center_vert_down)) / 2 # Redefine center to the original image: center_plate = [center_fwhm_x, center_fwhm_y] center = (bb_box[1] + margin + center_plate[0], bb_box[0] + margin + center_plate[1]) elif iso_method == "BB": center, bb_box = MLC_fieldsize._find_field_centroid(img1) if high_contrast: bb_coord, bw_bb_im = MLC_fieldsize._find_bb2( img1, bb_box) # Define as mechanical isocenter else: bb_coord, bw_bb_im = MLC_fieldsize._find_bb( img1, bb_box) # Define as mechanical isocenter center = (bb_coord[0], bb_coord[1]) elif iso_method == "CAX": center, bb_box = MLC_fieldsize._find_field_centroid( img1) # Define as mechanical isocenter elif iso_method == "Opposing coll angle": # Two fields with opposing collimator angles can be used to define the coll axis center, bb_box = MLC_fieldsize._find_field_centroid( img1) # Same as any field but now: # Go to line below where center is redefined to account for image 2 CAX. # Now calculate field size from second image # Calculate mlc and jaw positions # Define pixel size. Use either that from the dcm file or calculate it via Elekta plate: if iso_method == "Plate (Elekta)": dpmm = (vrt_px / 20.0 + hor_px / 20.0) / 2 else: if mmpd != 0: dpmm = 1.0 / mmpd else: dpmm = img2.dpmm center_rad, bb_box2 = MLC_fieldsize._find_field_centroid(img2) # If Opposing coll angle is used for isocenter definition, redefine center: if iso_method == "Opposing coll angle": center[0], center[1] = (center[0] + center_rad[0]) / 2.0, ( center[1] + center_rad[1]) / 2.0 marg_bb = 10 # This additional margin is included in the caluclation of bb_box2. Subtract it when needed! nr_leaf_sample_points = int(mlc_points) nr_jaw_sample_points = int(jaw_points) leaf_scaling = dpmm mlc_points = MLC_fieldsize.sample_points_mlc(nr_leaf_sample_points, leaf_type=mlc_type) # Sample mlc-s according o mlc_points, sample jaws equidistanly with center at rad center (derived from bb_box2) if mlc_direction == "X": # MLCS are horizontal center_pixel_mlc = center[0] center_pixel_jaws = center[1] mlc_pixels = MLC_fieldsize.points_to_pixels_mlc( leaf_scaling, mlc_points, bb_box2[0] + marg_bb, bb_box2[1] - marg_bb, center_pixel_jaws) jaw_pixels = MLC_fieldsize.sample_pixels_jaws(nr_jaw_sample_points, bb_box2[2] + marg_bb, bb_box2[3] - marg_bb) else: # MLCs are vertical center_pixel_mlc = center[1] center_pixel_jaws = center[0] mlc_pixels = MLC_fieldsize.points_to_pixels_mlc( leaf_scaling, mlc_points, bb_box2[2] + marg_bb, bb_box2[3] - marg_bb, center_pixel_jaws) jaw_pixels = MLC_fieldsize.sample_pixels_jaws(nr_jaw_sample_points, bb_box2[0] + marg_bb, bb_box2[1] - marg_bb) penL, penR = MLC_fieldsize.calculate_penumbra_pixels_mlc( img2, mlc_pixels, mlc_direction) penL_abs = np.abs(penL - center_pixel_mlc) penL_abs_avg = np.average(penL_abs, axis=1) penR_abs = np.abs(penR - center_pixel_mlc) penR_abs_avg = np.average(penR_abs, axis=1) widths_mlc = np.abs(penL - penR) widths_mlc_avg = np.average(widths_mlc, axis=1) penL_jaw, penR_jaw = MLC_fieldsize.calculate_penumbra_pixels_jaws( img2, jaw_pixels, mlc_direction) penL_jaw_abs = np.abs(penL_jaw - center_pixel_jaws) penR_jaw_abs = np.abs(penR_jaw - center_pixel_jaws) widths_jaw = np.abs(penL_jaw - penR_jaw) # Skewness of the rectangle (a measure of collimator angle): # linear regression of mlc/jaw points (except the first and the last) y=mx+c first_leaf = np.rint( np.searchsorted((mlc_pixels - center_pixel_jaws).flatten(), 0) / mlc_pixels.shape[1]).astype(int) leaf_numbers = np.hstack( (-np.arange(1, first_leaf + 1, 1)[::-1], np.arange(1, mlc_pixels.shape[0] - first_leaf + 1, 1))) temp_mlc = np.vstack([ (mlc_pixels[1:-1, :].flatten() - center_pixel_mlc) / dpmm, np.ones(len(mlc_pixels[1:-1, :].flatten())) ]).T m_mlcL, c_mlcL = np.linalg.lstsq(temp_mlc, penL_abs[1:-1, :].flatten() / dpmm, rcond=None)[0] m_mlcR, c_mlcR = np.linalg.lstsq(temp_mlc, penR_abs[1:-1, :].flatten() / dpmm, rcond=None)[0] temp_jaws = np.vstack([(jaw_pixels[1:-1] - center_pixel_jaws) / dpmm, np.ones(len(jaw_pixels[1:-1]))]).T m_jawL, c_jawL = np.linalg.lstsq(temp_jaws, penL_jaw_abs[1:-1] / dpmm, rcond=None)[0] m_jawR, c_jawR = np.linalg.lstsq(temp_jaws, penR_jaw_abs[1:-1] / dpmm, rcond=None)[0] # Not do some heavy plotting size = 7 fig1 = Figure(figsize=(size, size), tight_layout={"w_pad": 1, "pad": 1}) ax1 = fig1.add_subplot(1, 1, 1) if iso_method == "BB": ax1.imshow(img1.array, cmap=matplotlib.cm.Greys, interpolation="none", origin="lower", extent=[0, img1.shape[1], 0, img1.shape[0]]) ax1.plot(center[0], center[1], 'b+', markersize=24, markeredgewidth=3, zorder=2) border = np.average(np.percentile(bw_bb_im, [5, 99.9])) ax1.contour(bw_bb_im, levels=[border], colors=["red"]) # BB ax1.set_ylim(0, img1.shape[0]) ax1.set_xlim(0, img1.shape[1]) ax1.autoscale(enable=False) if iso_method == "Plate (Elekta)": ax1.imshow(img1_copy.array, cmap=matplotlib.cm.prism_r, interpolation="none", origin="lower", extent=[0, img1_copy.shape[1], 0, img1_copy.shape[0]]) ax1.plot(center[0], center[1], 'b+', markersize=24, markeredgewidth=3, zorder=2) ax1.plot([left_end + center[0] - center_plate[0]] * len(samples_horizontal), samples_horizontal + center[1] - center_plate[1], 'wo', markersize=3, markeredgewidth=0, zorder=2) ax1.plot([right_start + center[0] - center_plate[0]] * len(samples_horizontal), samples_horizontal + center[1] - center_plate[1], 'wo', markersize=3, markeredgewidth=0, zorder=2) ax1.plot(samples_vertical + center[0] - center_plate[0], [up_end + center[1] - center_plate[1]] * len(samples_vertical), 'wo', markersize=3, markeredgewidth=0, zorder=2) ax1.plot(samples_vertical + center[0] - center_plate[0], [down_start + center[1] - center_plate[1]] * len(samples_vertical), 'wo', markersize=3, markeredgewidth=0, zorder=2) ax1.set_ylim(bb_box[0] + margin, bb_box[2] - margin) ax1.set_xlim(bb_box[1] + margin, bb_box[3] - margin) ax1.autoscale(enable=False) if iso_method == "Manual" or iso_method == "CAX" or iso_method == "Opposing coll angle": ax1.imshow(img1.array, cmap=matplotlib.cm.prism_r, interpolation="none", origin="lower", extent=[0, img1.shape[1], 0, img1.shape[0]]) ax1.plot(center[0], center[1], 'b+', markersize=24, markeredgewidth=3, zorder=2) ax1.set_ylim(0, img1.shape[0]) ax1.set_xlim(0, img1.shape[1]) ax1.autoscale(enable=False) mpld3.plugins.connect(fig1, mpld3.plugins.MousePosition(fontsize=14, fmt=".1f")) script1 = mpld3.fig_to_html(fig1, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Second plot fig2 = Figure(figsize=(size, size), tight_layout={"w_pad": 1, "pad": 1}) ax2 = fig2.add_subplot(1, 1, 1) ax2.imshow(img2.array, cmap=matplotlib.cm.Greys, interpolation="none", origin="lower", extent=[0, img2.shape[1], 0, img2.shape[0]]) ax2.plot(center[0], center[1], marker='+', color="dodgerblue", markersize=24, markeredgewidth=3) level = np.average(np.percentile(img2.array, [5, 99.9])) ax2.contour(img2.array, levels=[level], colors=["magenta"], linewidths=1, alpha=0.7, zorder=1) ax2.plot(center_rad[0], center_rad[1], 'm+', markersize=24, markeredgewidth=3, zorder=2) ax2.plot([None], [None], marker='+', color="dodgerblue", ms=15, mew=3, label="Mechanical") ax2.plot([None], [None], "m+", ms=15, mew=3, label="Radiation") ax2.plot([0, img2.shape[1]], [center[1], center[1]], linestyle="--", color="dodgerblue", alpha=0.5) ax2.plot([center[0], center[0]], [0, img2.shape[0]], linestyle="--", color="dodgerblue", alpha=0.5) ax2.legend(framealpha=0, numpoints=1, ncol=2, loc='lower left', fontsize=8) m1s = 5 ms2 = 6 if mlc_direction == "X": m1 = ax2.plot(penL.flatten(), mlc_pixels.flatten(), 'ro', markersize=m1s, markeredgewidth=0, zorder=2) m2 = ax2.plot(penR.flatten(), mlc_pixels.flatten(), 'bo', markersize=m1s, markeredgewidth=0, zorder=2) j1 = ax2.plot(jaw_pixels, penL_jaw, 'go', markersize=ms2, markeredgewidth=0, zorder=2) j2 = ax2.plot(jaw_pixels, penR_jaw, 'yo', markersize=ms2, markeredgewidth=0, zorder=2) #ax2.plot(np.average((penL+penR)/2), np.average((penL_jaw+penR_jaw)/2), 'y+', markersize=24, markeredgewidth=3, zorder=2) else: m1 = ax2.plot(mlc_pixels.flatten(), penL.flatten(), 'ro', markersize=m1s, markeredgewidth=0, zorder=2) m2 = ax2.plot(mlc_pixels.flatten(), penR.flatten(), 'bo', markersize=m1s, markeredgewidth=0, zorder=2) j1 = ax2.plot(penL_jaw, jaw_pixels, 'go', markersize=ms2, markeredgewidth=0, zorder=2) j2 = ax2.plot(penR_jaw, jaw_pixels, 'yo', markersize=ms2, markeredgewidth=0, zorder=2) #ax2.plot(np.average((penL_jaw+penR_jaw)/2), np.average((penL+penR)/2), 'y+', markersize=24, markeredgewidth=3, zorder=2) labels_m1 = [ "Distance from center = {:04.2f} mm, width = {:04.2f} mm".format( penL_abs.flatten()[k] / dpmm, widths_mlc.flatten()[k] / dpmm) for k in range(0, len(penL_abs.flatten()), 1) ] labels_m2 = [ "Distance from center = {:04.2f} mm, width = {:04.2f} mm".format( penR_abs.flatten()[k] / dpmm, widths_mlc.flatten()[k] / dpmm) for k in range(0, len(penR_abs.flatten()), 1) ] labels_j1 = [ "Distance from center = {:04.2f} mm, width = {:04.2f} mm".format( penL_jaw_abs[k] / dpmm, widths_jaw[k] / dpmm) for k in range(0, len(penL_jaw_abs), 1) ] labels_j2 = [ "Distance from center = {:04.2f} mm, width = {:04.2f} mm".format( penR_jaw_abs[k] / dpmm, widths_jaw[k] / dpmm) for k in range(0, len(penR_jaw_abs), 1) ] ttip1 = mpld3.plugins.PointLabelTooltip(m1[0], labels_m1, location='top left') ttip2 = mpld3.plugins.PointLabelTooltip(m2[0], labels_m2, location='top left') ttip3 = mpld3.plugins.PointLabelTooltip(j1[0], labels_j1, location='top left') ttip4 = mpld3.plugins.PointLabelTooltip(j2[0], labels_j2, location='top left') margin_imshow = 35 ax2.set_ylim(bb_box2[0] - margin_imshow, bb_box2[1] + margin_imshow) ax2.set_xlim(bb_box2[2] - margin_imshow, bb_box2[3] + margin_imshow) ax2.autoscale(enable=False) mpld3.plugins.connect(fig2, mpld3.plugins.MousePosition(fontsize=14, fmt=".1f")) mpld3.plugins.connect(fig2, ttip1) mpld3.plugins.connect(fig2, ttip2) mpld3.plugins.connect(fig2, ttip3) mpld3.plugins.connect(fig2, ttip4) script2 = mpld3.fig_to_html(fig2, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Third plot fig3 = Figure(figsize=(10, 9), tight_layout={"w_pad": 2, "pad": 2}) ax3 = fig3.add_subplot(3, 1, 1) ax4 = fig3.add_subplot(3, 1, 2) ax5 = fig3.add_subplot(3, 1, 3) ax3.plot(np.arange(0, len(leaf_numbers), 1), penR_abs_avg / dpmm, "bo-", linewidth=0.8) ax3.set_xticks(np.arange(0, len(leaf_numbers), 1)) ax3.set_xticklabels([]) ax3.grid(linestyle='dotted', color="gray") ax3.set_title("Right leaf distance from center [mm]") ax3.margins(0.05) ax4.plot(np.arange(0, len(leaf_numbers), 1), -penL_abs_avg / dpmm, "ro-", linewidth=0.8) ax4.set_xticks(np.arange(0, len(leaf_numbers), 1)) ax4.grid(linestyle='dotted', color="gray") ax4.set_xticklabels([]) ax4.set_title("Left leaf distance from center [mm]") ax4.margins(0.05) ax5.plot(np.arange(0, len(leaf_numbers), 1), widths_mlc_avg / dpmm, "ko-", linewidth=0.8) ax5.set_xticks(np.arange(0, len(leaf_numbers), 1)) ax5.set_xticklabels(leaf_numbers) ax5.grid(linestyle='dotted', color="gray") ax5.set_xlabel("Leaf index") ax5.set_title("Distance between leaves [mm]") ax5.margins(0.05) script3 = mpld3.fig_to_html(fig3, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Fourth plot fig4 = Figure(figsize=(10, 9), tight_layout={"w_pad": 2, "pad": 2}) ax6 = fig4.add_subplot(3, 1, 1) ax7 = fig4.add_subplot(3, 1, 2) ax8 = fig4.add_subplot(3, 1, 3) ax6.plot(np.arange(0, len(penR_jaw_abs), 1), penR_jaw_abs / dpmm, "yo-", linewidth=0.8) ax6.set_xticks(np.arange(0, len(penR_jaw_abs), 1)) ax6.set_xticklabels([]) ax6.grid(linestyle='dotted', color="gray") ax6.set_title("Right jaw distance from center [mm]") ax6.margins(0.05) ax7.plot(np.arange(0, len(penL_jaw_abs), 1), -penL_jaw_abs / dpmm, "go-", linewidth=0.8) ax7.set_xticks(np.arange(0, len(penL_jaw_abs), 1)) ax7.grid(linestyle='dotted', color="gray") ax7.set_xticklabels([]) ax7.set_title("Left jaw distance from center [mm]") ax7.margins(0.05) ax8.plot(np.arange(0, len(widths_jaw), 1), widths_jaw / dpmm, "ko-", linewidth=0.8) ax8.set_xticks(np.arange(0, len(widths_jaw), 1)) ax8.set_xticklabels(np.arange(1, len(widths_jaw) + 1, 1)) ax8.grid(linestyle='dotted', color="gray") ax8.set_xlabel("Jaw sample point") ax8.set_title("Distance between jaws [mm]") ax8.margins(0.05) script4 = mpld3.fig_to_html(fig4, d3_url=D3_URL, mpld3_url=MPLD3_URL) # Caclculate stuff for the web interface MLC_size_full = np.average(widths_mlc_avg[1:-1] / dpmm) jaw_size_full = np.average(widths_jaw[1:-1] / dpmm) #sizes_nominal = np.array([small_nominal, medium_nominal, large_nominal]) size_nominal_names = ["Small field", "Medium field", "Large field"] sizes_exp_mlc = np.array([small_exp_mlc, medium_exp_mlc, large_exp_mlc ]) * 10.0 sizes_exp_jaw = np.array([small_exp_jaw, medium_exp_jaw, large_exp_jaw ]) * 10.0 tolerances_list_mlc = np.array([ tolerance_small_mlc, tolerance_medium_mlc, tolerance_large_mlc ]) * 10.0 tolerances_list_jaw = np.array([ tolerance_small_jaw, tolerance_medium_jaw, tolerance_large_jaw ]) * 10.0 # Guess which field was chosen guess_ind_mlc = np.argmin(np.abs(sizes_exp_mlc - MLC_size_full)) guess_ind_jaw = np.argmin(np.abs(sizes_exp_jaw - jaw_size_full)) guessed_exp_mlc = sizes_exp_mlc[guess_ind_mlc] guessed_exp_jaw = sizes_exp_jaw[guess_ind_jaw] guessed_nominal_name = size_nominal_names[guess_ind_mlc] tolerance_final_mlc = tolerances_list_mlc[guess_ind_mlc] tolerance_final_jaw = tolerances_list_jaw[guess_ind_jaw] tolerance_iso = tolerance_iso * 10.0 # Redefine to mm if abs(guessed_exp_mlc - MLC_size_full) <= tolerance_final_mlc: passed_mlc = True else: passed_mlc = False if abs(guessed_exp_jaw - jaw_size_full) <= tolerance_final_jaw: passed_jaw = True else: passed_jaw = False if (center_rad[0] - center[0])**2 + ( center_rad[1] - center[1])**2 <= dpmm * dpmm * tolerance_iso**2: passed_iso = True else: passed_iso = False variables = { "script1": script1, "script2": script2, "script3": script3, "script4": script4, "MLC_position_L": penL_abs_avg / dpmm, "MLC_position_R": penR_abs_avg / dpmm, "MLC_width": widths_mlc_avg / dpmm, "jaw_position_L": penL_jaw_abs / dpmm, "jaw_position_R": penR_jaw_abs / dpmm, "jaw_width": widths_jaw / dpmm, "leaf_numbers": leaf_numbers, "angle_mlc_L": np.arctan(m_mlcL) * 180 / PI, "angle_mlc_R": -np.arctan(m_mlcR) * 180 / PI, # changed sign! "angle_jaw_L": -np.arctan(m_jawL) * 180 / PI, # changed sign! "angle_jaw_R": np.arctan(m_jawR) * 180 / PI, "MLC_size_L": np.average(penL_abs_avg[1:-1] / dpmm), "MLC_size_R": np.average(penR_abs_avg[1:-1] / dpmm), "MLC_size_full": MLC_size_full, "jaw_size_L": np.average(penL_jaw_abs[1:-1] / dpmm), "jaw_size_R": np.average(penR_jaw_abs[1:-1] / dpmm), "jaw_size_full": jaw_size_full, "center_offset_x": (center_rad[0] - center[0]) / dpmm, "center_offset_y": (center_rad[1] - center[1]) / dpmm, "dpmm": 1 / dpmm, "center": center, "mlc_direction": mlc_direction, "center_rad": center_rad, "save_results": save_results, "passed_mlc": passed_mlc, "passed_jaw": passed_jaw, "passed_iso": passed_iso, "tolerance_mlc": tolerance_final_mlc, "tolerance_jaw": tolerance_final_jaw, "tolerance_iso": tolerance_iso, "expected_mlc": guessed_exp_mlc, "expected_jaw": guessed_exp_jaw, "guessed_fieldsize": guessed_nominal_name, "acquisition_datetime": acquisition_datetime, "iso_method": iso_method } general_functions.delete_files_in_subfolders([temp_folder1, temp_folder2 ]) # Delete image return template("fieldsize_results", variables)
def image_review_helperf(args): converthu = args["converthu"] invert = args["invert"] calculate_histogram = args["calculate_histogram"] colormap = args["colormap"] w = args["w"] general_functions.set_configuration(args["config"]) temp_folder, file_path = RestToolbox.GetSingleDcm(config.ORTHANC_URL, w) try: img = pylinac_image.DicomImage(file_path) if invert: img.invert() if converthu: # Convert back to pixel values if needed. img.array = (img.array - int(img.metadata.RescaleIntercept)) / int(img.metadata.RescaleSlope) img_array = np.flipud(img.array) except: return template("error_template", {"error_message": "Cannot read image."}) size_x = img_array.shape[1] size_y = img_array.shape[0] img_min = np.min(img_array) img_max = np.max(img_array) x1 = np.arange(0, size_x, 1).tolist() y1 = img_array[int(size_y//2), :].tolist() y2 = np.arange(0, size_y, 1).tolist() x2 = img_array[:, int(size_x//2)].tolist() source = ColumnDataSource(data=dict(x1=x1, y1=y1)) # Bottom plot source2 = ColumnDataSource(data=dict(x2=x2, y2=y2)) # Right plot sourcebp = ColumnDataSource(data=dict(xbp=[], ybp=[])) sourcerp = ColumnDataSource(data=dict(xrp=[], yrp=[])) # Add table for pixel position and pixel value hfmt = NumberFormatter(format="0.00") source6 = ColumnDataSource(dict( x=[], y=[], value=[] )) columns2 = [TableColumn(field="x", title="x", formatter=hfmt), TableColumn(field="y", title="y", formatter=hfmt), TableColumn(field="value", title="value", formatter=hfmt)] table2 = DataTable(source=source6, columns=columns2, editable=False, height=50, width = 220) # Plotting profiles callback = CustomJS(args=dict(source=source, source2=source2, sourcebp=sourcebp, sourcerp=sourcerp, source6=source6), code=""" var geometry = cb_data['geometry']; var x_data = parseInt(geometry.x); // current mouse x position in plot coordinates var y_data = parseInt(geometry.y); // current mouse y position in plot coordinates var data = source.data; var data2 = source2.data; var array = """+json.dumps(img_array.tolist())+"""; data['y1'] = array[y_data]; var column = []; for(var i=0; i < array.length; i++){ column.push(array[i][x_data]); } data2['x2'] = column; source.change.emit(); source2.change.emit(); var length_x = array[0].length; var length_y = array.length; if ((x_data<=length_x && x_data>=0) && (y_data<=length_y && y_data>=0)){ // Add points to plot: sourcebp.data['xbp'] = [x_data]; sourcebp.data['ybp'] = [array[y_data][x_data]]; sourcerp.data['xrp'] = [array[y_data][x_data]]; sourcerp.data['yrp'] = [y_data]; // Get position and pixel value for table source6.data["x"] = [geometry.x]; source6.data["y"] = [geometry.y]; source6.data["value"] = [array[y_data][x_data]]; sourcebp.change.emit(); sourcerp.change.emit(); source6.change.emit(); } """) # Add callback for calculating average value inside Rectangular ROI x3 = np.arange(0, size_x, 1).tolist() y3 = np.arange(0, size_y, 1).tolist() source3 = ColumnDataSource(data=dict(x3=x3, y3=y3)) source4 = ColumnDataSource(data=dict(x4=[], y4=[], width4=[], height4=[])) # Add table for mean and std source5 = ColumnDataSource(dict( mean=[], median=[], std=[], minn=[], maxx=[] )) columns = [TableColumn(field="mean", title="mean", formatter=hfmt), TableColumn(field="median", title="median", formatter=hfmt), TableColumn(field="std", title="std", formatter=hfmt), TableColumn(field="minn", title="min", formatter=hfmt), TableColumn(field="maxx", title="max", formatter=hfmt)] table = DataTable(source=source5, columns=columns, editable=False, height=50, width = 480) # Calculate things within ROI callback2 = CustomJS(args=dict(source3=source3, source4=source4, source5=source5), code=""" var geometry = cb_obj['geometry']; var data3 = source3.data; var data4 = source4.data; var data5 = source5.data; // Get data var x0 = parseInt(geometry['x0']); var x1 = parseInt(geometry['x1']); var y0 = parseInt(geometry['y0']); var y1 = parseInt(geometry['y1']); // calculate Rect attributes var width = x1 - x0; var height = y1 - y0; var x = x0 + width/2; var y = y0 + height/2; // update data source with new Rect attributes data4['x4'] = [x]; data4['y4'] = [y]; data4['width4'] = [width]; data4['height4'] = [height]; // Get average value inside ROI var array = """+json.dumps(img_array.tolist())+"""; var length_x = array[0].length; var length_y = array.length; if ((x0<=length_x && x0>=0) && (x1<=length_x && x1>=0) && (y0<=length_y && y0>=0) && (y1<=length_y && y1>=0)){ var avg_ROI = []; for (var i=y0; i< y1; i++){ for (var j=x0; j<x1; j++){ avg_ROI.push(array[i][j]); } } if (avg_ROI == undefined || avg_ROI.length==0){ data5["mean"] = [0]; data5["median"] = [0]; data5["std"] = [0]; } else{ data5["mean"] = [math.mean(avg_ROI)]; data5["median"] = [math.median(avg_ROI)]; data5["std"] = [math.std(avg_ROI, 'uncorrected')]; data5["maxx"] = [math.max(avg_ROI)]; data5["minn"] = [math.min(avg_ROI)]; } source4.change.emit(); source5.change.emit(); } """) plot_width = 500 plot_height = int((plot_width-20)*size_y/size_x) fig = figure(x_range=[0, size_x], y_range=[0, size_y], plot_width=plot_width, plot_height=plot_height, title="", toolbar_location="right", tools=["crosshair, wheel_zoom, pan, reset"]) fig_r = figure(x_range=[img_min, img_max], y_range=fig.y_range, plot_width=250, plot_height=plot_height, title="", toolbar_location="right", tools=[]) fig_b = figure(x_range=fig.x_range, y_range=[img_min, img_max], plot_width=plot_width, plot_height=200, title="", toolbar_location="right", tools=[]) # Define matplotlib palette and make it possible to be used dynamically. cmap = matplotlib.cm.get_cmap(colormap) #chose any matplotlib colormap here bokehpalette = [matplotlib.colors.rgb2hex(m) for m in cmap(np.arange(cmap.N)[::-1])] # Reversed direction of colormap mapper = LinearColorMapper(palette=bokehpalette, low=np.min(img_array), high=np.max(img_array)) callback3 = CustomJS(args=dict(mapper=mapper, x_range=fig_r.x_range, y_range=fig_b.y_range), code=""" mapper.palette = """+json.dumps(bokehpalette)+"""; var start = cb_obj.value[0]; var end = cb_obj.value[1]; mapper.low = start; mapper.high = end; x_range.setv({"start": start, "end": end}); y_range.setv({"start": start, "end": end}); //mapper.change.emit(); """) range_slider = RangeSlider(start=np.min(img_array), end=np.max(img_array), value=(np.min(img_array), np.max(img_array)), step=10, title="Level") range_slider.js_on_change('value', callback3) fig.image([img_array], x=0, y=0, dw=size_x, dh=size_y, color_mapper=mapper) fig_b.line(x='x1', y='y1', source=source) fig_r.line(x='x2', y='y2', source=source2) fig_b.circle(x='xbp', y='ybp', source=sourcebp, size=7, fill_color="red", fill_alpha=0.5, line_color=None) # For point connected to crosshair fig_r.circle(x='xrp', y='yrp', source=sourcerp, size=7, fill_color="red", fill_alpha=0.5, line_color=None) # For point connected to crosshair fig.rect(x='x4', y='y4', width='width4', height='height4', source=source4, fill_alpha=0.3, fill_color='#009933') fig.add_tools(HoverTool(tooltips=None, callback=callback)) fig.add_tools(BoxSelectTool()) fig.js_on_event(SelectionGeometry, callback2) # Add event_callback to Boxselecttool grid = gridplot([[table, range_slider], [fig, fig_r], [fig_b, table2]]) script, div = components(grid) # Calculate pixel value histogram if calculate_histogram: try: bitsstored = int(img.metadata.BitsStored) except: bitsstored = 16 max_pixelvalue = 2**bitsstored-1 counts, bins = np.histogram(img_array.flatten(), density=False, range=(0, max_pixelvalue), bins=max_pixelvalue+1) fig_hist = figure(x_range = [-100, max_pixelvalue*1.05], y_range=[0.5, np.max(counts)], plot_width=750, plot_height=400, title="Pixel value histogram", y_axis_type="log", toolbar_location="right", tools=["box_zoom, pan, reset"]) fig_hist.quad(top=counts, bottom=0.5, left=bins[:-1], right=bins[1:], alpha=0.5) fig_hist.grid.visible = False fig_hist.yaxis.axis_label = "Pixel Count" fig_hist.xaxis.axis_label = "Pixel Value" script_hist, div_hist = components(fig_hist) else: script_hist, div_hist = ["", ""] variables = {"script": script, "div": div, "bokeh_file_css": BOKEH_FILE_CSS, "bokeh_file_js": BOKEH_FILE_JS, "bokeh_widgets_js": BOKEH_WIDGETS_JS, "bokeh_tables_js": BOKEH_TABLES_JS, "script_hist": script_hist, "div_hist": div_hist } #gc.collect() general_functions.delete_files_in_subfolders([temp_folder]) # Delete image return template("image_review_results", variables)