Unit testing both Excel formulas and VB with the same tool? - vba

Are there any tools that allow for unit testing both Excel formulas and Visual Basic forms within Excel? I'm finding methods that will do one or the other, but not both. Rubberduck for example looks promising for testing VBA, but does not appear to allow testing of formulas within Excel spreadsheets.

The only way I have found to "Unit Test" functions within Excel is to create a sheet within you workbook for which it's purpose in life is to be the validations page. Define Various additional functions within this sheet which look for edge cases and check additions etc within your workbook.
It is quite helpful to keep a comments field as well as a boolean success field that can be aggregated to put a custom format message on your other input pages to cue the user to a failed "Unit Test".
This approach can work quite well in making your testing reusable as well as transparent to the end users of your workbooks.

It is possible to unit test both Excel formulas and VBA using FlyingKoala. FlyingKoala is an extension of xlwings.
xlwings offers a COM wrapper which provides the ability to execute VBA from Python (by getting Excel to run it). It's a great library/solution. The esteemed Felix from ZoomerAnalyitics has written a blogpost about unit testing VBA using xlwings with examples.
FlyingKoala uses a library (xlcalculator) to convert Excel formulas into Python which can then be unit tested in Python's unittest framework. So it's possible to evaluate the formula and check against a known goal value whether that is in Excel or pre-defined.
An example of unit testing formulas using FlyingKoala while Excel is running;
import unittest
import logging
import xlwings as xw
from flyingkoala import FlyingKoala
from pandas import DataFrame
from pandas import Series
from numpy import array
from numpy.testing import assert_array_equal
from pandas.testing import assert_series_equal
logging.basicConfig(level=logging.ERROR)
class Test_equation_1(unittest.TestCase):
def setUp(self):
self.workbook_name = r'growing_degrees_day.xlsm'
if len(xw.apps) == 0:
raise "We need an Excel workbook open for this unit test."
self.my_fk = FlyingKoala(self.workbook_name, load_koala=True)
self.my_fk.reload_koala('')
self.equation_name = xw.Range('Equation_1')
if self.equation_name not in self.my_fk.koala_models.keys():
model = None
wb = xw.books[self.workbook_name]
wb.activate()
for name in wb.names:
self.my_fk.load_model(self.equation_name)
if self.equation_name == name.name:
model = xw.Range(self.equation_name)
self.my_fk.generate_model_graph(model)
if model is None:
return 'Model "%s" has not been loaded into cache, if named range exists check spelling.' % self.equation_name
def test_Equation_1(self):
"""First type of test for Equation_1"""
xw.books[self.workbook_name].sheets['Growing Degree Day'].activate()
goal = xw.books[self.workbook_name].sheets['Growing Degree Day'].range(xw.Range('D2'), xw.Range('D6')).options(array).value
tmin = xw.books[self.workbook_name].sheets['Growing Degree Day'].range(xw.Range('B2'), xw.Range('B6')).options(array).value
tmax = xw.books[self.workbook_name].sheets['Growing Degree Day'].range(xw.Range('C2'), xw.Range('C6')).options(array).value
inputs_for_DegreeDay = DataFrame({'T_min': tmin, 'T_max': tmax})
result = self.my_fk.evaluate_koala_model('Equation_1', inputs_for_DegreeDay).to_numpy()
assert_array_equal(goal, result)
def test_Equation_1_predefined_goal(self):
"""First type of test for Equation_1"""
goal = Series([0.0, 0.0, 0.0, 0.0, 0.0, 5, 10, 15, 20])
tmin = [-20, -15, -10, -5, 0, 5, 10, 15, 20]
tmax = [0, 5, 10, 15, 20, 25, 30, 35, 40]
inputs_for_DegreeDay = DataFrame({'T_min': tmin, 'T_max': tmax})
result = self.my_fk.evaluate_koala_model('Equation_1', inputs_for_DegreeDay)
assert_series_equal(goal, result)
def test_VBA_Equation_1(self):
"""
The function definition being called;
Function VBA_Equation_1(T_min As Double, T_max As Double) As Double
VBA_Equation_1 = Application.WorksheetFunction.Max(((T_max + T_min) / 2) - 10, 0)
End Function
"""
goal = 20
vba_equation_1 = xw.books[self.workbook_name].macro('VBA_Equation_1')
result = vba_equation_1(20.0, 40.0)
self.assertEqual(goal, result)

