Esempio n. 1
0
def test_pull_structure():
    dicom_structure = dicom_dataset_from_dict({
        "StructureSetROISequence": [
            {
                "ROIName": A_STRUCTURE.name,
                "ROINumber": A_STRUCTURE.number
            },
            {
                "ROIName": ANOTHER_STRUCTURE.name,
                "ROINumber": ANOTHER_STRUCTURE.number,
            },
        ],
        "ROIContourSequence": [
            # Sequence purposely placed in reverse order to ensure ROI
            # number is being used and not list order.
            create_contour_sequence_dict(ANOTHER_STRUCTURE),
            create_contour_sequence_dict(A_STRUCTURE),
        ],
    })

    x, y, z = pull_structure(A_STRUCTURE.name, dicom_structure)

    assert np.all(x == np.array(A_STRUCTURE.coords)[:, 0, :])
    assert np.all(y == np.array(A_STRUCTURE.coords)[:, 1, :])
    assert np.all(z == np.array(A_STRUCTURE.coords)[:, 2, :])
Esempio n. 2
0
def align_cube_to_structure(
    structure_name: str,
    dcm_struct: pydicom.dataset.FileDataset,
    quiet=False,
    niter=10,
    x0=None,
):
    """Align a cube to a dicom structure set.

    Designed to allow arbitrary references frames within a dicom file
    to be extracted via contouring a cube.

    Parameters
    ----------
    structure_name
        The DICOM label of the cube structure
    dcm_struct
        The pydicom reference to the DICOM structure file.
    quiet : ``bool``
        Tell the function to not print anything. Defaults to False.
    x0 : ``np.ndarray``, optional
        A 3x3 array with each row defining a 3-D point in space.
        These three points are used as initial conditions to search for
        a cube that fits the contours. Choosing initial values close to
        the structure set, and in the desired orientation will allow
        consistent results. See examples within
        `pymedphys.experimental.cubify`_ on what the
        effects of each of the three points are on the resulting cube.
        By default, this parameter is defined using the min/max values
        of the contour structure.

    Returns
    -------
    cube_definition_array
        Four 3-D points the define the vertices of the cube.

    vectors
        The vectors between the points that can be used to traverse the cube.

    Examples
    --------
    >>> import numpy as np
    >>> import pydicom
    >>> import pymedphys
    >>> from pymedphys.experimental import align_cube_to_structure
    >>>
    >>> struct_path = str(pymedphys.data_path('example_structures.dcm'))
    >>> dcm_struct = pydicom.dcmread(struct_path, force=True)
    >>> structure_name = 'ANT Box'
    >>> cube_definition_array, vectors = align_cube_to_structure(
    ...     structure_name, dcm_struct, quiet=True, niter=1)
    >>> np.round(cube_definition_array)
    array([[-266.,  -31.,   43.],
           [-266.,   29.,   42.],
           [-207.,  -31.,   33.],
           [-276.,  -31.,  -16.]])
    >>>
    >>> np.round(vectors, 1)
    array([[  0.7,  59.9,  -0.5],
           [ 59.2,  -0.7,  -9.7],
           [ -9.7,  -0.4, -59.2]])
    """

    contours = pull_structure(structure_name, dcm_struct)
    contour_points = contour_to_points(contours)

    def to_minimise(cube):
        cube_definition = cubify([tuple(cube[0:3]), tuple(cube[3:6]), tuple(cube[6::])])
        min_dist_squared = calc_min_distance(cube_definition, contour_points)
        return np.sum(min_dist_squared)

    if x0 is None:
        concatenated_contours = [
            np.concatenate(contour_coord) for contour_coord in contours
        ]

        bounds = [
            (np.min(concatenated_contour), np.max(concatenated_contour))
            for concatenated_contour in concatenated_contours
        ]

        x0 = np.array(
            [
                (bounds[1][0], bounds[0][0], bounds[2][1]),
                (bounds[1][0], bounds[0][1], bounds[2][1]),
                (bounds[1][1], bounds[0][0], bounds[2][1]),
            ]
        )

    if quiet:

        def print_fun(x, f, accepted):  # pylint: disable = unused-argument
            pass

    else:

        def print_fun(x, f, accepted):  # pylint: disable = unused-argument
            print("at minimum %.4f accepted %d" % (f, int(accepted)))

    result = basinhopping(to_minimise, x0, callback=print_fun, niter=niter, stepsize=5)

    cube = result.x

    cube_definition = cubify([tuple(cube[0:3]), tuple(cube[3:6]), tuple(cube[6::])])

    cube_definition_array = np.array([np.array(list(item)) for item in cube_definition])

    vectors = [
        cube_definition_array[1] - cube_definition_array[0],
        cube_definition_array[2] - cube_definition_array[0],
        cube_definition_array[3] - cube_definition_array[0],
    ]

    return cube_definition_array, vectors