Ejemplo n.º 1
0
    def testOrthoReversed(self):
        grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
        plane = OrthoSlice(grid, YAXIS, SLICEPOS)

        # Invert Z axis
        affine = np.array([[1, 0, 0, 0], [0, 1, 0, 0],
                           [0, 0, -1, GRIDSIZE - 1], [0, 0, 0, 1]])
        datagrid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), affine)
        YD, XD, ZD = np.meshgrid(range(GRIDSIZE), range(GRIDSIZE),
                                 range(GRIDSIZE))

        xdata, _, _, _ = NumpyData(XD, name="test",
                                   grid=datagrid).slice_data(plane)
        ydata, _, _, _ = NumpyData(YD, name="test",
                                   grid=datagrid).slice_data(plane)
        zdata, _, transv, offset = NumpyData(ZD, name="test",
                                             grid=datagrid).slice_data(plane)

        # Reversal is reflected in the transformation
        self.assertTrue(np.all(transv == [[1, 0], [0, -1]]))

        self.assertTrue(np.all(ydata == SLICEPOS))
        for x in range(GRIDSIZE):
            self.assertTrue(np.all(xdata[x, :] == x))
            self.assertTrue(np.all(zdata[:, x] == x))
Ejemplo n.º 2
0
 def testHighRes(self):
     grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
     plane = OrthoSlice(grid, YAXIS, SLICEPOS)
     data = np.random.rand(GRIDSIZE * 2, GRIDSIZE * 2, GRIDSIZE * 2)
     datagrid = DataGrid((GRIDSIZE * 2, GRIDSIZE * 2, GRIDSIZE * 2),
                         np.identity(4) / 2)
     qpd = NumpyData(data, name="test", grid=datagrid)
     qpd.slice_data(plane)
Ejemplo n.º 3
0
 def testOrthoZ(self):
     grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
     plane = OrthoSlice(grid, ZAXIS, SLICEPOS)
     self.assertEquals(tuple(plane.origin), (0, 0, SLICEPOS))
     self.assertEquals(len(plane.basis), 2)
     self.assertTrue((0, 1, 0) in plane.basis)
     self.assertTrue((1, 0, 0) in plane.basis)
Ejemplo n.º 4
0
    def apply_transform(cls, reg_data, transform, options, queue):
        """
        Apply a previously calculated transformation to a data set

        We are not actually using FSL applyxfm for this although it would be
        an alternative option for the reference space output option. Instead
        we perform a non-lossy affine transformation and then resample onto
        the reference or registration spaces as required.
        """
        log = "Performing non-lossy affine transformation\n"
        order = options.pop("interp-order", 1)
        affine = transform.voxel_to_world(reg_data.grid)
        grid = DataGrid(reg_data.grid.shape, affine)
        qpdata = NumpyData(reg_data.raw(), grid=grid, name=reg_data.name)

        output_space = options.pop("output-space", "ref")
        if output_space == "ref":
            qpdata = qpdata.resample(transform.ref_grid,
                                     suffix="",
                                     order=order)
            log += "Resampling onto reference grid\n"
        elif output_space == "reg":
            qpdata = qpdata.resample(transform.reg_grid,
                                     suffix="",
                                     order=order)
            log += "Resampling onto input grid\n"

        return qpdata, log
Ejemplo n.º 5
0
    def testResampleToHiResLinear(self):
        self.ivm.add(self.data_3d, grid=self.grid, name="data_3d")
        hires_shape = [dim*2 for dim in self.data_3d.shape]
        hires_grid = DataGrid(hires_shape, np.identity(4) / 2)
        hires_data = np.tile(self.data_3d, (2, 2, 2))
        self.ivm.add(hires_data, grid=hires_grid, name="hires_data")

        self.w.order.setCurrentIndex(1)
        self.w.data.setCurrentIndex(self.w.data.findText("data_3d"))
        self.w.grid_data.setCurrentIndex(self.w.grid_data.findText("hires_data"))
        self.processEvents()

        self.assertEqual(self.w.output_name.value, "data_3d_res")
        
        self.w.run.btn.clicked.emit()
        self.processEvents()

        self.assertFalse(self.error)
        self.assertTrue("data_3d_res" in self.ivm.data)
        
        # Resampled data should match original data but at twice the resolution
        self.assertTrue(self.ivm.data["data_3d_res"].grid.matches(hires_grid))
        data_res = self.ivm.data["data_3d_res"].raw()
        
        X = range(self.data_3d.shape[0])
        Y = range(self.data_3d.shape[1])
        Z = range(self.data_3d.shape[2])
        
        for x in range(data_res.shape[0]):
            for y in range(data_res.shape[1]):
                for z in range(data_res.shape[2]):
                    gx, gy, gz = float(x)/2, float(y)/2, float(z)/2
                    from scipy.interpolate import interpn
                    d = interpn((X, Y, Z), self.data_3d, (gx, gy, gz), method="linear", bounds_error=False, fill_value=0)
                    self.assertAlmostEqual(data_res[x, y, z], d[0])
