notmatthancock / pylidc

An object relational mapping for the LIDC dataset using sqlalchemy.
https://pylidc.github.io
Other
105 stars 40 forks source link

bbox(): correct coordinates? #10

Closed WGierke closed 6 years ago

WGierke commented 6 years ago

Hey, I'd like to continue my post from #9 here since it appears to me to be a real issue. I'd expect bbox() to enclose an annotation in a cuboid. However, from my POV this appears to be not the case:

import matplotlib.pyplot as plt
import numpy as np
import pylidc as pl
from matplotlib.widgets import Slider

def cube_show_slider(cube, axis=2, is_mask=False, **kwargs):
    """
    Display a 3d ndarray with a slider to move along the third dimension.

    Extra keyword arguments are passed to imshow
    (Kudos to http://nbarbey.github.io/2011/07/08/matplotlib-slider.html)
    """
    import matplotlib.pyplot as plt
    from matplotlib.widgets import Slider

    # check dim
    if not cube.ndim == 3:
        raise ValueError("cube should be an ndarray with ndim == 3")

    # generate figure
    fig = plt.figure()
    subplot = fig.add_subplot(111)
    fig.subplots_adjust(left=0.25, bottom=0.25)

    if is_mask:
        im1 = subplot.imshow(cube[:, :, 0])
        fig.colorbar(im1)
    else:
        # select first image
        s = [slice(0, 1) if i == axis else slice(None) for i in range(3)]
        im = cube[s].squeeze()

        # display image
        l = subplot.imshow(im, **kwargs)

    # define slider
    axcolor = 'lightgoldenrodyellow'
    ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)

    slider = Slider(ax, 'Axis %i index' % axis, 0, cube.shape[axis] - 1, valinit=0, valfmt='%i')

    def update(val):
        if is_mask:
            subplot.imshow(cube[:, :, int(val)])
            fig.canvas.draw()
        else:
            ind = int(slider.val)
            s = [slice(ind, ind + 1) if i == axis else slice(None)
                 for i in range(3)]
            im = cube[s].squeeze()
            l.set_data(im, **kwargs)
        fig.canvas.draw()

    slider.on_changed(update)

    plt.show()

def display_training_pair(input_cube, output_cube, axis=2, **kwargs):
    # check dims
    if not input_cube.ndim == 3 or not output_cube.ndim == 3:
        raise ValueError("cube should be an ndarray with ndim == 3")

    # generate figure
    fig = plt.figure()
    subplot_input = fig.add_subplot(221)
    subplot_output = fig.add_subplot(222)
    fig.subplots_adjust(left=0.25, bottom=0.25)

    # select first image
    im1 = subplot_output.imshow(output_cube[:, :, 0])
    fig.colorbar(im1)

    s = [slice(0, 1) if i == axis else slice(None) for i in range(3)]
    im = input_cube[s].squeeze()
    # display image
    l = subplot_input.imshow(im, **kwargs)

    # define slider
    axcolor = 'lightgoldenrodyellow'
    ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)

    slider = Slider(ax, 'Axis %i index' % axis, 0, input_cube.shape[axis] - 1, valinit=0, valfmt='%i')

    def update(val):
        subplot_output.imshow(output_cube[:, :, int(val)])

        ind = int(slider.val)
        s = [slice(ind, ind + 1) if i == axis else slice(None)
             for i in range(3)]
        im = input_cube[s].squeeze()
        l.set_data(im, **kwargs)
        fig.canvas.draw()

    slider.on_changed(update)

    plt.show()

if __name__ == '__main__':
    scan = pl.query(pl.Scan).filter(pl.Scan.patient_id == 'LIDC-IDRI-0001').first()
    Z_RANGE = 340  # (from -340 to 0)
    SLICES = 133
    scan_volume = scan.to_volume()
    scan_volume_mask = np.zeros(scan_volume.shape)
    an = scan.annotations[0]
    an_box = an.bbox()
    z_start = int((Z_RANGE + an_box[2, 0]) / Z_RANGE * SLICES)
    z_end = int((Z_RANGE + an_box[2, 1]) / Z_RANGE * SLICES)
    scan_volume_mask[int(an_box[0, 0]):int(an_box[0, 1]),
                     int(an_box[1, 0]):int(an_box[1, 1]),
                     int(z_start):int(z_end)] = 1

    print("Expected outcome when mask is applied to lung image:")
    vol, seg = an.uniform_cubic_resample(49)
    cube_show_slider(vol * seg, is_mask=True)

    print("Current outcome:")
    display_training_pair(scan_volume, scan_volume_mask)
    cube_show_slider(scan_volume * scan_volume_mask, is_mask=True)

... produces the correct expected result when the binary mask is applied to the data (here using uniform_cubic_resample): screenshot from 2017-10-01 01-58-15 but one can see that the binary mask positioned using the bbox() coordinates is not at the exact same position as the annotation screenshot from 2017-10-01 01-58-29 which becomes even clearer when the mask is applied to the data and the annotation is not included in the masked image screenshot from 2017-10-01 01-58-42

Am I doing something wrong? All I want is a binary image whose pixels are 1 if there is a cancerous annotation and otherwise 0 while the image shape is the same as the one of scan.to_volume().

notmatthancock commented 6 years ago

You should set the option, an_box = an.bbox(image_coords=True). Actually, image_coords=True should probably be the default for this function so it is consistent with the returned bbox of get_boolean_mask(return_bbox=True).

Try out this code. If you have non-issue questions about using the software, feel free to email me.

WGierke commented 6 years ago

Your snippet works like a charm, thanks a lot! Sorry for the inconvenience.

notmatthancock commented 6 years ago

glad it worked out!