source: trunk/GSASIIimgGUI.py @ 4317

Last change on this file since 4317 was 4317, checked in by vondreele, 3 years ago

make esd multiplier for spot mask search a float
add timing to it

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