Ejemplo n.º 6
0
    def testResampleToHiResNN(self):
        self.ivm.add(self.data_3d, grid=self.grid, name="data_3d")
        hires_shape = [dim*2 for dim in self.data_3d.shape]
        hires_grid = DataGrid(hires_shape, np.identity(4) / 2)
        hires_data = np.tile(self.data_3d, (2, 2, 2))
        self.ivm.add(hires_data, grid=hires_grid, name="hires_data")

        self.w.order.setCurrentIndex(0)
        self.w.data.setCurrentIndex(self.w.data.findText("data_3d"))
        self.w.grid_data.setCurrentIndex(self.w.grid_data.findText("hires_data"))
        self.processEvents()

        self.assertEqual(self.w.output_name.value, "data_3d_res")
        
        self.w.run.btn.clicked.emit()
        self.processEvents()

        self.assertFalse(self.error)
        self.assertTrue("data_3d_res" in self.ivm.data)
        
        # Resampled data should match original data but at twice the resolution
        self.assertTrue(self.ivm.data["data_3d_res"].grid.matches(hires_grid))
        data_res = self.ivm.data["data_3d_res"].raw()
        
        for x in range(data_res.shape[0]):
            for y in range(data_res.shape[1]):
                for z in range(data_res.shape[2]):
                    nx, ny, nz = int(float(x)/2+0.5), int(float(y)/2+0.5), int(float(z)/2+0.5)
                    if nx < self.data_3d.shape[0] and ny < self.data_3d.shape[1] and nz < self.data_3d.shape[2]:
                        self.assertEqual(data_res[x, y, z], self.data_3d[nx, ny, nz])
                    else:
                        self.assertEqual(data_res[x, y, z], 0)
Ejemplo n.º 7
0
 def setUp(self):
     self.shape = [GRIDSIZE, GRIDSIZE, GRIDSIZE]
     self.grid = DataGrid(self.shape, np.identity(4))
     self.floats = np.random.rand(*self.shape)
     self.ints = np.random.randint(0, 10, self.shape)
     self.floats4d = np.random.rand(*(self.shape + [
         NVOLS,
     ]))
Ejemplo n.º 8
0
    def testOrthoY(self):
        grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
        YD, XD, ZD = np.meshgrid(range(GRIDSIZE), range(GRIDSIZE),
                                 range(GRIDSIZE))

        plane = OrthoSlice(grid, YAXIS, SLICEPOS)
        self.assertEquals(tuple(plane.origin), (0, SLICEPOS, 0))
        self.assertEquals(len(plane.basis), 2)
        self.assertTrue((1, 0, 0) in plane.basis)
        self.assertTrue((0, 0, 1) in plane.basis)
Ejemplo n.º 9
0
 def testAdd(self):
     shape = [GRIDSIZE, GRIDSIZE, GRIDSIZE]
     grid = DataGrid(shape, np.identity(4))
     qpd = NumpyData(np.random.rand(*shape), name="test", grid=grid)
     self.ivm.add(qpd)
     self.assertEqual(len(self.ivm.data), 1)
     self.assertEqual(len(self.ivm.rois), 0)
     self.assertTrue(self.ivm.current_data is None)
     self.assertTrue(self.ivm.current_roi is None)
     self.assertEqual(self.ivm.main, qpd)
     self.assertEqual(self.ivm.data["test"], qpd)
Ejemplo n.º 10
0
    def testGenericZ(self):
        trans = np.array([[0.3, 0.2, 1.7, 0], [0.1, 2.1, 0.11, 0],
                          [2.2, 0.7, 0.3, 0], [0, 0, 0, 1]])

        grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), trans)
        origin = list(SLICEPOS * trans[:3, 2])
        plane = OrthoSlice(grid, ZAXIS, SLICEPOS)
        self.assertAlmostEquals(list(plane.origin), origin)
        self.assertEquals(len(plane.basis), 2)
        self.assertTrue(tuple(trans[:3, 0]) in plane.basis)
        self.assertTrue(tuple(trans[:3, 1]) in plane.basis)
Ejemplo n.º 11
0
def fslimage_to_qpdata(img, name=None, vol=None, region=None):
    """ Convert fsl.data.Image to QpData """
    if not name: name = img.name
    if vol is not None:
        data = img.data[..., vol]
    else:
        data = img.data
    if region is not None:
        data = (data == region).astype(np.int)
    return NumpyData(data,
                     grid=DataGrid(img.shape[:3], img.voxToWorldMat),
                     name=name)
Ejemplo n.º 12
0
    def testOrthoOffset(self):
        grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
        plane = OrthoSlice(grid, YAXIS, SLICEPOS)

        # Offset X axis
        affine = np.array([[1, 0, 0, 2], [0, 1, 0, 0], [0, 0, 1, 0],
                           [0, 0, 0, 1]])
        datagrid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), affine)
        YD, XD, ZD = np.meshgrid(range(GRIDSIZE), range(GRIDSIZE),
                                 range(GRIDSIZE))

        xdata, _, _, _ = NumpyData(XD, name="test",
                                   grid=datagrid).slice_data(plane)
        ydata, _, _, _ = NumpyData(YD, name="test",
                                   grid=datagrid).slice_data(plane)
        zdata, _, transv, offset = NumpyData(ZD, name="test",
                                             grid=datagrid).slice_data(plane)

        self.assertTrue(np.all(ydata == SLICEPOS))
        for x in range(GRIDSIZE):
            self.assertTrue(np.all(xdata[x, :] == x))
            self.assertTrue(np.all(zdata[:, x] == x))
