def getDefaultParams(stentType=''): """ getDefaultParams() Get the paramater stuct filled with defaults. These defaults may not be optimal for you stent type and/or scanner configuration. """ # Generic params params = ssdf.new() # The threshold for detecting seed points params.seed_threshold = 650 # The scale factor for the data to create speed image params.mcp_speedFactor = 100 # The MCP threshold. Small=faster, but will miss connections if too small! # params.mcp_evolutionThreshold = 0.06 # deprecated in favor of mcp_maxCoverageFronts # The MCP max coverage fraction for evolving fronts params.mcp_maxCoverageFronts = 0.03 # The Expected Number of Connections params.graph_expectedNumberOfEdges = 2 # The th to determine a really strong connection params.graph_strongThreshold = 1200 # The th to determine a really weak connection params.graph_weakThreshold = 100 # The size of tails to trim and clusters to remove params.graph_trimLength = 3 params.graph_minimumClusterSize = 8 # The th (vector) and angTh to detect corners params.graph_angleVector = 5 params.graph_angleTh = 45 # Stent type dependencies if stentType == 'zenith': params.graph_expectedNumberOfEdges = 2 elif stentType == 'talent': params.graph_expectedNumberOfEdges = 2 elif stentType == 'aneurx': params.graph_expectedNumberOfEdges = 4 params.graph_minimumClusterSize = 400 elif stentType == 'anaconda': params.graph_expectedNumberOfEdges = 2 elif stentType == 'anacondaRing': params.graph_expectedNumberOfEdges = 3 params.mcp_maxCoverageFronts = 0.003 params.graph_trimLength = 0 # The length of the struts in anaconda proximal fixation rings params.graph_min_strutlength = 6 params.graph_max_strutlength = 12 elif stentType == 'endurant': params.graph_expectedNumberOfEdges = 2 elif stentType == 'excluder': params.graph_expectedNumberOfEdges = 2 elif stentType == 'nellix': params.graph_expectedNumberOfEdges = 2 elif stentType == 'branch': params.graph_expectedNumberOfEdges = 2 elif stentType: raise ValueError('Unknown stent type %s' % stentType) # Done return params
def getDefaultParams(): #Default params params = ssdf.new() params.max_dist = 2 params.weighting_factor = 0.99 params.weighting_factor_bif = 0.95 params.distance = 4 return params
def Pack(self): """ Pack() Pack the contents in an ssdf struct, such that it can be stored. """ # If nodelist is empty, ndim defaults to two ndim = 2 # Get a list of all edges cc = self.GetEdges() # Check whether the nodes are homogenous, otherwise we cannot store if len(self): ndim = self[0].ndim for node in self: if not node.ndim == ndim: raise ValueError('All nodes should have the same dimension.') # Init struct struct = ssdf.new() # Create array of nodes pp = Pointset(ndim) for node in self: pp.append(node) struct.nodes = pp.data # Create the edges tmp = np.zeros((len(cc), 2), dtype=np.uint32) for i in range(len(cc)): c = cc[i] tmp[i,0] = c._i1 tmp[i,1] = c._i2 struct.edges = tmp # Store the properties of the edges. The propRefs array # Does contain redundant data, but it can be stored efficiently # because the compression handles that... allProps = '' propRefs = np.zeros((len(cc), 2), dtype=np.uint32) for i in range(len(cc)): tmp = serializeProps(cc[i].props) propRefs[i, 0] = len(allProps) propRefs[i, 1] = len(allProps) + len(tmp) allProps += tmp struct.edgePropRefs = propRefs if allProps: struct.edgeProps = np.frombuffer( allProps, dtype=np.uint8) else: struct.edgeProps = np.zeros( (0,), dtype=np.uint8) # done return struct
def Pack(self): """ Pack() Pack the contents in an ssdf struct, such that it can be stored. """ # If nodelist is empty, ndim defaults to two ndim = 2 # Get a list of all edges cc = self.GetEdges() # Check whether the nodes are homogenous, otherwise we cannot store if len(self): ndim = self[0].ndim for node in self: if not node.ndim == ndim: raise ValueError('All nodes should have the same dimension.') # Init struct struct = ssdf.new() # Create array of nodes pp = Pointset(ndim) for node in self: pp.append(node) struct.nodes = pp.data # Create the edges tmp = np.zeros((len(cc), 2), dtype=np.uint32) for i in range(len(cc)): c = cc[i] tmp[i,0] = c._i1 tmp[i,1] = c._i2 struct.edges = tmp # Store the properties of the edges. The propRefs array # Does contain redundant data, but it can be stored efficiently # because the compression handles that... allProps = b'' propRefs = np.zeros((len(cc), 2), dtype=np.uint32) for i in range(len(cc)): tmp = serializeProps(cc[i].props) propRefs[i, 0] = len(allProps) propRefs[i, 1] = len(allProps) + len(tmp) allProps += tmp struct.edgePropRefs = propRefs if allProps: struct.edgeProps = np.frombuffer( allProps, dtype=np.uint8) else: struct.edgeProps = np.zeros( (0,), dtype=np.uint8) # done return struct
def cropaveraged(basedir, ptcode, ctcode, crop_in='stent', what='avg5090', crop_out='body'): """ Crop averaged volume of stent With loadvol, load ssdf containing an averaged volume and save new cropped ssdf """ s = loadvol(basedir, ptcode, ctcode, crop_in, what) vol = s.vol # Open cropper tool print('Set the appropriate cropping range for "%s" ' 'and click finish to continue' % crop_out) print('Loading... be patient') fig = vv.figure() fig.title = 'Cropping for "%s"' % crop_out vol_crop = cropper.crop3d(vol, fig) fig.Destroy() if vol_crop.shape == vol.shape: raise RuntimeError('User cancelled (no crop)') # Calculate crop range from origin rz = int(vol_crop.origin[0] / vol_crop.sampling[0] + 0.5) rz = rz, rz + vol_crop.shape[0] ry = int(vol_crop.origin[1] / vol_crop.sampling[1] + 0.5) ry = ry, ry + vol_crop.shape[1] rx = int(vol_crop.origin[2] / vol_crop.sampling[2] + 0.5) rx = rx, rx + vol_crop.shape[2] # Initialize struct s2 = ssdf.new() s2.sampling = vol_crop.sampling # z, y, x voxel size in mm s2.stenttype = s.stenttype for key in dir(s): if key.startswith('meta'): suffix = key[4:] s2['meta' + suffix] = s['meta' + suffix] # Set new croprange, origin and vol offset = [i[0] for i in s.croprange] # origin of crop_in s2.croprange = ([rz[0] + offset[0], rz[1] + offset[0] ], [ry[0] + offset[1], ry[1] + offset[1]], [rx[0] + offset[2], rx[1] + offset[2]]) s2.origin = vol_crop.origin s2.vol = vol_crop # Export and save filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, crop_out, what) file_out = os.path.join(basedir, ptcode, filename) ssdf.save(file_out, s2)
def __init__(self, fname=None): # Store filename self._fname = fname # Load if fname and os.path.exists(fname): self._db = ssdf.load(fname) else: self._db = ssdf.new() # Key of last added entry self._lastkey = ''
def pack(self): """ Pack the graph to an ssdf struct. This method is not stent-specific and is a generic graph export to ssdf. """ # Prepare s = ssdf.new() s.nodes = [] s.edges = [] # Serialize s.graph = ssdf.loads(ssdf.saves(self.graph)) for node in self.nodes_iter(): #node_info = {'node': node, 'attr': self.node[node]} node_info = node, self.node[node] s.nodes.append(node_info) for edge in self.edges_iter(): #edge_info = {'node1': edge[0], 'node2':edge[1], 'attr': self.edge[edge[0]][edge[1]]} edge_info = edge[0], edge[1], self.edge[edge[0]][edge[1]] s.edges.append(edge_info) return s
def __init__(self): # Define settings file name self._fname = os.path.join(appdata_dir('visvis'), 'config.ssdf') # Init settings self._s = ssdf.new() # Load settings if we can if os.path.exists(self._fname): try: self._s = ssdf.load(self._fname) except Exception: pass # Update any missing settings to their defaults for key in dir(self): if key.startswith('_'): continue self._s[key] = getattr(self, key) # Save now so the config file contains all settings self._Save()
def saveaveraged(basedir, ptcode, ctcode, cropname, phases): """ Step C: Save average of a number of volumes (phases in cardiac cycle) Load ssdf containing all phases and save averaged volume as new ssdf """ filename = '%s_%s_%s_phases.ssdf' % (ptcode, ctcode, cropname) file_in = os.path.join(basedir, ptcode, filename) if not os.path.exists(file_in): raise RuntimeError('Could not find ssdf for given input %s' % ptcode, ctcode, cropname) s = ssdf.load(file_in) s_avg = ssdf.new() averaged = np.zeros(s.vol0.shape, np.float64) if phases[1] < phases[0]: phaserange = list(range(0, 100, 10)) for i in range(phases[1] + 10, phases[0], 10): phaserange.remove(i) else: phaserange = range(phases[0], phases[1] + 10, 10) for phase in phaserange: averaged += s['vol%i' % phase] s_avg['meta%i' % phase] = s['meta%i' % phase] averaged *= 1.0 / len(phaserange) averaged = averaged.astype('float32') s_avg.vol = averaged s_avg.sampling = s.sampling # z, y, x voxel size in mm s_avg.origin = s.origin s_avg.stenttype = s.stenttype s_avg.croprange = s.croprange s_avg.ctcode = s.ctcode s_avg.ptcode = s.ptcode avg = 'avg' + str(phases[0]) + str(phases[1]) filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, avg) file_out = os.path.join(basedir, ptcode, filename) ssdf.save(file_out, s_avg)
import os from visvis import ssdf # vv.imwrite('d:/almar/projects/ims/test0.png', vv.getframe(vv.gcf())) fontGenApp = r'"C:\Program Files (x86)\BMFontGen\bmfontgen.exe"' # If the size is even, the even fontsizes in visvis will look much better # than the uneven. Matlab uses fontsize 9 for tickmarks by default, but I # like even fontsizes better. Additionally, too large size will result in # aliasing for smaller fontsizes in visvis. size = 16 # 18 is good, 20 won't fit, bmsize = 1024 outdir = 'tmp/' s = ssdf.new() s.serif = ssdf.new() s.serif.name = 'FreeSerif' fonts = { 'mono':'FreeMono', 'sans':'FreeSans', 'serif':'FreeSerif'} #fonts = { 'mono':'Courier New', 'sans':'Arial', 'serif':'Times new roman'} for font in fonts: fontName = fonts[font] options = [] # parameters options.append( '-name %s' % fontName ) options.append( '-size %i' % size ) options.append( '-bmsize %i' % bmsize )
def processFont(fontname): ## retrieve fontsize fontsize = 6 # default very small so we see the error for line in open(path+'tmp/temp_font'+'.txt'): if line.startswith('-size'): fontsize = int(line[6:]) break ## info entries = {} for line in open(path + 'tmp/' + fontname + '.xml'): # find character code i = line.find('code=') if i<0: continue ch = line[i+6:i+10] ch = int(ch,16) # create char char = Char() # find location in texture i = line.find('origin=') i1 = line.find("\"",i) if i<0 or i1<0: continue i2 = line.find("\"",i1+1) if i2<0: continue tmp = line[i1+1:i2] tmp = tmp.split(',') if len(tmp)!=2: continue char.origin = [int(i) for i in tmp] # find size i = line.find('size=') i1 = line.find("\"",i) if i<0 or i1<0: continue i2 = line.find("\"",i1+1) if i2<0: continue tmp = line[i1+1:i2] tmp = tmp.split('x') if len(tmp)!=2: continue char.size = [int(float(i)+0.5) for i in tmp] # find width i = line.find('aw=') i1 = line.find("\"",i) if i<0 or i1<0: continue i2 = line.find("\"",i1+1) if i2<0: continue tmp = line[i1+1:i2] char.aw = int(tmp) # store char entries[ch] = char ## make lists of the information # a list of all registered characters charcodes = entries.keys() L = max(charcodes)+1 origin = np.zeros((L,2), dtype=np.uint16) size = np.zeros((L,2), dtype=np.uint8) width = np.zeros((L,), dtype=np.uint8) for code in entries: char = entries[code] origin[code,0] = char.origin[0] origin[code,1] = char.origin[1] size[code,0] = char.size[0] size[code,1] = char.size[1] width[code] = char.aw ## image tmp = Image.open(path+'tmp/'+fontname+'-0.png') im = np.asarray(tmp) im = im[:,:,0] h, w = im.shape ## store data s = ssdf.new() s.charcodes = np.array(charcodes, dtype=np.uint16) s.origin = origin s.size = size s.width = width s.data = im.copy() s.fontsize = fontsize return s
## store data s = ssdf.new() s.charcodes = np.array(charcodes, dtype=np.uint16) s.origin = origin s.size = size s.width = width s.data = im.copy() s.fontsize = fontsize return s ## Scan available fonts and pack them files = os.listdir(path) base = ssdf.new() for font in ['mono', 'sans', 'serif']: ss = [] for fonttype in ['r', 'b', 'i']: # regular, bold, italic fontname = font+'_'+fonttype s = processFont(fontname) ss.append(s) # process to pack together sr, sb, si = ss[0], ss[1], ss[2] for fonttype in ['b', 'i']: s = ss[{'b':1,'i':2}[fonttype]] # cut interesting part of image in i or b [Iy,Ix] = np.where(s.data>0); y1 = Iy.max() data = s.data[0:y1+1,:] # paste that part in image of regular
appropriate. """ raise NotImplemented("This method needs to be implemented.") if __name__ == '__main__': # Test experiment # Let's say we want to estimate for which value of x some magic process # is maximum. We simulate this process using noise. By using a seed, # the experiments are repeatable. import time np.random.seed(1234) params = ssdf.new() params.noise_level = 3 params.x = 3 class TestExperiment(Experiment): """ In this experiment we will use series 0 to repeat the process (using different instantiations of the noise). We will use series 1 to vary the parameter x of which we want to know the optimum value. """ def experiment(self, params): """ experiment(params) Our magic box. Given params.x, it calculates a value. In a real experiment, this calculation may be unknown to us. """ t0 = time.time() noise = np.random.normal(0, params.noise_level)
print("deforms saved to disk.") # Store averaged volume, where the volumes are registered from visvis import ssdf # Create average volume from *all* volumes deformed to the "center" N = len(reg._ims) mean_vol = np.zeros(reg._ims[0].shape, 'float64') for i in range(N): vol, deform = reg._ims[i], reg.get_deform(i) mean_vol += deform.as_backward().apply_deformation(vol) mean_vol *= 1.0 / N # Create struct s_avg = ssdf.new() for i in range(10): phase = i * 10 s_avg['meta%i' % phase] = s['meta%i' % phase] s_avg.sampling = s.sampling # z, y, x voxel size in mm s_avg.origin = s.origin s_avg.stenttype = s.stenttype s_avg.croprange = s.croprange s_avg.vol = mean_vol.astype('float32') s_avg.params = s2.params # Save avg = 'avgreg' filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, avg) ssdf.save(os.path.join(basedir, ptcode, filename), s_avg) print("avgreg saved to disk.")
def processFont(fontname): ## retrieve fontsize fontsize = 6 # default very small so we see the error for line in open(path + 'tmp/temp_font' + '.txt'): if line.startswith('-size'): fontsize = int(line[6:]) break ## info entries = {} for line in open(path + 'tmp/' + fontname + '.xml'): # find character code i = line.find('code=') if i < 0: continue ch = line[i + 6:i + 10] ch = int(ch, 16) # create char char = Char() # find location in texture i = line.find('origin=') i1 = line.find("\"", i) if i < 0 or i1 < 0: continue i2 = line.find("\"", i1 + 1) if i2 < 0: continue tmp = line[i1 + 1:i2] tmp = tmp.split(',') if len(tmp) != 2: continue char.origin = [int(ii1) for ii1 in tmp] # find size i = line.find('size=') i1 = line.find("\"", i) if i < 0 or i1 < 0: continue i2 = line.find("\"", i1 + 1) if i2 < 0: continue tmp = line[i1 + 1:i2] tmp = tmp.split('x') if len(tmp) != 2: continue char.size = [int(float(ii2) + 0.5) for ii2 in tmp] # find width i = line.find('aw=') i1 = line.find("\"", i) if i < 0 or i1 < 0: continue i2 = line.find("\"", i1 + 1) if i2 < 0: continue tmp = line[i1 + 1:i2] char.aw = int(tmp) # store char entries[ch] = char ## make lists of the information # a list of all registered characters charcodes = list(entries.keys()) L = max(charcodes) + 1 origin = np.zeros((L, 2), dtype=np.uint16) size = np.zeros((L, 2), dtype=np.uint8) width = np.zeros((L, ), dtype=np.uint8) for code in entries: char = entries[code] origin[code, 0] = char.origin[0] origin[code, 1] = char.origin[1] size[code, 0] = char.size[0] size[code, 1] = char.size[1] width[code] = char.aw ## image tmp = Image.open(path + 'tmp/' + fontname + '-0.png') im = np.asarray(tmp) im = im[:, :, 0] h, w = im.shape ## store data s = ssdf.new() s.charcodes = np.array(charcodes, dtype=np.uint16) s.origin = origin s.size = size s.width = width s.data = im.copy() s.fontsize = fontsize return s
def __init__(self, ptcode, ctcode, basedir): ## Perform image registration import os, time import numpy as np import visvis as vv import pirt.reg # Python Image Registration Toolkit from stentseg.utils.datahandling import select_dir, loadvol import scipy from scipy import ndimage # Select dataset to register cropname = 'prox' what = 'phases' # Load volumes s = loadvol(basedir, ptcode, ctcode, cropname, what) vols = [] phases = [] for key in dir(s): if key.startswith('vol'): print(key) # create vol with zoom in z-direction zscale = (s[key].sampling[0] / s[key].sampling[1]) # z / y # resample vol using spline interpolation, 3rd order piecewise polynomial vol_zoom = scipy.ndimage.interpolation.zoom( s[key], [zscale, 1, 1], 'float32') s[key].sampling = [ s[key].sampling[1], s[key].sampling[1], s[key].sampling[2] ] # set scale and origin vol_zoom_type = vv.Aarray(vol_zoom, s[key].sampling, s[key].origin) vol = vol_zoom_type phases.append(key) vols.append(vol) t0 = time.time() # Initialize registration object reg = pirt.reg.GravityRegistration(*vols) reg.params.mass_transforms = 2 # 2nd order (Laplacian) triggers more at lines reg.params.speed_factor = 1.0 reg.params.deform_wise = 'groupwise' # groupwise! reg.params.mapping = 'backward' reg.params.deform_limit = 1.0 reg.params.final_scale = 1.0 # We might set this a wee bit lower than 1 (but slower!) reg.params.scale_sampling = 16 reg.params.final_grid_sampling = 20 reg.params.grid_sampling_factor = 0.5 # Go! reg.register(verbose=1) t1 = time.time() print('Registration completed, which took %1.2f min.' % ((t1 - t0) / 60)) # Store registration result from visvis import ssdf # Create struct s2 = vv.ssdf.new() N = len(vols) for key in dir(s): if key.startswith('meta'): s2[key] = s[key] s2.origin = s.origin s2.stenttype = s.stenttype s2.croprange = s.croprange # Obtain deform fields for i in range(N): fields = [field for field in reg.get_deform(i).as_backward()] phase = phases[i][3:] s2['deform%s' % phase] = fields s2.sampling = s2['deform%s' % phase][0].sampling # Sampling of deform is different! s2.originDeforms = s2['deform%s' % phase][0].origin # But origin is zero s2.params = reg.params # Save filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, 'deforms') ssdf.save(os.path.join(basedir, ptcode, filename), s2) print("deforms saved to disk.") #============================================================================ # Store averaged volume, where the volumes are registered #from visvis import ssdf # Create average volume from *all* volumes deformed to the "center" N = len(reg._ims) mean_vol = np.zeros(reg._ims[0].shape, 'float64') for i in range(N): vol, deform = reg._ims[i], reg.get_deform(i) mean_vol += deform.as_backward().apply_deformation(vol) mean_vol *= 1.0 / N # Create struct s_avg = ssdf.new() for key in dir(s): if key.startswith('meta'): s_avg[key] = s[key] s_avg.sampling = s.vol0.sampling # z, y, x after interpolation s_avg.origin = s.origin s_avg.stenttype = s.stenttype s_avg.croprange = s.croprange s_avg.vol = mean_vol.astype('float32') s_avg.params = s2.params fig1 = vv.figure(1) vv.clf() fig1.position = 0, 22, 1366, 706 a1 = vv.subplot(111) a1.daspect = 1, 1, -1 renderstyle = 'mip' a1b = vv.volshow(s_avg.vol, clim=(0, 2000), renderStyle=renderstyle) #a1b.isoThreshold = 600 vv.xlabel('x'), vv.ylabel('y'), vv.zlabel('z') vv.title('Average volume of %s phases (%s) (resized data)' % (len(phases), renderstyle)) # Save avg = 'avgreg' filename = '%s_%s_%s_%s.ssdf' % (ptcode, ctcode, cropname, avg) ssdf.save(os.path.join(basedir, ptcode, filename), s_avg) print("avgreg saved to disk.") t1 = time.time() print('Registration completed, which took %1.2f min.' % ((t1 - t0) / 60))
## store data s = ssdf.new() s.charcodes = np.array(charcodes, dtype=np.uint16) s.origin = origin s.size = size s.width = width s.data = im.copy() s.fontsize = fontsize return s ## Scan available fonts and pack them files = os.listdir(path) base = ssdf.new() for font in ['mono', 'sans', 'serif']: ss = [] for fonttype in ['r', 'b', 'i']: # regular, bold, italic fontname = font + '_' + fonttype s = processFont(fontname) ss.append(s) # process to pack together sr, sb, si = ss[0], ss[1], ss[2] for fonttype in ['b', 'i']: s = ss[{'b': 1, 'i': 2}[fonttype]] # cut interesting part of image in i or b [Iy, Ix] = np.where(s.data > 0) y1 = Iy.max() data = s.data[0:y1 + 1, :]
def savecropvols(vols, basedir, ptcode, ctcode, cropname, stenttype, sampling=None, meta=None): """ Step B: Crop and Save SSDF Input: vols from Step A Save 2 or 10 volumes (cardiac cycle phases) in one ssdf file Cropper tool opens to manually set the appropriate cropping range in x,y,z Click 'finish' to continue saving Meta information (dicom), origin, stenttype and cropping range are saved """ try: vol0 = vv.Aarray( vols[0], vols[0].meta.sampling) # vv.Aarray defines origin: 0,0,0 except AttributeError: print('AttributeError no meta with vol, use given sampling') vol0 = vv.Aarray(vols[0], sampling) # Open cropper tool print('Set the appropriate cropping range for "%s" ' 'and click finish to continue' % cropname) print('Loading... be patient') fig = vv.figure() fig.title = 'Cropping for "%s"' % cropname #vol_crop = cropper.crop3d(vol0, fig) vol_crop = crop3d(vol0, fig) # use local while error with cropper fig.Destroy() if vol_crop.shape == vol0.shape: print('User did not crop') # Calculate crop range from origin rz = int(vol_crop.origin[0] / vol_crop.sampling[0] + 0.5) rz = rz, rz + vol_crop.shape[0] ry = int(vol_crop.origin[1] / vol_crop.sampling[1] + 0.5) ry = ry, ry + vol_crop.shape[1] rx = int(vol_crop.origin[2] / vol_crop.sampling[2] + 0.5) rx = rx, rx + vol_crop.shape[2] # Initialize struct s = ssdf.new() s.sampling = vol_crop.sampling # z, y, x voxel size in mm s.origin = vol_crop.origin s.croprange = rz, ry, rx # in world coordinates s.stenttype = stenttype s.ctcode = ctcode s.ptcode = ptcode # Export and save if len(vols) == 1: # when static ct s.vol = vols[0][rz[0]:rz[1], ry[0]:ry[1], rx[0]:rx[1]] s.meta = vols[0].meta vols[0].meta.PixelData = None # avoid ssdf warning filename = '%s_%s_%s_phase.ssdf' % (ptcode, ctcode, cropname) else: # dynamic ct for volnr in range(0, len(vols)): phase = volnr * 10 if len(vols) == 2: # when only diastolic/systolic volume try: phase = vols[ volnr].meta.SeriesDescription[:2] # '78%, iDose' phase = int(phase) except AttributeError: # has no meta phase = volnr s['vol%i' % phase] = vols[volnr][rz[0]:rz[1], ry[0]:ry[1], rx[0]:rx[1]] try: s['meta%i' % phase] = vols[volnr].meta vols[volnr].meta.PixelData = None # avoid ssdf warning except AttributeError: # has no meta pass # s['meta%i'% phase] = meta # given input var # todo: meta type now error TypeError: unorderable types: DataElement() < DataElement() filename = '%s_%s_%s_phases.ssdf' % (ptcode, ctcode, cropname) file_out = os.path.join(basedir, ptcode, filename) ssdf.save(file_out, s) print() print('ssdf saved to {}.'.format(file_out))