pyemaps 2.0.0__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 (112) hide show
  1. pyemaps/COPYING +798 -0
  2. pyemaps/CifFile/CifFile_module.py +3117 -0
  3. pyemaps/CifFile/StarFile.py +2496 -0
  4. pyemaps/CifFile/TypeContentsParser.py +82 -0
  5. pyemaps/CifFile/YappsStarParser_1_0.py +289 -0
  6. pyemaps/CifFile/YappsStarParser_1_1.py +288 -0
  7. pyemaps/CifFile/YappsStarParser_2_0.py +369 -0
  8. pyemaps/CifFile/YappsStarParser_STAR2.py +382 -0
  9. pyemaps/CifFile/__init__.py +7 -0
  10. pyemaps/CifFile/drel/__init__.py +1 -0
  11. pyemaps/CifFile/drel/drel_ast_yacc.py +582 -0
  12. pyemaps/CifFile/drel/drel_lex.py +228 -0
  13. pyemaps/CifFile/drel/drel_runtime.py +126 -0
  14. pyemaps/CifFile/drel/parsetab.py +185 -0
  15. pyemaps/CifFile/drel/py_from_ast.py +628 -0
  16. pyemaps/CifFile/parsetab.py +185 -0
  17. pyemaps/CifFile/yapps3_compiled_rt.py +390 -0
  18. pyemaps/README.md +201 -0
  19. pyemaps/__config__.py +91 -0
  20. pyemaps/__init__.py +233 -0
  21. pyemaps/__main__.py +219 -0
  22. pyemaps/__version__.py +1 -0
  23. pyemaps/cdata/Aluminium.xtl +4 -0
  24. pyemaps/cdata/AluminiumOxide.xtl +5 -0
  25. pyemaps/cdata/Aluminium_FCC.xtl +4 -0
  26. pyemaps/cdata/BariumTitanate_180k.xtl +6 -0
  27. pyemaps/cdata/BariumTitanate_270k.xtl +7 -0
  28. pyemaps/cdata/BariumTitanate_Tetra.xtl +7 -0
  29. pyemaps/cdata/BiMnO3.xtl +27 -0
  30. pyemaps/cdata/Boron_Tetra.xtl +8 -0
  31. pyemaps/cdata/CadmiumSelenide_Hex.xtl +5 -0
  32. pyemaps/cdata/CadmiumSulfide_Cubic.xtl +5 -0
  33. pyemaps/cdata/CadmiumSulfide_Hex.xtl +5 -0
  34. pyemaps/cdata/Chromium_BCC.xtl +4 -0
  35. pyemaps/cdata/CoSb3_Skutterudite.xtl +5 -0
  36. pyemaps/cdata/CopperOxide.xtl +5 -0
  37. pyemaps/cdata/Copper_FCC.xtl +4 -0
  38. pyemaps/cdata/Cu2O_Cuprite.xtl +5 -0
  39. pyemaps/cdata/Diamond.xtl +4 -0
  40. pyemaps/cdata/ErbiumPyrogermanate.xtl +9 -0
  41. pyemaps/cdata/FePd_Tetra.xtl +5 -0
  42. pyemaps/cdata/FeS2_Pyrite.xtl +15 -0
  43. pyemaps/cdata/GalliumAntimonide.xtl +5 -0
  44. pyemaps/cdata/GalliumArsenide.xtl +5 -0
  45. pyemaps/cdata/GalliumNitride.xtl +5 -0
  46. pyemaps/cdata/Germanium.xtl +4 -0
  47. pyemaps/cdata/Gold_FCC.xtl +4 -0
  48. pyemaps/cdata/IndiumArsenide.xtl +5 -0
  49. pyemaps/cdata/LaMnO3.xtl +7 -0
  50. pyemaps/cdata/LeadZirconateTitanate.xtl +8 -0
  51. pyemaps/cdata/Li2MnO3.xtl +13 -0
  52. pyemaps/cdata/NaFeO2.xtl +6 -0
  53. pyemaps/cdata/Nb3Sn.xtl +5 -0
  54. pyemaps/cdata/Pentacene.xtl +39 -0
  55. pyemaps/cdata/Silicon.xtl +4 -0
  56. pyemaps/cdata/StrontiumTitanate.xtl +6 -0
  57. pyemaps/cdata/TelluriumDioxide.xtl +5 -0
  58. pyemaps/cdata/TinDioxide_RT.xtl +5 -0
  59. pyemaps/cdata/TitaniumDioxide_Anatase.xtl +5 -0
  60. pyemaps/cdata/TitaniumDioxide_Rutile.xtl +5 -0
  61. pyemaps/cdata/TungstenDiselenide.xtl +5 -0
  62. pyemaps/cdata/VanadiumDioxide_RT.xtl +6 -0
  63. pyemaps/cdata/ZincOxide.xtl +5 -0
  64. pyemaps/cdata/Zinc_HCP.xtl +4 -0
  65. pyemaps/cdata/ZirconiumNitride.xtl +5 -0
  66. pyemaps/cdata/limno2.xtl +6 -0
  67. pyemaps/crystals.py +1415 -0
  68. pyemaps/ddiffs.py +147 -0
  69. pyemaps/diffract/__init__.py +70 -0
  70. pyemaps/diffract/bloch_dec.py +779 -0
  71. pyemaps/diffract/csf_dec.py +242 -0
  72. pyemaps/diffract/dif_dec.py +519 -0
  73. pyemaps/diffract/dpgen_dec.py +175 -0
  74. pyemaps/diffract/mxtal_dec.py +201 -0
  75. pyemaps/diffract/powder_dec.py +137 -0
  76. pyemaps/diffract/stereo_dec.py +111 -0
  77. pyemaps/display.py +791 -0
  78. pyemaps/emcontrols.py +1017 -0
  79. pyemaps/errors.py +266 -0
  80. pyemaps/fileutils.py +1001 -0
  81. pyemaps/kdiffs.py +1221 -0
  82. pyemaps/license.txt +42 -0
  83. pyemaps/samples/adf.py +137 -0
  84. pyemaps/samples/al.img +0 -0
  85. pyemaps/samples/al_dpgen.py +57 -0
  86. pyemaps/samples/al_ediom.py +94 -0
  87. pyemaps/samples/convert_image.py +88 -0
  88. pyemaps/samples/dm_diff.py +225 -0
  89. pyemaps/samples/mask.im3 +0 -0
  90. pyemaps/samples/powder.py +51 -0
  91. pyemaps/samples/raw.img +0 -0
  92. pyemaps/samples/si_bloch.py +120 -0
  93. pyemaps/samples/si_constructor.py +51 -0
  94. pyemaps/samples/si_csf.py +59 -0
  95. pyemaps/samples/si_dif.py +134 -0
  96. pyemaps/samples/si_lacbed.py +59 -0
  97. pyemaps/samples/si_pyemaps.py +80 -0
  98. pyemaps/samples/si_scm.py +156 -0
  99. pyemaps/samples/si_stereo.py +116 -0
  100. pyemaps/scattering/__init__.py +26 -0
  101. pyemaps/scattering/sct_dec.py +31 -0
  102. pyemaps/spg/__init__.py +25 -0
  103. pyemaps/spg/spg_dec.py +60 -0
  104. pyemaps/stackimg.py +1031 -0
  105. pyemaps/xtal.py +222 -0
  106. pyemaps-2.0.0.dist-info/METADATA +231 -0
  107. pyemaps-2.0.0.dist-info/RECORD +112 -0
  108. pyemaps-2.0.0.dist-info/WHEEL +5 -0
  109. pyemaps-2.0.0.dist-info/entry_points.txt +2 -0
  110. pyemaps-2.0.0.dist-info/licenses/COPYING +798 -0
  111. pyemaps-2.0.0.dist-info/licenses/license.txt +42 -0
  112. pyemaps-2.0.0.dist-info/top_level.txt +1 -0
