def A_mean(self, A_mean=None): if A_mean is not None: if not isinstance(A_mean, ndarray): raise TypeError('if argument "A_mean" is provided, it ' 'must be of type {}'.format(ndarray)) elif not A_mean.size in A_mean.shape: raise ValueError('if argument "A_mean" is provided, it must be' ' a row or column vector. shape of argument: ' '{}'.format(A_mean.shape)) else: if self.__A_full is not None: if len(A_mean) != self.__A_full.shape[1]: raise ValueError('field "A_full" already set; ' 'proposed value for "A_mean" ' 'must have same number of entries ' '({}) as columns in A_full ({})' ''.format(len(A_mean), self.__A_full.shape[1])) self.__A_mean = vec(A_mean) elif self.__A_full is not None: if not sparse_or_dense(self.A_full): raise TypeError('cannot calculate structure.A_mean from' 'structure.A_full: A_full must be one of' ' ({},{},{})'.format(ndarray, csc_matrix, csr_matrix)) else: self.__A_mean = self.A_full.sum(0) / self.A_full.shape[0] if not isinstance(self.A_full, ndarray): self.__A_mean = vec(self.__A_mean)
def A_mean(self, A_mean=None): if A_mean is not None: if not isinstance(A_mean, np.ndarray): raise TypeError('if argument "A_mean" is provided, it must be ' 'of type {}'.format(np.ndarray)) elif not A_mean.size in A_mean.shape: raise ValueError( 'if argument "A_mean" is provided, it must be ' 'a row or column vector. shape of argument: {}' ''.format(A_mean.shape)) else: if self.__A_full is not None: if len(A_mean) != self.__A_full.shape[1]: raise ValueError( 'field "A_full" already set; proposed ' 'value for "A_mean" must have same ' 'number of entries ({}) as columns in ' 'A_full ({})'.format(len(A_mean), self.__A_full.shape[1])) self.__A_mean = vec(A_mean) elif self.__A_full is not None: if not sparse_or_dense(self.A_full): raise TypeError('cannot calculate structure.A_mean from' 'structure.A_full: A_full must be one of ' '({},{},{})'.format(np.ndarray, sp.csc_matrix, sp.csr_matrix)) else: if isinstance(self.A_full, np.ndarray): self.__A_mean = np.dot(self.voxel_weights, self.A_full) else: self.__A_mean = vec(self.voxel_weights * self.A_full) self.__A_mean /= float(self.weighted_size)
def data(self, data): self._validate(data) if isinstance(data, dict): data_contiguous = data.pop('contiguous', None) if data_contiguous is not None: self.data = data_contiguous if len(data) == 0: return for k in data: data[k] = vec(data[k]).astype(float) size = sum(w.size for w in data.values()) else: data = vec(data).astype(float) size = data.size if self.size is not None and self.size != size: raise ValueError( 'length of input data does not match known length ' 'of {}'.format(WeightVector)) else: self.__size = size if isinstance(data, dict): self.__slices.update(data) if data_contiguous is not None: self.data = data_contiguous else: self.__data = data
def primal_eval(self, y, voxel_weights=None): residuals = vec(y) - float(self.deadzone_dose) residuals *= residuals > 0 if voxel_weights is None: return self.weight * np.sum(residuals) else: return self.weight * np.dot(voxel_weights, residuals)
def calc_y(self, x): """ Calculate voxel doses as :attr:`Structure.y` = :attr:`Structure.A` * ``x``. Arguments: x: Vector-like input of beam intensities. Returns: None """ # calculate dose from input vector x: # y = Ax x = vec(x) if isinstance(self.A, (csr_matrix, csc_matrix)): self.__y = squeeze(self.A * x) elif isinstance(self.A, ndarray): self.__y = self.A.dot(x) self.__y_mean = self.A_mean.dot(x) if isinstance(self.__y_mean, ndarray): self.__y_mean = self.__y_mean[0] # make DVH curve from calculated dose self.dvh.data = self.__y
def indices_by_label(label_vector, label, vector_name): """ Retrieve indices of vector entries corresponding to a given value. Arguments: label_vector: Vector of values to search for entries corresponding label: Value to find. vector_name (:obj:`str`): Name of vector, for use in exception messages. Returns: :class:`~numpy.ndarray`: Vector of indices at which the entries of ``label_vector`` are equal to ``label``. Raises: ValueError: If ``label_vector`` is ``None``. KeyError: If ``label`` not found in ``label_vector``. """ if label_vector is None: raise ValueError('`{}.{}` not set, retrieval by label ' 'impossible'.format(DoseFrame, vector_name)) indices = listmap( lambda x: x[0], listfilter(lambda x: x[1] == label, enumerate(label_vector))) if len(indices) == 0: raise KeyError('label {} not found in entries of field ' '"{}"'.format(label, vector_name)) return vec(indices)
def calc_y(self, x): """ Calculate voxel doses as: attr:`Structure.y` = :attr:`Structure.A` * ``x``. Arguments: x: Vector-like input of beam intensities. Returns: None """ # calculate dose from input vector x: # y = Ax x = vec(x) if isinstance(self.A, (sp.csr_matrix, sp.csc_matrix)): self.__y = np.squeeze(self.A * x) elif isinstance(self.A, np.ndarray): self.__y = self.A.dot(x) self.__y_mean = self.A_mean.dot(x) if isinstance(self.__y_mean, np.ndarray): self.__y_mean = self.__y_mean[0] # make DVH curve from calculated dose if self.y is not None: self.dvh.data = self.y
def beam_labels(self, beam_labels): if self.beams in (None, np.nan): self.beams = len(beam_labels) if len(beam_labels) != self.beams: raise ValueError('length of `beam labels` ({}) must match ' 'number of beams in frame ({})' ''.format(len(beam_labels), self.beams)) self.__beam_labels = vec(beam_labels).astype(int)
def voxel_labels(self, voxel_labels): if self.voxels in (None, np.nan): self.voxels = len(voxel_labels) if len(voxel_labels) != self.voxels: raise ValueError('length of "voxel labels" ({}) must match ' 'number of voxels in frame ({})' ''.format(len(voxel_labels), self.voxels)) self.__voxel_labels = vec(voxel_labels).astype(int)
def dual_expr_pogs(self, size, voxel_weights=None): if OPTKIT_INSTALLED: weights = 1. if voxel_weights is None else vec(voxel_weights) return ok.api.PogsObjective(size, h='Zero', d=-float(self.deadzone_dose) * weights) else: raise NotImplementedError
def dual_domain_constraints(self, nu_var, voxel_weights=None): """ Return the constraint :math:`0 \le \nu \le w`. """ if voxel_weights is None: voxel_weights = 1 else: voxel_weights = vec(voxel_weights) return [nu_var <= voxel_weights * self.weight, nu_var >= 0]
def split_dose_by_label(self, dose_vector, labels): if isinstance(dose_vector, dict): doses = dose_vector else: y = vec(dose_vector) if y.size != self.voxels: raise ValueError('input vector must match voxel dimension of ' 'current dose frame') doses = {label: y[self.frame.voxel_lookup_by_label(label)]}
def primal_expr_pogs(self, size, voxel_weights=None): if OPTKIT_INSTALLED: weights = 1. if voxel_weights is None else vec(voxel_weights) return ok.api.PogsObjective(size, h='Abs', b=float(self.deadzone_dose), c=weights * self.weight / 2., d=weights * self.weight / 2.) else: raise NotImplementedError
def voxel_weights(self, weights): if self.size in (None, nan, 0): raise ValueError('structure size must be defined to add ' 'voxel weights') if len(weights) != self.size: raise ValueError('length of input "weights" ({}) does not ' 'match structure size ({}) of this {} ' 'object' ''.format(len(weights), self.size, Structure)) if any(weights < 0): raise ValueError('negative voxel weights not allowed') self.__voxel_weights = vec(weights)
def primal_eval(self, y, voxel_weights=None): r""" Return :math:`w_+ \omega^T(y-d)_+ + w_-\omega^T(y-d)_-`, for :math:`\omega \equiv` ``voxel weights``. """ residuals = vec(y) - float(self.target_dose) if voxel_weights is None: return float(self.weight_abs * np.linalg.norm(residuals, 1) + self.weight_linear * np.sum(residuals)) else: return float(self.weight_abs * np.dot(voxel_weights, np.abs(residuals)) + self.weight_linear * np.dot(voxel_weights, residuals))
def dual_domain_constraints(self, nu_var, voxel_weights=None): """ Return the constraint :math:`-w_- \le \nu \le w_+`. """ upper_bound = self.weight_overdose lower_bound = -self.weight_underdose if voxel_weights is None: voxel_weights = 1 else: voxel_weights = vec(voxel_weights) return [ nu_var <= voxel_weights * upper_bound, nu_var >= voxel_weights * lower_bound ]
def get_maxmargin_fulfillers(self, y, had_slack=False): r""" Get indices to values of ``y`` deepest in feasible set. In particular, given ``len(y)``, if ``m`` voxels are required to respect this :class:`PercentileConstraint` exactly, ``y`` is assumed to contain at least ``m`` entries that respect the constraint (for instance, ``y`` is generated by a convex program that includes a convex restriction of the dose constraint). Procedure. .. math:: :nowrap: \begin{array}{rl} \mathbf{0.} & \mbox{Define} \\ & p = \mbox{percent non-violating} \cdot \mbox{structure size} = \mbox{percent non-violating} \cdot \mathbf{len}(y) \\ \mathbf{1.} & \mbox{Get margins: } y - \mbox{dose bound}. \\ \mathbf{2.} & \mbox{Sort margin indices by margin values.} \\ \mathbf{3.} & \mbox{If upper constraint, return indices of $p$ most negative entries}. \\ \mathbf{4.} & \mbox{If lower constraint, return indices of $p$ most positive entries}. \\ \end{array} Arguments: y: Vector-like input data of length ``m``. had_slack (:obj:`bool`, optional): Define margin relative to slack-modulated dose value instead of the base dose value of this :class:`PercentileConstraint`. Returns: :class:`numpy.ndarray`: Vector of indices that yield the ``p`` entries of ``y`` that fulfill this :class:`PercentileConstraint` with the greatest margin. """ fraction = self.percentile.fraction non_viol = (1 - fraction) if self.upper else fraction n_returned = int(ceil(non_viol * len(y))) start = 0 if self.upper else -n_returned end = n_returned if self.upper else None dose = self.dose_achieved.value if had_slack else self.dose.value return (vec(y) - dose).argsort()[start:end]
def get_maxmargin_fulfillers(self, y, had_slack=False): r""" Get indices to values of ``y`` deepest in feasible set. In particular, given ``len(y)``, if ``m`` voxels are required to respect this :class:`PercentileConstraint` exactly, ``y`` is assumed to contain at least ``m`` entries that respect the constraint (for instance, ``y`` is generated by a convex program that includes a convex restriction of the dose constraint). Procedure. .. math:: :nowrap: \begin{array}{rl} \mathbf{0.} & \mbox{Define} \\ & p = \mbox{percent non-violating} \cdot \mbox{structure size} = \mbox{percent non-violating} \cdot \mathbf{len}(y) \\ \mathbf{1.} & \mbox{Get margins: } y - \mbox{dose bound}. \\ \mathbf{2.} & \mbox{Sort margin indices by margin values.} \\ \mathbf{3.} & \mbox{If upper constraint, return indices of $p$ most negative entries}. \\ \mathbf{4.} & \mbox{If lower constraint, return indices of $p$ most positive entries}. \\ \end{array} Arguments: y: Vector-like input data of length ``m``. had_slack (:obj:`bool`, optional): Define margin relative to slack-modulated dose value instead of the base dose value of this :class:`PercentileConstraint`. Returns: :class:`numpy.ndarray`: Vector of indices that yield the ``p`` entries of ``y`` that fulfill this :class:`PercentileConstraint` with the greatest margin. """ fraction = self.percentile.fraction non_viol = (1 - fraction) if self.upper else fraction n_returned = int(np.ceil(non_viol * len(y))) start = 0 if self.upper else -n_returned end = n_returned if self.upper else None dose = self.dose_achieved.value if had_slack else self.dose.value return (vec(y) - dose).argsort()[start:end]
def __init__(self, map_vector, target_dimension=None): r""" Initialize a one-to-one or many-to-one discrete relation. The size of the first set/vector space is taken to be the length of ``map_vector``, and the size of the second set/vector space is taken to be implied by the (base-``0``) value of the largest entry in ``map_vector``. Arguments: map_vector: Vector-like array of :obj:`int`, representing mapping. Let v be the input vector. Then the relation maps the i'th entry of the first set to the v[i]'th entry of the second set. """ self.__forwardmap = vec(map_vector).astype(int) self.__n_frame0 = len(self.__forwardmap) self.__n_frame1 = self.__forwardmap.max() + 1
def __init__(self, map_vector): """ Initialize a one-to-one or many-to-one discrete relation. The size of the first set/vector space is taken to be the length of ``map_vector``, and the size of the second set/vector space is taken to be implied by the (base-``0``) value of the largest entry in ``map_vector``. Arguments: map_vector: Vector-like array of :obj:`int`, representing mapping. Let v be the input vector. Then the relation maps the i'th entry of the first set to the v[i]'th entry of the second set. """ self.__forwardmap = vec(map_vector).astype(int) self.__n_frame0 = len(self.__forwardmap) self.__n_frame1 = self.__forwardmap.max() + 1
def voxel_weights(self, weights): if self.size in (None, np.nan, 0): raise ValueError('structure size must be defined to add voxel ' 'weights') if len(weights) != self.size and np.sum(weights) != self.size: raise ValueError('either the length ({}) or sum ({}) of input ' '`weights` does not match structure size ({}) ' 'of this {} object'.format( len(weights), np.sum(weights), self.size, Structure)) if any(weights < 0): raise ValueError('negative voxel weights not allowed') self.__voxel_weights = vec(weights) self.__weighted_size = np.sum(self.__voxel_weights) self.objective.normalization = 1. / self.weighted_size if self.weighted_size != self.size and self.A_full is not None: # Pass "None" to self.A_mean setter to trigger calculation of # mean dose matrix from full dose matrix. self.A_mean = None if self.y is not None: self.__y_mean = np.dot(self.voxel_weights, self.y) / self.weighted_size
def assign_dose(self, y): """ Assign dose vector to structure. Arguments: y: Vector-like input of voxel doses. Returns: None Raises: ValueError: if structure size is known and incompatible with length of ``y``. """ y = vec(y) if self.size is None: self.size = y.size elif self.size != y.size: raise ValueError('size of dose vector ({}) incompatible with size ' 'of structure ({})'.format(y.size, self.size)) self.__y = y self.__y_mean = np.dot(self.voxel_weights, y) / self.weighted_size self.dvh.data = self.__y
def test_case_plotter_set_display_groups(self): self.case.anatomy.label_order = [0, 1, 2] cp = CasePlotter(self.case) if cp is None: return cp.set_display_groups('together') self.assert_vector_equal( vec(list(cp.dvh_plot.series_panels.values())), vec([1, 1, 1]) ) cp.set_display_groups('separate') self.assert_vector_equal( vec(list(cp.dvh_plot.series_panels.values())), vec([1, 2, 3]) ) cp.set_display_groups('list', [('PTV',), ('OAR1', 'OAR2')]) self.assert_vector_equal( vec(list(cp.dvh_plot.series_panels.values())), vec([1, 2, 2]) )