source: trunk/GSASIIIO.py @ 3810

Last change on this file since 3810 was 3810, checked in by toby, 3 years ago

fix range object in GPX file; fix spurious warning on tutorial update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 115.2 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2019-02-03 23:05:41 +0000 (Sun, 03 Feb 2019) $
4# $Author: toby $
5# $Revision: 3810 $
6# $URL: trunk/GSASIIIO.py $
7# $Id: GSASIIIO.py 3810 2019-02-03 23:05:41Z toby $
8########### SVN repository information ###################
9'''
10*GSASIIIO: Misc I/O routines*
11=============================
12
13Module with miscellaneous routines for input and output. Many
14are GUI routines to interact with user.
15
16Includes support for image reading.
17
18Also includes base classes for data import routines.
19
20This module needs some work to separate wx from non-wx routines
21'''
22# If this is being used for GSASIIscriptable, don't depend on wx
23from __future__ import division, print_function
24try:
25    import wx
26except ImportError:
27    class Placeholder(object):
28        def __init__(self):
29            self.Dialog = object
30    wx = Placeholder()
31import math
32import numpy as np
33import numpy.ma as ma
34
35import copy
36import platform
37if '2' in platform.python_version_tuple()[0]:
38    import cPickle
39else:
40    import _pickle as cPickle
41import sys
42import re
43import glob
44import random as ran
45import GSASIIpath
46GSASIIpath.SetVersionNumber("$Revision: 3810 $")
47try:
48    import GSASIIdataGUI as G2gd
49except ImportError:
50    pass
51import GSASIIobj as G2obj
52import GSASIIlattice as G2lat
53import GSASIImath as G2mth
54try:
55    import GSASIIpwdGUI as G2pdG
56    import GSASIIimgGUI as G2imG
57except ImportError:
58    pass
59import GSASIIimage as G2img
60import GSASIIElem as G2el
61import GSASIIstrIO as G2stIO
62import GSASIImapvars as G2mv
63try:
64    import GSASIIctrlGUI as G2G
65except ImportError:
66    pass
67import os
68import os.path as ospath
69
70DEBUG = False       #=True for various prints
71TRANSP = False      #=true to transpose images for testing
72if GSASIIpath.GetConfigValue('Transpose'): TRANSP = True
73npsind = lambda x: np.sin(x*np.pi/180.)
74
75def sfloat(S):
76    'Convert a string to float. An empty field or a unconvertable value is treated as zero'
77    if S.strip():
78        try:
79            return float(S)
80        except ValueError:
81            pass
82    return 0.0
83
84def sint(S):
85    'Convert a string to int. An empty field is treated as zero'
86    if S.strip():
87        return int(S)
88    else:
89        return 0
90
91def trim(val):
92    '''Simplify a string containing leading and trailing spaces
93    as well as newlines, tabs, repeated spaces etc. into a shorter and
94    more simple string, by replacing all ranges of whitespace
95    characters with a single space.
96
97    :param str val: the string to be simplified
98
99    :returns: the (usually) shortened version of the string
100    '''
101    return re.sub('\s+', ' ', val).strip()
102
103def FileDlgFixExt(dlg,file):
104    'this is needed to fix a problem in linux wx.FileDialog'
105    ext = dlg.GetWildcard().split('|')[2*dlg.GetFilterIndex()+1].strip('*')
106    if ext not in file:
107        file += ext
108    return file
109       
110def GetPowderPeaks(fileName):
111    'Read powder peaks from a file'
112    sind = lambda x: math.sin(x*math.pi/180.)
113    asind = lambda x: 180.*math.asin(x)/math.pi
114    wave = 1.54052
115    File = open(fileName,'Ur')
116    Comments = []
117    peaks = []
118    S = File.readline()
119    while S:
120        if S[:1] == '#':
121            Comments.append(S[:-1])
122        else:
123            item = S.split()
124            if len(item) == 1:
125                peaks.append([float(item[0]),1.0])
126            elif len(item) > 1:
127                peaks.append([float(item[0]),float(item[0])])
128        S = File.readline()
129    File.close()
130    if Comments:
131       print ('Comments on file:')
132       for Comment in Comments: 
133            print (Comment)
134            if 'wavelength' in Comment:
135                wave = float(Comment.split('=')[1])
136    Peaks = []
137    if peaks[0][0] > peaks[-1][0]:          # d-spacings - assume CuKa
138        for peak in peaks:
139            dsp = peak[0]
140            sth = wave/(2.0*dsp)
141            if sth < 1.0:
142                tth = 2.0*asind(sth)
143            else:
144                break
145            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
146    else:                                   #2-thetas - assume Cuka (for now)
147        for peak in peaks:
148            tth = peak[0]
149            dsp = wave/(2.0*sind(tth/2.0))
150            Peaks.append([tth,peak[1],True,False,0,0,0,dsp,0.0])
151    limits = [1000.,0.]
152    for peak in Peaks:
153        limits[0] = min(limits[0],peak[0])
154        limits[1] = max(limits[1],peak[0])
155    limits[0] = max(1.,(int(limits[0]-1.)/5)*5.)
156    limits[1] = min(170.,(int(limits[1]+1.)/5)*5.)
157    return Comments,Peaks,limits,wave
158
159def GetCheckImageFile(G2frame,treeId):
160    '''Try to locate an image file if the project and image have been moved
161    together. If the image file cannot be found, request the location from
162    the user.
163
164    :param wx.Frame G2frame: main GSAS-II Frame and data object
165    :param wx.Id treeId: Id for the main tree item for the image
166    :returns: Npix,imagefile,imagetag with (Npix) number of pixels,
167       imagefile, if it exists, or the name of a file that does exist or False if the user presses Cancel
168       and (imagetag) an optional image number
169
170    '''
171    Npix,Imagefile,imagetag = G2frame.GPXtree.GetImageLoc(treeId)
172    if isinstance(Imagefile,list):
173        imagefile,imagetag = Imagefile
174    else:
175        imagefile = Imagefile
176    if not os.path.exists(imagefile):
177        print ('Image file '+imagefile+' not found')
178        fil = imagefile.replace('\\','/') # windows?!
179        # see if we can find a file with same name or in a similarly named sub-dir
180        pth,fil = os.path.split(fil)
181        prevpth = None
182        while pth and pth != prevpth:
183            prevpth = pth
184            if os.path.exists(os.path.join(G2frame.dirname,fil)):
185                print ('found image file '+os.path.join(G2frame.dirname,fil))
186                imagefile = os.path.join(G2frame.dirname,fil)
187                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
188                return Npix,imagefile,imagetag
189            pth,enddir = os.path.split(pth)
190            fil = os.path.join(enddir,fil)
191        # not found as a subdirectory, drop common parts of path for last saved & image file names
192        #    if image was .../A/B/C/imgs/ima.ge
193        #      & GPX was  .../A/B/C/refs/fil.gpx but is now .../NEW/TEST/TEST1
194        #    will look for .../NEW/TEST/TEST1/imgs/ima.ge, .../NEW/TEST/imgs/ima.ge, .../NEW/imgs/ima.ge and so on
195        Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
196        gpxPath = Controls.get('LastSavedAs','').replace('\\','/').split('/') # blank in older .GPX files
197        imgPath = imagefile.replace('\\','/').split('/')
198        for p1,p2 in zip(gpxPath,imgPath):
199            if p1 == p2:
200                gpxPath.pop(0),imgPath.pop(0)
201            else:
202                break
203        fil = os.path.join(*imgPath) # file with non-common prefix elements
204        prevpth = None
205        pth = os.path.abspath(G2frame.dirname)
206        while pth and pth != prevpth:
207            prevpth = pth
208            if os.path.exists(os.path.join(pth,fil)):
209                print ('found image file '+os.path.join(pth,fil))
210                imagefile = os.path.join(pth,fil)
211                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
212                return Npix,imagefile,imagetag
213            pth,enddir = os.path.split(pth)
214        #GSASIIpath.IPyBreak()
215
216    if not os.path.exists(imagefile):
217        # note that this fails (at least on Mac) to get an image during the GUI initialization
218        prevnam = os.path.split(imagefile)[1]
219        prevext = os.path.splitext(imagefile)[1]
220        wildcard = 'Image format (*'+prevext+')|*'+prevext
221        dlg = wx.FileDialog(G2frame, 'Previous image file ('+prevnam+') not found; open here', '.', prevnam,
222                            wildcard,wx.FD_OPEN)
223        try:
224            dlg.SetFilename(''+ospath.split(imagefile)[1])
225            if dlg.ShowModal() == wx.ID_OK:
226                imagefile = dlg.GetPath()
227                G2frame.GPXtree.UpdateImageLoc(treeId,imagefile)
228            else:
229                imagefile = False
230        finally:
231            dlg.Destroy()
232    return Npix,imagefile,imagetag
233
234def EditImageParms(parent,Data,Comments,Image,filename):
235    dlg = wx.Dialog(parent, wx.ID_ANY, 'Edit image parameters',
236                    style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
237    def onClose(event):
238        dlg.EndModal(wx.ID_OK)
239    mainsizer = wx.BoxSizer(wx.VERTICAL)
240    h,w = Image.shape[:2]
241    mainsizer.Add(wx.StaticText(dlg,wx.ID_ANY,'File '+str(filename)+'\nImage size: '+str(h)+' x '+str(w)),
242        0,wx.ALIGN_LEFT|wx.ALL, 2)
243   
244    vsizer = wx.BoxSizer(wx.HORIZONTAL)
245    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Wavelength (\xC5) '),
246        0,wx.ALIGN_LEFT|wx.ALL, 2)
247    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'wavelength')
248    vsizer.Add(wdgt)
249    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
250
251    vsizer = wx.BoxSizer(wx.HORIZONTAL)
252    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Pixel size (\xb5m). Width '),
253        0,wx.ALIGN_LEFT|wx.ALL, 2)
254    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],0,
255                                 size=(50,-1))
256    vsizer.Add(wdgt)
257    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Height '),wx.ALIGN_LEFT|wx.ALL, 2)
258    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['pixelSize'],1,size=(50,-1))
259    vsizer.Add(wdgt)
260    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
261
262    vsizer = wx.BoxSizer(wx.HORIZONTAL)
263    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Sample to detector (mm) '),
264        0,wx.ALIGN_LEFT|wx.ALL, 2)
265    wdgt = G2G.ValidatedTxtCtrl(dlg,Data,'distance')
266    vsizer.Add(wdgt)
267    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
268
269    vsizer = wx.BoxSizer(wx.HORIZONTAL)
270    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Beam center (pixels). X = '),
271        0,wx.ALIGN_LEFT|wx.ALL, 2)
272    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],0,size=(75,-1))
273    vsizer.Add(wdgt)
274    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'  Y = '),wx.ALIGN_LEFT|wx.ALL, 2)
275    wdgt = G2G.ValidatedTxtCtrl(dlg,Data['center'],1,size=(75,-1))
276    vsizer.Add(wdgt)
277    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
278
279    vsizer = wx.BoxSizer(wx.HORIZONTAL)
280    vsizer.Add(wx.StaticText(dlg,wx.ID_ANY,u'Comments '),
281        0,wx.ALIGN_LEFT|wx.ALL, 2)
282    wdgt = G2G.ValidatedTxtCtrl(dlg,Comments,0,size=(250,-1))
283    vsizer.Add(wdgt)
284    mainsizer.Add(vsizer,0,wx.ALIGN_LEFT|wx.ALL, 2)
285
286    btnsizer = wx.StdDialogButtonSizer()
287    OKbtn = wx.Button(dlg, wx.ID_OK, 'Continue')
288    OKbtn.SetDefault()
289    OKbtn.Bind(wx.EVT_BUTTON,onClose)
290    btnsizer.AddButton(OKbtn) # not sure why this is needed
291    btnsizer.Realize()
292    mainsizer.Add(btnsizer, 1, wx.ALIGN_CENTER|wx.ALL|wx.EXPAND, 5)
293    dlg.SetSizer(mainsizer)
294    dlg.CenterOnParent()
295    dlg.ShowModal()
296   
297def LoadImage2Tree(imagefile,G2frame,Comments,Data,Npix,Image):
298    '''Load an image into the tree. Saves the location of the image, as well as the
299    ImageTag (where there is more than one image in the file), if defined.
300    '''
301    ImgNames = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
302    TreeLbl = 'IMG '+os.path.basename(imagefile)
303    ImageTag = Data.get('ImageTag')
304    if ImageTag:
305        TreeLbl += ' #'+'%04d'%(ImageTag)
306        imageInfo = (imagefile,ImageTag)
307    else:
308        imageInfo = imagefile
309    TreeName = G2obj.MakeUniqueLabel(TreeLbl,ImgNames)
310    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=TreeName)
311    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)
312    Imax = np.amax(Image)
313    if G2frame.imageDefault:
314        Data = copy.copy(G2frame.imageDefault)
315        Data['showLines'] = True
316        Data['ring'] = []
317        Data['rings'] = []
318        Data['cutoff'] = 10.
319        Data['pixLimit'] = 20
320        Data['edgemin'] = 100000000
321        Data['calibdmin'] = 0.5
322        Data['calibskip'] = 0
323        Data['ellipses'] = []
324        Data['calibrant'] = ''
325        Data['GonioAngles'] = [0.,0.,0.]
326        Data['DetDepthRef'] = False
327    else:
328        Data['type'] = 'PWDR'
329        Data['color'] = GSASIIpath.GetConfigValue('Contour_color','Paired')
330        if 'tilt' not in Data:          #defaults if not preset in e.g. Bruker importer
331            Data['tilt'] = 0.0
332            Data['rotation'] = 0.0
333            Data['pixLimit'] = 20
334            Data['calibdmin'] = 0.5
335            Data['cutoff'] = 10.
336        Data['showLines'] = False
337        Data['calibskip'] = 0
338        Data['ring'] = []
339        Data['rings'] = []
340        Data['edgemin'] = 100000000
341        Data['ellipses'] = []
342        Data['GonioAngles'] = [0.,0.,0.]
343        Data['DetDepth'] = 0.
344        Data['DetDepthRef'] = False
345        Data['calibrant'] = ''
346        Data['IOtth'] = [5.0,50.0]
347        Data['LRazimuth'] = [0.,180.]
348        Data['azmthOff'] = 0.0
349        Data['outChannels'] = 2250
350        Data['outAzimuths'] = 1
351        Data['centerAzm'] = False
352        Data['fullIntegrate'] = GSASIIpath.GetConfigValue('fullIntegrate',True)
353        Data['setRings'] = False
354        Data['background image'] = ['',-1.0]                           
355        Data['dark image'] = ['',-1.0]
356        Data['Flat Bkg'] = 0.0
357        Data['Oblique'] = [0.5,False]
358    Data['setDefault'] = False
359    Data['range'] = [(0,Imax),[0,Imax]]
360    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),Data)
361    Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],'Thresholds':[(0,Imax),[0,Imax]]}
362    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),Masks)
363    G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Stress/Strain'),
364        {'Type':'True','d-zero':[],'Sample phi':0.0,'Sample z':0.0,'Sample load':0.0})
365    G2frame.GPXtree.SetItemPyData(Id,[Npix,imageInfo])
366    G2frame.PickId = Id
367    G2frame.PickIdText = G2frame.GetTreeItemsList(G2frame.PickId)
368    G2frame.Image = Id
369
370def GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None,FormatName=''):
371    '''Read a single image with an image importer. This is called to reread an image
372    after it has already been imported with :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
373    (or :func:`ReadImages` in Auto Integration) so it is not necessary to reload metadata.
374
375    :param wx.Frame G2frame: main GSAS-II Frame and data object.
376    :param str imagefile: name of image file
377    :param bool imageOnly: If True return only the image,
378      otherwise  (default) return more (see below)
379    :param int/str ImageTag: specifies a particular image to be read from a file.
380      First image is read if None (default).
381    :param str formatName: the image reader formatName
382
383    :returns: an image as a numpy array or a list of four items:
384      Comments, Data, Npix and the Image, as selected by imageOnly
385
386    '''
387    # determine which formats are compatible with this file
388    primaryReaders = []
389    secondaryReaders = []
390    for rd in G2frame.ImportImageReaderlist:
391        flag = rd.ExtensionValidator(imagefile)
392        if flag is None: 
393            secondaryReaders.append(rd)
394        elif flag:
395            if not FormatName:
396                primaryReaders.append(rd)
397            elif FormatName == rd.formatName:
398                primaryReaders.append(rd)
399    if len(secondaryReaders) + len(primaryReaders) == 0:
400        print('Error: No matching format for file '+imagefile)
401        raise Exception('No image read')
402    errorReport = ''
403    if not imagefile:
404        return
405    for rd in primaryReaders+secondaryReaders:
406        rd.ReInitialize() # purge anything from a previous read
407        rd.errors = "" # clear out any old errors
408        if not rd.ContentsValidator(imagefile): # rejected on cursory check
409            errorReport += "\n  "+rd.formatName + ' validator error'
410            if rd.errors: 
411                errorReport += ': '+rd.errors
412                continue
413        if imageOnly:
414            ParentFrame = None # prevent GUI access on reread
415        else:
416            ParentFrame = G2frame
417        if GSASIIpath.GetConfigValue('debug'):
418            flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
419        else:
420            flag = False
421            try:
422                flag = rd.Reader(imagefile,ParentFrame,blocknum=ImageTag)
423            except rd.ImportException as detail:
424                rd.errors += "\n  Read exception: "+str(detail)
425            except Exception as detail:
426                import traceback
427                rd.errors += "\n  Unhandled read exception: "+str(detail)
428                rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
429        if flag: # this read succeeded
430            if rd.Image is None:
431                raise Exception('No image read. Strange!')
432            if GSASIIpath.GetConfigValue('Transpose'):
433                print ('Transposing Image!')
434                rd.Image = rd.Image.T
435            #rd.readfilename = imagefile
436            if imageOnly:
437                return rd.Image
438            else:
439                return rd.Comments,rd.Data,rd.Npix,rd.Image
440    else:
441        print('Error reading file '+imagefile)
442        print('Error messages(s)\n'+errorReport)
443        raise Exception('No image read')   
444
445def ReadImages(G2frame,imagefile):
446    '''Read one or more images from a file and put them into the Tree
447    using image importers. Called only in :meth:`AutoIntFrame.OnTimerLoop`.
448
449    ToDo: Images are most commonly read in :meth:`GSASIIdataGUI.GSASII.OnImportGeneric`
450    which is called from :meth:`GSASIIdataGUI.GSASII.OnImportImage`
451    it would be good if these routines used a common code core so that changes need to
452    be made in only one place.
453
454    :param wx.Frame G2frame: main GSAS-II Frame and data object.
455    :param str imagefile: name of image file
456
457    :returns: a list of the id's of the IMG tree items created
458    '''
459    # determine which formats are compatible with this file
460    primaryReaders = []
461    secondaryReaders = []
462    for rd in G2frame.ImportImageReaderlist:
463        flag = rd.ExtensionValidator(imagefile)
464        if flag is None:
465            secondaryReaders.append(rd)
466        elif flag:
467            primaryReaders.append(rd)
468    if len(secondaryReaders) + len(primaryReaders) == 0:
469        print('Error: No matching format for file '+imagefile)
470        raise Exception('No image read')
471    errorReport = ''
472    rdbuffer = {} # create temporary storage for file reader
473    for rd in primaryReaders+secondaryReaders:
474        rd.ReInitialize() # purge anything from a previous read
475        rd.errors = "" # clear out any old errors
476        if not rd.ContentsValidator(imagefile): # rejected on cursory check
477            errorReport += "\n  "+rd.formatName + ' validator error'
478            if rd.errors: 
479                errorReport += ': '+rd.errors
480                continue
481        ParentFrame = G2frame
482        block = 0
483        repeat = True
484        CreatedIMGitems = []
485        while repeat: # loop if the reader asks for another pass on the file
486            block += 1
487            repeat = False
488            if GSASIIpath.GetConfigValue('debug'):
489                flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
490            else:
491                flag = False
492                try:
493                    flag = rd.Reader(imagefile,ParentFrame,blocknum=block,Buffer=rdbuffer)
494                except rd.ImportException as detail:
495                    rd.errors += "\n  Read exception: "+str(detail)
496                except Exception as detail:
497                    import traceback
498                    rd.errors += "\n  Unhandled read exception: "+str(detail)
499                    rd.errors += "\n  Traceback info:\n"+str(traceback.format_exc())
500            if flag: # this read succeeded
501                if rd.Image is None:
502                    raise Exception('No image read. Strange!')
503                if GSASIIpath.GetConfigValue('Transpose'):
504                    print ('Transposing Image!')
505                    rd.Image = rd.Image.T
506                rd.Data['ImageTag'] = rd.repeatcount
507                rd.readfilename = imagefile
508                # Load generic metadata, as configured
509                GetColumnMetadata(rd)
510                LoadImage2Tree(imagefile,G2frame,rd.Comments,rd.Data,rd.Npix,rd.Image)
511                repeat = rd.repeat
512            CreatedIMGitems.append(G2frame.Image)
513        if CreatedIMGitems: return CreatedIMGitems
514    else:
515        print('Error reading file '+imagefile)
516        print('Error messages(s)\n'+errorReport)
517        return []
518        #raise Exception('No image read')   
519
520def SaveMultipleImg(G2frame):
521    if not G2frame.GPXtree.GetCount():
522        print ('no images!')
523        return
524    choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
525    if len(choices) == 1:
526        names = choices
527    else:
528        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices)
529        dlg.SetSelections([])
530        names = []
531        if dlg.ShowModal() == wx.ID_OK:
532            names = [choices[sel] for sel in dlg.GetSelections()]
533        dlg.Destroy()
534    if not names: return
535    for name in names:
536        Id = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, name)
537        Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(Id)
538        imroot = os.path.splitext(imagefile)[0]
539        if imagetag:
540            imroot += '_' + str(imagetag)
541        Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'))
542        print('Writing '+imroot+'.imctrl')
543        File = open(imroot+'.imctrl','w')
544        keys = ['type','wavelength','calibrant','distance','center',
545                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth',
546                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
547                    'calibskip','pixLimit','cutoff','calibdmin','chisq','Flat Bkg',
548                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
549        for key in keys:
550            if key not in Data: continue    #uncalibrated!
551            File.write(key+':'+str(Data[key])+'\n')
552        File.close()
553        mask = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'))
554        G2imG.CleanupMasks(mask)
555        print('Writing '+imroot+'.immask')
556        File = open(imroot+'.immask','w')
557        for key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
558            File.write(key+':'+str(mask[key])+'\n')
559        File.close()
560       
561def PutG2Image(filename,Comments,Data,Npix,image):
562    'Write an image as a python pickle - might be better as an .edf file?'
563    File = open(filename,'wb')
564    cPickle.dump([Comments,Data,Npix,image],File,2)
565    File.close()
566    return
567
568objectScanIgnore = [int,bool,float,str,np.float64,np.int32,np.int64,np.ndarray,G2obj.G2VarObj,G2obj.ExpressionObj]
569try:
570    objectScanIgnore += [ma.MaskedArray] # fails in doc builds
571except AttributeError:
572    pass
573   
574if '2' in platform.python_version_tuple()[0]:
575    objectScanIgnore += [unicode,long,]
576   
577def objectScan(data,tag,indexStack=[]):
578    '''Recursively scan an object looking for unexpected data types.
579    This is used in debug mode to scan .gpx files for objects we did not
580    intend to be there.
581    '''
582    if type(data) is list or type(data) is tuple:
583        for i in range(len(data)):
584            objectScan(data[i],tag,indexStack+[i])
585    elif type(data) is dict:
586        for key in data:
587            objectScan(data[key],tag,indexStack+[key])
588    elif data is None:
589        return
590    elif type(data) in objectScanIgnore:
591        return
592    else:
593        s = 'unexpected object in '+tag
594        for i in indexStack:
595            s += "[{}]".format(i)
596        #print(s,data.__class__.__name__) # loses full name of class
597        print(s,type(data))
598        global unexpectedObject
599        unexpectedObject = True
600   
601def ProjFileOpen(G2frame,showProvenance=True):
602    'Read a GSAS-II project file and load into the G2 data tree'
603    if not os.path.exists(G2frame.GSASprojectfile):
604        print ('\n*** Error attempt to open project file that does not exist:\n   '+
605               str(G2frame.GSASprojectfile))
606        return
607    LastSavedUsing = None
608    filep = open(G2frame.GSASprojectfile,'rb')
609    if showProvenance: print ('loading from file: '+G2frame.GSASprojectfile)
610    wx.BeginBusyCursor()
611    try:
612        while True:
613            try:
614                if '2' in platform.python_version_tuple()[0]:
615                    data = cPickle.load(filep)
616                else:
617                    data = cPickle.load(filep,encoding='latin-1')
618            except EOFError:
619                break
620            datum = data[0]
621            # scan the GPX file for unexpected objects
622            if GSASIIpath.GetConfigValue('debug'):
623                global unexpectedObject
624                unexpectedObject = False               
625                objectScan(data,'tree item "{}" entry '.format(datum[0]))
626                #if unexpectedObject:
627                #    print(datum[0])
628                #    GSASIIpath.IPyBreak()
629            Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=datum[0])
630            if datum[0].startswith('PWDR'):               
631                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
632                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
633                G2frame.GPXtree.SetItemPyData(Id,datum[1][:3])  #temp. trim off junk (patch?)
634            elif datum[0].startswith('HKLF'): 
635                if 'ranId' not in datum[1][0]: # patch: add random Id if not present
636                    datum[1][0]['ranId'] = ran.randint(0,sys.maxsize)
637                G2frame.GPXtree.SetItemPyData(Id,datum[1])
638            else:
639                G2frame.GPXtree.SetItemPyData(Id,datum[1])             
640                if datum[0] == 'Controls' and 'LastSavedUsing' in datum[1]:
641                    LastSavedUsing = datum[1]['LastSavedUsing']
642                if datum[0] == 'Controls' and 'PythonVersions' in datum[1] and GSASIIpath.GetConfigValue('debug') and showProvenance:
643                    print('DBG_Packages used to create .GPX file:')
644                    if 'dict' in str(type(datum[1]['PythonVersions'])):  #patch
645                        for p in sorted(datum[1]['PythonVersions'],key=lambda s: s.lower()):
646                            print({:<14s}: {:s}".format(p[0],p[1]))
647                    else:
648                        for p in datum[1]['PythonVersions']:
649                            print({:<12s} {:s}".format(p[0]+':',p[1]))
650            oldPDF = False
651            for datus in data[1:]:
652#patch - 1/23/17 PDF cleanup
653                if datus[0][:4] in ['I(Q)','S(Q)','F(Q)','G(R)']:
654                    oldPDF = True
655                    data[1][1][datus[0][:4]] = copy.deepcopy(datus[1][:2])
656                    continue
657#end PDF cleanup
658                sub = G2frame.GPXtree.AppendItem(Id,datus[0])
659#patch
660                if datus[0] == 'Instrument Parameters' and len(datus[1]) == 1:
661                    if datum[0].startswith('PWDR'):
662                        datus[1] = [dict(zip(datus[1][3],zip(datus[1][0],datus[1][1],datus[1][2]))),{}]
663                    else:
664                        datus[1] = [dict(zip(datus[1][2],zip(datus[1][0],datus[1][1]))),{}]
665                    for item in datus[1][0]:               #zip makes tuples - now make lists!
666                        datus[1][0][item] = list(datus[1][0][item])
667#end patch
668                G2frame.GPXtree.SetItemPyData(sub,datus[1])
669            if 'PDF ' in datum[0][:4] and oldPDF:
670                sub = G2frame.GPXtree.AppendItem(Id,'PDF Peaks')
671                G2frame.GPXtree.SetItemPyData(sub,{'Limits':[1.,5.],'Background':[2,[0.,-0.2*np.pi],False],'Peaks':[]})
672            if datum[0].startswith('IMG'):                   #retrieve image default flag & data if set
673                Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
674                if Data['setDefault']:
675                    G2frame.imageDefault = Data               
676        if LastSavedUsing:
677            print('GPX load successful. Last saved with GSAS-II revision '+LastSavedUsing)
678        else:
679            print('project load successful')
680        G2frame.NewPlot = True
681    except Exception as errmsg:
682        if GSASIIpath.GetConfigValue('debug'):
683            print('\nError reading GPX file:',errmsg)
684            import traceback
685            print (traceback.format_exc())
686        msg = wx.MessageDialog(G2frame,message="Error reading file "+
687            str(G2frame.GSASprojectfile)+". This is not a current GSAS-II .gpx file",
688            caption="Load Error",style=wx.ICON_ERROR | wx.OK | wx.STAY_ON_TOP)
689        msg.ShowModal()
690    finally:
691        filep.close()
692        wx.EndBusyCursor()
693        G2frame.Status.SetStatusText('Mouse RB drag/drop to reorder',0)
694    G2frame.SetTitleByGPX()
695   
696def ProjFileSave(G2frame):
697    'Save a GSAS-II project file'
698    if not G2frame.GPXtree.IsEmpty():
699        file = open(G2frame.GSASprojectfile,'wb')
700        print ('save to file: '+G2frame.GSASprojectfile)
701        # stick the file name into the tree and version info into tree so they are saved.
702        # (Controls should always be created at this point)
703        try:
704            Controls = G2frame.GPXtree.GetItemPyData(
705                G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
706            Controls['LastSavedAs'] = os.path.abspath(G2frame.GSASprojectfile)
707            Controls['LastSavedUsing'] = str(GSASIIpath.GetVersionNumber())
708            Controls['PythonVersions'] = G2frame.PackageVersions
709        except:
710            pass
711        wx.BeginBusyCursor()
712        try:
713            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
714            while item:
715                data = []
716                name = G2frame.GPXtree.GetItemText(item)
717                data.append([name,G2frame.GPXtree.GetItemPyData(item)])
718                item2, cookie2 = G2frame.GPXtree.GetFirstChild(item)
719                while item2:
720                    name = G2frame.GPXtree.GetItemText(item2)
721                    data.append([name,G2frame.GPXtree.GetItemPyData(item2)])
722                    item2, cookie2 = G2frame.GPXtree.GetNextChild(item, cookie2)                           
723                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)                           
724                cPickle.dump(data,file,2)
725            file.close()
726            pth = os.path.split(os.path.abspath(G2frame.GSASprojectfile))[0]
727            if GSASIIpath.GetConfigValue('Save_paths'): G2G.SaveGPXdirectory(pth)
728            G2frame.LastGPXdir = pth
729        finally:
730            wx.EndBusyCursor()
731        print('project save successful')
732
733def SaveIntegration(G2frame,PickId,data,Overwrite=False):
734    'Save image integration results as powder pattern(s)'
735    waves = {'Cu':[1.54051,1.54433],'Ti':[2.74841,2.75207],'Cr':[2.28962,2.29351],
736        'Fe':[1.93597,1.93991],'Co':[1.78892,1.79278],'Mo':[0.70926,0.713543],
737        'Ag':[0.559363,0.563775]}
738    azms = G2frame.Integrate[1]
739    X = G2frame.Integrate[2][:-1]
740    N = len(X)
741    Id = G2frame.GPXtree.GetItemParent(PickId)
742    name = G2frame.GPXtree.GetItemText(Id)
743    name = name.replace('IMG ',data['type']+' ')
744    Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Comments'))
745    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.root, 'Controls'))
746    if 'PWDR' in name:
747        if 'target' in data:
748            names = ['Type','Lam1','Lam2','I(L2)/I(L1)','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
749            codes = [0 for i in range(14)]
750        else:
751            names = ['Type','Lam','Zero','Polariz.','U','V','W','X','Y','Z','SH/L','Azimuth'] 
752            codes = [0 for i in range(12)]
753    elif 'SASD' in name:
754        names = ['Type','Lam','Zero','Azimuth'] 
755        codes = [0 for i in range(4)]
756        X = 4.*np.pi*npsind(X/2.)/data['wavelength']    #convert to q
757    Xminmax = [X[0],X[-1]]
758    Azms = []
759    dazm = 0.
760    if data['fullIntegrate'] and data['outAzimuths'] == 1:
761        Azms = [45.0,]                              #a poor man's average?
762    else:
763        for i,azm in enumerate(azms[:-1]):
764            if azm > 360. and azms[i+1] > 360.:
765                Azms.append(G2img.meanAzm(azm%360.,azms[i+1]%360.))
766            else:   
767                Azms.append(G2img.meanAzm(azm,azms[i+1]))
768        dazm = np.min(np.abs(np.diff(azms)))/2.
769    G2frame.IntgOutList = []
770    for i,azm in enumerate(azms[:-1]):
771        Aname = name+" Azm= %.2f"%((azm+dazm)%360.)
772        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
773        # if Overwrite delete any duplicate
774        if Overwrite and G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname):
775            print('Replacing '+Aname)
776            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Aname)
777            G2frame.GPXtree.Delete(item)
778        else:
779            nOcc = 0
780            while item:
781                Name = G2frame.GPXtree.GetItemText(item)
782                if Aname in Name:
783                    nOcc += 1
784                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
785            if nOcc:
786                Aname += '(%d)'%(nOcc)
787        Sample = G2obj.SetDefaultSample()       #set as Debye-Scherrer
788        Sample['Gonio. radius'] = data['distance']
789        Sample['Omega'] = data['GonioAngles'][0]
790        Sample['Chi'] = data['GonioAngles'][1]
791        Sample['Phi'] = data['GonioAngles'][2]
792        Sample['Azimuth'] = (azm+dazm)%360.    #put here as bin center
793        polariz = 0.99    #set default polarization for synchrotron radiation!
794        for item in Comments:
795            if 'polariz' in item:
796                try:
797                    polariz = float(item.split('=')[1])
798                except:
799                    polariz = 0.99
800            for key in ('Temperature','Pressure','Time','FreePrm1','FreePrm2','FreePrm3','Omega',
801                'Chi','Phi'):
802                if key.lower() in item.lower():
803                    try:
804                        Sample[key] = float(item.split('=')[1])
805                    except:
806                        pass
807            if 'label_prm' in item.lower():
808                for num in ('1','2','3'):
809                    if 'label_prm'+num in item.lower():
810                        Controls['FreePrm'+num] = item.split('=')[1].strip()
811        if 'PWDR' in Aname:
812            if 'target' in data:    #from lab x-ray 2D imaging data
813                wave1,wave2 = waves[data['target']]
814                parms = ['PXC',wave1,wave2,0.5,0.0,polariz,290.,-40.,30.,6.,-14.,0.0,0.0001,Azms[i]]
815            else:
816                parms = ['PXC',data['wavelength'],0.0,polariz,1.0,-0.10,0.4,0.30,1.0,0.0,0.0001,Azms[i]]
817        elif 'SASD' in Aname:
818            Sample['Trans'] = data['SampleAbs'][0]
819            parms = ['LXC',data['wavelength'],0.0,Azms[i]]
820        Y = G2frame.Integrate[0][i]
821        Ymin = np.min(Y)
822        Ymax = np.max(Y)
823        W = np.where(Y>0.,1./Y,1.e-6)                    #probably not true
824        Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=Aname)
825        G2frame.IntgOutList.append(Id)
826        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),Comments)                   
827        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Limits'),copy.deepcopy([tuple(Xminmax),Xminmax]))
828        if 'PWDR' in Aname:
829            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Background'),[['chebyschev',1,3,1.0,0.0,0.0],
830                {'nDebye':0,'debyeTerms':[],'nPeaks':0,'peaksList':[]}])
831        inst = [dict(zip(names,zip(parms,parms,codes))),{}]
832        for item in inst[0]:
833            inst[0][item] = list(inst[0][item])
834        G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Instrument Parameters'),inst)
835        if 'PWDR' in Aname:
836            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
837            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Peak List'),{'sigDict':{},'peaks':[]})
838            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Index Peak List'),[[],[]])
839            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Unit Cells List'),[])
840            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Reflection Lists'),{})
841        elif 'SASD' in Aname:             
842            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Substances'),G2pdG.SetDefaultSubstances())
843            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Sample Parameters'),Sample)
844            G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Models'),G2pdG.SetDefaultSASDModel())
845        valuesdict = {
846            'wtFactor':1.0,'Dummy':False,'ranId':ran.randint(0,sys.maxsize),'Offset':[0.0,0.0],'delOffset':0.02*Ymax,
847            'refOffset':-0.1*Ymax,'refDelt':0.1*Ymax,'Yminmax':[Ymin,Ymax]}
848        G2frame.GPXtree.SetItemPyData(Id,[valuesdict,
849            [np.array(X),np.array(Y),np.array(W),np.zeros(N),np.zeros(N),np.zeros(N)]])
850    return Id       #last powder pattern generated
851   
852def XYsave(G2frame,XY,labelX='X',labelY='Y',names=[]):
853    'Save XY table data'
854    pth = G2G.GetExportPath(G2frame)
855    dlg = wx.FileDialog(
856        G2frame, 'Enter csv filename for XY table', pth, '',
857        'XY table file (*.csv)|*.csv',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
858    try:
859        if dlg.ShowModal() == wx.ID_OK:
860            filename = dlg.GetPath()
861            filename = os.path.splitext(filename)[0]+'.csv'
862            File = open(filename,'w')
863        else:
864            filename = None
865    finally:
866        dlg.Destroy()
867    if not filename:
868        return
869    for i in range(len(XY)):
870        if len(names):
871            header = '%s,%s(%s)\n'%(labelX,labelY,names[i])
872        else:
873            header = '%s,%s(%d)\n'%(labelX,labelY,i)
874        File.write(header)
875        for x,y in XY[i].T:
876            File.write('%.3f,%.3f\n'%(x,y))   
877    File.close()
878    print (' XY data saved to: '+filename)
879           
880def PDFSave(G2frame,exports,PDFsaves):
881    'Save a PDF I(Q), S(Q), F(Q) and G(r)  in column formats'
882    import scipy.interpolate as scintp
883    if len(exports) > 1:
884        dirname = G2G.askSaveDirectory(G2frame)
885        if not dirname: return
886    else:
887        defnam = exports[0].replace(' ','_')[5:]
888        filename = G2G.askSaveFile(G2frame,defnam,'.gr','G(r) file, etc.')
889        if not filename: return
890        dirname,filename = os.path.split(filename)
891        filename = os.path.splitext(filename)[0]
892    for export in exports:
893        if len(exports) > 1:
894            filename = export.replace(' ','_')[5:]
895        PickId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, export)
896        PDFControls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, PickId,'PDF Controls'))
897        if PDFsaves[0]:     #I(Q)
898            iqfilename = ospath.join(dirname,filename+'.iq')
899            iqdata = PDFControls['I(Q)'][0]
900            iqfxn = scintp.interp1d(iqdata[0],iqdata[1],kind='linear')
901            iqfile = open(iqfilename,'w')
902            iqfile.write('#T I(Q) %s\n'%(export))
903            iqfile.write('#L Q     I(Q)\n')
904            qnew = np.arange(iqdata[0][0],iqdata[0][-1],0.005)
905            iqnew = zip(qnew,iqfxn(qnew))
906            for q,iq in iqnew:
907                iqfile.write("%15.6g %15.6g\n" % (q,iq))
908            iqfile.close()
909            print (' I(Q) saved to: '+iqfilename)
910           
911        if PDFsaves[1]:     #S(Q)
912            sqfilename = ospath.join(dirname,filename+'.sq')
913            sqdata = PDFControls['S(Q)'][1]
914            sqfxn = scintp.interp1d(sqdata[0],sqdata[1],kind='linear')
915            sqfile = open(sqfilename,'w')
916            sqfile.write('#T S(Q) %s\n'%(export))
917            sqfile.write('#L Q     S(Q)\n')
918            qnew = np.arange(sqdata[0][0],sqdata[0][-1],0.005)
919            sqnew = zip(qnew,sqfxn(qnew))
920            for q,sq in sqnew:
921                sqfile.write("%15.6g %15.6g\n" % (q,sq))
922            sqfile.close()
923            print (' S(Q) saved to: '+sqfilename)
924           
925        if PDFsaves[2]:     #F(Q)
926            fqfilename = ospath.join(dirname,filename+'.fq')
927            fqdata = PDFControls['F(Q)'][1]
928            fqfxn = scintp.interp1d(fqdata[0],fqdata[1],kind='linear')
929            fqfile = open(fqfilename,'w')
930            fqfile.write('#T F(Q) %s\n'%(export))
931            fqfile.write('#L Q     F(Q)\n')
932            qnew = np.arange(fqdata[0][0],fqdata[0][-1],0.005)
933            fqnew = zip(qnew,fqfxn(qnew))
934            for q,fq in fqnew:
935                fqfile.write("%15.6g %15.6g\n" % (q,fq))
936            fqfile.close()
937            print (' F(Q) saved to: '+fqfilename)
938           
939        if PDFsaves[3]:     #G(R)
940            grfilename = ospath.join(dirname,filename+'.gr')
941            grdata = PDFControls['G(R)'][1]
942            grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
943            grfile = open(grfilename,'w')
944            grfile.write('#T G(R) %s\n'%(export))
945            grfile.write('#L R     G(R)\n')
946            rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
947            grnew = zip(rnew,grfxn(rnew))
948            for r,gr in grnew:
949                grfile.write("%15.6g %15.6g\n" % (r,gr))
950            grfile.close()
951            print (' G(R) saved to: '+grfilename)
952       
953        if PDFsaves[4]: #pdfGUI file for G(R)
954            pId = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, 'PWDR'+export[4:])
955            Inst = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, pId,'Instrument Parameters'))[0]
956            Limits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame, pId,'Limits'))
957            grfilename = ospath.join(dirname,filename+'.gr')
958            grdata = PDFControls['G(R)'][1]
959            qdata = PDFControls['I(Q)'][1][0]
960            grfxn = scintp.interp1d(grdata[0],grdata[1],kind='linear')
961            grfile = open(grfilename,'w')
962            rnew = np.arange(grdata[0][0],grdata[0][-1],0.010)
963            grnew = zip(rnew,grfxn(rnew))
964
965            grfile.write('[DEFAULT]\n')
966            grfile.write('\n')
967            grfile.write('version = GSAS-II-v'+str(GSASIIpath.GetVersionNumber())+'\n')
968            grfile.write('\n')
969            grfile.write('# input and output specifications\n')
970            grfile.write('dataformat = Qnm\n')
971            grfile.write('inputfile = %s\n'%(PDFControls['Sample']['Name']))
972            grfile.write('backgroundfile = %s\n'%(PDFControls['Sample Bkg.']['Name']))
973            grfile.write('outputtype = gr\n')
974            grfile.write('\n')
975            grfile.write('# PDF calculation setup\n')
976            if 'x' in Inst['Type']:
977                grfile.write('mode = %s\n'%('xray'))
978            elif 'N' in Inst['Type']:
979                grfile.write('mode = %s\n'%('neutron'))
980            wave = G2mth.getMeanWave(Inst)
981            grfile.write('wavelength = %.5f\n'%(wave))
982            formula = ''
983            for el in PDFControls['ElList']:
984                formula += el
985                num = PDFControls['ElList'][el]['FormulaNo']
986                if num == round(num):
987                    formula += '%d'%(int(num))
988                else:
989                    formula += '%.2f'%(num)
990            grfile.write('composition = %s\n'%(formula))
991            grfile.write('bgscale = %.3f\n'%(-PDFControls['Sample Bkg.']['Mult']))
992            highQ = 2.*np.pi/G2lat.Pos2dsp(Inst,Limits[1][1])
993            grfile.write('qmaxinst = %.2f\n'%(highQ))
994            grfile.write('qmin = %.5f\n'%(qdata[0]))
995            grfile.write('qmax = %.4f\n'%(qdata[-1]))
996            grfile.write('rmin = %.2f\n'%(PDFControls['Rmin']))
997            grfile.write('rmax = %.2f\n'%(PDFControls['Rmax']))
998            grfile.write('rstep = 0.01\n')
999           
1000           
1001            grfile.write('\n')
1002            grfile.write('# End of config '+63*'-')
1003            grfile.write('\n')
1004            grfile.write('#### start data\n')
1005            grfile.write('#S 1\n')
1006            grfile.write('#L r($\AA$)  G($\AA^{-2}$)\n')           
1007            for r,gr in grnew:
1008                grfile.write("%15.2F %15.6F\n" % (r,gr))
1009            grfile.close()
1010            print (' G(R) saved to: '+grfilename)
1011   
1012def PeakListSave(G2frame,file,peaks):
1013    'Save powder peaks to a data file'
1014    print ('save peak list to file: '+G2frame.peaklistfile)
1015    if not peaks:
1016        dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
1017        try:
1018            dlg.ShowModal()
1019        finally:
1020            dlg.Destroy()
1021        return
1022    for peak in peaks:
1023        file.write("%10.4f %12.2f %10.3f %10.3f \n" % \
1024            (peak[0],peak[2],peak[4],peak[6]))
1025    print ('peak list saved')
1026             
1027def IndexPeakListSave(G2frame,peaks):
1028    'Save powder peaks from the indexing list'
1029    file = open(G2frame.peaklistfile,'wa')
1030    print ('save index peak list to file: '+G2frame.peaklistfile)
1031    wx.BeginBusyCursor()
1032    try:
1033        if not peaks:
1034            dlg = wx.MessageDialog(G2frame, 'No peaks!', 'Nothing to save!', wx.OK)
1035            try:
1036                dlg.ShowModal()
1037            finally:
1038                dlg.Destroy()
1039            return
1040        for peak in peaks:
1041            file.write("%12.6f\n" % (peak[7]))
1042        file.close()
1043    finally:
1044        wx.EndBusyCursor()
1045    print ('index peak list saved')
1046   
1047class MultipleChoicesDialog(wx.Dialog):
1048    '''A dialog that offers a series of choices, each with a
1049    title and a wx.Choice widget. Intended to be used Modally.
1050    typical input:
1051
1052        *  choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1053        *  headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1054       
1055    selections are placed in self.chosen when OK is pressed
1056
1057    Also see GSASIIctrlGUI
1058    '''
1059    def __init__(self,choicelist,headinglist,
1060                 head='Select options',
1061                 title='Please select from options below',
1062                 parent=None):
1063        self.chosen = []
1064        wx.Dialog.__init__(
1065            self,parent,wx.ID_ANY,head, 
1066            pos=wx.DefaultPosition,style=wx.DEFAULT_DIALOG_STYLE)
1067        panel = wx.Panel(self)
1068        mainSizer = wx.BoxSizer(wx.VERTICAL)
1069        mainSizer.Add((10,10),1)
1070        topLabl = wx.StaticText(panel,wx.ID_ANY,title)
1071        mainSizer.Add(topLabl,0,wx.ALIGN_CENTER_VERTICAL|wx.CENTER,10)
1072        self.ChItems = []
1073        for choice,lbl in zip(choicelist,headinglist):
1074            mainSizer.Add((10,10),1)
1075            self.chosen.append(0)
1076            topLabl = wx.StaticText(panel,wx.ID_ANY,' '+lbl)
1077            mainSizer.Add(topLabl,0,wx.ALIGN_LEFT,10)
1078            self.ChItems.append(wx.Choice(self, wx.ID_ANY, (100, 50), choices = choice))
1079            mainSizer.Add(self.ChItems[-1],0,wx.ALIGN_CENTER,10)
1080
1081        OkBtn = wx.Button(panel,-1,"Ok")
1082        OkBtn.Bind(wx.EVT_BUTTON, self.OnOk)
1083        cancelBtn = wx.Button(panel,-1,"Cancel")
1084        cancelBtn.Bind(wx.EVT_BUTTON, self.OnCancel)
1085        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1086        btnSizer.Add((20,20),1)
1087        btnSizer.Add(OkBtn)
1088        btnSizer.Add((20,20),1)
1089        btnSizer.Add(cancelBtn)
1090        btnSizer.Add((20,20),1)
1091        mainSizer.Add(btnSizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
1092        panel.SetSizer(mainSizer)
1093        panel.Fit()
1094        self.Fit()
1095       
1096    def OnOk(self,event):
1097        parent = self.GetParent()
1098        if parent is not None: parent.Raise()
1099        # save the results from the choice widgets
1100        self.chosen = []
1101        for w in self.ChItems:
1102            self.chosen.append(w.GetSelection())
1103        self.EndModal(wx.ID_OK)             
1104           
1105    def OnCancel(self,event):
1106        parent = self.GetParent()
1107        if parent is not None: parent.Raise()
1108        self.chosen = []
1109        self.EndModal(wx.ID_CANCEL)             
1110           
1111def ExtractFileFromZip(filename, selection=None, confirmread=True,
1112                       confirmoverwrite=True, parent=None,
1113                       multipleselect=False):
1114    '''If the filename is a zip file, extract a file from that
1115    archive.
1116
1117    :param list Selection: used to predefine the name of the file
1118      to be extracted. Filename case and zip directory name are
1119      ignored in selection; the first matching file is used.
1120
1121    :param bool confirmread: if True asks the user to confirm before expanding
1122      the only file in a zip
1123
1124    :param bool confirmoverwrite: if True asks the user to confirm
1125      before overwriting if the extracted file already exists
1126
1127    :param bool multipleselect: if True allows more than one zip
1128      file to be extracted, a list of file(s) is returned.
1129      If only one file is present, do not ask which one, otherwise
1130      offer a list of choices (unless selection is used).
1131   
1132    :returns: the name of the file that has been created or a
1133      list of files (see multipleselect)
1134
1135    If the file is not a zipfile, return the name of the input file.
1136    If the zipfile is empty or no file has been selected, return None
1137    '''
1138    import zipfile # do this now, since we can save startup time by doing this only on need
1139    import shutil
1140    zloc = os.path.split(filename)[0]
1141    if not zipfile.is_zipfile(filename):
1142        #print("not zip")
1143        return filename
1144
1145    z = zipfile.ZipFile(filename,'r')
1146    zinfo = z.infolist()
1147
1148    if len(zinfo) == 0:
1149        #print('Zip has no files!')
1150        zlist = [-1]
1151    if selection:
1152        choices = [os.path.split(i.filename)[1].lower() for i in zinfo]
1153        if selection.lower() in choices:
1154            zlist = [choices.index(selection.lower())]
1155        else:
1156            print('debug: file '+str(selection)+' was not found in '+str(filename))
1157            zlist = [-1]
1158    elif len(zinfo) == 1 and confirmread:
1159        result = wx.ID_NO
1160        dlg = wx.MessageDialog(
1161            parent,
1162            'Is file '+str(zinfo[0].filename)+
1163            ' what you want to extract from '+
1164            str(os.path.split(filename)[1])+'?',
1165            'Confirm file', 
1166            wx.YES_NO | wx.ICON_QUESTION)
1167        try:
1168            result = dlg.ShowModal()
1169        finally:
1170            dlg.Destroy()
1171        if result == wx.ID_NO:
1172            zlist = [-1]
1173        else:
1174            zlist = [0]
1175    elif len(zinfo) == 1:
1176        zlist = [0]
1177    elif multipleselect:
1178        # select one or more from a from list
1179        choices = [i.filename for i in zinfo]
1180        dlg = G2G.G2MultiChoiceDialog(parent,'Select file(s) to extract from zip file '+str(filename),
1181            'Choose file(s)',choices)
1182        if dlg.ShowModal() == wx.ID_OK:
1183            zlist = dlg.GetSelections()
1184        else:
1185            zlist = []
1186        dlg.Destroy()
1187    else:
1188        # select one from a from list
1189        choices = [i.filename for i in zinfo]
1190        dlg = wx.SingleChoiceDialog(parent,
1191            'Select file to extract from zip file'+str(filename),'Choose file',
1192            choices,)
1193        if dlg.ShowModal() == wx.ID_OK:
1194            zlist = [dlg.GetSelection()]
1195        else:
1196            zlist = [-1]
1197        dlg.Destroy()
1198       
1199    outlist = []
1200    for zindex in zlist:
1201        if zindex >= 0:
1202            efil = os.path.join(zloc, os.path.split(zinfo[zindex].filename)[1])
1203            if os.path.exists(efil) and confirmoverwrite:
1204                result = wx.ID_NO
1205                dlg = wx.MessageDialog(parent,
1206                    'File '+str(efil)+' already exists. OK to overwrite it?',
1207                    'Confirm overwrite',wx.YES_NO | wx.ICON_QUESTION)
1208                try:
1209                    result = dlg.ShowModal()
1210                finally:
1211                    dlg.Destroy()
1212                if result == wx.ID_NO:
1213                    zindex = -1
1214        if zindex >= 0:
1215            # extract the file to the current directory, regardless of it's original path
1216            #z.extract(zinfo[zindex],zloc)
1217            eloc,efil = os.path.split(zinfo[zindex].filename)
1218            outfile = os.path.join(zloc, efil)
1219            fpin = z.open(zinfo[zindex])
1220            fpout = open(outfile, "wb")
1221            shutil.copyfileobj(fpin, fpout)
1222            fpin.close()
1223            fpout.close()
1224            outlist.append(outfile)
1225    z.close()
1226    if multipleselect and len(outlist) >= 1:
1227        return outlist
1228    elif len(outlist) == 1:
1229        return outlist[0]
1230    else:
1231        return None
1232
1233######################################################################
1234# base classes for reading various types of data files
1235#   not used directly, only by subclassing
1236######################################################################
1237def BlockSelector(ChoiceList, ParentFrame=None,title='Select a block',
1238    size=None, header='Block Selector',useCancel=True):
1239    ''' Provide a wx dialog to select a block if the file contains more
1240    than one set of data and one must be selected
1241    '''
1242    if useCancel:
1243        dlg = wx.SingleChoiceDialog(
1244            ParentFrame,title, header,ChoiceList)
1245    else:
1246        dlg = wx.SingleChoiceDialog(
1247            ParentFrame,title, header,ChoiceList,
1248            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.OK|wx.CENTRE)
1249    if size: dlg.SetSize(size)
1250    dlg.CenterOnParent()
1251    if dlg.ShowModal() == wx.ID_OK:
1252        sel = dlg.GetSelection()
1253        return sel
1254    else:
1255        return None
1256    dlg.Destroy()
1257
1258def MultipleBlockSelector(ChoiceList, ParentFrame=None,
1259    title='Select a block',size=None, header='Block Selector'):
1260    '''Provide a wx dialog to select a block of data if the
1261    file contains more than one set of data and one must be
1262    selected.
1263
1264    :returns: a list of the selected blocks
1265    '''
1266    dlg = wx.MultiChoiceDialog(ParentFrame,title, header,ChoiceList+['Select all'],
1267        wx.CHOICEDLG_STYLE)
1268    dlg.CenterOnScreen()
1269    if size: dlg.SetSize(size)
1270    if dlg.ShowModal() == wx.ID_OK:
1271        sel = dlg.GetSelections()
1272    else:
1273        return []
1274    dlg.Destroy()
1275    selected = []
1276    if len(ChoiceList) in sel:
1277        return range(len(ChoiceList))
1278    else:
1279        return sel
1280    return selected
1281
1282def MultipleChoicesSelector(choicelist, headinglist, ParentFrame=None, **kwargs):
1283    '''A modal dialog that offers a series of choices, each with a title and a wx.Choice
1284    widget. Typical input:
1285   
1286       * choicelist=[ ('a','b','c'), ('test1','test2'),('no choice',)]
1287       
1288       * headinglist = [ 'select a, b or c', 'select 1 of 2', 'No option here']
1289       
1290    optional keyword parameters are: head (window title) and title
1291    returns a list of selected indicies for each choice (or None)
1292    '''
1293    result = None
1294    dlg = MultipleChoicesDialog(choicelist,headinglist,
1295        parent=ParentFrame, **kwargs)         
1296    dlg.CenterOnParent()
1297    if dlg.ShowModal() == wx.ID_OK:
1298        result = dlg.chosen
1299    dlg.Destroy()
1300    return result
1301
1302def PhaseSelector(ChoiceList, ParentFrame=None,
1303    title='Select a phase', size=None,header='Phase Selector'):
1304    ''' Provide a wx dialog to select a phase if the file contains more
1305    than one phase
1306    '''
1307    return BlockSelector(ChoiceList,ParentFrame,title,
1308        size,header)
1309
1310######################################################################
1311def striphist(var,insChar=''):
1312    'strip a histogram number from a var name'
1313    sv = var.split(':')
1314    if len(sv) <= 1: return var
1315    if sv[1]:
1316        sv[1] = insChar
1317    return ':'.join(sv)
1318class ExportBaseclass(object):
1319    '''Defines a base class for the exporting of GSAS-II results.
1320
1321    This class is subclassed in the various exports/G2export_*.py files. Those files
1322    are imported in :meth:`GSASIIdataGUI.GSASII._init_Exports` which defines the
1323    appropriate menu items for each one and the .Exporter method is called
1324    directly from the menu item.
1325
1326    Routines may also define a .Writer method, which is used to write a single
1327    file without invoking any GUI objects.
1328    '''
1329    # TODO: review exporters producing exceptions where .Writer can't be used where G2frame is None (see CIF)
1330    # TODO: review conflicting uses of .Writer with mode (SeqRef) & elsewhere
1331    # TODO: move this class to G2fil
1332    def __init__(self,G2frame,formatName,extension,longFormatName=None,):
1333        self.G2frame = G2frame
1334        self.formatName = formatName # short string naming file type
1335        self.extension = extension
1336        if longFormatName: # longer string naming file type
1337            self.longFormatName = longFormatName
1338        else:
1339            self.longFormatName = formatName
1340        self.OverallParms = {}
1341        self.Phases = {}
1342        self.Histograms = {}
1343        self.powderDict = {}
1344        self.xtalDict = {}
1345        self.parmDict = {}
1346        self.sigDict = {}
1347        # updated in InitExport:
1348        self.currentExportType = None # type of export that has been requested
1349        # updated in ExportSelect (when used):
1350        self.phasenam = None # a list of selected phases
1351        self.histnam = None # a list of selected histograms
1352        self.filename = None # name of file to be written (single export) or template (multiple files)
1353        self.dirname = '' # name of directory where file(s) will be written
1354        self.fullpath = '' # name of file being written -- full path
1355       
1356        # items that should be defined in a subclass of this class
1357        self.exporttype = []  # defines the type(s) of exports that the class can handle.
1358        # The following types are defined: 'project', "phase", "powder", "single"
1359        self.multiple = False # set as True if the class can export multiple phases or histograms
1360        # self.multiple is ignored for "project" exports
1361
1362    def InitExport(self,event):
1363        '''Determines the type of menu that called the Exporter and
1364        misc initialization.
1365        '''
1366        self.filename = None # name of file to be written (single export)
1367        self.dirname = '' # name of file to be written (multiple export)
1368        if event:
1369            self.currentExportType = self.G2frame.ExportLookup.get(event.Id)
1370
1371    def MakePWDRfilename(self,hist):
1372        '''Make a filename root (no extension) from a PWDR histogram name
1373
1374        :param str hist: the histogram name in data tree (starts with "PWDR ")
1375        '''
1376        file0 = ''
1377        file1 = hist[5:]
1378        # replace repeated blanks
1379        while file1 != file0:
1380            file0 = file1
1381            file1 = file0.replace('  ',' ').strip()
1382        file0 = file1.replace('Azm= ','A')
1383        # if angle has unneeded decimal places on aziumuth, remove them
1384        if file0[-3:] == '.00': file0 = file0[:-3]
1385        file0 = file0.replace('.','_')
1386        file0 = file0.replace(' ','_')
1387        return file0
1388
1389    def ExportSelect(self,AskFile='ask'):
1390        '''Selects histograms or phases when needed. Sets a default file name when
1391        requested into self.filename; always sets a default directory in self.dirname.
1392
1393        :param bool AskFile: Determines how this routine processes getting a
1394          location to store the current export(s).
1395         
1396          * if AskFile is 'ask' (default option), get the name of the file to be written;
1397            self.filename and self.dirname are always set. In the case where
1398            multiple files must be generated, the export routine should do this
1399            based on self.filename as a template.
1400          * if AskFile is 'dir', get the name of the directory to be used;
1401            self.filename is not used, but self.dirname is always set. The export routine
1402            will always generate the file name.
1403          * if AskFile is 'single', get only the name of the directory to be used when
1404            multiple items will be written (as multiple files) are used
1405            *or* a complete file name is requested when a single file
1406            name is selected. self.dirname is always set and self.filename used
1407            only when a single file is selected. 
1408          * if AskFile is 'default', creates a name of the file to be used from
1409            the name of the project (.gpx) file. If the project has not been saved,
1410            then the name of file is requested.
1411            self.filename and self.dirname are always set. In the case where
1412            multiple file names must be generated, the export routine should do this
1413            based on self.filename.
1414          * if AskFile is 'default-dir', sets self.dirname from the project (.gpx)
1415            file. If the project has not been saved, then a directory is requested.
1416            self.filename is not used.
1417
1418        :returns: True in case of an error
1419        '''
1420       
1421        numselected = 1
1422        if self.currentExportType == 'phase':
1423            if len(self.Phases) == 0:
1424                self.G2frame.ErrorDialog(
1425                    'Empty project',
1426                    'Project does not contain any phases.')
1427                return True
1428            elif len(self.Phases) == 1:
1429                self.phasenam = list(self.Phases.keys())
1430            elif self.multiple: 
1431                choices = sorted(self.Phases.keys())
1432                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1433                if phasenum is None: return True
1434                self.phasenam = [choices[i] for i in phasenum]
1435                if not self.phasenam: return True
1436                numselected = len(self.phasenam)
1437            else:
1438                choices = sorted(self.Phases.keys())
1439                phasenum = G2G.ItemSelector(choices,self.G2frame)
1440                if phasenum is None: return True
1441                self.phasenam = [choices[phasenum]]
1442                numselected = len(self.phasenam)
1443        elif self.currentExportType == 'single':
1444            if len(self.xtalDict) == 0:
1445                self.G2frame.ErrorDialog(
1446                    'Empty project',
1447                    'Project does not contain any single crystal data.')
1448                return True
1449            elif len(self.xtalDict) == 1:
1450                self.histnam = self.xtalDict.values()
1451            elif self.multiple:
1452                choices = sorted(self.xtalDict.values())
1453                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1454                if not hnum: return True
1455                self.histnam = [choices[i] for i in hnum]
1456                numselected = len(self.histnam)
1457            else:
1458                choices = sorted(self.xtalDict.values())
1459                hnum = G2G.ItemSelector(choices,self.G2frame)
1460                if hnum is None: return True
1461                self.histnam = [choices[hnum]]
1462                numselected = len(self.histnam)
1463        elif self.currentExportType == 'powder':
1464            if len(self.powderDict) == 0:
1465                self.G2frame.ErrorDialog(
1466                    'Empty project',
1467                    'Project does not contain any powder data.')
1468                return True
1469            elif len(self.powderDict) == 1:
1470                self.histnam = self.powderDict.values()
1471            elif self.multiple:
1472                choices = sorted(self.powderDict.values())
1473                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=True)
1474                if not hnum: return True
1475                self.histnam = [choices[i] for i in hnum]
1476                numselected = len(self.histnam)
1477            else:
1478                choices = sorted(self.powderDict.values())
1479                hnum = G2G.ItemSelector(choices,self.G2frame)
1480                if hnum is None: return True
1481                self.histnam = [choices[hnum]]
1482                numselected = len(self.histnam)
1483        elif self.currentExportType == 'image':
1484            if len(self.Histograms) == 0:
1485                self.G2frame.ErrorDialog(
1486                    'Empty project',
1487                    'Project does not contain any images.')
1488                return True
1489            elif len(self.Histograms) == 1:
1490                self.histnam = list(self.Histograms.keys())
1491            else:
1492                choices = sorted(self.Histograms.keys())
1493                hnum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1494                if self.multiple:
1495                    if not hnum: return True
1496                    self.histnam = [choices[i] for i in hnum]
1497                else:
1498                    if hnum is None: return True
1499                    self.histnam = [choices[hnum]]
1500                numselected = len(self.histnam)
1501        if self.currentExportType == 'map':
1502            # search for phases with maps
1503            mapPhases = []
1504            choices = []
1505            for phasenam in sorted(self.Phases):
1506                phasedict = self.Phases[phasenam] # pointer to current phase info           
1507                if len(phasedict['General']['Map'].get('rho',[])):
1508                    mapPhases.append(phasenam)
1509                    if phasedict['General']['Map'].get('Flip'):
1510                        choices.append('Charge flip map: '+str(phasenam))
1511                    elif phasedict['General']['Map'].get('MapType'):
1512                        choices.append(
1513                            str(phasedict['General']['Map'].get('MapType'))
1514                            + ' map: ' + str(phasenam))
1515                    else:
1516                        choices.append('unknown map: '+str(phasenam))
1517            # select a map if needed
1518            if len(mapPhases) == 0:
1519                self.G2frame.ErrorDialog(
1520                    'Empty project',
1521                    'Project does not contain any maps.')
1522                return True
1523            elif len(mapPhases) == 1:
1524                self.phasenam = mapPhases
1525            else: 
1526                phasenum = G2G.ItemSelector(choices,self.G2frame,multiple=self.multiple)
1527                if self.multiple:
1528                    if not phasenum: return True
1529                    self.phasenam = [mapPhases[i] for i in phasenum]
1530                else:
1531                    if phasenum is None: return True
1532                    self.phasenam = [mapPhases[phasenum]]
1533            numselected = len(self.phasenam)
1534
1535        # items selected, now set self.dirname and usually self.filename
1536        if AskFile == 'ask' or (AskFile == 'single' and numselected == 1) or (
1537            AskFile == 'default' and not self.G2frame.GSASprojectfile
1538            ):
1539            filename = self.askSaveFile()
1540            if not filename: return True
1541            self.dirname,self.filename = os.path.split(filename)
1542        elif AskFile == 'dir' or AskFile == 'single' or (
1543            AskFile == 'default-dir' and not self.G2frame.GSASprojectfile
1544            ):
1545            self.dirname = self.askSaveDirectory()
1546            if not self.dirname: return True
1547        elif AskFile == 'default-dir' or AskFile == 'default':
1548            self.dirname,self.filename = os.path.split(
1549                os.path.splitext(self.G2frame.GSASprojectfile)[0] + self.extension
1550                )
1551        else:
1552            raise Exception('This should not happen!')
1553
1554    def loadParmDict(self):
1555        '''Load the GSAS-II refinable parameters from the tree into a dict (self.parmDict). Update
1556        refined values to those from the last cycle and set the uncertainties for the
1557        refined parameters in another dict (self.sigDict).
1558
1559        Expands the parm & sig dicts to include values derived from constraints.
1560
1561        This could be made faster for sequential fits by reducing the histogram list to only
1562        the active histogram being exported.
1563        '''
1564        self.parmDict = {}
1565        self.sigDict = {}
1566        rigidbodyDict = {}
1567        covDict = {}
1568        consDict = {}
1569        Histograms,Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1570        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1571        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1572        while item:
1573            name = self.G2frame.GPXtree.GetItemText(item)
1574            if name == 'Rigid bodies':
1575                 rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(item)
1576            elif name == 'Covariance':
1577                 covDict = self.G2frame.GPXtree.GetItemPyData(item)
1578            elif name == 'Constraints':
1579                 consDict = self.G2frame.GPXtree.GetItemPyData(item)
1580            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1581        rbVary,rbDict =  G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1582        self.parmDict.update(rbDict)
1583        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1584        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave =  G2stIO.GetPhaseData(
1585            Phases,RestraintDict=None,rbIds=rbIds,Print=False)
1586        self.parmDict.update(phaseDict)
1587        hapVary,hapDict,controlDict =  G2stIO.GetHistogramPhaseData(
1588            Phases,Histograms,Print=False,resetRefList=False)
1589        self.parmDict.update(hapDict)
1590        histVary,histDict,controlDict =  G2stIO.GetHistogramData(Histograms,Print=False)
1591        self.parmDict.update(histDict)
1592        self.parmDict.update(zip(
1593            covDict.get('varyList',[]),
1594            covDict.get('variables',[])))
1595        self.sigDict = dict(zip(
1596            covDict.get('varyList',[]),
1597            covDict.get('sig',[])))
1598        # expand to include constraints: first compile a list of constraints
1599        constList = []
1600        for item in consDict:
1601            if item.startswith('_'): continue
1602            constList += consDict[item]
1603        # now process the constraints
1604        G2mv.InitVars()
1605        constDict,fixedList,ignored = G2stIO.ProcessConstraints(constList)
1606        varyList = covDict.get('varyListStart')
1607        if varyList is None and len(constDict) == 0:
1608            # no constraints can use varyList
1609            varyList = covDict.get('varyList')
1610        elif varyList is None:
1611            # old GPX file from before pre-constraint varyList is saved
1612            print (' *** Old refinement: Please use Calculate/Refine to redo  ***')
1613            raise Exception(' *** Export aborted ***')
1614        else:
1615            varyList = list(varyList)
1616        # add symmetry-generated constraints
1617        rigidbodyDict = self.G2frame.GPXtree.GetItemPyData(   
1618            G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Rigid bodies'))
1619        rbIds = rigidbodyDict.get('RBIds',{'Vector':[],'Residue':[]})
1620        rbVary,rbDict = G2stIO.GetRigidBodyModels(rigidbodyDict,Print=False)
1621        Natoms,atomIndx,phaseVary,phaseDict,pawleyLookup,FFtables,BLtables,MFtables,maxSSwave = G2stIO.GetPhaseData(
1622            Phases,RestraintDict=None,rbIds=rbIds,Print=False) # generates atom symmetry constraints
1623        msg = G2mv.EvaluateMultipliers(constDict,phaseDict)
1624        if msg:
1625            print('Unable to interpret multiplier(s): '+msg)
1626            raise Exception(' *** CIF creation aborted ***')
1627        try:
1628            G2mv.GenerateConstraints(varyList,constDict,fixedList,self.parmDict)
1629            #print(G2mv.VarRemapShow(varyList))
1630        except:
1631            # this really should not happen
1632            print (' *** ERROR - constraints are internally inconsistent ***')
1633            errmsg, warnmsg = G2mv.CheckConstraints(varyList,constDict,fixedList)
1634            print ('Errors'+errmsg)
1635            if warnmsg: print ('Warnings'+warnmsg)
1636            raise Exception(' *** CIF creation aborted ***')
1637        # add the constrained values to the parameter dictionary
1638        G2mv.Dict2Map(self.parmDict,varyList)
1639        # and add their uncertainties into the esd dictionary (sigDict)
1640        if covDict.get('covMatrix') is not None:
1641            self.sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],covDict['varyList'],self.parmDict))
1642
1643    def loadTree(self):
1644        '''Load the contents of the data tree into a set of dicts
1645        (self.OverallParms, self.Phases and self.Histogram as well as self.powderDict
1646        & self.xtalDict)
1647       
1648        * The childrenless data tree items are overall parameters/controls for the
1649          entire project and are placed in self.OverallParms
1650        * Phase items are placed in self.Phases
1651        * Data items are placed in self.Histogram. The key for these data items
1652          begin with a keyword, such as PWDR, IMG, HKLF,... that identifies the data type.
1653        '''
1654        self.OverallParms = {}
1655        self.powderDict = {}
1656        self.xtalDict = {}
1657        self.Phases = {}
1658        self.Histograms = {}
1659        self.SeqRefdata = None
1660        self.SeqRefhist = None
1661        if self.G2frame.GPXtree.IsEmpty(): return # nothing to do
1662        histType = None       
1663        if self.currentExportType == 'phase':
1664            # if exporting phases load them here
1665            sub = G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,'Phases')
1666            if not sub:
1667                print ('no phases found')
1668                return True
1669            item, cookie = self.G2frame.GPXtree.GetFirstChild(sub)
1670            while item:
1671                phaseName = self.G2frame.GPXtree.GetItemText(item)
1672                self.Phases[phaseName] =  self.G2frame.GPXtree.GetItemPyData(item)
1673                item, cookie = self.G2frame.GPXtree.GetNextChild(sub, cookie)
1674            return
1675        elif self.currentExportType == 'single':
1676            histType = 'HKLF'
1677        elif self.currentExportType == 'powder':
1678            histType = 'PWDR'
1679        elif self.currentExportType == 'image':
1680            histType = 'IMG'
1681
1682        if histType: # Loading just one kind of tree entry
1683            item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1684            while item:
1685                name = self.G2frame.GPXtree.GetItemText(item)
1686                if name.startswith(histType):
1687                    if self.Histograms.get(name): # there is already an item with this name
1688                        print('Histogram name '+str(name)+' is repeated. Renaming')
1689                        if name[-1] == '9':
1690                            name = name[:-1] + '10'
1691                        elif name[-1] in '012345678':
1692                            name = name[:-1] + str(int(name[-1])+1)
1693                        else:                           
1694                            name += '-1'
1695                    self.Histograms[name] = {}
1696                    # the main info goes into Data, but the 0th
1697                    # element contains refinement results, carry
1698                    # that over too now.
1699                    self.Histograms[name]['Data'] = self.G2frame.GPXtree.GetItemPyData(item)[1]
1700                    self.Histograms[name][0] = self.G2frame.GPXtree.GetItemPyData(item)[0]
1701                    item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1702                    while item2: 
1703                        child = self.G2frame.GPXtree.GetItemText(item2)
1704                        self.Histograms[name][child] = self.G2frame.GPXtree.GetItemPyData(item2)
1705                        item2, cookie2 = self.G2frame.GPXtree.GetNextChild(item, cookie2)
1706                item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1707            # index powder and single crystal histograms by number
1708            for hist in self.Histograms:
1709                if hist.startswith("PWDR"): 
1710                    d = self.powderDict
1711                elif hist.startswith("HKLF"): 
1712                    d = self.xtalDict
1713                else:
1714                    return                   
1715                i = self.Histograms[hist].get('hId')
1716                if i is None and not d.keys():
1717                    i = 0
1718                elif i is None or i in d.keys():
1719                    i = max(d.keys())+1
1720                d[i] = hist
1721            return
1722        # else standard load: using all interlinked phases and histograms
1723        self.Histograms,self.Phases = self.G2frame.GetUsedHistogramsAndPhasesfromTree()
1724        item, cookie = self.G2frame.GPXtree.GetFirstChild(self.G2frame.root)
1725        while item:
1726            name = self.G2frame.GPXtree.GetItemText(item)
1727            item2, cookie2 = self.G2frame.GPXtree.GetFirstChild(item)
1728            if not item2: 
1729                self.OverallParms[name] = self.G2frame.GPXtree.GetItemPyData(item)
1730            item, cookie = self.G2frame.GPXtree.GetNextChild(self.G2frame.root, cookie)
1731        # index powder and single crystal histograms
1732        for hist in self.Histograms:
1733            i = self.Histograms[hist]['hId']
1734            if hist.startswith("PWDR"): 
1735                self.powderDict[i] = hist
1736            elif hist.startswith("HKLF"): 
1737                self.xtalDict[i] = hist
1738
1739    def dumpTree(self,mode='type'):
1740        '''Print out information on the data tree dicts loaded in loadTree.
1741        Used for testing only.
1742        '''
1743        if self.SeqRefdata and self.SeqRefhist:
1744            print('Note that dumpTree does not show sequential results')
1745        print ('\nOverall')
1746        if mode == 'type':
1747            def Show(arg): return type(arg)
1748        else:
1749            def Show(arg): return arg
1750        for key in self.OverallParms:
1751            print ('  '+key+Show(self.OverallParms[key]))
1752        print ('Phases')
1753        for key1 in self.Phases:
1754            print ('    '+key1+Show(self.Phases[key1]))
1755        print ('Histogram')
1756        for key1 in self.Histograms:
1757            print ('    '+key1+Show(self.Histograms[key1]))
1758            for key2 in self.Histograms[key1]:
1759                print ('      '+key2+Show(self.Histograms[key1][key2]))
1760
1761    def defaultSaveFile(self):
1762        return os.path.abspath(
1763            os.path.splitext(self.G2frame.GSASprojectfile
1764                             )[0]+self.extension)
1765       
1766    def askSaveFile(self):
1767        '''Ask the user to supply a file name
1768
1769        :returns: a file name (str) or None if Cancel is pressed
1770
1771        '''
1772        pth = G2G.GetExportPath(self.G2frame)
1773        if self.G2frame.GSASprojectfile:
1774            defnam = os.path.splitext(
1775                os.path.split(self.G2frame.GSASprojectfile)[1]
1776                )[0]+self.extension
1777        else:
1778            defnam = 'default' + self.extension
1779        return G2G.askSaveFile(self.G2frame,defnam,self.extension,self.longFormatName)
1780
1781    def askSaveDirectory(self):
1782        '''Ask the user to supply a directory name. Path name is used as the
1783        starting point for the next export path search.
1784
1785        :returns: a directory name (str) or None if Cancel is pressed
1786
1787        TODO: Can this be replaced with G2G.askSaveDirectory?
1788        '''
1789        pth = G2G.GetExportPath(self.G2frame)
1790        dlg = wx.DirDialog(
1791            self.G2frame, 'Input directory where file(s) will be written', pth,
1792            wx.DD_DEFAULT_STYLE)
1793        dlg.CenterOnParent()
1794        try:
1795            if dlg.ShowModal() == wx.ID_OK:
1796                filename = dlg.GetPath()
1797                self.G2frame.LastExportDir = filename
1798            else:
1799                filename = None
1800        finally:
1801            dlg.Destroy()
1802        return filename
1803
1804    # Tools for file writing.
1805    def OpenFile(self,fil=None,mode='w'):
1806        '''Open the output file
1807
1808        :param str fil: The name of the file to open. If None (default)
1809          the name defaults to self.dirname + self.filename.
1810          If an extension is supplied, it is not overridded,
1811          but if not, the default extension is used.
1812        :returns: the file object opened by the routine which is also
1813          saved as self.fp
1814        '''
1815        if mode == 'd': # debug mode
1816            self.fullpath = '(stdout)'
1817            self.fp = sys.stdout
1818            return
1819        if not fil:
1820            if not os.path.splitext(self.filename)[1]:
1821                self.filename += self.extension
1822            fil = os.path.join(self.dirname,self.filename)
1823        self.fullpath = os.path.abspath(fil)
1824        self.fp = open(self.fullpath,mode)
1825        return self.fp
1826
1827    def Write(self,line):
1828        '''write a line of output, attaching a line-end character
1829
1830        :param str line: the text to be written.
1831        '''
1832        self.fp.write(line+'\n')
1833       
1834    def CloseFile(self,fp=None):
1835        '''Close a file opened in OpenFile
1836
1837        :param file fp: the file object to be closed. If None (default)
1838          file object self.fp is closed.
1839        '''
1840        if self.fp == sys.stdout: return # debug mode
1841        if fp is None:
1842            fp = self.fp
1843            self.fp = None
1844        if fp is not None: fp.close()
1845       
1846    def SetSeqRef(self,data,hist):
1847        '''Set the exporter to retrieve results from a sequential refinement
1848        rather than the main tree
1849        '''
1850        self.SeqRefdata = data
1851        self.SeqRefhist = hist
1852        data_name = data[hist]
1853        for i,val in zip(data_name['varyList'],data_name['sig']):
1854            self.sigDict[i] = val
1855            self.sigDict[striphist(i)] = val
1856        for i in data_name['parmDict']:
1857            self.parmDict[striphist(i)] = data_name['parmDict'][i]
1858            self.parmDict[i] = data_name['parmDict'][i]
1859            # zero out the dA[xyz] terms, they would only bring confusion
1860            key = i.split(':')
1861            if len(key) < 3: continue
1862            if key[2].startswith('dA'):
1863                self.parmDict[i] = 0.0
1864        for i,(val,sig) in data_name.get('depParmDict',{}).items():
1865            self.parmDict[i] = val
1866            self.sigDict[i] = sig
1867        #GSASIIpath.IPyBreak()
1868
1869    def SetFromArray(self,hist,histname):
1870        '''Load a histogram into the exporter in preparation for use of the .Writer
1871        rather than the main tree. This is used in GSASIIscriptable when wx
1872        is not present.
1873        '''
1874        self.Histograms[histname] =  {}
1875        self.Histograms[histname]['Data'] = hist['data'][1]
1876        self.Histograms[histname]['Instrument Parameters'] = hist['Instrument Parameters']
1877        self.Histograms[histname]['Sample Parameters'] = hist['Sample Parameters']
1878
1879    # Tools to pull information out of the data arrays
1880    def GetCell(self,phasenam):
1881        """Gets the unit cell parameters and their s.u.'s for a selected phase
1882
1883        :param str phasenam: the name for the selected phase
1884        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1885          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1886          cell values and `cellSig` has their uncertainties.
1887        """
1888        if self.SeqRefdata and self.SeqRefhist:
1889            return self.GetSeqCell(phasenam,self.SeqRefdata[self.SeqRefhist])
1890        phasedict = self.Phases[phasenam] # pointer to current phase info
1891        try:
1892            pfx = str(phasedict['pId'])+'::'
1893            A,sigA = G2stIO.cellFill(pfx,phasedict['General']['SGData'],self.parmDict,self.sigDict)
1894            cellSig = G2stIO.getCellEsd(pfx,phasedict['General']['SGData'],A,
1895                self.OverallParms['Covariance'])  # returns 7 vals, includes sigVol
1896            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1897            return cellList,cellSig
1898        except KeyError:
1899            cell = phasedict['General']['Cell'][1:]
1900            return cell,7*[0]
1901           
1902    def GetSeqCell(self,phasenam,data_name):
1903        """Gets the unit cell parameters and their s.u.'s for a selected phase
1904        and histogram in a sequential fit
1905
1906        :param str phasenam: the name for the selected phase
1907        :param dict data_name: the sequential refinement parameters for the selected histogram
1908        :returns: `cellList,cellSig` where each is a 7 element list corresponding
1909          to a, b, c, alpha, beta, gamma, volume where `cellList` has the
1910          cell values and `cellSig` has their uncertainties.
1911        """
1912        phasedict = self.Phases[phasenam]
1913        SGdata = phasedict['General']['SGData']
1914        pId = phasedict['pId']
1915        RecpCellTerms = G2lat.cell2A(phasedict['General']['Cell'][1:7])
1916        ESDlookup = {}
1917        Dlookup = {}
1918        varied = [striphist(i) for i in data_name['varyList']]
1919        for item,val in data_name['newCellDict'].items():
1920            if item in varied:
1921                ESDlookup[val[0]] = item
1922                Dlookup[item] = val[0]
1923        A = RecpCellTerms[:]
1924        for i in range(6):
1925            var = str(pId)+'::A'+str(i)
1926            if var in ESDlookup:
1927                A[i] = data_name['newCellDict'][ESDlookup[var]][1] # override with refined value
1928        cellDict = dict(zip([str(pId)+'::A'+str(i) for i in range(6)],A))
1929        zeroDict = {i:0.0 for i in cellDict}
1930        A,zeros = G2stIO.cellFill(str(pId)+'::',SGdata,cellDict,zeroDict)
1931        covData = {
1932            'varyList': [Dlookup.get(striphist(v),v) for v in data_name['varyList']],
1933            'covMatrix': data_name['covMatrix']
1934            }
1935        return list(G2lat.A2cell(A)) + [G2lat.calc_V(A)], G2stIO.getCellEsd(str(pId)+'::',SGdata,A,covData)
1936               
1937    def GetAtoms(self,phasenam):
1938        """Gets the atoms associated with a phase. Can be used with standard
1939        or macromolecular phases
1940
1941        :param str phasenam: the name for the selected phase
1942        :returns: a list of items for eac atom where each item is a list containing:
1943          label, typ, mult, xyz, and td, where
1944
1945          * label and typ are the atom label and the scattering factor type (str)
1946          * mult is the site multiplicity (int)
1947          * xyz is contains a list with four pairs of numbers:
1948            x, y, z and fractional occupancy and
1949            their standard uncertainty (or a negative value)
1950          * td is contains a list with either one or six pairs of numbers:
1951            if one number it is U\ :sub:`iso` and with six numbers it is
1952            U\ :sub:`11`, U\ :sub:`22`, U\ :sub:`33`, U\ :sub:`12`, U\ :sub:`13` & U\ :sub:`23`
1953            paired with their standard uncertainty (or a negative value)
1954        """
1955        phasedict = self.Phases[phasenam] # pointer to current phase info           
1956        cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1957        cfrac = cx+3
1958        fpfx = str(phasedict['pId'])+'::Afrac:'       
1959        atomslist = []
1960        for i,at in enumerate(phasedict['Atoms']):
1961            if phasedict['General']['Type'] == 'macromolecular':
1962                label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
1963            else:
1964                label = at[ct-1]
1965            fval = self.parmDict.get(fpfx+str(i),at[cfrac])
1966            fsig = self.sigDict.get(fpfx+str(i),-0.009)
1967            mult = at[cs+1]
1968            typ = at[ct]
1969            xyz = []
1970            for j,v in enumerate(('x','y','z')):
1971                val = at[cx+j]
1972                pfx = str(phasedict['pId']) + '::A' + v + ':' + str(i)
1973                val = self.parmDict.get(pfx, val)
1974                dpfx = str(phasedict['pId'])+'::dA'+v+':'+str(i)
1975                sig = self.sigDict.get(dpfx,-0.000009)
1976                xyz.append((val,sig))
1977            xyz.append((fval,fsig))
1978            td = []
1979            if at[cia] == 'I':
1980                pfx = str(phasedict['pId'])+'::AUiso:'+str(i)
1981                val = self.parmDict.get(pfx,at[cia+1])
1982                sig = self.sigDict.get(pfx,-0.0009)
1983                td.append((val,sig))
1984            else:
1985                for i,var in enumerate(('AU11','AU22','AU33','AU12','AU13','AU23')):
1986                    pfx = str(phasedict['pId'])+'::'+var+':'+str(i)
1987                    val = self.parmDict.get(pfx,at[cia+2+i])
1988                    sig = self.sigDict.get(pfx,-0.0009)
1989                    td.append((val,sig))
1990            atomslist.append((label,typ,mult,xyz,td))
1991        return atomslist
1992######################################################################
1993def ExportPowderList(G2frame):
1994    '''Returns a list of extensions supported by :func:`GSASIIIO:ExportPowder`
1995    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
1996   
1997    :param wx.Frame G2frame: the GSAS-II main data tree window
1998    '''
1999    extList = []
2000    for obj in G2frame.exporterlist:
2001        if 'powder' in obj.exporttype:
2002            try:
2003                obj.Writer
2004                extList.append(obj.extension)
2005            except AttributeError:
2006                pass
2007    return extList
2008
2009def ExportPowder(G2frame,TreeName,fileroot,extension):
2010    '''Writes a single powder histogram using the Export routines.
2011    This is used in :meth:`GSASIIimgGUI.AutoIntFrame` only.
2012
2013    :param wx.Frame G2frame: the GSAS-II main data tree window
2014    :param str TreeName: the name of the histogram (PWDR ...) in the data tree
2015    :param str fileroot: name for file to be written, extension ignored
2016    :param str extension: extension for file to be written (start with '.'). Must
2017      match a powder export routine that has a Writer object.
2018    '''
2019    filename = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
2020    for obj in G2frame.exporterlist:
2021        if obj.extension == extension and 'powder' in obj.exporttype:
2022            obj.currentExportType = 'powder'
2023            obj.InitExport(None)
2024            obj.loadTree() # load all histograms in tree into dicts
2025            if TreeName not in obj.Histograms:
2026                raise Exception('Histogram not found: '+str(TreeName))
2027            try:
2028                obj.Writer
2029            except AttributeError:
2030                continue
2031            try:
2032                obj.Writer(TreeName,filename)
2033                print('wrote file '+filename)
2034                return
2035            except Exception:
2036                print('Export Routine for '+extension+' failed.')
2037    else:
2038        print('No Export routine supports extension '+extension)
2039
2040def ExportSequential(G2frame,data,obj,exporttype):
2041    '''
2042    Used to export from every phase/dataset in a sequential refinement using
2043    a .Writer method for either projects or phases. Prompts to select histograms
2044    and for phase exports, which phase(s).
2045
2046    :param wx.Frame G2frame: the GSAS-II main data tree window
2047    :param dict data: the sequential refinement data object
2048    :param str exporttype: indicates the type of export ('project' or 'phase')
2049    '''
2050    if len(data['histNames']) == 0:
2051        G2G.G2MessageBox(G2frame,'There are no sequential histograms','Warning')
2052    obj.InitExport(None)
2053    obj.loadTree()
2054    obj.loadParmDict()
2055    if len(data['histNames']) == 1:
2056        histlist = data['histNames']
2057    else:
2058        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select histograms to export from list',
2059                                 'Select histograms',data['histNames'])
2060        if dlg.ShowModal() == wx.ID_OK:
2061            histlist = [data['histNames'][l] for l in dlg.GetSelections()]
2062            dlg.Destroy()
2063        else:
2064            dlg.Destroy()
2065            return
2066    if exporttype == 'Phase':
2067        phaselist = list(obj.Phases.keys())
2068        if len(obj.Phases) == 0:
2069            G2G.G2MessageBox(G2frame,'There are no phases in sequential ref.','Warning')
2070            return
2071        elif len(obj.Phases) > 1:
2072            dlg = G2G.G2MultiChoiceDialog(G2frame,'Select phases to export from list',
2073                                    'Select phases', phaselist)
2074            if dlg.ShowModal() == wx.ID_OK:
2075                phaselist = [phaselist[l] for l in dlg.GetSelections()]
2076                dlg.Destroy()
2077            else:
2078                dlg.Destroy()
2079                return
2080        filename = obj.askSaveFile()
2081        if not filename: return True
2082        obj.dirname,obj.filename = os.path.split(filename)
2083        print('Writing output to file '+str(obj.filename)+"...")
2084        mode = 'w'
2085        for p in phaselist:
2086            for h in histlist:
2087                obj.SetSeqRef(data,h)
2088                #GSASIIpath.IPyBreak()
2089                obj.Writer(h,phasenam=p,mode=mode)
2090                mode = 'a'
2091        print('...done')
2092    elif exporttype == 'Project':  # note that the CIF exporter is not yet ready for this
2093        filename = obj.askSaveFile()
2094        if not filename: return True
2095        obj.dirname,obj.filename = os.path.split(filename)
2096        print('Writing output to file '+str(obj.filename)+"...")
2097        mode = 'w'
2098        for h in histlist:
2099            obj.SetSeqRef(data,h)
2100            obj.Writer(h,mode=mode)
2101            print('\t'+str(h)+' written')
2102            mode = 'a'
2103        print('...done')
2104    elif exporttype == 'Powder':
2105        filename = obj.askSaveFile()
2106        if not filename: return True
2107        obj.dirname,obj.filename = os.path.split(filename)
2108        print('Writing output to file '+str(obj.filename)+"...")
2109        mode = 'w'
2110        for h in histlist:
2111            obj.SetSeqRef(data,h)
2112            obj.Writer(h,mode=mode)
2113            print('\t'+str(h)+' written')
2114            mode = 'a'
2115        print('...done')
2116
2117def ReadDIFFaX(DIFFaXfile):
2118    print ('read '+DIFFaXfile)
2119    Layer = {'Laue':'-1','Cell':[False,1.,1.,1.,90.,90.,90,1.],'Width':[[10.,10.],[False,False]],
2120        'Layers':[],'Stacking':[],'Transitions':[],'Toler':0.01,'AtInfo':{}}
2121    df = open(DIFFaXfile,'r')
2122    lines = df.readlines()
2123    df.close()
2124    struct = False
2125    Struct = []
2126    stack = False
2127    Stack = []
2128    trans = False
2129    Trans = []
2130    for diff in lines:
2131        diff = diff[:-1].lower()
2132        if '!'  in diff:
2133            continue
2134        while '}' in diff: #strip comments
2135            iB = diff.index('{')
2136            iF = diff.index('}')+1
2137            if iB:
2138                diff = diff[:iB]
2139            else:
2140                diff = diff[iF:]
2141        if not diff:
2142            continue
2143        if diff.strip() == 'instrumental':
2144            continue
2145        if diff.strip() == 'structural':
2146            struct = True
2147            continue
2148        elif diff.strip() == 'stacking':
2149            struct = False
2150            stack = True
2151            continue
2152        elif diff.strip() == 'transitions':
2153            stack = False
2154            trans = True
2155            continue
2156        diff = diff.strip()
2157        if struct:
2158            if diff:
2159                Struct.append(diff)
2160        elif stack:
2161            if diff:
2162                Stack.append(diff)
2163        elif trans:
2164            if diff:
2165                Trans.append(diff)
2166   
2167#STRUCTURE records
2168    laueRec = Struct[1].split()
2169    Layer['Laue'] = laueRec[0]
2170    if Layer['Laue'] == 'unknown' and len(laueRec) > 1:
2171        Layer['Toler'] = float(laueRec[1])    #tolerance for 'unknown'?
2172    if Layer['Laue'] == '2/m(1)': Layer['Laue'] = '2/m(c)'
2173    if Layer['Laue'] == '2/m(2)': Layer['Laue'] = '2/m(ab)'
2174    cell = Struct[0].split()
2175    Layer['Cell'] = [False,float(cell[0]),float(cell[1]),float(cell[2]),90.,90.,float(cell[3]),1.0]
2176    nLayers = int(Struct[2])
2177    N = 3
2178    if 'layer' not in Struct[3]:
2179        N = 4
2180        if Struct[3] != 'infinite':
2181            width = Struct[3].split()
2182            Layer['Width'][0] = [float(width[0]),float(width[1])]
2183    for nL in range(nLayers):
2184        if '=' in Struct[N]:
2185            name = Struct[N].split('=')
2186            sameas = int(name[1])-1
2187            Layer['Layers'].append({'Name':name[0],'SameAs':Layer['Layers'][sameas]['Name'],'Symm':'None','Atoms':[]})
2188            N += 1
2189            continue
2190        Symm = 'None'
2191        if 'centro' in Struct[N+1]: Symm = '-1'
2192        Layer['Layers'].append({'Name':Struct[N],'SameAs':'','Symm':Symm,'Atoms':[]})
2193        N += 2
2194        while 'layer' not in Struct[N]:
2195            atom = Struct[N][4:].split()
2196            atomType = G2el.FixValence(Struct[N][:4].replace(' ','').strip().capitalize())
2197            if atomType not in Layer['AtInfo']:
2198                Layer['AtInfo'][atomType] = G2el.GetAtomInfo(atomType)
2199            atomName = '%s(%s)'%(atomType,atom[0])
2200            newVals = []
2201            for val in atom[1:6]:
2202                if '/' in val:
2203                    newVals.append(eval(val+'.'))
2204                else:
2205                    newVals.append(float(val))               
2206            atomRec = [atomName,atomType,newVals[0],newVals[1],newVals[2],newVals[4],newVals[3]/78.9568]
2207            Layer['Layers'][-1]['Atoms'].append(atomRec)
2208            N += 1
2209            if N > len(Struct)-1:
2210                break
2211#TRANSITIONS records
2212    transArray = []
2213    N = 0
2214    for i in range(nLayers):
2215        transArray.append([])
2216        for j in range(nLayers):
2217            vals = Trans[N].split()
2218            newVals = []
2219            for val in vals[:4]:
2220                if '/' in val:
2221                    newVals.append(eval(val+'.'))
2222                else:
2223                    newVals.append(float(val))
2224            transArray[-1].append(newVals+['',False])
2225            N += 1
2226    Layer['Transitions'] = transArray
2227#STACKING records
2228    Layer['Stacking'] = [Stack[0],'']
2229    if Stack[0] == 'recursive':
2230        Layer['Stacking'][1] = Stack[1]
2231    elif Stack[0] == 'explicit':
2232        if Stack[1] == 'random':
2233            Layer['Stacking'][1] = Stack[1]
2234        else:
2235            Layer['Stacking'][1] = 'list'
2236            Layer['Stacking'].append('')
2237            for stack in Stack[2:]:
2238                Layer['Stacking'][2] += ' '+stack
2239    return Layer
2240
2241def readColMetadata(imagefile):
2242    '''Reads image metadata from a column-oriented metadata table
2243    (1-ID style .par file). Called by :func:`GetColumnMetadata`
2244   
2245    The .par file has any number of columns separated by spaces.
2246    The directory for the file must be specified in
2247    Config variable ``Column_Metadata_directory``.
2248    As an index to the .par file a second "label file" must be specified with the
2249    same file root name as the .par file but the extension must be .XXX_lbls (where
2250    .XXX is the extension of the image) or if that is not present extension
2251    .lbls.
2252
2253    :param str imagefile: the full name of the image file (with extension, directory optional)
2254
2255    :returns: a dict with parameter values. Named parameters will have the type based on
2256       the specified Python function, named columns will be character strings
2257   
2258    The contents of the label file will look like this::
2259   
2260        # define keywords
2261        filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34
2262        distance: float | 75
2263        wavelength:lambda keV: 12.398425/float(keV)|9
2264        pixelSize:lambda x: [74.8, 74.8]|0
2265        ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4
2266        Temperature: float|53
2267        FreePrm2: int | 34 | Free Parm2 Label
2268        # define other variables
2269        0:day
2270        1:month
2271        2:date
2272        3:time
2273        4:year
2274        7:I_ring
2275
2276    This file contains three types of lines in any order.
2277     * Named parameters are evaluated with user-supplied Python code (see
2278       subsequent information). Specific named parameters are used
2279       to determine values that are used for image interpretation (see table,
2280       below). Any others are copied to the Comments subsection of the Image
2281       tree item.
2282     * Column labels are defined with a column number (integer) followed by
2283       a colon (:) and a label to be assigned to that column. All labeled
2284       columns are copied to the Image's Comments subsection.
2285     * Comments are any line that does not contain a colon.
2286
2287    Note that columns are numbered starting at zero.
2288
2289    Any named parameter may be defined provided it is not a valid integer,
2290    but the named parameters in the table have special meanings, as descibed.
2291    The parameter name is followed by a colon. After the colon, specify
2292    Python code that defines or specifies a function that will be called to
2293    generate a value for that parameter.
2294
2295    Note that several keywords, if defined in the Comments, will be found and
2296    placed in the appropriate section of the powder histogram(s)'s Sample
2297    Parameters after an integration: ``Temperature``,``Pressure``,``Time``,
2298    ``FreePrm1``,``FreePrm2``,``FreePrm3``,``Omega``,``Chi``, and ``Phi``.
2299
2300    After the Python code, supply a vertical bar (|) and then a list of one
2301    more more columns that will be supplied as arguments to that function.
2302
2303    Note that the labels for the three FreePrm items can be changed by
2304    including that label as a third item with an additional vertical bar. Labels
2305    will be ignored for any other named parameters.
2306   
2307    The examples above are discussed here:
2308
2309    ``filename:lambda x,y: "{}_{:0>6}".format(x,y)|33,34``
2310        Here the function to be used is defined with a lambda statement::
2311       
2312          lambda x,y: "{}_{:0>6}".format(x,y)
2313
2314        This function will use the format function to create a file name from the
2315        contents of columns 33 and 34. The first parameter (x, col. 33) is inserted directly into
2316        the file name, followed by a underscore (_), followed by the second parameter (y, col. 34),
2317        which will be left-padded with zeros to six characters (format directive ``:0>6``).
2318
2319        When there will be more than one image generated per line in the .par file, an alternate way to
2320        generate list of file names takes into account the number of images generated::
2321
2322          lambda x,y,z: ["{}_{:0>6}".format(x,int(y)+i) for i in range(int(z))]
2323
2324        Here a third parameter is used to specify the number of images generated, where
2325        the image number is incremented for each image.
2326         
2327    ``distance: float | 75``
2328        Here the contents of column 75 will be converted to a floating point number
2329        by calling float on it. Note that the spaces here are ignored.
2330       
2331    ``wavelength:lambda keV: 12.398425/float(keV)|9``
2332        Here we define an algebraic expression to convert an energy in keV to a
2333        wavelength and pass the contents of column 9 as that input energy
2334       
2335    ``pixelSize:lambda x: [74.8, 74.8]|0``
2336        In this case the pixel size is a constant (a list of two numbers). The first
2337        column is passed as an argument as at least one argument is required, but that
2338        value is not used in the expression.
2339
2340    ``ISOlikeDate: lambda dow,m,d,t,y:"{}-{}-{}T{} ({})".format(y,m,d,t,dow)|0,1,2,3,4``
2341        This example defines a parameter that takes items in the first five columns
2342        and formats them in a different way. This parameter is not one of the pre-defined
2343        parameter names below. Some external code could be used to change the month string
2344        (argument ``m``) to a integer from 1 to 12.
2345       
2346    ``FreePrm2: int | 34 | Free Parm2 Label``
2347        In this example, the contents of column 34 will be converted to an integer and
2348        placed as the second free-named parameter in the Sample Parameters after an
2349        integration. The label for this parameter will be changed to "Free Parm2 Label".
2350           
2351    **Pre-defined parameter names**
2352   
2353    =============  =========  ========  =====================================================
2354     keyword       required    type      Description
2355    =============  =========  ========  =====================================================
2356       filename    yes         str or   generates the file name prefix for the matching image
2357                               list     file (MyImage001 for file /tmp/MyImage001.tif) or
2358                                        a list of file names.
2359     polarization  no         float     generates the polarization expected based on the
2360                                        monochromator angle, defaults to 0.99.
2361       center      no         list of   generates the approximate beam center on the detector
2362                              2 floats  in mm, such as [204.8, 204.8].
2363       distance    yes        float     generates the distance from the sample to the detector
2364                                        in mm
2365       pixelSize   no         list of   generates the size of the pixels in microns such as
2366                              2 floats  [200.0, 200.0].
2367       wavelength  yes        float     generates the wavelength in Angstroms
2368    =============  =========  ========  =====================================================
2369   
2370    '''
2371    dir,fil = os.path.split(os.path.abspath(imagefile))
2372    imageName,ext = os.path.splitext(fil)
2373    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2374    parfiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2375    if len(parfiles) == 0:
2376        print('Sorry, No Column metadata (.par) file found in '+
2377              GSASIIpath.GetConfigValue('Column_Metadata_directory'))
2378        return {}
2379    for parFil in parfiles: # loop over all .par files (hope just 1) in image dir until image is found
2380        parRoot = os.path.splitext(parFil)[0]
2381        for e in (ext+'_lbls','.lbls'):
2382            if os.path.exists(parRoot+e):
2383                lblFil = parRoot+e
2384                break
2385        else:
2386            print('Warning: No labels definitions found for '+parFil)
2387            continue
2388        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFil)
2389        if errors:
2390            print('Errors in labels file '+lblFil)
2391            for i in errors: print('  '+i)
2392            continue
2393        else:
2394            print('Read '+lblFil)
2395        # scan through each line in this .par file, looking for the matching image rootname
2396        fp = open(parFil,'Ur')
2397        for iline,line in enumerate(fp):
2398            items = line.strip().split(' ')
2399            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2400            if type(nameList) is str:
2401                if nameList != imageName: continue
2402                name = nameList
2403            else:
2404                for name in nameList:
2405                    if name == imageName: break # got a match
2406                else:
2407                    continue
2408            # parse the line and finish
2409            metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp)
2410            metadata['par file'] = parFil
2411            metadata['lbls file'] = lblFil
2412            print("Metadata read from {} line {}".format(parFil,iline+1))
2413            fp.close()
2414            return metadata
2415        else:
2416            print("Image {} not found in {}".format(imageName,parFil))
2417            fp.close()
2418            continue
2419        fp.close()
2420    else:
2421        print("Warning: No .par metadata for image {}".format(imageName))
2422        return {}
2423
2424def readColMetadataLabels(lblFil):
2425    '''Read the .*lbls file and setup for metadata assignments
2426    '''
2427    lbldict = {}
2428    keyExp = {}
2429    keyCols = {}
2430    labels = {}
2431    errors = []
2432    fp = open(lblFil,'Ur')         # read column labels
2433    for iline,line in enumerate(fp): # read label definitions
2434        line = line.strip()
2435        if not line or line[0] == '#': continue # comments
2436        items = line.split(':')
2437        if len(items) < 2: continue # lines with no colon are also comments
2438        # does this line a definition for a named parameter?
2439        key = items[0]
2440        try: 
2441            int(key)
2442        except ValueError: # try as named parameter since not a valid number
2443            items = line.split(':',1)[1].split('|')
2444            try:
2445                f = eval(items[0]) # compile the expression
2446                if not callable(f):
2447                    errors += ['Expression "{}" for key {} is not a function (line {})'.
2448                           format(items[0],key,iline)]
2449                    continue
2450                keyExp[key] = f
2451            except Exception as msg:
2452                errors += ['Expression "{}" for key {} is not valid (line {})'.
2453                           format(items[0],key,iline)]
2454                errors += [str(msg)]
2455                continue
2456            keyCols[key] = [int(i) for i in items[1].strip().split(',')]
2457            if key.lower().startswith('freeprm') and len(items) > 2:
2458                labels[key] = items[2]
2459            continue
2460        if len(items) == 2: # simple column definition
2461            lbldict[int(items[0])] = items[1]
2462    fp.close()
2463    if 'filename' not in keyExp:
2464        errors += ["File {} is invalid. No valid filename expression.".format(lblFil)]
2465    return labels,lbldict,keyCols,keyExp,errors
2466
2467def evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,ShowError=False):
2468    '''Evaluate the metadata for a line in the .par file
2469    '''
2470    metadata = {lbldict[j]:items[j] for j in lbldict}
2471    named = {}
2472    for key in keyExp:
2473        try:
2474            res = keyExp[key](*[items[j] for j in keyCols[key]])
2475        except:
2476            if ShowError:
2477                res = "*** error ***"
2478            else:
2479                continue
2480        named[key] = res
2481    metadata.update(named)
2482    for lbl in labels: # add labels for FreePrm's
2483        metadata['label_'+lbl[4:].lower()] = labels[lbl]
2484    return metadata
2485
2486def GetColumnMetadata(reader):
2487    '''Add metadata to an image from a column-type metadata file
2488    using :func:`readColMetadata`
2489   
2490    :param reader: a reader object from reading an image
2491   
2492    '''
2493    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'): return
2494    parParms = readColMetadata(reader.readfilename)
2495    if not parParms: return # check for read failure
2496    specialKeys = ('filename',"polarization", "center", "distance", "pixelSize", "wavelength",)
2497    reader.Comments = ['Metadata from {} assigned by {}'.format(parParms['par file'],parParms['lbls file'])]
2498    for key in parParms:
2499        if key in specialKeys+('par file','lbls file'): continue
2500        reader.Comments += ["{} = {}".format(key,parParms[key])]
2501    if "polarization" in parParms:
2502        reader.Data['PolaVal'][0] = parParms["polarization"]
2503    else:
2504        reader.Data['PolaVal'][0] = 0.99
2505    if "center" in parParms:
2506        reader.Data['center'] = parParms["center"]
2507    if "pixelSize" in parParms:
2508        reader.Data['pixelSize'] = parParms["pixelSize"]
2509    if "wavelength" in parParms:
2510        reader.Data['wavelength'] = parParms['wavelength']
2511    else:
2512        print('Error: wavelength not defined in {}'.format(parParms['lbls file']))
2513    if "distance" in parParms:
2514        reader.Data['distance'] = parParms['distance']
2515        reader.Data['setdist'] = parParms['distance']
2516    else:
2517        print('Error: distance not defined in {}'.format(parParms['lbls file']))
2518
2519def testColumnMetadata(G2frame):
2520    '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results
2521    when using a .par and .lbls pair.
2522   
2523     * Select a .par file, if more than one in selected dir.
2524     * Select the .*lbls file, if more than one matching .par file.
2525     * Parse the .lbls file, showing errors if encountered; loop until errors are fixed.
2526     * Search for an image or a line in the .par file and show the results when interpreted
2527     
2528    See :func:`readColMetadata` for more details.
2529    '''
2530    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'):
2531        G2G.G2MessageBox(G2frame,'The configuration option for I-ID Metadata is not set.\n'+
2532                         'Please use the File/Preferences menu to set Column_Metadata_directory',
2533                         'Warning')
2534        return
2535    parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
2536    if not parFiles: 
2537        G2G.G2MessageBox(G2frame,'No .par files found in directory {}. '
2538                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2539                         '\nThis is set by config variable Column_Metadata_directory '+
2540                         '(Set in File/Preferences menu).',
2541                         'Warning')
2542        return
2543    parList = []
2544    for parFile in parFiles:
2545        lblList = []
2546        parRoot = os.path.splitext(parFile)[0]
2547        for f in glob.glob(parRoot+'.*lbls'):
2548            if os.path.exists(f): lblList.append(f)
2549        if not len(lblList):
2550            continue
2551        parList.append(parFile)
2552    if len(parList) == 0:
2553        G2G.G2MessageBox(G2frame,'No .lbls or .EXT_lbls file found for .par file(s) in directory {}. '
2554                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
2555                         '\nThis is set by config variable Column_Metadata_directory '+
2556                         '(Set in File/Preferences menu).',
2557                         'Warning')
2558        return
2559    elif len(parList) == 1:
2560        parFile = parList[0]
2561    else:
2562        dlg = G2G.G2SingleChoiceDialog(G2frame,
2563                'More than 1 .par file found. (Better if only 1!). Choose the one to test in '+
2564                GSASIIpath.GetConfigValue('Column_Metadata_directory'),
2565                'Choose .par file', [os.path.split(i)[1] for i in parList])
2566        if dlg.ShowModal() == wx.ID_OK:
2567            parFile = parList[dlg.GetSelection()]
2568            dlg.Destroy()
2569        else:
2570            dlg.Destroy()
2571            return
2572    # got .par file; now work on .*lbls file
2573    lblList = []
2574    parRoot = os.path.splitext(parFile)[0]
2575    for f in glob.glob(parRoot+'.*lbls'):
2576        if os.path.exists(f): lblList.append(f)
2577    if not len(lblList):
2578        raise Exception('How did this happen! No .*lbls files for '+parFile)
2579    elif len(lblList) == 1:
2580        lblFile = lblList[0]
2581    else:
2582        dlg = G2G.G2SingleChoiceDialog(G2frame,
2583                'Select label file for .par file '+parFile,
2584                'Choose label file', [os.path.split(i)[1] for i in lblList])
2585        if dlg.ShowModal() == wx.ID_OK:
2586            lblFile = lblList[dlg.GetSelection()]
2587            dlg.Destroy()
2588        else:
2589            dlg.Destroy()
2590            return
2591    # parse the labels file
2592    errors = True
2593    while errors:
2594        labels,lbldict,keyCols,keyExp,errors = readColMetadataLabels(lblFile)
2595        if errors:
2596            t = "Error reading file "+lblFile
2597            for l in errors:
2598                t += '\n'
2599                t += l
2600            t += "\n\nPlease edit the file and press OK (or Cancel to quit)"
2601            dlg = wx.MessageDialog(G2frame,message=t,
2602                caption="Read Error",style=wx.ICON_ERROR| wx.OK|wx.STAY_ON_TOP|wx.CANCEL)
2603            if dlg.ShowModal() != wx.ID_OK: return           
2604    # request a line number, read that line
2605    dlg = G2G.SingleStringDialog(G2frame,'Read what',
2606                                 'Enter a line number or an image file name (-1=last line)',
2607                                 '-1',size=(400,-1))
2608    if dlg.Show():
2609        fileorline = dlg.GetValue()
2610        dlg.Destroy()
2611    else:
2612        dlg.Destroy()
2613        return
2614    # and report the generated key pairs in metadata dict
2615    linenum = None
2616    try:
2617        linenum = int(fileorline)
2618    except:
2619        imageName = os.path.splitext(os.path.split(fileorline)[1])[0]
2620
2621    fp = open(parFile,'Ur')
2622    for iline,line in enumerate(fp):
2623        if linenum is not None:
2624            if iline == linenum:
2625                items = line.strip().split(' ')
2626                n = "Line {}".format(iline)
2627                break
2628            else:
2629                continue
2630        else:
2631            items = line.strip().split(' ')
2632            nameList = keyExp['filename'](*[items[j] for j in keyCols['filename']])
2633            if type(nameList) is str:
2634                if nameList != imageName: continue
2635                name = nameList
2636                break
2637            else:
2638                for name in nameList:
2639                    print (name,name == imageName)
2640                    if name == imageName:
2641                        n = "Image {} found in line {}".format(imageName,iline)
2642                        break # got a match
2643                else:
2644                    continue
2645                break
2646    else:
2647        if linenum is not None:
2648            n = "Line {}".format(iline)
2649        else:
2650            n = "Image {} not found. Reporting line {}".format(imageName,iline)
2651        items = line.strip().split(' ')
2652    fp.close()
2653    metadata = evalColMetadataDicts(items,labels,lbldict,keyCols,keyExp,True)
2654    title = "Results: ("+n+")"
2655    t = ['Files: '+parFile,lblFile,' ']
2656    n = ["Named parameters:"]
2657    l = ['',"Labeled columns:"]
2658    for key in sorted(metadata):
2659        if key == "filename" or key.startswith('label_prm'): continue
2660        if key in keyCols:
2661            n += [{} = {}".format(key,metadata[key])]
2662        elif key in lbldict.values():
2663            l += [{} = {}".format(key,metadata[key])]
2664        else:
2665            t += ["** Unexpected:  {}".format(key,metadata[key])]
2666    if type(metadata['filename']) is str:
2667        l += ["","Filename: "+ metadata['filename']]
2668    else:
2669        l += ["","Filename(s): "]
2670        for i,j in enumerate(metadata['filename']):
2671            if i: l[-1] += ', '
2672            l[-1] += j
2673    t += n + l + ['','Unused columns:']
2674    usedCols = list(lbldict.keys())
2675    for i in keyCols.values(): usedCols += i
2676    for i in range(len(items)):
2677        if i in usedCols: continue
2678        t += [{}: {}".format(i,items[i])]
2679    dlg = G2G.G2SingleChoiceDialog(None,title,'Column metadata parse results',t,
2680                                   monoFont=True,filterBox=False,size=(400,600),
2681                                   style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.CENTRE|wx.OK)
2682    dlg.ShowModal()
2683
2684if __name__ == '__main__':
2685    import GSASIIdataGUI
2686    application = GSASIIdataGUI.GSASIImain(0)
2687    G2frame = application.main
2688    #app = wx.PySimpleApp()
2689    #G2frame = wx.Frame(None) # create a frame
2690    #frm.Show(True)
2691    #filename = '/tmp/notzip.zip'
2692    #filename = '/tmp/all.zip'
2693    #filename = '/tmp/11bmb_7652.zip'
2694   
2695    #selection=None, confirmoverwrite=True, parent=None
2696    #print ExtractFileFromZip(filename, selection='11bmb_7652.fxye',parent=frm)
2697    #print ExtractFileFromZip(filename,multipleselect=True)
2698    #                         #confirmread=False, confirmoverwrite=False)
2699
2700    # choicelist=[ ('a','b','c'),
2701    #              ('test1','test2'),('no choice',)]
2702    # titles = [ 'a, b or c', 'tests', 'No option here']
2703    # dlg = MultipleChoicesDialog(
2704    #     choicelist,titles,
2705    #     parent=frm)
2706    # if dlg.ShowModal() == wx.ID_OK:
2707    #     print 'Got OK'
2708    imagefile = '/tmp/NDC5_00237_3.ge3'
2709    Comments, Data, Npix, Image = GetImageData(G2frame,imagefile,imageOnly=False,ImageTag=None)
2710
2711    print("\n\nResults loaded to Comments, Data, Npix and Image\n\n")
2712
2713    GSASIIpath.IPyBreak_base()
Note: See TracBrowser for help on using the repository browser.