-
Notifications
You must be signed in to change notification settings - Fork 0
/
volume_explorer.py
133 lines (109 loc) · 4.91 KB
/
volume_explorer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec as gs
from matplotlib.widgets import Slider
from imutils import load_volume, img_as_uint8
def coronal(volume, index):
'''
Return a coronal slice of the given volume
'''
return np.rot90(volume[index, ...])
def sagittal(volume, index):
'''
Return a sagittal slice of the given volume
'''
return np.rot90(volume[:, index, ...])
def transverse(volume, index):
'''
Return a transverse slice of the given volume
'''
return volume[:, :, index, ...]
class VolumeExplorer:
'''
Allows for exploration of a 3D volume via coronal, sagittal, and transverse slices
'''
def __init__(self, volume, cmap=None, clim=None):
# Volume to render
self._volume = img_as_uint8(volume)
# Data axes
grid = gs.GridSpec(nrows=2, ncols=3, height_ratios=[8, 1])
self._fig = plt.figure()
# self._fig.set_size_inches(11, 8.5)
self._cor_ax = self._fig.add_subplot(grid[0, 0])
self._cor_ax.set_title('Coronal')
self._sag_ax = self._fig.add_subplot(grid[0, 1])
self._sag_ax.set_title('Sagittal')
self._trans_ax = self._fig.add_subplot(grid[0, 2])
self._trans_ax.set_title('Transverse')
for ax in (self._cor_ax, self._sag_ax, self._trans_ax):
ax.axis('off')
plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05, wspace=0.3, hspace=0.1)
# Depth sliders
self._cor_slider = Slider(self._fig.add_subplot(grid[1, 0]), '', 1, volume.shape[0], valinit=1, valstep=1, valfmt='%d')
self._cor_slider.on_changed(self._update_cor)
self._sag_slider = Slider(self._fig.add_subplot(grid[1, 1]), '', 1, volume.shape[1], valinit=1, valstep=1, valfmt='%d')
self._sag_slider.on_changed(self._update_sag)
self._trans_slider = Slider(self._fig.add_subplot(grid[1, 2]), '', 1, volume.shape[2], valinit=1, valstep=1, valfmt='%d')
self._trans_slider.on_changed(self._update_trans)
# Initialize images
self._cor_img = self._cor_ax.imshow(coronal(self._volume, 0))
self._sag_img = self._sag_ax.imshow(sagittal(self._volume, 0))
self._trans_img = self._trans_ax.imshow(transverse(self._volume, 0))
self.set_clim(clim)
self.set_cmap(cmap)
# Initialize navigation crosshairs
self._cor_vline = self._cor_ax.axvline(0, color='r')
self._cor_hline = self._cor_ax.axhline(self._volume.shape[2] - 1, color='b')
self._sag_vline = self._sag_ax.axvline(self._volume.shape[0] - 1, color='g')
self._sag_hline = self._sag_ax.axhline(self._volume.shape[2] - 1, color='b')
self._trans_vline = self._trans_ax.axvline(0, color='r')
self._trans_hline = self._trans_ax.axhline(self._volume.shape[0] - 1, color='g')
@staticmethod
def from_directory(directory):
return VolumeExplorer(load_volume(directory))
def set_cmap(self, cmap):
self._cor_img.set_cmap(cmap)
self._sag_img.set_cmap(cmap)
self._trans_img.set_cmap(cmap)
self._draw()
def set_clim(self, clim):
clim = clim if clim else (np.min(self._volume), np.max(self._volume))
self._cor_img.set_clim(clim[0], clim[1])
self._sag_img.set_clim(clim[0], clim[1])
self._trans_img.set_clim(clim[0], clim[1])
def start(self):
plt.show()
def _update_cor(self, idx):
idx -= 1
self._cor_img.set_data(coronal(self._volume, self._volume.shape[0] - 1 - int(idx))) # reverse to match orientation
self._sag_vline.set_xdata(self._volume.shape[0] - 1 - idx)
self._trans_hline.set_ydata(self._volume.shape[0] - 1 - idx) # reverse to match orientation
self._draw()
def _update_sag(self, idx):
idx -= 1
self._sag_img.set_data(sagittal(self._volume, int(idx)))
self._cor_vline.set_xdata(idx)
self._trans_vline.set_xdata(idx)
self._draw()
def _update_trans(self, idx):
idx -= 1
self._trans_img.set_data(transverse(self._volume, int(idx)))
self._cor_hline.set_ydata(self._volume.shape[2] - 1 - idx) # reverse to match orientation
self._sag_hline.set_ydata(self._volume.shape[2] - 1 - idx)
self._draw()
def _draw(self):
self._fig.canvas.draw()
plt.pause(0.005)
if __name__ == '__main__':
import argparse
# Parse arguments
PARSER = argparse.ArgumentParser(description='Volume Explorer')
PARSER.add_argument('directory', help='Directory containing transverse slices of a volume')
PARSER.add_argument('--cmap', default=None, help='Colour map (ignored if input is RGB)')
PARSER.add_argument('--clim', default=None, help='Colour limits')
ARGS = PARSER.parse_args()
# Start explorer
EXPLORER = VolumeExplorer.from_directory(ARGS.directory)
EXPLORER.set_cmap(ARGS.cmap)
EXPLORER.set_clim(ARGS.clim)
EXPLORER.start()