Related

Python Panel dashboard causing BufferError and RuntimeErrors

I have struggled for some time to create a data streaming interface using Panel.
Essentially I have approximately 20 named python objects that I monitor and read the spectral output from.
I want to have a dashboard displaying this in the form of 20 plots which must continuously overwrite themselves as the spectral output must be displayed over the same x-range (channels).
The dashboard runs fine for some time and then I either get:
a) RuntimeError: _pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes
or
b) BufferError: Existing exports of data: object cannot be re-sized {PYTHON_ENV_PATH}/lib/python3.6/site-packages/bokeh/document/document.py:500: RuntimeWarning: coroutine 'WSHandler.send_message' was never awaited gc.collect()
I've drafted up a MRE as follows:
import numpy as np
import pandas as pd
import hvplot.streamz
import numpy as np
import panel as pn
from streamz.dataframe import PeriodicDataFrame
pn.extension()
#object from which data is collected:
class data_gen:
def __init__(self,name,size=1024,sets=4):
self.name = name
self.size = size
self.sets = sets
def get_data(self):
return np.random.randn(self.sets,self.size)
#Have a dictionary of items with name:
data_dict = {
"a" : data_gen("a"),
"b" : data_gen("b"),
"c" : data_gen("c"),
"d" : data_gen("d"),
"e" : data_gen("e"),
"f" : data_gen("f"),
}
#Generate dataframe
def name_dataFrame(**kwargs):
dct = {}
for name,dg in data_dict.items():
d = dg.get_data()
sets, size = d.shape
t_dict ={}
for i in range(sets):
t_dict[i] = {
c : d[i,c] for c in range(size)
}
t_df = pd.DataFrame(t_dict).transpose()
dct[name] = t_df
df = pd.concat(dct).transpose()
return df
#Have it be streamed
df = PeriodicDataFrame(name_dataFrame, interval='10s')
#Compose panel layout
pn_realtime = pn.Column("# Data Dashboard")
for name in data_dict:
pn_realtime.append(
(pn.Row(f"""##Name: {name}""")))
pn_realtime.append(pn.Row(
df[name].hvplot.line(backlog=1024, width = 600, height=500, xlabel="n", ylabel="f(n)", grid=True)
))
pn_realtime.servable()
My set up is:
# Name Version Build Channel
panel 0.12.1 pyhd3eb1b0_0
hvplot 0.7.3 pyhd3eb1b0_1
pandas 1.1.5 py36ha9443f7_0
streamz 0.6.3 pyhd3eb1b0_0
Python 3.6.13 :: Anaconda, Inc.
Ubuntu 20.04.3 LTS (Focal Fossa)
I'm pretty new to dashboard design (and pandas for that matter) so I wouldn't be surprised if there were a vastly simpler way to do what I am attempting to do.
My suspicion is that the appending of Panel objects is causing memory buffers to overfill and garbage collection cannot handle it. If so, what can I do?
Running this MRE on my beefier Windows machine with python 3.9.7 did not seem to crash, but perhaps that is simply because I've not run it for long enough?
I've also set ylims on the hvplot and that seemed to stop crashes from occurring (again maybe I did not run it for long enough), but due to the nature of my application, I cannot have static ylims.
I appreciate your time and input.
Cheers.

Is there a way to have multiple rectangles displayed at the same time using visual.Rect in PsychoPy?

