source: trunk/GSASIIimgGUI.py

Last change on this file was 5619, checked in by toby, 3 months ago

autoint GUI fixes; more scripting docs

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 204.7 KB
Line 
1# -*- coding: utf-8 -*-
2#GSASII - image data display routines
3########### SVN repository information ###################
4# $Date: 2023-06-22 04:36:16 +0000 (Thu, 22 Jun 2023) $
5# $Author: vondreele $
6# $Revision: 5619 $
7# $URL: trunk/GSASIIimgGUI.py $
8# $Id: GSASIIimgGUI.py 5619 2023-06-22 04:36:16Z vondreele $
9########### SVN repository information ###################
10'''Image GUI routines follow.
11'''
12from __future__ import division, print_function
13import os
14import copy
15import glob
16import time
17import re
18import math
19import sys
20import wx
21import wx.lib.mixins.listctrl  as  listmix
22import wx.grid as wg
23import matplotlib as mpl
24import numpy as np
25import numpy.ma as ma
26import GSASIIpath
27GSASIIpath.SetVersionNumber("$Revision: 5619 $")
28import GSASIIimage as G2img
29import GSASIImath as G2mth
30import GSASIIElem as G2elem
31import GSASIIpwdGUI as G2pdG
32import GSASIIplot as G2plt
33import GSASIIIO as G2IO
34import GSASIIfiles as G2fil
35import GSASIIdataGUI as G2gd
36import GSASIIctrlGUI as G2G
37import GSASIIobj as G2obj
38import ImageCalibrants as calFile
39
40# documentation build kludge. This prevents an error with sphinx 1.8.5 (fixed by 2.3) where all mock objects are of type _MockObject
41if (type(wx.ListCtrl).__name__ ==
42        type(listmix.ListCtrlAutoWidthMixin).__name__ ==
43        type(listmix.TextEditMixin).__name__):
44    print('Using Sphinx 1.8.5 _MockObject kludge fix in GSASIIimgGUI')
45    class Junk1(object): pass
46    listmix.ListCtrlAutoWidthMixin = Junk1
47    class Junk2(object): pass
48    listmix.TextEditMixin = Junk2
49
50try:   
51    #VERY_LIGHT_GREY = wx.Colour(235,235,235)
52    VERY_LIGHT_GREY = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)
53    WACV = wx.ALIGN_CENTER_VERTICAL
54except:
55    pass
56
57# trig functions in degrees
58sind = lambda x: math.sin(x*math.pi/180.)
59tand = lambda x: math.tan(x*math.pi/180.)
60cosd = lambda x: math.cos(x*math.pi/180.)
61asind = lambda x: 180.*math.asin(x)/math.pi
62tth2q = lambda t,w:4.0*math.pi*sind(t/2.0)/w
63tof2q = lambda t,C:2.0*math.pi*C/t
64atand = lambda x: 180.*math.atan(x)/math.pi
65atan2d = lambda y,x: 180.*math.atan2(y,x)/math.pi
66
67################################################################################
68##### Image Data
69################################################################################
70
71def GetImageZ(G2frame,data,newRange=False):
72    '''Gets image & applies dark, background & flat background corrections.
73
74    :param wx.Frame G2frame: main GSAS-II frame
75    :param dict data: Image Controls dictionary
76
77    :returns: array sumImg: corrected image for background/dark/flat back
78    '''
79    # Note that routine GSASIIscriptable._getCorrImage is based on this
80    # so changes made here should be repeated there.
81   
82    Npix,imagefile,imagetag = G2IO.GetCheckImageFile(G2frame,G2frame.Image)
83    if imagefile is None: return []
84    formatName = data.get('formatName','')
85    sumImg = np.array(G2IO.GetImageData(G2frame,imagefile,True,ImageTag=imagetag,FormatName=formatName),dtype='int32')
86    if sumImg is None:
87        return []
88    darkImg = False
89    if 'dark image' in data:
90        darkImg,darkScale = data['dark image']
91        if darkImg:
92            Did = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, darkImg)
93            if Did:
94                Ddata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Did,'Image Controls'))
95                dformatName = Ddata.get('formatName','')
96                Npix,darkfile,imagetag = G2IO.GetCheckImageFile(G2frame,Did)
97                darkImage = G2IO.GetImageData(G2frame,darkfile,True,ImageTag=imagetag,FormatName=dformatName)
98                if darkImg is not None:               
99                    sumImg += np.array(darkImage*darkScale,dtype='int32')
100            else:
101                print('Warning: resetting dark image (not found: {})'.format(
102                    darkImg))
103                data['dark image'][0] = darkImg = ''
104    if 'background image' in data:
105        backImg,backScale = data['background image']           
106        if backImg:     #ignores any transmission effect in the background image
107            Bid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, backImg)
108            if Bid:
109                Npix,backfile,imagetag = G2IO.GetCheckImageFile(G2frame,Bid)
110                Bdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Bid,'Image Controls'))
111                bformatName = Bdata.get('formatName','')
112                backImage = G2IO.GetImageData(G2frame,backfile,True,ImageTag=imagetag,FormatName=bformatName)
113                if darkImg and backImage is not None:
114                    backImage += np.array(darkImage*darkScale/backScale,dtype='int32')
115                if backImage is not None:
116                    sumImg += np.array(backImage*backScale,dtype='int32')
117    if 'Gain map' in data:
118        gainMap = data['Gain map']
119        if gainMap:
120            GMid = G2gd.GetGPXtreeItemId(G2frame, G2frame.root, gainMap)
121            if GMid:
122                Npix,gainfile,imagetag = G2IO.GetCheckImageFile(G2frame,GMid)               
123                Gdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,GMid,'Image Controls'))
124                gformat = Gdata['formatName']
125                GMimage = G2IO.GetImageData(G2frame,gainfile,True,ImageTag=imagetag,FormatName=gformat)
126                sumImg = sumImg*GMimage/1000
127    sumImg -= int(data.get('Flat Bkg',0))
128    Imax = np.max(sumImg)
129    if 'range' not in data or newRange:
130        data['range'] = [(0,Imax),[0,Imax]]
131    return np.asarray(sumImg,dtype='int32')
132
133def UpdateImageData(G2frame,data):
134   
135    def OnPixVal(invalid,value,tc):
136        G2plt.PlotExposedImage(G2frame,newPlot=True,event=tc.event)
137       
138    def OnPolaCalib(event):
139        if data['IOtth'][1] < 34.:
140            G2G.G2MessageBox(G2frame,'Maximum 2-theta not greater than 34 deg',
141                    'Polarization Calibration Error')
142            return
143        IOtth = [32.,data['IOtth'][1]-2.]
144        dlg = G2G.SingleFloatDialog(G2frame,'Polarization test arc mask',
145''' Do not use if pattern has uneven absorption
146 Set 2-theta max in image controls to be fully inside image
147 Enter 2-theta position for arc mask (32-%.1f) '''%IOtth[1],IOtth[1],IOtth,fmt='%.2f')
148        if dlg.ShowModal() == wx.ID_OK:
149            arcTth = dlg.GetValue()
150            G2fil.G2SetPrintLevel('none')
151            G2img.DoPolaCalib(G2frame.ImageZ,data,arcTth)
152            G2fil.G2SetPrintLevel('all')
153            UpdateImageData(G2frame,data)
154        dlg.Destroy()
155       
156    def OnMakeGainMap(event):
157        import scipy.ndimage.filters as sdif
158        sumImg = GetImageZ(G2frame,data)
159        masks = copy.deepcopy(G2frame.GPXtree.GetItemPyData(
160            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')))
161        Data = copy.deepcopy(data)
162        #force defaults for GainMap calc
163        Data['IOtth'] = [0.1,60.0]
164        Data['outAzimuths'] = 1
165        Data['LRazimuth'] = [0.,360.]
166        Data['outChannels'] = 5000
167        Data['binType'] = '2-theta'
168        Data['color'] = 'gray'
169        G2frame.Integrate = G2img.ImageIntegrate(sumImg,Data,masks,blkSize)           
170        Iy,azms,Ix = G2frame.Integrate[:3]
171        GainMap = G2img.MakeGainMap(sumImg,Ix,Iy,Data,blkSize)*1000.
172        Npix,imagefile,imagetag = G2IO.GetCheckImageFile(G2frame,G2frame.Image)
173        pth = os.path.split(os.path.abspath(imagefile))[0]
174        outname = 'GainMap'
175        dlg = wx.FileDialog(G2frame, 'Choose gain map filename', pth,outname, 
176            'G2img files (*.G2img)|*.G2img',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
177        if dlg.ShowModal() == wx.ID_OK:
178            newimagefile = dlg.GetPath()
179            newimagefile = G2IO.FileDlgFixExt(dlg,newimagefile)
180            Data['formatName'] = 'GSAS-II image'
181            Data['range'] = [(500,2000),[800,1200]]
182            GainMap = np.where(GainMap > 2000,2000,GainMap)
183            GainMap = np.where(GainMap < 500,500,GainMap)
184            masks['Thresholds'] = [(500.,2000.),[800.,1200.]]
185            G2IO.PutG2Image(newimagefile,[],data,Npix,GainMap)
186            GMname = 'IMG '+os.path.split(newimagefile)[1]
187            Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,GMname)
188            if not Id:           
189                Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text=GMname)
190                G2frame.GPXtree.SetItemPyData(Id,[Npix,newimagefile])
191                G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Comments'),[])
192                G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Image Controls'),Data)
193                G2frame.GPXtree.SetItemPyData(G2frame.GPXtree.AppendItem(Id,text='Masks'),masks)
194            else:
195                G2frame.GPXtree.SetItemPyData(Id,[Npix,newimagefile])
196                G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Comments'),[])
197                G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'),Data)
198                G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Masks'),masks)
199            G2frame.GPXtree.Expand(Id)
200            G2frame.GPXtree.SelectItem(Id)      #to show the gain map & put it in the list
201
202    G2frame.dataWindow.ClearData()
203    G2frame.ImageZ = GetImageZ(G2frame,data)
204    mainSizer = G2frame.dataWindow.GetSizer()
205    topSizer = wx.BoxSizer(wx.HORIZONTAL)
206    topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Do not change anything here unless you are absolutely sure!'),0)
207    topSizer.Add((-1,-1),1,wx.EXPAND)
208    topSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
209    mainSizer.Add(topSizer,0,wx.EXPAND)
210    mainSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Image size: %d by %d'%(data['size'][0],data['size'][1])),0)
211    pixSize = wx.FlexGridSizer(0,4,5,5)
212    pixLabels = [u' Pixel X-dimension (\xb5m)',u' Pixel Y-dimension (\xb5m)']
213    for i,[pixLabel,pix] in enumerate(zip(pixLabels,data['pixelSize'])):
214        pixSize.Add(wx.StaticText(G2frame.dataWindow,label=pixLabel),0,WACV)
215        pixVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['pixelSize'],i,nDig=(10,3),
216            typeHint=float,OnLeave=OnPixVal)
217        pixSize.Add(pixVal,0,WACV)
218    mainSizer.Add(pixSize,0)
219    distSizer = wx.BoxSizer(wx.HORIZONTAL)
220    distSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Set detector distance: '),0,WACV)
221    if 'setdist' not in data:
222        data['setdist'] = data['distance']
223    distSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'setdist',nDig=(10,4),
224        typeHint=float),0,WACV)
225    distSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Polarization: '),0,WACV)
226    if 'PolaVal' not in data:       #patch
227        data['PolaVal'] = [0.99,False]
228    distSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['PolaVal'],0,nDig=(10,4),
229        xmin=0.,xmax=1.,typeHint=float),0,WACV)
230    polaCalib = wx.Button(G2frame.dataWindow,label='Calibrate?')
231    polaCalib.Bind(wx.EVT_BUTTON,OnPolaCalib)
232    distSizer.Add(polaCalib,0,WACV)
233    mainSizer.Add(distSizer,0)
234#patch
235    if 'samplechangerpos' not in data or data['samplechangerpos'] is None:
236        data['samplechangerpos'] = 0.0
237    if 'det2theta' not in data:
238        data['det2theta'] = 0.0
239    if 'Gain map' not in data:
240        data['Gain map'] = ''
241#end patch
242    tthSizer = wx.BoxSizer(wx.HORIZONTAL)
243    tthSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Detector 2-theta: '),0,WACV)
244    tthSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'det2theta',xmin=-180.,xmax=180.,nDig=(10,2)),0,WACV)
245    tthSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Sample changer position %.2f mm '%data['samplechangerpos']),0,WACV)
246    mainSizer.Add(tthSizer,0)
247    if not data['Gain map']:
248        makeGain = wx.Button(G2frame.dataWindow,label='Make gain map from this image? NB: use only an amorphous pattern for this')
249        makeGain.Bind(wx.EVT_BUTTON,OnMakeGainMap)
250        mainSizer.Add(makeGain)
251    G2frame.dataWindow.SetDataSize()
252
253################################################################################
254##### Image Controls
255################################################################################                   
256blkSize = 128 #128 seems to be optimal; will break in polymask if >1024
257def UpdateImageControls(G2frame,data,masks,useTA=None,useMask=None,IntegrateOnly=False):
258    '''Shows and handles the controls on the "Image Controls"
259    data tree entry
260    '''
261#patch
262    if 'Flat Bkg' not in data:
263        data['Flat Bkg'] = 0.0
264    if 'Gain map' not in data:
265        data['Gain map'] = ''
266    if 'GonioAngles' not in data:
267        data['GonioAngles'] = [0.,0.,0.]
268    if 'DetDepth' not in data:
269        data['DetDepth'] = 0.
270    if 'SampleAbs' not in data:
271        data['SampleShape'] = 'Cylinder'
272        data['SampleAbs'] = [0.0,False]
273    if 'binType' not in data:
274        if 'PWDR' in data['type']:
275            data['binType'] = '2-theta'
276        elif 'SASD' in data['type']:
277            data['binType'] = 'log(q)'
278    if 'varyList' not in data:
279        data['varyList'] = {'dist':True,'det-X':True,'det-Y':True,'tilt':True,'phi':True,'dep':False,'wave':False}
280    if data['DetDepth'] > 0.5:
281        data['DetDepth'] /= data['distance']
282    if 'setdist' not in data:
283        data['setdist'] = data['distance']
284    if 'linescan' not in data:
285        data['linescan'] = [False,0.0]      #includes azimuth to draw line scan
286    if 'det2theta' not in data:
287        data['det2theta'] = 0.0
288    if 'orientation' not in data:
289        data['orientation'] = 'horizontal'
290#end patch
291
292# Menu items
293
294    def OnCalibrate(event):
295        if not data['calibrant']:
296            G2G.G2MessageBox(G2frame,'No calibrant material specified.\n'+
297                             'Please correct this and try again.')
298            return
299        G2frame.GetStatusBar().SetStatusText('Select > 4 points on 1st used ring; LB to pick (shift key to force pick), RB on point to delete else RB to finish',1)
300        G2frame.ifGetRing = True
301               
302    def OnRecalibrate(event):
303        '''Use existing calibration values as starting point for a calibration
304        fit
305        '''
306        G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,data,masks)
307        wx.CallAfter(UpdateImageControls,G2frame,data,masks)
308       
309    def OnRecalibAll(event):
310        '''Use existing calibration values as starting point for a calibration
311        fit for a selected series of images
312        '''
313        Id = None
314        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
315        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image calibration controls','Select images to recalibrate:',Names)
316        try:
317            if dlg.ShowModal() == wx.ID_OK:
318                Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential image calibration results')
319                if Id:
320                    SeqResult = G2frame.GPXtree.GetItemPyData(Id)
321                else:
322                    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential image calibration results')
323                SeqResult = {'SeqPseudoVars':{},'SeqParFitEqList':[]}
324                items = dlg.GetSelections()
325                G2frame.EnablePlot = False
326                for item in items:
327                    name = Names[item]
328                    print ('calibrating'+name)
329                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
330                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
331                    G2frame.ImageZ = GetImageZ(G2frame,Data)
332                    Data['setRings'] = True
333                    Mid = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')
334                    Masks = G2frame.GPXtree.GetItemPyData(Mid)
335                    result = G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,Data,Masks)
336                    if not len(result):
337                        print('calibrant missing from local image calibrants files')
338                        return
339                    vals,varyList,sigList,parmDict,covar = result
340                    sigList = list(sigList)
341                    if 'dist' not in varyList:
342                        vals.append(parmDict['dist'])
343                        varyList.append('dist')
344                        sigList.append(None)
345                    vals.append(Data.get('setdist',Data['distance']))
346                    # add setdist to varylist etc. so that it is displayed in Seq Res table
347                    varyList.append('setdist')
348                    sigList.append(None)
349                    covar = np.lib.pad(covar, (0,1), 'constant')
350#                    vals.append(Data.get('samplechangerpos',Data['samplechangerpos']))
351#                    varyList.append('chgrpos')
352#                    sigList.append(None)
353                   
354                    SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[],
355                        'covMatrix':covar,'title':name,'parmDict':parmDict}
356                SeqResult['histNames'] = Names               
357                G2frame.GPXtree.SetItemPyData(Id,SeqResult)
358        finally:
359            dlg.Destroy()
360        print ('All selected images recalibrated - results in Sequential image calibration results')
361        G2frame.G2plotNB.Delete('Sequential refinement')    #clear away probably invalid plot
362        G2plt.PlotExposedImage(G2frame,event=None)
363        if Id: G2frame.GPXtree.SelectItem(Id)
364       
365    def OnCalcRings(event):
366        '''Use existing calibration values to compute rings & display them
367        '''
368        G2img.CalcRings(G2frame,G2frame.ImageZ,data,masks)
369        G2plt.PlotExposedImage(G2frame,event=None)
370        wx.CallAfter(UpdateImageControls,G2frame,data,masks)
371
372    def OnDistRecalib(event):
373        '''Assemble rings & calibration input for a series of images with
374        differing distances
375        '''
376        obsArr = np.array([]).reshape(0,4)
377        parmDict = {}
378        varList = []
379        HKL = {}
380        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
381        startID = G2frame.GPXtree.GetSelection()
382        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image calibration controls','Select images to recalibrate:',Names)
383        try:
384            if dlg.ShowModal() == wx.ID_OK:
385                wx.BeginBusyCursor()
386                items = dlg.GetSelections()
387                print('Scanning for ring picks...')
388#                G2frame.EnablePlot = False
389                for item in items:
390                    name = Names[item]
391                    print ('getting rings for',name)
392                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
393                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
394                    key = str(int(Data['setdist']))
395                    # create a parameter dict for combined fit
396                    if 'wavelength' not in parmDict:
397                        parmDict['wavelength'] = Data['wavelength']
398                        if Data['varyList']['wave']:
399                            varList += ['wavelength']
400                            if Data['varyList']['dist']:
401                                G2G.G2MessageBox(G2frame,
402                                'You cannot vary individual detector positions and the global wavelength.\n\nChange flags for 1st image.',
403                                'Conflicting vars')
404                                return
405                        parmDict['dep'] = Data['DetDepth']
406                        if Data['varyList']['dep']:
407                            varList += ['dep']
408                        # distance flag determines if individual values are refined
409                        if not Data['varyList']['dist']:
410                            # starts as zero, single variable, always refined
411                            parmDict['deltaDist'] = 0.
412                            varList += ['deltaDist']
413                        parmDict['phi'] = Data['rotation']
414                        if Data['varyList']['phi']:
415                            varList += ['phi']
416                        parmDict['tilt'] = Data['tilt']
417                        if Data['varyList']['tilt']:
418                            varList += ['tilt']
419                    G2frame.ImageZ = GetImageZ(G2frame,Data)
420                    Data['setRings'] = True
421                    Mid = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks')
422                    Masks = G2frame.GPXtree.GetItemPyData(Mid)
423                    result = G2img.ImageRecalibrate(G2frame,G2frame.ImageZ,Data,Masks,getRingsOnly=True)
424                    if not len(result):
425                        print('calibrant missing from local image calibrants files')
426                        return
427                    rings,HKL[key] = result
428                    # add detector set dist into data array, create a single really large array
429                    distarr = np.zeros_like(rings[:,2:3])
430                    if 'setdist' not in Data:
431                        print('Distance (setdist) not in image metadata')
432                        return
433                    distarr += Data['setdist']
434                    obsArr = np.concatenate((
435                        obsArr,
436                        np.concatenate((rings[:,0:2],distarr,rings[:,2:3]),axis=1)),axis=0)
437                    if 'deltaDist' not in parmDict:
438                        # starts as zero, variable refined for each image
439                         parmDict['delta'+key] = 0
440                         varList += ['delta'+key]
441                    for i,z in enumerate(['X','Y']):
442                        v = 'det-'+z
443                        if v+key in parmDict:
444                            print('Error: two images with setdist ~=',key)
445                            return
446                        parmDict[v+key] = Data['center'][i]
447                        if Data['varyList'][v]:
448                            varList += [v+key]
449                #GSASIIpath.IPyBreak()
450                print('\nFitting',obsArr.shape[0],'ring picks and',len(varList),'variables...')
451                result = G2img.FitMultiDist(obsArr,varList,parmDict,covar=True)
452                covar = result[3]
453                covData = {'title':'Multi-distance recalibrate','covMatrix':covar,'varyList':varList,'variables':result[1]}
454                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Covariance')
455                G2frame.GPXtree.SetItemPyData(Id,covData)
456               
457                for item in items:
458                    name = Names[item]
459                    print ('updating',name)
460                    G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
461                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
462                    Data['wavelength'] = parmDict['wavelength']
463                    key = str(int(Data['setdist']))
464                    Data['center'] = [parmDict['det-X'+key],parmDict['det-Y'+key]]
465                    if 'deltaDist' in parmDict:
466                        Data['distance'] = Data['setdist'] - parmDict['deltaDist']
467                    else:
468                        Data['distance'] = Data['setdist'] - parmDict['delta'+key]
469                    Data['rotation'] = np.mod(parmDict['phi'],360.0)
470                    Data['tilt'] = parmDict['tilt']
471                    Data['DetDepth'] = parmDict['dep']
472                    #Data['chisq'] = chisq
473                    N = len(Data['ellipses'])
474                    Data['ellipses'] = []           #clear away individual ellipse fits
475                    for H in HKL[key][:N]:
476                        ellipse = G2img.GetEllipse(H[3],Data)
477                        Data['ellipses'].append(copy.deepcopy(ellipse+('b',)))
478                G2frame.EnablePlot = True
479                G2frame.GPXtree.SelectItem(G2frame.root) # there is probably a better way to force the reload of the current page
480                wx.CallAfter(G2frame.GPXtree.SelectItem,startID)
481                    #GSASIIpath.IPyBreak()
482                   
483               
484                # create a sequential table?
485#                Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential image calibration results')
486#                if Id:
487#                    SeqResult = G2frame.GPXtree.GetItemPyData(Id)
488#                else:
489#                    Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential image calibration results')
490                #SeqResult = {'SeqPseudoVars':{},'SeqParFitEqList':[]}
491#                    vals,varyList,sigList,parmDict,covar = result
492#                    sigList = list(sigList)
493#                    if 'dist' not in varyList:
494#                        vals.append(parmDict['dist'])
495#                        varyList.append('dist')
496#                        sigList.append(None)
497#                    vals.append(Data.get('setdist',Data['distance']))
498#                    # add setdist to varylist etc. so that it is displayed in Seq Res table
499#                    varyList.append('setdist')
500#                    sigList.append(None)
501#                    covar = np.lib.pad(covar, (0,1), 'constant')
502#                    vals.append(Data.get('samplechangerpos',Data['samplechangerpos']))
503#                    varyList.append('chgrpos')
504#                    sigList.append(None)
505                   
506#                    SeqResult[name] = {'variables':vals,'varyList':varyList,'sig':sigList,'Rvals':[],
507#                        'covMatrix':covar,'title':name,'parmDict':parmDict}
508#                SeqResult['histNames'] = Names               
509#                G2frame.GPXtree.SetItemPyData(Id,SeqResult)
510            else:
511                wx.BeginBusyCursor()
512        finally:
513            dlg.Destroy()
514            wx.EndBusyCursor()
515
516#        print ('All selected images recalibrated - results in Sequential image calibration results')
517#        G2frame.G2plotNB.Delete('Sequential refinement')    #clear away probably invalid plot
518#        G2plt.PlotExposedImage(G2frame,event=None)
519#        G2frame.GPXtree.SelectItem(Id)
520
521       
522    def OnClearCalib(event):
523        data['ring'] = []
524        data['rings'] = []
525        data['ellipses'] = []
526        G2plt.PlotExposedImage(G2frame,event=event)
527           
528    def ResetThresholds():
529        Imin = max(0.,np.min(G2frame.ImageZ))
530        Imax = np.max(G2frame.ImageZ)
531        data['range'] = [(0,Imax),[Imin,Imax]]
532        masks['Thresholds'] = [(0,Imax),[Imin,Imax]]
533        G2frame.slideSizer.GetChildren()[1].Window.SetValue(Imax)   #tricky
534        G2frame.slideSizer.GetChildren()[4].Window.SetValue(Imin)   #tricky
535         
536    def OnIntegrate(event,useTA=None,useMask=None):
537        '''Integrate image in response to a menu event or from the AutoIntegrate
538        dialog. In the latter case, event=None.
539        '''
540        CleanupMasks(masks)
541        sumImg = GetImageZ(G2frame,data)
542        if masks.get('SpotMask',{'spotMask':None})['spotMask'] is not None:
543            sumImg = ma.array(sumImg,mask=masks['SpotMask']['spotMask'])
544        G2frame.Integrate = G2img.ImageIntegrate(sumImg,data,masks,blkSize,useTA=useTA,useMask=useMask)           
545        G2frame.PauseIntegration = G2frame.Integrate[-1]
546        del sumImg  #force cleanup
547        Id = G2IO.SaveIntegration(G2frame,G2frame.PickId,data,(event is None))
548        G2frame.PatternId = Id
549        G2frame.GPXtree.SelectItem(Id)
550        G2frame.GPXtree.Expand(Id)
551        for item in G2frame.MakePDF: item.Enable(True)
552       
553    def OnIntegrateAll(event):
554        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
555        dlg = G2G.G2MultiChoiceDialog(G2frame,'Image integration controls','Select images to integrate:',Names)
556        try:
557            if dlg.ShowModal() == wx.ID_OK:
558                items = dlg.GetSelections()
559                G2frame.EnablePlot = False
560                dlgp = wx.ProgressDialog("Elapsed time","2D image integrations",len(items)+1,
561                    style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT,parent=G2frame)
562                try:
563                    pId = 0
564                    oldData = {'tilt':0.,'distance':0.,'rotation':0.,'center':[0.,0.],'DetDepth':0.,'azmthOff':0.,'det2theta':0.}
565                    oldMhash = 0
566                    for icnt,item in enumerate(items):
567                        dlgp.Raise()
568                        GoOn = dlgp.Update(icnt)
569                        if not GoOn[0]:
570                            break
571                        name = Names[item]
572                        G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
573                        CId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')
574                        Data = G2frame.GPXtree.GetItemPyData(CId)
575                        same = True
576                        for item in ['tilt','distance','rotation','DetDepth','azmthOff','det2theta']:
577                            if Data[item] != oldData[item]:
578                                same = False
579                        if (Data['center'][0] != oldData['center'][0] or
580                            Data['center'][1] != oldData['center'][1]):
581                                same = False
582                        if not same:
583                            t0 = time.time()
584                            useTA = G2img.MakeUseTA(Data,blkSize)
585                            print(' Use new image controls; new xy -> th,azm time %.3f'%(time.time()-t0))
586                        Masks = G2frame.GPXtree.GetItemPyData(
587                            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))
588                        Mhash = copy.deepcopy(Masks)
589                        Mhash.pop('Thresholds')
590                        Mhash = hash(str(Mhash))
591                        if  Mhash != oldMhash:
592                            t0 = time.time()
593                            useMask = G2img.MakeUseMask(Data,Masks,blkSize)
594                            print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
595                            oldMhash = Mhash
596                        image = GetImageZ(G2frame,Data)
597                        if not Masks['SpotMask']['spotMask'] is None:
598                            image = ma.array(image,mask=Masks['SpotMask']['spotMask'])
599                        G2frame.Integrate = G2img.ImageIntegrate(image,Data,Masks,blkSize,useTA=useTA,useMask=useMask)
600                        del image   #force cleanup
601                        pId = G2IO.SaveIntegration(G2frame,CId,Data)
602                        oldData = Data
603                finally:   
604                    dlgp.Destroy()
605                    G2frame.EnablePlot = True
606                    if pId:
607                        G2frame.GPXtree.SelectItem(pId)
608                        G2frame.GPXtree.Expand(pId)
609                        G2frame.PatternId = pId
610        finally:
611            dlg.Destroy()
612       
613    def OnCopyControls(event):
614        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
615        if len(Names) == 1:
616            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
617            return
618        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
619        Names.pop(Names.index(Source))
620# select targets & do copy
621        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy image controls','Copy controls from '+Source+' to:',Names)
622        try:
623            if dlg.ShowModal() == wx.ID_OK:
624                items = dlg.GetSelections()
625                G2frame.EnablePlot = False
626                for item in items:      #preserve some values
627                    name = Names[item]
628                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
629                    CId = G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls')
630                    oldData = copy.deepcopy(G2frame.GPXtree.GetItemPyData(CId))
631                    Data = copy.deepcopy(data)
632                    Data['range'][0] = oldData['range'][0]
633                    Data['size'] = oldData['size']
634                    Data['GonioAngles'] = oldData.get('GonioAngles', [0.,0.,0.])
635                    Data['samplechangerpos'] = oldData.get('samplechangerpos',0.0)
636                    Data['det2theta'] = oldData.get('det2theta',0.0)
637                    Data['ring'] = []
638                    Data['rings'] = []
639                    Data['ellipses'] = []
640                    if name == Data['dark image'][0]:
641                        Data['dark image'] = ['',-1.]
642                    if name == Data['background image'][0]:
643                        Data['background image'] = ['',-1.]
644                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Image Controls'),Data)
645        finally:
646            dlg.Destroy()
647            if G2frame.PickId: G2frame.GPXtree.SelectItem(G2frame.PickId)
648           
649    def OnCopySelected(event):
650        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
651        if len(Names) == 1:
652            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
653            return
654        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
655        # Assemble a list of item labels
656        keyList = ['type','color','wavelength','calibrant','distance','center','Oblique',
657                    'tilt','rotation','azmthOff','fullIntegrate','LRazimuth','setdist',
658                    'IOtth','outChannels','outAzimuths','invert_x','invert_y','DetDepth',
659                    'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg','varyList','orientation',
660                    'binType','SampleShape','PolaVal','SampleAbs','dark image','background image']
661        keyList.sort(key=lambda s: s.lower())
662        keyText = [i+' = '+str(data[i]) for i in keyList]
663        # sort both lists together, ordered by keyText
664        selectedKeys = []
665        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select which image controls\nto copy',
666            'Select image controls', keyText)
667        try:
668            if dlg.ShowModal() == wx.ID_OK:
669                selectedKeys = [keyList[i] for i in dlg.GetSelections()]
670        finally:
671            dlg.Destroy()
672        if not selectedKeys: return # nothing to copy
673        copyDict = {}
674        for parm in selectedKeys:
675            copyDict[parm] = data[parm]
676        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy image controls from\n'+Source+' to...',
677            'Copy image controls', Names)
678        try:
679            if dlg.ShowModal() == wx.ID_OK:
680                result = dlg.GetSelections()
681                for i in result: 
682                    item = Names[i]
683                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,item)
684                    Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
685                    Controls.update(copy.deepcopy(copyDict))
686        finally:
687            dlg.Destroy()           
688               
689    def OnSaveControls(event):
690        pth = G2G.GetExportPath(G2frame)
691        dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 
692            'image control files (*.imctrl)|*.imctrl',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
693        try:
694            if dlg.ShowModal() == wx.ID_OK:
695                filename = dlg.GetPath()
696                # make sure extension is .imctrl
697                filename = os.path.splitext(filename)[0]+'.imctrl'
698                G2fil.WriteControls(filename,data)
699        finally:
700            dlg.Destroy()
701       
702    def OnSaveMultiControls(event):
703        '''Save controls from multiple images
704        '''
705        imglist = []
706        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
707        while item:
708            name = G2frame.GPXtree.GetItemText(item)
709            if name.startswith('IMG '): 
710                imglist.append(name)               
711            item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
712        if not imglist:
713            print('No images!')
714            return
715        dlg = G2G.G2MultiChoiceDialog(G2frame, 'Which images to select?',
716            'Select images', imglist, wx.CHOICEDLG_STYLE)
717        try:
718            if dlg.ShowModal() == wx.ID_OK:
719                treeEntries = [imglist[i] for i in dlg.GetSelections()]
720        finally:
721            dlg.Destroy()
722        if not treeEntries:
723            print('No images selected!')
724            return
725        pth = G2G.GetExportPath(G2frame)
726        dlg = wx.DirDialog(
727            G2frame, 'Select directory for output files',pth,wx.DD_DEFAULT_STYLE)
728        dlg.CenterOnParent()
729        outdir = None
730        try:
731            if dlg.ShowModal() == wx.ID_OK:
732                outdir = dlg.GetPath()
733        finally:
734            dlg.Destroy()
735        if not outdir:
736            print('No directory')
737            return
738        for img in treeEntries:
739            item = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
740            data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
741                G2frame,item,'Image Controls'))
742            Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(item)
743            filename = os.path.join(outdir,
744                                    os.path.splitext(os.path.split(imagefile)[1])[0]
745                                    + '.imctrl')
746            print('writing '+filename)
747            G2fil.WriteControls(filename,data)
748           
749    def OnLoadControls(event):
750        pth = G2G.GetImportPath(G2frame)
751        if not pth: pth = '.'
752        dlg = wx.FileDialog(G2frame, 'Choose image controls file', pth, '', 
753            'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN)
754        try:
755            if dlg.ShowModal() == wx.ID_OK:
756                filename = dlg.GetPath()
757                File = open(filename,'r')
758                Slines = File.readlines()
759                File.close()
760                G2fil.LoadControls(Slines,data)
761        finally:
762            dlg.Destroy()
763        G2frame.ImageZ = GetImageZ(G2frame,data)
764        ResetThresholds()
765        G2plt.PlotExposedImage(G2frame,event=event)
766        wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
767       
768    def OnLoadMultiControls(event):         #TODO: how read in multiple image controls & match them by 'twoth' tag?
769        print('This is not implemented yet, sorry')
770        G2G.G2MessageBox(G2frame,'This is not implemented yet, sorry')
771        return
772        pth = G2G.GetImportPath(G2frame)
773        if not pth: pth = '.'
774        controlsDict = {}
775        dlg = wx.FileDialog(G2frame, 'Choose image control files', pth, '', 
776            'image control files (*.imctrl)|*.imctrl',wx.FD_OPEN|wx.FD_MULTIPLE)
777        try:
778            if dlg.ShowModal() == wx.ID_OK:
779                filelist = dlg.GetPaths()
780                if len(filelist) == 0: return
781                for filename in filelist:
782                    File = open(filename,'r')
783                    Slines = File.readlines()
784                    for S in Slines:
785                        if S.find('twoth') == 0:
786                            indx = S.split(':')[1][:-1]     #remove '\n'!
787                    controlsDict[indx] = Slines
788                    File.close()
789        finally:
790            dlg.Destroy()
791        if not len(controlsDict):
792            return
793        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
794        dlg = G2G.G2MultiChoiceDialog(G2frame,'Select images','Select images for updating controls:',
795            Names)
796        try:
797            if dlg.ShowModal() == wx.ID_OK:
798                images = dlg.GetSelections()
799                if not len(images):
800                    return
801                for image in images:
802                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Names[image])
803                    imctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
804                    Slines = controlsDict[imctrls['twoth']]
805                    G2fil.LoadControls(Slines,imctrls)
806        finally:
807            dlg.Destroy()
808       
809    def OnTransferAngles(event):
810        '''Sets the integration range for the selected Images based on the difference in detector distance
811        '''
812        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
813        if len(Names) == 1:
814            G2frame.ErrorDialog('No images to transfer integration angles to','Need more "IMG"s')
815            return
816        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
817        Names.pop(Names.index(Source))
818        # select targets & do copy
819        extraopts = {"label_1":"Xfer scaled calib d-min", "value_1":False,
820                     "label_2":"Xfer scaled 2-theta min", "value_2":False,
821                     "label_3":"Xfer scaled 2-theta max", "value_3":True,
822                     "label_4":"Xfer fixed background  ", "value_4":False,
823                     }
824        dlg = G2G.G2MultiChoiceDialog(G2frame,'Xfer angles','Transfer integration range from '+Source+' to:',
825            Names,extraOpts=extraopts)
826        try:
827            if dlg.ShowModal() == wx.ID_OK:
828                for i in '_1','_2','_3','_4':
829                    if extraopts['value'+i]: break
830                else:
831                    G2G.G2MessageBox(G2frame,'Nothing to do!')
832                    return
833                xferAng = lambda tth,dist1,dist2: atand(dist1 * tand(tth) / dist2)
834                items = dlg.GetSelections()
835                G2frame.EnablePlot = False
836                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,Source)
837                data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
838                ttmin0,ttmax0 = data['IOtth']
839                dist0 = data['distance']
840                wave0 = data['wavelength']
841                dsp0 = data['calibdmin']
842                flatBkg = data['Flat Bkg']
843                print('distance = {:.2f} integration range: [{:.4f}, {:.4f}], calib dmin {:.3f}'
844                            .format(dist0,ttmin0,ttmax0,dsp0))
845                for item in items:
846                    name = Names[item]
847                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
848                    data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
849                    dist1 = data['distance']
850                    if extraopts["value_2"]:
851                        data['IOtth'][0] = xferAng(ttmin0,dist0,dist1)
852                    if extraopts["value_3"]:
853                        data['IOtth'][1] = xferAng(ttmax0,dist0,dist1)
854                    if extraopts["value_1"]:
855                        ang1 = xferAng(2.0*asind(wave0/(2.*dsp0)),dist0,dist1)
856                        data['calibdmin'] = data['wavelength']/(2.*sind(ang1/2.))
857                        print('distance = {:.2f} integration range: [{:.4f}, {:.4f}], calib dmin {:.3f}'
858                            .format(dist1,data['IOtth'][0],data['IOtth'][1],data['calibdmin']))
859                    if extraopts['value_4']:
860                        data['Flat Bkg'] = flatBkg*(dist0/dist1)**2
861                    else:
862                        print('distance = {:.2f} integration range: [{:.4f}, {:.4f}]'
863                            .format(dist1,data['IOtth'][0],data['IOtth'][1]))
864        finally:
865            dlg.Destroy()
866            G2frame.GPXtree.SelectItem(G2frame.PickId)       
867           
868    def OnResetDist(event):
869        dlg = wx.MessageDialog(G2frame,'Are you sure you want to do this?',caption='Reset dist to set dist',style=wx.YES_NO|wx.ICON_EXCLAMATION)
870        if dlg.ShowModal() != wx.ID_YES:
871            dlg.Destroy()
872            return
873        dlg.Destroy()
874        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
875        dlg = G2G.G2MultiChoiceDialog(G2frame,'Reset dist','Reset dist to set dist for:',Names)
876        try:
877            if dlg.ShowModal() == wx.ID_OK:
878                items = dlg.GetSelections()
879                for item in items:
880                    name = Names[item]
881                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
882                    Data = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
883                    Data['distance'] = Data['setdist']
884        finally:
885            dlg.Destroy()
886        wx.CallAfter(UpdateImageControls,G2frame,data,masks)
887           
888# Sizers
889    Indx = {}                                   
890    def ComboSizer():
891
892        def OnDataType(event):
893            data['type'] = typeSel.GetValue()[:4]
894            if 'SASD' in data['type']:
895                data['SampleAbs'][0] = np.exp(-data['SampleAbs'][0]) #switch from muT to trans!
896                if data['binType'] == '2-theta': data['binType'] = 'log(q)'  #switch default bin type
897            elif 'PWDR' in data['type']:
898                data['SampleAbs'][0] = -np.log(data['SampleAbs'][0])  #switch from trans to muT!
899                if data['binType'] == 'log(q)': data['binType'] = '2-theta'  #switch default bin type                 
900            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
901   
902        def OnNewColorBar(event):
903            data['color'] = colSel.GetValue()
904            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
905       
906        def OnAzmthOff(invalid,value,tc):
907            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
908       
909        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
910        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Type of image data: '),0,WACV)
911        typeSel = wx.ComboBox(parent=G2frame.dataWindow,value=typeDict[data['type']],choices=typeList,
912            style=wx.CB_READONLY|wx.CB_DROPDOWN)
913        typeSel.SetValue(data['type'])
914        typeSel.Bind(wx.EVT_COMBOBOX, OnDataType)
915        comboSizer.Add(typeSel,0,WACV)
916        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Color bar '),0,WACV)
917        colSel = wx.ComboBox(parent=G2frame.dataWindow,value=data['color'],choices=colorList,
918            style=wx.CB_READONLY|wx.CB_DROPDOWN)
919        colSel.Bind(wx.EVT_COMBOBOX, OnNewColorBar)
920        comboSizer.Add(colSel,0,WACV)
921        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Azimuth offset '),0,WACV)
922        azmthOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'azmthOff',nDig=(10,2),
923            typeHint=float,OnLeave=OnAzmthOff)
924        comboSizer.Add(azmthOff,0,WACV)
925        return comboSizer
926       
927    def MaxSizer():
928        '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap
929        for the image, as well as callback routines.
930        '''
931        def OnNewVal(invalid,value,tc):
932            '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts
933            the value into the data['range'] nested list).
934            This adjusts the slider positions to match the current values
935            '''
936            scaleSel.SetSelection(len(scaleChoices)-1)
937            r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
938            if r11 != Range[1][1]:
939                Range[1][1] = r11
940                maxVal.SetValue(int(Range[1][1]))
941            r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
942            if r10 != Range[1][0]:
943                Range[1][0] = r10
944                minVal.SetValue(int(Range[1][0]))
945            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
946            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
947            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
948            maxSel.SetValue(sv1)
949            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
950            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
951            minSel.SetValue(sv0)
952            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
953            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
954            if mplOld:
955                Page.canvas.draw()
956            else:
957                Page.canvas.draw_idle()
958           
959        G2frame.prevMaxValue = None   
960        def OnMaxSlider(event):
961            val = maxSel.GetValue()
962            if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat
963            scaleSel.SetSelection(len(scaleChoices)-1)
964            G2frame.prevMaxValue = val
965            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
966            Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
967            maxVal.SetValue(int(0.5+Range[1][1]))
968            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
969            minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
970            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
971            minSel.SetValue(sv0)
972            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
973            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
974            if mplOld:
975                Page.canvas.draw()
976            else:
977                Page.canvas.draw_idle()
978           
979        G2frame.prevMinValue = None   
980        def OnMinSlider(event):
981            val = minSel.GetValue()
982            scaleSel.SetSelection(len(scaleChoices)-1)
983            if G2frame.prevMinValue == val: return # if this val has been processed, no need to repeat
984            G2frame.prevMinValue = val
985            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
986            Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
987            minVal.SetValue(int(Range[1][0]))
988            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
989            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
990            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
991            maxSel.SetValue(sv1)
992            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
993            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
994            if mplOld:
995                Page.canvas.draw()
996            else:
997                Page.canvas.draw_idle()
998           
999        def OnAutoSet(event):
1000            '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values
1001            for the image so that 95% (etc.) of pixels are inside the color map limits.
1002            An equal number of pixels are dropped at the minimum and maximum levels.
1003            '''
1004            try:
1005                val = int(event.GetEventObject().GetStringSelection()[:-1])
1006                margin = (100-val)/2.
1007            except:
1008                margin = 0
1009                event.GetEventObject().SetSelection(0)
1010            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1011            if margin == 0:
1012                Range[1] = list(Range[0])
1013            else:
1014                Range[1][0] = int(np.percentile(Page.ImgObj.get_array().compressed(),margin))
1015                Range[1][1] = int(np.percentile(Page.ImgObj.get_array().compressed(),100-margin))
1016            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1017            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1018            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1019            maxSel.SetValue(sv1)
1020            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1021            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1022            minSel.SetValue(sv0)
1023            minVal.SetValue(int(Range[1][0]))
1024            maxVal.SetValue(int(Range[1][1]))
1025            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1026            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1027            if mplOld:
1028                Page.canvas.draw()
1029            else:
1030                Page.canvas.draw_idle()
1031               
1032        def OnLineScan(event):
1033            data['linescan'][0] = linescan.GetValue()
1034            wx.CallAfter(UpdateImageControls,G2frame,data,masks)
1035            G2plt.PlotExposedImage(G2frame,event=event)
1036           
1037        def OnNewLineScan(invalid,value,tc):
1038            G2plt.PlotExposedImage(G2frame,event=None)
1039           
1040        def OnMoveAzm(event):
1041            data['linescan'][1] += float(azmSpin.GetValue())
1042            data['linescan'][1] = data['linescan'][1]%360.
1043            G2frame.scanazm.SetValue(data['linescan'][1])
1044            G2plt.PlotExposedImage(G2frame,event=event)
1045
1046        mplv = mpl.__version__.split('.')
1047        mplOld = mplv[0] == '1' and int(mplv[1]) < 4 # use draw_idle for newer matplotlib versions
1048        # Plot color scaling uses limits as below:
1049        #   (Imin0, Imax0) => Range[0] = data['range'][0] # lowest to highest pixel intensity
1050        #   [Imin, Imax] => Range[1] = data['range'][1] #   lowest to highest pixel intensity on cmap scale
1051        maxSizer = wx.BoxSizer(wx.VERTICAL)
1052        G2frame.slideSizer = wx.FlexGridSizer(2,3,5,5)
1053        G2frame.slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Max intensity'),0,WACV)
1054        # maxSel is a slider with 101 steps scaled from Imin+1 to Imax0 with sqrt scaling
1055        # slider value = sv = 100 * sqrt((Imax-Imin-1)/(Imax0-Imin-1))
1056        # Imax = (sv * sqrt(Imax0-Imin-1) / 100)**2 + Imin + 1
1057        sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1058        sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1059        sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1060        maxSel = G2G.G2Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv1)
1061        G2frame.slideSizer.AddGrowableCol(2)
1062        maxSel.Bind(wx.EVT_SLIDER, OnMaxSlider)
1063        maxVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],1,xmin=Range[0][0]+1,
1064            xmax=Range[0][1],OnLeave=OnNewVal)
1065        G2frame.slideSizer.Add(maxVal,0,WACV)
1066        G2frame.slideSizer.Add(maxSel,flag=wx.EXPAND|wx.ALL)
1067        G2frame.slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min intensity'),0,WACV)
1068        # minSel is a slider with 101 steps scaled from Imin0 to Imax-1 with linear scaling
1069        # slider value = sv0 = 100 * (Imin-Imin0)/(Imax-Imin0-1)
1070        # Imin = sv0 * (Imax-Imin0-1) / 100 + Imin0
1071        DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
1072        sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1073        minSel = G2G.G2Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv0)
1074        minSel.Bind(wx.EVT_SLIDER, OnMinSlider)
1075        minVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],0,
1076            xmax=Range[0][1],typeHint=int,OnLeave=OnNewVal)
1077        G2frame.slideSizer.Add(minVal,0,WACV)
1078        G2frame.slideSizer.Add(minSel,flag=wx.EXPAND|wx.ALL)
1079        maxSizer.Add(G2frame.slideSizer,flag=wx.EXPAND|wx.ALL)
1080        autoSizer = wx.BoxSizer(wx.HORIZONTAL)
1081        autoSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Auto scaler '),0,WACV)
1082        scaleChoices = ("100%","99%","95%","90%","80%","?")
1083        scaleSel = wx.Choice(G2frame.dataWindow,choices=scaleChoices,size=(-1,-1))
1084        if (Range[1][0] == Range[0][0] and
1085            Range[1][1] == Range[0][1]):
1086            scaleSel.SetSelection(0)
1087        else:
1088            scaleSel.SetSelection(len(scaleChoices)-1)
1089        scaleSel.Bind(wx.EVT_CHOICE,OnAutoSet)
1090        autoSizer.Add(scaleSel,0,WACV)
1091        if data['linescan'][0]:
1092            linescan = wx.CheckBox(G2frame.dataWindow,label=' Show line scan at azm = ')
1093        else:
1094            linescan = wx.CheckBox(G2frame.dataWindow,label=' Show line scan')
1095        linescan.Bind(wx.EVT_CHECKBOX,OnLineScan)
1096        linescan.SetValue(data['linescan'][0])
1097        autoSizer.Add((5,0),0)
1098        autoSizer.Add(linescan,0,WACV)
1099        if data['linescan'][0]:
1100            G2frame.scanazm = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['linescan'],1,xmin=0.,
1101            xmax=360.,OnLeave=OnNewLineScan)
1102            autoSizer.Add(G2frame.scanazm,0,WACV)
1103            azmSpin = wx.SpinButton(G2frame.dataWindow,style=wx.SP_VERTICAL,size=wx.Size(20,25))
1104            azmSpin.SetValue(0)
1105            azmSpin.SetRange(-1,1)
1106            azmSpin.Bind(wx.EVT_SPIN, OnMoveAzm)
1107            autoSizer.Add(azmSpin,0,WACV)
1108
1109        maxSizer.Add(autoSizer)
1110        return maxSizer
1111       
1112    def CalibCoeffSizer():
1113       
1114        def OnCalRef(event):
1115            Obj = event.GetEventObject()
1116            name = Indx[Obj]
1117            data['varyList'][name] = Obj.GetValue()
1118           
1119        calibSizer = wx.FlexGridSizer(0,2,5,5)
1120        calibSizer.SetFlexibleDirection(wx.HORIZONTAL)
1121        calibSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration coefficients'),0,WACV)   
1122        calibSizer.Add((5,0),0)
1123        Names = ['det-X','det-Y','wave','dist','tilt','phi']
1124        if 'PWDR' in data['type']:
1125            Names.append('dep') 
1126        Parms = {'dist':['Distance',(10,3),data,'distance'],'det-X':['Beam center X',(10,3),data['center'],0],
1127            'det-Y':['Beam center Y',(10,3),data['center'],1],'tilt':['Tilt angle*',(10,3),data,'tilt'],
1128            'phi':['Tilt rotation*',(10,2),data,'rotation'],'dep':['Penetration*',(10,4),data,'DetDepth'],
1129            'wave':['Wavelength*',(10,6),data,'wavelength']}
1130        for name in Names:
1131            calSel = wx.CheckBox(parent=G2frame.dataWindow,label=Parms[name][0])
1132            calibSizer.Add(calSel,0,WACV)
1133            calSel.Bind(wx.EVT_CHECKBOX, OnCalRef)
1134            calSel.SetValue(data['varyList'][name])
1135            Indx[calSel] = name
1136            if name == 'wave':
1137                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1138                    Parms[name][3],xmin=0.01,xmax=10.,nDig=Parms[name][1],typeHint=float)
1139            elif name == 'dep':
1140                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1141                    Parms[name][3],xmin=0.0,xmax=0.2,nDig=Parms[name][1],typeHint=float)
1142            else:
1143                calVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Parms[name][2],
1144                    Parms[name][3],nDig=Parms[name][1],typeHint=float)
1145            calibSizer.Add(calVal,0,WACV)
1146        return calibSizer
1147   
1148    def IntegrateSizer():
1149       
1150        def OnNewBinType(event):
1151            data['binType'] = binSel.GetValue()
1152            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1153       
1154        def OnIOtth(invalid,value,tc):
1155            Ltth = float(G2frame.InnerTth.GetValue())
1156            Utth = float(G2frame.OuterTth.GetValue())
1157            if Ltth > Utth:
1158                Ltth,Utth = Utth,Ltth
1159            if 'q' in data['binType'].lower():
1160                data['IOtth'] = [2.*asind(Ltth*wave/(4.*math.pi)),2.*asind(Utth*wave/(4.*math.pi))]
1161            else:
1162                data['IOtth'] = [Ltth,Utth]
1163            G2frame.InnerTth.SetValue(Ltth)
1164            G2frame.OuterTth.SetValue(Utth)
1165            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1166       
1167        def OnLRazim(invalid,value,tc):
1168            Lazm = float(G2frame.Lazim.GetValue())%360.
1169            Razm = float(G2frame.Razim.GetValue())%360.
1170            if Lazm > Razm:
1171                Razm += 360.
1172            if data['fullIntegrate']:
1173                Razm = Lazm+360.
1174            G2frame.Lazim.SetValue(Lazm)
1175            G2frame.Razim.SetValue(Razm)
1176            data['LRazimuth'] = [Lazm,Razm]
1177            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1178               
1179        def OnNumOutAzms(invalid,value,tc):
1180            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1181           
1182        def OnNumOutBins(invalid,value,tc):
1183            data['outChannels'] = (data['outChannels']//4)*4
1184            outChan.SetValue(data['outChannels'])
1185       
1186        def OnOblique(event):
1187            data['Oblique'][1] = not data['Oblique'][1]
1188               
1189        def OnSampleShape(event):
1190            data['SampleShape'] = samShape.GetValue()
1191            if 'Cylind' in data['SampleShape']:
1192                data['SampleAbs'][0] = 0.0
1193            elif 'Fixed' in data['SampleShape']:
1194                data['SampleAbs'][0] = 1.0
1195            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1196                           
1197        def OnSamAbs(event):
1198            data['SampleAbs'][1] = not data['SampleAbs'][1]
1199            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1200                           
1201        def OnShowLines(event):
1202            data['showLines'] = not data['showLines']
1203            G2plt.PlotExposedImage(G2frame,event=event)
1204           
1205        def OnFullIntegrate(event):
1206            Lazm = float(G2frame.Lazim.GetValue())
1207            if data['fullIntegrate']:
1208                data['fullIntegrate'] = False
1209                data['LRazimuth'] = [Lazm,Lazm+20.]
1210            else:
1211                data['fullIntegrate'] = True
1212                data['LRazimuth'] = [Lazm,Lazm+360.]
1213            wx.CallLater(100,UpdateImageControls,G2frame,data,masks)
1214            G2plt.PlotExposedImage(G2frame,event=event)
1215           
1216        def OnSetDefault(event):
1217            if data['setDefault']:
1218                G2frame.imageDefault = {}
1219                data['setDefault'] = False
1220            else:
1221                G2frame.imageDefault = copy.deepcopy(data)
1222                G2frame.imageDefault['setDefault'] = False
1223                if 'formatName' in G2frame.imageDefault: del G2frame.imageDefault['formatName']
1224                data['setDefault'] = True
1225               
1226        def OnCenterAzm(event):
1227            data['centerAzm'] = not data['centerAzm']
1228            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1229               
1230        def OnApplyPola(event):
1231            data['PolaVal'][1] = not data['PolaVal'][1]
1232           
1233        def OnIfPink(event):
1234            data['IfPink'] = not data['IfPink']
1235           
1236        def OnOchoice(event):
1237            data['orientation'] = ochoice.GetValue()
1238               
1239        dataSizer = wx.FlexGridSizer(0,2,5,3)
1240        dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Integration coefficients'),0,WACV)   
1241        dataSizer.Add((5,0),0)
1242        if 'PWDR' in data['type']:
1243            binChoice = ['2-theta','Q']
1244        elif 'SASD' in data['type']:
1245            binChoice = ['2-theta','Q','log(q)']
1246        dataSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Bin style: Constant step bins in'),0,WACV)
1247        littleSizer = wx.BoxSizer(wx.HORIZONTAL)                   
1248        binSel = wx.ComboBox(G2frame.dataWindow,value=data['binType'],choices=binChoice,
1249            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1250        binSel.Bind(wx.EVT_COMBOBOX, OnNewBinType)
1251        littleSizer.Add(binSel,0,WACV)
1252        pinkSel = wx.CheckBox(G2frame.dataWindow,label=' Pink beam source?')
1253        pinkSel.SetValue(data['IfPink'])
1254        pinkSel.Bind(wx.EVT_CHECKBOX,OnIfPink)
1255        littleSizer.Add(pinkSel,0,WACV)
1256        dataSizer.Add(littleSizer)
1257        binType = '2-theta'
1258        if 'q' in data['binType'].lower():
1259            binType = 'Q'
1260        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Inner/Outer '+binType),0,WACV)           
1261        IOtth = data['IOtth'][:]
1262        if 'q' in data['binType'].lower():
1263            wave = data['wavelength']
1264            IOtth = [4.*math.pi*sind(IOtth[0]/2.)/wave,4.*math.pi*sind(IOtth[1]/2.)/wave]
1265        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1266        G2frame.InnerTth = G2G.ValidatedTxtCtrl(G2frame.dataWindow,IOtth,0,nDig=(8,3,'f'),xmin=0.001,typeHint=float,OnLeave=OnIOtth)
1267        littleSizer.Add(G2frame.InnerTth,0,WACV)
1268        G2frame.OuterTth = G2G.ValidatedTxtCtrl(G2frame.dataWindow,IOtth,1,nDig=(8,3,'f'),xmin=0.001,typeHint=float,OnLeave=OnIOtth)
1269        littleSizer.Add(G2frame.OuterTth,0,WACV)
1270        dataSizer.Add(littleSizer,0,)
1271        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Start/End azimuth'),0,WACV)
1272        LRazim = data['LRazimuth']
1273        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1274        G2frame.Lazim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,LRazim,0,nDig=(6,1,'f'),typeHint=float,OnLeave=OnLRazim)
1275        littleSizer.Add(G2frame.Lazim,0,WACV)
1276        G2frame.Razim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,LRazim,1,nDig=(6,1,'f'),typeHint=float,OnLeave=OnLRazim)
1277        if data['fullIntegrate']:
1278            G2frame.Razim.Enable(False)
1279            G2frame.Razim.SetBackgroundColour(VERY_LIGHT_GREY)
1280            G2frame.Razim.SetValue(LRazim[0]+360.)
1281        littleSizer.Add(G2frame.Razim,0,WACV)
1282        dataSizer.Add(littleSizer,0,)
1283        dataSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' No. 2-theta/azimuth bins'),0,WACV)
1284        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1285        outChan = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'outChannels',typeHint=int,xmin=10,OnLeave=OnNumOutBins)
1286        littleSizer.Add(outChan,0,WACV)
1287        outAzim = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'outAzimuths',nDig=(10,4),xmin=1,typeHint=int,OnLeave=OnNumOutAzms)
1288        littleSizer.Add(outAzim,0,WACV)
1289        dataSizer.Add(littleSizer)
1290        showLines = wx.CheckBox(parent=G2frame.dataWindow,label='Show integration limits?')
1291        dataSizer.Add(showLines,0,WACV)
1292        showLines.Bind(wx.EVT_CHECKBOX, OnShowLines)
1293        showLines.SetValue(data['showLines'])
1294        fullIntegrate = wx.CheckBox(parent=G2frame.dataWindow,label='Do full integration?')
1295        dataSizer.Add(fullIntegrate,0,WACV)
1296        fullIntegrate.Bind(wx.EVT_CHECKBOX, OnFullIntegrate)
1297        fullIntegrate.SetValue(data['fullIntegrate'])
1298        setDefault = wx.CheckBox(parent=G2frame.dataWindow,label='Use for all new images?')
1299        dataSizer.Add(setDefault,0,WACV)
1300        setDefault.Bind(wx.EVT_CHECKBOX, OnSetDefault)
1301        setDefault.SetValue(data['setDefault'])
1302        centerAzm = wx.CheckBox(parent=G2frame.dataWindow,label='Azimuth at bin center?')
1303        dataSizer.Add(centerAzm,0,WACV)
1304        centerAzm.Bind(wx.EVT_CHECKBOX, OnCenterAzm)
1305        centerAzm.SetValue(data['centerAzm'])
1306        #SampleShape - cylinder or flat plate choice?
1307        littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1308        samabs = wx.CheckBox(parent=G2frame.dataWindow,label='Apply sample absorption?')
1309        dataSizer.Add(samabs,0,WACV)
1310        samabs.Bind(wx.EVT_CHECKBOX, OnSamAbs)
1311        samabs.SetValue(data['SampleAbs'][1])
1312        minmax = [0.,2.]
1313        if data['SampleAbs'][1]:
1314            samplechoice = ['Cylinder','Fixed flat plate',]
1315            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select shape '),0,WACV)
1316            samShape = wx.ComboBox(G2frame.dataWindow,value=data['SampleShape'],choices=samplechoice,
1317                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1318            samShape.Bind(wx.EVT_COMBOBOX,OnSampleShape)
1319            littleSizer.Add(samShape,0,WACV)
1320        dataSizer.Add(littleSizer)
1321        if data['SampleAbs'][1]:
1322            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1323            if 'Cylind' in data['SampleShape']: #cylinder mu*R; flat plate transmission
1324                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='mu*R (0.00-2.0) '),0,WACV)
1325            elif 'Fixed' in data['SampleShape']:
1326                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='transmission '),0,WACV) #for flat plate
1327                minmax = [.05,1.0]
1328            samabsVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['SampleAbs'],0,nDig=(10,3),
1329                typeHint=float,xmin=minmax[0],xmax=minmax[1])
1330            littleSizer.Add(samabsVal,0,WACV)
1331            dataSizer.Add(littleSizer,0,WACV)
1332        if 'Cylind' in data['SampleShape'] and data['SampleAbs'][1]:
1333            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1334            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select orientation '),0,WACV)
1335            choice = ['horizontal','vertical']
1336            ochoice = wx.ComboBox(G2frame.dataWindow,value=data['orientation'],choices=choice,
1337                style=wx.CB_READONLY|wx.CB_DROPDOWN)
1338            ochoice.Bind(wx.EVT_COMBOBOX,OnOchoice)
1339            littleSizer.Add(ochoice,0,WACV)
1340            dataSizer.Add(littleSizer)
1341        if 'flat' in data['SampleShape'] and data['SampleAbs'][1]:
1342            dataSizer.Add((5,5),0)
1343        if 'PWDR' in data['type']:
1344            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1345            oblique = wx.CheckBox(parent=G2frame.dataWindow,label='Apply detector absorption?')
1346            dataSizer.Add(oblique,0,WACV)
1347            oblique.Bind(wx.EVT_CHECKBOX, OnOblique)
1348            oblique.SetValue(data['Oblique'][1])
1349            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Value (0.01-0.99)  '),0,WACV)
1350            obliqVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['Oblique'],0,nDig=(10,3),typeHint=float,xmin=0.01,xmax=0.99)
1351            littleSizer.Add(obliqVal,0,WACV)
1352            dataSizer.Add(littleSizer,0,)
1353        if 'SASD' in data['type']:
1354            littleSizer = wx.BoxSizer(wx.HORIZONTAL)
1355            setPolariz = wx.CheckBox(parent=G2frame.dataWindow,label='Apply polarization?')
1356            dataSizer.Add(setPolariz,0,WACV)
1357            setPolariz.Bind(wx.EVT_CHECKBOX, OnApplyPola)
1358            setPolariz.SetValue(data['PolaVal'][1])
1359            littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='Value (0.001-0.999)  '),0,WACV)
1360            polaVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['PolaVal'],0,nDig=(10,3),typeHint=float,xmin=0.001,xmax=0.999)
1361            littleSizer.Add(polaVal,0,WACV)
1362            dataSizer.Add(littleSizer,0,)
1363       
1364        return dataSizer
1365       
1366    def BackSizer():
1367       
1368        global oldFlat
1369        def OnBackImage(event):
1370            data['background image'][0] = backImage.GetValue()
1371            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1372            ResetThresholds()
1373            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1374           
1375        def OnDarkImage(event):
1376            data['dark image'][0] = darkImage.GetValue()
1377            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1378            ResetThresholds()
1379            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1380           
1381        def OnFlatBkg(invalid,value,tc):
1382            global oldFlat
1383            G2frame.ImageZ += int(oldFlat-data['Flat Bkg'])
1384            oldFlat = data['Flat Bkg']
1385            ResetThresholds()
1386            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1387
1388        def OnMult(invalid,value,tc):
1389            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1390            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=tc.event)
1391           
1392        def OnGainMap(event):
1393            data['Gain map'] = gainMap.GetValue()
1394            G2frame.ImageZ = GetImageZ(G2frame,data,newRange=True)
1395            ResetThresholds()
1396            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1397           
1398        backSizer = wx.FlexGridSizer(0,6,5,5)
1399        oldFlat = data.get('Flat Bkg',0.)
1400
1401        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Dark image'),0,WACV)
1402        Choices = ['',]+G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
1403        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
1404        Choices.pop(Choices.index(Source))
1405        darkImage = wx.ComboBox(parent=G2frame.dataWindow,value=data['dark image'][0],choices=Choices,
1406            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1407        darkImage.Bind(wx.EVT_COMBOBOX,OnDarkImage)
1408        backSizer.Add(darkImage)
1409        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' multiplier'),0,WACV)
1410        darkMult = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['dark image'],1,nDig=(10,3),
1411            typeHint=float,OnLeave=OnMult)
1412        backSizer.Add(darkMult,0,WACV)
1413        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Flat Bkg: '),0,WACV)
1414        flatbkg = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Flat Bkg',nDig=(10,0),
1415            typeHint=float,OnLeave=OnFlatBkg)
1416        backSizer.Add(flatbkg,0,WACV)
1417
1418        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Background image'),0,WACV)
1419        backImage = wx.ComboBox(parent=G2frame.dataWindow,value=data['background image'][0],choices=Choices,
1420            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1421        backImage.Bind(wx.EVT_COMBOBOX,OnBackImage)
1422        backSizer.Add(backImage)
1423        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' multiplier'),0,WACV)
1424        backMult = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['background image'],1,nDig=(10,3),
1425            typeHint=float,OnLeave=OnMult)
1426        backSizer.Add(backMult,0,WACV)
1427        backSizer.Add((5,5),0)
1428        backSizer.Add((5,5),0)
1429        backSizer.Add(wx.StaticText(G2frame.dataWindow,-1,' Gain map'),0,WACV)
1430        gainMap = wx.ComboBox(G2frame.dataWindow,value=data['Gain map'],choices=Choices,
1431            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1432        gainMap.Bind(wx.EVT_COMBOBOX,OnGainMap)
1433        backSizer.Add(gainMap)
1434        return backSizer
1435                       
1436    def CalibSizer():
1437               
1438        def OnNewCalibrant(event):
1439            data['calibrant'] = calSel.GetValue().strip()
1440            if data['calibrant']:
1441                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBRATE,enable=True)
1442                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMCALIBRATE,enable=True)
1443                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBALL,enable=True)
1444                data['calibskip'] = calFile.Calibrants[data['calibrant']][3]
1445                limits = calFile.Calibrants[data['calibrant']][4]
1446                data['calibdmin'],data['pixLimit'],data['cutoff'] = limits
1447                pixLimit.SetValue(str(limits[1]))
1448                cutOff.SetValue(limits[2])
1449                calibSkip.SetValue(str(data['calibskip']))
1450                G2frame.calibDmin.SetValue(limits[0])
1451            else:
1452                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBRATE,enable=False)
1453                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMCALIBRATE,enable=False)
1454                G2frame.dataWindow.ImageEdit.Enable(id=G2G.wxID_IMRECALIBALL,enable=False)
1455               
1456        def OnCalibSkip(event):
1457            data['calibskip'] = int(calibSkip.GetValue())
1458           
1459        def OnPixLimit(event):
1460            data['pixLimit'] = int(pixLimit.GetValue())
1461           
1462        def OnSetRings(event):
1463            data['setRings'] = not data['setRings']
1464            G2plt.PlotExposedImage(G2frame,event=event)
1465   
1466        calibSizer = wx.FlexGridSizer(0,3,5,5)
1467        comboSizer = wx.BoxSizer(wx.HORIZONTAL)   
1468        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibrant '),0,WACV)
1469        if (GSASIIpath.GetConfigValue('Image_calibrant') and
1470                    GSASIIpath.GetConfigValue('Image_calibrant') in calList and
1471                    not data['calibrant']):
1472            data['calibrant'] = GSASIIpath.GetConfigValue('Image_calibrant')
1473        calSel = wx.ComboBox(parent=G2frame.dataWindow,value=data['calibrant'],choices=calList,
1474            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1475        calSel.Bind(wx.EVT_COMBOBOX, OnNewCalibrant)
1476        comboSizer.Add(calSel,0,WACV)
1477        calibSizer.Add(comboSizer,0)
1478       
1479        comboSizer = wx.BoxSizer(wx.HORIZONTAL)   
1480        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calib lines to skip   '),0,WACV)
1481        calibSkip  = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['calibskip']),choices=[str(i) for i in range(25)],
1482            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1483        calibSkip.Bind(wx.EVT_COMBOBOX, OnCalibSkip)
1484        comboSizer.Add(calibSkip,0,WACV)
1485        calibSizer.Add(comboSizer,0)
1486       
1487        comboSizer = wx.BoxSizer(wx.HORIZONTAL)       
1488        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min calib d-spacing '),0,WACV)
1489        G2frame.calibDmin = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'calibdmin',nDig=(10,2),typeHint=float,xmin=0.25)
1490        comboSizer.Add(G2frame.calibDmin,0,WACV)
1491        calibSizer.Add(comboSizer,0)
1492       
1493        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1494        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min ring I/Ib '),0,WACV)
1495        cutOff = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'cutoff',nDig=(10,2),xmin=0.1)
1496        comboSizer.Add(cutOff,0,WACV)
1497        calibSizer.Add(comboSizer,0)
1498       
1499        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1500        comboSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Pixel search range '),0,WACV)
1501        pixLimit = wx.ComboBox(parent=G2frame.dataWindow,value=str(data['pixLimit']),choices=['1','2','5','10','15','20'],
1502            style=wx.CB_READONLY|wx.CB_DROPDOWN)
1503        pixLimit.Bind(wx.EVT_COMBOBOX, OnPixLimit)
1504        comboSizer.Add(pixLimit,0,WACV)
1505        calibSizer.Add(comboSizer,0)
1506       
1507        comboSizer = wx.BoxSizer(wx.HORIZONTAL)
1508        setRings = wx.CheckBox(parent=G2frame.dataWindow,label='Show ring picks?')
1509        comboSizer.Add(setRings,0)
1510        setRings.Bind(wx.EVT_CHECKBOX, OnSetRings)
1511        setRings.SetValue(data['setRings'])
1512        calibSizer.Add(comboSizer,0)
1513        return calibSizer
1514       
1515    def GonioSizer():
1516       
1517        def OnGlobalEdit(event):
1518            Names = []
1519            Items = []
1520            if G2frame.GPXtree.GetCount():
1521                Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
1522                while Id:
1523                    name = G2frame.GPXtree.GetItemText(Id)
1524                    if 'IMG' in name:
1525                        ctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
1526                        Names.append(name)
1527                        Items.append(ctrls['GonioAngles'])
1528                    Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
1529                if len(Names) == 1:
1530                    G2frame.ErrorDialog('Nothing for global editing','There must be more than one "IMG" pattern')
1531                    return
1532                dlg = G2G.G2HistoDataDialog(G2frame,' Edit sample goniometer data:',
1533                    'Edit data',['Omega','Chi','Phi'],['%.2f','%.2f','%.2f'],Names,Items)
1534                try:
1535                    if dlg.ShowModal() == wx.ID_OK:
1536                        Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
1537                        while Id:
1538                            name = G2frame.GPXtree.GetItemText(Id)
1539                            if 'IMG' in name:
1540                                ctrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Image Controls'))
1541                                vals = Items[Names.index(name)]
1542                                ctrls['GonioAngles'] = vals
1543                            Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
1544                finally:
1545                    dlg.Destroy()
1546                    G2frame.GPXtree.SelectItem(G2frame.PickId)
1547       
1548        gonioSizer = wx.BoxSizer(wx.HORIZONTAL)
1549        names = ['Omega','Chi','Phi']
1550        gonioSizer.Add(wx.StaticText(G2frame.dataWindow,-1,'Sample goniometer angles: '),0,WACV)
1551        for i,name in enumerate(names):
1552            gonioSizer.Add(wx.StaticText(G2frame.dataWindow,-1,name),0,WACV)
1553            angle = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data['GonioAngles'],i,nDig=(8,2),typeHint=float)
1554            gonioSizer.Add(angle,0,WACV)
1555        globEdit = wx.Button(G2frame.dataWindow,-1,'Global edit')
1556        globEdit.Bind(wx.EVT_BUTTON,OnGlobalEdit)
1557        gonioSizer.Add(globEdit,0,WACV)
1558        return gonioSizer
1559       
1560    # UpdateImageControls: Image Controls main code             
1561                           
1562    #fix for old files:
1563    if 'azmthOff' not in data:
1564        data['azmthOff'] = 0.0
1565    if 'background image' not in data:
1566        data['background image'] = ['',-1.0]
1567    if 'dark image' not in data:
1568        data['dark image'] = ['',-1.0]
1569    if 'centerAzm' not in data:
1570        data['centerAzm'] = False
1571    if 'Oblique' not in data:
1572        data['Oblique'] = [0.5,False]
1573    if 'PolaVal' not in data:
1574        data['PolaVal'] = [0.99,False]
1575    if 'IfPink' not in data:
1576        data['IfPink'] = False
1577    #end fix
1578   
1579    if IntegrateOnly:
1580        Masks = G2frame.GPXtree.GetItemPyData(
1581            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))
1582        # Mhash = hash(str(Masks))  # TODO: implement this to save integration time (?)
1583        # if  Mhash != oldMhash:
1584        #     t0 = time.time()
1585        useMask = G2img.MakeUseMask(data,Masks,blkSize)
1586        #     print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
1587        #     oldMhash = Mhash
1588        OnIntegrate(None,useTA=useTA,useMask=useMask)
1589        return
1590   
1591    G2frame.GetStatusBar().SetStatusText('* Global parameters in Multi-dist recalib.',1)
1592    colorList = sorted([m for m in mpl.cm.datad.keys() ]+['GSPaired','GSPaired_r',],key=lambda s: s.lower())   #if not m.endswith("_r")
1593    calList = sorted([m for m in calFile.Calibrants.keys()],key=lambda s: s.lower())
1594    typeList = ['PWDR - powder diffraction data','SASD - small angle scattering data',]
1595    if not data.get('type'):                        #patch for old project files
1596        data['type'] = 'PWDR'
1597    typeDict = {'PWDR':typeList[0],'SASD':typeList[1],}
1598    G2frame.dataWindow.ClearData()
1599    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.ImageMenu)
1600    G2frame.Bind(wx.EVT_MENU, OnCalibrate, id=G2G.wxID_IMCALIBRATE)
1601    G2frame.Bind(wx.EVT_MENU, OnRecalibrate, id=G2G.wxID_IMRECALIBRATE)
1602    G2frame.Bind(wx.EVT_MENU, OnRecalibAll, id=G2G.wxID_IMRECALIBALL)
1603    G2frame.Bind(wx.EVT_MENU, OnCalcRings, id=G2G.wxID_CALCRINGS)
1604    G2frame.Bind(wx.EVT_MENU, OnDistRecalib, id=G2G.wxID_IMDISTRECALIB)
1605    G2frame.Bind(wx.EVT_MENU, OnClearCalib, id=G2G.wxID_IMCLEARCALIB)
1606#    if data.get('calibrant'):
1607#        mode = True
1608#    else:
1609#        mode = False
1610#    G2frame.Enable(id=G2G.wxID_IMRECALIBRATE,enable=mode)
1611#    G2frame.Enable(id=G2G.wxID_IMCALIBRATE,enable=mode)
1612#    G2frame.Enable(id=G2G.wxID_IMRECALIBALL,enable=mode)
1613    G2frame.Bind(wx.EVT_MENU, OnIntegrate, id=G2G.wxID_IMINTEGRATE)
1614    G2frame.Bind(wx.EVT_MENU, OnIntegrateAll, id=G2G.wxID_INTEGRATEALL)
1615    G2frame.Bind(wx.EVT_MENU, OnCopyControls, id=G2G.wxID_IMCOPYCONTROLS)
1616    G2frame.Bind(wx.EVT_MENU, OnCopySelected, id=G2G.wxID_IMCOPYSELECTED)
1617    G2frame.Bind(wx.EVT_MENU, OnSaveControls, id=G2G.wxID_IMSAVECONTROLS)
1618    G2frame.Bind(wx.EVT_MENU, OnSaveMultiControls, id=G2G.wxID_SAVESELECTEDCONTROLS)
1619    G2frame.Bind(wx.EVT_MENU, OnLoadControls, id=G2G.wxID_IMLOADCONTROLS)
1620    G2frame.Bind(wx.EVT_MENU, OnLoadMultiControls, id=G2G.wxID_LOADELECTEDCONTROLS)
1621    G2frame.Bind(wx.EVT_MENU, OnTransferAngles, id=G2G.wxID_IMXFERCONTROLS)
1622    G2frame.Bind(wx.EVT_MENU, OnResetDist, id=G2G.wxID_IMRESETDIST)
1623    def OnDestroy(event):
1624        G2frame.autoIntFrame = None
1625    def OnAutoInt(event):
1626        if G2frame.autoIntFrame: # ensure only one open at a time
1627            G2frame.autoIntFrame.Raise()
1628            return
1629        else:
1630            print('Auto-integration window already open')
1631        PollTime = GSASIIpath.GetConfigValue('Autoint_PollTime',30.)
1632        G2frame.autoIntFrame = AutoIntFrame(G2frame,PollTime=PollTime)
1633        # debug code to reload code for window on each use
1634        #import GSASIIimgGUI
1635        #reload(GSASIIimgGUI)
1636        #G2frame.autoIntFrame = GSASIIimgGUI.AutoIntFrame(G2frame,PollTime=PollTime)
1637
1638        G2frame.autoIntFrame.Bind(wx.EVT_WINDOW_DESTROY,OnDestroy) # clean up name on window close
1639    G2frame.Bind(wx.EVT_MENU, OnAutoInt, id=G2G.wxID_IMAUTOINTEG)
1640    def OnIntPDFtool(event):
1641        import subprocess
1642        ex = sys.executable
1643        if sys.platform == "darwin": # mac requires pythonw which is not always reported as sys.executable
1644            if os.path.exists(ex+'w'): ex += 'w'
1645        if G2frame.GSASprojectfile:
1646            project = os.path.abspath(G2frame.GSASprojectfile)
1647        else:
1648            project = ''
1649        subprocess.Popen([ex,os.path.join(GSASIIpath.path2GSAS2,'GSASIIIntPDFtool.py'),project])
1650    G2frame.Bind(wx.EVT_MENU, OnIntPDFtool, id=G2G.wxID_IMINTEGPDFTOOL)
1651
1652    mainSizer = G2frame.dataWindow.GetSizer() 
1653    topSizer = wx.BoxSizer(wx.HORIZONTAL)
1654    topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Image Controls:'))
1655    topSizer.Add((-1,-1),1,wx.EXPAND)
1656    topSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
1657    mainSizer.Add(topSizer,0,wx.EXPAND)
1658    mainSizer.Add((5,10),0)   
1659    mainSizer.Add(ComboSizer(),0,wx.ALIGN_LEFT)
1660    mainSizer.Add((5,5),0)
1661    Range = data['range'] # allows code to be same in Masks
1662    MaxSizer = MaxSizer()               #keep this so it can be changed in BackSizer   
1663    mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL)
1664   
1665    mainSizer.Add((5,5),0)
1666    DataSizer = wx.FlexGridSizer(0,2,5,0)
1667    DataSizer.Add(CalibCoeffSizer(),0)
1668    DataSizer.Add(IntegrateSizer(),0)       
1669    mainSizer.Add(DataSizer,0)
1670    mainSizer.Add((5,5),0)           
1671    mainSizer.Add(BackSizer(),0)
1672    mainSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Calibration controls:'),0)
1673    mainSizer.Add((5,5),0)
1674    mainSizer.Add(CalibSizer(),0)
1675    mainSizer.Add((5,5),0)
1676    mainSizer.Add(GonioSizer(),0)   
1677    G2frame.dataWindow.SetDataSize()
1678   
1679################################################################################
1680##### Masks
1681################################################################################
1682def CleanupMasks(data):
1683    '''If a mask creation is not completed, an empty mask entry is created in the
1684    masks array. This cleans them out. It is called when the masks page is first loaded
1685    and before saving them or after reading them in. This should also probably be done
1686    before they are used for integration.
1687    '''
1688    for key in ['Points','Rings','Arcs','Polygons',]:
1689        data[key] = data.get(key,[])
1690        l1 = len(data[key])
1691        data[key] = [i for i in data[key] if len(i)]
1692        l2 = len(data[key])
1693        if GSASIIpath.GetConfigValue('debug') and l1 != l2:
1694            print ('DBG_Mask Cleanup: %s was %d entries, now %d'%(key,l1,l2))
1695   
1696def UpdateMasks(G2frame,data):
1697    '''Shows and handles the controls on the "Masks" data tree entry
1698    '''
1699   
1700    def OnTextMsg(event):
1701        Obj = event.GetEventObject()
1702        Obj.SetToolTipString('Drag this mask on 2D Powder Image with mouse to change ')
1703
1704    def Replot(*args,**kwargs):
1705        wx.CallAfter(G2plt.PlotExposedImage,G2frame)
1706
1707    def newReplot(*args,**kwargs):
1708        wx.CallAfter(G2plt.PlotExposedImage,G2frame,newPlot=True)
1709
1710    def onDeleteMask(event):
1711        Obj = event.GetEventObject()
1712        typ = Obj.locationcode.split('+')[1]
1713        num = int(Obj.locationcode.split('+')[2])
1714        del(data[typ][num])
1715        wx.CallAfter(UpdateMasks,G2frame,data)
1716        G2plt.PlotExposedImage(G2frame,event=event)
1717       
1718    def OnSpotChange(event):
1719        r,c = event.GetRow(),event.GetCol()
1720        if c == 2:
1721            del Spots[r]
1722            SpotTable.DeleteRow(r)
1723        else:
1724            Spots[r][2] = float(SpotGrid.GetCellValue(r,c))
1725        SpotGrid.ForceRefresh()
1726        wx.CallAfter(UpdateMasks,G2frame,data)
1727        G2plt.PlotExposedImage(G2frame,event=event)
1728        event.Skip()
1729
1730    def onDeleteFrame(event):
1731        data['Frames'] = []
1732        wx.CallAfter(UpdateMasks,G2frame,data)
1733        G2plt.PlotExposedImage(G2frame,event=event)
1734
1735    def OnCopyMask(event):
1736        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
1737        if len(Names) == 1:
1738            G2frame.ErrorDialog('Nothing to copy masks to','There must be more than one "IMG" pattern')
1739            return
1740        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
1741        Names.pop(Names.index(Source))
1742        Data = copy.deepcopy(data)
1743        Thresh = Data.pop('Thresholds')     # & remove it as well
1744        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy mask data','Copy masks from '+Source+' to:',Names)
1745        try:
1746            if dlg.ShowModal() == wx.ID_OK:
1747                items = dlg.GetSelections()
1748                for item in items:
1749                    name = Names[item]
1750                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1751                    MId = G2gd.GetGPXtreeItemId(G2frame,Id,'Masks')
1752                    Mask = G2frame.GPXtree.GetItemPyData(MId)
1753                    Mask.update(copy.deepcopy(Data))
1754                    Mask['Thresholds'][1][0] = Thresh[1][0]  #copy only lower threshold
1755                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Masks'),Mask)
1756        finally:
1757            dlg.Destroy()
1758
1759    def OnSaveMask(event):
1760        CleanupMasks(data)
1761        pth = G2G.GetExportPath(G2frame)
1762        dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 
1763            'image mask files (*.immask)|*.immask',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
1764        try:
1765            if dlg.ShowModal() == wx.ID_OK:
1766                filename = dlg.GetPath()
1767                filename = os.path.splitext(filename)[0]+'.immask'
1768                File = open(filename,'w')
1769                keys = ['Points','Rings','Arcs','Polygons','Xlines','Ylines','Frames','Thresholds']
1770                for key in keys:
1771                    File.write(key+':'+str(data[key])+'\n')
1772                File.close()
1773        finally:
1774            dlg.Destroy()
1775       
1776    def OnLoadMask(event):
1777        if event.Id == G2G.wxID_MASKLOADNOT:
1778            ignoreThreshold = True
1779        else:
1780            ignoreThreshold = False
1781        pth = G2G.GetImportPath(G2frame)
1782        if not pth: pth = '.'
1783        dlg = wx.FileDialog(G2frame, 'Choose image mask file', pth, '', 
1784            'image mask files (*.immask)|*.immask',wx.FD_OPEN)
1785        try:
1786            if dlg.ShowModal() == wx.ID_OK:
1787                filename = dlg.GetPath()
1788               
1789                G2fil.readMasks(filename,data,ignoreThreshold)
1790                wx.CallAfter(UpdateMasks,G2frame,data)
1791                G2plt.PlotExposedImage(G2frame,event=event)               
1792        finally:
1793            dlg.Destroy()
1794           
1795    def OnFindPixelMask(event):
1796        '''Do auto search for pixels to mask
1797        Called from (Masks) Operations->"Pixel mask search"
1798        '''
1799        Controls = G2frame.GPXtree.GetItemPyData( 
1800            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
1801        try:
1802            wave = Controls['wavelength']
1803            LUtth = np.array(Controls['IOtth'])
1804            dsp0 = wave/(2.0*sind(LUtth[0]/2.0))
1805            dsp1 = wave/(2.0*sind(LUtth[1]/2.0))
1806            x0 = G2img.GetDetectorXY2(dsp0,0.0,Controls)[0]
1807            x1 = G2img.GetDetectorXY2(dsp1,0.0,Controls)[0]
1808            if not np.any(x0) or not np.any(x1):
1809                raise Exception
1810            nChans = int(1000*(x1-x0)/Controls['pixelSize'][0])//2
1811        except:
1812            print('Invalid limits - pixel mask search not done')
1813
1814        if G2img.TestFastPixelMask() and data['SpotMask'].get('FastSearch',True):
1815            wx.BeginBusyCursor()
1816            dlg = wx.ProgressDialog("Pixel masking search",
1817                        "Setting up fast scan",parent=G2frame)
1818            dlg.Update(1)
1819            dlg.CenterOnParent()
1820            time0 = time.time()
1821            if data['SpotMask'].get('ClearPrev',True) or data['SpotMask']['spotMask'] is None:
1822                 data['SpotMask']['spotMask'] = G2img.FastAutoPixelMask(G2frame.ImageZ,data,Controls,nChans,dlg)
1823            else:
1824                data['SpotMask']['spotMask'] |= G2img.FastAutoPixelMask(G2frame.ImageZ,data,Controls,nChans,dlg)
1825            print(' Pixel mask search time: %.2f sec'%((time.time()-time0)))
1826            wx.CallAfter(UpdateMasks,G2frame,data)
1827            wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1828            dlg.Destroy()
1829            wx.EndBusyCursor()
1830            return
1831   
1832# since we now have a Cancel button, we really don't need to ask anymore
1833#        dlg = wx.MessageDialog(G2frame.dataWindow,
1834#                'NB: This can be slow (0.5 to 2 min)',
1835#                'Pixel mask search', wx.OK|wx.CANCEL)
1836        dlg = wx.ProgressDialog("Pixel masking search for %d rings"%nChans,"Processed 2-theta rings = ",nChans+3,
1837            style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT,parent=G2frame)
1838        time0 = time.time()
1839        mask = G2img.AutoPixelMask(G2frame.ImageZ,data,Controls,nChans,dlg)
1840        dlg.Destroy()
1841        if mask is None:
1842            print(' Pixel mask search not completed')
1843            return
1844        if data['SpotMask'].get('ClearPrev',True) or data['SpotMask']['spotMask'] is None:
1845            data['SpotMask']['spotMask'] = mask
1846        else:
1847            data['SpotMask']['spotMask'] |= mask
1848        print(' Pixel mask search time: %.2f m'%((time.time()-time0)/60.))
1849        wx.CallAfter(UpdateMasks,G2frame,data)
1850        wx.CallAfter(G2plt.PlotExposedImage,G2frame,event=event)
1851           
1852    def OnAutoFindPixelMask(event):
1853        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
1854        fast = G2img.TestFastPixelMask()
1855        dlg = G2G.G2MultiChoiceDialog(G2frame,
1856                    'Multiple image pixel mask search',
1857                    'Select images for pixel masking:',Names)
1858        if dlg.ShowModal() != wx.ID_OK: return
1859        items = dlg.GetSelections()
1860        G2frame.EnablePlot = False
1861        for item in items:
1862            try:
1863                name = Names[item]
1864                G2frame.Image = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
1865                Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls'))
1866                Mask = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Masks'))
1867                G2frame.ImageZ = GetImageZ(G2frame,Controls)
1868                wave = Controls['wavelength']
1869                LUtth = np.array(Controls['IOtth'])
1870                dsp0 = wave/(2.0*sind(LUtth[0]/2.0))
1871                dsp1 = wave/(2.0*sind(LUtth[1]/2.0))
1872                x0 = G2img.GetDetectorXY2(dsp0,0.0,Controls)[0]
1873                x1 = G2img.GetDetectorXY2(dsp1,0.0,Controls)[0]
1874                if not np.any(x0) or not np.any(x1):
1875                    raise Exception
1876                nChans = int(1000*(x1-x0)/Controls['pixelSize'][0])//2
1877
1878                if fast and Mask['SpotMask'].get('FastSearch',True):
1879                    print ('Fast pixel mask search for '+name)
1880                    wx.BeginBusyCursor()
1881                    dlg = wx.ProgressDialog("Pixel masking search",
1882                            "Setting up fast scan",parent=G2frame)
1883                    dlg.Update(1)
1884                    dlg.CenterOnParent()
1885                    time0 = time.time()
1886                    if Mask['SpotMask'].get('ClearPrev',True) or Mask['SpotMask']['spotMask'] is None:
1887                        Mask['SpotMask']['spotMask'] = G2img.FastAutoPixelMask(
1888                            G2frame.ImageZ,Mask,Controls,nChans,dlg)
1889                    else:
1890                        Mask['SpotMask']['spotMask'] |= G2img.FastAutoPixelMask(
1891                            G2frame.ImageZ,Mask,Controls,nChans,dlg)
1892                    print('Pixel mask search time: %.2f sec'%((time.time()-time0)))
1893                    dlg.Destroy()
1894                    wx.EndBusyCursor()
1895                    continue
1896                else:
1897                    print ('Std pixel mask search for '+name)
1898                    try:
1899                        dlg = wx.ProgressDialog("Pixel mask search for %d bins"%nChans,"Processed 2-theta rings = ",nChans+3,
1900                            style = wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT,
1901                                                    parent=G2frame)
1902                        time0 = time.time()
1903                        mask = G2img.AutoPixelMask(G2frame.ImageZ,Mask,Controls,nChans,dlg)
1904                        if mask is None: return  # aborted search
1905                        if Mask['SpotMask'].get('ClearPrev',True) or Mask['SpotMask']['spotMask'] is None:
1906                            Mask['SpotMask']['spotMask'] = mask
1907                        else:
1908                            Mask['SpotMask']['spotMask'] |= mask
1909                        print('Pixel mask search time: %.2f m'%((time.time()-time0)/60.))
1910                    finally:
1911                        dlg.Destroy()
1912            except Exception as msg:
1913                print('Invalid limits - pixel mask search not done')
1914                if GSASIIpath.GetConfigValue('debug'): print(msg)
1915        G2plt.PlotExposedImage(G2frame,event=None)
1916       
1917
1918    def OnDeleteSpotMask(event):
1919        data['Points'] = []
1920        wx.CallAfter(UpdateMasks,G2frame,data)
1921        G2plt.PlotExposedImage(G2frame,newPlot=True,event=event)         
1922           
1923    def ToggleSpotMaskMode(event):
1924        G2plt.ToggleMultiSpotMask(G2frame)
1925       
1926    def OnNewArcMask(event):
1927        'Start a new arc mask'
1928        G2frame.MaskKey = 'a'
1929        G2plt.OnStartMask(G2frame)
1930       
1931    def OnNewRingMask(event):
1932        'Start a new ring mask'
1933        G2frame.MaskKey = 'r'
1934        G2plt.OnStartMask(G2frame)
1935       
1936    def OnNewXlineMask(event):
1937        'Start a new x-line mask'
1938        print('x')
1939        G2frame.MaskKey = 'x'
1940        G2plt.OnStartMask(G2frame)
1941       
1942    def OnNewYlineMask(event):
1943        'Start a new y-line mask'
1944        G2frame.MaskKey = 'y'
1945        G2plt.OnStartMask(G2frame)
1946       
1947    def OnNewPolyMask(event):
1948        'Start a new polygon mask'
1949        G2frame.MaskKey = 'p'
1950        G2plt.OnStartMask(G2frame)
1951       
1952    def OnNewFrameMask(event):
1953        'Start a new Frame mask'
1954        G2frame.MaskKey = 'f'
1955        G2plt.OnStartMask(G2frame)
1956       
1957    def MaxSizer():
1958        '''Defines a sizer with sliders and TextCtrl widgets for controlling the colormap
1959        for the image, as well as callback routines.
1960        '''
1961        def OnNewVal(invalid,value,tc):
1962            '''Called when a Imax or Imin value is typed into a Validated TextCrtl (which puts
1963            the value into the data['range'] nested list).
1964            This adjusts the slider positions to match the current values
1965            '''
1966            scaleSel.SetSelection(len(scaleChoices)-1)
1967            r11 = min(max(Range[1][1],Range[1][0]+1),Range[0][1]) # keep values in range
1968            if r11 != Range[1][1]:
1969                Range[1][1] = r11
1970                maxVal.SetValue(int(Range[1][1]))
1971            r10 = max(min(Range[1][0],Range[1][1]-1),Range[0][0])
1972            if r10 != Range[1][0]:
1973                Range[1][0] = r10
1974                minVal.SetValue(int(Range[1][0]))
1975            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1976            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
1977            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
1978            maxSel.SetValue(sv1)
1979            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1980            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
1981            minSel.SetValue(sv0)
1982            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
1983            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
1984            if mplOld:
1985                Page.canvas.draw()
1986            else:
1987                Page.canvas.draw_idle()
1988           
1989        G2frame.prevMaxValue = None   
1990        def OnMaxSlider(event):
1991            val = maxSel.GetValue()
1992            if G2frame.prevMaxValue == val: return # if this val has been processed, no need to repeat
1993            scaleSel.SetSelection(len(scaleChoices)-1)
1994            G2frame.prevMaxValue = val
1995            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
1996            Range[1][1] = int(0.5 + (val * sqrtDeltZero / 100.)**2 + Range[1][0] + 1)
1997            maxVal.SetValue(int(0.5+Range[1][1]))
1998            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
1999            minSel.SetValue(int(0.5 + 100*(Range[1][0]/DeltOne)))
2000            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
2001            minSel.SetValue(sv0)
2002            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
2003            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
2004            if mplOld:
2005                Page.canvas.draw()
2006            else:
2007                Page.canvas.draw_idle()
2008           
2009        G2frame.prevMinValue = None   
2010        def OnMinSlider(event):
2011            val = minSel.GetValue()
2012            scaleSel.SetSelection(len(scaleChoices)-1)
2013            if G2frame.prevMinValue == val: return # if this val has been processed, no need to repeat
2014            G2frame.prevMinValue = val
2015            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
2016            Range[1][0] = max(0,int(0.5 + val * DeltOne / 100 + Range[0][0]))
2017            minVal.SetValue(int(Range[1][0]))
2018            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
2019            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
2020            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
2021            maxSel.SetValue(sv1)
2022            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
2023            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
2024            if mplOld:
2025                Page.canvas.draw()
2026            else:
2027                Page.canvas.draw_idle()
2028           
2029        def OnAutoSet(event):
2030            '''Responds to a button labeled 95%, etc; Sets the Imax and Imin values
2031            for the image so that 95% (etc.) of pixels are inside the color map limits.
2032            An equal number of pixels are dropped at the minimum and maximum levels.
2033            '''
2034            try:
2035                val = int(event.GetEventObject().GetStringSelection()[:-1])
2036                margin = (100-val)/2.
2037            except:
2038                margin = 0
2039                event.GetEventObject().SetSelection(0)
2040            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
2041            if margin == 0:
2042                Range[1] = list(Range[0])
2043            else:
2044                Range[1][0] = int(np.percentile(Page.ImgObj.get_array().compressed(),margin))
2045                Range[1][1] = int(np.percentile(Page.ImgObj.get_array().compressed(),100-margin))
2046            sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
2047            sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
2048            sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
2049            maxSel.SetValue(sv1)
2050            DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1)
2051            sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
2052            minSel.SetValue(sv0)
2053            minVal.SetValue(int(Range[1][0]))
2054            maxVal.SetValue(int(Range[1][1]))
2055            new,plotNum,Page,Plot,lim = G2frame.G2plotNB.FindPlotTab('2D Powder Image','mpl',newImage=False)
2056            Page.ImgObj.set_clim([Range[1][0],Range[1][1]])
2057            if mplOld:
2058                Page.canvas.draw()
2059            else:
2060                Page.canvas.draw_idle()
2061
2062        mplv = mpl.__version__.split('.')
2063        mplOld = mplv[0] == '1' and int(mplv[1]) < 4 # use draw_idle for newer matplotlib versions
2064        # Plot color scaling uses limits as below:
2065        #   (Imin0, Imax0) => Range[0] = data['range'][0] # lowest to highest pixel intensity
2066        #   [Imin, Imax] => Range[1] = data['range'][1] #   lowest to highest pixel intensity on cmap scale
2067       
2068        maxSizer = wx.BoxSizer(wx.VERTICAL)
2069        slideSizer = wx.FlexGridSizer(2,3,5,5)
2070        slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Max intensity'),0,WACV)
2071        # maxSel is a slider with 101 steps scaled from Imin+1 to Imax0 with sqrt scaling
2072        # slider value = sv = 100 * sqrt((Imax-Imin-1)/(Imax0-Imin-1))
2073        # Imax = (sv * sqrt(Imax0-Imin-1) / 100)**2 + Imin + 1
2074        sqrtDeltZero = math.sqrt(max(1.0,Range[0][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax0-Imin-1)
2075        sqrtDeltOne  = math.sqrt(max(1.0,Range[1][1]-max(0.0,Range[1][0])-1)) # sqrt(Imax-Imin-1)
2076        sv1 = min(100,max(0,int(0.5+100.*sqrtDeltOne/sqrtDeltZero)))
2077        maxSel = G2G.G2Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv1)
2078        maxVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],1,xmin=Range[0][0]+1,
2079            xmax=Range[0][1],OnLeave=OnNewVal)
2080        slideSizer.Add(maxVal,0,WACV)
2081        slideSizer.Add(maxSel,flag=wx.EXPAND|wx.ALL)
2082        slideSizer.AddGrowableCol(2)
2083        maxSel.Bind(wx.EVT_SLIDER, OnMaxSlider)
2084        slideSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Min intensity'),0,WACV)
2085        # minSel is a slider with 101 steps scaled from Imin0 to Imax-1 with linear scaling
2086        # slider value = sv0 = 100 * (Imin-Imin0)/(Imax-Imin0-1)
2087        # Imin = sv0 * (Imax-Imin0-1) / 100 + Imin0
2088        DeltOne  = max(1.0,Range[1][1]-max(0.0,Range[0][0])-1) # Imax-Imin0-1
2089        sv0 = min(100,max(0,int(0.5+100.*(Range[1][0]-Range[0][0])/DeltOne)))
2090        minVal = G2G.ValidatedTxtCtrl(G2frame.dataWindow,Range[1],0,
2091            xmax=Range[0][1],typeHint=int,OnLeave=OnNewVal)
2092        slideSizer.Add(minVal,0,WACV)
2093        minSel = G2G.G2Slider(parent=G2frame.dataWindow,style=wx.SL_HORIZONTAL,value=sv0)
2094        slideSizer.Add(minSel,flag=wx.EXPAND|wx.ALL)
2095        minSel.Bind(wx.EVT_SLIDER, OnMinSlider)
2096        maxSizer.Add(slideSizer,flag=wx.EXPAND|wx.ALL)
2097        autoSizer = wx.BoxSizer(wx.HORIZONTAL)
2098        autoSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Auto scaler '),0,WACV)
2099        scaleChoices = ("100%","99%","95%","90%","80%","?")
2100        scaleSel = wx.Choice(G2frame.dataWindow,choices=scaleChoices,size=(-1,-1))
2101        if (Range[1][0] == Range[0][0] and
2102            Range[1][1] == Range[0][1]):
2103            scaleSel.SetSelection(0)
2104        else:
2105            scaleSel.SetSelection(len(scaleChoices)-1)
2106        scaleSel.Bind(wx.EVT_CHOICE,OnAutoSet)
2107        autoSizer.Add(scaleSel,0,WACV)
2108        maxSizer.Add(autoSizer)
2109        return maxSizer
2110   
2111    def OnDelPixMask(event):
2112        data['SpotMask'] = {'esdMul':3.,'spotMask':None}
2113        wx.CallAfter(UpdateMasks,G2frame,data)
2114        G2plt.PlotExposedImage(G2frame,event=event)
2115                   
2116    G2frame.dataWindow.ClearData()
2117    startScroll = None
2118    if G2frame.dataWindow:
2119        startScroll = G2frame.dataWindow.GetScrollPos(wx.VERTICAL) # save scroll position
2120    else:
2121        CleanupMasks(data) # posting page for 1st time; clean out anything unfinished
2122    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.MaskMenu)
2123    G2frame.Bind(wx.EVT_MENU, OnCopyMask, id=G2G.wxID_MASKCOPY)
2124    G2frame.Bind(wx.EVT_MENU, OnLoadMask, id=G2G.wxID_MASKLOAD)
2125    G2frame.Bind(wx.EVT_MENU, OnLoadMask, id=G2G.wxID_MASKLOADNOT)
2126    G2frame.Bind(wx.EVT_MENU, OnSaveMask, id=G2G.wxID_MASKSAVE)
2127    G2frame.Bind(wx.EVT_MENU, OnFindPixelMask, id=G2G.wxID_FINDSPOTS)
2128    G2frame.Bind(wx.EVT_MENU, OnAutoFindPixelMask, id=G2G.wxID_AUTOFINDSPOTS)
2129    G2frame.Bind(wx.EVT_MENU, OnDeleteSpotMask, id=G2G.wxID_DELETESPOTS)
2130    G2frame.Bind(wx.EVT_MENU, ToggleSpotMaskMode, id=G2G.wxID_NEWMASKSPOT)
2131    G2frame.Bind(wx.EVT_MENU, OnNewArcMask, id=G2G.wxID_NEWMASKARC)
2132    G2frame.Bind(wx.EVT_MENU, OnNewRingMask, id=G2G.wxID_NEWMASKRING)
2133    G2frame.Bind(wx.EVT_MENU, OnNewXlineMask, id=G2G.wxID_NEWMASKXLINE)
2134    G2frame.Bind(wx.EVT_MENU, OnNewYlineMask, id=G2G.wxID_NEWMASKYLINE)
2135    G2frame.Bind(wx.EVT_MENU, OnNewPolyMask, id=G2G.wxID_NEWMASKPOLY)
2136    G2frame.Bind(wx.EVT_MENU, OnNewFrameMask, id=G2G.wxID_NEWMASKFRAME)
2137    if G2frame.MaskKey == 'f':
2138        G2frame.GetStatusBar().SetStatusText('Frame mask active - LB pick next point, RB close polygon',1)
2139    elif G2frame.MaskKey == 'p':
2140        G2frame.GetStatusBar().SetStatusText('Polygon mask active - LB pick next point, RB close polygon',1)
2141    elif G2frame.MaskKey == 'a':
2142        G2frame.GetStatusBar().SetStatusText('Arc mask active - LB pick arc location',1)
2143    elif G2frame.MaskKey == 'r':
2144        G2frame.GetStatusBar().SetStatusText('Ring mask active - LB pick ring location',1)
2145    elif G2frame.MaskKey == 'x':
2146        G2frame.GetStatusBar().SetStatusText('X-line mask active - LB pick x line of pixels',1)
2147    elif G2frame.MaskKey == 'y':
2148        G2frame.GetStatusBar().SetStatusText('Y-line mask active - LB pick y line of pixels',1)
2149    else:
2150        G2frame.GetStatusBar().SetStatusText("To add mask: press a,r,s,x,y,p or f on 2D image for arc/ring/spot/xline/yline/polygon/frame",1)
2151    mainSizer = G2frame.dataWindow.GetSizer()
2152    topSizer = wx.BoxSizer(wx.HORIZONTAL)
2153    topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Mask Controls:'))
2154    topSizer.Add((-1,-1),1,wx.EXPAND)
2155    topSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
2156    mainSizer.Add(topSizer,0,wx.EXPAND)
2157    mainSizer.Add((5,10),0)
2158
2159    thresh = data['Thresholds']         #min/max intensity range
2160    Spots = data['Points']               #x,y,radius in mm
2161    Rings = data['Rings']               #radius, thickness
2162    Polygons = data['Polygons']         #3+ x,y pairs
2163    if 'Xlines' not in data:            #single rows/columns of bad pixels
2164        data['Xlines'] = []
2165        data['Ylines'] = []
2166    Xlines = data['Xlines']
2167    Ylines = data['Ylines']
2168    # not a good place for patch -- this not always called
2169    if 'Frames' not in data:
2170        data['Frames'] = []
2171    if 'SpotMask' not in data:
2172        data['SpotMask'] = {'esdMul':3.,'spotMask':None}
2173    frame = data['Frames']             #3+ x,y pairs
2174    Arcs = data['Arcs']                 #radius, start/end azimuth, thickness
2175
2176    ######################################################################
2177    data['SpotMask']['FastSearch'] = data['SpotMask'].get('FastSearch',True)
2178    data['SpotMask']['ClearPrev'] = data['SpotMask'].get('ClearPrev',True)
2179    data['SpotMask']['SearchMin'] = data['SpotMask'].get('SearchMin',0.0)
2180    data['SpotMask']['SearchMax'] = data['SpotMask'].get('SearchMax',180.)
2181    CId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image,'Image Controls')
2182    controlData = G2frame.GPXtree.GetItemPyData(CId)
2183    Range = controlData['range']
2184    MaxSizer = MaxSizer()               #keep this so it can be changed in BackSizer   
2185    mainSizer.Add(MaxSizer,0,wx.ALIGN_LEFT|wx.EXPAND|wx.ALL)
2186
2187    littleSizer = wx.FlexGridSizer(0,3,0,5)
2188    littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Lower/Upper limits '),0,WACV)
2189    Text = wx.TextCtrl(G2frame.dataWindow,value=str(thresh[0][0]),style=wx.TE_READONLY)
2190    littleSizer.Add(Text,0,WACV)
2191    Text.SetBackgroundColour(VERY_LIGHT_GREY)
2192    Text = wx.TextCtrl(G2frame.dataWindow,value=str(thresh[0][1]),style=wx.TE_READONLY)
2193    littleSizer.Add(Text,0,WACV)
2194    Text.SetBackgroundColour(VERY_LIGHT_GREY)
2195    littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' Lower/Upper thresholds '),0,WACV)
2196    lowerThreshold = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=thresh[1],key=0,
2197        xmin=thresh[0][0],OnLeave=newReplot,typeHint=int)
2198    littleSizer.Add(lowerThreshold,0,WACV)
2199    upperThreshold = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=thresh[1],key=1,
2200        xmax=thresh[0][1],OnLeave=newReplot,typeHint=int)
2201    littleSizer.Add(upperThreshold,0,WACV)
2202    mainSizer.Add(littleSizer,0,)
2203    G2G.HorizontalLine(mainSizer,G2frame.dataWindow)
2204    spotSizer = wx.BoxSizer(wx.HORIZONTAL)
2205    data['SpotMask']['esdMul'] = float(data['SpotMask']['esdMul'])
2206    spotSizer.Add(wx.StaticText(G2frame.dataWindow,label='Pixel masking: '),0,WACV)
2207    numPix = 0
2208    if data['SpotMask']['spotMask'] is not None:
2209        numPix = np.count_nonzero(data['SpotMask']['spotMask'])
2210    spotSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Number of masked pixels: %d  '%numPix),0,WACV)
2211    delbtn = wx.Button(G2frame.dataWindow,label='Clear pixel mask')
2212    delbtn.Bind(wx.EVT_BUTTON,OnDelPixMask)
2213    spotSizer.Add(delbtn,0,WACV)
2214    mainSizer.Add(spotSizer,0)
2215    spotSizer = wx.BoxSizer(wx.HORIZONTAL)
2216    spotSizer.Add(wx.StaticText(G2frame.dataWindow,label='Select n*sigma rejection (n=1-10): '),0,WACV)
2217    spotSizer.Add(G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=data['SpotMask'],
2218        key='esdMul',xmin=1.,xmax=10.,size=(40,25)),0,WACV)
2219    spotSizer.Add(G2G.G2CheckBoxFrontLbl(G2frame.dataWindow,'Clear previous pixel mask on search',
2220                  data['SpotMask'],'ClearPrev'),0,WACV)
2221    mainSizer.Add(spotSizer,0)
2222    spotSizer = wx.BoxSizer(wx.HORIZONTAL)
2223    if G2img.TestFastPixelMask():
2224        spotSizer.Add(G2G.G2CheckBoxFrontLbl(G2frame.dataWindow,
2225                        'Use fast search',data['SpotMask'],'FastSearch',
2226                        OnChange=lambda x: wx.CallAfter(UpdateMasks,G2frame,data)),0,WACV)
2227    else:
2228        data['SpotMask']['FastSearch'] = False
2229        spotSizer.Add(wx.StaticText(G2frame.dataWindow,
2230                        label='(Fast search not installed) '),0,WACV)
2231    txt = wx.StaticText(G2frame.dataWindow,
2232                            label='  Pixel mask search range, 2theta min: ')
2233    spotSizer.Add(txt,0,WACV)
2234#    if data['SpotMask']['FastSearch']:
2235#        txt.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
2236    txt = G2G.ValidatedTxtCtrl(G2frame.dataWindow,
2237                        loc=data['SpotMask'],
2238                        key='SearchMin',xmin=0.,xmax=180.,size=(40,25))
2239    spotSizer.Add(txt,0,WACV)
2240#    if data['SpotMask']['FastSearch']: txt.Enable(False)
2241    txt = wx.StaticText(G2frame.dataWindow,label='  2theta max: ')
2242    spotSizer.Add(txt,0,WACV)
2243#    if data['SpotMask']['FastSearch']:
2244#        txt.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
2245    txt = G2G.ValidatedTxtCtrl(G2frame.dataWindow,
2246                        loc=data['SpotMask'],
2247                        key='SearchMax',xmin=0.,xmax=180.,size=(40,25))
2248    spotSizer.Add(txt,0,WACV)
2249#    if data['SpotMask']['FastSearch']: txt.Enable(False)
2250    mainSizer.Add(spotSizer,0)
2251    if len(Spots):
2252        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Spot masks (on plot, LB drag to move, shift-LB drag to resize, RB to delete)')
2253        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2254        lbl.SetForegroundColour(wx.Colour(50,50,50))
2255        mainSizer.Add(lbl,0,wx.EXPAND,0)
2256        colTypes = [wg.GRID_VALUE_STRING,wg.GRID_VALUE_FLOAT+':10,2',wg.GRID_VALUE_BOOL]
2257        colIds = ['position, mm','diameter, mm','Delete?']
2258        rowIds = [str(i) for i in range(len(Spots))]
2259        table = [['%.2f,%.2f'%(item[0],item[1]),item[2],False] for item in Spots]
2260        SpotTable = G2G.Table(table,rowLabels=rowIds,colLabels=colIds,types=colTypes)
2261        SpotGrid = G2G.GSGrid(G2frame.dataWindow)
2262        SpotGrid.SetTable(SpotTable,True)
2263        SpotGrid.AutoSizeColumns(True)
2264        SpotGrid.SetColSize(1,80)
2265        for r in range(len(Spots)):
2266            SpotGrid.SetCellStyle(r,0,VERY_LIGHT_GREY,True)
2267        if 'phoenix' in wx.version():
2268            SpotGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnSpotChange)
2269        else:
2270            SpotGrid.Bind(wg.EVT_GRID_CELL_CHANGE, OnSpotChange)
2271        mainSizer.Add(SpotGrid,0,)
2272    if Rings:
2273        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Ring masks')
2274        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2275        lbl.SetForegroundColour(wx.Colour(50,50,50))
2276        mainSizer.Add(lbl,0,wx.EXPAND,0)
2277        littleSizer = wx.FlexGridSizer(0,3,0,5)
2278        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' 2-theta,deg'),0,WACV)
2279        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' thickness, deg'),0,WACV)
2280        littleSizer.Add((5,0),0)
2281        for i in range(len(Rings)):
2282            if Rings[i]:
2283                ringText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%.3f" % (Rings[i][0])),
2284                    style=wx.TE_READONLY)
2285                ringText.SetBackgroundColour(VERY_LIGHT_GREY)
2286                ringText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
2287                littleSizer.Add(ringText,0,WACV)
2288                ringThick = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=Rings[i],key=1,
2289                    xmin=0.001,xmax=1.,OnLeave=Replot,nDig=[8,3])
2290                littleSizer.Add(ringThick,0,WACV)
2291                ringDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2292                    locationcode='Delete+Rings+'+str(i),handler=onDeleteMask)
2293                littleSizer.Add(ringDelete,0,WACV)
2294        mainSizer.Add(littleSizer,0,)
2295    if Arcs:
2296        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Arc masks')
2297        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2298        lbl.SetForegroundColour(wx.Colour(50,50,50))
2299        mainSizer.Add(lbl,0,wx.EXPAND,0)
2300        littleSizer = wx.FlexGridSizer(0,4,0,5)
2301        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' 2-theta,deg'),0,WACV)
2302        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' azimuth, deg'),0,WACV)
2303        littleSizer.Add(wx.StaticText(parent=G2frame.dataWindow,label=' thickness, deg'),0,WACV)
2304        littleSizer.Add((5,0),0)
2305        for i in range(len(Arcs)):
2306            if Arcs[i]:
2307                tth,azimuth,thick = Arcs[i]
2308                arcText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%.3f" % (tth)),
2309                    style=wx.TE_READONLY)
2310                arcText.SetBackgroundColour(VERY_LIGHT_GREY)
2311                arcText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
2312                littleSizer.Add(arcText,0,WACV)
2313                azmText = wx.TextCtrl(parent=G2frame.dataWindow,value=("%d,%d" % (azimuth[0],azimuth[1])),
2314                    style=wx.TE_READONLY)
2315                azmText.SetBackgroundColour(VERY_LIGHT_GREY)
2316                azmText.Bind(wx.EVT_ENTER_WINDOW,OnTextMsg)
2317                littleSizer.Add(azmText,0,WACV)
2318                arcThick = G2G.ValidatedTxtCtrl(G2frame.dataWindow,loc=Arcs[i],key=2,
2319                    xmin=0.001,xmax=20.,OnLeave=Replot,nDig=[8,3])
2320                littleSizer.Add(arcThick,0,WACV)
2321                arcDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2322                    locationcode='Delete+Arcs+'+str(i),handler=onDeleteMask)
2323                littleSizer.Add(arcDelete,0,WACV)
2324        mainSizer.Add(littleSizer,0,)
2325       
2326    if Xlines:
2327        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' X line masks')
2328        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2329        lbl.SetForegroundColour(wx.Colour(50,50,50))
2330        mainSizer.Add(lbl,0,wx.EXPAND,0)
2331        littleSizer = wx.FlexGridSizer(0,2,0,5)
2332        for i in range(len(Xlines)):
2333            if Xlines[i]:
2334                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='at Y-pixel: %d'%(Xlines[i])),0,WACV)
2335                xlineDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2336                    locationcode='Delete+Xlines+'+str(i),handler=onDeleteMask)
2337                littleSizer.Add(xlineDelete,0,WACV)
2338        mainSizer.Add(littleSizer,0,)
2339       
2340    if Ylines:
2341        lbl = wx.StaticText(parent=G2frame.dataWindow,label=' Y line masks')
2342        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2343        lbl.SetForegroundColour(wx.Colour(50,50,50))
2344        mainSizer.Add(lbl,0,wx.EXPAND,0)
2345        littleSizer = wx.FlexGridSizer(0,2,0,5)
2346        for i in range(len(Ylines)):
2347            if Ylines[i]:
2348                littleSizer.Add(wx.StaticText(G2frame.dataWindow,label='at X-pixel: %d'%(Ylines[i])),0,WACV)
2349                ylineDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2350                    locationcode='Delete+Ylines+'+str(i),handler=onDeleteMask)
2351                littleSizer.Add(ylineDelete,0,WACV)
2352        mainSizer.Add(littleSizer,0,)
2353       
2354    if Polygons:
2355        lbl = wx.StaticText(parent=G2frame.dataWindow,
2356            label=' Polygon masks (on plot LB vertex drag to move, RB vertex drag to insert)')
2357        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2358        lbl.SetForegroundColour(wx.Colour(50,50,50))
2359        mainSizer.Add(lbl,0,wx.EXPAND,0)
2360        littleSizer = wx.FlexGridSizer(0,2,0,5)
2361        for i in range(len(Polygons)):
2362            if Polygons[i]:
2363                polyList = []
2364                for x,y in Polygons[i]:
2365                    polyList.append("%.2f, %.2f"%(x,y))
2366                polyText = wx.ComboBox(G2frame.dataWindow,value=polyList[0],choices=polyList,style=wx.CB_READONLY)
2367                littleSizer.Add(polyText,0,WACV)
2368                polyDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2369                    locationcode='Delete+Polygons+'+str(i),handler=onDeleteMask)
2370                littleSizer.Add(polyDelete,0,WACV)
2371        mainSizer.Add(littleSizer,0,)
2372    if frame:
2373        lbl = wx.StaticText(parent=G2frame.dataWindow,
2374            label=' Frame mask (on plot LB vertex drag to move, RB vertex drag to insert)')
2375        lbl.SetBackgroundColour(wx.Colour(200,200,210))
2376        lbl.SetForegroundColour(wx.Colour(50,50,50))
2377        mainSizer.Add(lbl,0,wx.EXPAND,0)
2378        littleSizer = wx.FlexGridSizer(0,2,0,5)
2379        frameList = []
2380        for x,y in frame:
2381            frameList.append("%.2f, %.2f"%(x,y))
2382        frameText = wx.ComboBox(G2frame.dataWindow,value=frameList[0],choices=frameList,style=wx.CB_READONLY)
2383        littleSizer.Add(frameText,0,WACV)
2384        frameDelete = G2G.G2LoggedButton(G2frame.dataWindow,label='delete?',
2385            locationcode='Delete+Frame',handler=onDeleteFrame)
2386        littleSizer.Add(frameDelete,0,WACV)
2387        mainSizer.Add(littleSizer,0,)
2388    G2frame.dataWindow.SetDataSize()
2389    if startScroll: # reset scroll to saved position
2390        G2frame.dataWindow.Scroll(0,startScroll) # set to saved scroll position
2391        wx.Yield()
2392
2393################################################################################
2394##### Stress/Strain
2395################################################################################
2396
2397def UpdateStressStrain(G2frame,data):
2398    '''Shows and handles the controls on the "Stress/Strain"
2399    data tree entry
2400    '''
2401   
2402    def OnAppendDzero(event):
2403        data['d-zero'].append({'Dset':1.0,'Dcalc':0.0,'pixLimit':10,'cutoff':1.0,
2404            'ImxyObs':[[],[]],'ImtaObs':[[],[]],'ImtaCalc':[[],[]],'Emat':[1.0,1.0,1.0],'fixDset':False,'Ivar':0})
2405        UpdateStressStrain(G2frame,data)
2406       
2407    def OnUpdateDzero(event):
2408        for item in data['d-zero']:
2409            if item['Dcalc']:   #skip unrefined ones
2410                item['Dset'] = item['Dcalc']
2411        UpdateStressStrain(G2frame,data)
2412           
2413    def OnCopyStrSta(event):
2414        Names = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
2415        if len(Names) == 1:
2416            G2frame.ErrorDialog('Nothing to copy controls to','There must be more than one "IMG" pattern')
2417            return
2418        Source = G2frame.GPXtree.GetItemText(G2frame.Image)
2419        Names.pop(Names.index(Source))
2420        dlg = G2G.G2MultiChoiceDialog(G2frame,'Copy stress/strain controls','Copy controls from '+Source+' to:',Names)
2421        try:
2422            if dlg.ShowModal() == wx.ID_OK:
2423                items = dlg.GetSelections()
2424                for item in items:
2425                    name = Names[item]
2426                    Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
2427                    CId = G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain')
2428                    oldData = G2frame.GPXtree.GetItemPyData(CId)
2429                    load = oldData.get('Sample load',0.0)
2430                    Data = copy.deepcopy(data)
2431                    Data['Sample load'] = load
2432                    G2frame.GPXtree.SetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain'),Data)
2433        finally:
2434            dlg.Destroy()
2435            G2frame.GPXtree.SelectItem(G2frame.PickId)
2436
2437    def OnLoadStrSta(event):
2438        pth = G2G.GetImportPath(G2frame)
2439        if not pth: pth = '.'
2440        dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 
2441            'image control files (*.strsta)|*.strsta',wx.FD_OPEN)
2442        try:
2443            if dlg.ShowModal() == wx.ID_OK:
2444                filename = dlg.GetPath()
2445                File = open(filename,'r')
2446                S = File.read()
2447                data = eval(S)
2448                Controls = G2frame.GPXtree.GetItemPyData(
2449                    G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2450                G2img.FitStrSta(G2frame.ImageZ,data,Controls)
2451                UpdateStressStrain(G2frame,data)
2452                G2plt.PlotExposedImage(G2frame,event=event)
2453                G2plt.PlotStrain(G2frame,data,newPlot=True)
2454                File.close()
2455        finally:
2456            dlg.Destroy()
2457
2458    def OnSaveStrSta(event):
2459        pth = G2G.GetExportPath(G2frame)
2460        dlg = wx.FileDialog(G2frame, 'Choose stress/strain file', pth, '', 
2461            'image control files (*.strsta)|*.strsta',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2462        try:
2463            if dlg.ShowModal() == wx.ID_OK:
2464                filename = dlg.GetPath()
2465                File = open(filename,'w')
2466                keys = ['Type','Sample phi','Sample z','Sample load']
2467                keys2 = ['Dset','Dcalc','pixLimit','cutoff','Emat','fixDset']
2468                File.write('{\n\t')
2469                for key in keys:
2470                    if key in 'Type':
2471                        File.write("'"+key+"':'"+data[key]+"',")
2472                    else:
2473                        File.write("'"+key+"':"+str(data[key])+',')
2474                File.write('\n\t'+"'d-zero':[\n")
2475                for data2 in data['d-zero']:
2476                    File.write('\t\t{')
2477                    for key in keys2:
2478                        File.write("'"+key+"':"+str(data2[key])+',')
2479                    File.write("'ImxyObs':[[],[]],'ImtaObs':[[],[]],'ImtaCalc':[[],[]]},\n")
2480                File.write('\t]\n}')
2481                File.close()
2482        finally:
2483            dlg.Destroy()
2484           
2485    def OnStrStaSample(event):
2486        filename = ''
2487        pth = G2G.GetImportPath(G2frame)
2488        if not pth: pth = '.'
2489        dlg = wx.FileDialog(G2frame, 'Choose multihistogram metadata text file', pth, '', 
2490            'metadata file (*.*)|*.*',wx.FD_OPEN)
2491        try:
2492            if dlg.ShowModal() == wx.ID_OK:
2493                filename = dlg.GetPath()
2494                File = open(filename,'r')
2495                S = File.readline()
2496                newItems = []
2497                itemNames = []
2498                Comments = []
2499                while S:
2500                    if S[0] == '#':
2501                        Comments.append(S)
2502                        S = File.readline()
2503                        continue
2504                    S = S.replace(',',' ').replace('\t',' ')
2505                    Stuff = S[:-1].split()
2506                    itemNames.append(Stuff[0])
2507                    newItems.append(Stuff[1:])
2508                    S = File.readline()               
2509                File.close()
2510        finally:
2511            dlg.Destroy()
2512        if not filename:
2513            G2frame.ErrorDialog('Nothing to do','No file selected')
2514            return
2515        dataDict = dict(zip(itemNames,newItems))
2516        ifany = False
2517        Names = [' ','Sample phi','Sample z','Sample load']
2518        dlg = G2G.G2ColumnIDDialog( G2frame,' Choose multihistogram metadata columns:',
2519            'Select columns',Comments,Names,np.array(newItems).T)
2520        try:
2521            if dlg.ShowModal() == wx.ID_OK:
2522                colNames,newData = dlg.GetSelection()
2523                dataDict = dict(zip(itemNames,newData.T))
2524                for item in colNames:
2525                    if item != ' ':
2526                        ifany = True
2527        finally:
2528            dlg.Destroy()
2529        if not ifany:
2530            G2frame.ErrorDialog('Nothing to do','No columns identified')
2531            return
2532        histList = []
2533        item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)       
2534        while item:
2535            name = G2frame.GPXtree.GetItemText(item)
2536            if name.startswith('IMG'):
2537                histList.append(name)
2538            item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
2539        colIds = {}
2540        for i,name in enumerate(colNames):
2541            if name != ' ':
2542                colIds[name] = i
2543        for hist in histList:
2544            name = hist.split()[1]  #this is file name
2545            if name in dataDict:
2546                newItems = {}
2547                for item in colIds:
2548                    newItems[item] = float(dataDict[name][colIds[item]])
2549                Id = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,hist)
2550                stsrData = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id,'Stress/Strain'))
2551                stsrData.update(newItems)       
2552        UpdateStressStrain(G2frame,data)       
2553   
2554    def OnPlotStrSta(event):
2555        Controls = G2frame.GPXtree.GetItemPyData(
2556            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2557        RingInt = G2img.IntStrSta(G2frame.ImageZ,data,Controls)
2558        Names = ['d=%.3f'%(ring['Dcalc']) for ring in data['d-zero']]
2559        G2plt.PlotExposedImage(G2frame,event=event)
2560        G2frame.G2plotNB.Delete('Ring Intensities')
2561        G2plt.PlotXY(G2frame,RingInt,labelX='Azimuth',
2562            labelY='MRD',newPlot=True,Title='Ring Intensities',
2563            names=Names,lines=True)
2564       
2565    def OnSaveStrRing(event):
2566        Controls = G2frame.GPXtree.GetItemPyData(
2567            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2568        RingInt = G2img.IntStrSta(G2frame.ImageZ,data,Controls)
2569        Names = ['d=%.3f'%(ring['Dcalc']) for ring in data['d-zero']]
2570        pth = G2G.GetExportPath(G2frame)
2571        dlg = wx.FileDialog(G2frame, 'Choose strain ring intensity file', pth, '', 
2572            'ring intensity file (*.txt)|*.txt',wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
2573        try:
2574            if dlg.ShowModal() == wx.ID_OK:
2575                filename = dlg.GetPath()
2576                File = open(filename,'w')
2577                for i,name in enumerate(Names):
2578                    File.write('%s%s\n'%(' Ring intensity for ',name))
2579                    File.write('%12s %12s\n'%('Azimuth','RMD'))
2580                    for item in RingInt[i].T:
2581                        File.write(' %12.3f %12.3f\n'%(item[0],item[1]))
2582                    File.write('\n')
2583                File.close()
2584        finally:
2585            dlg.Destroy()
2586               
2587               
2588    def OnFitStrSta(event):
2589        Controls = G2frame.GPXtree.GetItemPyData(
2590            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls'))
2591        G2img.FitStrSta(G2frame.ImageZ,data,Controls)
2592        print ('Strain fitting finished')
2593        UpdateStressStrain(G2frame,data)
2594        G2plt.PlotExposedImage(G2frame,event=event)
2595        G2plt.PlotStrain(G2frame,data,newPlot=True)
2596       
2597    def OnFitAllStrSta(event):
2598        choices = G2gd.GetGPXtreeDataNames(G2frame,['IMG ',])
2599        od = {'label_1':'Copy to next','value_1':False,'label_2':'Reverse order','value_2':False}
2600        dlg = G2G.G2MultiChoiceDialog(G2frame,'Stress/Strain fitting','Select images to fit:',choices,extraOpts=od)
2601        names = []
2602        if dlg.ShowModal() == wx.ID_OK:
2603            for sel in dlg.GetSelections():
2604                names.append(choices[sel])
2605            Id =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,'Sequential strain fit results')
2606            if Id:
2607                SeqResult = G2frame.GPXtree.GetItemPyData(Id)
2608            else:
2609                SeqResult = {}
2610                Id = G2frame.GPXtree.AppendItem(parent=G2frame.root,text='Sequential strain fit results')
2611            SeqResult.update({'SeqPseudoVars':{},'SeqParFitEqList':[]})
2612        else:
2613            dlg.Destroy()
2614            return
2615        dlg.Destroy()
2616        if not names:
2617            return
2618        dlg = wx.ProgressDialog('Sequential IMG Strain fit','Data set name = '+names[0],len(names), 
2619            style = wx.PD_ELAPSED_TIME|wx.PD_AUTO_HIDE|wx.PD_REMAINING_TIME|wx.PD_CAN_ABORT)         
2620        wx.BeginBusyCursor()
2621        goodnames = []
2622        if od['value_2']:
2623            names.reverse()
2624        try:
2625            varyList = []
2626            variables = []
2627            for i,name in enumerate(names):
2628                print (' Sequential strain fit for '+name)
2629                dlg.Raise()
2630                GoOn = dlg.Update(i,newmsg='Data set name = '+name)[0]
2631                if not GoOn:
2632                    break
2633                sId =  G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name)
2634                G2frame.Image = sId
2635                Controls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Image Controls'))
2636                StaCtrls = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,sId, 'Stress/Strain'))
2637                if not len(StaCtrls['d-zero']):
2638                    continue
2639                goodnames.append(name)
2640                Npix,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(sId)
2641                image = GetImageZ(G2frame,Controls)
2642                sig = []
2643                if i and od['value_1']:
2644                    for j,ring in enumerate(StaCtrls['d-zero']):
2645                        ring['Emat'] = copy.copy(variables[4*j:4*j+3])
2646                varyList = []
2647                variables = []
2648                #get results from previous & put in StaCtrls
2649                G2img.FitStrSta(image,StaCtrls,Controls)
2650                G2plt.PlotStrain(G2frame,StaCtrls,newPlot=True)
2651                parmDict = {'Sample load':StaCtrls['Sample load'],}
2652                varyNames = ['e11','e12','e22']
2653                Nvar = 5*len(StaCtrls['d-zero'])
2654                coVar = np.zeros((Nvar,Nvar))
2655                for j,item in enumerate(StaCtrls['d-zero']):
2656                    variables += item['Emat']
2657                    sig += item['Esig']
2658                    varylist = ['%d;%s'%(j,Name) for Name in varyNames]
2659                    varyList += varylist
2660                    parmDict.update(dict(zip(varylist,item['Emat'])))
2661                    parmDict['%d;Dcalc'%(j)] = item['Dcalc']
2662                    variables.append(1.e6*(item['Dcalc']/item['Dset']-1.))
2663                    varyList.append('%d;h-mstrain'%(j))
2664                    sig.append(0)
2665                    parmDict['%d;Ivar'%(j)] = item['Ivar']
2666                    variables.append(item['Ivar'])
2667                    varyList.append('%d;Ivar'%(j))
2668                    sig.append(0)
2669                    j4 = j*5
2670                    coVar[j4:j4+3,j4:j4+3] = item['covMat']
2671                SeqResult[name] = {'variables':variables,'varyList':varyList,'sig':sig,'Rvals':[],
2672                    'covMatrix':coVar,'title':name,'parmDict':parmDict}
2673            else:
2674                SeqResult['histNames'] = goodnames
2675                dlg.Destroy()
2676                print (' ***** Sequential strain refinement successful *****')
2677        finally:
2678            wx.EndBusyCursor()   
2679        SeqResult['histNames'] = choices
2680        G2frame.GPXtree.SetItemPyData(Id,SeqResult)
2681        G2frame.GPXtree.SelectItem(Id)
2682        print ('All images fitted')
2683       
2684    def SamSizer():
2685       
2686        def OnStrainType(event):
2687            data['Type'] = strType.GetValue()
2688       
2689        samSizer = wx.FlexGridSizer(0,4,0,5)
2690        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Strain type: '),0,WACV)
2691        strType = wx.ComboBox(G2frame.dataWindow,value=data['Type'],choices=['True','Conventional'],
2692            style=wx.CB_READONLY|wx.CB_DROPDOWN)
2693        strType.SetValue(data['Type'])
2694        strType.Bind(wx.EVT_COMBOBOX, OnStrainType)
2695        samSizer.Add(strType,0,WACV)
2696        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample phi: '),0,WACV)
2697        samPhi = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample phi',nDig=(10,3),typeHint=float,xmin=-360.,xmax=360.)
2698        samSizer.Add(samPhi,0,WACV)
2699        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample delta-z(mm): '),0,WACV)
2700        samZ = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample z',nDig=(10,3),typeHint=float)
2701        samSizer.Add(samZ,0,WACV)
2702        samSizer.Add(wx.StaticText(G2frame.dataWindow,-1,label=' Sample load(MPa): '),0,WACV)
2703        samLoad = G2G.ValidatedTxtCtrl(G2frame.dataWindow,data,'Sample load',
2704                nDig=[8,3],typeHint=float,)
2705        samSizer.Add(samLoad,0,WACV)
2706
2707        return samSizer
2708       
2709    def DzeroSizer():
2710               
2711        def OnStrainChange(event):
2712#            print (event)
2713            r,c = event.GetRow(),event.GetCol()
2714            if c == 0:
2715                data['d-zero'][r]['Dset'] = min(max(float(StrainGrid.GetCellValue(r,c)),0.25),20.)
2716            elif c == 1:
2717                data['d-zero'][r]['fixDset'] = bool(StrainGrid.GetCellValue(r,c))
2718            elif c == 3:
2719                data['d-zero'][r]['cutoff'] = min(max(float(StrainGrid.GetCellValue(r,c)),0.5),20.)
2720            elif c == 4:
2721                data['d-zero'][r]['pixLimit'] = int(StrainGrid.GetCellValue(r,c))
2722            elif c == 8:
2723                del data['d-zero'][r]
2724                StrainTable.DeleteRow(r)
2725                wx.CallAfter(UpdateStressStrain,G2frame,data)
2726            G2plt.PlotExposedImage(G2frame,event=event)
2727            G2plt.PlotStrain(G2frame,data,newPlot=True)
2728           
2729        def OnSetCol(event):
2730            c = event.GetCol()
2731            if c == 1:
2732                StrainGrid.ClearSelection()
2733                StrainGrid.SelectCol(c,True)
2734                choice = ['Y - set all','N - set none',]
2735                dlg = wx.SingleChoiceDialog(G2frame,'Select option for '+StrainGrid.GetColLabelValue(c-1),
2736                    'Strain controls',choice)
2737                dlg.CenterOnParent()
2738                if dlg.ShowModal() == wx.ID_OK:
2739                    sel = dlg.GetSelection()
2740                    if sel == 0:
2741                        for row in range(StrainGrid.GetNumberRows()): data['d-zero'][row]['fixDset']=True
2742                    else:
2743                        for row in range(StrainGrid.GetNumberRows()): data['d-zero'][row]['fixDset']=False
2744                wx.CallAfter(UpdateStressStrain,G2frame,data)
2745           
2746        colTypes = [wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_BOOL,wg.GRID_VALUE_FLOAT+':10,5',wg.GRID_VALUE_FLOAT+':10,1',
2747            wg.GRID_VALUE_CHOICE+':1,2,5,10,15,20',]+3*[wg.GRID_VALUE_FLOAT+':10,2',]+[wg.GRID_VALUE_BOOL,]+2*[wg.GRID_VALUE_FLOAT+':10,2',]
2748        colIds = ['d-zero','Poisson\n mean?','d-zero ave','I/Ib','nPix','e11','e12','e22','Delete?','h-mustrain','Ivar']
2749        rowIds = [str(i) for i in range(len(data['d-zero']))]
2750        table = [[item['Dset'],item.get('fixDset',False),item['Dcalc'],item['cutoff'],item['pixLimit'], 
2751            item['Emat'][0],item['Emat'][1],item['Emat'][2],False,1.e6*(item['Dcalc']/item['Dset']-1.),item['Ivar']] for item in data['d-zero']]
2752        StrainTable = G2G.Table(table,rowLabels=rowIds,colLabels=colIds,types=colTypes)
2753        StrainGrid = G2G.GSGrid(G2frame.dataWindow)
2754        StrainGrid.SetTable(StrainTable,True)
2755        StrainGrid.AutoSizeColumns(True)
2756        for r in range(len(data['d-zero'])):
2757            StrainGrid.SetCellStyle(r,2,VERY_LIGHT_GREY,True)
2758            StrainGrid.SetCellStyle(r,5,VERY_LIGHT_GREY,True)
2759            StrainGrid.SetCellStyle(r,6,VERY_LIGHT_GREY,True)
2760            StrainGrid.SetCellStyle(r,7,VERY_LIGHT_GREY,True)
2761            StrainGrid.SetCellStyle(r,9,VERY_LIGHT_GREY,True)
2762            StrainGrid.SetCellStyle(r,10,VERY_LIGHT_GREY,True)
2763        if 'phoenix' in wx.version():
2764            StrainGrid.Bind(wg.EVT_GRID_CELL_CHANGED, OnStrainChange)
2765        else:
2766            StrainGrid.Bind(wg.EVT_GRID_CELL_CHANGE, OnStrainChange)
2767        StrainGrid.Bind(wg.EVT_GRID_LABEL_LEFT_CLICK,OnSetCol)
2768        return StrainGrid
2769# patches
2770    if 'Sample load' not in data:
2771        data['Sample load'] = 0.0
2772# end patches
2773   
2774    G2frame.dataWindow.ClearData()
2775    G2gd.SetDataMenuBar(G2frame,G2frame.dataWindow.StrStaMenu)
2776    G2frame.Bind(wx.EVT_MENU, OnAppendDzero, id=G2G.wxID_APPENDDZERO)
2777    G2frame.Bind(wx.EVT_MENU, OnUpdateDzero, id=G2G.wxID_UPDATEDZERO)
2778    G2frame.Bind(wx.EVT_MENU, OnFitStrSta, id=G2G.wxID_STRSTAFIT)
2779    G2frame.Bind(wx.EVT_MENU, OnPlotStrSta, id=G2G.wxID_STRSTAPLOT) 
2780    G2frame.Bind(wx.EVT_MENU, OnSaveStrRing, id=G2G.wxID_STRRINGSAVE) 
2781    G2frame.Bind(wx.EVT_MENU, OnFitAllStrSta, id=G2G.wxID_STRSTAALLFIT)
2782    G2frame.Bind(wx.EVT_MENU, OnCopyStrSta, id=G2G.wxID_STRSTACOPY)
2783    G2frame.Bind(wx.EVT_MENU, OnLoadStrSta, id=G2G.wxID_STRSTALOAD)
2784    G2frame.Bind(wx.EVT_MENU, OnSaveStrSta, id=G2G.wxID_STRSTASAVE)
2785    G2frame.Bind(wx.EVT_MENU, OnStrStaSample, id=G2G.wxID_STRSTSAMPLE)       
2786    if G2frame.StrainKey == 'a':    #probably doesn't happen
2787        G2frame.GetStatusBar().SetStatusText('Add strain ring active - LB pick d-zero value',1)
2788    else:
2789        G2frame.GetStatusBar().SetStatusText("To add strain data: On 2D Powder Image, key a:add ring",1)
2790       
2791    mainSizer = G2frame.dataWindow.GetSizer()
2792    topSizer = wx.BoxSizer(wx.HORIZONTAL)
2793    topSizer.Add(wx.StaticText(G2frame.dataWindow,label=' Stress/Strain Controls:'))
2794    topSizer.Add((-1,-1),1,wx.EXPAND)
2795    topSizer.Add(G2G.HelpButton(G2frame.dataWindow,helpIndex=G2frame.dataWindow.helpKey))
2796    mainSizer.Add(topSizer,0,wx.EXPAND)
2797    mainSizer.Add((5,10),0)
2798    mainSizer.Add(SamSizer())
2799    mainSizer.Add((5,10),0)
2800    mainSizer.Add(DzeroSizer())
2801    G2frame.dataWindow.SetDataSize()
2802   
2803###########################################################################
2804# Autointegration
2805###########################################################################
2806def ReadMask(filename):
2807    'Read a mask (.immask) file'
2808    File = open(filename,'r')
2809    save = {}
2810    S = File.readline()
2811    while S:
2812        if S[0] == '#':
2813            S = File.readline()
2814            continue
2815        [key,val] = S.strip().split(':',1)
2816        if key in ['Points','Rings','Arcs','Polygons','Frames','Thresholds']:
2817            save[key] = eval(val)
2818        S = File.readline()
2819    File.close()
2820    CleanupMasks(save)
2821    return save
2822
2823def ReadControls(filename):
2824    'read an image controls (.imctrl) file'
2825    cntlList = ['wavelength','distance','tilt','invert_x','invert_y','type',
2826            'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
2827            'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg',
2828            'PolaVal','SampleAbs','dark image','background image']
2829    File = open(filename,'r')
2830    save = {}
2831    S = File.readline()
2832    while S:
2833        if S[0] == '#':
2834            S = File.readline()
2835            continue
2836        [key,val] = S.strip().split(':',1)
2837        if key in ['type','calibrant','binType','SampleShape',]:    #strings
2838            save[key] = val
2839        elif key in ['rotation']:
2840            save[key] = float(val)
2841        elif key in ['center',]:
2842            if ',' in val:
2843                save[key] = eval(val)
2844            else:
2845                vals = val.strip('[] ').split()
2846                save[key] = [float(vals[0]),float(vals[1])] 
2847        elif key in cntlList:
2848            save[key] = eval(val)
2849        S = File.readline()
2850    File.close()
2851    return save
2852
2853def Read_imctrl(imctrl_file):
2854    '''Read an image control file and record control parms into a dict, with some simple
2855    type conversions
2856    '''
2857    save = {'filename':imctrl_file}
2858    immask_file = os.path.splitext(imctrl_file)[0]+'.immask'
2859    if os.path.exists(immask_file):
2860        save['maskfile'] = immask_file
2861    else:
2862        save['maskfile'] = '(none)'
2863    cntlList = ['wavelength','distance','tilt','invert_x','invert_y','type',
2864                        'fullIntegrate','outChannels','outAzimuths','LRazimuth','IOtth','azmthOff','DetDepth',
2865                        'calibskip','pixLimit','cutoff','calibdmin','Flat Bkg',
2866                        'PolaVal','SampleAbs','dark image','background image','setdist']
2867    File = open(imctrl_file,'r')
2868    fullIntegrate = False
2869    try:
2870        S = File.readline()
2871        while S:
2872            if S[0] == '#':
2873                S = File.readline()
2874                continue
2875            [key,val] = S.strip().split(':',1)
2876            if val.find(':') != -1:
2877                #print 'rejecting ',key,val
2878                S = File.readline()
2879                continue
2880            if key in ['type','calibrant','binType','SampleShape',]:    #strings
2881                save[key] = val
2882            elif key == 'rotation':
2883                save[key] = float(val)
2884            elif key == 'fullIntegrate':
2885                fullIntegrate = eval(val)
2886            elif key == 'LRazimuth':
2887                vals = eval(val)
2888                save['LRazimuth_min'] = float(vals[0])
2889                save['LRazimuth_max'] = float(vals[1])
2890            elif key == 'IOtth':
2891                save['IOtth_min'],save['IOtth_max'] = eval(val)[0:2]
2892            elif key == 'center':
2893                if ',' in val:
2894                    vals = eval(val)
2895                else:
2896                    vals = val.strip('[] ').split()
2897                    vals = [float(vals[0]),float(vals[1])] 
2898                save['center_x'],save['center_y'] = vals[0:2]
2899            elif key in cntlList:
2900                save[key] = eval(val)
2901            S = File.readline()
2902    finally:
2903        File.close()
2904        if fullIntegrate: save['LRazimuth_min'],save['LRazimuth_max'] = 0.,360.
2905    return save
2906   
2907class AutoIntFrame(wx.Frame):
2908    '''Creates a wx.Frame window for the Image AutoIntegration.
2909    The intent is that this will be used as a non-modal dialog window.
2910   
2911    Implements a Start button that morphs into a pause and resume button.
2912    This button starts a processing loop that is repeated every
2913    :meth:`PollTime` seconds.
2914
2915    :param wx.Frame G2frame: main GSAS-II frame
2916    :param float PollTime: frequency in seconds to repeat calling the
2917      processing loop. (Default is 30.0 seconds.)
2918    '''
2919    def __init__(self,G2frame,PollTime=30.0):
2920        def OnStart(event):
2921            '''Called when the start button is pressed. Changes button label
2922            to Pause. When Pause is pressed the label changes to Resume.
2923            When either Start or Resume is pressed, the processing loop
2924            is started. When Pause is pressed, the loop is stopped.
2925            '''
2926            # check inputs for errors before starting
2927            #err = ''
2928            #if not any([self.params[fmt] for fmt in self.fmtlist]):
2929            #    err += '\nPlease select at least one output format\n'
2930            #if err:
2931            #    G2G.G2MessageBox(self,err)
2932            #    return
2933            self.Pause = False
2934            # change button label
2935            if self.btnstart.GetLabel() != 'Pause':
2936                self.btnstart.SetLabel('Pause')
2937                self.Status.SetStatusText('Press Pause to delay integration or Reset to prepare to reintegrate all images')
2938                if self.timer.IsRunning(): self.timer.Stop()
2939                self.PreventReEntryTimer = False
2940                if self.StartLoop():
2941                    G2G.G2MessageBox(self,'Error in setting up integration. See console')
2942                    return
2943                self.OnTimerLoop(None) # run once immediately
2944                if not self.Pause:
2945                    # no pause, so start timer to check for new files
2946                    self.timer.Start(int(1000*PollTime),oneShot=False)
2947                    return
2948            # we will get to this point if Paused
2949            self.OnPause()
2950           
2951        def OnReset(event):
2952            '''Called when Reset button is pressed. This stops the
2953            processing loop and resets the list of integrated files so
2954            all images can be reintegrated.
2955            '''
2956            self.btnstart.SetLabel('Restart')
2957            self.Status.SetStatusText('Press Restart to reload and re-integrate images matching filter')
2958            if self.timer.IsRunning(): self.timer.Stop()
2959            self.Reset = True
2960            self.Pause = True
2961           
2962        def OnQuit(event):
2963            '''Stop the processing loop and close the Frame
2964            '''
2965            if self.timer.IsRunning(): self.timer.Stop() # make sure we stop first
2966            wx.CallAfter(self.Destroy)
2967           
2968        def OnBrowse(event):
2969            '''Responds when the Browse button is pressed to load a file.
2970            The routine determines which button was pressed and gets the
2971            appropriate file type and loads it into the appropriate place
2972            in the dict.
2973            '''
2974            if btn3 == event.GetEventObject():
2975                dlg = wx.DirDialog(
2976                    self, 'Select directory for output files',
2977                    self.params['outdir'],wx.DD_DEFAULT_STYLE)
2978                dlg.CenterOnParent()
2979                try:
2980                    if dlg.ShowModal() == wx.ID_OK:
2981                        self.params['outdir'] = dlg.GetPath()
2982                        fInp3.SetValue(self.params['outdir'])
2983                finally:
2984                    dlg.Destroy()
2985                return
2986            elif btn4 == event.GetEventObject():
2987                msg = ''
2988                pth = G2G.GetExportPath(G2frame)
2989                dlg = wx.FileDialog(
2990                    self, 'Select a PDF parameter file',
2991                    pth, self.params['pdfprm'], 
2992                    "PDF controls file (*.pdfprm)|*.pdfprm",
2993                    wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
2994                dlg.CenterOnParent()
2995                try:
2996                    if dlg.ShowModal() == wx.ID_OK:
2997                        self.params['pdfprm'] = dlg.GetPath()
2998                        fInp4.SetValue(self.params['pdfprm'])
2999                        scanPDFprm()
3000                        msg = self.checkPDFprm(True)
3001                finally:
3002                    dlg.Destroy()
3003                if 'Error' in msg:
3004                    print(msg)
3005                    lbl = 'PDFPRM error'
3006                else:
3007                    msg = 'Information from file {}\n\n{}'.format(self.params['pdfprm'],msg)
3008                    lbl = 'PDFPRM information'
3009                G2G.G2MessageBox(self,msg,lbl)
3010                return
3011               
3012        def OnRadioSelect(event):
3013            '''Respond to a radiobutton selection and when in table
3014            mode, get distance-dependent parameters from user.
3015            '''
3016            self.Evaluator = None
3017            if self.useTable.GetValue():
3018                dlg = None
3019                try:
3020                    dlg = IntegParmTable(self) # create the dialog
3021                    dlg.CenterOnParent()
3022                    if dlg.ShowModal() == wx.ID_OK:
3023                        self.ImgTblParms = dlg.parms
3024                        self.IMfileList = dlg.IMfileList
3025                        self.Evaluator = DefineEvaluator(dlg)
3026                        self.params['Mode'] = 'table'
3027                        self.editTable.Enable(True)
3028                    else:
3029                        self.useActive.SetValue(True)
3030                finally:
3031                    if dlg: dlg.Destroy()
3032            elif self.useActive.GetValue():
3033                self.params['Mode'] = 'active'
3034                self.imageBase = G2frame.Image
3035                self.useActive.SetLabel("Active Image: "+
3036                        G2frame.GPXtree.GetItemText(self.imageBase))
3037                self.editTable.Enable(False)
3038            else:
3039                print('unexpected mode in OnRadioSelect')
3040
3041        def OnEditTable(event):
3042            '''Called to edit the distance-dependent parameter look-up table.
3043            Should be called only when table is defined and active.
3044            '''
3045            dlg = None
3046            try:
3047                dlg = IntegParmTable(self,self.ImgTblParms,self.IMfileList)
3048                dlg.CenterOnParent()
3049                if dlg.ShowModal() == wx.ID_OK:
3050                    self.ImgTblParms = dlg.parms
3051                    self.IMfileList = dlg.IMfileList
3052                    self.Evaluator = DefineEvaluator(dlg)
3053                    self.params['Mode'] = 'table'
3054                    self.editTable.Enable(True)
3055                else:
3056                    self.useActive.SetValue(True)
3057                    self.params['Mode'] = 'active'
3058                    self.imageBase = G2frame.Image
3059                    self.useActive.SetLabel("Active Image: "+
3060                            G2frame.GPXtree.GetItemText(self.imageBase))
3061                    self.editTable.Enable(False)
3062            finally:
3063                if dlg: dlg.Destroy()
3064               
3065        def showPDFctrls(event):
3066            '''Called to show or hide AutoPDF widgets. Note that fInp4 must be included in the
3067            sizer layout with .Show(True) before .Show(False) will work properly.
3068            '''
3069            fInp4.Enable(self.params['ComputePDF'])
3070            fInp4.Show(self.params['ComputePDF'])
3071            fInp4a.Enable(self.params['ComputePDF'])
3072            btn4.Enable(self.params['ComputePDF'])
3073            cOpt.Enable(self.params['ComputePDF'])
3074            if self.params['ComputePDF']:
3075                lbl4.SetForegroundColour("black")
3076                lbl4a.SetForegroundColour("black")
3077            else:
3078                lbl4.SetForegroundColour("gray")
3079                lbl4a.SetForegroundColour("gray")
3080                                   
3081        def scanPDFprm(**kw):
3082            fInp4.invalid = not os.path.exists(fInp4.GetValue())
3083            fInp4._IndicateValidity()
3084           
3085        def OnAutoScale(event):
3086            self.AutoScale = autoscale.GetValue()
3087           
3088        def OnAutoScaleName(event):
3089            self.AutoScaleName = scalename.GetValue()
3090            self.Scale[0] = self.AutoScales[self.AutoScaleName]
3091            scaleval.SetValue(self.Scale[0])
3092               
3093        ##################################################
3094        # beginning of __init__ processing
3095        ##################################################
3096        self.G2frame = G2frame
3097        self.ImgTblParms = None
3098        self.IMfileList = None
3099        self.Evaluator = None
3100        self.params = {}
3101        self.Reset = False
3102        self.Pause = False
3103        self.PreventReEntryShowMatch = False
3104        self.PreventReEntryTimer = False
3105        self.params['IMGfile'] = ''
3106        self.params['MaskFile'] = ''
3107        self.params['IgnoreMask'] = True
3108        self.fmtlist = G2IO.ExportPowderList(G2frame)
3109        self.timer = wx.Timer()
3110        self.timer.Bind(wx.EVT_TIMER,self.OnTimerLoop)
3111        self.imageBase = G2frame.Image
3112        self.params['ComputePDF'] = False
3113        self.params['pdfDmax'] = 0.0
3114        self.params['pdfprm'] = ''
3115        self.params['optPDF'] = True
3116        self.pdfControls = {}
3117        self.AutoScale = False
3118        self.Scale = [1.0,]
3119
3120        G2frame.GPXtree.GetSelection()
3121        size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(self.imageBase) 
3122        self.params['readdir'],fileroot = os.path.split(imagefile)
3123        self.params['filter'] = '*'+os.path.splitext(fileroot)[1]
3124        self.params['outdir'] = os.path.abspath(self.params['readdir'])
3125        Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3126            G2frame,self.imageBase, 'Comments'))
3127        DefaultAutoScaleNames = GSASIIpath.GetConfigValue('Autoscale_ParmNames')
3128        self.AutoScaleName = GSASIIpath.GetConfigValue('DefaultAutoScale')
3129        self.AutoScales = {}
3130        if DefaultAutoScaleNames is not None:
3131            for comment in Comments:
3132                if '=' in comment:
3133                    name,val = comment.split('=',1) 
3134                    if name in DefaultAutoScaleNames:
3135                        try:
3136                            self.AutoScales[name] = float(val)
3137                            if name == self.AutoScaleName:
3138                                self.Scale[0] = float(val)
3139                        except ValueError:
3140                            continue
3141        wx.Frame.__init__(self, G2frame, title='Automatic Integration',
3142                          style=wx.DEFAULT_FRAME_STYLE ^ wx.CLOSE_BOX)
3143        self.Status = self.CreateStatusBar()
3144        self.Status.SetStatusText('Press Start to load and integrate images matching filter')
3145        mnpnl = wx.Panel(self)
3146        mnsizer = wx.BoxSizer(wx.VERTICAL)
3147        # box for integration controls & masks input
3148        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "Integration Control")
3149        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
3150        lblsizr.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Use integration parameters from:'))
3151        self.useActive = wx.RadioButton(mnpnl, wx.ID_ANY, style = wx.RB_GROUP)
3152        self.useActive.Bind(wx.EVT_RADIOBUTTON, OnRadioSelect)
3153        self.useActive.SetLabel("Active Image: "+G2frame.GPXtree.GetItemText(self.imageBase))
3154        lblsizr.Add(self.useActive,0,wx.EXPAND,1)
3155        self.useActive.SetValue(True)
3156        minisizer = wx.BoxSizer(wx.HORIZONTAL)
3157        self.useTable = wx.RadioButton(mnpnl, wx.ID_ANY, "From distance look-up table")
3158        minisizer.Add(self.useTable,0,wx.ALIGN_LEFT|wx.ALL,1)
3159        self.useTable.Bind(wx.EVT_RADIOBUTTON, OnRadioSelect)
3160        self.editTable = wx.Button(mnpnl,  wx.ID_ANY, "Edit table")
3161        minisizer.Add(self.editTable,0,wx.ALIGN_LEFT,10)
3162        self.editTable.Enable(False)
3163        self.editTable.Bind(wx.EVT_BUTTON, OnEditTable)
3164        # bind button and deactivate be default
3165        lblsizr.Add(minisizer)
3166        mnsizer.Add(lblsizr,0,wx.EXPAND,0)
3167
3168        # file filter stuff
3169        sizer = wx.BoxSizer(wx.HORIZONTAL)
3170        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Read images from '))
3171        self.readDir = G2G.ValidatedTxtCtrl(mnpnl,self.params,'readdir',
3172                            OnLeave=self.ShowMatchingFiles,size=(200,-1))
3173        sizer.Add(self.readDir,1,wx.EXPAND,1)
3174        btn3 = wx.Button(mnpnl, wx.ID_ANY, "Browse")
3175        btn3.Bind(wx.EVT_BUTTON, self.SetSourceDir)
3176        sizer.Add(btn3,0,WACV)
3177        mnsizer.Add(sizer,0,wx.EXPAND,0)
3178        # not yet implemented
3179        sizer = wx.BoxSizer(wx.HORIZONTAL)
3180        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Keep read images in tree '))
3181        self.params['keepReadImage'] = True
3182        keepImage = G2G.G2CheckBox(mnpnl,'',self.params,'keepReadImage')
3183        sizer.Add(keepImage)
3184        keepImage.Enable(False)
3185        sizer.Add((-1,-1),1,wx.EXPAND,1)
3186        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'  Image filter'))
3187        flterInp = G2G.ValidatedTxtCtrl(mnpnl,self.params,'filter',
3188                                        OnLeave=self.ShowMatchingFiles)
3189        sizer.Add(flterInp)
3190        mnsizer.Add(sizer,0,wx.EXPAND,0)
3191       
3192        self.ListBox = wx.ListBox(mnpnl,size=(-1,100))
3193        mnsizer.Add(self.ListBox,1,wx.EXPAND,1)
3194        self.ShowMatchingFiles(self.params['filter'])
3195
3196        # box for output selections
3197        lbl = wx.StaticBox(mnpnl, wx.ID_ANY, "Output settings")
3198        lblsizr = wx.StaticBoxSizer(lbl, wx.VERTICAL)
3199        sizer = wx.BoxSizer(wx.HORIZONTAL)
3200        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Write to: '),0,WACV)
3201        fInp3 = G2G.ValidatedTxtCtrl(mnpnl,self.params,'outdir',notBlank=False,size=(300,-1))
3202        sizer.Add(fInp3,1,wx.EXPAND)
3203        btn3 = wx.Button(mnpnl,  wx.ID_ANY, "Browse")
3204        btn3.Bind(wx.EVT_BUTTON, OnBrowse)
3205        sizer.Add(btn3,0,WACV)
3206        lblsizr.Add(sizer,0,wx.EXPAND)
3207        sizer = wx.BoxSizer(wx.HORIZONTAL)
3208        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Select format(s):'))
3209        # format choice selection
3210        usedfmt = []
3211        self.multipleFmtChoices = {}
3212        for dfmt in sorted(self.fmtlist[0]):
3213            sizer.Add((6,2)) # add a bit of extra space
3214            fmt = dfmt[1:]
3215            self.params[fmt] = False
3216            if fmt in usedfmt: # is extension used more than once
3217                self.multipleFmtChoices[fmt] = None
3218            else:
3219                usedfmt.append(fmt)
3220                btn = G2G.G2CheckBox(mnpnl,dfmt,self.params,fmt,
3221                                     OnChange=self.TestInput)
3222                sizer.Add(btn)
3223        lblsizr.Add(sizer)
3224        sizer = wx.BoxSizer(wx.HORIZONTAL)
3225        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Separate dir for each format: '))
3226        self.params['SeparateDir'] = False
3227        sizer.Add(G2G.G2CheckBox(mnpnl,'',self.params,'SeparateDir'))
3228        lblsizr.Add(sizer)
3229        if self.AutoScales:
3230            sizer = wx.BoxSizer(wx.HORIZONTAL)
3231            autoscale = wx.CheckBox(mnpnl,label='Do autoscaling with:')
3232            autoscale.Bind(wx.EVT_CHECKBOX,OnAutoScale)
3233            sizer.Add(autoscale,0,WACV)
3234            scalename = wx.ComboBox(mnpnl,value=self.AutoScaleName,choices=list(self.AutoScales.keys()),
3235                style=wx.CB_READONLY|wx.CB_DROPDOWN)
3236            scalename.Bind(wx.EVT_COMBOBOX,OnAutoScaleName)
3237            sizer.Add(scalename,0,WACV)
3238            sizer.Add(wx.StaticText(mnpnl,label=' to '),0,WACV)
3239            scaleval = G2G.ValidatedTxtCtrl(mnpnl,self.Scale,0,nDig=(10,2),xmin=1.)
3240            sizer.Add(scaleval,0,WACV)
3241            lblsizr.Add(sizer,0)
3242        #ToDO: Autonormalize, parm name?, scaling value?
3243        sizer = wx.BoxSizer(wx.HORIZONTAL)
3244        sizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'Autocompute PDF:'),0,WACV)
3245        sizer.Add(G2G.G2CheckBox(mnpnl,'',self.params,'ComputePDF',OnChange=showPDFctrls))
3246        lbl4a = wx.StaticText(mnpnl, wx.ID_ANY,'Max detector distance: ')
3247        sizer.Add(lbl4a,0,WACV)
3248        fInp4a = G2G.ValidatedTxtCtrl(mnpnl,self.params,'pdfDmax',xmin=0.0)
3249        sizer.Add(fInp4a,0,WACV)
3250        cOpt = G2G.G2CheckBox(mnpnl,'Optimize',self.params,'optPDF')
3251        sizer.Add(cOpt)
3252        lblsizr.Add(sizer,0)
3253        sizer = wx.BoxSizer(wx.HORIZONTAL)
3254        lbl4 = wx.StaticText(mnpnl, wx.ID_ANY,'PDF control: ')
3255        sizer.Add(lbl4,0,WACV)
3256        fInp4 = G2G.ValidatedTxtCtrl(mnpnl,self.params,'pdfprm',notBlank=True,size=(300,-1),
3257                                     OnLeave=scanPDFprm)
3258        sizer.Add(fInp4,1,wx.EXPAND)
3259        btn4 = wx.Button(mnpnl,  wx.ID_ANY, "Browse")
3260        btn4.Bind(wx.EVT_BUTTON, OnBrowse)
3261        sizer.Add(btn4,0,WACV)
3262        lblsizr.Add(sizer,0,wx.EXPAND)
3263        mnsizer.Add(lblsizr,0,wx.EXPAND,1)
3264        # buttons on bottom
3265        mnsizer.Add(wx.StaticText(mnpnl, wx.ID_ANY,'AutoIntegration controls'),0,wx.TOP,5)
3266        sizer = wx.BoxSizer(wx.HORIZONTAL)
3267        sizer.Add((20,-1))
3268        self.btnstart = wx.Button(mnpnl,  wx.ID_ANY, "Start")
3269        self.btnstart.Bind(wx.EVT_BUTTON, OnStart)
3270        sizer.Add(self.btnstart)
3271        self.btnreset = wx.Button(mnpnl,  wx.ID_ANY, "Reset")
3272        self.btnreset.Bind(wx.EVT_BUTTON, OnReset)
3273        sizer.Add(self.btnreset)
3274        sizer.Add((20,-1),wx.EXPAND,1)
3275        self.btnclose = wx.Button(mnpnl,  wx.ID_ANY, "Close")
3276        self.btnclose.Bind(wx.EVT_BUTTON, OnQuit)
3277        sizer.Add(self.btnclose)
3278        sizer.Add((20,-1))
3279        mnsizer.Add(sizer,0,wx.EXPAND|wx.BOTTOM|wx.TOP,5)
3280        # finish up window
3281        mnpnl.SetSizer(mnsizer)
3282        OnRadioSelect(None) # disable widgets
3283        mnsizer.Fit(self)
3284        self.CenterOnParent()
3285        self.Show()
3286        showPDFctrls(None)
3287       
3288    def TestInput(self,event):
3289        for fmt in self.multipleFmtChoices:
3290            if not self.params[fmt]: continue
3291            if self.multipleFmtChoices[fmt]: continue
3292            choices = []
3293            for f,l in zip(self.fmtlist[0],self.fmtlist[1]): 
3294                if f[1:] == fmt: choices.append(l)
3295            if len(choices) < 2:
3296                print('Error: why no choices in TestInput?')
3297                return
3298            # select the format here
3299            dlg = G2G.G2SingleChoiceDialog(self,
3300                        'There is more than one format with a '+
3301                        '.{} output. Choose the one to use'.format(fmt),
3302                        'Choose output format',choices)
3303            dlg.clb.SetSelection(0)  # force a selection
3304            if dlg.ShowModal() == wx.ID_OK and dlg.GetSelection() >= 0:
3305                self.multipleFmtChoices[fmt] = choices[dlg.GetSelection()]
3306                dlg.Destroy()
3307            else:
3308                dlg.Destroy()
3309                return
3310
3311    def checkPDFprm(self,ShowContents=False):
3312        '''Read in the PDF (.pdfprm) parameter file and check for problems.
3313        If ShowContents is True, a formatted text version of some of the file
3314        contents is returned. If errors are found, the return string will contain
3315        the string "Error:" at least once.
3316        '''
3317        self.pdfControls = {}
3318        msg = ''
3319        File = None
3320        try:
3321            File = open(self.params['pdfprm'],'r')
3322            S = File.readline()
3323            while S:
3324                if '#' in S:
3325                    S = File.readline()
3326                    continue
3327                key,val = S.split(':',1)
3328                try:
3329                    self.pdfControls[key] = eval(val)
3330                except:
3331                    self.pdfControls[key] = val
3332                S = File.readline()
3333        except Exception as err:
3334            msg += 'PDF Processing Error: error with open or read of {}'.format(self.params['pdfprm'])
3335            if GSASIIpath.GetConfigValue('debug'):
3336                print('DBG_'+msg)
3337                print('DBG_'+err)
3338            self.pdfControls = {}
3339            return msg
3340        finally:
3341            if File: File.close()
3342        formula = ''
3343        for el in self.pdfControls['ElList']:
3344            if self.pdfControls['ElList'][el]['FormulaNo'] <= 0: continue
3345            if formula: formula += ' '
3346            formula += '{}({:.1f})'.format(el,self.pdfControls['ElList'][el]['FormulaNo'])
3347        if not formula:
3348            msg += 'Error: no chemical formula in file'
3349        for key in ['Sample Bkg.','Container','Container Bkg.']:
3350            if key not in self.pdfControls:
3351                if msg: msg += '\n'
3352                msg += 'Error: missing key in self.pdfControls: '+key
3353                continue
3354        if msg or not ShowContents: return msg  # stop on error
3355        msg += 'Default formula: '+formula+'\n'
3356        for key in ['Sample Bkg.','Container','Container Bkg.']:
3357            name = self.pdfControls[key]['Name']
3358            mult = self.pdfControls[key].get('Mult',0.0)
3359            if not name: continue
3360            msg += '\n{}: {:.2f} * "{}"'.format(key,mult,name)
3361            if not G2gd.GetGPXtreeItemId(self.G2frame,self.G2frame.root,name):
3362                msg += ' *** missing ***'
3363        return msg
3364   
3365    def SetSourceDir(self,event):
3366        '''Use a dialog to get a directory for image files
3367        '''
3368        dlg = wx.DirDialog(self, 'Select directory for image files',
3369                        self.params['readdir'],wx.DD_DEFAULT_STYLE)
3370        dlg.CenterOnParent()
3371        try:
3372            if dlg.ShowModal() == wx.ID_OK:
3373                self.params['readdir'] = dlg.GetPath()
3374            self.readDir.SetValue(self.params['readdir'])
3375            self.ShowMatchingFiles(None)
3376        finally:
3377            dlg.Destroy()
3378        return
3379       
3380    def ShowMatchingFiles(self,value,invalid=False,**kwargs):
3381        '''Find and show images in the tree and the image files matching the image
3382        file directory (self.params['readdir']) and the image file filter
3383        (self.params['filter']) and add this information to the GUI list box
3384        '''
3385        G2frame = self.G2frame
3386        if invalid: return
3387        msg = ''
3388        if self.PreventReEntryShowMatch: return
3389        self.PreventReEntryShowMatch = True
3390        imageFileList = []
3391        for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3392            imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3393            size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3394            if imagefile not in imageFileList: imageFileList.append(imagefile)
3395            if img not in G2frame.IntegratedList:
3396                if msg: msg += '\n'
3397                msg += '  ' + img
3398        if msg: msg = "Loaded images to integrate:\n" + msg + "\n"
3399        msg1 = ""
3400        try:
3401            if os.path.exists(self.params['readdir']): 
3402                imageList = sorted(
3403                    glob.glob(os.path.join(self.params['readdir'],self.params['filter'])))
3404                if not imageList:
3405                    msg1 = 'Warning: No files match search string '+os.path.join(self.params['readdir'],self.params['filter'])
3406                else:
3407                    for fil in imageList:
3408                        if fil not in imageFileList: msg1 += '\n  '+fil
3409                    if msg1:
3410                        msg += 'Files to integrate from '+os.path.join(self.params['readdir'],self.params['filter'])+msg1
3411                    else:
3412                        msg += 'No files found to read in '+self.params['readdir']
3413            else:
3414                msg += 'Warning: does not exist: '+self.params['readdir']
3415        except IndexError:
3416            msg += 'Error searching for files named '+os.path.join(self.params['readdir'],self.params['filter'])
3417        self.ListBox.Clear()
3418        self.ListBox.AppendItems(msg.split('\n'))
3419        self.PreventReEntryShowMatch = False
3420        return
3421       
3422    def OnPause(self):
3423        '''Respond to Pause, changes text on button/Status line, if needed
3424        Stops timer
3425        self.Pause should already be True
3426        '''
3427        if self.timer.IsRunning(): self.timer.Stop()
3428        if self.btnstart.GetLabel() == 'Restart':
3429            return
3430        if self.btnstart.GetLabel() != 'Resume':
3431            print('\nPausing autointegration\n')
3432            self.btnstart.SetLabel('Resume')
3433            self.Status.SetStatusText(
3434                    'Press Resume to continue integration or Reset to prepare to reintegrate all images')
3435        self.Pause = True
3436           
3437    def IntegrateImage(self,img,useTA=None,useMask=None):
3438        '''Integrates a single image. Ids for created PWDR entries (more than one is possible)
3439        are placed in G2frame.IntgOutList
3440        '''
3441        G2frame = self.G2frame
3442        imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3443        G2frame.Image = imgId
3444        G2frame.PickId = G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Image Controls')
3445        # do integration
3446        size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3447        if self.AutoScale:
3448            Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3449                G2frame,imgId, 'Comments'))
3450            for comment in Comments:
3451                if '=' in comment:
3452                    name,val = comment.split('=',1) 
3453                    if name == self.AutoScaleName:
3454                        val = float(val)
3455                        if val > 0.:
3456                            Scale = self.Scale[0]/val
3457                        break
3458        masks = G2frame.GPXtree.GetItemPyData(
3459            G2gd.GetGPXtreeItemId(G2frame,G2frame.Image, 'Masks'))
3460        data = G2frame.GPXtree.GetItemPyData(G2frame.PickId)
3461        # simulate a Image Controls press, since that is where the
3462        # integration is hidden
3463        UpdateImageControls(G2frame,data,masks,useTA=useTA,useMask=useMask,IntegrateOnly=True)
3464        G2frame.IntegratedList.append(img) # note this as integrated
3465        # split name and control number
3466        try:
3467            s = re.split(r'(\d+)\Z',os.path.split(os.path.splitext(imagefile)[0])[1])
3468        except AttributeError: # not sure why, but sometimes imagefile is a list here (should not be)!
3469            s = re.split(r'(\d+)\Z',os.path.split(os.path.splitext(imagefile[0])[0])[1])
3470        namepre = s[0]
3471        if len(s) > 1:
3472            namenum = s[1]
3473        else:
3474            namenum = ''
3475        for Id in G2frame.IntgOutList: # loop over newly created PWDR entry(ies)
3476            # save the created PWDR tree names so that a reset can delete them
3477            G2frame.Image = Id
3478            treename = G2frame.GPXtree.GetItemText(Id)
3479            G2frame.AutointPWDRnames.append(treename)
3480            # write out the images in the selected formats
3481            Sdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(G2frame,Id, 'Sample Parameters'))
3482            if self.AutoScale:
3483                print ('Rescale by %.4f'%(Scale))
3484                y,w = G2frame.GPXtree.GetItemPyData(Id)[1][1:3]
3485                y *= Scale
3486                w /= Scale**2
3487            # determine the name for the current file
3488            fileroot = namepre
3489            if len(G2frame.IntgOutList) > 1:
3490                fileroot += "_AZM"
3491                if 'Azimuth' in Sdata:
3492                    fileroot += str(int(10*Sdata['Azimuth']))
3493                fileroot += "_" 
3494            fileroot += namenum
3495            # loop over selected formats
3496            for fmt in self.params:
3497                if not self.params[fmt]: continue
3498                if '.'+fmt not in self.fmtlist[0]: continue
3499                if self.params['SeparateDir']:
3500                    subdir = fmt
3501                else:
3502                    subdir = ''
3503                hint = ''
3504                if self.multipleFmtChoices.get(fmt):
3505                    hint = self.multipleFmtChoices.get(fmt)
3506                fil = os.path.join(self.params['outdir'],subdir,fileroot)
3507                G2IO.ExportPowder(G2frame,treename,fil+'.x','.'+fmt,hint=hint) # dummy extension (.x) is replaced before write)
3508               
3509    def EnableButtons(self,flag):
3510        '''Relabels and enable/disables the buttons at window bottom when auto-integration is running
3511        '''
3512        # for unclear reasons disabling these buttons causes OnRadioSelect to be invoked
3513        # on windows
3514        if sys.platform != "win32":
3515            for item in (self.btnstart,self.btnreset,self.btnclose): item.Enable(flag)
3516        self.btnstart.SetLabel('Pause')
3517        wx.Yield()
3518               
3519    def ResetFromTable(self,dist):
3520        '''Sets integration parameters based on values from
3521        the lookup table
3522        '''
3523        #dist = self.controlsDict['distance']
3524        interpDict,imgctrl,immask = self.Evaluator(dist) # interpolated calibration values
3525        if GSASIIpath.GetConfigValue('debug'):
3526            print ('DBG_interpolated values: ',interpDict)
3527        self.ImageControls = ReadControls(imgctrl)
3528        self.ImageControls.update(interpDict)
3529        self.ImageControls['showLines'] = True
3530        self.ImageControls['ring'] = []
3531        self.ImageControls['rings'] = []
3532        self.ImageControls['ellipses'] = []
3533        self.ImageControls['setDefault'] = False
3534        for i in 'range','size','GonioAngles':
3535            if i in self.ImageControls:
3536                del self.ImageControls[i]
3537        # load copy of Image Masks
3538        if immask:
3539            self.ImageMasks = ReadMask(immask)
3540            if list(self.ImageMasks['Thresholds'][0]) == self.ImageMasks['Thresholds'][1]:     #avoid copy of unchanged thresholds
3541                del self.ImageMasks['Thresholds']
3542        else:
3543            self.ImageMasks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],
3544                'SpotMask':{'esdMul':3.,'spotMask':None},}
3545       
3546    def StartLoop(self):
3547        '''Prepare to start autointegration timer loop.
3548        Save current Image params for use in future integrations
3549        also label the window so users understand what is being used
3550        '''
3551        print('\nStarting new autointegration\n')
3552        G2frame = self.G2frame
3553        # show current IMG base
3554        if self.params['Mode'] != 'table':
3555            self.useActive.SetLabel("Active Image: "+
3556                                    G2frame.GPXtree.GetItemText(self.imageBase))
3557            # load copy of Image Controls from current image and clean up
3558            # items that should not be copied
3559            self.ImageControls = copy.deepcopy(
3560                G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3561                    G2frame,self.imageBase, 'Image Controls')))
3562            self.ImageControls['showLines'] = True
3563            self.ImageControls['ring'] = []
3564            self.ImageControls['rings'] = []
3565            self.ImageControls['ellipses'] = []
3566            self.ImageControls['setDefault'] = False
3567            del self.ImageControls['range']
3568            del self.ImageControls['size']
3569            del self.ImageControls['GonioAngles']
3570            # load copy of Image Masks, keep thresholds
3571            self.ImageMasks = copy.deepcopy(
3572                G2frame.GPXtree.GetItemPyData(
3573                    G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks')))
3574            self.Thresholds = self.ImageMasks['Thresholds'][:]
3575            if list(self.Thresholds[0]) == self.Thresholds[1]:     #avoid copy of unchanged thresholds
3576                del self.ImageMasks['Thresholds']   
3577        # make sure all output directories exist
3578        if self.params['SeparateDir']:
3579            for dfmt in self.fmtlist[0]:
3580                if not self.params[dfmt[1:]]: continue
3581                dir = os.path.join(self.params['outdir'],dfmt[1:])
3582                if not os.path.exists(dir): os.makedirs(dir)
3583        else:
3584            if not os.path.exists(self.params['outdir']):
3585                os.makedirs(self.params['outdir'])
3586        if self.Reset: # special things to do after Reset has been pressed
3587            self.G2frame.IntegratedList = []
3588           
3589            if self.params['Mode'] != 'table': # reset controls and masks for all IMG items in tree to master
3590                for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3591                    # update controls from master
3592                    controlsDict = G2frame.GPXtree.GetItemPyData(
3593                        G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Image Controls'))
3594                    controlsDict.update(self.ImageControls)
3595                    # update masks from master
3596                    ImageMasks = G2frame.GPXtree.GetItemPyData(
3597                        G2gd.GetGPXtreeItemId(G2frame,self.imageBase, 'Masks'))
3598                    ImageMasks.update(self.ImageMasks)
3599            # delete all PWDR items created after last Start was pressed
3600            idlist = []
3601            item, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
3602            while item:
3603                itemName = G2frame.GPXtree.GetItemText(item)
3604                if itemName in G2frame.AutointPWDRnames:
3605                    idlist.append(item)
3606                item, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
3607            for item in idlist:
3608                G2frame.GPXtree.Delete(item)
3609            wx.Yield()
3610            self.Reset = False
3611        G2frame.AutointPWDRnames = [] # list of created PWDR tree item names
3612        G2frame.AutointPDFnames = [] # list of created PWDR tree item names
3613        # check that AutoPDF input is OK, offer chance to use alternate PWDRs if referenced ones
3614        # are not present
3615        if self.params['ComputePDF']:
3616            msg = self.checkPDFprm()
3617            if 'Error:' in msg:
3618                print(msg)
3619                return True
3620            fileList = []
3621            Id, cookie = G2frame.GPXtree.GetFirstChild(G2frame.root)
3622            while Id:
3623                name = G2frame.GPXtree.GetItemText(Id)
3624                if name.startswith('PWDR '): fileList.append(name)
3625                Id, cookie = G2frame.GPXtree.GetNextChild(G2frame.root, cookie)
3626            if not fileList:
3627                print(msg)
3628                print('No PWDR entries to select')
3629                return True
3630            for key in ['Sample Bkg.','Container','Container Bkg.']:
3631                name = self.pdfControls[key]['Name']
3632                if not name: continue
3633                if not G2gd.GetGPXtreeItemId(G2frame,G2frame.root,name):
3634                    indx = G2G.ItemSelector(fileList, self, header='Select PWDR item',
3635                                    title='Select a PWDR tree item for '+key+'\n(or cancel to quit)')
3636                    if indx is None:
3637                        print('No PWDR entry selected for '+key)
3638                        return True
3639                    self.pdfControls[key]['Name'] = fileList[indx]
3640        return False
3641               
3642    def OnTimerLoop(self,event):
3643        '''A method that is called every :meth:`PollTime` seconds that is
3644        used to check for new files and process them. Integrates new images.
3645        Also optionally sets up and computes PDF.
3646        This is called only after the "Start" button is pressed (then its label reads "Pause").
3647        '''
3648        def AutoIntegrateImage(imgId,useTA=None,useMask=None):
3649            '''Integrates an image that has been read into the data tree and updates the
3650            AutoInt window.
3651            '''
3652            img = G2frame.GPXtree.GetItemText(imgId)
3653            controlsDict = G2frame.GPXtree.GetItemPyData(
3654                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3655            ImageMasks = G2frame.GPXtree.GetItemPyData(
3656                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Masks'))
3657            if self.params['Mode'] == 'table': # look up parameter values from table
3658                useTA = None        #force remake of x,y-->2th,azm map
3659                self.ResetFromTable(controlsDict['setdist'])
3660            # update controls from master
3661            controlsDict.update(self.ImageControls)
3662            # update masks from master w/o Thresholds
3663            ImageMasks.update(self.ImageMasks)
3664            self.EnableButtons(False)
3665            try:
3666                self.IntegrateImage(img,useTA=useTA,useMask=useMask)
3667            finally:
3668                self.EnableButtons(True)
3669            self.G2frame.oldImagefile = '' # mark image as changed; reread as needed
3670            wx.Yield()
3671            self.ShowMatchingFiles(self.params['filter'])
3672            wx.Yield()
3673           
3674        def AutoComputePDF(imgId):
3675            '''Computes a PDF for a PWDR data tree tree item
3676            '''
3677            for pwdr in G2frame.AutointPWDRnames[:]:
3678                if not pwdr.startswith('PWDR '): continue
3679                if pwdr in G2frame.AutointPDFnames: continue
3680                PWid = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,pwdr)
3681                controlsDict = G2frame.GPXtree.GetItemPyData(
3682                    G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3683                if self.params['pdfDmax'] != 0 and controlsDict['distance'] > self.params['pdfDmax']:
3684                    print('Skipping PDF for '+pwdr+' due to detector position')
3685                    continue
3686                # Setup PDF
3687                Data = G2frame.GPXtree.GetItemPyData(PWid)[1]
3688                pwdrMin = np.min(Data[1])
3689                Parms = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3690                    G2frame,PWid,'Instrument Parameters'))[0]
3691                fullLimits = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3692                    G2frame,PWid,'Limits'))[0]
3693                if 'C' in Parms['Type'][0]:
3694                    qMax = tth2q(fullLimits[1],G2mth.getWave(Parms))
3695                else:
3696                    qMax = tof2q(fullLimits[0],Parms['difC'][1])
3697                Qlimits = [0.9*qMax,qMax]
3698
3699                item = pwdr
3700                Comments = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3701                    G2frame,imgId, 'Comments'))
3702                ElList = {}
3703                sumnum = 1.0
3704                for item in Comments:           #grab chemical formula from Comments, if there
3705                    if 'formula' in item[:15].lower():
3706                        formula = item.split('=')[1].split()
3707                        elems = formula[::2]
3708                        nums = formula[1::2]
3709                        formula = zip(elems,nums)
3710                        sumnum = 0.
3711                        for [elem,num] in formula:
3712                            ElData = G2elem.GetElInfo(elem,Parms)
3713                            ElData['FormulaNo'] = float(num)
3714                            sumnum += float(num)
3715                            ElList[elem] = ElData
3716                PDFnames = G2gd.GetGPXtreeDataNames(G2frame,['PDF ',])
3717                PDFid = G2obj.CreatePDFitems(G2frame,pwdr,ElList.copy(),Qlimits,sumnum,pwdrMin,PDFnames)
3718                if not PDFid: continue
3719                PDFdata = G2frame.GPXtree.GetItemPyData(G2gd.GetGPXtreeItemId(
3720                    G2frame,PDFid, 'PDF Controls'))
3721                PDFdata.update(copy.deepcopy(self.pdfControls))
3722                if ElList: PDFdata['ElList'] = ElList # override with formula from comments, if present
3723                PDFdata['Sample']['Name'] = pwdr
3724                # compute PDF
3725                wx.Yield()
3726                G2pdG.computePDF(G2frame,PDFdata)
3727                wx.Yield()
3728                G2frame.PatternId = PDFid
3729                G2plt.PlotISFG(G2frame,PDFdata,newPlot=False,plotType='G(R)')
3730                if self.params['optPDF']:
3731                    G2pdG.OptimizePDF(G2frame,PDFdata,maxCycles=10,)
3732                    wx.Yield()
3733                    G2plt.PlotISFG(G2frame,PDFdata,newPlot=False,plotType='G(R)')
3734                G2frame.AutointPDFnames.append(pwdr)
3735                # save names of PDF entry to be deleted later if needed
3736                G2frame.AutointPWDRnames.append(G2frame.GPXtree.GetItemText(PDFid))
3737           
3738        G2frame = self.G2frame
3739        try:
3740            self.currImageList = sorted(
3741                glob.glob(os.path.join(self.params['readdir'],self.params['filter'])))
3742            self.ShowMatchingFiles(self.params['filter'])
3743        except IndexError:
3744            self.currImageList = []
3745            return
3746
3747        if self.PreventReEntryTimer: return
3748        self.PreventReEntryTimer = True
3749        imageFileList = []
3750        # integrate the images that have already been read in, but
3751        # have not yet been processed           
3752        oldData = {'tilt':0.,'distance':0.,'rotation':0.,'center':[0.,0.],'DetDepth':0.,'azmthOff':0.}
3753        oldMhash = 0
3754        if 'useTA' not in dir(self):    #initial definition; reuse if after Resume
3755            self.useTA = None
3756            self.useMask = None
3757        for img in G2gd.GetGPXtreeDataNames(G2frame,['IMG ']):
3758            imgId = G2gd.GetGPXtreeItemId(G2frame,G2frame.root,img)
3759            size,imagefile,imagetag = G2frame.GPXtree.GetImageLoc(imgId)
3760            # Create a list of image files that have been read in
3761            if imagefile not in imageFileList: imageFileList.append(imagefile)
3762            # skip if already integrated
3763            if img in G2frame.IntegratedList: continue
3764            Data = G2frame.GPXtree.GetItemPyData(
3765                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Image Controls'))
3766            sameTA = True
3767            for item in ['tilt','distance','rotation','center','DetDepth','azmthOff']:
3768                if Data[item] != oldData[item]:
3769                    sameTA = False
3770            if not sameTA:
3771                t0 = time.time()
3772                self.useTA = G2img.MakeUseTA(Data,blkSize)
3773                print(' Use new image controls; xy->th,azm mtime: %.3f'%(time.time()-t0))
3774            Mask = G2frame.GPXtree.GetItemPyData(
3775                G2gd.GetGPXtreeItemId(G2frame,imgId, 'Masks'))
3776            Mhash = copy.deepcopy(Mask)
3777            Mhash.pop('Thresholds')
3778            Mhash = hash(str(Mhash))
3779            if  Mhash != oldMhash:
3780                t0 = time.time()
3781                self.useMask = G2img.MakeUseMask(Data,Mask,blkSize)
3782                print(' Use new mask; make mask time: %.3f'%(time.time()-t0))
3783                oldMhash = Mhash
3784            AutoIntegrateImage(imgId,self.useTA,self.useMask)
3785            oldData = Data
3786            if self.pdfControls: AutoComputePDF(imgId)
3787            self.Pause |= G2frame.PauseIntegration
3788            if self.Pause:
3789                self.OnPause()
3790                self.PreventReEntryTimer = False
3791                self.Raise()
3792                return
3793
3794        # loop over image files matching glob, reading in any new ones
3795        if self.useTA is None or self.useMask is None:
3796            print('Integration will not be fast; there is no beginning image controls')     #TODO: work this out??
3797        for newImage in self.currImageList:
3798            if newImage in imageFileList or self.Pause: continue # already read?
3799            for imgId in G2IO.ReadImages(G2frame,newImage):
3800                AutoIntegrateImage(imgId,self.useTA,self.useMask)
3801                if self.pdfControls: AutoComputePDF(imgId)
3802                self.Pause |= G2frame.PauseIntegration
3803                if self.Pause:
3804                    self.OnPause()
3805                    self.PreventReEntryTimer = False
3806                    self.Raise()
3807                    return
3808        if GSASIIpath.GetConfigValue('debug'):
3809            import datetime
3810            print ("DBG_Timer tick at {:%d %b %Y %H:%M:%S}\n".format(datetime.datetime.now()))
3811        self.PreventReEntryTimer = False
3812        self.Raise()
3813
3814def DefineEvaluator(dlg):
3815    '''Creates a function that provides interpolated values for a given distance value
3816    '''
3817    def Evaluator(dist):
3818        '''Interpolate image parameters for a supplied distance value
3819
3820        :param float dist: distance to use for interpolation
3821        :returns: a list with 3 items:
3822
3823          * a dict with interpolated parameter values,
3824          * the closest imctrl and
3825          * the closest maskfile (or None)
3826        '''           
3827        x = np.array([float(i) for i in parms[0]])
3828        closest = abs(x-dist).argmin()
3829        D = {'setdist':dist}
3830        imctfile = IMfileList[closest]
3831        if parms[-1][closest].lower() != '(none)':
3832            maskfile = parms[-1][closest]
3833        else:
3834            maskfile = None
3835        for c in range(1,cols-1):
3836            lbl = ParmList[c]
3837            if lbl in nonInterpVars:
3838                if lbl in ['outChannels',]:
3839                    D[lbl] = int(float(parms[c][closest]))
3840                else:
3841                    D[lbl] = float(parms[c][closest])
3842            else:
3843                y = np.array([float(i) for i in parms[c]])
3844                D[lbl] = np.interp(dist,x,y)
3845        # full integration when angular range is 0
3846        D['fullIntegrate'] = (D['LRazimuth_min'] == D['LRazimuth_max'])
3847        # conversion for paired values
3848        for a,b in ('center_x','center_y'),('LRazimuth_min','LRazimuth_max'),('IOtth_min','IOtth_max'):
3849            r = a.split('_')[0]
3850            D[r] = [D[a],D[b]]
3851            if r in ['LRazimuth',]:
3852                D[r] = [int(D[a]),int(D[b])]
3853            del D[a]
3854            del D[b]
3855        return D,imctfile,maskfile
3856    # save local copies of values needed in Evaluator
3857    parms = dlg.ReadImageParmTable()
3858    IMfileList = dlg.IMfileList
3859    cols = dlg.list.GetColumnCount()
3860    ParmList = dlg.ParmList
3861    nonInterpVars = dlg.nonInterpVars
3862    return Evaluator
3863
3864class IntegParmTable(wx.Dialog):
3865    '''Creates a dialog window with a table of integration parameters.
3866    :meth:`ShowModal` will return wx.ID_OK if the process has been successful.
3867    In this case, :func:`DefineEvaluator` should be called to obtain a function that
3868    creates a dictionary with interpolated parameter values.
3869    '''
3870    ParmList = ('setdist','distance','center_x','center_y','wavelength','tilt','rotation','DetDepth',
3871            'LRazimuth_min','LRazimuth_max','IOtth_min','IOtth_max','outChannels',
3872            'maskfile',
3873            )
3874    nonInterpVars = ('tilt','rotation','LRazimuth_min','LRazimuth_max','IOtth_min','IOtth_max',
3875                     'outChannels')  # values in this list are taken from nearest rather than interpolated
3876    HeaderList = ('Set Dist','Calib Dist','X cntr','Y cntr','wavelength','tilt','rotation','DetDepth',
3877            'Azimuth min','Azimuth max','2Th min','2Th max','Int. pts',
3878            'Mask File',
3879            )
3880    def __init__(self,parent,parms=None,IMfileList=None,readFileList=None):
3881        self.G2frame = parent.G2frame
3882        dlg = None
3883        pth = ''
3884        wx.Dialog.__init__(self,parent,style=wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE)
3885        if readFileList:
3886            self.parms,self.IMfileList = self.ReadFiles(readFileList)
3887        elif parms:
3888            self.parms = parms # list of values by column
3889            self.IMfileList = IMfileList # list of .imctrl file names for each entry in table
3890        else:
3891            self.parms = [] # list of values by column
3892            self.IMfileList = [] # list of .imctrl file names for each entry in table
3893            files = []
3894            try:
3895                pth = G2G.GetImportPath(self.G2frame)
3896                if not pth: pth = '.'
3897                dlg = wx.FileDialog(parent, 'Read previous table or build new table by selecting image control files', pth,
3898                    style=wx.FD_OPEN| wx.FD_MULTIPLE,
3899                    wildcard='Integration table (*.imtbl)|*.imtbl|image control files (.imctrl)|*.imctrl')
3900                dlg.CenterOnParent()
3901                if dlg.ShowModal() == wx.ID_OK:
3902                    files = dlg.GetPaths()
3903                    self.parms,self.IMfileList = self.ReadFiles(files)
3904            finally:
3905                if dlg: dlg.Destroy()
3906            if not files:
3907                wx.CallAfter(self.EndModal,wx.ID_CANCEL)
3908                return
3909        mainSizer = wx.BoxSizer(wx.VERTICAL)
3910        self.list = ImgIntLstCtrl(self, wx.ID_ANY,style=wx.LC_REPORT| wx.BORDER_SUNKEN)
3911        mainSizer.Add(self.list,1,wx.EXPAND,1)
3912        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
3913        btn = wx.Button(self, wx.ID_OK)
3914        btnsizer.Add(btn)
3915        btn = wx.Button(self, wx.ID_ANY,'Save as file')
3916        btn.Bind(wx.EVT_BUTTON,self._onSave)
3917        btnsizer.Add(btn)
3918        btn = wx.Button(self, wx.ID_CLOSE,'Quit')
3919        btn.Bind(wx.EVT_BUTTON,self._onClose)
3920        btnsizer.Add(btn)
3921        mainSizer.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)   
3922        self.SetSizer(mainSizer)
3923        self.list.FillList(self.parms)
3924       
3925    def ReadFiles(self,files):
3926        '''Reads a list of .imctrl files or a single .imtbl file
3927        '''
3928        tmpDict = {}
3929        if not files: return
3930        # option 1, a dump from a previous save
3931        if os.path.splitext(files[0])[1] == '.imtbl':
3932            fp = open(files[0],'r')
3933            S = fp.readline()
3934            while S:
3935                if S[0] != '#':
3936                    [key,val] = S[:-1].split(':',1)
3937                    tmpDict[key] = eval(val)
3938                S = fp.readline()
3939            fp.close()
3940            # delete entries where files do not exist
3941            m1 = [i for i,f in enumerate(tmpDict['filenames']) if not os.path.exists(f)]
3942            if m1:
3943                print('\nimctrl file not found:')
3944                for i in m1: print('\t#'+str(i)+': '+tmpDict['filenames'][i])
3945            m2 = [i for i,f in enumerate(tmpDict['maskfile']) if not (os.path.exists(f) or f.startswith('('))]
3946            if m2:
3947                print('\nmask file not found')
3948                for i in m2: print('\t#'+str(i)+': '+tmpDict['maskfile'][i])
3949            m3 = [i for i,d in enumerate(tmpDict['distance']) if d < 0]
3950            if m3:
3951                print('\nDropping entries due to negative distance: '+str(m3))
3952            m = sorted(set(m1 + m2 + m3))
3953            m.reverse()
3954            for c in m:
3955                for key in tmpDict:
3956                    del tmpDict[key][c]
3957            fileList = tmpDict.get('filenames','[]')
3958            parms = []
3959            if 'setdist' not in tmpDict:
3960                print(u'Old file, recreate before using: {}'.format(files[0]))
3961                return [[]],[]
3962            for key in self.ParmList:
3963                try:
3964                    float(tmpDict[key][0])
3965                    parms.append([str(G2fil.FormatSigFigs(val1,sigfigs=5)) for val1 in tmpDict[key]])
3966                except ValueError:
3967                    parms.append(tmpDict[key])
3968                except IndexError:
3969                    print('No valid image control entries read')
3970                    wx.CallAfter(self.EndModal,wx.ID_CANCEL)
3971                    return [[]],[]
3972            return parms,fileList
3973        # option 2, read in a list of files
3974        for file in files: # read all files; place in dict by distance
3975            imgDict = Read_imctrl(file)
3976            dist = imgDict.get('setdist',imgDict['distance'])
3977            if dist is None:
3978                print('Skipping old file, redo: {}'.format(file))
3979            tmpDict[dist] = imgDict
3980        parms = [[] for key in self.ParmList]
3981        fileList = []
3982        for d in sorted(tmpDict):
3983            fileList.append(tmpDict[d].get('filename'))
3984            if d is None: continue
3985            if d < 0: continue
3986            for i,key in enumerate(self.ParmList):
3987                val = tmpDict[d].get(key)
3988                try:
3989                    val = str(G2fil.FormatSigFigs(val,sigfigs=5))
3990                except:
3991                    val = str(val)
3992                parms[i].append(val)
3993        return parms,fileList
3994   
3995    def ReadImageParmTable(self):
3996        '''Reads possibly edited values from the ListCtrl table and returns a list
3997        of values for each column.
3998        '''
3999        rows = self.list.GetItemCount()
4000        cols = self.list.GetColumnCount()
4001        parms = []
4002        for c in range(cols):
4003            parms.append([])
4004            for r in range(rows):
4005                parms[c].append(self.list.GetItem(r,c).GetText())
4006        return parms
4007
4008    def _onClose(self,event):
4009        'Called when Cancel button is pressed'
4010        self.EndModal(wx.ID_CANCEL)
4011       
4012    def _onSave(self,event):
4013        'Called when save button is pressed; creates a .imtbl file'
4014        fil = ''
4015        if self.G2frame.GSASprojectfile:
4016            fil = os.path.splitext(self.G2frame.GSASprojectfile)[0]+'.imtbl'
4017        dir,f = os.path.split(fil)
4018        pth = G2G.GetExportPath(self.G2frame)
4019        try:
4020            dlg = wx.FileDialog(self, 'Save table data as',
4021                        defaultDir=pth, defaultFile=f, style=wx.SAVE,
4022                        wildcard='G2 Image Param Table file (*.imtbl)|*.imtbl')
4023            dlg.CenterOnParent()
4024            if dlg.ShowModal() != wx.ID_OK: return
4025            fil = dlg.GetPath()
4026            fil = os.path.splitext(fil)[0]+'.imtbl'
4027        finally:
4028            dlg.Destroy()       
4029        parms = self.ReadImageParmTable()
4030        print('Writing image parameter table as '+fil)
4031        fp = open(fil,'w')
4032        for c in range(len(parms)-1):
4033            lbl = self.ParmList[c]
4034            fp.write(lbl+': '+str([eval(i) for i in parms[c]])+'\n')
4035        lbl = self.ParmList[c+1]
4036        fp.write(lbl+': '+str(parms[c+1])+'\n')
4037        lbl = 'filenames'
4038        fp.write(lbl+': '+str(self.IMfileList)+'\n')
4039        fp.close()
4040
4041class ImgIntLstCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin,listmix.TextEditMixin):
4042    '''Creates a custom ListCtrl for editing Image Integration parameters
4043    '''
4044    def __init__(self, parent, ID, pos=wx.DefaultPosition,size=(1000,200),style=0):
4045        self.parent=parent
4046        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
4047        listmix.ListCtrlAutoWidthMixin.__init__(self)
4048        listmix.TextEditMixin.__init__(self)
4049        self.Bind(wx.EVT_LEFT_DCLICK, self.OnDouble)
4050        #self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
4051       
4052    def FillList(self,parms):
4053        'Places the current parms into the table'
4054        # the use of InsertStringItem and SetStringItem are depricated in 4.0 but
4055        # I am not quite sure how to replace them with InsertItem and SetItem yet.
4056        # Perhaps switch to  ULC.UltimateListCtrl?
4057        #
4058        maxint = 2**31-1
4059        self.ClearAll()
4060        self.rowlen = len(self.parent.ParmList)
4061        for i,lbl in enumerate(self.parent.HeaderList):
4062            self.InsertColumn(i, lbl)
4063        for r,d in enumerate(parms[0]):
4064            if d is None: continue
4065            if d == 'None': continue
4066            if float(d) < 0: continue
4067            index = self.InsertStringItem(maxint, d)
4068            for j in range(1,len(parms)):
4069                self.SetStringItem(index, j, parms[j][r])
4070        for i,lbl in enumerate(self.parent.ParmList):
4071            self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
4072
4073    def OnDouble(self,evt):
4074        'respond to a double-click'
4075        self.CloseEditor()
4076        fil = '(none)'
4077        pth = G2G.GetImportPath(self.parent.G2frame)
4078        if not pth: pth = '.'
4079        try:
4080            dlg = wx.FileDialog(self, 'Select mask or control file to add (Press cancel if none)', pth,
4081                style=wx.FD_OPEN,wildcard='Add GSAS-II mask file (.immask)|*.immask|add image control file (.imctrl)|*.imctrl')
4082            dlg.CenterOnParent()
4083            if dlg.ShowModal() == wx.ID_OK:
4084                fil = dlg.GetPath()
4085        finally:
4086            dlg.Destroy()
4087        if os.path.splitext(fil)[1] != '.imctrl':
4088            self.SetStringItem(self.curRow, self.rowlen-1, fil)
4089            self.SetColumnWidth(self.rowlen-1, wx.LIST_AUTOSIZE)
4090        else:
4091            # insert or overwrite an instrument parameter set
4092            if not os.path.exists(fil):
4093                print('Does not exist: '+fil)
4094                return
4095            imgDict = Read_imctrl(fil)
4096            dist = imgDict['distance']
4097            parms = self.parent.ReadImageParmTable()
4098            x = np.array([float(i) for i in parms[0]])
4099            closest = abs(x-dist).argmin()
4100            closeX = x[closest]
4101            # fix IMfileList
4102            for c,lbl in enumerate(self.parent.ParmList):
4103                try:
4104                    vali = G2fil.FormatSigFigs(float(imgDict[lbl]),sigfigs=5)
4105                except ValueError:
4106                    vali = imgDict[lbl]
4107                if abs(closeX-dist) < 1.: # distance is within 1 mm, replace
4108                    parms[c][closest] = vali
4109                elif dist > closeX: # insert after
4110                    parms[c].insert(closest+1,vali)
4111                else:
4112                    parms[c].insert(closest,vali)
4113            if abs(closeX-dist) < 1.: # distance is within 1 mm, replace
4114                self.parent.IMfileList[closest] = fil
4115            elif dist > closeX: # insert after
4116                self.parent.IMfileList.insert(closest+1,fil)
4117            else:
4118                self.parent.IMfileList.insert(closest,fil)
4119            self.FillList(parms)
4120# Autointegration end
4121###########################################################################
4122
4123def testColumnMetadata(G2frame):
4124    '''Test the column-oriented metadata parsing, as implemented at 1-ID, by showing results
4125    when using a .par and .lbls pair.
4126   
4127     * Select a .par file, if more than one in selected dir.
4128     * Select the .*lbls file, if more than one matching .par file.
4129     * Parse the .lbls file, showing errors if encountered; loop until errors are fixed.
4130     * Search for an image or a line in the .par file and show the results when interpreted
4131     
4132    See :func:`GSASIIfiles.readColMetadata` for more details.
4133    '''
4134    if not GSASIIpath.GetConfigValue('Column_Metadata_directory'):
4135        G2G.G2MessageBox(G2frame,'The configuration option for I-ID Metadata is not set.\n'+
4136                         'Please use the File/Preferences menu to set Column_Metadata_directory',
4137                         'Warning')
4138        return
4139    parFiles = glob.glob(os.path.join(GSASIIpath.GetConfigValue('Column_Metadata_directory'),'*.par'))
4140    if not parFiles: 
4141        G2G.G2MessageBox(G2frame,'No .par files found in directory {}. '
4142                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
4143                         '\nThis is set by config variable Column_Metadata_directory '+
4144                         '(Set in File/Preferences menu).',
4145                         'Warning')
4146        return
4147    parList = []
4148    for parFile in parFiles:
4149        lblList = []
4150        parRoot = os.path.splitext(parFile)[0]
4151        for f in glob.glob(parRoot+'.*lbls'):
4152            if os.path.exists(f): lblList.append(f)
4153        if not len(lblList):
4154            continue
4155        parList.append(parFile)
4156    if len(parList) == 0:
4157        G2G.G2MessageBox(G2frame,'No .lbls or .EXT_lbls file found for .par file(s) in directory {}. '
4158                         .format(GSASIIpath.GetConfigValue('Column_Metadata_directory'))+
4159                         '\nThis is set by config variable Column_Metadata_directory '+
4160                         '(Set in File/Preferences menu).',
4161                         'Warning')
4162        return
4163    elif len(parList) == 1:
4164        parFile = parList[0]
4165    else:
4166        dlg = G2G.G2SingleChoiceDialog(G2frame,
4167                'More than 1 .par file found. (Better if only 1!). Choose the one to test in '+
4168                GSASIIpath.GetConfigValue('Column_Metadata_directory'),
4169                'Choose .par file', [os.path.split(i)[1] for i in parList])
4170        if dlg.ShowModal() == wx.ID_OK:
4171            parFile = parList[dlg.GetSelection()]
4172            dlg.Destroy()
4173        else:
4174            dlg.Destroy()
4175            return
4176    # got .par file; now work on .*lbls file
4177    lblList = []
4178    parRoot = os.path.splitext(parFile)[0]
4179    for f in glob.glob(parRoot+'.*lbls'):
4180        if os.path.exists(f): lblList.append(f)
4181    if not len(