source: trunk/GSASIIIO.py @ 3319

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

set a default name when the gpx file name is blank

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