Ejemplo n.º 13
0
    def run(self, options):
        """ Generate test data from Fabber model """
        kwargs = {
            "patchsize":
            int(math.floor(options.pop("num-voxels", 1000)**(1. / 3) + 0.5)),
            "nt":
            options.pop("num-vols", 10),
            "noise":
            options.pop("noise", 0),
            "param_rois":
            options.pop("save-rois", False),
        }
        param_test_values = options.pop("param-test-values", None)
        output_name = options.pop("output-name", "fabber_test_data")
        grid_data_name = options.pop("grid", None)

        if not param_test_values:
            raise QpException("No test values given for model parameters")

        api = FabberProcess.api(options.pop("model-group", None))
        from fabber import generate_test_data
        test_data = generate_test_data(api, options, param_test_values,
                                       **kwargs)

        data = test_data["data"]
        self.debug("Data shape: %s", data.shape)

        if grid_data_name is None:
            grid = DataGrid(data.shape[:3], np.identity(4))
        else:
            grid_data = self.ivm.data.get(grid_data_name, None)
            if grid_data is None:
                raise QpException("Data not found for output grid: %s" %
                                  grid_data_name)
            grid = grid_data.grid

        self.ivm.add(data, name=output_name, grid=grid, make_current=True)

        clean_data = test_data.get("clean", None)
        if clean_data is not None:
            self.ivm.add(clean_data,
                         name="%s_clean" % output_name,
                         grid=grid,
                         make_current=False)

        for param, param_roi in test_data.get("param-rois", {}).items():
            self.ivm.add(param_roi,
                         name="%s_roi_%s" % (output_name, param),
                         grid=grid)
Ejemplo n.º 14
0
    def testOrtho(self):
        grid = DataGrid((GRIDSIZE, GRIDSIZE, GRIDSIZE), np.identity(4))
        YD, XD, ZD = np.meshgrid(range(GRIDSIZE), range(GRIDSIZE),
                                 range(GRIDSIZE))

        plane = OrthoSlice(grid, YAXIS, SLICEPOS)
        xdata, _, _, _ = NumpyData(XD, name="test",
                                   grid=grid).slice_data(plane)
        ydata, _, _, _ = NumpyData(YD, name="test",
                                   grid=grid).slice_data(plane)
        zdata, _, _, _ = NumpyData(ZD, name="test",
                                   grid=grid).slice_data(plane)

        self.assertTrue(np.all(ydata == SLICEPOS))
        for x in range(GRIDSIZE):
            self.assertTrue(np.all(xdata[x, :] == x))
            self.assertTrue(np.all(zdata[:, x] == x))
Ejemplo n.º 15
0
    def reg_3d(cls, reg_data, ref_data, options, queue):
        """
        Static function for performing 3D registration
        """
        from fsl import wrappers as fsl
        reg = qpdata_to_fslimage(reg_data)
        ref = qpdata_to_fslimage(ref_data)

        set_environ(options)

        output_space = options.pop("output-space", "ref")
        interp = _interp(options.pop("interp-order", 1))
        twod = reg_data.grid.shape[2] == 1
        logstream = six.StringIO()
        flirt_output = fsl.flirt(reg,
                                 ref,
                                 interp=interp,
                                 out=fsl.LOAD,
                                 omat=fsl.LOAD,
                                 twod=twod,
                                 log={
                                     "cmd": logstream,
                                     "stdout": logstream,
                                     "stderr": logstream
                                 },
                                 **options)
        transform = FlirtTransform(ref_data.grid,
                                   flirt_output["omat"],
                                   name="flirt_xfm")

        if output_space == "ref":
            qpdata = fslimage_to_qpdata(flirt_output["out"], reg_data.name)
        elif output_space == "reg":
            qpdata = fslimage_to_qpdata(flirt_output["out"],
                                        reg_data.name).resample(reg_data.grid,
                                                                suffix="")
            qpdata.name = reg_data.name
        elif output_space == "trans":
            trans_affine = transform.voxel_to_world(reg_data.grid)
            trans_grid = DataGrid(reg_data.grid.shape, trans_affine)
            qpdata = NumpyData(reg_data.raw(),
                               grid=trans_grid,
                               name=reg_data.name)

        return qpdata, transform, logstream.getvalue()
Ejemplo n.º 16
0
    def run(self, options):
        data = self.get_data(options)
        if data.ndim != 4:
            raise QpException("Can only simulate motion on 4D data")

        output_name = options.pop("output-name", "%s_moving" % data.name)
        std = float(options.pop("std"))
        std_voxels = [std / size for size in data.grid.spacing]
        output_grid = data.grid
        output_shape = data.grid.shape

        padding = options.pop("padding", 0)
        if padding > 0:
            padding_voxels = [
                int(math.ceil(padding / size)) for size in data.grid.spacing
            ]
            # Need to adjust the origin so the output data lines up with the input
            output_origin = np.copy(data.grid.origin)
            output_shape = np.copy(data.grid.shape)
            output_affine = np.copy(data.grid.affine)
            for axis in range(3):
                output_origin[axis] -= np.dot(padding_voxels,
                                              data.grid.transform[axis, :])
                output_shape[axis] += 2 * padding_voxels[axis]
            output_affine[:3, 3] = output_origin
            output_grid = DataGrid(output_shape, output_affine)

        moving_data = np.zeros(list(output_shape) + [
            data.nvols,
        ])
        for vol in range(data.nvols):
            voldata = data.volume(vol)
            if padding > 0:
                voldata = np.pad(voldata, [(v, v) for v in padding_voxels],
                                 'constant',
                                 constant_values=0)
            shift = np.random.normal(scale=std_voxels, size=3)
            shifted_data = scipy.ndimage.interpolation.shift(voldata, shift)
            moving_data[..., vol] = shifted_data

        self.ivm.add(moving_data,
                     grid=output_grid,
                     name=output_name,
                     make_current=True)
