示例#1
0
 def _calc_symmetry(self, method: str, vert_position: float,
                    horiz_position: float):
     vert_profile = SingleProfile(
         self.image.
         array[:, int(round(self.image.array.shape[1] * vert_position))])
     horiz_profile = SingleProfile(self.image.array[
         int(round(self.image.array.shape[0] * horiz_position)), :])
     # calc sym from profile
     symmetry_calculation = SYMMETRY_EQUATIONS[method.lower()]
     vert_sym, vert_sym_array, vert_lt, vert_rt = symmetry_calculation(
         vert_profile)
     horiz_sym, horiz_sym_array, horiz_lt, horiz_rt = symmetry_calculation(
         horiz_profile)
     return {
         'method': method,
         'horizontal': {
             'profile': horiz_profile,
             'value': horiz_sym,
             'array': horiz_sym_array,
             'profile left': horiz_lt,
             'profile right': horiz_rt,
         },
         'vertical': {
             'profile': vert_profile,
             'value': vert_sym,
             'array': vert_sym_array,
             'profile left': vert_lt,
             'profile right': vert_rt,
         },
     }
示例#2
0
    def test_interpolation(self):
        # centered field
        field = generate_open_field()
        # no interp
        p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.NONE)
        self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)]))

        # linear interp
        p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.LINEAR, interpolation_factor=10)
        self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*10)

        p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.LINEAR,
                          dpmm=1/field.pixel_size, interpolation_resolution_mm=0.1)
        # right length
        self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*field.pixel_size/0.1)
        # right dpmm
        self.assertEqual(p.dpmm, 10)

        # spline interp
        p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.SPLINE, interpolation_factor=10)
        self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*10)

        p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.SPLINE,
                          dpmm=1/field.pixel_size, interpolation_resolution_mm=0.1)
        # right length
        self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*field.pixel_size/0.1)
        # right dpmm
        self.assertEqual(p.dpmm, 10)
示例#3
0
 def _calc_flatness(self, method: str, vert_position: float,
                    horiz_position: float):
     vert_profile = SingleProfile(
         self.image.
         array[:, int(round(self.image.array.shape[1] * vert_position))])
     horiz_profile = SingleProfile(self.image.array[
         int(round(self.image.array.shape[0] * horiz_position)), :])
     # calc flatness from profile
     flatness_calculation = FLATNESS_EQUATIONS[method.lower()]
     vert_flatness, vert_max, vert_min, vert_lt, vert_rt = flatness_calculation(
         vert_profile)
     horiz_flatness, horiz_max, horiz_min, horiz_lt, horiz_rt = flatness_calculation(
         horiz_profile)
     return {
         'method': method,
         'horizontal': {
             'value': horiz_flatness,
             'profile': horiz_profile,
             'profile max': horiz_max,
             'profile min': horiz_min,
             'profile left': horiz_lt,
             'profile right': horiz_rt,
         },
         'vertical': {
             'value': vert_flatness,
             'profile': vert_profile,
             'profile max': vert_max,
             'profile min': vert_min,
             'profile left': vert_lt,
             'profile right': vert_rt,
         },
     }
示例#4
0
    def _get_reasonable_start_point(self):
        """Set the algorithm starting point automatically.

        Notes
        -----
        The determination of an automatic start point is accomplished by finding the Full-Width-80%-Max.
        Finding the maximum pixel does not consistently work, esp. in the presence of a pin prick. The
        FW80M is a more consistent metric for finding a good start point.
        """
        # sum the image along each axis within the central 1/3 (avoids outlier influence from say, gantry shots)
        top_third = int(self.image.array.shape[0] / 3)
        bottom_third = int(top_third * 2)
        left_third = int(self.image.array.shape[1] / 3)
        right_third = int(left_third * 2)
        central_array = self.image.array[top_third:bottom_third,
                                         left_third:right_third]

        x_sum = np.sum(central_array, 0)
        y_sum = np.sum(central_array, 1)

        # Calculate Full-Width, 80% Maximum center
        fwxm_x_point = SingleProfile(x_sum).fwxm_center(80) + left_third
        fwxm_y_point = SingleProfile(y_sum).fwxm_center(80) + top_third
        center_point = Point(fwxm_x_point, fwxm_y_point)
        return center_point
