orGUI 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. orGUI-1.0.1.dist-info/LICENSE +21 -0
  2. orGUI-1.0.1.dist-info/METADATA +147 -0
  3. orGUI-1.0.1.dist-info/RECORD +83 -0
  4. orGUI-1.0.1.dist-info/WHEEL +5 -0
  5. orGUI-1.0.1.dist-info/entry_points.txt +2 -0
  6. orGUI-1.0.1.dist-info/top_level.txt +1 -0
  7. orgui/__init__.py +36 -0
  8. orgui/app/ArrayTableDialog.py +433 -0
  9. orgui/app/QReflectionSelector.py +538 -0
  10. orgui/app/QScanSelector.py +692 -0
  11. orgui/app/QUBCalculator.py +1210 -0
  12. orgui/app/__init__.py +36 -0
  13. orgui/app/database.py +487 -0
  14. orgui/app/orGUI.py +2613 -0
  15. orgui/app/qutils.py +51 -0
  16. orgui/backend/__init__.py +32 -0
  17. orgui/backend/backends.py +157 -0
  18. orgui/backend/beamline/ID31DiffractLinTilt.py +77 -0
  19. orgui/backend/beamline/P212_tools.py +577 -0
  20. orgui/backend/beamline/__init__.py +36 -0
  21. orgui/backend/beamline/fio_reader.py +110 -0
  22. orgui/backend/beamline/id31_tools.py +651 -0
  23. orgui/backend/scans.py +95 -0
  24. orgui/backend/udefaults.py +163 -0
  25. orgui/backend/universalScanLoader.py +105 -0
  26. orgui/datautils/__init__.py +32 -0
  27. orgui/datautils/util.py +705 -0
  28. orgui/datautils/xrayutils/CTRcalc.py +3022 -0
  29. orgui/datautils/xrayutils/CTRopt.py +623 -0
  30. orgui/datautils/xrayutils/CTRplotutil.py +904 -0
  31. orgui/datautils/xrayutils/DetectorCalibration.py +685 -0
  32. orgui/datautils/xrayutils/HKLVlieg.py +1360 -0
  33. orgui/datautils/xrayutils/ReciprocalNavigation.py +401 -0
  34. orgui/datautils/xrayutils/_CTRcalc_accel.py +181 -0
  35. orgui/datautils/xrayutils/__init__.py +46 -0
  36. orgui/datautils/xrayutils/element_data.py +213 -0
  37. orgui/datautils/xrayutils/test/__init__.py +57 -0
  38. orgui/datautils/xrayutils/test/test_CTRcalc.py +152 -0
  39. orgui/datautils/xrayutils/test/test_DetectorCalibration.py +336 -0
  40. orgui/datautils/xrayutils/test/test_HKLcalc.py +88 -0
  41. orgui/datautils/xrayutils/unitcells/Fe3O4(100).bul +59 -0
  42. orgui/datautils/xrayutils/unitcells/Pt100.bul +7 -0
  43. orgui/datautils/xrayutils/unitcells/Pt100_small.bul +5 -0
  44. orgui/datautils/xrayutils/unitcells/Pt110.bul +5 -0
  45. orgui/datautils/xrayutils/unitcells/Pt111.bul +6 -0
  46. orgui/datautils/xrayutils/unitcells/Pt310.bul +13 -0
  47. orgui/datautils/xrayutils/unitcells/Pt3O4(100).bul +19 -0
  48. orgui/datautils/xrayutils/unitcells/PtO(001).bul +9 -0
  49. orgui/datautils/xrayutils/unitcells/PtO(010).bul +9 -0
  50. orgui/datautils/xrayutils/unitcells/PtO(100).bul +9 -0
  51. orgui/datautils/xrayutils/unitcells/__init__.py +67 -0
  52. orgui/datautils/xrayutils/unitcells/a-PtO2(0001).bul +6 -0
  53. orgui/main.py +101 -0
  54. orgui/resources/__init__.py +40 -0
  55. orgui/resources/icons/alpha.png +0 -0
  56. orgui/resources/icons/alpha.svg +67 -0
  57. orgui/resources/icons/diffractometer_v3.png +0 -0
  58. orgui/resources/icons/disable-image.png +0 -0
  59. orgui/resources/icons/disable-image.svg +68 -0
  60. orgui/resources/icons/document-nx-open.png +0 -0
  61. orgui/resources/icons/document-nx-open.svg +152 -0
  62. orgui/resources/icons/document-nx-save.png +0 -0
  63. orgui/resources/icons/document-nx-save.svg +73 -0
  64. orgui/resources/icons/logo.png +0 -0
  65. orgui/resources/icons/logo.svg +808 -0
  66. orgui/resources/icons/max_image.png +0 -0
  67. orgui/resources/icons/max_image.svg +77 -0
  68. orgui/resources/icons/max_image2.png +0 -0
  69. orgui/resources/icons/max_image2.svg +83 -0
  70. orgui/resources/icons/search-image.png +0 -0
  71. orgui/resources/icons/search-image.svg +94 -0
  72. orgui/resources/icons/search-reflection.png +0 -0
  73. orgui/resources/icons/search-reflection.svg +126 -0
  74. orgui/resources/icons/search.png +0 -0
  75. orgui/resources/icons/search.svg +91 -0
  76. orgui/resources/icons/select-image.png +0 -0
  77. orgui/resources/icons/select-image.svg +60 -0
  78. orgui/resources/icons/set-reflection.png +0 -0
  79. orgui/resources/icons/set-reflection.svg +91 -0
  80. orgui/resources/icons/sum_image.png +0 -0
  81. orgui/resources/icons/sum_image.svg +63 -0
  82. orgui/resources/icons/sum_image2.png +0 -0
  83. orgui/resources/icons/sum_image2.svg +75 -0
