source: trunk/GSASIIIO.py @ 3814

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

refactor to move some IO-only routines; add initial image support to scriptable

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