pyemaps/stackimg.py ADDED
@@ -0,0 +1,1031 @@
1
+ '''
2
+ .. This file is part of pyEMAPS
3
+
4
+ .. ----
5
+
6
+ .. pyEMAPS is free software. You can redistribute it and/or modify
7
+ .. it under the terms of the GNU General Public License as published
8
+ .. by the Free Software Foundation, either version 3 of the License,
9
+ .. or (at your option) any later version..
10
+
11
+ .. pyEMAPS is distributed in the hope that it will be useful,
12
+ .. but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ .. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ .. GNU General Public License for more details.
15
+
16
+ .. You should have received a copy of the GNU General Public License
17
+ .. along with pyEMAPS. If not, see `<https://www.gnu.org/licenses/>`_.
18
+
19
+ .. Contact supprort@emlabsoftware.com for any questions and comments.
20
+
21
+ .. ----
22
+
23
+ '''
24
+ product_name = "pyEMAPS"
25
+ from . import stem4d, send, ediom
26
+
27
+ from . import (E_INT, EM_INT,
28
+ E_FLOAT, EM_FLOAT,
29
+ E_DOUBLE, EM_DOUBLE,
30
+
31
+ MAX_IMAGESIZE, MIN_IMAGESIZE,
32
+ MAX_IMAGESTACK, MIN_IMAGESTACK,
33
+ DEF_FILTER_THRESHOLD, DEF_SEARCH_THRESHOLD,
34
+
35
+ DEF_RMIN, DEF_BOXSIZE,
36
+ DEF_CC, DEF_SIGMA, DEF_ICENTER,
37
+ DEF_XSCALE, DEF_TSCALE,
38
+
39
+ E_SH,
40
+ E_RAW,
41
+ E_NPY,
42
+ # imageloading mode
43
+ EL_ONE, #STEM4D image loading only one particular stack
44
+ EL_MORE #STEM4D image loading all stacks
45
+ )
46
+ import numpy as np
47
+
48
+
49
+ from .errors import XDPImageError
50
+
51
+ from .display import normalizeImage, displayXImage
52
+
53
+
54
+ def _getDateTimeString():
55
+ '''
56
+ Helper function to create a unique name for the image file name.
57
+
58
+ '''
59
+ from datetime import datetime
60
+
61
+ # Get the current date and time
62
+ current_time = datetime.now()
63
+
64
+ # Format the date-time string (up to seconds)
65
+ date_time_string = current_time.strftime("%Y-%m-%d %H:%M:%S")
66
+
67
+ # Remove all whitespace characters using join and a generator
68
+ date_time_string_no_spaces = ''.join(char for char in date_time_string if not char.isspace())
69
+
70
+ print("Current date-time string without spaces:", date_time_string_no_spaces)
71
+
72
+ return date_time_string_no_spaces
73
+
74
+ class StackImage():
75
+ """
76
+
77
+ Simple wrapper class for experimental images used in pyemaps
78
+ Stem4d analysis:
79
+
80
+ - Diffraction pattern indexing
81
+ - Annular Drak Field image generation
82
+ - Masked images
83
+
84
+ All image files must be of raw bitmap format, or known as headered raw format:
85
+
86
+ - **Header**: stores metadata about the image, often including three numbers
87
+ for dimensions (e.g. width, height, and number of bit depth, or stacks).
88
+
89
+ - **Data Offset**: After the header, the actual image or pixel data starts.
90
+
91
+ - **Pixel Data**: Stored in a row-major order, meaning pixels are arranged
92
+ row-byrow across columns.
93
+
94
+
95
+ The image can also be represented by a numpy array directly. In this case,
96
+ the StackImage object created will ignore its file name member.
97
+
98
+ and more to come.
99
+
100
+ """
101
+ def __init__(self,
102
+ imgfn,
103
+ nformat=E_SH,
104
+ ndtype = E_FLOAT,
105
+ dim = (MIN_IMAGESIZE,
106
+ MIN_IMAGESIZE,
107
+ MIN_IMAGESTACK),
108
+ noffset = 8):
109
+
110
+ self._data = None # current image data
111
+ self._npdata = None # numpy input image (E_NPY)
112
+ setattr(self, 'nformat', nformat) # image file type
113
+ setattr(self, 'fname', imgfn) # image file name
114
+ setattr(self, 'noffset', noffset) # image header offset to image data
115
+ setattr(self, 'ndtype', ndtype) # image data offset
116
+ setattr(self, 'dim', dim) # dimension of the image
117
+
118
+ @property
119
+ def fname(self):
120
+ """
121
+ Experimental diffraction pattern image input file full path.
122
+ In case of numpy array, this field is ignored.
123
+ """
124
+ return self._fname
125
+
126
+ @property
127
+ def nformat(self):
128
+ """
129
+ The type of the image file
130
+
131
+ - E_RAW: raw image files with image data at an `noffset` from the beginning of the file.
132
+ - E_SH: small header (pyEMAPS propietory). Header is a tuple of three short integers
133
+ and data type represented also by a short integer. Total bytes of the header or the offset
134
+ is 8)
135
+ - E_NPY: numpy array.
136
+
137
+ """
138
+ return self._nformat
139
+
140
+ @property
141
+ def ndtype(self):
142
+ """
143
+ The type of the image data - int, float, double are supported.
144
+ The constants to used to represent the types are:
145
+
146
+ - E_INT and EM_INT: single and multiple stack integral image data type.
147
+ - E_FLOAT and EM_FLOAT: single and multiple stack real image data type.
148
+ - E_DOUBLE and EM_DOUBLE: single and multiple stack 8-byte real image data type.
149
+
150
+ """
151
+ return self._ndtype
152
+
153
+ @property
154
+ def noffset(self):
155
+ """
156
+
157
+ image data offset from start of the file. Normally, it is the size of the
158
+ header in bytes.
159
+
160
+ """
161
+ return self._noffset
162
+
163
+ @property
164
+ def dim(self):
165
+ """
166
+
167
+ image dimension, support 3 dimensional images with input of
168
+ 3 integer tuple of integers (width, height, stack).
169
+
170
+ """
171
+ return self._dim
172
+
173
+ @property
174
+ def data(self):
175
+ """
176
+
177
+ Raw image data in numpy ndarray when it is read from a numpy array
178
+ Otherwise, the data will be generated from Stem4d when the input image
179
+ file is loaded.
180
+
181
+ With `nformat` set to E_NPY, set this to a 2-D numpy array of any
182
+ boolean, integer or floating-point type, 100 to 512 pixels per side,
183
+ before calling `loadImage`. It is converted to float32 when loaded.
184
+
185
+ """
186
+ return self._data
187
+
188
+ @fname.setter
189
+ def fname(self, fn):
190
+ if self._nformat != E_NPY:
191
+ if not isinstance(fn, str) or len(fn) == 0:
192
+ raise XDPImageError('Input image file name invalid')
193
+
194
+ self._fname = fn
195
+
196
+ @nformat.setter
197
+ def nformat(self, fmt):
198
+ if not isinstance(fmt, int):
199
+ raise XDPImageError('Invalid image file type')
200
+
201
+ if fmt != E_SH and fmt != E_RAW and fmt != E_NPY:
202
+ raise XDPImageError("Unsuported image file type")
203
+
204
+ self._nformat = fmt
205
+
206
+ @ndtype.setter
207
+ def ndtype(self, dty):
208
+ if not isinstance(dty, int):
209
+ raise XDPImageError('Invalid image data type')
210
+
211
+ if dty != E_INT and dty != EM_INT and \
212
+ dty != E_FLOAT and dty != EM_FLOAT and \
213
+ dty != E_DOUBLE and dty != EM_DOUBLE:
214
+ raise XDPImageError("Unsuported image data type")
215
+
216
+ self._ndtype = dty
217
+
218
+ @noffset.setter
219
+ def noffset(self, offset):
220
+ if not isinstance(offset, int):
221
+ raise XDPImageError('Invalid image data offset')
222
+
223
+ self._noffset = offset
224
+
225
+ @dim.setter
226
+ def dim(self, d):
227
+ if not isinstance(d, tuple) or len(d) !=3 or \
228
+ not all(isinstance(x, (int, float)) for x in d) or \
229
+ d[0] < MIN_IMAGESIZE or d[1] < MIN_IMAGESIZE or d[2] < MIN_IMAGESTACK or \
230
+ d[0] > MAX_IMAGESIZE or d[1] > MAX_IMAGESIZE or d[2] > MAX_IMAGESTACK:
231
+
232
+ raise XDPImageError('Invalid image data offset')
233
+
234
+ self._dim = d
235
+
236
+ @data.setter
237
+ def data(self, idata):
238
+ if not isinstance(idata, np.ndarray):
239
+ raise XDPImageError('Raw image data must be a numpy array')
240
+
241
+ # loadImage reads the input from _npdata; _data is replaced by
242
+ # whichever image is loaded or generated later
243
+ self._npdata = idata
244
+ self._data = idata
245
+
246
+ def __del__(self):
247
+
248
+ stem4d.freeStem4d()
249
+
250
+ def loadImage(self, rmode= EL_ONE, stack = 1):
251
+ """
252
+ Loads image into stem4d module for analysis.
253
+
254
+ """
255
+ img_format = self.nformat
256
+ if img_format != E_NPY and img_format != E_SH and img_format != E_RAW:
257
+ raise XDPImageError("Invalid image file type")
258
+
259
+ ximage = stem4d.cvar.ximg
260
+ ret = -1
261
+ if img_format == E_NPY:
262
+ if self._npdata is None:
263
+ raise XDPImageError("No image data: set the data property to a 2-D numpy array before loading")
264
+ try:
265
+ ret = ximage.setImage_from_numpy(self._npdata)
266
+ except (TypeError, ValueError, MemoryError) as e:
267
+ raise XDPImageError(f"Invalid numpy image: {e}") from e
268
+
269
+ # load proprietory small header formatted image file
270
+ if img_format == E_SH:
271
+ ret = stem4d.loadXImage(self.fname, rmode = rmode, stack = stack)
272
+
273
+ # load raw image file
274
+ if img_format == E_RAW:
275
+ ret = stem4d.loadRawImage(self.fname,
276
+ self.noffset, self.ndtype,
277
+ self.dim[0], self.dim[1], self.dim[2],
278
+ rmode = rmode, stack=stack)
279
+
280
+ # spell out license issues here before bailing out
281
+ if ret == stem4d.LICENSE_EXPIRED:
282
+ print(f"Your {product_name} license has expired. ")
283
+ print("Upgrade your license by purchasing a full license token by contacting support@emlabsoftware.com.")
284
+ print("Contact support@emlabsoftware.com if you think you have a valid license and the problem persists.")
285
+ elif ret == stem4d.LICENSE_NONEXISTENT:
286
+ print(f"Fail to find {product_name} license.")
287
+ print("Contact support@emlabsoftware.com if you think you have a valid license and the problem persists.")
288
+ print(f"Run \"pyemaps -l trial\" to obtain a free 7-day trial {product_name} license.")
289
+ print(f"Or, contact support@emlabsoftware.com to purchase a {product_name} license activation token.")
290
+ elif ret == stem4d.LICENSE_OTHERS:
291
+ print(f'Unknown error retrieving {product_name} license info!')
292
+ print(f'If persists, contact support@emlabsoftware.com')
293
+
294
+ if ret != 0:
295
+ raise XDPImageError("Failed to import the image into pyEMAPS")
296
+
297
+ self._dim = (ximage.h.ncol,ximage.h.nrow,ximage.h.nlayer)
298
+ self._data = ximage.getImageData(stack)
299
+
300
+ def viewExpImage(self, peakDP = False, iStack=1, title = "", iShow = False):
301
+ """
302
+ Helper function in stem4d module to display experimental diffraction database
303
+ diffraction pattern currently loaded.
304
+
305
+ :param peakDP: kinematic diffraction with miller index. This parameter is only valid for EDIOM operations
306
+ :type peakDP: Boolean, optional
307
+
308
+ :param Indexed: show the kinematic patterns after indexing is completed.
309
+ :type Indexed: boolean, optional, default to `False`
310
+
311
+ :param iStack: Image stack number to show. For single stack image, this parameter takes default value of 1
312
+ :type iStack: integer, optional, default to 1
313
+
314
+ :param iShow: whether to show miller indexes
315
+ :type iShow: boolean, optional, default to `False`
316
+
317
+ :return:
318
+ :rtype: None
319
+
320
+ This function is typically used to display experimental image after diffraction
321
+ patterns matched and indexed. It can also be used to display intermediate
322
+ search results after peaks are found but before indexes are matched. The latter
323
+ display is turned on only by *bDebug* flag in
324
+ `loadDPDB <pyemaps.crystals.html#pyemaps.crystals.Crystal.loadDPDB>`_ call.
325
+
326
+ """
327
+
328
+ xnc, xnr, nl = self._dim[0], self._dim[1], self._dim[2]
329
+
330
+ if iStack < 1 or iStack > nl+1:
331
+ print(f'The stack number of the image is invalid')
332
+ return
333
+
334
+ if self._data is None:
335
+ print(f'Experimental image is not loaded or invalid')
336
+ return
337
+
338
+ try:
339
+ yi = np.ascontiguousarray(self._data,
340
+ dtype=np.float32).reshape(xnc, xnr)
341
+ except Exception as e:
342
+ print("Error converting image data to numpy array")
343
+ raise XDPImageError from e
344
+ else:
345
+ xkdisk = None
346
+ if peakDP:
347
+ nps = ediom.getExpImagePeaks()
348
+
349
+ if nps <= 0:
350
+ print(f'Image is not indexed or indexing data is invalid')
351
+ return
352
+
353
+ ret, xkdisk = ediom.getXKDif(nps+1)
354
+
355
+ if ret != 0:
356
+ print(f'Python ----Failed to get diffraction patterns for the experimental image')
357
+ return ret
358
+
359
+ displayXImage(yi,
360
+ fsize=(xnc, xnr),
361
+ iShow = iShow,
362
+ ds = xkdisk,
363
+ suptitle = title)
364
+ @staticmethod
365
+ def showMatchedDBDP():
366
+ """
367
+ Helper function in stem4d module to display diffraction database diffraction pattern
368
+ that best matches that of the experimental diffraction image pattern.
369
+
370
+ This function is typically used to confirm its match with experimental diffraction
371
+ pattern. See sample code *al_ediom.py* for usage of this function.
372
+
373
+ """
374
+ nr, nc = MAX_IMAGESIZE, MAX_IMAGESIZE
375
+
376
+ img1d = np.ascontiguousarray(np.zeros(shape=(nc,nr)), dtype=np.float32)
377
+
378
+ nPeaks = ediom.DPGetCurrentImage(img1d)
379
+ if nPeaks <=0:
380
+ print(f'Python ----Failed to get selected database image')
381
+ return -1
382
+
383
+ if not normalizeImage(img1d):
384
+ print("Python ----Error normalizing the image for display")
385
+ return -1
386
+
387
+ knum = nPeaks+1
388
+ ret, kdisk = ediom.getDPListKDif(knum)
389
+
390
+ if ret != 0:
391
+ print(f'Python ----Failed to get diffraction disks list')
392
+ exit(1)
393
+ displayXImage(img1d,
394
+ fsize=(nc,nr),
395
+ iShow=True,
396
+ ds=kdisk,
397
+ suptitle = 'Matching Database Diffraction Pattern')
398
+
399
+ staticmethod
400
+ def displayFitImage(nr, nc):
401
+ """
402
+ Internal intermediate helper function for display fit images.
403
+ """
404
+
405
+ filen = nr*nc
406
+ ret, fimg = ediom.getFitImage(filen)
407
+ if ret != 0:
408
+ print(f'Python ----Failed to get fit image')
409
+ return ret
410
+
411
+ displayXImage(fimg.reshape(nr, nc), fsize=(nr, nc))
412
+ return 0
413
+
414
+ @staticmethod
415
+ def displaySearchMask(nr, nc):
416
+ """
417
+ Internal stem4d helper function displaying image mask image
418
+ generated in peak search.
419
+
420
+ :return:
421
+ :rtype: None
422
+
423
+ """
424
+ len = nr*nc
425
+ ret = 1
426
+
427
+
428
+ ret, img1d = ediom.getXMask(len)
429
+
430
+ if ret != 0 :
431
+ print(f'Python ----Error getting current image slice')
432
+
433
+ displayXImage(img1d.reshape((nr,nc)), fsize = (nr,nc), suptitle='Search Mask')
434
+ return 0
435
+
436
+ @staticmethod
437
+ def displaySearchKernel(nr, nc):
438
+ """
439
+ Internal stem4d helper function displaying image kernel image
440
+ generated in peak search.
441
+
442
+ :return:
443
+ :rtype: None
444
+
445
+ """
446
+ len = nr*nc
447
+ ret = -1
448
+
449
+
450
+ ret, img1d = ediom.getXTemplateMask(len)
451
+
452
+ if ret != 0 :
453
+ print(f'Python ----Error getting current image slice')
454
+ return -1
455
+
456
+ displayXImage(img1d.reshape((nr,nc)),
457
+ fsize = (nr,nc),
458
+ isMask=True,
459
+ suptitle = 'Search Kernel')
460
+ return 0
461
+
462
+ @staticmethod
463
+ def showDPDBMMask(mr, mc):
464
+
465
+ """
466
+ Internal function to display loaded DP database stereo projection map
467
+ as explained in the following
468
+
469
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/stereoprojectionmap.png
470
+ :width: 75%
471
+ :align: center
472
+
473
+ """
474
+ len = mr*mc
475
+
476
+ mask = np.ascontiguousarray(np.zeros(len), dtype=np.short)
477
+
478
+ ret= ediom.DPGetMask(mask)
479
+
480
+ if ret != 0:
481
+ print(f'Python ----Error getting the mask data')
482
+ return -1
483
+
484
+ displayXImage(mask.reshape(mr,mc),
485
+ fsize =(mr, mc),
486
+ isMask = True,
487
+ suptitle="Stereo Projection Map")
488
+
489
+ @staticmethod
490
+ def loadDPDB(dbfn, bShowDBMap=False):
491
+ """
492
+ Loads the diffraction database file *dbfn* into stem4d for diffraction peak searching and indexing.
493
+
494
+ See sample code *si_dpgen.py* for how to generate a diffraction pattern database with
495
+ pyemaps `generateDPDB <pyemaps.crystals.html#pyemaps.crystals.Crystal.generateDPDB>`_.
496
+
497
+ The database file can also be saved and reused.
498
+
499
+ :param dbfn: DP database file name.
500
+ :type dbfn: string, required
501
+
502
+ :param bShowDBMap: weather to display the stereodigram project map after successful
503
+ loading of the database file
504
+
505
+ :type bShowDBMap: boolean, optional, default to `False`
506
+
507
+ :return: status code, stereo projection map dimension - two integers tuple
508
+ :rtype: tuple
509
+
510
+ See the following for stereo projection map representing the DP database:
511
+
512
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/stereoprojectionmap.png
513
+ :width: 75%
514
+ :align: center
515
+
516
+ """
517
+ # load DP database into stem4d and return database fitmap dimensions
518
+ ret, mrow, mcol = ediom.readDPDB(dbfn)
519
+ if ret != 0: #need to replace this with stem4d return value
520
+ print(f'Error loading image file')
521
+ return ret, 0, 0
522
+
523
+ print(f'Diffraction Database loaded successfully.')
524
+ print(f'Dimension of stereo projection map: width ={mcol}, height={mrow}')
525
+
526
+ if bShowDBMap:
527
+ StackImage.showDPDBMMask(mrow, mcol)
528
+
529
+ return ret, mrow, mcol
530
+
531
+ @staticmethod
532
+ def showMatchingIndexMap(fr, fc):
533
+
534
+ """
535
+ Helper function in stem4d module to display matching indexes map. The colored
536
+ heat map with the peak indicates the orientation in which experimental and
537
+ database diffraction patterns best match.
538
+
539
+ :param fr: stereo projection map height. This number is obtained from
540
+ `loadDPDB <pyemaps.crystals.html#pyemaps.crystals.Crystal.loadDPDB>`_ call.
541
+ :type fr: int, required
542
+
543
+ :param fc: stereo projection map width. This number is obtained from
544
+ `loadDPDB <pyemaps.crystals.html#pyemaps.crystals.Crystal.loadDPDB>`_ call.
545
+ :type: int, required
546
+
547
+ :return: status code
548
+ :rtype: int
549
+
550
+ """
551
+
552
+ flen = fc*fr
553
+
554
+ ret, fmask = ediom.getFitMap(flen)
555
+ if ret != 0:
556
+ print(f'Python ----Failed to get fit map')
557
+ return ret
558
+
559
+ displayXImage(fmask.reshape(fr,fc),
560
+ fsize =(fr, fc),
561
+ isMask = True,
562
+ bColor=True,
563
+ suptitle = 'Matching Index Map')
564
+
565
+ def indexImage(self,
566
+ dpdbfn,
567
+ cc = DEF_CC,
568
+ sigma = DEF_SIGMA,
569
+ img_center = DEF_ICENTER,
570
+ rmin = DEF_RMIN,
571
+ search_box = DEF_BOXSIZE,
572
+ scaling_option = (DEF_XSCALE, DEF_TSCALE),
573
+ filter_threshold = DEF_FILTER_THRESHOLD,
574
+ peak_threshold = DEF_SEARCH_THRESHOLD,
575
+ ssel = 1,
576
+ bDebug = False
577
+ ):
578
+ """
579
+ Searches and indexes an experimental diffraction image. The experimental diffraction image will be loaded
580
+ with StackImage class constructor and its `loadImage <pyemaps.stackimg.html#pyemaps.stackimg.StackImage.loadImage>`_
581
+ method before the search and indexes are performed.
582
+
583
+ In addition, a diffraction database also needs to be loaded preceeding this call with
584
+ `loadDPDB <pyemaps.crystals.html#pyemaps.crystals.Crystal.loadDPDB>`_
585
+
586
+ :param dpdbfn: diffraction pattern database file name. The file is generated by pyemaps dp_gen module
587
+ or database file name saved from previous runs.
588
+ :type dpdbfn: string, required
589
+
590
+ :param cc: Camera constant in 1/Angstrom/pixel.
591
+ :type cc: float, optional
592
+
593
+ :param sigma: Diffraction peak width - estimated image peak width.
594
+ :type sigma: float, optional.
595
+
596
+ :param img_center: diffraction pattern center in (col, row) coordinate.
597
+ :type img_center: float tuple, optional
598
+
599
+ :param rmin: low cutoff radius for excluding center beam and diffuse intensities.
600
+ :type rmin: float, optional
601
+
602
+ :param search_box: Parameter in pixels used to define peak search. Recommended value
603
+ of 3 to 4 times of sigma value for selected area diffraction.
604
+ :type search_box: float, optional
605
+
606
+ :param filter_threshold: Diffraction pattern screening threshold. Between 0.0 and 1.0
607
+ use 0 to include all database patterns for search.
608
+ :type filter_threshold: float, optional
609
+
610
+ :param peak_threshold: Peak threshold. value between 0.0 and 1.0. used to remove noisy peaks.
611
+ :type peak_threshold: float, optional
612
+
613
+ :param ssel: image stack selection. The value must be in range of the input image layers.
614
+ :type ssel: integer, optional. Default value to 1, the first image layer in the input image stack.
615
+
616
+ :param bDebug: debug option. This is for displaying intermediate searching and masking images used.
617
+ :type bDebug: Boolean, optional. Default value to `False`.
618
+
619
+ :return: status code, 0 for successful run
620
+ :rtype: integer
621
+
622
+
623
+ The image control parameters cc - rmin above are image meaurements controls. The accuraccies
624
+ of these measurements can greatly affect the peak search and eventually indexing results.
625
+
626
+ The remaining input parameters directly control the search and indexing functions and its results.
627
+ Use these parameters to tune your results.
628
+
629
+ The following is a typical sequence of displays during the execution:
630
+
631
+ 1. Once the DP database is loaded successfully, the steoreo projection map associated
632
+ with the DP database will be shown.
633
+
634
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/stereoprojmap.png
635
+ :width: 75%
636
+ :align: center
637
+
638
+ 2. Successful experimental DP image importing will also show the image. This gives users
639
+ the opportunity to verify the image:
640
+
641
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/expimagoriginal.png
642
+ :width: 75%
643
+ :align: center
644
+
645
+ 3. Following the display of the experimental DP image and if *bDebug* is not set (by default),
646
+ the indexing result will be displayed on top of the experimental image:
647
+
648
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/expimgindexed.png
649
+ :width: 75%
650
+ :align: center
651
+
652
+ 4. Once the indexing results are generated, users have the option to call more display
653
+ *STEM4D* functions to show other results generated by this call. For examples,
654
+ the matching database DP image and DP database matching index map as shown below:
655
+
656
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/matchingDPDB.png
657
+ :width: 75%
658
+ :align: center
659
+
660
+ .. image:: https://github.com/emlab-solutions/imagepypy/raw/main/matchingindexmap.png
661
+ :width: 75%
662
+ :align: center
663
+
664
+ The latter map presents the likely matching location (in red) between the DP in database,
665
+ also considered the theoretical diffraction patterns, and the experimental DP image.
666
+
667
+ 5. Finally, in addition to the above visual representation of the run and results,
668
+ more verbose results are also shown in standard output:
669
+
670
+ .. code-block:: console
671
+
672
+ Number of peaks found: 45
673
+
674
+ -----------------Diffraction Pattern(DP) Indexing Results--------------------
675
+ ***** DP stack loaded and indexed: 1
676
+ ***** Best matching database DP: 1715
677
+ ***** DP rotation angle: 284.191 (degrees)
678
+ ***** Number of diffraction peaks indexed: 11
679
+ ***** Experimental and database DP correlation maximum: 0.988308
680
+ ***** Beam direction: 0.0575418 0.0599394 0.232559
681
+ ***** Reliability index (> 5.0): 30.5407
682
+
683
+
684
+ -----------------Indexed Diffraction Pattern Details------------------
685
+ DP # Miller Index Distance DP Loc Intensity
686
+ 1 2 -2 0 18.805359 114.912613 109.021492 14.05505
687
+ 2 -2 2 0 20.888754 81.346603 87.833191 100.00000
688
+ 3 3 1 -1 22.201900 95.978333 120.995316 28.63353
689
+ 4 -1 -3 1 22.830843 117.979164 86.309898 9.12775
690
+ 5 -3 -1 1 23.202806 101.336617 75.915146 17.33628
691
+ 6 1 3 -1 23.803307 78.843224 111.661034 97.01090
692
+ 7 5 -1 -1 36.265705 112.098000 132.817802 2.99402
693
+ 8 -5 1 1 36.616173 85.120682 65.116249 4.92946
694
+ 9 -1 5 -1 36.805595 62.239502 100.821404 33.52511
695
+ 10 -4 4 0 39.269745 66.114388 77.537254 18.77301
696
+ 11 4 4 -2 42.499973 76.063011 134.779076 2.83128
697
+
698
+ where Reliability index greater than 5.0 is considered successful run. DP stands for
699
+ diffraction pattern. If your results do not fit in a run, adjusting search and indexing paramemers
700
+ such as filter_threshold and peak threshold can also help stem4d module to search and match peaks.
701
+
702
+ .. note::
703
+
704
+ When *bDebug* flag is set (to 'True'), users are offered to chance to display more information of
705
+ stem4d's intermediate execution steps. For example, peak discovery step will show results
706
+ of all found peaks by image peak matching algorithms before indexing is done.
707
+ These intermediate images are mainly for debugging purposes.
708
+
709
+ """
710
+
711
+ if scaling_option[0] < 0 or scaling_option[0] > 5:
712
+ raise XDPImageError('Experimental scaling factor out of range')
713
+
714
+ if scaling_option[1] < 0 or scaling_option[1] > 5:
715
+ raise XDPImageError('Theoretical scaling factor out of range')
716
+
717
+ soption = scaling_option[0] * 10 + scaling_option[1]
718
+ if soption == 0:
719
+ raise XDPImageError('Both scaling factors out of range')
720
+
721
+ # loading theoretical diffraction image database
722
+ # generated from pyemaps dpgen module
723
+ ret, mr, mc = StackImage.loadDPDB(dpdbfn, bShowDBMap=True)
724
+ if ret != 0:
725
+ raise XDPImageError("Error loading theoretical diffraction pattern datanase")
726
+
727
+ try:
728
+ self.loadImage(rmode = 1, stack = ssel) #indexing one stack image at a time
729
+ except Exception as e:
730
+ raise XDPImageError(f"Error loading image for stem4d analysis: {e}") from e
731
+
732
+ self.viewExpImage(title='Experimental Diffraction Image - input')
733
+
734
+ edc = stem4d.cvar.edc
735
+
736
+ # setting image control parameters
737
+ edc.cc = cc
738
+ edc.sigma = sigma
739
+
740
+ # validate image center
741
+
742
+ if img_center[0] < 0 or img_center[0] > self.dim[0] or \
743
+ img_center[1] < 0 or img_center[1] >self.dim[1]:
744
+ raise ValueError('Point of interest on Experimental image invalid')
745
+
746
+ edc.set_center(img_center[0], img_center[1])
747
+
748
+ # load stem4d control parameters with which the image is generated
749
+ # and search and return search kernel dimensions
750
+ ret, kc, kr = stem4d.prepareSearch(img_center[0], img_center[1], rmin)
751
+ if ret != 0 or kc <= 0 or kr <= 0:
752
+ print(f'Error preparing for experimental image peak search')
753
+ return -1
754
+
755
+ if bDebug:
756
+ StackImage.displaySearchMask(self._dim[1], self._dim[0])
757
+ StackImage.displaySearchKernel(kr, kc)
758
+
759
+ # Start peaks search...
760
+ xpeaks = ediom.searchXPeaks(search_box, threshold=peak_threshold)
761
+
762
+ if xpeaks == -1:
763
+ print(f'Peaks search failed')
764
+ return -1
765
+
766
+ if xpeaks <= 3:
767
+ print(f'Insufficient number of peaks found for indexing')
768
+ return -1
769
+
770
+ if bDebug:
771
+ self.viewExpImage(peakDP=True, title = 'Experimental Diffraction Image', iShow = False)
772
+
773
+ ret = ediom.indexXPeaks(rmin, soption, filter_threshold)
774
+
775
+ if ret != 0:
776
+ print(f'Error indexing experimental image for DP')
777
+ return ret
778
+
779
+ self.viewExpImage(peakDP=True, title = 'Experimental Diffraction Image - Indexed', iShow = True)
780
+
781
+ StackImage.showMatchingIndexMap(mr,mc)
782
+
783
+ print("\n")
784
+ print("-----------------Indexed Diffraction Pattern Details------------------")
785
+
786
+ print(f"""{'DP #':^7}{'Miller Index':^15}{'Distance':^15}{'DP Loc':^23}{'Intensity':^11}""")
787
+
788
+ ediom.printIndexDetails()
789
+ return 0
790
+
791
+ def generateBDF(self,
792
+ center=(0.0, 0.0),
793
+ rads = (0.0, 0.0),
794
+ scol = 0.0,
795
+ bShow=False):
796
+ """
797
+ This method generates an (Annular) Bright/Dark Field image from an experimental stack image.
798
+ It is a wrapper for the function `getBDF`function in the new SEND module.
799
+
800
+ :param center: The center of detector.
801
+ :type center: tuple of floating point numbers
802
+
803
+ :param rads: a pair of radius for inner and outer field detectors.
804
+ When the inner radius is set to zero, the resulting image will be either
805
+ Bright Field(BF) or Dark Field(DF) depending on if the center is on
806
+ the bright spot of the image or not. Otherwise, a positive inner radius
807
+ will result in Annular Bright Field (ABF) or Annular Dark Field (ADF).
808
+ :type rads: tuple, optional.
809
+
810
+ :param scol: The size of the output image along column direction.
811
+ :type scol: floating point numbers, optional
812
+
813
+ :param bShow: whether to display the resulting image.
814
+ :type bShow: boolean, optional, default `False`
815
+
816
+ :return: (status code, image data), status code of 0 successful, otherwise failed
817
+ :rtype: tuple of a short integer and image data array.
818
+
819
+ .. note::
820
+
821
+ The experimental image loaded must have more than one stack.
822
+
823
+ """
824
+ if not isinstance(scol, (int, float)) or scol <= 0:
825
+ print(f'The size of the output image along column direction must be numeral and positive')
826
+ return -1, None
827
+
828
+ if not isinstance(center, tuple) or len(center) !=2 or \
829
+ not all(isinstance(x, (int, float)) for x in center):
830
+ print(f'Invalid annular dark or bright field detector center')
831
+ return -2, None
832
+
833
+ if not isinstance(rads, tuple) or len(rads) !=2 or \
834
+ not all(isinstance(x, (int, float)) for x in rads) or \
835
+ rads[0] == rads[1]:
836
+ print(f'Invalid annular dark or bright field detector inner and outer radius, it must be a pair of numerals')
837
+ return -3, None
838
+
839
+ try:
840
+ self.loadImage(rmode = EL_MORE)
841
+ except Exception as e:
842
+ print(f'Error loading the experimental image with message: {e}')
843
+ return -4, None
844
+
845
+ ret = send.getBDF(center[0], center[1],
846
+ rads[0], rads[1],
847
+ scol)
848
+ if ret != 0:
849
+ print(f'Failed to generate annular dark or bright field image for experimental image {self.fname}')
850
+ return -4, None
851
+
852
+ adfimg = stem4d.cvar.ximg
853
+ self._dim = (adfimg.h.ncol, adfimg.h.nrow, 1)
854
+ self._data = adfimg.getImageData(1)
855
+
856
+ if bShow:
857
+ self.viewExpImage(title='Annular Dark or Bright Field Image' if rads[0] > 0.0 else \
858
+ 'Dark or Bright Field Image')
859
+
860
+ return 0, self._data
861
+
862
+ def generateMaskedImage(self,
863
+ maskfn,
864
+ scancol = 0.0,
865
+ bShow=False):
866
+ """
867
+ generates an Bright Field(ADF) image from an experimental stack image input.
868
+ and a mask image.
869
+
870
+ :param maskfn: mask image file name.
871
+ :type maskfn: string, required
872
+
873
+ :param scancol: The center of ADF detector. default value 0.0
874
+ :type scancol: float
875
+
876
+ :param bShow: whether to display the resulting ADF image.
877
+ :type bShow: boolean, optional, default `False`
878
+
879
+ :return: status code, 0 successful, otherwise failed
880
+ :rtype: integer
881
+
882
+ .. note::
883
+ """
884
+ if not isinstance(scancol, (int, float)) or scancol <= 0:
885
+ print(f'The scancol input must be numeral and positive')
886
+ return -1, None
887
+
888
+ if not isinstance(maskfn, str) and maskfn.strip():
889
+ print(f'The mask image file name must be string and not empty ')
890
+ return -2, None
891
+
892
+ try:
893
+ self.loadImage(rmode = EL_MORE)
894
+ except Exception as e:
895
+ return -4, None
896
+
897
+ ret = send.getMaskedImage(maskfn, scancol)
898
+
899
+ if ret != 0:
900
+ print(f'Failed to generate masked image for experimental image {self.fname}')
901
+ return -3, None
902
+
903
+ maskedimg = stem4d.cvar.ximg
904
+ self._dim = (maskedimg.h.ncol, maskedimg.h.nrow, 1)
905
+ self._data = maskedimg.getImageData(1)
906
+
907
+ if bShow:
908
+ self.viewExpImage(title = "Experimental Diffraction Image - Masked")
909
+
910
+ return 0, self._data
911
+
912
+
913
+ @staticmethod
914
+ def convertImage(
915
+ imgfn,
916
+ nformat=E_RAW,
917
+ ndtype = E_FLOAT,
918
+ dim = (MIN_IMAGESIZE,
919
+ MIN_IMAGESIZE,
920
+ MIN_IMAGESTACK),
921
+ noffset = 8):
922
+ '''
923
+
924
+ converts any other suported image formats into pyEMAPS' proprietory small
925
+ header format.
926
+
927
+ This static method is very useful for all auxillary images to be used with
928
+ stem4d module and are required to have the proprietory image format by the
929
+ pyEMAPS simulation backend.
930
+
931
+ :param imgfn: input image file path
932
+ :type imgfn: string, required. Ignored when nformat is E_NPY
933
+
934
+ :param nformat: format of the image file. Support all bitmap formatted image format
935
+ :type nformat: integer, optional, default to `E_RAW`
936
+
937
+ :param ndtype: image date types. It can be integer, float or double
938
+ :type ndtype: integer, optional, default to E_INT
939
+
940
+ :param dim: three integer tuple for the dimension of the image data (width, height, stack/depth)
941
+ :type dim: integer tuple, optional, default to (MIN_IMAGESIZE, MIN_IMAGESIZE, MIN_IMAGESTACK)
942
+
943
+ :param noffset: header offset or starting byte of the image data.
944
+ :type noffset: integer, optional, default to 8.
945
+
946
+ :return:
947
+ :rtype: None
948
+
949
+ .. note::
950
+
951
+ If the input raw image file name does not contain directory,
952
+ this method will search the file in pyEMAPS data diretcory
953
+ set by environment variable PYEMAPS_DATA or current working
954
+ directory in that order.
955
+
956
+ If the input file contains the fully qualified file path,
957
+ pyEMAPS will be using the folder that contains the file
958
+ to place the output image file. So, please make sure that
959
+ the input directory is writable.
960
+
961
+ The result pyEMAPS image file with small header will be saved
962
+ in a folder name `sh_converted` in the dierctory that
963
+ contains the original raw image file.
964
+
965
+ '''
966
+
967
+ if nformat==E_SH or (nformat != E_RAW and nformat !=E_NPY):
968
+ raise ValueError('pyEmaps only converts raw and numpy images to its proprietory format.')
969
+
970
+ oExt = '.im3'
971
+ iname = ''
972
+ idir=''
973
+ imgFile = imgfn
974
+ if nformat !=E_NPY:
975
+ import os
976
+
977
+ if not imgfn or imgfn is None:
978
+ raise ValueError("Input image file error!")
979
+
980
+ # first, split the image path
981
+ idir, ifile = os.path.split(imgfn)
982
+
983
+ if not ifile:
984
+ raise ValueError("Input image file error!")
985
+
986
+ if not idir:
987
+ if 'PYEMAPS_DATA' in os.environ:
988
+ idir = os.environ['PYEMAPS_DATA']
989
+ else:
990
+ idir = os.getcwd()
991
+
992
+ imgFile = os.path.join(idir, ifile)
993
+ if not os.path.exists(imgFile):
994
+ raise FileNotFoundError("Input image file not found!")
995
+
996
+ iname, inxt = os.path.splitext(ifile)
997
+
998
+ if not iname or not inxt:
999
+ raise ValueError('Input file name error!')
1000
+
1001
+ else:
1002
+ if 'PYEMAPS_DATA' in os.environ:
1003
+ idir = os.environ['PYEMAPS_DATA']
1004
+ else:
1005
+ idir = os.getcwd()
1006
+ iname = "shimage_from_numpy" + _getDateTimeString()
1007
+
1008
+ odir = os.path.join(idir, 'sh_converted')
1009
+
1010
+ os.makedirs(odir, exist_ok=True)
1011
+ oImageFn = os.path.join(odir, f"{iname}{oExt}")
1012
+
1013
+ # now create a StackImage object.
1014
+
1015
+ img = StackImage(imgFile,
1016
+ nformat=nformat,
1017
+ ndtype = ndtype,
1018
+ dim = dim,
1019
+ noffset = noffset)
1020
+ try:
1021
+ img.loadImage()
1022
+ stem4d.saveImage(oImageFn)
1023
+
1024
+ except Exception as e:
1025
+ print(f'Error converting image into pyEMAPS proprietory image file: {e}')
1026
+
1027
+ else:
1028
+ print(f"The image file {imgFile} was successfully converted to pyEMAPS image format and saved as {oImageFn}")
1029
+
1030
+
1031
+