source: trunk/GSASIIIO.py @ 3217

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

request file/dir name before saving a PDF

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