def phantom_angle(self): """Determine the angle of the phantom. This is done by searching for square-like boxes of the canny image. There must be two: one lead and one copper. The angle from the lead to the copper ROI centers is determined (modified from original, which used the phantom center, and was not as stable) Returns ------- angle : float The angle in radians. """ if self._phantom_angle is not None: return self._phantom_angle expected_length = self.phantom_radius * 0.52 regions = self._regions square_rois = [ roi for roi in self._blobs if np.isclose(self._regions[roi].major_axis_length, expected_length, rtol=0.5) ] if len(square_rois) == 2: idx_sort = np.argsort( [regions[roi].mean_intensity for roi in square_rois]) lead_idx = idx_sort[1] cu_idx = idx_sort[0] lead_roi = regions[square_rois[lead_idx]] lead_center = bbox_center(lead_roi) cu_roi = regions[square_rois[cu_idx]] cu_center = bbox_center(cu_roi) adjacent = lead_center.x - cu_center.x opposite = lead_center.y - cu_center.y angle = np.arctan2(opposite, adjacent) return angle elif len(square_rois) == 1: lead_idx = np.argsort( [regions[roi].mean_intensity for roi in square_rois])[-1] lead_roi = regions[square_rois[lead_idx]] lead_center = bbox_center(lead_roi) adjacent = lead_center.x - self.phantom_center.x opposite = lead_center.y - self.phantom_center.y angle = np.arctan2(opposite, adjacent) return angle else: raise ValueError("Could not find the Phantom Angle")
def _phantom_center_calc(self) -> Point: """The center point of the phantom. Returns ------- center : Point """ return bbox_center(self.phantom_ski_region)
def _phantom_center_calc(self) -> Point: """Determine the phantom center. This is done by searching for circular ROIs of the canny image. Those that are circular and roughly the same size as the biggest circle ROI are all sampled for the center of the bounding box. The values are averaged over all the detected circles to give a more robust value. Returns ------- center : Point """ if self._phantom_center is not None: return self._phantom_center circles = [roi for roi in self._blobs if np.isclose(self._regions[roi].major_axis_length, self.phantom_radius * 3.35, rtol=0.3)] # get average center of all circles circle_rois = [self._regions[roi] for roi in circles] y = np.mean([bbox_center(roi).y for roi in circle_rois]) x = np.mean([bbox_center(roi).x for roi in circle_rois]) return Point(x, y)
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 _phantom_center_calc(self) -> Point: return bbox_center(self._phantom_ski_region_calc())