示例#5
0
    def _find_bb(self):
        """Find the BB within the radiation field. Iteratively searches for a circle-like object
        by lowering a low-pass threshold value until found.

        Returns
        -------
        Point
            The weighted-pixel value location of the BB.
        """

        def is_boxlike(array):
            """Whether the binary object's dimensions are symmetric, i.e. box-like"""
            ymin, ymax, xmin, xmax = get_bounding_box(array)
            y = abs(ymax - ymin)
            x = abs(xmax - xmin)
            if x > max(y * 1.05, y+3) or x < min(y * 0.95, y-3):
                return False
            return True

        # get initial starting conditions
        hmin = np.percentile(self.array, 5)
        hmax = self.array.max()
        spread = hmax - hmin
        max_thresh = hmax

        # search for the BB by iteratively lowering the low-pass threshold value until the BB is found.
        found = False
        while not found:
            try:
                lower_thresh = hmax - spread / 2
                t = np.where((max_thresh > self) & (self >= lower_thresh), 1, 0)
                labeled_arr, num_roi = ndimage.measurements.label(t)
                roi_sizes, bin_edges = np.histogram(labeled_arr, bins=num_roi + 1)
                bw_node_cleaned = np.where(labeled_arr == np.argsort(roi_sizes)[-3], 1, 0)
                expected_fill_ratio = np.pi / 4
                actual_fill_ratio = get_filled_area_ratio(bw_node_cleaned)
                if (expected_fill_ratio * 1.1 < actual_fill_ratio) or (actual_fill_ratio < expected_fill_ratio * 0.9):
                    raise ValueError
                if not is_boxlike(bw_node_cleaned):
                    raise ValueError
            except (IndexError, ValueError):
                max_thresh -= 0.05 * spread
                if max_thresh < hmin:
                    raise ValueError("Unable to locate the BB")
            else:
                found = True

        # determine the center of mass of the BB
        inv_img = Image.load(self.array)
        inv_img.invert()
        x_arr = np.abs(np.average(bw_node_cleaned, weights=inv_img, axis=0))
        x_com = SingleProfile(x_arr).fwxm_center(interpolate=True)
        y_arr = np.abs(np.average(bw_node_cleaned, weights=inv_img, axis=1))
        y_com = SingleProfile(y_arr).fwxm_center(interpolate=True)
        return Point(x_com, y_com)
示例#6
0
 def find_mlc_peak(self, mlc_center):
     """Determine the center of the picket."""
     mlc_rows = np.arange(mlc_center - self.sample_width, mlc_center + self.sample_width + 1)
     if self.settings.orientation == orientations['UD']:
         pix_vals = np.median(self.picket_array[mlc_rows, :], axis=0)
     else:
         pix_vals = np.median(self.picket_array[:, mlc_rows], axis=1)
     if max(pix_vals) > np.percentile(self.picket_array, 80):
         prof = SingleProfile(pix_vals)
         fw80mc = prof.get_FWXM_center(70, interpolate=True)
         return fw80mc + self.approximate_idx - self.spacing
示例#7
0
 def _determine_measured_gap(profile: np.ndarray) -> float:
     """Return the measured gap based on profile height"""
     mid_value = profile[int(len(profile) / 2)]
     prof = SingleProfile(profile, normalization_method=Normalization.NONE)
     if mid_value < profile.mean():
         prof.invert()
     _, props = find_peaks(prof.values, max_number=1)
     if mid_value < profile.mean():
         return -props['prominences'][0]
     else:
         return props['prominences'][0]
示例#8
0
def flatness_elekta(profile: SingleProfile):
    """The Elekta specification for calculating flatness"""
    try:
        dmax = profile.field_calculation(field_width=0.8, calculation='max')
        dmin = profile.field_calculation(field_width=0.8, calculation='min')
    except ValueError:
        raise ValueError(
            "An error was encountered in the flatness calculation. The image is likely inverted. Try inverting the image before analysis with <instance>.image.invert()."
        )
    flatness = 100 * (dmax / dmin)
    lt_edge, rt_edge = profile.field_edges()
    return flatness, dmax, dmin, lt_edge, rt_edge
示例#9
0
 def find_mlc_peak(self, mlc_center):
     """Determine the center of the picket."""
     mlc_rows = np.arange(mlc_center - self.sample_width,
                          mlc_center + self.sample_width + 1)
     if self.settings.orientation == orientations['UD']:
         pix_vals = np.median(self.picket_array[mlc_rows, :], axis=0)
     else:
         pix_vals = np.median(self.picket_array[:, mlc_rows], axis=1)
     if max(pix_vals) > np.percentile(self.picket_array, 80):
         prof = SingleProfile(pix_vals)
         fw80mc = prof.fwxm_center(70, interpolate=True)
         return fw80mc + self.approximate_idx - self.spacing
