source: trunk/GSASIIIO.py @ 3571

Last change on this file since 3571 was 3506, checked in by svnjenkins, 7 years ago

fix scriptable problem due to change in Restraints; update Tutorials; misc. fixes for Sphinx docs

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