@@ -0,0 +1,685 @@
1
+ # -*- coding: utf-8 -*-
2
+ # /*##########################################################################
3
+ #
4
+ # Copyright (c) 2020-2024 Timo Fuchs
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+ # ###########################################################################*/
25
+ __author__ = "Timo Fuchs"
26
+ __copyright__ = "Copyright 2020-2024 Timo Fuchs"
27
+ __license__ = "MIT License"
28
+ __version__ = "1.0.0"
29
+ __maintainer__ = "Timo Fuchs"
30
+ __email__ = "fuchs@physik.uni-kiel.de"
31
+
32
+
33
+ import numpy as np
34
+ import numpy.linalg as LA
35
+ from .. import util
36
+ import math as m
37
+ import scipy.optimize as opt
38
+ import pyFAI
39
+ from pyFAI import geometry
40
+ from pyFAI.utils import binning
41
+ #import pyFAI.azimuthalIntegrator
42
+ from pyFAI.ext import invert_geometry
43
+ import copy
44
+ import warnings
45
+
46
+ from .HKLVlieg import crystalAngles_singleArray, vacAngles_singleArray, primBeamAngles, vliegDiffracAngles
47
+
48
+ def load(ponifile):
49
+ det = Detector2D_SXRD()
50
+ det.load(ponifile)
51
+ return det
52
+
53
+ def loadNXdict(nxdict):
54
+ det = Detector2D_SXRD()
55
+ det.fromNXdict(nxdict)
56
+ return det
57
+
58
+
59
+ """
60
+
61
+ Attention!!!!
62
+
63
+ There is something wierd with pyFAI
64
+
65
+ x,y seem to be inverted
66
+
67
+ """
68
+ class Detector2D_SXRD(geometry.Geometry):
69
+
70
+ def __init__(self,*args,**keyargs):
71
+ super(Detector2D_SXRD,self).__init__(*args,**keyargs)
72
+ self.setAzimuthalReference(0)
73
+ self.setPolarization(0,0)
74
+
75
+ def toNXdict(self):
76
+ """To be used with silx.io.dictdump.dicttonx
77
+ to save and load the data from any nexus file.
78
+
79
+ """
80
+ pyFAI_dict = self.getPyFAI()
81
+
82
+ if hasattr(self, '_roi'):
83
+ pyFAI_dict['poni1'] = self.poni1_max
84
+ pyFAI_dict['poni2'] = self.poni2_max
85
+
86
+ nxdict = {
87
+ "detector_SXRD":
88
+ {
89
+ "config" : {**pyFAI_dict},
90
+ "title" : u"pyFAI detector calibration",
91
+ "@NX_class": u"NXcollection",
92
+ "azimuth" : self._deltaChi,
93
+ "polarization" : self._polFactor,
94
+ "polarization_axis" : self._polAxis,
95
+ "binning" : np.array(self.detector.binning)
96
+ },
97
+ '@NX_class': u'NXcollection',
98
+ "@creator" : "datautils v %s" % __version__
99
+ }
100
+
101
+ if hasattr(self, '_roi'):
102
+ nxdict['detector_SXRD']["roi"] = np.array(self._roi)
103
+ return nxdict
104
+
105
+ def fromNXdict(self, nxdict):
106
+ """Reads detector config from nexus dict written by toNXdict().
107
+ Use silx.io.dictdump.nxtodict to read these nexus files.
108
+
109
+ First searches for a "detector_SXRD" entry in the passed dict, otherwise
110
+ assumes that the config is in the current group.
111
+ """
112
+ if "detector_SXRD" in nxdict:
113
+ detdict = nxdict["detector_SXRD"]
114
+ else:
115
+ detdict = nxdict
116
+ for entry in list(detdict['config']):
117
+ if entry.startswith("@"):
118
+ del detdict['config'][entry]
119
+ detdict['config']['detector'] = str(detdict['config']['detector'])
120
+ detdict['config']['max_shape'] = tuple(detdict['config']['max_shape'])
121
+ self.setPyFAI(**detdict['config'])
122
+ self.setAzimuthalReference(detdict['azimuth'])
123
+ self.setPolarization(detdict['polarization_axis'], detdict['polarization'])
124
+ if 'binning' in detdict:
125
+ self.detector.set_binning(tuple(detdict['binning']))
126
+ if 'roi' in detdict:
127
+ roi = detdict['roi']
128
+ self.set_roi(list(roi[0]), list(roi[1]))
129
+ return self
130
+
131
+
132
+ def copy_bin_roi_applied(self):
133
+ """Returns a new Detector2D_SXRD instance, where the current binning and shape
134
+ have been set to the source image file. This is intended for further binning
135
+ of images which already have been processed with ``pply_bin_roi_to_image` once.
136
+ """
137
+ new_det = Detector2D_SXRD()
138
+ ddict = self.toNXdict()
139
+ new_det.fromNXdict(ddict)
140
+ binns = new_det.detector.binning
141
+ new_det.detector.max_shape = new_det.detector.shape
142
+ new_det.poni1_max = new_det.poni1
143
+ new_det.poni2_max = new_det.poni2
144
+ new_det.detector.pixel1 *= binns[0]
145
+ new_det.detector.pixel2 *= binns[1]
146
+ new_det.detector.set_binning((1,1))
147
+ new_det.set_roi([0,new_det.detector.shape[0]], [0,new_det.detector.shape[1]])
148
+ new_det.reset()
149
+ return new_det
150
+
151
+ def __bin_img(self, dat):
152
+ dim1, dim2 = self.detector.binning
153
+ if dim1 == 1 and dim2 == 1:
154
+ return dat
155
+ t1 = dat.shape[0] % dim1
156
+ t2 = dat.shape[1] % dim2
157
+ dat_binned = binning(dat[:-t1, :-t2], (dim1, dim2))
158
+ return dat_binned
159
+
160
+ def set_roi(self, range1, range2):
161
+ self._roi = range1, range2
162
+ offset1 = range1[0]
163
+ offset2 = range2[0]
164
+ self.roi_range = range1, range2
165
+ if not hasattr(self, 'poni1_max'):
166
+ self.poni1_max = self.poni1
167
+ if not hasattr(self, 'poni2_max'):
168
+ self.poni2_max = self.poni2
169
+ self.poni1 = self.poni1_max - offset1 * self.detector.pixel1
170
+ self.poni2 = self.poni2_max - offset2 * self.detector.pixel2
171
+ binns = self.detector.binning
172
+ self.detector.set_binning((1,1))
173
+ self.detector.set_binning(binns) # reset shape
174
+ self.detector.shape = (range1[1] - range1[0], range2[1] - range2[0])
175
+ self.reset()
176
+
177
+ def apply_bin_roi_to_image(self, img):
178
+ mask = np.isfinite(img).astype(np.int32)
179
+ img_binned = self.__bin_img(np.nan_to_num(img))
180
+ img_cnts = self.__bin_img(mask).astype(np.float64)
181
+ img_cnts[~np.isfinite(img_cnts)] = np.nan
182
+ img_binned *= (np.prod(self.detector.binning) / img_cnts)
183
+ if hasattr(self, 'roi_range'):
184
+ range1, range2 = self.roi_range
185
+ return img_binned[range1[0]: range1[1], range2[0]: range2[1]]
186
+ else:
187
+ return img_binned
188
+
189
+
190
+ def getBornAgain(self):
191
+ """Only for surface facing upwards (along y), i.e. azimuth = 90°.
192
+ """
193
+ ba_config = {}
194
+
195
+ ba_config['NbinsX'] = self.detector.shape[1]
196
+ ba_config['Width'] = (self.detector.shape[1] * self.detector.pixel2) *1e3 # mm
197
+ ba_config['NbinsY'] = self.detector.shape[0]
198
+ ba_config['Height'] = (self.detector.shape[0] * self.detector.pixel1) *1e3 # mm
199
+ ba_config['wavelength'] = self.get_wavelength()*1e9 # nm
200
+ _cal = self.getFit2D()
201
+ ba_config['Distance'] = _cal['directDist'] # mm
202
+ ba_config['v0(vertBeamPos)'] = (self.detector.shape[0] - _cal['centerY']) * self.detector.pixel1 *1e3 # mm
203
+ ba_config['u0(horizontBeamPos)'] = _cal['centerX'] * self.detector.pixel2 *1e3 # mm
204
+ return ba_config
205
+
206
+ #in rad!!!
207
+ def setAzimuthalReference(self,deltaChi):
208
+ self._deltaChi = copy.copy(deltaChi)
209
+ self._cached_array = {}
210
+
211
+ def setPolarization(self,angle,factor):
212
+ self._polAxis = angle
213
+ self._polFactor = factor
214
+ self._cached_array = {}
215
+
216
+ def primBeamAngles(self,shape=None):
217
+ """gives angles in laboratory reference frame.
218
+
219
+ """
220
+ if self._cached_array.get("gamma_p") is None or self._cached_array.get("delta_p") is None:
221
+ azimuth = self.chiArray(shape=shape) + self._deltaChi
222
+ tth = self.array_from_unit(unit=pyFAI.units.TTH_RAD)
223
+ sintth = np.sin(tth); costth = np.cos(tth)
224
+
225
+ delta_p = np.arctan2(sintth*np.sin(azimuth), costth)
226
+ gamma_p = np.arctan2(sintth*np.cos(azimuth)*np.cos(delta_p), costth)
227
+
228
+ self._cached_array['gamma_p'] = gamma_p
229
+ self._cached_array['delta_p'] = delta_p
230
+
231
+ return self._cached_array['gamma_p'], self._cached_array['delta_p']
232
+
233
+ #numpy array!!!
234
+ def primBeamPoints(self,x,y):
235
+ azimuth = self.chi(x,y) + self._deltaChi
236
+ tth = self.tth(x,y)
237
+ sintth = np.sin(tth); costth = np.cos(tth)
238
+ delta_p = np.arctan2(sintth*np.sin(azimuth), costth)
239
+ gamma_p = np.arctan2(sintth*np.cos(azimuth)*np.cos(delta_p), costth)
240
+
241
+ return gamma_p, delta_p
242
+
243
+ def surfaceAngles(self,alpha_i,shape=None):
244
+ """Angles in the reference frame, where the crystal is tilted by alpha_i.
245
+
246
+
247
+ """
248
+ if hasattr(self,'_alpha_i'):
249
+ if self._alpha_i == alpha_i:
250
+ if self._cached_array.get("gamma") is not None and self._cached_array.get("delta") is not None:
251
+ return self._cached_array.get("gamma"), self._cached_array.get("delta")
252
+ self._alpha_i = alpha_i
253
+ gamma_p, delta_p = self.primBeamAngles(shape)
254
+ gamma = np.arcsin( np.cos(alpha_i)*np.sin(gamma_p) - np.sin(alpha_i)*np.cos(delta_p)*np.cos(gamma_p) )
255
+ delta = np.arcsin( (np.sin(delta_p)*np.cos(gamma_p))/np.cos(gamma) ) # evil, revise!!!
256
+ self._cached_array["gamma"] = gamma
257
+ self._cached_array["delta"] = delta
258
+ return self._cached_array.get("gamma"), self._cached_array.get("delta")
259
+
260
+ def crystalAngles(self,alpha_i,refraction_index,shape=None):
261
+ if hasattr(self,'_n_refr'):
262
+ if self._n_refr == refraction_index:
263
+ if self._cached_array.get("gamma_cry") is not None and hasattr(self,'_alpha_i_ref'):
264
+ return self._cached_array.get("gamma_cry"), self._cached_array.get("delta"), self._alpha_i_ref
265
+ self._n_refr = refraction_index
266
+ gamma, delta = self.surfaceAngles(alpha_i,shape)
267
+ self._cached_array["gamma_cry"] = crystalAngles_singleArray(gamma,self._n_refr)
268
+ self._alpha_i_ref = crystalAngles_singleArray(self._alpha_i,self._n_refr)
269
+ return self._cached_array.get("gamma_cry"), self._cached_array.get("delta"), self._alpha_i_ref
270
+
271
+
272
+ def surfaceAnglesPoint(self,x,y,alpha_i):
273
+ gamma_p, delta_p = self.primBeamPoints(x,y)
274
+ gamma = np.arcsin( np.cos(alpha_i)*np.sin(gamma_p) - np.sin(alpha_i)*np.cos(delta_p)*np.cos(gamma_p) )
275
+ delta = np.arcsin( (np.sin(delta_p)*np.cos(gamma_p))/np.cos(gamma) ) # evil, revise!!!
276
+ return gamma, delta
277
+
278
+ def crystalAnglesPoint(self,x,y,alpha_i,refraction_index):
279
+ gamma, delta = self.surfaceAnglesPoint(x,y,alpha_i)
280
+ gamma_cry = crystalAngles_singleArray(gamma,refraction_index)
281
+ alpha_i_cry = crystalAngles_singleArray(alpha_i,refraction_index)
282
+ return gamma_cry, delta, alpha_i_cry
283
+
284
+ def pixelsTthChi(self,tth,chi):
285
+ tth = np.atleast_1d(np.asarray(tth))
286
+ chi = np.atleast_1d(np.asarray(chi))
287
+ shape = tth.shape
288
+ assert tth.shape == chi.shape
289
+
290
+ # from here everything flat
291
+ #tanchi = np.tan(chi.flatten())
292
+ #nu = np.tan(tth.flatten()) * np.sin(chi.flatten())
293
+
294
+ R = self.rotation_matrix() # detector rotation
295
+ #M = np.empty((np.prod(shape), 2, 2))
296
+ #A = np.empty((np.prod(shape), 2))
297
+ chi = chi.flatten()
298
+ tth = tth.flatten()
299
+
300
+ sinchi = np.sin(chi)
301
+ coschi = np.cos(chi)
302
+
303
+ sintth = np.sin(tth)
304
+ costth = np.cos(tth)
305
+
306
+ nu = sintth * sinchi
307
+
308
+ R = self.rotation_matrix() # detector rotation
309
+
310
+ a = R[0,0] * coschi - R[1,0] * sinchi
311
+ b = R[0,1] * coschi - R[1,1] * sinchi
312
+ c = R[0,0] * costth - R[2,0] * nu
313
+ d = R[0,1] * costth - R[2,1] * nu
314
+
315
+ A1 = (R[1,2] * sinchi - R[0,2] * coschi) * self.dist
316
+ A2 = (R[2,2] * nu - R[0,2] * costth) * self.dist
317
+
318
+ """
319
+ The rest is a fast way to solve
320
+ M @ ptilde = A
321
+ with
322
+
323
+ M = np.empty((np.prod(shape), 2, 2))
324
+ A = np.empty((np.prod(shape), 2))
325
+
326
+ A[:, 0] = A1
327
+ A[:, 1] = A2
328
+
329
+ M[:, 0, 0] = a
330
+ M[:, 0, 1] = b
331
+ M[:, 1, 0] = c
332
+ M[:, 1, 1] = d
333
+
334
+ linalg.solve doesn't work since singular matrices are present
335
+
336
+ linalg.lstsq - solution: extremely slow!!!
337
+ (> 10 s for Pilatus 2M pixels ~1600*1400 )
338
+
339
+ ptilde = np.empty((np.prod(shape), 2))
340
+ for i in range(a.size):
341
+ ptilde[i] = LA.lstsq(M[i],A[i], rcond=None)[0]
342
+
343
+ # Direct SVD:
344
+ (about 10 s for Pilatus 2M pixels ~1600*1400)
345
+ M_pinv = LA.pinv(M)
346
+ ptilde_svd = np.empty((np.prod(shape), 2))
347
+ for i in range(a.size):
348
+ ptilde_svd[i] = M_pinv[i] @ A[i]
349
+
350
+ so have to do it explicitly and find failing solutions manually:
351
+ (about 1 s for Pilatus 2M pixels ~1600*1400)
352
+ """
353
+ determ = b*c - a*d
354
+
355
+ ptilde1 = (b*A2 - d*A1) / determ # this raises divide by 0 warnings, not sure how to suppress them
356
+ ptilde2 = (c*A1 - a*A2) / determ # this raises divide by 0 warnings, not sure how to suppress them
357
+
358
+ mask = np.isclose(chi, 0., atol=1e-12) # solution fails at chi = 0
359
+ # Special case chi = 0
360
+ if mask.any():
361
+ A = np.zeros((3,np.count_nonzero(mask)))
362
+ A[1,:] = sintth[mask]
363
+ A[2,:] = costth[mask]
364
+
365
+ X = R.T @ A
366
+
367
+ s = X[2] / self.dist
368
+
369
+ ptilde1[mask] = X[0] / s
370
+ ptilde2[mask] = X[1] / s
371
+
372
+
373
+ p = (np.column_stack((ptilde1,ptilde2)) + np.array([self.poni1, self.poni2])) / np.array([self.pixel1, self.pixel2])
374
+
375
+ return p.reshape((*shape, 2))
376
+
377
+
378
+
379
+ @property
380
+ def rangegamdel_p(self):
381
+ """Is not identical to rangegamdel_p_full_det, where each pixel is calculated, but close within 1e-5
382
+
383
+ Not sure why...
384
+ """
385
+ xx = np.array([0.,self.detector.shape[0],0,self.detector.shape[0]]) #+ 0.5 # corner pixel center coordinates
386
+ yy = np.array([0.,0., self.detector.shape[1],self.detector.shape[1]]) #+ 0.5
387
+ gamma_p, delta_p = self.primBeamPoints(xx,yy)
388
+ delrange = np.amin(delta_p), np.amax(delta_p)
389
+ gamrange = np.amin(gamma_p), np.amax(gamma_p)
390
+ return gamrange, delrange
391
+
392
+ @property
393
+ def Qmax(self):
394
+ """in A-1
395
+ """
396
+ xx = np.array([0.,self.detector.shape[0],0,self.detector.shape[0]]) #+ 0.5 # corner pixel center coordinates
397
+ yy = np.array([0.,0., self.detector.shape[1],self.detector.shape[1]]) #+ 0.5
398
+ Q = self.qFunction(xx,yy) / 10.
399
+ return np.amax(Q)
400
+
401
+ @property
402
+ def Qmin(self):
403
+ """in A-1
404
+ """
405
+ qmin, qmax = self.Qrange
406
+ return qmin
407
+
408
+ @property
409
+ def Qrange(self):
410
+ """in A-1
411
+ """
412
+ xx = np.array([0.,self.detector.shape[0],0,self.detector.shape[0]]) #+ 0.5 # corner pixel center coordinates
413
+ yy = np.array([0.,0., self.detector.shape[1],self.detector.shape[1]]) #+ 0.5
414
+ Q = self.qFunction(xx,yy) / 10.
415
+ f2d_cal = self.getFit2D()
416
+ # beam on detector ?
417
+ if 0 <= f2d_cal['centerX'] <= self.detector.shape[1] and 0 <= f2d_cal['centerY'] <= self.detector.shape[0]:
418
+ return 0., np.amax(Q)
419
+ else: # fallback to numerical search
420
+ edge_1_x = np.arange(self.detector.shape[0])
421
+ edge_1_y = np.zeros(self.detector.shape[0])
422
+ edge_2_x = edge_1_x
423
+ edge_2_y = np.full(self.detector.shape[0], self.detector.shape[1])
424
+ edge_3_x = np.arange(self.detector.shape[1])
425
+ edge_3_y = np.zeros(self.detector.shape[1])
426
+ edge_4_x = edge_3_x
427
+ edge_4_y = np.full(self.detector.shape[1], self.detector.shape[0])
428
+
429
+ xx = np.concatenate([edge_1_x, edge_2_x, edge_3_x, edge_4_x])
430
+ yy = np.concatenate([edge_1_y, edge_2_y, edge_3_y, edge_4_y])
431
+ Q = self.qFunction(xx,yy) / 10.
432
+ return np.amin(Q), np.amax(Q)
433
+
434
+ def rangegamdel(self, alpha_i):
435
+ """Is not identical to _rangegamdel_full_det, where each pixel is calculated, but close within 1e-5
436
+
437
+ Not sure why...
438
+ """
439
+ xx = np.array([0.,self.detector.shape[0],0,self.detector.shape[0]]) #+ 0.5 # corner pixel center coordinates
440
+ yy = np.array([0.,0., self.detector.shape[1],self.detector.shape[1]]) #+ 0.5
441
+ gamma, delta = self.surfaceAnglesPoint(xx,yy,alpha_i)
442
+ delrange = np.amin(delta), np.amax(delta)
443
+ gamrange = np.amin(gamma), np.amax(gamma)
444
+ return gamrange, delrange
445
+
446
+
447
+ @property
448
+ def _rangegamdel_p_full_det(self):
449
+ gamma_p, delta_p = self.primBeamAngles()
450
+ delrange = np.amin(delta_p), np.amax(delta_p)
451
+ gamrange = np.amin(gamma_p), np.amax(gamma_p)
452
+ return gamrange, delrange
453
+
454
+ def _rangegamdel_full_det(self, alpha_i):
455
+ gamma_p, delta_p = self.surfaceAngles(alpha_i)
456
+ delrange = np.amin(delta_p), np.amax(delta_p)
457
+ gamrange = np.amin(gamma_p), np.amax(gamma_p)
458
+ return gamrange, delrange
459
+
460
+
461
+
462
+ def pixelsPrimeBeam(self,gamma_p,delta_p):
463
+ gamma_p = np.atleast_1d(np.asarray(gamma_p))
464
+ delta_p = np.atleast_1d(np.asarray(delta_p))
465
+ shape = gamma_p.shape
466
+ assert gamma_p.shape == delta_p.shape
467
+
468
+ singam = np.sin(gamma_p); cosgam = np.cos(gamma_p)
469
+
470
+ azimuth = np.arctan2(np.sin(delta_p) * cosgam, singam )
471
+ tth = np.arctan2(singam, np.cos(delta_p) * cosgam * np.cos(azimuth))
472
+
473
+ chi = azimuth - self._deltaChi
474
+
475
+ return self.pixelsTthChi(tth, chi)
476
+
477
+ def pixelsSurfaceAngles(self,gamma,delta,alpha_i):
478
+ gamma_p = np.arcsin( np.cos(alpha_i)*np.sin(gamma) + np.sin(alpha_i)*np.cos(delta)*np.cos(gamma) )
479
+ delta_p = np.arcsin( (np.sin(delta)*np.cos(gamma))/np.cos(gamma_p) )
480
+ return self.pixelsPrimeBeam(gamma_p,delta_p)
481
+
482
+ def pixelsCrystalAngles(self,gamma_cry,delta,alpha_i_cry, refraction_index):
483
+ gamma = vacAngles_singleArray(gamma_cry,refraction_index)
484
+ alpha_i = vacAngles_singleArray(alpha_i_cry,refraction_index)
485
+ return self.pixelsSurfaceAngles(gamma,delta,alpha_i)
486
+
487
+
488
+
489
+ # for now numerical solution, only iterables!
490
+ # in rad
491
+ def pixelsPrimPoint(self,gamma_p,delta_p,shape=None):
492
+ warnings.warn("pixelsPrimPoint is deprecated and will be removed in the future. Use pixelsPrimeBeam instead.", FutureWarning)
493
+ """
494
+ gamma_p_det, delta_p_det = self.primBeamAngles(shape)
495
+ ig = invert_geometry.InvertGeometry(gamma_p_det,delta_p_det)
496
+ xy = []
497
+ for gam_p, del_p in zip(gamma_p,delta_p):
498
+ xy.append(ig(gam_p,del_p))
499
+ return xy
500
+ """
501
+ return self.pixelsPrimeBeam(np.asarray(gamma_p),np.asarray(delta_p))
502
+
503
+
504
+ def pixelsSurfacePoint(self,gamma,delta,alpha_i,shape=None):
505
+ warnings.warn("pixelsSurfacePoint is deprecated and will be removed in the future. Use pixelsSurfaceAngles instead.", FutureWarning)
506
+ """
507
+ gamma_det, delta_det = self.surfaceAngles(alpha_i,shape)
508
+ ig = invert_geometry.InvertGeometry(gamma_det,delta_det)
509
+ xy = []
510
+ for gam, delt in zip(gamma,delta):
511
+ xy.append(ig(gam,delt))
512
+ return xy
513
+ """
514
+ return self.pixelsSurfaceAngles(np.asarray(gamma),np.asarray(delta),np.asarray(alpha_i))
515
+
516
+ def correctionArray(self,shape=None):
517
+ if shape is None:
518
+ shape = self.get_shape()
519
+ if self._cached_array.get("corrarr") is not None:
520
+ return self._cached_array.get("corrarr")
521
+ else:
522
+ self._cached_array["corrarr"] = self.solidAngleArray(shape)*self.polarization(shape=shape,factor=self._polFactor,axis_offset=self._polAxis)
523
+ return self._cached_array.get("corrarr")
524
+
525
+
526
+ class DetectorCalibration():
527
+
528
+ def __init__(self,E,calibrationCrystal,pixelsize):
529
+ self._energy = E
530
+ self._crystal = calibrationCrystal
531
+ self._pixelsize = pixelsize
532
+
533
+ def setE(self,E):
534
+ self._energy = E
535
+
536
+ # shapes: hkl = [[h1,k1,l1], [h2,k2,l2], ...] , xy = [[x1,y1], [x2,y2], ...]
537
+ def calibrateFromReflections(self,hkl,xy):
538
+ x_centralPixel = 730.
539
+ xy = xy.T
540
+ xyz = np.zeros((3,xy.shape[1]))
541
+ xyz[:2] = xy
542
+
543
+ twoTheta = self._crystal.get2ThetaFromHKL(hkl,self._energy)
544
+
545
+ """
546
+ p[0]: th_x, p[1]: th_y, p[2]: d_detector, p[3]: y_centralPixel
547
+ """
548
+ def Chi2(p):
549
+ centralPixel = np.array([x_centralPixel,p[3],0.])
550
+ _xyz = np.matrix((xyz.T - centralPixel).T)
551
+ # uncomment to enable detector tilt correction:
552
+ #_xyz = util.x_rotation(p[0])*util.y_rotation(p[1])*_xyz
553
+ _xyz[2] = 0
554
+ d = LA.norm(_xyz,axis=0)*self._pixelsize
555
+ print(list((twoTheta - np.arctan(d/p[2])) / twoTheta ))
556
+ return LA.norm((twoTheta - np.arctan(d/p[2]))/twoTheta)
557
+
558
+ fitbounds = [(-0.1,0.1),(-0.1,0.1),(0.5,1.),(1400,1430)]
559
+ res = opt.minimize(Chi2,np.array([0.0,0.0,0.72,1415.]),bounds=fitbounds,method='TNC')
560
+ print(res)
561
+ res = opt.minimize(Chi2,res.x)
562
+ print(res)
563
+ self.setCalibration([x_centralPixel,res.x[3]],res.x[2],[res.x[0],res.x[1]])
564
+ self._rotz = None
565
+
566
+ def setCalibration(self,centralPixel,distance,rotz=None):
567
+ self._centralPixel = centralPixel
568
+ self._distance = distance
569
+ self._rotz = rotz
570
+
571
+ def xyToDelGam(self,x,y):
572
+ x = (x - self._centralPixel[0])*self._pixelsize
573
+ y = (y - self._centralPixel[1])*-self._pixelsize
574
+ #print(x)
575
+ xy = np.vstack([x,y])
576
+ if self._rotz is not None:
577
+ mat = np.matrix([[np.cos(self._rotz),-np.sin(self._rotz)],
578
+ [np.sin(self._rotz),np.cos(self._rotz)]])
579
+ #print(xy)
580
+ xy = mat*xy
581
+ xy = xy.A
582
+ #print(xy)
583
+ #raise NotImplementedError("Detector tilt is not implemented")
584
+
585
+ delta = np.arctan(xy[0]/self._distance)
586
+ gamma = np.arctan(xy[1]/self._distance)
587
+
588
+ #print(delta)
589
+ return delta , gamma
590
+
591
+ def delGamToxy(self,delta,gamma):
592
+ x = np.tan(delta)*self._distance
593
+ y = np.tan(gamma)*self._distance
594
+
595
+ x = (x/self._pixelsize) + self._centralPixel[0]
596
+ y = -(y/self._pixelsize) + self._centralPixel[1]
597
+
598
+ return [x,y]
599
+
600
+ def xyToDelGam_corr(self,x,y):
601
+ x = (x - self._centralPixel[0])*self._pixelsize
602
+ y = (y - self._centralPixel[1])*-self._pixelsize
603
+ #print(x)
604
+ #xy = np.vstack([x,y])
605
+ """
606
+ if self._rotz is not None:
607
+ mat = np.matrix([[np.cos(self._rotz),-np.sin(self._rotz)],
608
+ [np.sin(self._rotz),np.cos(self._rotz)]])
609
+ #print(xy)
610
+ xy = mat*xy
611
+ xy = xy.A
612
+ #print(xy)
613
+ #raise NotImplementedError("Detector tilt is not implemented")
614
+ """
615
+ delta = np.arctan(x/self._distance)
616
+ gamma = np.arctan(y/self._distance)
617
+
618
+ deldelta = np.arctan((x+self._pixelsize)*(1/self._distance)) - delta
619
+ delgamma = np.arctan((y+self._pixelsize)*(1/self._distance)) - gamma
620
+
621
+ dd,dg = np.meshgrid(deldelta,delgamma)
622
+
623
+ correction = dd*dg
624
+ correction /= np.amax(correction)
625
+ #print(delta)
626
+ return delta , gamma, correction
627
+
628
+ def xyToDelGam_grid(self,x,y):
629
+ x = (np.array(x,dtype=np.float64) - self._centralPixel[0])*self._pixelsize
630
+ y = (np.array(y,dtype=np.float64) - self._centralPixel[1])*-self._pixelsize
631
+ #print(x)
632
+
633
+ xx, yy = np.meshgrid(x,y)
634
+
635
+ if self._rotz is not None:
636
+ xx = np.cos(self._rotz)*xx - np.sin(self._rotz)*yy
637
+ yy = np.sin(self._rotz)*xx + np.cos(self._rotz)*yy
638
+
639
+
640
+
641
+ delta = np.arctan(xx*(1/self._distance))
642
+ gamma = np.arctan(yy*(1/self._distance))
643
+
644
+ deldelta = np.arctan((xx+self._pixelsize)*(1/self._distance)) - delta
645
+ delgamma = np.arctan((yy+self._pixelsize)*(1/self._distance)) - gamma
646
+
647
+ correction = deldelta*delgamma
648
+ correction /= np.amax(correction)
649
+
650
+ return delta , gamma, correction
651
+
652
+ # **** Depricated ****
653
+ # Doesn't work good with just 4 points, but nice idea
654
+ # P1 and P2 are one pair, P3, P4 the other, lower theta first
655
+ def powderCalibration(self,P1,P2,P3,P4):
656
+ hkl1 , pos1 = P1
657
+ hkl2 , pos2 = P2
658
+ hkl3 , pos3 = P3
659
+ hkl4 , pos4 = P4
660
+
661
+ twoTheta1 = self._crystal.get2ThetaFromHKL(hkl1,self._energy)
662
+ twoTheta2 = self._crystal.get2ThetaFromHKL(hkl2,self._energy)
663
+ gamma = twoTheta2 -twoTheta1
664
+
665
+ l1 = LA.norm(pos1 - pos2)*pixelsize
666
+ l2 = LA.norm(pos3 - pos4)*pixelsize
667
+
668
+ delta = util.solveTrigEquation(twoTheta1,twoTheta2,l1,l2)
669
+
670
+ b1 = (l1/m.sin(gamma))*m.cos(delta - twoTheta2)
671
+
672
+ A = (m.cos(delta - twoTheta1) / m.sin(twoTheta1) )**2 - 1.
673
+ B = -2. * b1 *m.cos(delta - twoTheta1) * (1/m.tan(twoTheta1))
674
+ print(b1)
675
+
676
+ ld = - (B/(2*A)) + m.sqrt((B/(2*A))**2 - (b1**2)/A)
677
+ print(m.sqrt((B/(2*A))**2 - (b1**2)/A))
678
+
679
+ print("angle : %s, distance: %s pixel" % (np.rad2deg(delta),ld/pixelsize ))
680
+
681
+ direction = (pos1 - pos2)/LA.norm(pos1 - pos2)
682
+ print ("Central pixel: %s" % (direction*ld/pixelsize + pos1))
683
+ #print("prev: %s" % LA.norm(pos1 - centralPixel))
684
+
685
+