Ejemplo n.º 17
0
def create_test_data(obj, shape=(10, 10, 10), nt=20, motion_scale=0.5):
    """
    Create test data

    Creates the following attributes on obj, each a Numpy array

     - grid
     - data_3d
     - data_4d
     - data_4d_moving
     - mask
    """
    shape = list(shape)
    centre = [float(v) / 2 for v in shape]

    obj.grid = DataGrid(shape, np.identity(4))
    obj.data_3d = np.zeros(shape, dtype=np.float32)
    obj.data_4d = np.zeros(shape + [
        nt,
    ], dtype=np.float32)
    obj.data_4d_moving = np.zeros(shape + [
        nt,
    ], dtype=np.float32)
    obj.mask = np.zeros(shape, dtype=np.int)

    for x in range(shape[0]):
        for y in range(shape[1]):
            for z in range(shape[2]):
                nx = 2 * float(x - centre[0]) / shape[0]
                ny = 2 * float(y - centre[1]) / shape[1]
                nz = 2 * float(z - centre[2]) / shape[2]
                d = math.sqrt(nx**2 + ny**2 + nz**2)
                obj.data_3d[x, y, z] = _test_fn(nx, ny, nz)
                obj.mask[x, y, z] = int(d < 0.5)
                for t in range(nt):
                    ft = float(t) / nt
                    obj.data_4d[x, y, z, t] = _test_fn(nx, ny, nz, ft)

    for t in range(nt):
        tdata = obj.data_4d[:, :, :, t]
        shift = np.random.normal(scale=motion_scale, size=3)
        odata = scipy.ndimage.interpolation.shift(tdata, shift)
        obj.data_4d_moving[:, :, :, t] = odata
Ejemplo n.º 18
0
    def testAngle45Squiffy(self):
        affine = np.identity(4)
        affine[1, 1] = 2
        affine[2, 2] = 3
        grid = DataGrid(self.data_3d.shape, affine)
        self.ivm.add(self.data_3d, grid=grid, name="data_3d")
        self.harmless_click(self.w._angle_btn)
        self.processEvents()
        self.ivl._pick(0, (2, 2, 2, 0))
        self.processEvents()
        self.ivl._pick(0, (4, 3, 2, 0))
        self.processEvents()
        self.ivl._pick(0, (2, 3, 2, 0))
        self.processEvents()
        self.assertFalse(self.error)

        regex = re.compile("angle.*\s+(\d+(\.\d+)?).*", re.IGNORECASE)
        m = re.match(regex, self.w._label.text())
        self.assertTrue(m is not None)
        self.assertAlmostEquals(float(m.groups()[0]), 45, delta=0.01)
Ejemplo n.º 19
0
 def testAddTwoMixed2(self):
     shape = [GRIDSIZE, GRIDSIZE, GRIDSIZE]
     grid = DataGrid(shape, np.identity(4))
     qpd = NumpyData(np.random.rand(*shape), name="test", grid=grid)
     qpd2 = NumpyData(np.random.randint(0, 10, size=shape),
                      name="test2",
                      grid=grid,
                      roi=True)
     self.assertFalse(qpd.roi)
     self.assertTrue(qpd2.roi)
     self.ivm.add(qpd2)
     self.ivm.add(qpd)
     self.assertEqual(len(self.ivm.data), 2)
     self.assertEqual(len(self.ivm.rois), 1)
     self.assertEqual(self.ivm.main, qpd2)
     self.assertEqual(self.ivm.current_data, qpd)
     self.assertTrue(self.ivm.current_roi is None)
     self.assertEqual(self.ivm.data["test"], qpd)
     self.assertEqual(self.ivm.data["test2"], qpd2)
     self.assertEqual(self.ivm.rois["test2"], qpd2)
Ejemplo n.º 20
0
    def testDistance3dNonSquiffy(self):
        """
        Distance with non-squiffy grid
        """
        affine = np.identity(4)
        affine[1, 1] = 2
        affine[2, 2] = 3
        grid = DataGrid(self.data_3d.shape, affine)

        self.ivm.add(self.data_3d, grid=grid, name="data_3d")
        self.harmless_click(self.w._dist_btn)
        self.processEvents()
        self.ivl._pick(0, (2, 2, 2, 0))
        self.processEvents()
        self.ivl._pick(0, (4, 3, 3, 0))
        self.processEvents()
        self.assertFalse(self.error)

        regex = re.compile("distance.*\s+(\d+(\.\d+)?)\s*mm", re.IGNORECASE)
        m = re.match(regex, self.w._label.text())
        self.assertTrue(m is not None)
        self.assertAlmostEquals(float(m.groups()[0]), 4.1231, delta=0.01)
Ejemplo n.º 21
0
    def run(self, options):
        data = self.get_data(options)
        if data.ndim != 4:
            raise QpException("Can only simulate motion on 4D data")

        output_name = options.pop("output-name", "%s_moving" % data.name)
        std = float(options.pop("std", "0"))
        std_voxels = [std / size for size in data.grid.spacing]
        std_degrees = float(options.pop("std_rot", "0"))
        order = int(options.pop("order", "1"))
        output_grid = data.grid
        output_shape = data.grid.shape

        padding = options.pop("padding", 0)
        if padding > 0:
            padding_voxels = [
                int(math.ceil(padding / size)) for size in data.grid.spacing
            ]
            for dim in range(3):
                if data.shape[dim] == 1:
                    padding_voxels[dim] = 0
            # Need to adjust the origin so the output data lines up with the input
            output_origin = np.copy(data.grid.origin)
            output_shape = np.copy(data.grid.shape)
            output_affine = np.copy(data.grid.affine)
            for axis in range(3):
                output_origin[axis] -= np.dot(padding_voxels,
                                              data.grid.transform[axis, :])
                output_shape[axis] += 2 * padding_voxels[axis]
            output_affine[:3, 3] = output_origin
            output_grid = DataGrid(output_shape, output_affine)

        moving_data = np.zeros(list(output_shape) + [
            data.nvols,
        ])
        centre_offset = output_shape / 2
        for vol in range(data.nvols):
            voldata = data.volume(vol)
            if padding > 0:
                voldata = np.pad(voldata, [(v, v) for v in padding_voxels],
                                 'constant',
                                 constant_values=0)
            shift = np.random.normal(scale=std_voxels, size=3)
            for dim in range(3):
                if voldata.shape[dim] == 1:
                    shift[dim] = 0
            shifted_data = scipy.ndimage.shift(voldata, shift, order=order)

            # Generate random rotation and scale it to the random angle
            required_angle = np.random.normal(scale=std_degrees, size=1)
            rot = scipy.spatial.transform.Rotation.random().as_rotvec()
            rot_angle = np.degrees(np.sqrt(np.sum(np.square(rot))))
            rot *= required_angle / rot_angle
            rot_matrix = scipy.spatial.transform.Rotation.from_rotvec(
                rot).as_matrix()

            offset = centre_offset - centre_offset.dot(rot_matrix)
            rotated_data = scipy.ndimage.affine_transform(shifted_data,
                                                          rot_matrix.T,
                                                          offset=offset,
                                                          order=order)
            moving_data[..., vol] = rotated_data

        self.ivm.add(moving_data,
                     grid=output_grid,
                     name=output_name,
                     make_current=True)