I'm trying to create stimuli that consist of 100 small lines in the centre of the screen, with orientations sampled from a Gaussian distribution (please see the image link below):
Orientation stimuli
I've managed to achieve something that almost fits the bill, but this code only works in isolation:
from psychopy import visual, core, event
import numpy as np
from numpy.random import random
import random
Lines = visual.Rect(
win=win, name='Lines',
width=(0.015, 0.0025)[0], height=(0.015, 0.0025)[1],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1, depth=-2.0, interpolate=True)
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 315
sigma = 15
for i in range(100):
Lines.pos = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
Lines.ori = random.gauss(mu, sigma)
I've tried to manipulate this code so that I can integrate it into the full experiment I'm designing in PsychoPy's experiment builder. I run the below code in the experiment builder's coding window calling 'gdist' and 'loc' as values for the 'Orientation' and 'Position' of the rectangles, respectively:
import random
gdist =[]
loc = []
lines_hpos = np.random.uniform(-0.49,0.49,100)
mu = 90
sigma = 20
for i in range(100):
rloc = [lines_hpos[i],np.random.uniform(-0.49,0.49)]
loc.append(rloc)
gauss = random.gauss(mu, sigma)
gdist.append(gauss)
When I attempt to run the experiment, I get an error return and the experiment fails to start:
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 238, in __del__
File "C:\Users\r02mj20\AppData\Local\PsychoPy3\lib\site-packages\pyglet\gl\lib.py", line 97, in errcheck
ImportError: sys.meta_path is None, Python is likely shutting down
I'm assuming this has something to do with pyglet not liking the idea of there being 100 rectangles all at once (side note: the script works fine if range(1)). If anyone has any suggestions for how I might fix or work around this problem, I'd be eternally grateful.
i don't see any problem with this idea, except you better use visual.Line instead of Rect, and your units of measure are not described; the key to preserving video memory is BufferImageStim, btw
from psychopy import visual, core, event, monitors
from psychopy.iohub.client import launchHubServer
import random
import numpy as np
MU = 315; SIGMA = 15
num_lines = 100
io = launchHubServer(iohub_config_name='iohub_config.yaml')
display = io.devices.display
mon = monitors.Monitor(name = display.getPsychopyMonitorName())
win = visual.Window([640, 480], units='pix', viewScale = 1.0,
monitor = mon, winType='pyglet',
fullScr = False, waitBlanking = True, useFBO = True, useLights = False,
allowStencil=False, allowGui = True,
screen = display.getIndex(), colorSpace = 'rgb255', color = [128,128,128],
name = 'my_win01')
rects = []
lines_hpos = np.random.uniform(-0.49, 0.49, num_lines)
for i in range(num_lines):
line_rect = visual.Rect(win=win, size=(0.001, 1.0), units='norm',
pos=(0,0), lineWidth=1, lineColor=[1,1,1], fillColor=[1,1,1], opacity=1, depth=-2.0,
name='lines_rect', interpolate=True, autoLog=False, autoDraw=False)
line_rect.pos = [lines_hpos[i], np.random.uniform(-0.49,0.49)]
line_rect.ori = random.gauss(MU, SIGMA)
rects.append(line_rect)
rect_buffer = visual.BufferImageStim(win, buffer='back', stim=rects, sqPower2=False, interpolate=False, name='rect-buffer', autoLog=True)
rect_buffer.draw()
win.flip()
event.waitKeys()

Plotly Dash: Chained callbacks are working fine, but always throw ValueError. How to fix or silence?

