This program was running fine in chaco 3.2, but with chaco 4, scrollbar does not show at all.
I would like either to find the problem or find a workaround.
PanTool may be a workaround, but this will conflict with some linecursors used with mouse.
#!/usr/bin/env python
# Major library imports
from numpy import linspace
from scipy.special import jn
# Enthought library imports
from enthought.enable.api import Component, ComponentEditor
from enthought.traits.api import HasTraits, Instance
from enthought.traits.ui.api import Item, Group, View
# Chaco imports
from enthought.chaco.api import ArrayPlotData, VPlotContainer, \
Plot, OverlayPlotContainer, add_default_axes, add_default_grids
from enthought.chaco.plotscrollbar import PlotScrollBar
from enthought.chaco.tools.api import PanTool, ZoomTool
#===============================================================================
# # Create the Chaco plot.
#===============================================================================
def _create_plot_component():
# Create some x-y data series to plot
x = linspace(-2.0, 10.0, 100)
pd = ArrayPlotData(index = x)
for i in range(5):
pd.set_data("y" + str(i), jn(i,x))
# Create some line plots of some of the data
plot1 = Plot(pd)
plot1.plot(("index", "y0", "y1", "y2"), name="j_n, n<3", color="red")[0]
p = plot1.plot(("index", "y3"), name="j_3", color="blue")[0]
# Add the scrollbar
plot1.padding_top = 0
p.index_range.high_setting = 1
# Create a container and add our plots
container = OverlayPlotContainer(padding = 5,fill_padding = True,
bgcolor = "lightgray", use_backbuffer=True)
hscrollbar = PlotScrollBar(component=p, mapper=p.index_mapper,axis="index", resizable="",use_backbuffer = False,
height=15,position=(0,0))
hscrollbar.force_data_update()
plot1.overlays.append(hscrollbar)
hgrid,vgrid = add_default_grids(plot1)
add_default_axes(plot1)
container.add(plot1)
container.invalidate_and_redraw()
return container
#===============================================================================
# Attributes to use for the plot view.
size=(900,500)
title="Scrollbar example"
#===============================================================================
# # Demo class that is used by the demo.py application.
#===============================================================================
class Demo(HasTraits):
plot = Instance(Component)
traits_view = View(
Group(
Item('plot', editor=ComponentEditor(size=size),
show_label=False),
orientation = "vertical"),
resizable=True, title=title
)
def _plot_default(self):
return _create_plot_component()
demo = Demo()
if __name__ == "__main__":
demo.configure_traits()
#--EOF---
We investigated and found some problems in the code of enable api (https://github.com/enthought/enable), that had disallowed the scrollbar to display in wx backend.
The following patch solves the problem.
There are other issues, like height setting that does not work, we will continue to investigate.
diff --git a/enable/wx/scrollbar.py b/enable/wx/scrollbar.py
index 02d0da0..003cc90 100644
--- a/enable/wx/scrollbar.py
+++ b/enable/wx/scrollbar.py
## -136,7 +136,7 ##
# We have to do this flip_y business because wx and enable use opposite
# coordinate systems, and enable defines the component's position as its
# lower left corner, while wx defines it as the upper left corner.
- window = getattr(gc, "window", None)
+ window = getattr(self, "window", None)
if window is None:
return
wx_ypos = window._flip_y(wx_ypos)
Related
We develop app which should display earth map with satellite and satellite track. That's how plot looks like
We are using python and PySide6.QT for UI manipulation, for earth map plot with satellite we are using GroundTrackPotter. That library makes plot in browser.
I need put plot in central widget**(check image)
Class for app
class Window(QWidget, QQmlApplicationEngine):
def __init__(self):
super().__init__()
border_layout = BorderLayout()
plot_3d = self.get_3d_lpot()
border_layout.addWidget(plot_3d, Position.Center)
satellite_dropdown = self.create_dropdown_satellite()
border_layout.addWidget(satellite_dropdown, Position.West)
satellite_info = self.get_satellite_info()
border_layout.addWidget(satellite_info, Position.South)
self.setLayout(border_layout)
self.setWindowTitle("Satellite tracker")
#staticmethod
def create_label(text: str):
label = QLabel(text)
label.setFrameStyle(QFrame.Box | QFrame.Raised)
return label
#staticmethod
def create_dropdown_satellite():
widget = QComboBox()
widget.addItems(["KITSUNE", "BEESAT", "ITUPSAT"])
return widget
#staticmethod
def get_satellite_info():
satellite_info = satellite_state.get_satellite_param_string()
widget = QLabel(satellite_info)
return widget
#staticmethod
def get_3d_lpot():
*Here I need create widget for earth plot
return widget
Earth plotting:
gp = GroundtrackPlotter()
def get_orbit_plot(orbit: Orbit):
# Build spacecraft instance
satellite_spacecraft = EarthSatellite(orbit, None)
t_span = time_range(start=orbit.epoch - 1.5 * u.h, periods=150, end=orbit.epoch + 1.5 * u.h)
# Generate an instance of the plotter, add title and show latlon grid
gp.update_layout(title="International Space Station groundtrack")
# Plot previously defined EarthSatellite object
gp.plot(
satellite_spacecraft,
t_span,
label="Satellite",
color="red",
marker={"size": 10, "symbol": "triangle-right", "line": {"width": 1, "color": "black"}},
)
# For building geo traces
import plotly.graph_objects as go
# Faculty of Radiophysics and Computer Technologies coordinates
STATION = [53.83821551524637, 27.476136409973797] * u.deg
# Let us add a new trace in original figure
gp.add_trace(
go.Scattergeo(
lat=STATION[0],
lon=STATION[-1],
name="Faculty of Radiophysics and Computer Technologies",
marker={"color": "blue"},
)
)
gp.fig.show()
# Switch to three dimensional representation
gp.update_geos(projection_type="orthographic")
gp.fig.show()
def get_gp_value():
return gp
You can generate html using Figure from plotly and embed it in a QWebEngineView:
from astropy import units as u
from poliastro.earth import EarthSatellite
from poliastro.earth.plotting import GroundtrackPlotter
from poliastro.examples import iss
from poliastro.util import time_range
import plotly
from PySide6.QtWidgets import QApplication
from PySide6.QtWebEngineWidgets import QWebEngineView
def build_plot(fig):
html = "".join(
[
"<html><body>",
plotly.offline.plot(fig, output_type="div", include_plotlyjs="cdn"),
"</body></html>",
]
)
return html
def create_plot():
satellite_spacecraft = EarthSatellite(iss, None)
t_span = time_range(iss.epoch - 1.5 * u.h, periods=150, end=iss.epoch + 1.5 * u.h)
gp = GroundtrackPlotter()
gp.update_layout(title="International Space Station groundtrack")
# Plot previously defined EarthSatellite object
gp.plot(
satellite_spacecraft,
t_span,
label="Satellite",
color="red",
marker={"size": 10, "symbol": "triangle-right", "line": {"width": 1, "color": "black"}},
)
# For building geo traces
import plotly.graph_objects as go
# Faculty of Radiophysics and Computer Technologies coordinates
STATION = [53.83821551524637, 27.476136409973797] * u.deg
# Let us add a new trace in original figure
gp.add_trace(
go.Scattergeo(
lat=STATION[0],
lon=STATION[-1],
name="Faculty of Radiophysics and Computer Technologies",
marker={"color": "blue"},
)
)
# Switch to three dimensional representation
gp.update_geos(projection_type="orthographic")
return gp.fig
def main():
app = QApplication([])
fig = create_plot()
html = build_plot(fig)
view = QWebEngineView()
view.setHtml(html)
view.resize(640, 480)
view.show()
app.exec()
if __name__ == "__main__":
main()
This sample is altered from the bokeh example, sliders can control the bars in the figure. (sliders.py)
This sample is altered from the bokeh example, sliders can control the bars in the figure. (sliders.py)
In my situation, there are more than 30 sliders on the left. It seems a little messy, so I am trying to use bokeh select element to connect sliders. The aim is the slider area will show only one slider when I select.
I read the document, and there are two ways to use:
One is disabled. If True, the widget will be greyed-out and not responsive to UI events. But it would not hide. Another is visible, but I got an attribute error:
AttributeError("unexpected attribute 'visible' to Slider, similar attributes are disabled")
Is it possible to make bokeh sliders hide or invisible? Or is there any other way to make sliders (more than 30) distinguish more clearly?
Here is the code which can run in Jupyter notebook
import bokeh.plotting.figure as bk_figure
from bokeh.io import curdoc, show
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, Select
from bokeh.models.widgets import Slider, TextInput
from bokeh.io import output_notebook # enables plot interface in J notebook
import numpy as np
# init bokeh
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
output_notebook()
# Set up data
data = {
'line_x' : [1,2,3,4],
'line_y' : [4,3,2,1],
'bar_x':[1, 2, 3, 4],
'bar_bottom':[1,1,1,1],
'bar_top':[0.2, 2.5, 3.7, 4],
}
#bar_color
determine_top = data['bar_top']
determine_bottom = data['bar_bottom']
determine_color = []
for i in range(0,4):
if (determine_top[i] > determine_bottom[i]):
determine_color.append('#B3DE69') #green
else:
determine_color.append('firebrick')
i+=1
data['determine_colors'] = determine_color
source = ColumnDataSource(data=data)
# Set up plot
plot = bk_figure(plot_height=400, plot_width=400, title="test",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 10], y_range=[-5, 5])
plot.vbar(x='bar_x', width=0.5, top='bar_top',bottom='bar_bottom',
source=source, color='determine_colors')
# Set up widgets
select = Select(title="days_select:",
options=["d1_select", "d2_select", "d3_select", "d4_select"])
d1 = Slider(title="d1", value=0.0, start=-5.0, end=5.0, step=0.1)
d2 = Slider(title="d2", value=1.0, start=-5.0, end=5.0, step=0.1)
d3 = Slider(title="d3", value=0.0, start=0.0, end=2*np.pi)
d4 = Slider(title="d4", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_data(attrname, old, new):
# Get the current slider values
d1_value = d1.value
d2_value = d2.value
d3_value = d3.value
d4_value = d4.value
##select
#if select.value == "d2_select":
# d3.disabled = True
# d4.visible = False
# Generate the new curve
new_data = {
'line_x' : [1,2,3,4],
'line_y' : [4,3,2,1],
'bar_x': [1, 2, 3, 4],
'bar_bottom':[d1_value, d2_value, d3_value, d4_value],
'bar_top':[0.2, d1_value, d2_value, d3_value],
}
#bar_color
determine_top = new_data['bar_top']
determine_bottom = new_data['bar_bottom']
determine_color = []
for i in range(0,4):
if (determine_top[i] > determine_bottom[i]):
determine_color.append('green') #green
else:
determine_color.append('red')
i+=1
new_data['determine_colors'] = determine_color
source.data = new_data
for w in [select, d1, d2, d3, d4]:
w.on_change('value', update_data)
# Set up layouts and add to document
layout = row(widgetbox(select, d1, d2, d3, d4), plot)
def modify_doc(doc):
doc.add_root(row(layout, width=800))
doc.title = "Sliders"
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)
Thanks for any suggestions.
very new to scripting with python in maya so excuse my limited knowledge.
I need help figuring out how to define the variable for a floatSlider. I need two float sliders for the assignment I'm doing. I need one that will change the size of the selected or specified objects, and I need another that will use MASH to change the count of that object.
I have script with those sliders and a Distribute button laid out. I'm not sure what I need to include to link the scale of the object to the slider I have.
This is the code I have so far:
from maya import cmds
if cmds.window('mainUI2', exists=True):
cmds.deleteUI
win = cmds.window("mainUI2", title="Bush Generator", widthHeight=(300, 300))
# Layout
cmds.columnLayout(adjustableColumn=True)
cmds.text(label='Bush Generator')
cmds.button(label='Distribute', command='DistributeMesh()')
cmds.text(label=' ')
# need help defining Leaf_size
Leaf_size = cmds.floatSlider(min=0, max=100, value=0, step=1)
# I tried another type of slider
LeafScale = cmds.intSliderGrp(min=0, max=100, f=True)
cmds.text(label='Leaf Size')
# need defining Leaf_amount and linking to mash count
Leaf_amount = cmds.floatSlider(min=0, max=100, value=0, step=1)
cmds.text(label='Leaf Amount')
# Bush tool
def DistributeMesh():
cmds.loadPlugin("MASH", quiet=True)
import MASH.api as mapi
count = 3000
source_mesh = "pCube2"
scatter_mesh = "pSphere1"
source_shape = cmds.listRelatives(scatter_mesh, children=True)[0]
cmds.select(source_mesh)
mash_network = mapi.Network()
mash_network.createNetwork(name="Test", geometry="Instancer")
# set to use meshes to scatter
cmds.setAttr(mash_network.distribute + ".arrangement", 4)
cmds.setAttr(mash_network.distribute + ".pointCount", count)
# connect mesh
cmds.connectAttr(
source_shape + ".worldMesh[0]",
mash_network.distribute + ".inputMesh",
force=True)
cmds.showWindow(win)
Scale is a float value so you can use cmds.floatSliderGrp to set the source mesh's scale. First you have to define a separate function that will be triggered when you change the value of floatSliderGrp, then in floatSliderGrp set its changeCommand parameter to that function:
from maya import cmds
# Define a function that will be called when the slider changes values.
def on_size_slider_changed(value):
source_mesh = "pCube2"
if cmds.objExists(source_mesh): # Check if it exists.
cmds.setAttr("{}.scale".format(source_mesh), value, value, value) # Set its scale.
if cmds.window('mainUI2', exists=True):
cmds.deleteUI
win = cmds.window("mainUI2", title="Bush Generator", widthHeight=(300, 300))
# Layout
cmds.columnLayout(adjustableColumn=True)
cmds.text(label='Bush Generator')
cmds.button(label='Distribute', command='DistributeMesh()')
# Use `changeCommand` to define what function it should call.
leaf_size_slider = cmds.floatSliderGrp(label="Size", field=True, min=0, max=100, value=1, changeCommand=on_size_slider_changed)
# Bush tool
def DistributeMesh():
cmds.loadPlugin("MASH", quiet=True)
import MASH.api as mapi
count = 3000
source_mesh = "pCube2"
scatter_mesh = "pSphere1"
source_shape = cmds.listRelatives(scatter_mesh, children=True)[0]
cmds.select(source_mesh)
mash_network = mapi.Network()
mash_network.createNetwork(name="Test", geometry="Instancer")
# set to use meshes to scatter
cmds.setAttr(mash_network.distribute + ".arrangement", 4)
cmds.setAttr(mash_network.distribute + ".pointCount", count)
# connect mesh
cmds.connectAttr(
source_shape + ".worldMesh[0]",
mash_network.distribute + ".inputMesh",
force=True)
cmds.showWindow(win)
Dragging the slider will now set the scale of the cube. Though to be honest the structure of the code here is very messy and a bit too hard-coded (think about how it would work with the current selection instead of explicitly using the object's names)
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
def histogram_tab(webs):
def make_dataset(params_list, range_start = 0.0, range_end = 1, bin_width = 0.005):
#check to make sure the start is less than the end
assert range_start < range_end, "Start must be less than end!"
#by_params = pd.DataFrame(columns=[ ,'Max', 'Avarage', 'Min','color'])
by_params = pd.DataFrame(columns=[ 'left','right', 'proportion', 'p_proportion','p_interval', 'name', 'w_name','color'])
#
range_extent = range_end - range_start
values = ['Min', "Avarage", 'Max']
# Iterate through all the parameters
for i, para_name in enumerate(params_list):
#print para_name
# Subset to the parameter
subset = webs[para_name]
# note: subset have to be a list of values
# [webs.columns[i%6]]
# Create a histogram with specified bins and range
arr_hist, edges = np.histogram(subset,
bins = int(range_extent / bin_width),
range = [range_start, range_end])
# Divide the counts by the total to get a proportion and create df
arr_df= pd.DataFrame({'proportion': arr_hist ,
'left': edges[:-1], 'right': edges[1:]}) #/ np.sum(arr_hist)
# Format the proportion
arr_df['p_proportion'] = ['%0.00005f' % proportion for proportion in arr_df['proportion']]
# Format the interval
arr_df['p_interval'] = ['%d to %d scale' % (left, right) for left,
right in zip(arr_df['left'], arr_df['right'])]
# Assign the parameter for labels
arr_df['name'] = para_name
arr_df['w_name'] = webs['Site name']
# Color each parametr differently
arr_df['color'] = Category20_16[i]
# Add to the overall dataframe
by_params = by_params.append(arr_df)
# Overall dataframe
by_params = by_params.sort_values(['name','left'])
return ColumnDataSource(by_params)
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size ='20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
# Blank plot with correct labels
p = figure(plot_width = 700, plot_height = 700,
title = "Histogram of Parametes for the websites",
x_axis_label = 'parameters', y_axis_label = "values")
# Quad glyphs to create a histogram
p.quad(source=src, bottom =0,left = 'left', right = 'right', color ='color', top= 'proportion',fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
hover_fill_alpha = 1.0, line_color = 'white') #top='proportion',
# Hover tool with vline mode
hover = HoverTool(tooltips=[('Parameter','#name'),
('Website','#w_name'),
('Proportion','p_proportion')
],
mode='vline')
p.add_tools(hover)
# Stypling
p = style(p)
return p
# Update function takes three default parameters
def update(attr, old, new):
# Get the list of parameter for the graph
parameter_to_plot = [para_selection.labels[i] for i in para_selection.active]
# Make a new dataset based on the selected parameter and the
# make_dataset function defined earlier
new_src = make_dataset(parameter_to_plot, range_start = 0, range_end = 1, bin_width = 0.005) # note range are not specified
# Convert dataframe to column data source
new_src = ColumnDataSource(new_src)
# Update the source used the quad glpyhs
src.data.update(new_src.data)
list_of_params = list(webs.columns[1:].unique())
list_of_params.sort()
para_selection = CheckboxGroup(labels=list_of_params, active = [0,1])
para_selection.on_change('active',update)
binwidth_select = Slider(start =0, end = 1,
step = 0.00025, value = 0.0005,
title = 'Change in parameter')
binwidth_select.on_change('value', update)
range_select = RangeSlider(start=0, end=1, value =(0,1),
step=0.00025, title = 'Change in range')
range_select.on_change('value', update)
initial_params = [para_selection.labels[i] for i in para_selection.active]
src = make_dataset(initial_params,
range_start = range_select.value[0],
range_end = range_select.value[1],
bin_width = binwidth_select.value)
p = make_plot(src)
#show(p)
# Put controls in a single element
controls = WidgetBox(para_selection, binwidth_select, range_select)
# Create a row layout
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child = layout, title = 'Histogram')
#return tab
tabs = Tabs(tabs=[tab])
webs.add_root(tabs)
# Set up an application
handler = FunctionHandler(histogram_tab(webs))
app = Application(handler)
add_root is a method on Document, you are trying to call it on a DataFrame called webs, apparently, which is why you get that message. The structure of a Bokeh app in a notebook should look like this:
# create a function to define the app, must accept "doc" as the parameter
def myfunc(doc):
# make Bokeh objects
# add stuff to doc
doc.add_root(stuff)
# pass the function, but *don't* execute it
handler = FunctionHandler(myfunc)
app = Application(handler)
Note that the last two lines are not necessary in recent version of Bokeh, you can just call:
show(myfunc)
directly. There is a full example in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
Your code should be structured very similarly to that.
I'm trying to display further images (ct-scan) using numpy/vtk as describe in this sample code (http://www.vtk.org/Wiki/VTK/Examples/Python/vtkWithNumpy) but I don't get it and don't know why.
If someone could help me it would be kind.
Here's my code :
import vtk
import numpy as np
import os
import cv, cv2
import matplotlib.pyplot as plt
import PIL
import Image
DEBUG =True
directory="splitted_mri/"
w = 226
h = 186
d = 27
stack = np.zeros((w,d,h))
k=-1 #add the next picture in a differente level of depth/z-positions
for file in os.listdir(directory):
k+=1
img = directory + file
im = Image.open(img)
temp = np.asarray(im, dtype=int)
stack[:,k,:]= temp
print stack.shape
#~ plt.imshow(test)
#~ plt.show()
print type(stack[10,10,15])
res = np.amax(stack)
res1 = np.amin(stack)
print res, type(res)
print res1, type(res1)
#~ for (x,y,z), value in np.ndenumerate(stack):
#~ stack[x,y,z]=np.require(stack[x,y,z],dtype=np.int16)
#~ print type(stack[x,y,z])
stack = np.require(stack,dtype=np.uint16)
print stack.dtype
if DEBUG : print stack.shape
dataImporter = vtk.vtkImageImport()
data_string = stack.tostring()
dataImporter.CopyImportVoidPointer(data_string, len(data_string))
dataImporter.SetDataScalarTypeToUnsignedChar()
dataImporter.SetNumberOfScalarComponents(1)
dataImporter.SetDataExtent(0, w-1, 0, 1, 0, h-1)
dataImporter.SetWholeExtent(0, w-1, 0, 1, 0, h-1)
essai = raw_input()
alphaChannelFunc = vtk.vtkPiecewiseFunction()
colorFunc = vtk.vtkColorTransferFunction()
for i in range (0,255):
alphaChannelFunc.AddPoint(i, 0.9)
colorFunc.AddRGBPoint(i,i,i,i)
volumeProperty = vtk.vtkVolumeProperty()
volumeProperty.SetColor(colorFunc)
#volumeProperty.ShadeOn()
volumeProperty.SetScalarOpacity(alphaChannelFunc)
# This class describes how the volume is rendered (through ray tracing).
compositeFunction = vtk.vtkVolumeRayCastCompositeFunction()
# We can finally create our volume. We also have to specify the data for it, as well as how the data will be rendered.
volumeMapper = vtk.vtkVolumeRayCastMapper()
volumeMapper.SetVolumeRayCastFunction(compositeFunction)
volumeMapper.SetInputConnection(dataImporter.GetOutputPort())
# The class vtkVolume is used to pair the preaviusly declared volume as well as the properties to be used when rendering that volume.
volume = vtk.vtkVolume()
volume.SetMapper(volumeMapper)
volume.SetProperty(volumeProperty)
# With almost everything else ready, its time to initialize the renderer and window, as well as creating a method for exiting the application
renderer = vtk.vtkRenderer()
renderWin = vtk.vtkRenderWindow()
renderWin.AddRenderer(renderer)
renderInteractor = vtk.vtkRenderWindowInteractor()
renderInteractor.SetRenderWindow(renderWin)
# We add the volume to the renderer ...
renderer.AddVolume(volume)
# ... set background color to white ...
renderer.SetBackground(1, 1, 1)
# ... and set window size.
renderWin.SetSize(400, 400)
# A simple function to be called when the user decides to quit the application.
def exitCheck(obj, event):
if obj.GetEventPending() != 0:
obj.SetAbortRender(1)
# Tell the application to use the function as an exit check.
renderWin.AddObserver("AbortCheckEvent", exitCheck)
#to quit, press q
renderInteractor.Initialize()
# Because nothing will be rendered without any input, we order the first render manually before control is handed over to the main-loop.
renderWin.Render()
renderInteractor.Start()
I finally find out what was wrong
here's my new code
import vtk
import numpy as np
import os
import matplotlib.pyplot as plt
import PIL
import Image
DEBUG =False
directory="splitted_mri/"
l = []
k=0 #add the next picture in a differente level of depth/z-positions
for file in os.listdir(directory):
img = directory + file
if DEBUG : print img
l.append(img)
# the os.listdir function do not give the files in the right order
#so we need to sort them
l=sorted(l)
temp = Image.open(l[0])
h, w = temp.size
d = len(l)*5 #with our sample each images will be displayed 5times to get a better view
if DEBUG : print 'width, height, depth : ',w,h,d
stack = np.zeros((w,d,h),dtype=np.uint8)
for i in l:
im = Image.open(i)
temp = np.asarray(im, dtype=int)
for i in range(5):
stack[:,k+i,:]= temp
k+=5
#~ stack[:,k,:]= temp
#~ k+=1
if DEBUG :
res = np.amax(stack)
print 'max value',res
res1 = np.amin(stack)
print 'min value',res1
#convert the stack in the right dtype
stack = np.require(stack,dtype=np.uint8)
if DEBUG :#check if the image have not been modified
test = stack [:,0,:]
plt.imshow(test,cmap='gray')
plt.show()
if DEBUG : print 'stack shape & dtype' ,stack.shape,',',stack.dtype
dataImporter = vtk.vtkImageImport()
data_string = stack.tostring()
dataImporter.CopyImportVoidPointer(data_string, len(data_string))
dataImporter.SetDataScalarTypeToUnsignedChar()
dataImporter.SetNumberOfScalarComponents(1)
#vtk uses an array in the order : height, depth, width which is
#different of numpy (w,h,d)
w, d, h = stack.shape
dataImporter.SetDataExtent(0, h-1, 0, d-1, 0, w-1)
dataImporter.SetWholeExtent(0, h-1, 0, d-1, 0, w-1)
alphaChannelFunc = vtk.vtkPiecewiseFunction()
colorFunc = vtk.vtkColorTransferFunction()
for i in range(256):
alphaChannelFunc.AddPoint(i, 0.2)
colorFunc.AddRGBPoint(i,i/255.0,i/255.0,i/255.0)
# for our test sample, we set the black opacity to 0 (transparent) so as
#to see the sample
alphaChannelFunc.AddPoint(0, 0.0)
colorFunc.AddRGBPoint(0,0,0,0)
volumeProperty = vtk.vtkVolumeProperty()
volumeProperty.SetColor(colorFunc)
#volumeProperty.ShadeOn()
volumeProperty.SetScalarOpacity(alphaChannelFunc)
# This class describes how the volume is rendered (through ray tracing).
compositeFunction = vtk.vtkVolumeRayCastCompositeFunction()
# We can finally create our volume. We also have to specify the data for
# it, as well as how the data will be rendered.
volumeMapper = vtk.vtkVolumeRayCastMapper()
# function to reduce the spacing between each image
volumeMapper.SetMaximumImageSampleDistance(0.01)
volumeMapper.SetVolumeRayCastFunction(compositeFunction)
volumeMapper.SetInputConnection(dataImporter.GetOutputPort())
# The class vtkVolume is used to pair the preaviusly declared volume as
#well as the properties to be used when rendering that volume.
volume = vtk.vtkVolume()
volume.SetMapper(volumeMapper)
volume.SetProperty(volumeProperty)
# With almost everything else ready, its time to initialize the renderer and window,
# as well as creating a method for exiting the application
renderer = vtk.vtkRenderer()
renderWin = vtk.vtkRenderWindow()
renderWin.AddRenderer(renderer)
renderInteractor = vtk.vtkRenderWindowInteractor()
renderInteractor.SetRenderWindow(renderWin)
# We add the volume to the renderer ...
renderer.AddVolume(volume)
# ... set background color to white ...
renderer.SetBackground(1, 1, 1)
# ... and set window size.
renderWin.SetSize(550, 550)
renderWin.SetMultiSamples(4)
# A simple function to be called when the user decides to quit the application.
def exitCheck(obj, event):
if obj.GetEventPending() != 0:
obj.SetAbortRender(1)
# Tell the application to use the function as an exit check.
renderWin.AddObserver("AbortCheckEvent", exitCheck)
#to auit, press q
renderInteractor.Initialize()
# Because nothing will be rendered without any input, we order the first
# render manually before control is handed over to the main-loop.
renderWin.Render()
renderInteractor.Start()
If you are ok with a solution not using VTK, you could use Matplotlib imshow and interactive navigation with keys.
This tutorial shows how:
https://www.datacamp.com/community/tutorials/matplotlib-3d-volumetric-data
https://github.com/jni/mpl-volume-viewer
and here an implementation for viewing RTdose files:
https://github.com/pydicom/contrib-pydicom/pull/19
See also:
https://github.com/napari/napari