Ejemplo n.º 22
0
    def get_simulated_data(self,
                           data_model,
                           param_values,
                           output_param_maps=False):
        if len(param_values) != 1:
            raise QpException(
                "Can only have a single structure in the checkerboard model")
        param_values = list(param_values.values())[0]

        param_values_list = {}
        varying_params = []
        for param, values in param_values.items():
            if isinstance(values, (int, float)):
                values = [values]
            if len(values) > 1:
                varying_params.append(param)
            param_values_list[param] = values

        num_varying_params = len(varying_params)
        if num_varying_params > 3:
            raise QpException("At most 3 parameters can vary")
        elif num_varying_params == 0:
            # Make it a square for simplicity
            num_varying_params = 2

        voxels_per_patch = self.options.get("voxels-per-patch", 100)
        side_length = int(
            round(voxels_per_patch**(1.0 / float(num_varying_params))))
        patch_dims = [side_length] * num_varying_params
        while len(patch_dims) < 3:
            patch_dims += [
                1,
            ]

        repeats = [[0], [0], [0]]
        checkerboard_dims = []
        for idx, param in enumerate(varying_params):
            num_values = len(param_values_list[param])
            repeats[idx] = range(num_values)
            checkerboard_dims.append(patch_dims[idx] * num_values)
        for idx in range(len(varying_params), 3):
            checkerboard_dims.append(patch_dims[idx])

        output_data = None
        import itertools
        for indexes in itertools.product(*repeats):
            patch_values = dict(param_values)
            for idx, param in enumerate(varying_params):
                patch_values[param] = patch_values[param][indexes[idx]]

            timeseries = data_model.get_timeseries(patch_values)
            if output_data is None:
                output_data = np.zeros(list(checkerboard_dims) +
                                       [len(timeseries)],
                                       dtype=np.float32)
                if output_param_maps:
                    param_maps = {}
                    for param in param_values:
                        param_maps[param] = np.zeros(checkerboard_dims,
                                                     dtype=np.float32)

            slices = []
            for dim_idx, patch_idx in enumerate(indexes):
                dim_length = patch_dims[dim_idx]
                slices.append(
                    slice(patch_idx * dim_length,
                          (patch_idx + 1) * dim_length))
            output_data[slices] = timeseries
            if output_param_maps:
                for param, value in patch_values.items():
                    param_maps[param][slices] = value

        grid = DataGrid(checkerboard_dims, np.identity(4))
        sim_data = NumpyData(output_data, grid=grid, name="sim_data")

        if output_param_maps:
            for param in param_values:
                param_maps[param] = NumpyData(param_maps[param],
                                              grid=grid,
                                              name=param)
            return sim_data, param_maps
        else:
            return sim_data
Ejemplo n.º 23
0
def fslimage_to_qpdata(img, name=None):
    """ Convert fsl.data.image.Image to QpData """
    if not name: name = img.name
    return NumpyData(img.data,
                     grid=DataGrid(img.shape[:3], img.voxToWorldMat),
                     name=name)
