From efe1fd06b439b8a5c491273f63cb16cb97b22624 Mon Sep 17 00:00:00 2001 From: maclariz Date: Fri, 31 May 2024 13:36:25 +0100 Subject: [PATCH 01/19] Create DDF.py Add a new section under process for Digital Dark Field imaging --- py4DSTEM/process/digitaldarkfield/DDF.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 py4DSTEM/process/digitaldarkfield/DDF.py diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -0,0 +1 @@ + From fee10c8d9827d0ddbfb509f3fa1487cc7b5ade9d Mon Sep 17 00:00:00 2001 From: maclariz Date: Tue, 25 Jun 2024 22:05:52 +0100 Subject: [PATCH 02/19] Update DDF.py All DDF functions added --- py4DSTEM/process/digitaldarkfield/DDF.py | 153 +++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index 8b1378917..064081035 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -1 +1,154 @@ +""" +A general note on all these functions is that they are designed for use with rotation calibration into the pointslist. +However, they have to date only been used with the Qx and Qy in pixels and not calibrated into reciprocal units. +There is no reason why this should not work, but the default tolerance would need adjustment. +""" +from alive_progress import alive_bar +def aperture_array_generator( + shape, center, pad, mode, + g1, g2=(0,0), s1=0, s2=0, r1=0, r2=250, n1lims=(-5,5), n2lims=(-5,5), + returns = 'both' +): + #shape is a tuple describing the shape of the diffraction patterns + #center is a tuple of the centre position (vertical, horizontal) + #pad is + #mode tells what kind of calculation is desired. Which parameters are required depends on this choice: + #'single': just one aperture at a specified position: + #g1 and g2, lattice vectors (non-colinear) + #s1 and s2, multiples of these used to find the required lattice position + #i.e. aperture placed at s1*g1 + s2*g2 + #r1, r2 unused + #'2-beam': just a line of apertures along spots for a 2-beam condition: + #g1: lattice vector + #n1lims: tuple of integers giving the largest multiples of this lattice vector to be used, + #negative and positive + #r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) + #'array': an array defined by g1 and g2 centred on s1*g1+s2*g2 + #r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) + #n1lims and n2lims: tuple of integers giving the largest multiples of each lattice vector to be used + #r1 set to a small but non-zero value (a few pixels) can be used to exclude the primary beam + #returns sets whether the function returns: + #'both' = a centered array and an array in raw pixel numbers (uncentered) + #'centered' = just the version centered on [0,0] + #in all cases, the return is a list of (Qx,Qy) tuples + + V, H = shape[0], shape[1] + + if mode=='single': + apertureposition = [ + (center[0]+s1*g1[0]+s2*g2[0], + center[1]+s1*g1[1]+s2*g2[1]) + ] + centeredapertureposition = [ + ( + s1*g1[0]+s2*g2[0], + s1*g1[1]+s2*g2[1] + ) + ] + if returns == 'both': + return apertureposition, centeredapertureposition + elif returns == 'centered': + return centeredapertureposition + else: + print ('incorrect selection of return parameter') + + if mode=='2-beam': + aperturepositions = [] + centeredaperturepositions = [] + + for i in np.arange(n1lims[0],n1lims[1]+1): + v = center[0]+i*g1[0] + h = center[1]+i*g1[1] + vc = i*g1[0] + hc = i*g1[1] + r = (vc**2+hc**2)**.5 + if pad tol), axis=0) + for row in range(intensities2[:,0].shape[0]): + image[ + intensities2[row,1].astype(int), + intensities2[row,2].astype(int) + ] += intensities2[row,0] + bar() + return image #as a 2D numpy array From 5ff6acf600628c70469653bb5a766633c3e39bda Mon Sep 17 00:00:00 2001 From: maclariz Date: Fri, 5 Jul 2024 16:56:46 +0100 Subject: [PATCH 03/19] Update DDF v2.py --- py4DSTEM/process/digitaldarkfield/DDF.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index 064081035..082142b07 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -123,23 +123,30 @@ def pointlist_to_array(bplist, idim, jdim): ps = np.vstack((ps,nps)) bar() return ps #as a numpy array + #ps will be an 2D numpy array of n points x 5 columns: + #qx + #qy + #I + #Rx + #Ry -def pointlist_differences(lattice_point, aperturearray): - #calculates differences between a specific point on a lattice +def pointlist_differences(apertureposition, pointsarray): + #calculates differences between a specific aperture position #and a whole list of detected points for a dataset #returns the Euclidean distances as a 1D array subtractor = np.array([ - [lattice_point[0], lattice_point[1]]*aperturearray.shape[0] - ]).reshape((aperturearray.shape[0],2)) - diff = ((aperturearray[:,:2]-subtractor)**2).sum(axis=1)**.5 + [apertureposition[0], apertureposition[1]]*pointsarray.shape[0] + ]).reshape((pointsarray.shape[0],2)) + diff = ((pointsarray[:,:2]-subtractor)**2).sum(axis=1)**.5 return diff #as a numpy array def DDFimage(dataset, points, aperturearray, tol=1): - #dataset is a 4DSTEM dataset + #dataset is a 4DSTEM dataset as a numpy array, only used to get the size of image #points is an array of points as calculated by pointlist_to_array above - #centerarray is a list of tuples of centers, as defined by aperture_array_generator above + #centerarray is a list of tuples of aperture centers, + #as defined by aperture_array_generator above #tol is the tolerance for a displacement between points and centers (in pixels) - #this does rely on the differences function + #this does rely on the pointslist_differences function image = np.zeros_like(dataset[:,:,0,0]) with alive_bar(len(aperturearray), force_tty=True) as bar: for apertureposition in aperturearray: From 674211831c4b382769acdad851dcd499231db598 Mon Sep 17 00:00:00 2001 From: maclariz Date: Mon, 8 Jul 2024 07:51:50 +0100 Subject: [PATCH 04/19] Update DDF v3.py Adding in the import numpy as np that seems to have triggered the build errors. I foolishly assumed that this was fine as numpy is standard throughout py4dstem. --- py4DSTEM/process/digitaldarkfield/DDF.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index 082142b07..e8e7b9be7 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -3,6 +3,7 @@ However, they have to date only been used with the Qx and Qy in pixels and not calibrated into reciprocal units. There is no reason why this should not work, but the default tolerance would need adjustment. """ +import numpy as np from alive_progress import alive_bar def aperture_array_generator( From d665ca1a3841f846ec5fc7011ec2c0ef01d56def Mon Sep 17 00:00:00 2001 From: gvarnavi Date: Mon, 8 Jul 2024 02:17:00 -0700 Subject: [PATCH 05/19] lint with black --- py4DSTEM/process/digitaldarkfield/DDF.py | 286 ++++++++++++----------- 1 file changed, 151 insertions(+), 135 deletions(-) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index e8e7b9be7..74f4913d5 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -3,160 +3,176 @@ However, they have to date only been used with the Qx and Qy in pixels and not calibrated into reciprocal units. There is no reason why this should not work, but the default tolerance would need adjustment. """ + import numpy as np from alive_progress import alive_bar + def aperture_array_generator( - shape, center, pad, mode, - g1, g2=(0,0), s1=0, s2=0, r1=0, r2=250, n1lims=(-5,5), n2lims=(-5,5), - returns = 'both' + shape, + center, + pad, + mode, + g1, + g2=(0, 0), + s1=0, + s2=0, + r1=0, + r2=250, + n1lims=(-5, 5), + n2lims=(-5, 5), + returns="both", ): - #shape is a tuple describing the shape of the diffraction patterns - #center is a tuple of the centre position (vertical, horizontal) - #pad is - #mode tells what kind of calculation is desired. Which parameters are required depends on this choice: - #'single': just one aperture at a specified position: - #g1 and g2, lattice vectors (non-colinear) - #s1 and s2, multiples of these used to find the required lattice position - #i.e. aperture placed at s1*g1 + s2*g2 - #r1, r2 unused - #'2-beam': just a line of apertures along spots for a 2-beam condition: - #g1: lattice vector - #n1lims: tuple of integers giving the largest multiples of this lattice vector to be used, - #negative and positive - #r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) - #'array': an array defined by g1 and g2 centred on s1*g1+s2*g2 - #r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) - #n1lims and n2lims: tuple of integers giving the largest multiples of each lattice vector to be used - #r1 set to a small but non-zero value (a few pixels) can be used to exclude the primary beam - #returns sets whether the function returns: - #'both' = a centered array and an array in raw pixel numbers (uncentered) - #'centered' = just the version centered on [0,0] - #in all cases, the return is a list of (Qx,Qy) tuples - - V, H = shape[0], shape[1] - - if mode=='single': - apertureposition = [ - (center[0]+s1*g1[0]+s2*g2[0], - center[1]+s1*g1[1]+s2*g2[1]) - ] - centeredapertureposition = [ - ( - s1*g1[0]+s2*g2[0], - s1*g1[1]+s2*g2[1] - ) - ] - if returns == 'both': - return apertureposition, centeredapertureposition - elif returns == 'centered': - return centeredapertureposition - else: - print ('incorrect selection of return parameter') - - if mode=='2-beam': - aperturepositions = [] - centeredaperturepositions = [] - - for i in np.arange(n1lims[0],n1lims[1]+1): - v = center[0]+i*g1[0] - h = center[1]+i*g1[1] - vc = i*g1[0] - hc = i*g1[1] - r = (vc**2+hc**2)**.5 - if pad tol), axis=0) - for row in range(intensities2[:,0].shape[0]): + intensities = np.vstack( + (points[:, 2:].T, pointlist_differences(apertureposition, points)) + ).T + intensities2 = np.delete( + intensities, np.where(intensities[:, 3] > tol), axis=0 + ) + for row in range(intensities2[:, 0].shape[0]): image[ - intensities2[row,1].astype(int), - intensities2[row,2].astype(int) - ] += intensities2[row,0] + intensities2[row, 1].astype(int), intensities2[row, 2].astype(int) + ] += intensities2[row, 0] bar() - return image #as a 2D numpy array + return image # as a 2D numpy array From 568b7836bc278fef4d5f910dcfae8093d3649b0b Mon Sep 17 00:00:00 2001 From: gvarnavi Date: Mon, 8 Jul 2024 02:25:24 -0700 Subject: [PATCH 06/19] switched to tqdmnd --- py4DSTEM/process/digitaldarkfield/DDF.py | 73 +++++++++++------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index 74f4913d5..ee5efaad9 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -5,7 +5,7 @@ """ import numpy as np -from alive_progress import alive_bar +from emdfile import tqdmnd def aperture_array_generator( @@ -108,32 +108,28 @@ def aperture_array_generator( def pointlist_to_array(bplist, idim, jdim): # This function turns the py4dstem pointslist object to a simple array that is more # convenient for rapid array processing in numpy - with alive_bar(idim * jdim, force_tty=True) as bar: - for i in range(idim): - for j in range(jdim): - if i == j == 0: - ps = np.array( - [ - bplist.cal[i, j].qx, - bplist.cal[i, j].qy, - bplist.cal[i, j].I, - bplist.cal[i, j].qx.shape[0] * [i], - bplist.cal[i, j].qx.shape[0] * [j], - ] - ).T - bar() - else: - nps = np.array( - [ - bplist.cal[i, j].qx, - bplist.cal[i, j].qy, - bplist.cal[i, j].I, - bplist.cal[i, j].qx.shape[0] * [i], - bplist.cal[i, j].qx.shape[0] * [j], - ] - ).T - ps = np.vstack((ps, nps)) - bar() + for i, j in tqdmnd(idim, jdim): + if i == j == 0: + ps = np.array( + [ + bplist.cal[i, j].qx, + bplist.cal[i, j].qy, + bplist.cal[i, j].I, + bplist.cal[i, j].qx.shape[0] * [i], + bplist.cal[i, j].qx.shape[0] * [j], + ] + ).T + else: + nps = np.array( + [ + bplist.cal[i, j].qx, + bplist.cal[i, j].qy, + bplist.cal[i, j].I, + bplist.cal[i, j].qx.shape[0] * [i], + bplist.cal[i, j].qx.shape[0] * [j], + ] + ).T + ps = np.vstack((ps, nps)) return ps # as a numpy array # ps will be an 2D numpy array of n points x 5 columns: # qx @@ -162,17 +158,14 @@ def DDFimage(dataset, points, aperturearray, tol=1): # tol is the tolerance for a displacement between points and centers (in pixels) # this does rely on the pointslist_differences function image = np.zeros_like(dataset[:, :, 0, 0]) - with alive_bar(len(aperturearray), force_tty=True) as bar: - for apertureposition in aperturearray: - intensities = np.vstack( - (points[:, 2:].T, pointlist_differences(apertureposition, points)) - ).T - intensities2 = np.delete( - intensities, np.where(intensities[:, 3] > tol), axis=0 - ) - for row in range(intensities2[:, 0].shape[0]): - image[ - intensities2[row, 1].astype(int), intensities2[row, 2].astype(int) - ] += intensities2[row, 0] - bar() + for aperture_index in tqdmnd(len(aperturearray)): + apertureposition = aperturearray[aperture_index] + intensities = np.vstack( + (points[:, 2:].T, pointlist_differences(apertureposition, points)) + ).T + intensities2 = np.delete(intensities, np.where(intensities[:, 3] > tol), axis=0) + for row in range(intensities2[:, 0].shape[0]): + image[ + intensities2[row, 1].astype(int), intensities2[row, 2].astype(int) + ] += intensities2[row, 0] return image # as a 2D numpy array From eae5561c7bbeffd7fe9bfa2977b31a671b430322 Mon Sep 17 00:00:00 2001 From: maclariz Date: Mon, 8 Jul 2024 15:18:07 +0100 Subject: [PATCH 07/19] Create __init__.py Creating the init file to make the directory discoverable --- py4DSTEM/process/digitaldarkfield/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 py4DSTEM/process/digitaldarkfield/__init__.py diff --git a/py4DSTEM/process/digitaldarkfield/__init__.py b/py4DSTEM/process/digitaldarkfield/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/py4DSTEM/process/digitaldarkfield/__init__.py @@ -0,0 +1 @@ + From dffd10c8f04ea32530fcfae2c64485b233a9a223 Mon Sep 17 00:00:00 2001 From: maclariz Date: Mon, 8 Jul 2024 15:22:47 +0100 Subject: [PATCH 08/19] Update __init__.py Adding the correct imports --- py4DSTEM/process/digitaldarkfield/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/py4DSTEM/process/digitaldarkfield/__init__.py b/py4DSTEM/process/digitaldarkfield/__init__.py index 8b1378917..ad204d499 100644 --- a/py4DSTEM/process/digitaldarkfield/__init__.py +++ b/py4DSTEM/process/digitaldarkfield/__init__.py @@ -1 +1,6 @@ - +from py4DSTEM.process.digitaldarkfield.DDF import ( + aperture_array_generator, + pointlist_to_array, + pointlist_differences, + DDFimage, +) From bff3634e71d30280030fda6ac23aa004a20852bd Mon Sep 17 00:00:00 2001 From: maclariz Date: Mon, 8 Jul 2024 15:23:41 +0100 Subject: [PATCH 09/19] Update __init__.py --- py4DSTEM/process/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py4DSTEM/process/__init__.py b/py4DSTEM/process/__init__.py index 0509d181e..2239e6c60 100644 --- a/py4DSTEM/process/__init__.py +++ b/py4DSTEM/process/__init__.py @@ -7,3 +7,4 @@ from py4DSTEM.process import classification from py4DSTEM.process import diffraction from py4DSTEM.process import wholepatternfit +from py4DSTEM.process import digitaldarkfield From 8b176e8a0f05b2250d4923507ecbdb7f49ab6489 Mon Sep 17 00:00:00 2001 From: maclariz Date: Mon, 8 Jul 2024 16:05:57 +0100 Subject: [PATCH 10/19] Update DDF.py updated the comments to better suit style used elsewhere in repository Some slight variable renaming for consistency --- py4DSTEM/process/digitaldarkfield/DDF.py | 131 ++++++++++++++--------- 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index ee5efaad9..2d0e8d100 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -23,28 +23,43 @@ def aperture_array_generator( n2lims=(-5, 5), returns="both", ): - # shape is a tuple describing the shape of the diffraction patterns - # center is a tuple of the centre position (vertical, horizontal) - # pad is - # mode tells what kind of calculation is desired. Which parameters are required depends on this choice: - #'single': just one aperture at a specified position: - # g1 and g2, lattice vectors (non-colinear) - # s1 and s2, multiples of these used to find the required lattice position - # i.e. aperture placed at s1*g1 + s2*g2 - # r1, r2 unused - #'2-beam': just a line of apertures along spots for a 2-beam condition: - # g1: lattice vector - # n1lims: tuple of integers giving the largest multiples of this lattice vector to be used, - # negative and positive - # r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) - #'array': an array defined by g1 and g2 centred on s1*g1+s2*g2 - # r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) - # n1lims and n2lims: tuple of integers giving the largest multiples of each lattice vector to be used - # r1 set to a small but non-zero value (a few pixels) can be used to exclude the primary beam - # returns sets whether the function returns: - #'both' = a centered array and an array in raw pixel numbers (uncentered) - #'centered' = just the version centered on [0,0] - # in all cases, the return is a list of (Qx,Qy) tuples + """ + shape is a tuple describing the shape of the diffraction patterns + + center is a tuple of the centre position (vertical, horizontal) + + pad is any edge boundary desired (i.e. no aperture positions within pad pixels of edge) + + mode tells what kind of calculation is desired. Which parameters are required and exactly what they mean + depends on this choice: + + 'single': just one aperture at a specified position: + g1 and g2, lattice vectors (non-colinear) + s1 and s2, multiples of these used to find the required lattice position + i.e. aperture placed at s1*g1 + s2*g2 + r1, r2 unused + + '2-beam': just a line of apertures along spots for a 2-beam condition: + g1: lattice vector + g2: unused + n1lims: tuple of integers giving the largest multiples of this lattice vector to be used, + negative and positive + r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) + this is a good way to exclude the central spot by setting r > disc radius + + 'array': an array defined by g1 and g2 centred on s1*g1+s2*g2 + r1 and r2, inner and outerradii in pixels as for '2-beam' + n1lims and n2lims: tuple of integers giving the largest multiples of each lattice vector to be used, + as for 2-beam + + returns sets whether the function returns: + + 'both' = a centered array and an array in raw pixel numbers (uncentered) + + 'centered' = just the version centered on [0,0] + + in all cases, the return is a list of (Qx,Qy) tuples + """ V, H = shape[0], shape[1] @@ -104,13 +119,24 @@ def aperture_array_generator( else: print("incorrect mode selection") - def pointlist_to_array(bplist, idim, jdim): - # This function turns the py4dstem pointslist object to a simple array that is more - # convenient for rapid array processing in numpy + """ + This function turns the py4dstem pointslist object to a simple numpy array that is more + convenient for rapid array processing in numpy + + idim and jdim are the dimensions in the Rx and Ry directions + + returns an array called pointsarray + This will be an 2D numpy array of n points x 5 columns: + qx + qy + I + Rx + Ry + """ for i, j in tqdmnd(idim, jdim): if i == j == 0: - ps = np.array( + pointsarray = np.array( [ bplist.cal[i, j].qx, bplist.cal[i, j].qy, @@ -129,43 +155,46 @@ def pointlist_to_array(bplist, idim, jdim): bplist.cal[i, j].qx.shape[0] * [j], ] ).T - ps = np.vstack((ps, nps)) - return ps # as a numpy array - # ps will be an 2D numpy array of n points x 5 columns: - # qx - # qy - # I - # Rx - # Ry - - + pointsarray = np.vstack((pointsarray, nps)) + return pointsarray + def pointlist_differences(apertureposition, pointsarray): - # calculates differences between a specific aperture position - # and a whole list of detected points for a dataset - # returns the Euclidean distances as a 1D array + """ + calculates differences between a specific aperture position + and a whole list of detected points for a dataset (as an array) + + returns the Euclidean distances as a 1D numpy array + """ subtractor = np.array( [[apertureposition[0], apertureposition[1]] * pointsarray.shape[0]] ).reshape((pointsarray.shape[0], 2)) diff = ((pointsarray[:, :2] - subtractor) ** 2).sum(axis=1) ** 0.5 - return diff # as a numpy array - - -def DDFimage(dataset, points, aperturearray, tol=1): - # dataset is a 4DSTEM dataset as a numpy array, only used to get the size of image - # points is an array of points as calculated by pointlist_to_array above - # centerarray is a list of tuples of aperture centers, - # as defined by aperture_array_generator above - # tol is the tolerance for a displacement between points and centers (in pixels) - # this does rely on the pointslist_differences function + return diff + + +def DDFimage(dataset, pointsarray, aperturearray, tol=1): + """ + dataset is a 4DSTEM dataset as a numpy array, only used to get the size of image + + pointsarray is an array of points as calculated by pointlist_to_array + + aperturearray is a list of tuples of aperture centers generated by aperture_array_generator + + tol is the tolerance for a displacement between points and centers (in pixels) + + this does rely on the pointslist_differences function + + returns a the DDF image as a 2D numpy array + """ image = np.zeros_like(dataset[:, :, 0, 0]) for aperture_index in tqdmnd(len(aperturearray)): apertureposition = aperturearray[aperture_index] intensities = np.vstack( - (points[:, 2:].T, pointlist_differences(apertureposition, points)) + (pointsarray[:, 2:].T, pointlist_differences(apertureposition, pointsarray)) ).T intensities2 = np.delete(intensities, np.where(intensities[:, 3] > tol), axis=0) for row in range(intensities2[:, 0].shape[0]): image[ intensities2[row, 1].astype(int), intensities2[row, 2].astype(int) ] += intensities2[row, 0] - return image # as a 2D numpy array + return image From 3509e0a9126e4ec5b02c411c7b439c63aead15db Mon Sep 17 00:00:00 2001 From: maclariz Date: Thu, 11 Jul 2024 19:05:37 +0100 Subject: [PATCH 11/19] Update DDF.py lint with black --- py4DSTEM/process/digitaldarkfield/DDF.py | 36 +++++++++++++----------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/process/digitaldarkfield/DDF.py index 2d0e8d100..415a831cf 100644 --- a/py4DSTEM/process/digitaldarkfield/DDF.py +++ b/py4DSTEM/process/digitaldarkfield/DDF.py @@ -25,20 +25,20 @@ def aperture_array_generator( ): """ shape is a tuple describing the shape of the diffraction patterns - + center is a tuple of the centre position (vertical, horizontal) - + pad is any edge boundary desired (i.e. no aperture positions within pad pixels of edge) - + mode tells what kind of calculation is desired. Which parameters are required and exactly what they mean depends on this choice: - + 'single': just one aperture at a specified position: g1 and g2, lattice vectors (non-colinear) s1 and s2, multiples of these used to find the required lattice position i.e. aperture placed at s1*g1 + s2*g2 r1, r2 unused - + '2-beam': just a line of apertures along spots for a 2-beam condition: g1: lattice vector g2: unused @@ -46,18 +46,18 @@ def aperture_array_generator( negative and positive r1 and r2, inner and outerradii in pixels over which aperture points will be found (optional) this is a good way to exclude the central spot by setting r > disc radius - + 'array': an array defined by g1 and g2 centred on s1*g1+s2*g2 r1 and r2, inner and outerradii in pixels as for '2-beam' n1lims and n2lims: tuple of integers giving the largest multiples of each lattice vector to be used, as for 2-beam - + returns sets whether the function returns: - + 'both' = a centered array and an array in raw pixel numbers (uncentered) - + 'centered' = just the version centered on [0,0] - + in all cases, the return is a list of (Qx,Qy) tuples """ @@ -119,6 +119,7 @@ def aperture_array_generator( else: print("incorrect mode selection") + def pointlist_to_array(bplist, idim, jdim): """ This function turns the py4dstem pointslist object to a simple numpy array that is more @@ -126,7 +127,7 @@ def pointlist_to_array(bplist, idim, jdim): idim and jdim are the dimensions in the Rx and Ry directions - returns an array called pointsarray + returns an array called pointsarray This will be an 2D numpy array of n points x 5 columns: qx qy @@ -157,12 +158,13 @@ def pointlist_to_array(bplist, idim, jdim): ).T pointsarray = np.vstack((pointsarray, nps)) return pointsarray - + + def pointlist_differences(apertureposition, pointsarray): """ calculates differences between a specific aperture position and a whole list of detected points for a dataset (as an array) - + returns the Euclidean distances as a 1D numpy array """ subtractor = np.array( @@ -175,13 +177,13 @@ def pointlist_differences(apertureposition, pointsarray): def DDFimage(dataset, pointsarray, aperturearray, tol=1): """ dataset is a 4DSTEM dataset as a numpy array, only used to get the size of image - + pointsarray is an array of points as calculated by pointlist_to_array - + aperturearray is a list of tuples of aperture centers generated by aperture_array_generator - + tol is the tolerance for a displacement between points and centers (in pixels) - + this does rely on the pointslist_differences function returns a the DDF image as a 2D numpy array From 88d3d3313287eb6018bb48bf3c01c656cc489278 Mon Sep 17 00:00:00 2001 From: cophus Date: Fri, 12 Jul 2024 16:46:49 -0700 Subject: [PATCH 12/19] moving DDF functions to braggvectors --- py4DSTEM/braggvectors/__init__.py | 1 + .../DDF.py => braggvectors/digital_dark_field.py} | 0 py4DSTEM/process/digitaldarkfield/__init__.py | 6 ------ 3 files changed, 1 insertion(+), 6 deletions(-) rename py4DSTEM/{process/digitaldarkfield/DDF.py => braggvectors/digital_dark_field.py} (100%) delete mode 100644 py4DSTEM/process/digitaldarkfield/__init__.py diff --git a/py4DSTEM/braggvectors/__init__.py b/py4DSTEM/braggvectors/__init__.py index 482b1f31e..767494d59 100644 --- a/py4DSTEM/braggvectors/__init__.py +++ b/py4DSTEM/braggvectors/__init__.py @@ -3,6 +3,7 @@ from py4DSTEM.braggvectors.braggvector_methods import BraggVectorMap from py4DSTEM.braggvectors.diskdetection import * from py4DSTEM.braggvectors.probe import * +from py4DSTEM.braggvectors.digital_dark_field import * # from .diskdetection_aiml import * # from .diskdetection_parallel_new import * diff --git a/py4DSTEM/process/digitaldarkfield/DDF.py b/py4DSTEM/braggvectors/digital_dark_field.py similarity index 100% rename from py4DSTEM/process/digitaldarkfield/DDF.py rename to py4DSTEM/braggvectors/digital_dark_field.py diff --git a/py4DSTEM/process/digitaldarkfield/__init__.py b/py4DSTEM/process/digitaldarkfield/__init__.py deleted file mode 100644 index ad204d499..000000000 --- a/py4DSTEM/process/digitaldarkfield/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from py4DSTEM.process.digitaldarkfield.DDF import ( - aperture_array_generator, - pointlist_to_array, - pointlist_differences, - DDFimage, -) From 5ec8b67bb7abeb9940f090cd800ddf55d19ff97a Mon Sep 17 00:00:00 2001 From: cophus Date: Wed, 17 Jul 2024 19:24:20 -0700 Subject: [PATCH 13/19] working --- py4DSTEM/braggvectors/__init__.py | 1 - py4DSTEM/process/__init__.py | 1 - py4DSTEM/process/diffraction/__init__.py | 1 + .../diffraction}/digital_dark_field.py | 4 ++-- 4 files changed, 3 insertions(+), 4 deletions(-) rename py4DSTEM/{braggvectors => process/diffraction}/digital_dark_field.py (99%) diff --git a/py4DSTEM/braggvectors/__init__.py b/py4DSTEM/braggvectors/__init__.py index 767494d59..482b1f31e 100644 --- a/py4DSTEM/braggvectors/__init__.py +++ b/py4DSTEM/braggvectors/__init__.py @@ -3,7 +3,6 @@ from py4DSTEM.braggvectors.braggvector_methods import BraggVectorMap from py4DSTEM.braggvectors.diskdetection import * from py4DSTEM.braggvectors.probe import * -from py4DSTEM.braggvectors.digital_dark_field import * # from .diskdetection_aiml import * # from .diskdetection_parallel_new import * diff --git a/py4DSTEM/process/__init__.py b/py4DSTEM/process/__init__.py index 2239e6c60..0509d181e 100644 --- a/py4DSTEM/process/__init__.py +++ b/py4DSTEM/process/__init__.py @@ -7,4 +7,3 @@ from py4DSTEM.process import classification from py4DSTEM.process import diffraction from py4DSTEM.process import wholepatternfit -from py4DSTEM.process import digitaldarkfield diff --git a/py4DSTEM/process/diffraction/__init__.py b/py4DSTEM/process/diffraction/__init__.py index 942547749..40fa63dfd 100644 --- a/py4DSTEM/process/diffraction/__init__.py +++ b/py4DSTEM/process/diffraction/__init__.py @@ -2,3 +2,4 @@ from py4DSTEM.process.diffraction.flowlines import * from py4DSTEM.process.diffraction.tdesign import * from py4DSTEM.process.diffraction.crystal_phase import * +from py4DSTEM.process.diffraction.digital_dark_field import * \ No newline at end of file diff --git a/py4DSTEM/braggvectors/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py similarity index 99% rename from py4DSTEM/braggvectors/digital_dark_field.py rename to py4DSTEM/process/diffraction/digital_dark_field.py index 415a831cf..fbb77e9e2 100644 --- a/py4DSTEM/braggvectors/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -4,8 +4,8 @@ There is no reason why this should not work, but the default tolerance would need adjustment. """ -import numpy as np -from emdfile import tqdmnd +# import numpy as np +# from emdfile import tqdmnd def aperture_array_generator( From c52a4d553e97f85f7e09dfb6fbaa81e15456598b Mon Sep 17 00:00:00 2001 From: cophus Date: Mon, 22 Jul 2024 20:02:50 -0700 Subject: [PATCH 14/19] Adding subtraction utility function, cleaning up --- .../process/diffraction/digital_dark_field.py | 210 +++++++++++++----- 1 file changed, 158 insertions(+), 52 deletions(-) diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index fbb77e9e2..e59816980 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -1,12 +1,14 @@ + """ A general note on all these functions is that they are designed for use with rotation calibration into the pointslist. However, they have to date only been used with the Qx and Qy in pixels and not calibrated into reciprocal units. There is no reason why this should not work, but the default tolerance would need adjustment. """ -# import numpy as np -# from emdfile import tqdmnd - +import numpy as np +import matplotlib.pyplot as plt +from emdfile import tqdmnd +from py4DSTEM import show def aperture_array_generator( shape, @@ -22,6 +24,12 @@ def aperture_array_generator( n1lims=(-5, 5), n2lims=(-5, 5), returns="both", + plot_result = False, + plot_image = None, + plot_marker_size = 100, + figsize = (6,6), + returnfig = False, + **kwargs, ): """ shape is a tuple describing the shape of the diffraction patterns @@ -64,20 +72,16 @@ def aperture_array_generator( V, H = shape[0], shape[1] if mode == "single": - apertureposition = [ + aperture_position = [ (center[0] + s1 * g1[0] + s2 * g2[0], center[1] + s1 * g1[1] + s2 * g2[1]) ] - centeredapertureposition = [(s1 * g1[0] + s2 * g2[0], s1 * g1[1] + s2 * g2[1])] - if returns == "both": - return apertureposition, centeredapertureposition - elif returns == "centered": - return centeredapertureposition - else: - print("incorrect selection of return parameter") + centered_aperture_position = [ + (s1 * g1[0] + s2 * g2[0], s1 * g1[1] + s2 * g2[1]) + ] - if mode == "2-beam": - aperturepositions = [] - centeredaperturepositions = [] + elif mode == "2-beam": + aperture_positions = [] + centered_aperture_positions = [] for i in np.arange(n1lims[0], n1lims[1] + 1): v = center[0] + i * g1[0] @@ -86,18 +90,12 @@ def aperture_array_generator( hc = i * g1[1] r = (vc**2 + hc**2) ** 0.5 if pad < v < V - pad and pad < h < H - pad and r1 <= r <= r2: - aperturepositions += [(v, h)] - centeredaperturepositions += [(vc, hc)] - if returns == "both": - return aperturepositions, centeredaperturepositions - elif returns == "centered": - return centeredaperturepositions - else: - print("incorrect selection of return parameter") + aperture_positions += [(v, h)] + centered_aperture_positions += [(vc, hc)] - if mode == "array": - aperturepositions = [] - centeredaperturepositions = [] + elif mode == "array": + aperture_positions = [] + centered_aperture_positions = [] for i in np.arange(n1lims[0], n1lims[1] + 1): for j in np.arange(n2lims[0], n2lims[1] + 1): @@ -107,27 +105,121 @@ def aperture_array_generator( hc = i * g1[1] + j * g2[1] + s1 * g1[1] + s2 * g2[1] r = (vc**2 + hc**2) ** 0.5 if pad < v < V - pad and pad < h < H - pad and r1 <= r <= r2: - aperturepositions += [(v, h)] - centeredaperturepositions += [(vc, hc)] - if returns == "both": - return aperturepositions, centeredaperturepositions - elif returns == "centered": - return centeredaperturepositions - else: - print("incorrect selection of return parameter") - + aperture_positions += [(v, h)] + centered_aperture_positions += [(vc, hc)] else: print("incorrect mode selection") + # Convert lists to numpy arrays + aperture_positions = np.array(aperture_positions) + centered_aperture_positions = np.array(centered_aperture_positions) + + # plotting + if plot_result: + if plot_image is None: + plot_image = np.zeros(shape) + + fig,ax = show( + plot_image, + ticks = False, + returnfig = True, + **kwargs, + ) + ax.scatter( + aperture_positions[:,1], + aperture_positions[:,0], + color = (0.0,1.0,0.0,0.3), + s = plot_marker_size, + ) + + if returns == "both": + if returnfig: + return aperture_positions, centered_aperture_positions, fig, ax + else: + return aperture_positions, centered_aperture_positions + elif returns == "centered": + if returnfig: + return centered_aperture_positions, fig, ax + else: + return centered_aperture_positions + else: + print("incorrect selection of return parameter") + + +def aperture_array_subtract( + aperture_positions, + aperture_positions_delete, + tol = 1.0, + plot_result = False, + plot_image = None, + plot_marker_size = 100, + figsize = (6,6), + returnfig = False, + **kwargs, + ): + """ + This function takes in a set of aperture positions, and removes apertures within + the user-specified tolerance from aperture_array_delete. + """ -def pointlist_to_array(bplist, idim, jdim): + # Determine which apertures to keep + keep = np.zeros(aperture_positions.shape[0],dtype='bool') + tol2 = tol**2 + for a0 in range(aperture_positions.shape[0]): + dist2_min = np.min( + np.sum( + (aperture_positions[a0] - aperture_positions_delete)**2, + axis = 1, + ), + ) + if dist2_min > tol2: + keep[a0] = True + + aperture_positions_new = aperture_positions[keep] + + # plotting + if plot_result: + aperture_positions_del = aperture_positions[np.logical_not(keep)] + + + if plot_image is None: + plot_image = np.zeros(shape) + + fig,ax = show( + plot_image, + ticks = False, + returnfig = True, + **kwargs, + ) + ax.scatter( + aperture_positions_del[:,1], + aperture_positions_del[:,0], + color = (1.0,0.0,0.0,0.3), + s = plot_marker_size, + ) + ax.scatter( + aperture_positions_new[:,1], + aperture_positions_new[:,0], + color = (0.0,1.0,0.0,0.3), + s = plot_marker_size, + ) + + return aperture_positions_new + + + +def pointlist_to_array( + bplist, + # idim, + # jdim + ): """ This function turns the py4dstem pointslist object to a simple numpy array that is more convenient for rapid array processing in numpy idim and jdim are the dimensions in the Rx and Ry directions - returns an array called pointsarray + returns an array called points_array This will be an 2D numpy array of n points x 5 columns: qx qy @@ -135,9 +227,9 @@ def pointlist_to_array(bplist, idim, jdim): Rx Ry """ - for i, j in tqdmnd(idim, jdim): + for i, j in tqdmnd(bplist.Rshape[0], bplist.Rshape[1]): if i == j == 0: - pointsarray = np.array( + points_array = np.array( [ bplist.cal[i, j].qx, bplist.cal[i, j].qy, @@ -156,11 +248,11 @@ def pointlist_to_array(bplist, idim, jdim): bplist.cal[i, j].qx.shape[0] * [j], ] ).T - pointsarray = np.vstack((pointsarray, nps)) - return pointsarray + points_array = np.vstack((points_array, nps)) + return points_array -def pointlist_differences(apertureposition, pointsarray): +def pointlist_differences(aperture_position, points_array): """ calculates differences between a specific aperture position and a whole list of detected points for a dataset (as an array) @@ -168,19 +260,26 @@ def pointlist_differences(apertureposition, pointsarray): returns the Euclidean distances as a 1D numpy array """ subtractor = np.array( - [[apertureposition[0], apertureposition[1]] * pointsarray.shape[0]] - ).reshape((pointsarray.shape[0], 2)) - diff = ((pointsarray[:, :2] - subtractor) ** 2).sum(axis=1) ** 0.5 + [[aperture_position[0], aperture_position[1]] * points_array.shape[0]] + ).reshape((points_array.shape[0], 2)) + diff = ((points_array[:, :2] - subtractor) ** 2).sum(axis=1) ** 0.5 return diff -def DDFimage(dataset, pointsarray, aperturearray, tol=1): +def DDFimage( + points_array, + aperture_positions, + Rshape = None, + tol=1 +): """ - dataset is a 4DSTEM dataset as a numpy array, only used to get the size of image + points_array is an array of points as calculated by pointlist_to_array - pointsarray is an array of points as calculated by pointlist_to_array + aperture_positions is a numpy.array of aperture centers generated by aperture_array_generator, + with dimensions 2xN for N apertures. - aperturearray is a list of tuples of aperture centers generated by aperture_array_generator + Rshape is a 2 element vector giving the real space dimensions. If not specified, we + take these value from the max along points_array. tol is the tolerance for a displacement between points and centers (in pixels) @@ -188,11 +287,18 @@ def DDFimage(dataset, pointsarray, aperturearray, tol=1): returns a the DDF image as a 2D numpy array """ - image = np.zeros_like(dataset[:, :, 0, 0]) - for aperture_index in tqdmnd(len(aperturearray)): - apertureposition = aperturearray[aperture_index] + + if Rshape is None: + Rshape = ( + np.max(np.max(points_array[:,3])).astype('int')+1, + np.max(np.max(points_array[:,4])).astype('int')+1, + ) + + image = np.zeros(Rshape) + for aperture_index in tqdmnd(len(aperture_positions)): + aperture_position = aperture_positions[aperture_index] intensities = np.vstack( - (pointsarray[:, 2:].T, pointlist_differences(apertureposition, pointsarray)) + (points_array[:, 2:].T, pointlist_differences(aperture_position, points_array)) ).T intensities2 = np.delete(intensities, np.where(intensities[:, 3] > tol), axis=0) for row in range(intensities2[:, 0].shape[0]): From 0d02e3fa643710eee3e1eefb82b1a45d1c97768f Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 10 Aug 2024 15:32:47 -0700 Subject: [PATCH 15/19] Adding ability to plot marker radii in pixels --- .../process/diffraction/digital_dark_field.py | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index e59816980..fa972612d 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -7,6 +7,7 @@ import numpy as np import matplotlib.pyplot as plt +from matplotlib.collections import EllipseCollection from emdfile import tqdmnd from py4DSTEM import show @@ -27,6 +28,7 @@ def aperture_array_generator( plot_result = False, plot_image = None, plot_marker_size = 100, + plot_marker_radius_pixels = None, figsize = (6,6), returnfig = False, **kwargs, @@ -72,10 +74,10 @@ def aperture_array_generator( V, H = shape[0], shape[1] if mode == "single": - aperture_position = [ + aperture_positions = [ (center[0] + s1 * g1[0] + s2 * g2[0], center[1] + s1 * g1[1] + s2 * g2[1]) ] - centered_aperture_position = [ + centered_aperture_positions = [ (s1 * g1[0] + s2 * g2[0], s1 * g1[1] + s2 * g2[1]) ] @@ -125,12 +127,32 @@ def aperture_array_generator( returnfig = True, **kwargs, ) - ax.scatter( - aperture_positions[:,1], - aperture_positions[:,0], - color = (0.0,1.0,0.0,0.3), - s = plot_marker_size, - ) + if plot_marker_size is None or plot_marker_radius_pixels is not None: + offsets = list( + zip( + aperture_positions[:,1], + aperture_positions[:,0], + ), + ) + ax.add_collection( + EllipseCollection( + widths=2.0*plot_marker_radius_pixels, + heights=2.0*plot_marker_radius_pixels, + angles=0, + units='xy', + facecolors=(0.0,1.0,0.0,0.3), + # facecolors=plt.cm.hsv(color), + offsets=offsets, + transOffset=ax.transData, + ), + ) + else: + ax.scatter( + aperture_positions[:,1], + aperture_positions[:,0], + color = (0.0,1.0,0.0,0.3), + s = plot_marker_size, + ) if returns == "both": if returnfig: From 18ea00cd48e23ca54ecd34bc9a1ae2392d982030 Mon Sep 17 00:00:00 2001 From: cophus Date: Sat, 10 Aug 2024 15:43:19 -0700 Subject: [PATCH 16/19] adding docstring, black formatting --- py4DSTEM/process/diffraction/__init__.py | 2 +- .../process/diffraction/digital_dark_field.py | 168 +++++++++++------- 2 files changed, 108 insertions(+), 62 deletions(-) diff --git a/py4DSTEM/process/diffraction/__init__.py b/py4DSTEM/process/diffraction/__init__.py index 40fa63dfd..4601e06a4 100644 --- a/py4DSTEM/process/diffraction/__init__.py +++ b/py4DSTEM/process/diffraction/__init__.py @@ -2,4 +2,4 @@ from py4DSTEM.process.diffraction.flowlines import * from py4DSTEM.process.diffraction.tdesign import * from py4DSTEM.process.diffraction.crystal_phase import * -from py4DSTEM.process.diffraction.digital_dark_field import * \ No newline at end of file +from py4DSTEM.process.diffraction.digital_dark_field import * diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index fa972612d..64cb75faf 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -1,4 +1,3 @@ - """ A general note on all these functions is that they are designed for use with rotation calibration into the pointslist. However, they have to date only been used with the Qx and Qy in pixels and not calibrated into reciprocal units. @@ -11,6 +10,7 @@ from emdfile import tqdmnd from py4DSTEM import show + def aperture_array_generator( shape, center, @@ -25,12 +25,12 @@ def aperture_array_generator( n1lims=(-5, 5), n2lims=(-5, 5), returns="both", - plot_result = False, - plot_image = None, - plot_marker_size = 100, - plot_marker_radius_pixels = None, - figsize = (6,6), - returnfig = False, + plot_result=False, + plot_image=None, + plot_marker_size=100, + plot_marker_radius_pixels=None, + figsize=(6, 6), + returnfig=False, **kwargs, ): """ @@ -65,10 +65,60 @@ def aperture_array_generator( returns sets whether the function returns: 'both' = a centered array and an array in raw pixel numbers (uncentered) - 'centered' = just the version centered on [0,0] - in all cases, the return is a list of (Qx,Qy) tuples + + Parameters + ---------- + shape: tuple, list, np.array + 2-element vector of the diffraction space shape. + center: tuple, list, np.array + 2-element vector of the center coordinate. + pad: float + Spacing around the boundaries where no apertures are generated. + mode: string + 'single', '2-beam' or 'array' depending on desired aperture configuration. + g1: tuple, list, np.array + 2-element vector for first g vector. + g2: tuple, list, np.array + 2-element vector for second g vector. + s1: int + Multiples of g1 to position aperture. + s2: int + Multiples of g2 to position aperture. + r1: float + inner radius + r2: float + outer radius + n1lims: (int,int) + Limits for the g1 vector. + n2lims=(int,int) + Limits for the g2 vector. + returns: + What function returns. + plot_result: bool + Plot the aperture array + plot_image: bool + Image to show in background of the aperture array + plot_marker_size: float + Marker size in points (standard matplotlib) + plot_marker_radius_pixels: float + Marker radius in pixels. + figsize: (float, float) + Figure size. + returnfig: bool + Set to true to return the figure handles. + + Returns + ---------- + aperture_positions: + (N,2) array containing the aperture positions in the image coordinate system. + centered_aperture_positions: + (N,2) array containing the aperture positions in a centered coordinate system. + fig, ax: + Figure and axes handles for plot. + + """ V, H = shape[0], shape[1] @@ -121,26 +171,26 @@ def aperture_array_generator( if plot_image is None: plot_image = np.zeros(shape) - fig,ax = show( + fig, ax = show( plot_image, - ticks = False, - returnfig = True, + ticks=False, + returnfig=True, **kwargs, ) if plot_marker_size is None or plot_marker_radius_pixels is not None: offsets = list( zip( - aperture_positions[:,1], - aperture_positions[:,0], + aperture_positions[:, 1], + aperture_positions[:, 0], ), ) ax.add_collection( EllipseCollection( - widths=2.0*plot_marker_radius_pixels, - heights=2.0*plot_marker_radius_pixels, - angles=0, - units='xy', - facecolors=(0.0,1.0,0.0,0.3), + widths=2.0 * plot_marker_radius_pixels, + heights=2.0 * plot_marker_radius_pixels, + angles=0, + units="xy", + facecolors=(0.0, 1.0, 0.0, 0.3), # facecolors=plt.cm.hsv(color), offsets=offsets, transOffset=ax.transData, @@ -148,12 +198,12 @@ def aperture_array_generator( ) else: ax.scatter( - aperture_positions[:,1], - aperture_positions[:,0], - color = (0.0,1.0,0.0,0.3), - s = plot_marker_size, + aperture_positions[:, 1], + aperture_positions[:, 0], + color=(0.0, 1.0, 0.0, 0.3), + s=plot_marker_size, ) - + if returns == "both": if returnfig: return aperture_positions, centered_aperture_positions, fig, ax @@ -161,7 +211,7 @@ def aperture_array_generator( return aperture_positions, centered_aperture_positions elif returns == "centered": if returnfig: - return centered_aperture_positions, fig, ax + return centered_aperture_positions, fig, ax else: return centered_aperture_positions else: @@ -171,27 +221,27 @@ def aperture_array_generator( def aperture_array_subtract( aperture_positions, aperture_positions_delete, - tol = 1.0, - plot_result = False, - plot_image = None, - plot_marker_size = 100, - figsize = (6,6), - returnfig = False, + tol=1.0, + plot_result=False, + plot_image=None, + plot_marker_size=100, + figsize=(6, 6), + returnfig=False, **kwargs, - ): +): """ This function takes in a set of aperture positions, and removes apertures within the user-specified tolerance from aperture_array_delete. """ - # Determine which apertures to keep - keep = np.zeros(aperture_positions.shape[0],dtype='bool') + # Determine which apertures to keep + keep = np.zeros(aperture_positions.shape[0], dtype="bool") tol2 = tol**2 for a0 in range(aperture_positions.shape[0]): dist2_min = np.min( np.sum( - (aperture_positions[a0] - aperture_positions_delete)**2, - axis = 1, + (aperture_positions[a0] - aperture_positions_delete) ** 2, + axis=1, ), ) if dist2_min > tol2: @@ -203,38 +253,36 @@ def aperture_array_subtract( if plot_result: aperture_positions_del = aperture_positions[np.logical_not(keep)] - if plot_image is None: plot_image = np.zeros(shape) - fig,ax = show( + fig, ax = show( plot_image, - ticks = False, - returnfig = True, + ticks=False, + returnfig=True, **kwargs, ) ax.scatter( - aperture_positions_del[:,1], - aperture_positions_del[:,0], - color = (1.0,0.0,0.0,0.3), - s = plot_marker_size, + aperture_positions_del[:, 1], + aperture_positions_del[:, 0], + color=(1.0, 0.0, 0.0, 0.3), + s=plot_marker_size, ) ax.scatter( - aperture_positions_new[:,1], - aperture_positions_new[:,0], - color = (0.0,1.0,0.0,0.3), - s = plot_marker_size, + aperture_positions_new[:, 1], + aperture_positions_new[:, 0], + color=(0.0, 1.0, 0.0, 0.3), + s=plot_marker_size, ) return aperture_positions_new - def pointlist_to_array( - bplist, - # idim, + bplist, + # idim, # jdim - ): +): """ This function turns the py4dstem pointslist object to a simple numpy array that is more convenient for rapid array processing in numpy @@ -288,12 +336,7 @@ def pointlist_differences(aperture_position, points_array): return diff -def DDFimage( - points_array, - aperture_positions, - Rshape = None, - tol=1 -): +def DDFimage(points_array, aperture_positions, Rshape=None, tol=1): """ points_array is an array of points as calculated by pointlist_to_array @@ -312,15 +355,18 @@ def DDFimage( if Rshape is None: Rshape = ( - np.max(np.max(points_array[:,3])).astype('int')+1, - np.max(np.max(points_array[:,4])).astype('int')+1, + np.max(np.max(points_array[:, 3])).astype("int") + 1, + np.max(np.max(points_array[:, 4])).astype("int") + 1, ) image = np.zeros(Rshape) for aperture_index in tqdmnd(len(aperture_positions)): aperture_position = aperture_positions[aperture_index] intensities = np.vstack( - (points_array[:, 2:].T, pointlist_differences(aperture_position, points_array)) + ( + points_array[:, 2:].T, + pointlist_differences(aperture_position, points_array), + ) ).T intensities2 = np.delete(intensities, np.where(intensities[:, 3] > tol), axis=0) for row in range(intensities2[:, 0].shape[0]): From c37803923bd3c2d9ff3cef1ba9c1e096d4dc732d Mon Sep 17 00:00:00 2001 From: maclariz Date: Wed, 28 Aug 2024 17:29:42 +0100 Subject: [PATCH 17/19] Update digital_dark_field.py Updated docstrings --- .../process/diffraction/digital_dark_field.py | 88 ++++++++++++++----- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index 64cb75faf..f82e03322 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -117,8 +117,6 @@ def aperture_array_generator( (N,2) array containing the aperture positions in a centered coordinate system. fig, ax: Figure and axes handles for plot. - - """ V, H = shape[0], shape[1] @@ -232,6 +230,32 @@ def aperture_array_subtract( """ This function takes in a set of aperture positions, and removes apertures within the user-specified tolerance from aperture_array_delete. + + Parameters + ---------- + aperture_positions: tuple, list, np.array + 2-element vector(s) of the diffraction space shape of positions of apertures + aperture_positions: tuple, list, np.array + 2-element vector(s) of the diffraction space shape of positions of apertures to remove from the list + tol: float + a single number giving the tolerance for a maximum distance between aperture positions in the two lists to still be considered a match + plot_result: bool + Plot the aperture array + plot_marker_size: float + Marker size in points (standard matplotlib) + plot_marker_radius_pixels: float + Marker radius in pixels. + figsize: (float, float) + Figure size. + returnfig: bool + Set to true to return the figure handles. + + Returns + ---------- + aperture_positions: + (N,2) array containing the aperture positions in the image coordinate system. + fig, ax: + Figure and axes handles for plot. """ # Determine which apertures to keep @@ -287,16 +311,23 @@ def pointlist_to_array( This function turns the py4dstem pointslist object to a simple numpy array that is more convenient for rapid array processing in numpy - idim and jdim are the dimensions in the Rx and Ry directions + idim and jdim are the dimensions in the Rx and Ry directions and are determined from the input + + Parameters + ---------- + bplist: pointslist + py4dstem pointslist - returns an array called points_array - This will be an 2D numpy array of n points x 5 columns: + Returns + ---------- + points_array: numpy array + This will be an 2D numpy array of n points x 5 columns: qx qy I Rx Ry - """ + """ for i, j in tqdmnd(bplist.Rshape[0], bplist.Rshape[1]): if i == j == 0: points_array = np.array( @@ -324,10 +355,20 @@ def pointlist_to_array( def pointlist_differences(aperture_position, points_array): """ - calculates differences between a specific aperture position - and a whole list of detected points for a dataset (as an array) + calculates Euclidean distances between a specific aperture position + and a whole list of detected points for a dataset + + Parameters + ---------- + aperture_position: tuple + 2-element vector of the diffraction space shape of a position of an aperture + points_array: numpy array + as produced by pointlist_to_array and defined in docstring for that function - returns the Euclidean distances as a 1D numpy array + Returns + ---------- + diff: numpy array + the Euclidean distances as a 1D numpy array """ subtractor = np.array( [[aperture_position[0], aperture_position[1]] * points_array.shape[0]] @@ -338,19 +379,26 @@ def pointlist_differences(aperture_position, points_array): def DDFimage(points_array, aperture_positions, Rshape=None, tol=1): """ - points_array is an array of points as calculated by pointlist_to_array - - aperture_positions is a numpy.array of aperture centers generated by aperture_array_generator, - with dimensions 2xN for N apertures. - - Rshape is a 2 element vector giving the real space dimensions. If not specified, we - take these value from the max along points_array. - - tol is the tolerance for a displacement between points and centers (in pixels) + Calculates a Digital Dark Field image from a list of detected diffraction peak positions in a points_array and a list of aperture_positions, within a defined matching tolerance + + This does rely on the pointslist_differences function for the calculation - this does rely on the pointslist_differences function + Parameters + ---------- + points_array: numpy array + as produced by pointlist_to_array and defined in docstring for that function + aperture_positions: tuple, list, np.array + 2-element vector(s) of the diffraction space shape of positions of apertures + Rshape: tuple, list, array + a 2 element vector giving the real space dimensions. If not specified, this is determined from the max along points_array + tol: float + the tolerance in pixels or calibrated units for a point in the points_array to be considered to match to an aperture position in the aperture_positions array - returns a the DDF image as a 2D numpy array + Returns + ---------- + image: numpy array + 2D numpy array with dimensions determined by Rshape + """ if Rshape is None: From f2c5d21c414c07e4add4f05360db6f816a9a8332 Mon Sep 17 00:00:00 2001 From: Stephanie Ribet Date: Wed, 28 Aug 2024 14:33:54 -0700 Subject: [PATCH 18/19] small changes to calibration for pointlist_to_array --- .../process/diffraction/digital_dark_field.py | 79 +++++++++++++------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index f82e03322..15d6daea1 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -117,6 +117,7 @@ def aperture_array_generator( (N,2) array containing the aperture positions in a centered coordinate system. fig, ax: Figure and axes handles for plot. + """ V, H = shape[0], shape[1] @@ -230,7 +231,7 @@ def aperture_array_subtract( """ This function takes in a set of aperture positions, and removes apertures within the user-specified tolerance from aperture_array_delete. - + Parameters ---------- aperture_positions: tuple, list, np.array @@ -253,7 +254,7 @@ def aperture_array_subtract( Returns ---------- aperture_positions: - (N,2) array containing the aperture positions in the image coordinate system. + (N,2) array containing the aperture positions in the image coordinate system. fig, ax: Figure and axes handles for plot. """ @@ -303,20 +304,28 @@ def aperture_array_subtract( def pointlist_to_array( - bplist, - # idim, - # jdim + bragg_peaks, + center=None, + ellipse=None, + pixel=None, + rotate=None, ): """ - This function turns the py4dstem pointslist object to a simple numpy array that is more + This function turns the py4DSTEM BraggVectors to a simple numpy array that is more convenient for rapid array processing in numpy - idim and jdim are the dimensions in the Rx and Ry directions and are determined from the input - Parameters ---------- - bplist: pointslist - py4dstem pointslist + bragg_peaks: BraggVectors + py4DSTEM BraggVectors + center: bool + If True, applies center calibration to bragg_peaks + ellipse: bool + if True, applies elliptical calibration to bragg_peaks + pixel: bool + if True, applies pixel calibration to bragg_peaks + rotate: bool + if True, applies rotational calibration to bragg_peaks Returns ---------- @@ -327,29 +336,51 @@ def pointlist_to_array( I Rx Ry - """ - for i, j in tqdmnd(bplist.Rshape[0], bplist.Rshape[1]): + """ + if center is None: + center = bragg_peaks.calstate["center"] + + if ellipse is None: + ellipse = bragg_peaks.calstate["ellipse"] + + if pixel is None: + pixel = bragg_peaks.calstate["pixel"] + + if rotate is None: + rotate = bragg_peaks.calstate["rotate"] + + for i, j in tqdmnd(bragg_peaks.Rshape[0], bragg_peaks.Rshape[1]): + vectors = bragg_peaks.get_vectors( + scan_x=i, + scan_y=j, + center=center, + ellipse=ellipse, + pixel=pixel, + rotate=rotate, + ) + if i == j == 0: points_array = np.array( [ - bplist.cal[i, j].qx, - bplist.cal[i, j].qy, - bplist.cal[i, j].I, - bplist.cal[i, j].qx.shape[0] * [i], - bplist.cal[i, j].qx.shape[0] * [j], + vectors.qx, + vectors.qy, + vectors.I, + vectors.qx.shape[0] * [i], + vectors.qx.shape[0] * [j], ] ).T else: nps = np.array( [ - bplist.cal[i, j].qx, - bplist.cal[i, j].qy, - bplist.cal[i, j].I, - bplist.cal[i, j].qx.shape[0] * [i], - bplist.cal[i, j].qx.shape[0] * [j], + vectors.qx, + vectors.qy, + vectors.I, + vectors.qx.shape[0] * [i], + vectors.qx.shape[0] * [j], ] ).T points_array = np.vstack((points_array, nps)) + return points_array @@ -380,7 +411,7 @@ def pointlist_differences(aperture_position, points_array): def DDFimage(points_array, aperture_positions, Rshape=None, tol=1): """ Calculates a Digital Dark Field image from a list of detected diffraction peak positions in a points_array and a list of aperture_positions, within a defined matching tolerance - + This does rely on the pointslist_differences function for the calculation Parameters @@ -398,7 +429,7 @@ def DDFimage(points_array, aperture_positions, Rshape=None, tol=1): ---------- image: numpy array 2D numpy array with dimensions determined by Rshape - + """ if Rshape is None: From 0cb2ecd30146754b537904058fcb09c4272fdf4f Mon Sep 17 00:00:00 2001 From: Stephanie Ribet Date: Wed, 28 Aug 2024 14:52:51 -0700 Subject: [PATCH 19/19] small bug in plotting --- py4DSTEM/process/diffraction/digital_dark_field.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/py4DSTEM/process/diffraction/digital_dark_field.py b/py4DSTEM/process/diffraction/digital_dark_field.py index 15d6daea1..cf91bfb22 100644 --- a/py4DSTEM/process/diffraction/digital_dark_field.py +++ b/py4DSTEM/process/diffraction/digital_dark_field.py @@ -5,7 +5,6 @@ """ import numpy as np -import matplotlib.pyplot as plt from matplotlib.collections import EllipseCollection from emdfile import tqdmnd from py4DSTEM import show @@ -220,6 +219,7 @@ def aperture_array_generator( def aperture_array_subtract( aperture_positions, aperture_positions_delete, + shape, tol=1.0, plot_result=False, plot_image=None, @@ -238,6 +238,8 @@ def aperture_array_subtract( 2-element vector(s) of the diffraction space shape of positions of apertures aperture_positions: tuple, list, np.array 2-element vector(s) of the diffraction space shape of positions of apertures to remove from the list + shape: tuple, list, np.array + 2-element vector of the diffraction space shape tol: float a single number giving the tolerance for a maximum distance between aperture positions in the two lists to still be considered a match plot_result: bool