示例#10
0
    def find_bb(self):
        """Find the BB within the radiation field. Dervived from pylinac WL test.  Looks at the central 60x60 pixels and finds
        a bb

        Returns
        -------
        Point
            The weighted-pixel value location of the BB.
        """
        span=20
        bbox=[int(self.cax.y-span),int(self.cax.x-span),int(self.cax.y+span),int(self.cax.x+span)]
        subim=ndimage.gaussian_filter(np.array(self.array[bbox[0]:bbox[2],bbox[1]:bbox[3]],dtype=np.float),sigma=(1,1),order=0)

        self._bbox_max=np.max(subim)
        self._bbox_min=np.min(subim)
        
        hmin, hmax = np.percentile(subim, [10, 100.0])
        spread = hmax - hmin
        max_thresh = hmax
        lower_thresh = hmax - spread*.2
        # search for the BB by iteratively lowering the low-pass threshold value until the BB is found.
        found = False
        while not found:
            try:
                binary_arr = np.logical_and((subim <= max_thresh), (subim >= lower_thresh))
                labeled_arr, num_roi = ndimage.measurements.label(binary_arr)
                roi_sizes, bin_edges = np.histogram(labeled_arr, bins=num_roi + 1)
                bw_bb_img = np.where(labeled_arr == np.argsort(roi_sizes)[-2], 1, 0)
                if not self._is_round(bw_bb_img):
                    raise ValueError
                if not self._is_modest_size(bw_bb_img,subim.shape):
                    raise ValueError
                if not self._is_symmetric(bw_bb_img):
                    raise ValueError
            except (IndexError, ValueError):
                lower_thresh -= 0.05 * spread
                if lower_thresh < hmin:
                    raise ValueError("Unable to locate the BB. Make sure the field edges do not obscure the BB and that there is no artifact in the images.")
            else:
                found = True

        # determine the center of mass of the BB
        
        x_arr = np.abs(np.average(subim*bw_bb_img, axis=0))
        #plt.figure()
        #plt.plot(x_arr)
        #plt.figure()
        #plt.imshow(bw_bb_img)
        x_com = SingleProfile(np.array(x_arr,dtype=np.float)).fwxm_center(interpolate=True)
        y_arr = np.abs(np.average(subim*bw_bb_img, axis=1))
        y_com = SingleProfile(y_arr).fwxm_center(interpolate=True)              
        self.bb=Point(x_com+bbox[1], y_com+bbox[0])
示例#11
0
def symmetry_pdq_iec(profile: SingleProfile):
    """Symmetry calculation by way of PDQ IEC"""
    values = profile.field_values(field_width=0.8)
    lt_edge, rt_edge = profile.field_edges(field_width=0.8)
    max_val = 0
    sym_array = []
    for lt_pt, rt_pt in zip(values, values[::-1]):
        val = max(abs(lt_pt / rt_pt), abs(rt_pt / lt_pt))
        sym_array.append(val)
        if val > max_val:
            max_val = val
    symmetry = 100 * max_val
    return symmetry, sym_array, lt_edge, rt_edge
示例#12
0
def symmetry_point_difference(profile: SingleProfile):
    """Calculation of symmetry by way of point difference equidistant from the CAX"""
    values = profile.field_values(field_width=0.8)
    lt_edge, rt_edge = profile.field_edges(field_width=0.8)
    cax = profile.fwxm_center()
    dcax = profile.values[cax]
    max_val = 0
    sym_array = []
    for lt_pt, rt_pt in zip(values, values[::-1]):
        val = 100 * abs(lt_pt - rt_pt) / dcax
        sym_array.append(val)
        if val > max_val:
            max_val = val
    symmetry = max_val
    return symmetry, sym_array, lt_edge, rt_edge
示例#13
0
    def median_profiles(self):
        """Return two median profiles from the open and dmlc image. For visual comparison."""
        # dmlc median profile
        dmlc_prof = SingleProfile(np.median(self.image_dmlc, axis=0))
        dmlc_prof.stretch()
        # open median profile
        open_prof = SingleProfile(np.median(self.image_open, axis=0))
        open_prof.stretch()

        # normalize the profiles to near the same value
        norm_val = np.percentile(dmlc_prof.values, 90)
        dmlc_prof.normalize(norm_val)
        norm_val = np.percentile(open_prof.values, 90)
        open_prof.normalize(norm_val)

        return dmlc_prof, open_prof
示例#14
0
    def _get_profile(self, plane, position):
        """Get a profile at the given position along the specified plane."""
        if not self._img_is_loaded:
            raise AttributeError("An image has not yet been loaded")

        position = self._convert_position(position, plane)

        # if position == 'auto':
        #     y, x = self._determine_center()
        # else:
        #     if _is_crossplane(plane):
        #         self._check_position_inbounds(position, plane)
        #         y = position
        #     elif _is_inplane(plane):
        #         self._check_position_inbounds(position, plane)
        #         x = position

        if _is_crossplane(plane):
            prof = SingleProfile(self.image[position[0], :])
        elif _is_inplane(plane):
            prof = SingleProfile(self.image[:, position[0]])
        return prof