Ejemplo n.º 24
0
    def run(self, options):
        data = self.get_data(options)
        if data.roi:
            default_order = 0
        else:
            default_order = 1
        order = options.pop("order", default_order)
        resample_type = options.pop("type", "data")
        output_name = options.pop("output-name", "%s_res" % data.name)
        grid_data = options.pop("grid", None)
        factor = options.pop("factor", None)
        only2d = options.pop("2d", None)

        # The different types of resampling require significantly different strategies
        #
        # Data->Data resampling is implemented in the QpData class although this will give
        # results which are not ideal when resampling to a lower resolution.
        # Upsampling can use scipy.ndimage.zoom
        # Downsampling is nore naturally implemented as a mean over subvoxels using Numpy slicing
        #
        # Note that factor is an integer for now. It could easily be a float for upsampling but
        # this would break the downsampling algorithm (and make it significanlty more complex to
        # implement)
        #
        # This is all pretty messy now especially with the '2d only' option.
        if resample_type == "data":
            if grid_data is None:
                raise QpException(
                    "Must provide 'grid' option to specify data item to get target grid from"
                )
            elif grid_data not in self.ivm.data:
                raise QpException("Data item '%s' not found" % grid_data)

            grid = self.ivm.data[grid_data].grid
            output_data = data.resample(grid, order=order)
        elif resample_type == "up":
            # Upsampling will need to use interpolation
            orig_data = data.raw()
            zooms = [factor for idx in range(3)]
            if only2d:
                zooms[2] = 1
            if data.ndim == 4:
                zooms.append(1)
            output_data = scipy.ndimage.zoom(orig_data, zooms, order=order)

            # Work out new grid origin
            voxel_offset = [
                float(factor - 1) / (2 * factor) for idx in range(3)
            ]
            if only2d:
                voxel_offset[2] = 0
            offset = data.grid.grid_to_world(voxel_offset, direction=True)
            output_affine = np.array(data.grid.affine)
            for idx in range(3):
                if idx < 2 or not only2d:
                    output_affine[:3, idx] /= factor
            output_affine[:3, 3] -= offset

            output_grid = DataGrid(output_data.shape[:3], output_affine)
            output_data = NumpyData(output_data,
                                    grid=output_grid,
                                    name=output_name)
        elif resample_type == "down":
            # Downsampling takes a mean of the voxels inside the new larger voxel
            # Only uses integral factor at present
            orig_data = data.raw()
            new_shape = [
                max(1, int(dim_size / factor))
                for dim_size in orig_data.shape[:3]
            ]
            if data.ndim == 4:
                new_shape.append(orig_data.shape[3])
            if only2d:
                new_shape[2] = orig_data.shape[2]

            # Note that output data must be float data type even if original data was integer
            output_data = np.zeros(new_shape, dtype=np.float32)
            num_samples = 0
            for start1 in range(factor):
                for start2 in range(factor):
                    for start3 in range(factor):
                        if start1 >= new_shape[
                                0] * factor or start2 >= new_shape[
                                    1] * factor or start3 >= new_shape[
                                        2] * factor:
                            continue
                        slices = [
                            slice(start1, new_shape[0] * factor, factor),
                            slice(start2, new_shape[1] * factor, factor),
                            slice(start3, new_shape[2] * factor, factor),
                        ]
                        if only2d:
                            slices[2] = slice(None)
                        downsampled_data = orig_data[slices]
                        output_data += downsampled_data
                        num_samples += 1
            output_data /= num_samples
            # FIXME this will not work for 2D data
            voxel_offset = [
                0.5 * (factor - 1), 0.5 * (factor - 1), 0.5 * (factor - 1)
            ]
            if only2d:
                voxel_offset[2] = 0
            offset = data.grid.grid_to_world(voxel_offset, direction=True)
            output_affine = np.array(data.grid.affine)
            for idx in range(3):
                if idx < 2 or not only2d:
                    output_affine[:3, idx] *= factor
            output_affine[:3, 3] += offset

            output_grid = DataGrid(output_data.shape[:3], output_affine)
            output_data = NumpyData(output_data,
                                    grid=output_grid,
                                    name=output_name)
        else:
            raise QpException("Unknown resampling type: %s" % resample_type)

        self.ivm.add(output_data,
                     name=output_name,
                     make_current=True,
                     roi=data.roi and order == 0)
