source: trunk/GSASIIIO.py @ 3869

Last change on this file since 3869 was 3869, checked in by toby, 3 years ago

more clear working of Cancel on relocate image

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