├── .gitignore ├── README.md ├── deformation_generation.py ├── measure_deformation.py ├── predic ├── C_First_Order.py ├── DIC_NR_images.py └── __init__.py ├── requirements.txt └── test ├── __main__.py ├── test_C_First_Order.py ├── test_DIC_NR_images.py └── testing_images ├── def50.bmp ├── def500.bmp ├── ref50.bmp ├── ref500.bmp └── test_image_1.bmp /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | # Mac 127 | *.DS_Store 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PReDIC 2 | (**P**ython **Re**written **Digital Image Correlation**) 3 | 4 | Digital Image Correlation in Python 3. Using spline interpolation and Newton-Raphson convergence. 5 | All contributors give full credit to Dr Ghulam Mubashar Hassan for providing the original matlab code on which this program is based. 6 | 7 | ## Setup 8 | To setup & install dependencies we will create a virtual environment and install from `requirements.txt`. 9 | 10 | First run 11 | `python3 -m venv venv` 12 | to create a virtual environment, then 13 | `python3 -m pip install -r requirements.txt` 14 | to install the necessary packages into the virtual environment. 15 | 16 | ## Using in a program 17 | From the `predic` package, import the class DIC_NR. 18 | 19 | In code you create it, then supply it with the parameters in `set_parameters` to calculate deformation from. 20 | 21 | These parameters are the `reference image`, `deformed image`, `subset size`, and `initial guess`. 22 | 23 | After that, the method `calculate` will return the results as a numpy array. 24 | 25 | For example: 26 | ```python3 27 | import predic as dm 28 | 29 | dic = dm.DIC_NR() 30 | dic.set_parameters("ref_image.bmp", "def_image.bmp", 11, [0, 0]) 31 | results = dic.calculate() 32 | 33 | print(results) 34 | ``` 35 | 36 | ## Using from the command line 37 | A helpful script is included in the root directory of this repo named `measure_deformation.py`. 38 | 39 | To run it with default settings, mark it as executable and then use `./measure_deformation.py ref_image.bmp def_image.bmp`. 40 | 41 | For an explanation of all the parameters run `./measure_deformation.py -h`. 42 | 43 | ## Testing 44 | Run `python test` to run the full test suite. 45 | 46 | For testing a specific file you can use `python test Test_C_First_Order` or `python test Test_DIC_NR`. 47 | -------------------------------------------------------------------------------- /deformation_generation.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | from PIL import Image 4 | from math import * 5 | import math 6 | import os 7 | import cairo 8 | 9 | #Requires pycairo & pkg-config 10 | #Refer here: https://pycairo.readthedocs.io/en/latest/getting_started.html 11 | 12 | #These functions allow for the generation of speckle images. 13 | #Images are generated via the use of a vector graphics library pycairo. 14 | #This allows for image transformations without the need for interpolation or resampling error consideration. 15 | #Precision necessary when measuring accuracy of DIC algorithm to sub-pixel level. 16 | #The x and y displacements can then be calculated precisely according to the transformation. 17 | 18 | #Matrix transforms used for deformations 19 | #[a1, c3, e5, 20 | # b2, d4, f6, 21 | # 0, 0, 1] these numbers relate to the .Matrix(1,2,3,4,5,6) variables 22 | 23 | # x' = a1*x + c3*y + e5 24 | # y' = d4*y + b2*x + f6 25 | 26 | # (a1&d4):provide scale 27 | # (c3&b2):provide shear 28 | # (ef+f6):provide translation 29 | 30 | 31 | #Function will generate reference image, deformed image and provide x&y translation arrays 32 | def generate_images(image_size,seed,a1,b2,c3,d4,e5,f6): 33 | gen_ref(image_size, seed, a1,b2,c3,d4,e5,f6) 34 | gen_def(image_size, seed, a1,b2,c3,d4,e5,f6) 35 | calc_translations(image_size, seed, a1,b2,c3,d4,e5,f6) 36 | 37 | #Will draw speckles using uniform random distribution, change seed for different speckle pattern 38 | def draw_speckles(context, seed): 39 | 40 | #Create white background 41 | context.set_source_rgb(1, 1, 1) 42 | context.rectangle(0, 0, 1, 1) # Rectangle(x0, y0, x1, y1) 43 | context.fill() 44 | 45 | #Change colour to black 46 | context.set_source_rgb(0,0,0) 47 | context.move_to(0, 0) 48 | 49 | size = 3000 # The number of speckles 50 | 51 | # To make the images the same each time (matching ref & def) 52 | np.random.seed(seed= seed) 53 | min = 0 54 | max = 1 55 | 56 | # Use a uniform random distribution 57 | initial_x = np.random.uniform(min, max, size) 58 | initial_y = np.random.uniform(min, max, size) 59 | 60 | for i in range(size): 61 | #circle (xc, yc, radius, start_angle, end_angle) 62 | context.arc(initial_x[i], initial_y[i], 0.01, 0, 2*math.pi) 63 | context.fill() 64 | 65 | #Calculates x and y displacements between reference image and deformed image, according to transformation matrix 66 | def calc_translations(image_size,seed,a1,b2,c3,d4,e5,f6): 67 | 68 | #create transformation matrix 69 | trans_matrix = [[a1,c3],[b2,d4]] 70 | 71 | #original x and y coordinates 72 | orig_y, orig_x = np.mgrid[1:image_size + 1,1:image_size+1] 73 | 74 | #x,y pairs to be transformed 75 | xy_points = np.mgrid[1:image_size + 1,1:image_size + 1].reshape((2,image_size*image_size)) 76 | xy_points[[1,0]] = xy_points[[0,1]] 77 | 78 | #transformed x,y pairs 79 | new_points = np.dot(trans_matrix,xy_points) 80 | x, y = new_points.reshape((2,image_size,image_size)) 81 | 82 | #add translation element of matrix 83 | x = np.add(x, e5).reshape((image_size,image_size)) 84 | y = np.add(y, f6).reshape((image_size,image_size)) 85 | 86 | #calculate the x and y displacements 87 | xd = (x - orig_x) 88 | yd = (y - orig_y) 89 | 90 | name = filename(image_size,seed,a1,b2,c3,d4,e5,f6) 91 | 92 | x_name = "x_trans_" + name 93 | 94 | y_name = "y_trans_" + name 95 | 96 | savetxt_compact(x_name, xd) 97 | 98 | savetxt_compact(y_name, yd) 99 | 100 | #Generate reference images 101 | def gen_ref(image_size, seed, a1,b2,c3,d4,e5,f6): 102 | WIDTH, HEIGHT = image_size, image_size 103 | 104 | surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) 105 | context = cairo.Context(surface) 106 | 107 | context.scale(WIDTH, HEIGHT) # Normalizing the canvas 108 | 109 | draw_speckles(context, seed) 110 | 111 | context.close_path() 112 | 113 | name = filename(image_size,seed,a1,b2,c3,d4,e5,f6) 114 | 115 | img_name = "ref_" + name + ".bmp" 116 | 117 | write_image(surface, image_size, img_name) 118 | 119 | #Generate deformed images 120 | def gen_def(image_size, seed, a1,b2,c3,d4,e5,f6): 121 | WIDTH, HEIGHT = image_size, image_size 122 | 123 | format = cairo.FORMAT_ARGB32 124 | 125 | surface = cairo.ImageSurface(format, WIDTH, HEIGHT) 126 | context = cairo.Context(surface) 127 | 128 | # Normalizing the canvas 129 | context.scale(WIDTH, HEIGHT) 130 | 131 | #Matrix transform 132 | #[a1, c3, e5, 133 | # b2, d4, f6, 134 | # 0, 0, 1] these numbers relate to the .Matrix(1,2,3,4,5,6) variables 135 | 136 | # x' = a1*x + c3*y + e5 137 | # y' = d4*y + b2*x + f6 138 | 139 | # (a1&d4):provide scale 140 | # (c3&b2):provide shear 141 | # (ef+f6):provide translation 142 | 143 | mtx = cairo.Matrix(a1,b2,c3,d4,e5,f6) 144 | context.transform(mtx) 145 | 146 | draw_speckles(context, seed) 147 | 148 | context.close_path() 149 | 150 | name = filename(image_size,seed,a1,b2,c3,d4,e5,f6) 151 | 152 | img_name = "def_" + name + ".bmp" 153 | 154 | write_image(surface, image_size, img_name) 155 | 156 | def filename(image_size,seed,a1,b2,c3,d4,e5,f6): 157 | matrix = '{:.2f}'.format(a1) + "_" + '{:.2f}'.format(b2) + "_" + '{:.2f}'.format(c3) + "_" + '{:.2f}'.format(d4) + "_" + '{:.2f}'.format(e5) + "_" + '{:.2f}'.format(f6) 158 | filename = str(image_size) + "_" + str(seed) + "_" + matrix.replace(".", "-") 159 | return filename 160 | 161 | #Writes image to /img_gen directory in format as specified by filename (currently works for .bmp) 162 | def write_image(surface, image_size, file_name): 163 | save_dir = os.path.dirname(os.path.realpath(__file__)) + "/img_gen" 164 | 165 | if not os.path.exists(save_dir): 166 | os.makedirs(save_dir) 167 | 168 | buf = surface.get_data() 169 | data = np.ndarray(shape=(image_size, image_size), dtype=np.uint32,buffer=buf) 170 | 171 | out = Image.fromarray(data, 'RGBA') 172 | out.save(save_dir +"/"+file_name) 173 | 174 | def savetxt_compact(fname, x, fmt="%.6g", delimiter=','): 175 | with open(f"compact_{fname}.csv", 'w+') as fh: 176 | for row in x: 177 | line = delimiter.join("0" if value == 0 else fmt % value for value in row) 178 | fh.write(line + '\n') 179 | 180 | def main(): 181 | 182 | if(len(sys.argv) == 1): 183 | generate_images(50,19,1.1,0.0,0.0,1,0.0,0.0) 184 | 185 | elif (len(sys.argv) == 9) : 186 | try: 187 | generate_images(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4],sys.argv[5],sys.argv[6],sys.argv[7],sys.argv[8]) 188 | except TypeError: 189 | print("Type Error, input parameters are : int image_size, int seed, float a1, float b2, float c3, float d4, float e5, float f6. See documentation for specification of matrix elements.") 190 | else: 191 | print("Error, input parameters are : int image_size, int seed, float a1, float b2, float c3, float d4, float e5, float f6. See documentation for specification of matrix elements.") 192 | 193 | else : 194 | print("Requires parameters: image_size, seed, a1, b2, c3, d4, e5, f6. Default generation is: 50,19,1.1,0.0,0.0,1,0.0,0.0") 195 | 196 | 197 | 198 | if __name__ == "__main__": 199 | main() 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /measure_deformation.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import argparse 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | import predic as dm 7 | 8 | def main(): 9 | parser = argparse.ArgumentParser(description="Measure deformation between two images, given a subset size and optional initial guess.") 10 | 11 | parser.add_argument("ref_image", metavar="ref_image", type=str, 12 | help="The reference image to calculate deformations to") 13 | 14 | parser.add_argument("def_image", metavar="def_image", type=str, 15 | help="The deformed image to calculate deformations from") 16 | 17 | parser.add_argument("-s", "--subset_size", metavar="N", type=int, 18 | nargs="?", default=21, required=False, 19 | help="The subset size to use. Default=11") 20 | 21 | parser.add_argument("-i", "--initial_guess", metavar="N", type=float, 22 | nargs=2, default=[0.0, 0.0], required=False, 23 | action="store", dest="ini_guess", 24 | help="""Set the initial guess to work from. 25 | Defaults to [0.0, 0.0]. 26 | Example: -i 1.0 1.0""") 27 | 28 | parser.add_argument("-d", "--debug", dest="debug_mode", action="store_true", 29 | help="Use debug print mode.") 30 | 31 | 32 | parser.add_argument("-p", "--parallel", dest="parallel_mode", action="store_true", 33 | help="Run in parallel *Please note that to run in parallel at larger image sizes will require a manual tweak of the file in your sitepackages/joblib/parallel.py line 475 change max_nbytes=1M to max_nbytes=50M or larger*") 34 | 35 | parser.add_argument("-o", "--output", dest="output_file", 36 | type=str, required=False, 37 | help="File to write formatted csv output.") 38 | 39 | 40 | parser.add_argument("-v", "--visualise", dest="visualise", action="store_true", 41 | help="Automatically use matplotlib to visualise the output.") 42 | 43 | args = parser.parse_args() 44 | 45 | 46 | dic = dm.DIC_NR(args.debug_mode, args.parallel_mode) 47 | 48 | 49 | dic.set_parameters(args.ref_image, args.def_image, args.subset_size, args.ini_guess) 50 | results = dic.calculate() 51 | 52 | x,y,z = results.shape 53 | output = np.swapaxes(results, 2, 1).reshape((x, y*z), order="A") 54 | 55 | def vis_plotter(results, arr_name): 56 | x = results[:,:,0] 57 | y = results[:,:,0] 58 | residual = (x**2 + y**2 )**0.5 59 | 60 | ig, ax = plt.subplots() 61 | 62 | plt.subplot(1, 2, 1) 63 | plt.title(arr_name + " X Deformations") 64 | imgplot = plt.imshow(x) 65 | imgplot.set_cmap('YlGnBu') 66 | ax.set_xlabel("X") 67 | ax.set_ylabel("Y") 68 | plt.colorbar() 69 | 70 | plt.subplot(1, 2, 2) 71 | plt.title(arr_name + " Y Deformations") 72 | imgplot = plt.imshow(y) 73 | imgplot.set_cmap('YlGnBu') 74 | ax.set_xlabel("X") 75 | ax.set_ylabel("Y") 76 | plt.colorbar() 77 | 78 | plt.show() 79 | plt.close() 80 | 81 | if args.visualise: 82 | vis_plotter(results, args.def_image) 83 | 84 | if args.output_file != None: 85 | with open(args.output_file, 'w+') as fh: 86 | for row in output: 87 | line = ",".join("0" if n == 0 else f"{n:.6g}" for n in row) 88 | fh.write(line + '\n') 89 | print(f"Result written to {args.output_file}") 90 | else: 91 | for row in output: 92 | print(",".join("0" if n == 0 else f"{n:.6g}" for n in row)) 93 | 94 | if __name__ == '__main__': 95 | main() 96 | -------------------------------------------------------------------------------- /predic/C_First_Order.py: -------------------------------------------------------------------------------- 1 | from math import floor 2 | import numpy as np 3 | 4 | class C_First_Order(object): 5 | def set_image(self, ref_image, subset_size): 6 | self.ref_image = ref_image 7 | self.subset_size = subset_size 8 | 9 | 10 | def set_splines(self, def_interp, def_interp_x, def_interp_y): 11 | self.def_interp = def_interp 12 | self.def_interp_x = def_interp_x 13 | self.def_interp_y = def_interp_y 14 | 15 | 16 | def define_deformed_subset(self, q, Xp, Yp): 17 | half_subset = floor(self.subset_size / 2) 18 | 19 | i = np.arange(-half_subset, half_subset + 1, dtype=int) 20 | j = np.arange(-half_subset, half_subset + 1, dtype=int) 21 | 22 | self.I_matrix, self.J_matrix = np.meshgrid(i, j) 23 | 24 | self.I = self.I_matrix.flatten() 25 | self.J = self.J_matrix.flatten() 26 | 27 | u = q[0] 28 | v = q[1] 29 | du_dx = q[2] 30 | dv_dy = q[3] 31 | du_dy = q[4] 32 | dv_dx = q[5] 33 | 34 | self.X = Xp + u + self.I + np.multiply(self.I, du_dx) + np.multiply(self.J, du_dy) 35 | self.Y = Yp + v + self.J + np.multiply(self.J, dv_dy) + np.multiply(self.I, dv_dx) 36 | 37 | 38 | def calculate(self, q, Xp, Yp, nargout=3): 39 | C = 0.0 40 | GRAD = 0.0 41 | HESS = 0.0 42 | 43 | half_subset = floor(self.subset_size / 2) 44 | 45 | self.define_deformed_subset(q, Xp, Yp) 46 | 47 | g = self.def_interp.ev(self.Y, self.X) 48 | 49 | y0 = Yp - half_subset 50 | y1 = Yp + half_subset+1 51 | 52 | x0 = Xp - half_subset 53 | x1 = Xp + half_subset+1 54 | 55 | reference_subset = self.ref_image[y0:y1, x0:x1, 0] 56 | f = reference_subset.flatten() 57 | 58 | SS_f_g = np.sum(np.sum(np.square((f-g)))) 59 | SS_f_sq = np.sum(np.sum(np.square(f))) 60 | 61 | if(SS_f_sq == 0): 62 | raise error("Reference subset contains no image data, a larger subset is necessary to capture more speckle information.") 63 | 64 | C = np.divide(SS_f_g, SS_f_sq) 65 | 66 | if nargout > 1: 67 | 68 | dg_dX = self.def_interp.ev(self.Y, self.X, 0, 1) 69 | dg_dY = self.def_interp.ev(self.Y, self.X, 1, 0) 70 | 71 | dX_du = 1 72 | dX_dv = 0 73 | dX_dudx = self.I 74 | dX_dudy = self.J 75 | dX_dvdx = 0 76 | dX_dvdy = 0 77 | 78 | dY_du = 0 79 | dY_dv = 1 80 | dY_dudx = 0 81 | dY_dudy = 0 82 | dY_dvdy = self.J 83 | dY_dvdx = self.I 84 | 85 | dg_du = np.multiply(dg_dX, dX_du) + np.multiply(dg_dY, dY_du) 86 | dg_dv = np.multiply(dg_dX, dX_dv) + np.multiply(dg_dY, dY_dv) 87 | 88 | dg_dudx = np.multiply(dg_dX, dX_dudx) + np.multiply(dg_dY, dY_dudx) 89 | dg_dvdy = np.multiply(dg_dX, dX_dvdy) + np.multiply(dg_dY, dY_dvdy) 90 | dg_dudy = np.multiply(dg_dX, dX_dudy) + np.multiply(dg_dY, dY_dudy) 91 | dg_dvdx = np.multiply(dg_dX, dX_dvdx) + np.multiply(dg_dY, dY_dvdx) 92 | 93 | dC_du = np.sum(np.sum(np.multiply((g-f), dg_du))) 94 | dC_dv = np.sum(np.sum(np.multiply(g-f, dg_dv))) 95 | dC_dudx = np.sum(np.sum(np.multiply(g-f, dg_dudx))) 96 | dC_dvdy = np.sum(np.sum(np.multiply(g-f, dg_dvdy))) 97 | dC_dudy = np.sum(np.sum(np.multiply(g-f, dg_dudy))) 98 | dC_dvdx = np.sum(np.sum(np.multiply(g-f, dg_dvdx))) 99 | 100 | GRAD = np.multiply(2/SS_f_sq, np.array([dC_du, dC_dv, dC_dudx, dC_dvdy, dC_dudy, dC_dvdx])) 101 | 102 | if nargout > 2: 103 | d2C_du2 = np.sum(np.sum(np.multiply(dg_du, dg_du))) 104 | d2C_dv2 = np.sum(np.sum(np.multiply(dg_dv, dg_dv))) 105 | d2C_dudx2 = np.sum(np.sum(np.multiply(dg_dudx, dg_dudx))) 106 | d2C_dvdy2 = np.sum(np.sum(np.multiply(dg_dvdy, dg_dvdy))) 107 | d2C_dudy2 = np.sum(np.sum(np.multiply(dg_dudy, dg_dudy))) 108 | d2C_dvdx2 = np.sum(np.sum(np.multiply(dg_dvdx, dg_dvdx))) 109 | 110 | d2C_dudv = np.sum(np.sum(np.multiply(dg_du, dg_dv))) 111 | d2C_dududx = np.sum(np.sum(np.multiply(dg_du, dg_dudx))) 112 | d2C_dudvdy = np.sum(np.sum(np.multiply(dg_du, dg_dvdy))) 113 | d2C_dududy = np.sum(np.sum(np.multiply(dg_du, dg_dudy))) 114 | d2C_dudvdx = np.sum(np.sum(np.multiply(dg_du, dg_dvdx))) 115 | 116 | d2C_dvdudx = np.sum(np.sum(np.multiply(dg_dv, dg_dudx))) 117 | d2C_dvdvdy = np.sum(np.sum(np.multiply(dg_dv, dg_dvdy))) 118 | d2C_dvdudy = np.sum(np.sum(np.multiply(dg_dv, dg_dudy))) 119 | d2C_dvdvdx = np.sum(np.sum(np.multiply(dg_dv, dg_dvdx))) 120 | 121 | d2C_dudxdvdy = np.sum(np.sum(np.multiply(dg_dudx, dg_dvdy))) 122 | d2C_dudxdudy = np.sum(np.sum(np.multiply(dg_dudx, dg_dudy))) 123 | d2C_dudxdvdx = np.sum(np.sum(np.multiply(dg_dudx, dg_dvdx))) 124 | 125 | d2C_dvdydudy = np.sum(np.sum(np.multiply(dg_dvdy, dg_dudy))) 126 | d2C_dvdydvdx = np.sum(np.sum(np.multiply(dg_dvdy, dg_dvdx))) 127 | 128 | d2C_dudydvdx = np.sum(np.sum(np.multiply(dg_dudy, dg_dvdx))) 129 | 130 | marr = np.array([ 131 | [d2C_du2, d2C_dudv, d2C_dududx, d2C_dudvdy, d2C_dududy, d2C_dudvdx], 132 | [d2C_dudv, d2C_dv2, d2C_dvdudx, d2C_dvdvdy, d2C_dvdudy, d2C_dvdvdx], 133 | [d2C_dududx, d2C_dvdudx, d2C_dudx2, d2C_dudxdvdy, d2C_dudxdudy, d2C_dudxdvdx], 134 | [d2C_dudvdy, d2C_dvdvdy, d2C_dudxdvdy, d2C_dvdy2, d2C_dvdydudy, d2C_dvdydvdx], 135 | [d2C_dududy, d2C_dvdudy, d2C_dudxdudy, d2C_dvdydudy, d2C_dudy2, d2C_dudydvdx], 136 | [d2C_dudvdx, d2C_dvdvdx, d2C_dudxdvdx, d2C_dvdydvdx, d2C_dudydvdx, d2C_dvdx2] 137 | ]) 138 | HESS = np.multiply((2/SS_f_sq), marr) 139 | 140 | return C, GRAD, HESS -------------------------------------------------------------------------------- /predic/DIC_NR_images.py: -------------------------------------------------------------------------------- 1 | from .C_First_Order import C_First_Order 2 | 3 | from math import floor, ceil 4 | from datetime import datetime 5 | 6 | import numpy as np 7 | from PIL import Image 8 | from scipy.interpolate import RectBivariateSpline 9 | 10 | from joblib import Parallel, delayed 11 | import multiprocessing 12 | 13 | class DIC_NR: 14 | def __init__(self, debug=False, parallel=False): 15 | self.debug = debug 16 | self.parallel = parallel 17 | 18 | def set_parameters(self, ref_img: str, def_img: str, subset_size: int = 21, ini_guess: list = [0, 0]): 19 | # Initialize variables 20 | self.subset_size = subset_size 21 | self.spline_order = 5 22 | self.ini_guess = ini_guess 23 | 24 | # Make sure that the subset size specified is valid (not odd at this point) 25 | if (self.subset_size % 2 == 0): 26 | raise ValueError("Subset size must be odd") 27 | 28 | #Prepare for trouble (load images) (default directory is current working directory) https://stackoverflow.com/questions/12201577/how-can-i-convert-an-rgb-image-into-grayscale-in-python 29 | if type(ref_img) == type("s") or type(def_img) == type("s"): 30 | ref_img = np.array(Image.open(ref_img).convert('LA')) # numpy.array 31 | def_img = np.array(Image.open(def_img).convert('LA')) # numpy.array 32 | 33 | self.ref_image = ref_img 34 | self.def_image = def_img 35 | 36 | # Make it double 37 | self.ref_image = self.ref_image.astype('d') # convert to double 38 | self.def_image = self.def_image.astype('d') # convert to double 39 | 40 | # Obtain the size of the reference image 41 | self.X_size, self.Y_size, self._tmp= self.ref_image.shape 42 | 43 | # Termination condition for newton-raphson iteration 44 | self.Max_num_iter = 40 # maximum number of iterations 45 | self.TOL = [0,0] 46 | self.TOL[0] = 10**(-8) # change in correlation coeffiecient 47 | self.TOL[1] = 10**(-8)/2 # change in sum of all gradients. 48 | 49 | ''' 50 | condition to check that point of interest is not close to edge. Point 51 | must away from edge greater than half of subset adding 15 to it to have 52 | range of initial guess accuracy. 53 | ''' 54 | # +15 due to range of calc in initial_guess 55 | # border is number of places from edge required 56 | border = floor((self.subset_size/2) + 15) 57 | # Due to 0 indexing, min position can be placed "at" border. 58 | self.Xmin = border 59 | self.Ymin = self.Xmin 60 | 61 | #Due to zero indexing, to get last position, subtract 1. 62 | self.Xmax = (self.X_size - 1)-border 63 | self.Ymax = (self.Y_size - 1)-border 64 | 65 | self.Xp = self.Xmin 66 | self.Yp = self.Ymin 67 | 68 | if (self.Xp < self.Xmin) or (self.Yp < self.Ymin) or (self.Xp > self.Xmax) or (self.Yp > self.Ymax): 69 | raise ValueError('Process terminated!!! First point of centre of subset is on the edge of the image. ') 70 | 71 | self.initial_guess() 72 | self.fit_spline() 73 | 74 | self.cfo = C_First_Order() 75 | self.cfo.set_image(self.ref_image, self.subset_size) 76 | self.cfo.set_splines(self.def_interp, self.def_interp_x, self.def_interp_y) 77 | 78 | self.initialised = True 79 | 80 | 81 | def initial_guess(self, ref_img=None, def_img=None): 82 | #For use in testing initial guess 83 | if type(ref_img) == type(None) or type(def_img) == type(None): 84 | ref_img = self.ref_image 85 | def_img = self.def_image 86 | if type(ref_img) == type(None): 87 | raise error("Tried to run initial_guess without supplying ref_img") 88 | 89 | # Automatic Initial Guess 90 | q_0 = np.zeros(6) 91 | q_0[0:2] = self.ini_guess 92 | 93 | if(q_0[0]< -15 or q_0[0] > 15 or q_0[1]< -15 or q_0[1] > 15): 94 | raise error("Initial guess outside of range, must be within +/- 15.") 95 | 96 | # check all values of u & v within +/- 15 range of initial guess 97 | range_ = 15 98 | u_check = np.arange((round(q_0[0]) - range_), (round(q_0[0]) + range_)+1, 1, dtype=int) 99 | v_check = np.arange((round(q_0[1]) - range_), (round(q_0[1]) + range_)+1, 1, dtype=int) 100 | 101 | half_subset = floor(self.subset_size / 2) 102 | 103 | # Define the intensities of the first reference subset 104 | y0 = self.Yp - half_subset 105 | y1 = self.Yp + half_subset 106 | 107 | x0 = self.Xp - half_subset 108 | x1 = self.Xp + half_subset 109 | 110 | subref = ref_img[y0:y1, x0:x1, 0] 111 | 112 | # Preallocate some matrix space 113 | sum_diff_sq = np.zeros((u_check.size, v_check.size)) 114 | 115 | # Check every value of u and v and see where the best match occurs 116 | for iter1 in range(u_check.size): 117 | for iter2 in range(v_check.size): 118 | #Define intensities for deformed subset 119 | y0 = self.Yp - half_subset + v_check[iter2] 120 | y1 = self.Yp + half_subset + v_check[iter2] 121 | 122 | x0 = self.Xp - half_subset + u_check[iter1] 123 | x1 = self.Xp + half_subset + u_check[iter1] 124 | 125 | subdef = def_img[y0:y1, x0:x1, 0] 126 | 127 | sum_diff_sq[iter1, iter2] = np.sum(np.square(subref - subdef)) 128 | 129 | #These indexes locate the u & v value(in the initial range we are checking through) which returned the smallest sum of differences squared. 130 | u_value_index = np.argmin(np.min(sum_diff_sq, axis=1)) 131 | v_value_index = np.argmin(np.min(sum_diff_sq, axis=0)) 132 | 133 | q_0[0] = u_check[u_value_index] 134 | q_0[1] = v_check[v_value_index] 135 | 136 | self.q_k = q_0[0:6] 137 | 138 | 139 | def fit_spline(self): 140 | if type(self.ref_image) == type(None): 141 | raise error("Tried to run fit_spline before supplying parameters") 142 | 143 | # Obtain the size of the reference image 144 | Y_size, X_size,tmp = self.ref_image.shape 145 | 146 | # Define the deformed image's coordinates 147 | X_defcoord = np.arange(0, X_size, dtype=int) # Maybe zero? 148 | Y_defcoord = np.arange(0, Y_size, dtype=int) 149 | 150 | #Fit spline 151 | self.def_interp = RectBivariateSpline(X_defcoord, Y_defcoord, self.def_image[:,:,0], 152 | kx=self.spline_order, ky=self.spline_order) 153 | 154 | #Evaluate derivatives at coordinates 155 | self.def_interp_x = self.def_interp(X_defcoord, Y_defcoord, 0, 1) 156 | self.def_interp_y = self.def_interp(X_defcoord, Y_defcoord, 1, 0) 157 | 158 | 159 | def parallel_calculate_helper(self, xx, yy, calc_start_time): 160 | if yy > self.Ymin: 161 | pass 162 | 163 | #Points for correlation and initializaing the q matrix 164 | self.Xp = xx 165 | self.Yp = yy 166 | 167 | start = datetime.now() - calc_start_time 168 | 169 | # __________OPTIMIZATION ROUTINE: FIND BEST FIT____________________________ 170 | # Initialize some values 171 | n = 0 172 | C_last, GRAD_last, HESS = self.cfo.calculate(self.q_k, self.Xp, self.Yp) # q_k was the result from last point or the user's guess 173 | optim_completed = False 174 | 175 | if np.isnan(abs(np.mean(np.mean(HESS)))): 176 | optim_completed = True 177 | 178 | while not optim_completed: 179 | # Compute the next guess and update the values 180 | delta_q = np.linalg.lstsq(HESS,(-GRAD_last), rcond=None) # Find the difference between q_k+1 and q_k 181 | self.q_k = self.q_k + delta_q[0] #q_k+1 = q_k + delta_q[0] 182 | C, GRAD, HESS = self.cfo.calculate(self.q_k, self.Xp, self.Yp) # Compute new values 183 | 184 | # Add one to the iteration counter 185 | n = n + 1 # Keep track of the number of iterations 186 | 187 | # Check to see if the values have converged according to the stopping criteria 188 | if n > self.Max_num_iter or (abs(C-C_last) < self.TOL[0] and all(abs(delta_q[0]) < self.TOL[1])): #needs to be tested... 189 | optim_completed = True 190 | 191 | C_last = C #Save the C value for comparison in the next iteration 192 | GRAD_last = GRAD # Save the GRAD value for comparison in the next iteration 193 | #_________________________________________________________________________ 194 | end = (datetime.now() - calc_start_time) - start 195 | 196 | #_______STORE RESULTS AND PREPARE INDICES OF NEXT SUBSET__________________ 197 | # Store the current displacements 198 | self.DEFORMATION_PARAMETERS[yy,xx,0] = self.q_k[0] # displacement x 199 | self.DEFORMATION_PARAMETERS[yy,xx,1] = self.q_k[1] # displacement y 200 | self.DEFORMATION_PARAMETERS[yy,xx,2] = self.q_k[2] 201 | self.DEFORMATION_PARAMETERS[yy,xx,3] = self.q_k[3] 202 | self.DEFORMATION_PARAMETERS[yy,xx,4] = self.q_k[4] 203 | self.DEFORMATION_PARAMETERS[yy,xx,5] = self.q_k[5] 204 | self.DEFORMATION_PARAMETERS[yy,xx,6] = 1 - C # correlation co-efficient final value 205 | 206 | # store points which are correlated in reference image i.e. center of subset 207 | self.DEFORMATION_PARAMETERS[yy,xx,7] = self.Xp 208 | self.DEFORMATION_PARAMETERS[yy,xx,8] = self.Yp 209 | 210 | self.DEFORMATION_PARAMETERS[yy,xx,9] = n # number of iterations 211 | self.DEFORMATION_PARAMETERS[yy,xx,10] = start.total_seconds() #t_tmp # time of spline process 212 | self.DEFORMATION_PARAMETERS[yy,xx,11] = end.total_seconds() #t_optim #time of optimization process 213 | 214 | if self.debug: 215 | print(yy) 216 | print(xx) 217 | 218 | return self.DEFORMATION_PARAMETERS[yy,xx] 219 | 220 | 221 | def sequential_calculate(self, calc_start_time): 222 | for yy in range(self.Ymin, self.Ymax + 1): 223 | if yy > self.Ymin: 224 | self.q_k[0:6] = self.DEFORMATION_PARAMETERS[yy - 1, self.Xmin, 0:6] 225 | 226 | for xx in range(self.Xmin, self.Xmax + 1): 227 | #Points for correlation and initializaing the q matrix 228 | self.Xp = xx 229 | self.Yp = yy 230 | 231 | start = datetime.now() - calc_start_time 232 | 233 | # __________OPTIMIZATION ROUTINE: FIND BEST FIT____________________________ 234 | # Initialize some values 235 | n = 0 236 | C_last, GRAD_last, HESS = self.cfo.calculate(self.q_k, self.Xp, self.Yp) # q_k was the result from last point or the user's guess 237 | optim_completed = False 238 | 239 | if np.isnan(abs(np.mean(np.mean(HESS)))): 240 | optim_completed = True 241 | 242 | while not optim_completed: 243 | # Compute the next guess and update the values 244 | delta_q = np.linalg.lstsq(HESS,(-GRAD_last), rcond=None) # Find the difference between q_k+1 and q_k 245 | self.q_k = self.q_k + delta_q[0] #q_k+1 = q_k + delta_q[0] 246 | C, GRAD, HESS = self.cfo.calculate(self.q_k, self.Xp, self.Yp) # Compute new values 247 | 248 | # Add one to the iteration counter 249 | n = n + 1 # Keep track of the number of iterations 250 | 251 | # Check to see if the values have converged according to the stopping criteria 252 | if n > self.Max_num_iter or (abs(C-C_last) < self.TOL[0] and all(abs(delta_q[0]) < self.TOL[1])): #needs to be tested... 253 | optim_completed = True 254 | 255 | C_last = C #Save the C value for comparison in the next iteration 256 | GRAD_last = GRAD # Save the GRAD value for comparison in the next iteration 257 | #_________________________________________________________________________ 258 | end = (datetime.now() - calc_start_time) - start 259 | 260 | #_______STORE RESULTS AND PREPARE INDICES OF NEXT SUBSET__________________ 261 | # Store the current displacements 262 | self.DEFORMATION_PARAMETERS[yy,xx,0] = self.q_k[0] # displacement x 263 | self.DEFORMATION_PARAMETERS[yy,xx,1] = self.q_k[1] # displacement y 264 | self.DEFORMATION_PARAMETERS[yy,xx,2] = self.q_k[2] 265 | self.DEFORMATION_PARAMETERS[yy,xx,3] = self.q_k[3] 266 | self.DEFORMATION_PARAMETERS[yy,xx,4] = self.q_k[4] 267 | self.DEFORMATION_PARAMETERS[yy,xx,5] = self.q_k[5] 268 | self.DEFORMATION_PARAMETERS[yy,xx,6] = 1 - C # correlation co-efficient final value 269 | 270 | # store points which are correlated in reference image i.e. center of subset 271 | self.DEFORMATION_PARAMETERS[yy,xx,7] = self.Xp 272 | self.DEFORMATION_PARAMETERS[yy,xx,8] = self.Yp 273 | 274 | self.DEFORMATION_PARAMETERS[yy,xx,9] = n # number of iterations 275 | self.DEFORMATION_PARAMETERS[yy,xx,10] = start.total_seconds() #t_tmp # time of spline process 276 | self.DEFORMATION_PARAMETERS[yy,xx,11] = end.total_seconds() #t_optim #time of optimization process 277 | 278 | if self.debug: 279 | print(yy) 280 | print(xx) 281 | 282 | def calculate(self): 283 | if not self.initialised: 284 | raise error("Tried to run calculate before setting parameters. Please use set_parameters first.") 285 | 286 | self.DEFORMATION_PARAMETERS = np.zeros((self.Y_size,self.X_size,12), dtype = float) 287 | 288 | calc_start_time = datetime.now() 289 | 290 | if self.parallel: 291 | num_cores = multiprocessing.cpu_count() 292 | vb = 10 if (self.debug) else 0 293 | results = Parallel(batch_size=floor(self.Xmax/num_cores),n_jobs=num_cores,max_nbytes=None,verbose=vb)(delayed(self.parallel_calculate_helper)(i, j, calc_start_time) for i in range(self.Xmin, self.Xmax + 1) for j in range(self.Ymin,self.Ymax + 1)) 294 | self.DEFORMATION_PARAMETERS = np.zeros((self.Y_size,self.X_size,12), dtype = float) 295 | for i in range(len(results[:])): 296 | self.DEFORMATION_PARAMETERS[int(results[i][7]),int(results[i][8])] = results[i] 297 | else: 298 | self.sequential_calculate(calc_start_time) 299 | 300 | return self.DEFORMATION_PARAMETERS 301 | -------------------------------------------------------------------------------- /predic/__init__.py: -------------------------------------------------------------------------------- 1 | from .DIC_NR_images import * 2 | from .C_First_Order import * -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib==3.1.1 2 | numpy==1.21.0 3 | scipy==1.3.1 4 | Pillow==9.0.1 5 | pytest==5.2.1 6 | pycairo==1.18.1 7 | joblib==0.14.0 -------------------------------------------------------------------------------- /test/__main__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from test_DIC_NR_images import * 4 | from test_C_First_Order import * 5 | 6 | if __name__ == '__main__': 7 | unittest.main() -------------------------------------------------------------------------------- /test/test_C_First_Order.py: -------------------------------------------------------------------------------- 1 | import sys, os, unittest, pytest 2 | 3 | PARENT_DIR = os.path.dirname(os.path.realpath(__file__)) + "/../" 4 | sys.path.append(PARENT_DIR) 5 | 6 | TEST_IMAGE_DIR = os.path.dirname(os.path.realpath(__file__)) + "/testing_images/" 7 | 8 | from predic import DIC_NR, C_First_Order 9 | 10 | import numpy as np 11 | from PIL import Image 12 | from scipy.interpolate import RectBivariateSpline 13 | 14 | class Test_C_First_Order(unittest.TestCase): 15 | 16 | def test_define_deformed_subset(self): 17 | ref_img = np.expand_dims(np.reshape(np.arange(40*40), (40,40)), axis = 2) 18 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 19 | 20 | q = [1,0,0,0,0,0] 21 | 22 | cfo = C_First_Order() 23 | cfo.set_image(ref_img, 11) 24 | cfo.define_deformed_subset(q, 20,20) 25 | 26 | #Center of deformed subset 27 | #Moved from (20,20) to (21,20) according to u=1 28 | self.assertEqual(cfo.X[60] , 21) 29 | self.assertEqual(cfo.Y[60] , 20) 30 | 31 | def test_calculate(self): 32 | ref_img = np.expand_dims(np.reshape(np.arange(41*41), (41,41)), axis = 2) 33 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 34 | def_img = np.roll(ref_img.copy(), 1, axis = 1) 35 | 36 | dicr_1 = DIC_NR() 37 | dicr_1.set_parameters(ref_img, def_img, 11, [0,0]) 38 | 39 | q = [1,0,0,0,0,0] 40 | 41 | cfo = C_First_Order() 42 | cfo.set_image(ref_img, 11) 43 | cfo.set_splines(dicr_1.def_interp, dicr_1.def_interp_x, dicr_1.def_interp_y) 44 | a, b, c = cfo.calculate(q, 20, 20) 45 | 46 | def test_calculate2(self): 47 | ref_img = np.expand_dims(np.reshape(np.arange(41*41), (41,41)), axis = 2) 48 | ref_img = np.multiply(ref_img, 0.1*ref_img) 49 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 50 | def_img = np.roll(ref_img.copy(), 1, axis = 1) 51 | 52 | dicr_1 = DIC_NR() 53 | dicr_1.set_parameters(ref_img, def_img, 11, [0,0]) 54 | 55 | q = [1,0,0,0,0,0] 56 | 57 | cfo = C_First_Order() 58 | cfo.set_image(ref_img, 11) 59 | cfo.set_splines(dicr_1.def_interp, dicr_1.def_interp_x, dicr_1.def_interp_y) 60 | a, b, c = cfo.calculate(q, 20, 20) 61 | -------------------------------------------------------------------------------- /test/test_DIC_NR_images.py: -------------------------------------------------------------------------------- 1 | import sys, os, unittest, pytest 2 | 3 | PARENT_DIR = os.path.dirname(os.path.realpath(__file__)) + "/../" 4 | sys.path.append(PARENT_DIR) 5 | 6 | TEST_IMAGE_DIR = os.path.dirname(os.path.realpath(__file__)) + "/testing_images/" 7 | 8 | from predic import DIC_NR 9 | 10 | import numpy as np 11 | from PIL import Image 12 | from scipy.interpolate import RectBivariateSpline 13 | import math 14 | 15 | class Test_DIC_NR(unittest.TestCase): 16 | 17 | def test_set_parameters(self): 18 | #TEST 1 19 | #Image too small for subset size and buffer room of 15. 20 | ref_img = np.expand_dims(np.reshape(np.arange(40*40), (40,40)), axis = 2) 21 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 22 | def_img = np.roll(ref_img.copy(), 1, axis = 1) 23 | 24 | dicr_1 = DIC_NR() 25 | 26 | with pytest.raises(ValueError): 27 | dicr_1.set_parameters(ref_img, def_img, 11, [0,0]) 28 | 29 | #TEST 2 30 | #Image size okay. 31 | ref_img = np.expand_dims(np.reshape(np.arange(41*41), (41,41)), axis = 2) 32 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 33 | def_img = np.roll(ref_img.copy(), 1, axis = 1) 34 | 35 | dicr_1 = DIC_NR() 36 | dicr_1.set_parameters(ref_img, def_img, 11, [0,0]) 37 | 38 | self.assertEqual(dicr_1.Xmin, 20) 39 | self.assertEqual(dicr_1.Ymin, 20) 40 | self.assertEqual(dicr_1.Xmax, 20) 41 | self.assertEqual(dicr_1.Ymax, 20) 42 | self.assertEqual(dicr_1.Xp, 20) 43 | self.assertEqual(dicr_1.Xp, 20) 44 | 45 | def test_initial_guess(self): 46 | #TEST 1 47 | #moving "image" all to right by 1, u = 1, v = 0 48 | #check the logic that u & v are indexed correctly 49 | ref_img = np.expand_dims(np.reshape(np.arange(41*41), (41,41)), axis = 2) 50 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 51 | def_img = np.roll(ref_img.copy(), 1, axis = 1) 52 | 53 | dicr_1 = DIC_NR() 54 | dicr_1.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "def50.bmp", 11, [0,0]) 55 | dicr_1.initial_guess(ref_img, def_img) 56 | 57 | self.assertEqual(dicr_1.q_k[0], 1) 58 | self.assertEqual(dicr_1.q_k[1], 0) 59 | 60 | #TEST 2 61 | #moving "image" all down by 1, u = 0, v = 1 62 | #check the logic that u & v are indexed correctly 63 | ref_img = np.expand_dims(np.reshape(np.arange(41*41), (41,41)), axis = 2) 64 | ref_img = np.insert(ref_img, 1, 0, axis = 2) 65 | def_img = np.roll(ref_img.copy(), 1, axis = 0) 66 | 67 | dicr_1 = DIC_NR() 68 | dicr_1.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "def50.bmp", 11, [0,0]) 69 | dicr_1.initial_guess(ref_img, def_img) 70 | 71 | self.assertEqual(dicr_1.q_k[0], 0) 72 | self.assertEqual(dicr_1.q_k[1], 1) 73 | 74 | #TEST 3 75 | #comparing the same image with itself, u & v should equal 0 76 | dicr = DIC_NR() 77 | dicr.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "ref50.bmp", 11, [0,0]) 78 | dicr.initial_guess() 79 | 80 | self.assertEqual(dicr.q_k[0], 0) 81 | self.assertEqual(dicr.q_k[1], 0) 82 | 83 | #TEST 4 84 | #comparing test images, u=2, v = 0 85 | dicr = DIC_NR() 86 | dicr.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "def50.bmp", 11, [0,0]) 87 | dicr.initial_guess() 88 | 89 | self.assertEqual(dicr.q_k[0], 2) 90 | self.assertEqual(dicr.q_k[1], 0) 91 | 92 | 93 | def test_fit_spline(self): 94 | 95 | test_image_1 = np.array(Image.open(TEST_IMAGE_DIR + "ref50.bmp").convert('LA')) # numpy.array 96 | test_image_1 = test_image_1.astype('d') 97 | 98 | actual_val_48_0 = test_image_1[48,0,0] 99 | actual_val_49_0 = test_image_1[49,0,0] 100 | 101 | #Note: this is using same image as ref and def 102 | dicnr = DIC_NR() 103 | dicnr.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "ref50.bmp", 11, [0,0]) 104 | dicnr.initial_guess() 105 | dicnr.fit_spline() 106 | 107 | result1 = dicnr.def_interp.ev(48,0) 108 | result2 = dicnr.def_interp.ev(48.5,0) 109 | result3 = dicnr.def_interp.ev(49,0) 110 | 111 | self.assertTrue(math.isclose(actual_val_48_0, result1, rel_tol = 0.01)) 112 | self.assertTrue(actual_val_48_0<= result2 <= actual_val_49_0) 113 | self.assertTrue(math.isclose(actual_val_49_0, result3, rel_tol = 0.01)) 114 | 115 | #TODO: test derivative spline 116 | 117 | def test_whole(self): 118 | dic_2 = DIC_NR() 119 | dic_2.set_parameters(TEST_IMAGE_DIR + "ref50.bmp", TEST_IMAGE_DIR + "def50.bmp",11,[0,0]) 120 | 121 | output_2 = dic_2.calculate() 122 | 123 | self.assertEqual(output_2[20,20,0], 2) 124 | self.assertTrue(math.isclose(output_2[20,20,1], 0.0, abs_tol = 0.01)) 125 | -------------------------------------------------------------------------------- /test/testing_images/def50.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texm/PReDIC/61df73f8ced4aad61dfec69f7722750eebbed2f5/test/testing_images/def50.bmp -------------------------------------------------------------------------------- /test/testing_images/def500.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texm/PReDIC/61df73f8ced4aad61dfec69f7722750eebbed2f5/test/testing_images/def500.bmp -------------------------------------------------------------------------------- /test/testing_images/ref50.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texm/PReDIC/61df73f8ced4aad61dfec69f7722750eebbed2f5/test/testing_images/ref50.bmp -------------------------------------------------------------------------------- /test/testing_images/ref500.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texm/PReDIC/61df73f8ced4aad61dfec69f7722750eebbed2f5/test/testing_images/ref500.bmp -------------------------------------------------------------------------------- /test/testing_images/test_image_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/texm/PReDIC/61df73f8ced4aad61dfec69f7722750eebbed2f5/test/testing_images/test_image_1.bmp --------------------------------------------------------------------------------