Ejemplo n.º 25
0
class ImageView(QtGui.QSplitter, LogSource):
    """
    Widget containing three orthogonal slice views, two histogram/LUT widgets plus
    navigation sliders and data summary view.

    The viewer maintains two main pieces of data: a grid defining the main co-ordinate
    system of the viewer and a point of focus, in co-ordinates relative to the viewing grid.

    In addition, the viewer supports 'arrows' to mark positions in space, and variable
    pickers which control the selection of data.

    The grid is generally either a straightforward 1mm RAS grid, or an approximate RAS grid
    derived from the grid of the main data. Although the focus position is provided and set
    according to this grid by default, the ``focus`` and ``set_focus`` methods allow for
    the co-ordinates to be set or retrieved according to another arbitrary grid.

    :ivar grid: Grid the ImageView uses as the basis for the orthogonal slices.
                This is typically an RAS-aligned version of the main data grid, or
                alternatively an RAS world-grid
    """

    # Signals when point of focus is changed
    sig_focus_changed = QtCore.Signal(list)

    # Signals when the set of marker arrows has changed
    sig_arrows_changed = QtCore.Signal(list)

    # Signals when the picker mode is changed
    sig_picker_changed = QtCore.Signal(object)

    # Signals when a point is picked. Emission of this signal depends
    # on the picking mode selected
    sig_selection_changed = QtCore.Signal(object)

    def __init__(self, ivm, opts):
        LogSource.__init__(self)
        QtGui.QSplitter.__init__(self, QtCore.Qt.Vertical)

        self.grid = DataGrid([1, 1, 1], np.identity(4))
        self._pos = [0, 0, 0, 0]

        self.ivm = ivm
        self.opts = opts
        self.picker = PointPicker(self)
        self.arrows = []

        # Visualisation information for data and ROIs
        self.main_data_view = MainDataView(self.ivm)
        self.current_data_view = OverlayView(self.ivm)
        self.current_roi_view = RoiView(self.ivm)

        # Navigation controls layout
        control_box = QtGui.QWidget()
        vbox = QtGui.QVBoxLayout()
        vbox.setSpacing(5)
        control_box.setLayout(vbox)

        # Create the navigation sliders and the ROI/Overlay view controls
        vbox.addWidget(DataSummary(self))
        hbox = QtGui.QHBoxLayout()
        nav_box = NavigationBox(self)
        hbox.addWidget(nav_box)
        roi_box = RoiViewWidget(self, self.current_roi_view)
        hbox.addWidget(roi_box)
        ovl_box = OverlayViewWidget(self, self.current_data_view)
        hbox.addWidget(ovl_box)
        vbox.addLayout(hbox)

        # Histogram which controls colour map and levels for main volume
        self.main_data_view.histogram = MultiImageHistogramWidget(
            self, self.main_data_view, percentile=99)

        # Histogram which controls colour map and levels for data
        self.current_data_view.histogram = MultiImageHistogramWidget(
            self, self.current_data_view)

        # For each view window, this is the volume indices of the x, y and z axes for the view
        self.ax_map = [[0, 1, 2], [0, 2, 1], [1, 2, 0]]
        self.ax_labels = [("L", "R"), ("P", "A"), ("I", "S")]

        # Create three orthogonal views
        self.ortho_views = {}
        for i in range(3):
            win = OrthoView(self, self.ivm, self.ax_map[i], self.ax_labels)
            win.sig_pick.connect(self._pick)
            win.sig_drag.connect(self._drag)
            win.sig_doubleclick.connect(self._toggle_maximise)
            win.add_data_view(self.main_data_view)
            win.add_data_view(self.current_data_view)
            win.add_data_view(self.current_roi_view)
            self.ortho_views[win.zaxis] = win

        # Main graphics layout
        #gview = pg.GraphicsView(background='k')
        gview = QtGui.QWidget()
        self.layout_grid = QtGui.QGridLayout()
        self.layout_grid.setHorizontalSpacing(2)
        self.layout_grid.setVerticalSpacing(2)
        self.layout_grid.setContentsMargins(0, 0, 0, 0)
        self.layout_grid.addWidget(
            self.ortho_views[1],
            0,
            0,
        )
        self.layout_grid.addWidget(self.ortho_views[0], 0, 1)
        self.layout_grid.addWidget(self.main_data_view.histogram, 0, 2)
        self.layout_grid.addWidget(self.ortho_views[2], 1, 0)
        self.layout_grid.addWidget(self.current_data_view.histogram, 1, 2)
        self.layout_grid.setColumnStretch(0, 3)
        self.layout_grid.setColumnStretch(1, 3)
        self.layout_grid.setColumnStretch(2, 1)
        self.layout_grid.setRowStretch(0, 1)
        self.layout_grid.setRowStretch(1, 1)
        gview.setLayout(self.layout_grid)
        self.addWidget(gview)
        self.addWidget(control_box)
        self.setStretchFactor(0, 5)
        self.setStretchFactor(1, 1)

        self.ivm.sig_main_data.connect(self._main_data_changed)
        self.opts.sig_options_changed.connect(self._opts_changed)

    def focus(self, grid=None):
        """
        Get the current focus position

        :param grid: Report position using co-ordinates relative to this grid.
                     If not specified, report current view grid co-ordinates
        :return: 4D sequence containing position plus the current data volume index
        """
        if grid is None:
            return list(self._pos)
        else:
            world = self.grid.grid_to_world(self._pos)
            return list(grid.world_to_grid(world))

    def set_focus(self, pos, grid=None):
        """
        Set the current focus position

        :param grid: Specify position using co-ordinates relative to this grid.
                     If not specified, position is in current view grid co-ordinates
        """
        if grid is not None:
            world = grid.grid_to_world(pos)
            pos = self.grid.world_to_grid(world)

        self._pos = list(pos)
        if len(self._pos) != 4:
            raise Exception("Position must be 4D")

        self.debug("Cursor position: %s", self._pos)
        self.sig_focus_changed.emit(self._pos)

    def set_picker(self, pickmode):
        """
        Set the picking mode

        :param pickmode: Picking mode from :class:`PickMode`
        """
        self.picker.cleanup()
        self.picker = PICKERS[pickmode](self)
        self.sig_picker_changed.emit(self.picker)

    def add_arrow(self, pos, grid=None, col=None):
        """
        Add an arrow to mark a particular position

        :param pos:  Position co-ordinates
        :param grid: Grid co-ordinates are relative to, if not specified
                     uses viewing grid
        :param col:  Colour as RGB sequence, if not specified uses a default
        """
        if grid is not None:
            world = grid.grid_to_world(pos)
            pos = self.grid.world_to_grid(world)

        if col is None:
            # Default to grey arrow
            col = [127, 127, 127]

        self.arrows.append((pos, col))
        self.sig_arrows_changed.emit(self.arrows)

    def remove_arrows(self):
        """
        Remove all the arrows that have been placed
        """
        self.arrows = []
        self.sig_arrows_changed.emit(self.arrows)

    def capture_view_as_image(self, window, outputfile):
        """
        Export an image using pyqtgraph

        FIXME this is not working at the moment
        """
        if window not in (1, 2, 3):
            raise RuntimeError("No such window: %i" % window)

        expimg = self.ortho_views[window - 1].img
        exporter = ImageExporter(expimg)
        exporter.parameters()['width'] = 2000
        exporter.export(str(outputfile))

    def redraw(self):
        """
        Redraw the view, e.g. on data change

        This is a hack
        """
        for view in self.ortho_views.values():
            view.force_redraw = True
        self.sig_focus_changed.emit(self._pos)

    def _pick(self, win, pos):
        """
        Called when a point is picked in one of the viewing windows
        """
        self.picker.pick(win, pos)
        self.sig_selection_changed.emit(self.picker)

    def _drag(self, win, pos):
        """
        Called when a drag selection is changed in one of the viewing windows
        """
        self.picker.drag(win, pos)
        self.sig_selection_changed.emit(self.picker)

    def _toggle_maximise(self, win, state=-1):
        """
        Maximise/Minimise view window
        If state=1, maximise, 0=show all, -1=toggle
        """
        win1 = (win + 1) % 3
        win2 = (win + 2) % 3
        if state == 1 or (state == -1 and self.ortho_views[win1].isVisible()):
            # Maximise
            self.layout_grid.addWidget(self.ortho_views[win], 0, 0, 2, 2)
            self.ortho_views[win1].setVisible(False)
            self.ortho_views[win2].setVisible(False)
            self.ortho_views[win].setVisible(True)
        elif state == 0 or (state == -1
                            and not self.ortho_views[win1].isVisible()):
            # Show all three
            self.layout_grid.addWidget(self.ortho_views[1], 0, 0)
            self.layout_grid.addWidget(self.ortho_views[0], 0, 1)
            self.layout_grid.addWidget(self.ortho_views[2], 1, 0)
            for oview in range(3):
                self.ortho_views[oview].setVisible(True)
                self.ortho_views[oview].update()

    def _opts_changed(self):
        z_roi = int(self.opts.display_order == self.opts.ROI_ON_TOP)
        self.current_roi_view.set("z_value", z_roi)
        self.current_data_view.set("z_value", 1 - z_roi)
        self.current_data_view.set("interp_order", self.opts.interp_order)

    def _main_data_changed(self, data):
        if data is not None:
            self.grid = data.grid.get_standard()
            self.debug("Main data raw grid")
            self.debug(data.grid.affine)
            self.debug("RAS aligned")
            self.debug(self.grid.affine)

            initial_focus = [int(v / 2)
                             for v in data.grid.shape] + [int(data.nvols / 2)]
            self.debug("Initial focus (data): %s", initial_focus)
            self.set_focus(initial_focus, grid=data.grid)
            self.debug("Initial focus (std): %s", self._pos)
            # If one of the dimensions has size 1 the data is 2D so
            # maximise the relevant slice
            self._toggle_maximise(0, state=0)
            data_axes = data.grid.get_ras_axes()
            for idx in range(3):
                self.ortho_views[idx].reset()
                if data.grid.shape[data_axes[idx]] == 1:
                    self._toggle_maximise(idx, state=1)