示例#15
0
    def to_profiles(
        self,
        n_detectors_row: int = 63,
        **kwargs
    ) -> Tuple[SingleProfile, SingleProfile, SingleProfile, SingleProfile]:
        """Convert the SNC data to SingleProfiles. These can be analyzed directly or passed to other modules like flat/sym.

        Parameters
        ----------
        n_detectors_row : int
            The number of detectors in a given row. Note that they Y profile includes 2 extra detectors from the other 3.
        """
        x_prof = SingleProfile(self.integrated_dose[:n_detectors_row],
                               **kwargs)
        y_prof = SingleProfile(
            self.integrated_dose[n_detectors_row:2 * n_detectors_row + 2],
            **kwargs)
        pos_prof = SingleProfile(
            self.integrated_dose[2 * n_detectors_row + 2:3 * n_detectors_row +
                                 2], **kwargs)
        neg_prof = SingleProfile(
            self.integrated_dose[3 * n_detectors_row + 2:4 * n_detectors_row +
                                 2], **kwargs)
        return x_prof, y_prof, pos_prof, neg_prof
示例#16
0
    def test_normalization(self):
        array = np.random.rand(1, 100).squeeze()

        # don't apply normalization
        max_v = array.max()
        p = SingleProfile(array, normalization_method=Normalization.NONE, interpolation=Interpolation.NONE, ground=False)
        self.assertEqual(max_v, p.values.max())

        # apply max norm
        p = SingleProfile(array, normalization_method=Normalization.MAX, interpolation=Interpolation.NONE)
        self.assertEqual(1.0, p.values.max())

        # make sure interpolation doesn't affect the norm
        p = SingleProfile(array, normalization_method=Normalization.MAX, interpolation=Interpolation.LINEAR)
        self.assertEqual(1.0, p.values.max())

        # test out a real field
        field = generate_open_field()
        p = SingleProfile(field.image[:, 500], normalization_method=Normalization.MAX)
        self.assertEqual(1.0, p.values.max())

        # filtered beam center is less than max value
        p = SingleProfile(field.image[:, 500], normalization_method=Normalization.BEAM_CENTER)
        self.assertGreaterEqual(p.values.max(), 1.0)
示例#17
0
    def test_geometric_center(self):
        # centered field
        field = generate_open_field()
        p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE)
        self.assertAlmostEqual(p.geometric_center()['index (exact)'], field.shape[0]/2, delta=1)

        # offset field should still be centered
        field = generate_open_field(center=(20, 20))
        p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE)
        self.assertAlmostEqual(p.geometric_center()['index (exact)'], field.shape[0]/2, delta=1)
示例#18
0
    def test_beam_center(self):
        # centered field
        field = generate_open_field()
        p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE)
        self.assertAlmostEqual(p.beam_center()['index (exact)'], field.shape[0]/2, delta=1)

        # offset field
        field = generate_open_field(center=(10, 10))
        p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE)
        self.assertAlmostEqual(p.beam_center()['index (exact)'], 422, delta=1)
示例#19
0
    def _determine_center(self, plane):
        """Automatically find the center of the field based on FWHM."""
        if not self._img_is_loaded:
            raise AttributeError("An image has not yet been loaded")

        self.image.check_inversion()
        self.image.ground()

        col_prof = np.median(self.image, 0)
        col_prof = SingleProfile(col_prof)
        row_prof = np.median(self.image, 1)
        row_prof = SingleProfile(row_prof)

        x_cen = col_prof.fwxm_center()
        y_cen = row_prof.fwxm_center()

        if _is_crossplane(plane):
            return y_cen
        elif _is_inplane(plane):
            return x_cen
        elif _is_both_planes(plane):
            return y_cen, x_cen
示例#20
0
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)
示例#21
0
    def median_profiles(self):
        """Return two median profiles from the open and dmlc image. For visual comparison."""
        # dmlc median profile
        dmlc_prof = SingleProfile(np.median(self.image_dmlc, axis=0))
        dmlc_prof.stretch()
        # open median profile
        open_prof = SingleProfile(np.median(self.image_open, axis=0))
        open_prof.stretch()

        # normalize the profiles to near the same value
        norm_val = np.percentile(dmlc_prof.values, 90)
        dmlc_prof.normalize(norm_val)
        norm_val = np.percentile(open_prof.values, 90)
        open_prof.normalize(norm_val)

        return dmlc_prof, open_prof
示例#22
0
 def setUpClass(cls):
     cls.profile = SingleProfile(cls.ydata, normalize_sides=cls.normalize_sides)
示例#23
0
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)