source: trunk/GSASIIIO.py @ 3187

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

misc docs cleanups; add 1-ID metadata reader & new config variable

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