source: trunk/GSASIIimgGUI.py @ 4301

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

initialize SpotMask? for image import

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