Ejemplo n.º 26
0
    def __init__(self, ivm, opts):
        LogSource.__init__(self)
        QtGui.QSplitter.__init__(self, QtCore.Qt.Vertical)

        self.grid = DataGrid([1, 1, 1], np.identity(4))
        self._pos = [0, 0, 0, 0]

        self.ivm = ivm
        self.opts = opts
        self.picker = PointPicker(self)
        self.arrows = []

        # Visualisation information for data and ROIs
        self.main_data_view = MainDataView(self.ivm)
        self.current_data_view = OverlayView(self.ivm)
        self.current_roi_view = RoiView(self.ivm)

        # Navigation controls layout
        control_box = QtGui.QWidget()
        vbox = QtGui.QVBoxLayout()
        vbox.setSpacing(5)
        control_box.setLayout(vbox)

        # Create the navigation sliders and the ROI/Overlay view controls
        vbox.addWidget(DataSummary(self))
        hbox = QtGui.QHBoxLayout()
        nav_box = NavigationBox(self)
        hbox.addWidget(nav_box)
        roi_box = RoiViewWidget(self, self.current_roi_view)
        hbox.addWidget(roi_box)
        ovl_box = OverlayViewWidget(self, self.current_data_view)
        hbox.addWidget(ovl_box)
        vbox.addLayout(hbox)

        # Histogram which controls colour map and levels for main volume
        self.main_data_view.histogram = MultiImageHistogramWidget(
            self, self.main_data_view, percentile=99)

        # Histogram which controls colour map and levels for data
        self.current_data_view.histogram = MultiImageHistogramWidget(
            self, self.current_data_view)

        # For each view window, this is the volume indices of the x, y and z axes for the view
        self.ax_map = [[0, 1, 2], [0, 2, 1], [1, 2, 0]]
        self.ax_labels = [("L", "R"), ("P", "A"), ("I", "S")]

        # Create three orthogonal views
        self.ortho_views = {}
        for i in range(3):
            win = OrthoView(self, self.ivm, self.ax_map[i], self.ax_labels)
            win.sig_pick.connect(self._pick)
            win.sig_drag.connect(self._drag)
            win.sig_doubleclick.connect(self._toggle_maximise)
            win.add_data_view(self.main_data_view)
            win.add_data_view(self.current_data_view)
            win.add_data_view(self.current_roi_view)
            self.ortho_views[win.zaxis] = win

        # Main graphics layout
        #gview = pg.GraphicsView(background='k')
        gview = QtGui.QWidget()
        self.layout_grid = QtGui.QGridLayout()
        self.layout_grid.setHorizontalSpacing(2)
        self.layout_grid.setVerticalSpacing(2)
        self.layout_grid.setContentsMargins(0, 0, 0, 0)
        self.layout_grid.addWidget(
            self.ortho_views[1],
            0,
            0,
        )
        self.layout_grid.addWidget(self.ortho_views[0], 0, 1)
        self.layout_grid.addWidget(self.main_data_view.histogram, 0, 2)
        self.layout_grid.addWidget(self.ortho_views[2], 1, 0)
        self.layout_grid.addWidget(self.current_data_view.histogram, 1, 2)
        self.layout_grid.setColumnStretch(0, 3)
        self.layout_grid.setColumnStretch(1, 3)
        self.layout_grid.setColumnStretch(2, 1)
        self.layout_grid.setRowStretch(0, 1)
        self.layout_grid.setRowStretch(1, 1)
        gview.setLayout(self.layout_grid)
        self.addWidget(gview)
        self.addWidget(control_box)
        self.setStretchFactor(0, 5)
        self.setStretchFactor(1, 1)

        self.ivm.sig_main_data.connect(self._main_data_changed)
        self.opts.sig_options_changed.connect(self._opts_changed)