source: trunk/GSASIIimgGUI.py @ 4302

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

full implementation of the new spotmask finder.

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