I have a dataset that lists different jobs and what tools are need by date and location. Here is a tiny example of my df:
State,County,Job,Tool,Start,End
LA,Acadia,A,Hammer,2020-10-19,2020-11-02
LA,Acadia,A,Drill,2020-11-02,2020-12-02
LA,Acadia,B,Hammer,2020-11-28,2020-12-30
LA,Orleans,A,Hammer,2020-10-03,2020-11-02
LA,Orleans,A,Drill,2020-11-05,2020-12-02
LA,Orleans,B,Hammer,2020-12-03,2020-12-30
TX,Travis,A,Hammer,2020-10-19,2020-11-02
TX,Travis,A,Drill,2020-11-02,2020-12-02
TX,Travis,B,Hammer,2020-11-28,2020-12-30
TX,Brazoria,A,Hammer,2020-10-03,2020-11-02
TX,Brazoria,A,Drill,2020-11-05,2020-12-02
TX,Brazoria,B,Hammer,2020-12-03,2020-12-30
My dcc.Dropdowns and dcc.RadioItems are as follows:
html.Div([
html.Label("State:", style={'fontSize': 30, 'textAlign': 'center'}),
dcc.Dropdown(
id='state-dpdn',
options=[{'label': s, 'value': s} for s in sorted(df.State.unique())],
value='LA',
clearable=False,
)],
html.Div([
html.Label("County:", style={'fontSize': 30, 'textAlign': 'center'}),
dcc.Dropdown(
id='county-dpdn',
options=[],
value=[],
clearable=False
)],
html.Div([
html.Label("Job:", style={'fontSize': 30, 'textAlign': 'center'}),
dcc.RadioItems(
id='radio-buttons',
options=[],
),
And here are my callbacks:
#app.callback(
Output('county-dpdn', 'options'),
Output('county-dpdn', 'value'),
Input('state-dpdn', 'value'),
)
def set_county_options(chosen_state):
dff = df[df.State == chosen_state]
counties_of_state = [{'label': c, 'value': c} for c in sorted(dff.County.unique())]
values_selected = [x['value'] for x in counties_of_state]
return counties_of_state, values_selected
#app.callback(
Output('radio-buttons', 'options'),
Output('radio-buttons', 'value'),
Input('county-dpdn', 'value'),
)
def set_job_options(chosen_county):
dff = df[df.County == chosen_county] ###THIS IS LINE 255 W/ ERROR###
job_of_county = [{'label': c, 'value': c} for c in dff.Job.unique()]
values_selected = job_of_county[0]['value']
return job_of_county, values_selected
#app.callback(
Output('graph1', 'children'),
Input('radio-buttons', 'value'),
Input('county-dpdn', 'value'),
Input('state-dpdn', 'value')
)
def build_graph(job_selected, location_selected, state_selected):
if isinstance(state_selected, str):
df['State'].eq(state_selected)
if isinstance(location_selected, str):
df['County'].eq(location_selected)
if isinstance(job_selected, str):
df['Job'].eq(job_selected)
dffgraph = df[df['State'].eq(state_selected) & df['County'].eq(location_selected) & df['Job'].eq(job_selected)]
###dffgraph manipulation and figure creation down here###
All of this works, loads the data as intended, and my graphs are great. My problem comes when trying to get this out to users without throwing errors. Errors were helpful in constructing everything but when it came time to tweak the presentation of my graphs, I silenced the errors to come back to them later using dev_tools:
if __name__ == '__main__':
app.run_server(debug=False, port=5000, dev_tools_ui=False, dev_tools_props_check=False)
I am creating a multipage dash app which works great, but when I put this code into my main multipage app or if I remove the dev tools from the local 'app.run_server' shown above I get the following error:
File "/Users/Documents/pythonProject/app.py", line 255, in set_job_options
dff = df[df.County == chosen_county]
File "/Users/venv/lib/python3.7/site-packages/pandas/core/ops/common.py", line 65, in new_method
return method(self, other)
File "/Users/venv/lib/python3.7/site-packages/pandas/core/ops/__init__.py", line 370, in wrapper
res_values = comparison_op(lvalues, rvalues, op)
File "/Users/venv/lib/python3.7/site-packages/pandas/core/ops/array_ops.py", line 225, in comparison_op
"Lengths must match to compare", lvalues.shape, rvalues.shape
ValueError: ('Lengths must match to compare', (4781,), (64,))
The csv file used for creating my df is 4781 lines long, and 64 is the number of parishes in Louisiana, so the number make sense.
While ignoring this error has worked thus far, after weeks of trying to occasionally figure this out I would love some help with this error. Thank you!
Edit 1
For help with simply silencing the errors, it is best to show how my app is setup. I have a multipage Flask app, with my Dash app being called when the user is directed to it.
My dashboard.py file looks like:
def init_dashboard(server):
"""Create a Plotly Dash dashboard."""
dash_app = dash.Dash(
server=server,
routes_pathname_prefix='/dashapp/',
external_stylesheets=[dbc.themes.FLATLY]
)
# Load DataFrame
df = create_dataframe()
and so on....
return dash_app.server
The init.py file in my flask app that calls my dashboard.py:
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
.... all the normal init stuff ....
from .plotlydash.dashboard import init_dashboard
app = init_dashboard(app)
return app
Prior to deployment while testing my dash app as a single entity, debug=False, dev_tools_ui=False, dev_tools_props_check=False all work well to silence the dash errors. When I put my working dash app into my flask app as shown above it still works just fine, but it throws errors which are logged and sent to my email like crazy. I've tried a few different approaches to silencing only the errors from my dash app but haven't found a solution yet.
Edit at bottom.
The problem is you're doing this:
dff = df[df.County == chosen_county]
but the == won't work with a list, and chosen_county is a list. You should do
dff = df[df.County.isin(chosen_county)]
or require that only a single value be selected and it not be a list.
Edit:
To do this with a single value dropdown, I think the right way would be:
dff = df[df['County'].eq(chosen_county)]
The dropdown should not have multi=True, that was from my previous misunderstanding. Please let me know if this still is not working.

Two Class instances in Python not different

I'm working on another data acquisition project, which has turned into an object oriented programming question. In “main” at the bottom of my code I make two instances of the Object DAQInput. When I wrote this, I thought my method .getData would refer to the taskHandle of the particular instance, but it does not. When I run, the code does the getData task with the first handle twice, so clearly I don’t really understand object oriented programming in Python. I’m sorry this code will not run without PyDAQmx and a National Instruments board attached.
from PyDAQmx import *
import numpy
class DAQInput:
# Declare variables passed by reference
taskHandle = TaskHandle()
read = int32()
data = numpy.zeros((10000,),dtype=numpy.float64)
sumi = [0,0,0,0,0,0,0,0,0,0]
def __init__(self, num_data, num_chan, channel, high, low):
""" This is init function that opens the channel"""
#Get the passed variables
self.num_data = num_data
self.channel = channel
self.high = high
self.low = low
self.num_chan = num_chan
# Create a task and configure a channel
DAQmxCreateTask(b"",byref(self.taskHandle))
DAQmxCreateAIThrmcplChan(self.taskHandle, self.channel, b"",
self.low, self.high,
DAQmx_Val_DegC,
DAQmx_Val_J_Type_TC,
DAQmx_Val_BuiltIn, 0, None)
# Start the task
DAQmxStartTask(self.taskHandle)
def getData(self):
""" This function gets the data from the board and calculates the average"""
print(self.taskHandle)
DAQmxReadAnalogF64(self.taskHandle, self.num_data, 10,
DAQmx_Val_GroupByChannel, self.data, 10000,
byref(self.read), None)
# Calculate the average of the values in data (could be several channels)
i = self.read.value
for j in range(self.num_chan):
self.sumi[j] = numpy.sum(self.data[j*i:(j+1)*i])/self.read.value
return self.sumi
def killTask(self):
""" This function kills the tasks"""
# If the task is still alive kill it
if self.taskHandle != 0:
DAQmxStopTask(self.taskHandle)
DAQmxClearTask(self.taskHandle)
if __name__ == '__main__':
myDaq1 = DAQInput(1, 4, b"cDAQ1Mod1/ai0:3", 200.0, 10.0)
myDaq2 = DAQInput(1, 4, b"cDAQ1Mod2/ai0:3", 200.0, 10.0)
result = myDaq1.getData()
print (result[0:4])
result2 = myDaq2.getData()
print (result2[0:4])
myDaq1.killTask()
myDaq2.killTask()
These variables:
class DAQInput:
# Declare variables passed by reference
taskHandle = TaskHandle()
read = int32()
data = numpy.zeros((10000,),dtype=numpy.float64)
sumi = [0,0,0,0,0,0,0,0,0,0]
Are class variables. They belong to the class itself and are shared among instances of the class (i.e. if you modify self.data in Instance1, Instace2's self.data is modified as well).
If you want them to be instance variables, define them in __init__.

Exporting a 3D numpy to a VTK file for viewing in Paraview/Mayavi

For those that want to export a simple 3D numpy array (along with axes) to a .vtk (or .vtr) file for post-processing and display in Paraview or Mayavi there's a little module called PyEVTK that does exactly that. The module supports structured and unstructured data etc..
Unfortunately, even though the code works fine in unix-based systems I couldn't make it work (keeps crashing) on any windows installation which simply makes things complicated. Ive contacted the developer but his suggestions did not work
Therefore my question is:
How can one use the from vtk.util import numpy_support function to export a 3D array (the function itself doesn't support 3D arrays) to a .vtk file? Is there a simple way to do it without creating vtkDatasets etc etc?
Thanks a lot!
It's been forever and I had entirely forgotten asking this question but I ended up figuring it out. I've written a post about it in my blog (PyScience) providing a tutorial on how to convert between NumPy and VTK. Do take a look if interested:
pyscience.wordpress.com/2014/09/06/numpy-to-vtk-converting-your-numpy-arrays-to-vtk-arrays-and-files/
It's not a direct answer to your question, but if you have tvtk (if you have mayavi, you should have it), you can use it to write your data to vtk format. (See: http://code.enthought.com/projects/files/ETS3_API/enthought.tvtk.misc.html )
It doesn't use PyEVTK, and it supports a broad range of data sources (more than just structured and unstructured grids), so it will probably work where other things aren't.
As a quick example (Mayavi's mlab interface can make this much less verbose, especially if you're already using it.):
import numpy as np
from enthought.tvtk.api import tvtk, write_data
data = np.random.random((10,10,10))
grid = tvtk.ImageData(spacing=(10, 5, -10), origin=(100, 350, 200),
dimensions=data.shape)
grid.point_data.scalars = np.ravel(order='F')
grid.point_data.scalars.name = 'Test Data'
# Writes legacy ".vtk" format if filename ends with "vtk", otherwise
# this will write data using the newer xml-based format.
write_data(grid, 'test.vtk')
And a portion of the output file:
# vtk DataFile Version 3.0
vtk output
ASCII
DATASET STRUCTURED_POINTS
DIMENSIONS 10 10 10
SPACING 10 5 -10
ORIGIN 100 350 200
POINT_DATA 1000
SCALARS Test%20Data double
LOOKUP_TABLE default
0.598189 0.228948 0.346975 0.948916 0.0109774 0.30281 0.643976 0.17398 0.374673
0.295613 0.664072 0.307974 0.802966 0.836823 0.827732 0.895217 0.104437 0.292796
0.604939 0.96141 0.0837524 0.498616 0.608173 0.446545 0.364019 0.222914 0.514992
...
...
TVTK of Mayavi has a beautiful way of writing vtk files. Here is a test example I have written for myself following #Joe and tvtk documentation. The advantage it has over evtk, is the support for both ascii and html.Hope it will help other people.
from tvtk.api import tvtk, write_data
import numpy as np
#data = np.random.random((3, 3, 3))
#
#i = tvtk.ImageData(spacing=(1, 1, 1), origin=(0, 0, 0))
#i.point_data.scalars = data.ravel()
#i.point_data.scalars.name = 'scalars'
#i.dimensions = data.shape
#
#w = tvtk.XMLImageDataWriter(input=i, file_name='spoints3d.vti')
#w.write()
points = np.array([[0,0,0], [1,0,0], [1,1,0], [0,1,0]], 'f')
(n1, n2) = points.shape
poly_edge = np.array([[0,1,2,3]])
print n1, n2
## Scalar Data
#temperature = np.array([10., 20., 30., 40.])
#pressure = np.random.rand(n1)
#
## Vector Data
#velocity = np.random.rand(n1,n2)
#force = np.random.rand(n1,n2)
#
##Tensor Data with
comp = 5
stress = np.random.rand(n1,comp)
#
#print stress.shape
## The TVTK dataset.
mesh = tvtk.PolyData(points=points, polys=poly_edge)
#
## Data 0 # scalar data
#mesh.point_data.scalars = temperature
#mesh.point_data.scalars.name = 'Temperature'
#
## Data 1 # additional scalar data
#mesh.point_data.add_array(pressure)
#mesh.point_data.get_array(1).name = 'Pressure'
#mesh.update()
#
## Data 2 # Vector data
#mesh.point_data.vectors = velocity
#mesh.point_data.vectors.name = 'Velocity'
#mesh.update()
#
## Data 3 additional vector data
#mesh.point_data.add_array( force)
#mesh.point_data.get_array(3).name = 'Force'
#mesh.update()
mesh.point_data.tensors = stress
mesh.point_data.tensors.name = 'Stress'
# Data 4 additional tensor Data
#mesh.point_data.add_array(stress)
#mesh.point_data.get_array(4).name = 'Stress'
#mesh.update()
write_data(mesh, 'polydata.vtk')
# XML format
# Method 1
#write_data(mesh, 'polydata')
# Method 2
#w = tvtk.XMLPolyDataWriter(input=mesh, file_name='polydata.vtk')
#w.write()
I know it is a bit late and I do love your tutorials #somada141. This should work too.
def numpy2VTK(img, spacing=[1.0, 1.0, 1.0]):
# evolved from code from Stou S.,
# on http://www.siafoo.net/snippet/314
# This function, as the name suggests, converts numpy array to VTK
importer = vtk.vtkImageImport()
img_data = img.astype('uint8')
img_string = img_data.tostring() # type short
dim = img.shape
importer.CopyImportVoidPointer(img_string, len(img_string))
importer.SetDataScalarType(VTK_UNSIGNED_CHAR)
importer.SetNumberOfScalarComponents(1)
extent = importer.GetDataExtent()
importer.SetDataExtent(extent[0], extent[0] + dim[2] - 1,
extent[2], extent[2] + dim[1] - 1,
extent[4], extent[4] + dim[0] - 1)
importer.SetWholeExtent(extent[0], extent[0] + dim[2] - 1,
extent[2], extent[2] + dim[1] - 1,
extent[4], extent[4] + dim[0] - 1)
importer.SetDataSpacing(spacing[0], spacing[1], spacing[2])
importer.SetDataOrigin(0, 0, 0)
return importer
Hope it helps!
Here's a SimpleITK version with the function load_itk taken from here:
import SimpleITK as sitk
import numpy as np
if len(sys.argv)<3:
print('Wrong number of arguments.', file=sys.stderr)
print('Usage: ' + __file__ + ' input_sitk_file' + ' output_sitk_file', file=sys.stderr)
sys.exit(1)
def quick_read(filename):
# Read image information without reading the bulk data.
file_reader = sitk.ImageFileReader()
file_reader.SetFileName(filename)
file_reader.ReadImageInformation()
print('image size: {0}\nimage spacing: {1}'.format(file_reader.GetSize(), file_reader.GetSpacing()))
# Some files have a rich meta-data dictionary (e.g. DICOM)
for key in file_reader.GetMetaDataKeys():
print(key + ': ' + file_reader.GetMetaData(key))
def load_itk(filename):
# Reads the image using SimpleITK
itkimage = sitk.ReadImage(filename)
# Convert the image to a numpy array first and then shuffle the dimensions to get axis in the order z,y,x
data = sitk.GetArrayFromImage(itkimage)
# Read the origin of the ct_scan, will be used to convert the coordinates from world to voxel and vice versa.
origin = np.array(list(reversed(itkimage.GetOrigin())))
# Read the spacing along each dimension
spacing = np.array(list(reversed(itkimage.GetSpacing())))
return data, origin, spacing
def convert(data, output_filename):
image = sitk.GetImageFromArray(data)
writer = sitk.ImageFileWriter()
writer.SetFileName(output_filename)
writer.Execute(image)
def wait():
print('Press Enter to load & convert or exit using Ctrl+C')
input()
quick_read(sys.argv[1])
print('-'*20)
wait()
data, origin, spacing = load_itk(sys.argv[1])
convert(sys.argv[2])