source: trunk/GSASIIIO.py @ 3447

Last change on this file since 3447 was 3447, checked in by toby, 5 years ago

fix seq ref constraint numbering bug; fix use if HAP vars in PSvars & equations; complete RepaintHistogramInfo? w/Py3 + misc Py3 fixes; improve initialization after project read

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