diff --git a/nibabel/tests/test_viewers.py b/nibabel/tests/test_viewers.py index 72d839c92..dff93926d 100644 --- a/nibabel/tests/test_viewers.py +++ b/nibabel/tests/test_viewers.py @@ -134,3 +134,203 @@ def test_viewer_nonRAS(): assert_array_equal(sag, data1[6, :, :]) assert_array_equal(cor, data1[:, :, 32].T) assert_array_equal(axi, data1[:, 13, :].T) + + + +@needs_mpl +def test_viewer_nonRAS_on_mouse(): + """ + test on_mouse selection on non RAS matrices + + """ + # This affine simulates an acquisition on a quadruped subject that is in a prone position. + # This corresponds to an acquisition with: + # - LR inverted on scanner x (i) + # - IS on scanner y (j) + # - PA on scanner z (k) + # This example enables to test also OrthoSlicer3D properties `_flips` and `_order`. + + (I, J, K) = (10, 20, 40) + data1 = np.random.rand(I, J, K) + (i_target, j_target, k_target) = (2, 14, 12) + i1 = i_target - 2 + i2 = i_target + 2 + j1 = j_target - 3 + j2 = j_target + 3 + k1 = k_target - 4 + k2 = k_target + 4 + data1[i1: i2 + 1, j1: j2 + 1, k1: k2 + 1] = 0 + data1[i_target, j_target, k_target] = 1 + valp1 = 1.5 + valm1 = 0.5 + data1[i_target - 1, j_target, k_target] = valp1 # x flipped + data1[i_target + 1, j_target, k_target] = valm1 # x flipped + data1[i_target, j_target - 1, k_target] = valm1 + data1[i_target, j_target + 1, k_target] = valp1 + data1[i_target, j_target, k_target - 1] = valm1 + data1[i_target, j_target, k_target + 1] = valp1 + + aff1 = np.array([[-1, 0, 0, 5], + [0, 0, 1, -10], + [0, 1, 0, -30], + [0, 0, 0, 1]]) + + o1 = OrthoSlicer3D(data1, aff1) + + class Event: + def __init__(self): + self.name = "simulated mouse event" + self.button = 1 + + event = Event() + event.xdata = k_target + event.ydata = j_target + event.inaxes = o1._ims[0].axes + o1._on_mouse(event) + + event.inaxes = o1._ims[1].axes + event.xdata = (I - 1) - i_target # x flipped + event.ydata = j_target + o1._on_mouse(event) + + event.inaxes = o1._ims[2].axes + event.xdata = (I - 1) - i_target # x flipped + event.ydata = k_target + o1._on_mouse(event) + + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + + assert_array_equal(sag, data1[i_target, :, :]) # + assert_array_equal(cor, data1[::-1, :, k_target].T) # x flipped + assert_array_equal(axi, data1[::-1, j_target, :].T) # x flipped + return None + + +@needs_mpl +def test_viewer_nonRAS_on_scroll(): + """ + test scrolling on non RAS matrices + + """ + # This affine simulates an acquisition on a quadruped subject that is in a prone position. + # This corresponds to an acquisition with: + # - LR inverted on scanner x (i) + # - IS on scanner y (j) + # - PA on scanner z (k) + # This example enables to test also OrthoSlicer3D properties `_flips` and `_order`. + + (I, J, K) = (10, 20, 40) + data1 = np.random.rand(I, J, K) + (i_target, j_target, k_target) = (2, 14, 12) + i1 = i_target - 2 + i2 = i_target + 2 + j1 = j_target - 3 + j2 = j_target + 3 + k1 = k_target - 4 + k2 = k_target + 4 + data1[i1: i2 + 1, j1: j2 + 1, k1: k2 + 1] = 0 + data1[i_target, j_target, k_target] = 1 + valp1 = 1.5 + valm1 = 0.5 + data1[i_target - 1, j_target, k_target] = valp1 # x flipped + data1[i_target + 1, j_target, k_target] = valm1 # x flipped + data1[i_target, j_target - 1, k_target] = valm1 + data1[i_target, j_target + 1, k_target] = valp1 + data1[i_target, j_target, k_target - 1] = valm1 + data1[i_target, j_target, k_target + 1] = valp1 + + aff1 = np.array([[-1, 0, 0, 5], + [0, 0, 1, -10], + [0, 1, 0, -30], + [0, 0, 0, 1]]) + + o1 = OrthoSlicer3D(data1, aff1) + + class Event: + def __init__(self): + self.name = "simulated mouse event" + self.button = None + self.key = None + + i_last = data1.shape[0] - 1 + + [x_t, y_t, z_t] = list(aff1.dot(np.array([i_target, j_target, k_target, 1]))[:3]) + # print(x_t, y_t, z_t) + # scanner positions are x_t=3, y_t=2, z_t=16 + + event = Event() + + # Sagittal plane - one scroll up + # x coordinate is flipped so index decrease by 1 + o1.set_position(x_t, y_t, z_t) + event.inaxes = o1._ims[0].axes + event.button = 'up' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target - 1, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target].T) # ::-1 because the array is flipped in x + assert_array_equal(axi, data1[::-1, j_target, :].T) # ::-1 because the array is flipped in x + + # Sagittal plane - one scrolled down + o1.set_position(x_t, y_t, z_t) + event.button = 'down' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target + 1, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target].T) + assert_array_equal(axi, data1[::-1, j_target, :].T) + + # Coronal plane - one scroll up + # y coordinate is increase by 1 + o1.set_position(x_t, y_t, z_t) + event.inaxes = o1._ims[1].axes + event.button = 'up' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target + 1].T) # ::-1 because the array is flipped in x + assert_array_equal(axi, data1[::-1, j_target, :].T) # ::-1 because the array is flipped in x + + # Coronal plane - one scrolled down + o1.set_position(x_t, y_t, z_t) + event.button = 'down' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target - 1].T) + assert_array_equal(axi, data1[::-1, j_target, :].T) + + # Axial plane - one scroll up + # y is increase by 1 + o1.set_position(x_t, y_t, z_t) + event.inaxes = o1._ims[2].axes + event.button = 'up' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target].T) # ::-1 because the array is flipped in x + assert_array_equal(axi, data1[::-1, j_target + 1, :].T) # ::-1 because the array is flipped in x + + # Axial plane - one scrolled down + o1.set_position(x_t, y_t, z_t) + event.button = 'down' + o1._on_scroll(event) + sag = o1._ims[0].get_array() + cor = o1._ims[1].get_array() + axi = o1._ims[2].get_array() + assert_array_equal(sag, data1[i_target, :, :]) + assert_array_equal(cor, data1[::-1, :, k_target].T) + assert_array_equal(axi, data1[::-1, j_target - 1, :].T) + return None \ No newline at end of file diff --git a/nibabel/viewers.py b/nibabel/viewers.py index 0dc2f0daf..07881eb69 100644 --- a/nibabel/viewers.py +++ b/nibabel/viewers.py @@ -103,7 +103,7 @@ def __init__(self, data, affine=None, axes=None, title=None): # | | | | # | | | | # +---------+ +---------+ - # A --> <-- R + # A --> R --> # ^ +---------+ +---------+ # | | | | | # | Axial | | Vol | @@ -111,7 +111,7 @@ def __init__(self, data, affine=None, axes=None, title=None): # | | | | # | | | | # +---------+ +---------+ - # <-- R <-- t --> + # R --> <-- t --> fig, axes = plt.subplots(2, 2) fig.set_size_inches((8, 8), forward=True) @@ -419,7 +419,7 @@ def _set_position(self, x, y, z, notify=True): # deal with crosshairs loc = self._data_idx[ii] if self._flips[ii]: - loc = self._sizes[ii] - loc + loc = self._sizes[ii] - 1 - loc loc = [loc] * 2 if ii == 0: self._crosshairs[2]['vert'].set_xdata(loc) @@ -468,12 +468,17 @@ def _on_scroll(self, event): dv *= 1.0 if event.button == 'up' else -1.0 dv *= -1 if self._flips[ii] else 1 val = self._data_idx[ii] + dv + if ii == 3: self._set_volume_index(val) else: - coords = [self._data_idx[k] for k in range(3)] + [1.0] + coords = [self._data_idx[k] for k in range(3)] coords[ii] = val - self._set_position(*np.dot(self._affine, coords)[:3]) + coords_ordered = [0, 0, 0, 1] + for k in range(3): + coords_ordered[self._order[k]] = coords[k] + position = np.dot(self._affine, coords_ordered)[:3] + self._set_position(*position) self._draw() def _on_mouse(self, event): @@ -488,18 +493,19 @@ def _on_mouse(self, event): self._set_volume_index(event.xdata) else: # translate click xdata/ydata to physical position - xax, yax = [[1, 2], [0, 2], [0, 1]][ii] + xax, yax = [[self._order[1], self._order[2]], + [self._order[0], self._order[2]], + [self._order[0], self._order[1]]][ii] x, y = event.xdata, event.ydata - x = self._sizes[xax] - x if self._flips[xax] else x - y = self._sizes[yax] - y if self._flips[yax] else y + x = self._sizes[xax] - x - 1 if self._flips[xax] else x + y = self._sizes[yax] - y - 1 if self._flips[yax] else y idxs = np.ones(4) idxs[xax] = x idxs[yax] = y - idxs[ii] = self._data_idx[ii] - idxs[:3] = idxs[self._order] - self._set_position(*np.dot(self._affine, idxs)[:3]) + idxs[self._order[ii]] = self._data_idx[ii] + self._set_position(*np.dot(self._affine, idxs)[:3]) self._draw() - + def _on_keypress(self, event): """Handle mpl keypress events""" if event.key is not None and 'escape' in event.key: