├── .gitignore ├── DIC.py ├── LICENSE.md ├── PythonDIC_Documentation.pdf ├── README.md ├── functions ├── CpCorr.py ├── DIC_Global.py ├── filterFunctions.py ├── getData.py ├── initData.py ├── masks.py ├── newProcessCorrelations.py ├── plot2D.py ├── plot3D.py └── startOptions.py └── interface ├── StrainAnalysis.py ├── analysisInfos.py ├── controlWidget.py ├── deleteImages.py ├── devMode.py ├── dispVsPos.py ├── dockWidget.py ├── filterWidget.py ├── generateGrid.py ├── initApp.py ├── maskInstances.py ├── maskMarkers.py ├── menubar.py ├── newCoordinates.py ├── newNeighbors.py ├── profile.py ├── progressWidget.py └── relativeNeighborsDialog.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.cfg 3 | 4 | .DS_Store 5 | 6 | *.pyc 7 | 8 | *.md 9 | 10 | *.png 11 | -------------------------------------------------------------------------------- /DIC.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 21/03/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the main gui application on start-up and include functions to create process and threads 12 | """ 13 | 14 | import sys, multiprocessing 15 | from PyQt4.QtGui import * 16 | from PyQt4.QtCore import * 17 | from interface import initApp, devMode 18 | 19 | #PARAMETERS 20 | PROFILE_FILE = 'profile.cfg' 21 | DEFAULT_PROFILE = (['User','Guest'],['Default','1'],['FullScreen','0'],['Width','800'],['Height','600'],['CorrSize','15'],['nbProcesses','1']) #default values when new profile is created and when no profile file found 22 | DEV_MODE = 0 23 | #END PARAMETERS 24 | 25 | class MainWindow(QMainWindow): 26 | def __init__(self): #initiate the main window 27 | super(MainWindow, self).__init__() 28 | initApp.initProfile(self, PROFILE_FILE, DEFAULT_PROFILE) 29 | initApp.setUpInterface(self, self.currentProfile) 30 | self.devWindow = devMode.DevMode(self, DEV_MODE) 31 | 32 | if __name__ == '__main__': 33 | multiprocessing.freeze_support() 34 | multiprocessing.set_start_method('spawn') 35 | app = QApplication(sys.argv) 36 | mainWindow = MainWindow() 37 | sys.exit(app.exec_()) 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Fraunhofer IWM 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PythonDIC_Documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChrisEberl/Python_DIC/e9d04921e2220feeedce0677fae74cd3eecbd954/PythonDIC_Documentation.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Python DIC Software 2 | 3 | This is a Python implementation of digital image correlation functions which are still in beta status. Nevertheless you can already use the Python functions to analyse your set of images to calculate different kind of displacements and strains. 4 | Functions can be use to filter and correlate images first before analysing results using a graphical user interface. 5 | 6 | ### Acknowledgment: 7 | * Code Author: Charlie Bourigault 8 | * Involved People: Melanie Senn, Chris Eberl 9 | * Collaborators for strain measurements and reviewers: Felix Schiebel, Tobias Kennerknecht, Marco Sebastiani, Jiří Dluhoš 10 | * Partial funding was provided by the European iStress project (http://www.stm.uniroma3.it/iSTRESS/Pages/iSTRESS%20Home%20page.aspx) 11 | 12 | ### License 13 | 14 | Copyright 2016 Fraunhofer IWM 15 | 16 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 20 | -------------------------------------------------------------------------------- /functions/CpCorr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on --/--/20-- 5 | 6 | @author: --- 7 | Revised by Charlie Bourigault 8 | @contact: bourigault.charlie@gmail.com 9 | 10 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 11 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 12 | 13 | Current File: This file has been translated, adapted and further developed from 'Digital Image Correlation and Tracking' for Matlab exchanged by Melanie Senn on Mathworks 14 | """ 15 | 16 | import numpy as np, cv2, scipy.interpolate 17 | 18 | def cpcorr(InputPoints,BasePoints,Input,Base, CORRSIZE): 19 | 20 | [xymoving_in,xyfixed_in,moving,fixed] = ParseInputs(InputPoints,BasePoints,Input,Base) 21 | CorrCoef=[] 22 | 23 | # get all rectangle coordinates 24 | rects_moving = np.array(calc_rects(xymoving_in,CORRSIZE,moving)).astype(np.int) 25 | rects_fixed = np.array(calc_rects(xyfixed_in,2*CORRSIZE,fixed)).astype(np.int) 26 | ncp = len(np.atleast_1d(xymoving_in)) 27 | 28 | xymoving = xymoving_in # initialize adjusted control points matrix 29 | CorrCoef=np.zeros((ncp,1)) 30 | StdX=np.zeros((ncp,1)) 31 | StdY=np.zeros((ncp,1)) 32 | 33 | errorInfos = np.zeros((ncp,1)) 34 | #### Error Type #### 35 | # 1 : Edge Area 36 | # 2 : Marker Out 37 | # 3 : Non finite number 38 | # 4 : No Std. Dev 39 | # 5 : SubPx outside limits 40 | # 6 : Div. by 0 41 | # 7 : Low Corr. 42 | # 8 : Peak badly constrained 43 | #################### 44 | 45 | for icp in range(ncp): 46 | 47 | if (rects_moving[2][icp] == 0 and rects_moving[3][icp] == 0) or (rects_fixed[2][icp] == 0 and rects_moving[3][icp] == 0): 48 | #near edge, unable to adjust 49 | #print 'CpCorr : Edge area. No Adjustement.' 50 | errorInfos[icp] = 1 51 | continue 52 | 53 | sub_moving = moving[rects_moving[1][icp]:rects_moving[1][icp]+rects_moving[3][icp],rects_moving[0][icp]:rects_moving[0][icp]+rects_moving[2][icp]] 54 | sub_fixed = fixed[rects_fixed[1][icp]:rects_fixed[1][icp]+rects_fixed[3][icp],rects_fixed[0][icp]:rects_fixed[0][icp]+rects_fixed[2][icp]] 55 | 56 | #make sure the image data exist 57 | if sub_moving.shape[0] == 0 or sub_moving.shape[1] == 0 or sub_fixed.shape[0] == 0 or sub_fixed.shape[1] == 0: 58 | #print 'CpCorr : Marker out of image.' 59 | errorInfos[icp] = 2 60 | continue 61 | 62 | #make sure finite 63 | if np.logical_or(np.any(np.isfinite(sub_moving[:])==False),np.any(np.isfinite(sub_fixed[:]))==False): 64 | # NaN or Inf, unable to adjust 65 | #print 'CpCorr : Wrong Number. No Adjustement.' 66 | errorInfos[icp] = 3 67 | continue 68 | 69 | 70 | # check that template rectangle moving has nonzero std 71 | if np.std(sub_moving[:])== 0: 72 | # zero standard deviation of template image, unable to adjust 73 | #print 'CpCorr : No Std Dev. No Adjustement.' 74 | errorInfos[icp] = 4 75 | continue 76 | 77 | 78 | norm_cross_corr = cv2.matchTemplate(sub_moving,sub_fixed,cv2.TM_CCORR_NORMED) 79 | #norm_cross_corr=scipy.signal.correlate2d(sub_fixed, sub_moving) 80 | #norm_cross_corr=sklearn.preprocessing.normalize(norm_cross_corr, norm='l2', axis=1, copy=True) 81 | #norm_cross_corr=match_template(sub_fixed,sub_moving) 82 | 83 | # get subpixel resolution from cross correlation 84 | subpixel = True 85 | [xpeak, ypeak, stdx, stdy, corrcoef, info] = findpeak(norm_cross_corr,subpixel) 86 | CorrCoef[icp]=corrcoef 87 | StdX[icp]=stdx 88 | StdY[icp]=stdy 89 | xpeak = float(xpeak) 90 | ypeak = float(ypeak) 91 | 92 | if info == 1: 93 | errorInfos[icp] = 5 94 | elif info == 2: 95 | errorInfos[icp] = 6 96 | 97 | 98 | # eliminate any poor correlations 99 | THRESHOLD = 0.5 100 | if (corrcoef < THRESHOLD): 101 | # low correlation, unable to adjust 102 | #print 'CpCorr : Low Correlation. Marker avoided.' 103 | errorInfos[icp] = 7 104 | continue 105 | 106 | # offset found by cross correlation 107 | corroffset = [xpeak-CORRSIZE, ypeak-CORRSIZE] 108 | 109 | # eliminate any big changes in control points 110 | if corroffset[0] > (CORRSIZE-1) or corroffset[1] > (CORRSIZE-1): 111 | # peak of norxcorr2 not well constrained, unable to adjust 112 | #print 'CpCorr : Peak not well constrained. No adjustement' 113 | errorInfos[icp] = 8 114 | continue 115 | 116 | movingfractionaloffset = np.array([xymoving[icp,:] - np.around(xymoving[icp,:])]) 117 | fixedfractionaloffset = np.array([xyfixed_in[icp,:] - np.around(xyfixed_in[icp,:])]) 118 | 119 | 120 | # adjust control point 121 | xymoving[icp,:] = xymoving[icp,:] - movingfractionaloffset - corroffset + fixedfractionaloffset 122 | #xymoving[icp,:] = xymoving[icp,:] - corroffset 123 | 124 | 125 | return xymoving,StdX,StdY,CorrCoef, errorInfos 126 | 127 | def calc_rects(xy,halfwidth,img): 128 | 129 | # Calculate rectangles so imcrop will return image with xy coordinate inside center pixel 130 | default_width = 2*halfwidth 131 | default_height = default_width 132 | [row, col] = img.shape 133 | 134 | # xy specifies center of rectangle, need upper left 135 | upperleft=np.around(xy)-halfwidth 136 | lowerright=np.around(xy)+halfwidth 137 | 138 | 139 | # need to modify for pixels near edge of images 140 | left = upperleft[:,0] 141 | upper = upperleft[:,1] 142 | right = lowerright[:,0] 143 | lower = lowerright[:,1] 144 | 145 | #lower = upper + default_height 146 | #right = left + default_width 147 | width = default_width * np.ones(np.shape(upper)) 148 | height = default_height * np.ones(np.shape(upper)) 149 | 150 | #check edges for coordinates outside image 151 | [upper, height] = adjust_lo_edge(upper,1,height) 152 | [lower, height] = adjust_hi_edge(lower,row,height) 153 | [left,width] = adjust_lo_edge(left,1,width) 154 | [right, width] = adjust_hi_edge(right,col,width) 155 | 156 | # set width and height to zero when less than default size 157 | #iw = find(width edge: 179 | #breadth[indx] = breadth[indx] - np.absolute(coordinates[indx]-edge) 180 | breadth[indx] = 0 181 | coordinates[indx] = edge 182 | return coordinates, breadth 183 | 184 | def ParseInputs(InputPoints,BasePoints,Input,Base): 185 | 186 | xymoving_in = InputPoints 187 | xyfixed_in = BasePoints 188 | moving = Input 189 | fixed = Base 190 | return xymoving_in,xyfixed_in,moving,fixed 191 | 192 | # sub pixel accuracy by 2D polynomial fit (quadratic) 193 | def findpeak(f,subpixel): 194 | stdx=1e-4 195 | stdy=1e-4 196 | 197 | # Get absolute peak pixel 198 | 199 | max_f = np.amax(f) 200 | [xpeak,ypeak] = np.unravel_index(f.argmax(), f.shape) #coordinates of the maximum value in f 201 | 202 | if subpixel == False or xpeak==0 or xpeak==np.shape(f)[0]-1 or ypeak==0 or ypeak==np.shape(f)[1]-1: # on edge 203 | #print 'CpCorr : No Subpixel Adjustement.' 204 | return ypeak, xpeak, stdx, stdy, max_f, 0# return absolute peak 205 | 206 | else: 207 | # fit a 2nd order polynomial to 9 points 208 | # using 9 pixels centered on irow,jcol 209 | u = f[xpeak-1:xpeak+2,ypeak-1:ypeak+2] 210 | u = np.reshape(np.transpose(u),(9,1)) 211 | x = np.array([-1, 0, 1, -1, 0, 1, -1, 0, 1]) 212 | y = np.array([-1, -1, -1, 0, 0, 0, 1, 1, 1]) 213 | x = np.reshape(x,(9,1)) 214 | y = np.reshape(y,(9,1)) 215 | 216 | # u(x,y) = A(0) + A(1)*x + A(2)*y + A(3)*x*y + A(4)*x^2 + A(5)*y^2 217 | X = np.hstack((np.ones((9,1)), x, y, x*y, x**2, y**2)) 218 | # u = X*A 219 | 220 | #A = np.linalg.lstsq(X,u, rcond=1e-1) 221 | A = np.linalg.lstsq(X,u, rcond=1e-20) 222 | 223 | e = A[1] #residuals returned by Linalg Lstsq 224 | A=np.reshape(A[0],(6,1)) # A[0] array of least square solution to the u = AX equation 225 | 226 | 227 | # get absolute maximum, where du/dx = du/dy = 0 228 | x_num = (-A[2]*A[3]+2*A[5]*A[1]) 229 | y_num = (-A[3]*A[1]+2*A[4]*A[2]) 230 | 231 | den = (A[3]**2-4*A[4]*A[5]) 232 | x_offset = x_num / den 233 | y_offset = y_num / den 234 | 235 | 236 | #print x_offset, y_offset 237 | if np.absolute(x_offset)>1 or np.absolute(y_offset)>1: 238 | #print 'CpCorr : Subpixel outside limit. No adjustement' 239 | # adjusted peak falls outside set of 9 points fit, 240 | return ypeak, xpeak, stdx, stdy, max_f, 1 # return absolute peak 241 | 242 | #x_offset = np.round(10000*x_offset)/10000 243 | #y_offset = np.round(10000*y_offset)/10000 244 | x_offset = np.around(x_offset, decimals=4) 245 | y_offset = np.around(y_offset, decimals=4) 246 | 247 | xpeak = xpeak + x_offset 248 | ypeak = ypeak + y_offset 249 | #print xpeak, ypeak 250 | 251 | # calculate residuals 252 | #e=u-np.dot(X,A) 253 | 254 | # calculate estimate of the noise variance 255 | n=9 # number of data points 256 | p=6 # number of fitted parameters 257 | var=np.sum(e**2)/(n-p) 258 | 259 | # calculate covariance matrix 260 | cov=np.linalg.inv(np.dot(np.transpose(X),X))*var 261 | # produce vector of std deviations on each term 262 | s=np.sqrt([cov[0,0],cov[1,1],cov[2,2],cov[3,3],cov[4,4],cov[5,5]]) 263 | # Calculate standard deviation of denominator, and numerators 264 | if A[1] == 0 or A[2] == 0 or A[3] == 0 or A[4] == 0 or A[5] == 0: #avoid divide by zero error and invalid value 265 | #print 'CpCorr : Div. by 0 error escaped.' 266 | return ypeak, xpeak, stdx, stdy, max_f, 2# return absolute peak 267 | else: 268 | x_num_std=np.sqrt(4*A[5]**2*A[1]**2*((s[5]/A[5])**2+(s[1]/A[1])**2)+A[2]**2*A[3]**2*((s[2]/A[2])**2+(s[3]/A[3])**2)) 269 | den_std=np.sqrt(16*A[4]**2*A[5]**2*((s[4]/A[4])**2+(s[5]/A[5])**2)+2*s[3]**2*A[3]**2) 270 | y_num_std=np.sqrt(4*A[4]**2*A[2]**2*((s[4]/A[4])**2+(s[2]/A[2])**2)+A[3]**2*A[1]**2*((s[3]/A[3])**2+(s[1]/A[1])**2)) 271 | 272 | # Calculate standard deviation of x and y positions 273 | stdx=np.sqrt(x_offset**2*((x_num_std/x_num)**2+(den_std/den)**2)) 274 | stdy=np.sqrt(y_offset**2*((den_std/den)**2+(y_num_std/y_num)**2)) 275 | 276 | # Calculate extremum of fitted function 277 | max_f = np.dot([1, x_offset, y_offset, x_offset*y_offset, x_offset**2, y_offset**2],A) 278 | max_f = np.absolute(max_f) 279 | 280 | 281 | return ypeak, xpeak, stdx, stdy, max_f, 0 282 | 283 | # sub pixel accuracy by upsampling and interpolation 284 | def findpeak2(f,subpixel): 285 | stdx=1e-4 286 | stdy=1e-4 287 | 288 | kernelsize=3 289 | 290 | # get absolute peak pixel 291 | max_f = np.amax(f) 292 | [xpeak,ypeak] = np.unravel_index(f.argmax(), f.shape) 293 | 294 | if subpixel==False or xpeak < kernelsize or xpeak > np.shape(f)[0]-kernelsize or ypeak < kernelsize or ypeak > np.shape(f)[1]-kernelsize: # on edge 295 | return xpeak, ypeak, stdx, stdy, max_f # return absolute peak 296 | else: 297 | # determine sub pixel accuracy by upsampling and interpolation 298 | fextracted=f[xpeak-kernelsize:xpeak+kernelsize+1,ypeak-kernelsize:ypeak+kernelsize+1] 299 | totalsize=2*kernelsize+1 300 | upsampling=totalsize*10+1 301 | #step=2/upsampling 302 | x=np.linspace(-kernelsize,kernelsize,totalsize) 303 | #[X,Y]=np.meshgrid(x,x) 304 | xq=np.linspace(-kernelsize,kernelsize,upsampling) 305 | #[Xq,Yq]=np.meshgrid(xq,xq) 306 | 307 | bilinterp = interpolate.interp2d(x, x, fextracted, kind='cubic') 308 | fq = bilinterp(xq, xq) 309 | #splineint = RectBivariateSpline(x, x, fextracted, kx=3, ky=3, s=0) 310 | #fq=splineint(xq,xq) 311 | #fq=griddata((x, x), fextracted, (Xq, Yq), method='cubic') 312 | 313 | max_f = np.amax(fq) 314 | [xpeaknew,ypeaknew] = np.unravel_index(fq.argmax(), fq.shape) 315 | 316 | #xoffset=Xq[0,xpeaknew] 317 | #yoffset=Yq[ypeaknew,0] 318 | xoffset=xq[xpeaknew] 319 | yoffset=xq[ypeaknew] 320 | 321 | # return only one-thousandths of a pixel precision 322 | xoffset = np.round(1000*xoffset)/1000 323 | yoffset = np.round(1000*yoffset)/1000 324 | xpeak=xpeak+xoffset 325 | ypeak=ypeak+yoffset 326 | 327 | # peak width (full width at half maximum) 328 | scalehalfwidth=1.1774; 329 | fextractedx=np.mean(fextracted,0) 330 | fextractedy=np.mean(fextracted,1) 331 | stdx=scalehalfwidth*np.std(fextractedx) 332 | stdy=scalehalfwidth*np.std(fextractedy) 333 | 334 | return xpeak, ypeak, stdx, stdy, max_f 335 | 336 | # sub pixel accuracy by centroid 337 | def findpeak3(f,subpixel): 338 | stdx=1e-4 339 | stdy=1e-4 340 | 341 | kernelsize=3 342 | 343 | # get absolute peak pixel 344 | max_f = np.amax(f) 345 | [xpeak,ypeak] = np.unravel_index(f.argmax(), f.shape) 346 | 347 | if subpixel==False or xpeak < kernelsize or xpeak > np.shape(f)[0]-kernelsize or ypeak < kernelsize or ypeak > np.shape(f)[1]-kernelsize: # on edge 348 | return xpeak, ypeak, stdx, stdy, max_f # return absolute peak 349 | else: 350 | # determine sub pixel accuracy by centroid 351 | fextracted=f[xpeak-kernelsize:xpeak+kernelsize+1,ypeak-kernelsize:ypeak+kernelsize+1] 352 | fextractedx=np.mean(fextracted,0) 353 | fextractedy=np.mean(fextracted,1) 354 | x=np.arange(-kernelsize,kernelsize+1,1) 355 | y=np.transpose(x) 356 | 357 | xoffset=np.dot(x,fextractedx) 358 | yoffset=np.dot(y,fextractedy) 359 | 360 | # return only one-thousandths of a pixel precision 361 | xoffset = np.round(1000*xoffset)/1000 362 | yoffset = np.round(1000*yoffset)/1000 363 | xpeak=xpeak+xoffset 364 | ypeak=ypeak+yoffset 365 | 366 | # 2D linear interpolation 367 | bilinterp = interpolate.interp2d(x, x, fextracted, kind='linear') 368 | max_f = bilinterp(xoffset,yoffset) 369 | 370 | # peak width (full width at half maximum) 371 | scalehalfwidth=1.1774 372 | stdx=scalehalfwidth*np.std(fextractedx) 373 | stdy=scalehalfwidth*np.std(fextractedy) 374 | 375 | return xpeak, ypeak, stdx, stdy, max_f 376 | -------------------------------------------------------------------------------- /functions/DIC_Global.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 18/10/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: Contains mains classes and functions 12 | """ 13 | 14 | import time, multiprocessing, numpy as np, matplotlib as mpl 15 | from PyQt4.QtGui import * 16 | from PyQt4.QtCore import * 17 | from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 18 | from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar 19 | from mpl_toolkits.mplot3d import Axes3D 20 | mpl.rcParams.update({'figure.autolayout': True}) 21 | from interface import dockWidget 22 | 23 | class matplotlibWidget(FigureCanvas): 24 | 25 | def __init__(self, graphType=None, parent=None, toolbar=None): 26 | 27 | super(matplotlibWidget,self).__init__(mpl.figure.Figure()) 28 | self.figure = mpl.figure.Figure() 29 | self.figure.set_facecolor('none') 30 | self.canvas = FigureCanvas(self.figure) 31 | 32 | if parent is not None: 33 | self.parentWidget = parent 34 | 35 | if graphType is not None: 36 | self.canvas.setParent(self) 37 | #initialize the plot for visualisation 38 | if graphType == 1: #3D plots 39 | self.matPlot = self.figure.add_subplot(111, projection='3d') 40 | self.toolbar = matplotlibToolbar(self.canvas, self, plotType='3d') 41 | else: #2D plots 42 | self.matPlot = self.figure.add_subplot(111) 43 | self.matPlot.tick_params(labelsize=9) 44 | self.matPlot.locator_params(nbins=6) 45 | self.toolbar = matplotlibToolbar(self.canvas, self, toolbar=toolbar) 46 | else: 47 | self.matPlot = self.figure.add_subplot(111) 48 | 49 | self.matPlot.set_aspect('auto') 50 | self.setContentsMargins(0,0,0,0) 51 | #self.figure.tight_layout() 52 | 53 | class matplotlibToolbar(NavigationToolbar): 54 | 55 | def __init__(self, canvas, parent, plotType=None, toolbar=None): 56 | 57 | self.parent = parent 58 | if plotType is not None: 59 | self.toolitems = [('Save', 'Save the figure', 'filesave', 'save_figure'),('Parameters', 'Change plot parameters.', 'hand', 'changeParams')] 60 | else: 61 | self.toolitems = (('Home', 'Reset original view', 'home', 'home'),('Pan', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),('Zoom', 'Zoom to rectangle', 'zoom_to_rect', 'zoom'),('Save', 'Save the figure', 'filesave', 'save_figure'),('Parameters', 'Change plot parameters.', 'hand', 'changeParams')) 62 | 63 | super(matplotlibToolbar, self).__init__(canvas, parent) 64 | if toolbar is not None: 65 | self.setOrientation(Qt.Horizontal) 66 | else: 67 | self.setOrientation(Qt.Vertical) 68 | if plotType is not None: 69 | self.layout().takeAt(2) 70 | else: 71 | self.layout().takeAt(5) 72 | 73 | def changeParams(self): 74 | 75 | graphDisplay = self.parent.parentWidget.graphDisplay 76 | parametersDialog = dockWidget.dockParameters(self.parent, graphDisplay) 77 | parametersDialog.exec_() 78 | 79 | def createThread(parent, args, function, signal=None): #a signal will be created as long as signal is different from None, callable with thread.signal.threadSignal.emit([]) : [] can contains data to be emitted 80 | 81 | thread = Thread(signal) 82 | args.append(thread) #thread instance placed at the end of the argument list (used for example for signals) 83 | thread.getReady(function, args) 84 | return thread 85 | 86 | def createProcess(self, function, args, PROCESSES, progressBar=None, textBar=None): # create PROCESSES processes and execute function with args, write progressBar if given (with textBar) 87 | 88 | p=[] 89 | q=[] 90 | parent_conn = [] 91 | child_conn = [] 92 | alive = 1 93 | for i in range(0,PROCESSES): 94 | parent_conn_t, child_conn_t = multiprocessing.Pipe() #connection pipe to exchange data with a Process (direct link) 95 | parent_conn.append(parent_conn_t) 96 | child_conn.append(child_conn_t) 97 | q.append(multiprocessing.Queue()) #queue to exchange data with process (indirect link) 98 | #if __name__ == "__main__": 99 | p.append(multiprocessing.Process(target=function, args=args[i]+(q[i],)+(child_conn[i],))) #create the process with the target function to execute 100 | p[i].start() #start the calculation 101 | 102 | if progressBar is not None: #calculation and update of the progressBar 103 | progressBar.currentTitle = textBar 104 | lastPercent = 0 105 | a = [] 106 | for i in range(PROCESSES): 107 | a.append(0) 108 | while alive == 1: 109 | alive = 0 110 | for i in range(PROCESSES): 111 | if parent_conn[i].poll(): 112 | t = parent_conn[i].recv() 113 | if t > a[i]: 114 | a[i] = t 115 | if q[i].empty(): # stay in the loop as long as at least one task is not finished, as long as the queue is still empty 116 | alive = 1 117 | total = sum(a)/PROCESSES 118 | if total != lastPercent: 119 | progressBar.percent = total #change the value of the progresBar every percent 120 | lastPercent = total 121 | time.sleep(.1) #refresh every 0.1 sec - Important for the loop (freeze if too fast) 122 | 123 | if q[0] == 0: 124 | return None 125 | q[0] = q[0].get() #get the queue result 126 | for i in range(1,PROCESSES): 127 | if q[i] == 0: 128 | return None 129 | q[i] = np.hstack((q[i-1], q[i].get())) #putting the result in the same variable 130 | 131 | result = q[PROCESSES-1] 132 | return result 133 | 134 | class Thread(QThread): 135 | 136 | def __init__(self, signal): 137 | super(Thread, self).__init__() 138 | if signal is not None: 139 | self.signal = threadSignal() 140 | 141 | def getReady(self, function, args): #setting up variables for the function to be executed by the thread 142 | self.function = function 143 | self.args = args 144 | 145 | def run(self): #when start() is called, execute the function 146 | self.function(*self.args) 147 | 148 | 149 | class threadSignal(QObject): 150 | 151 | threadSignal = pyqtSignal(list) #each signal can emit lists 152 | -------------------------------------------------------------------------------- /functions/filterFunctions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 20/10/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: Contains functions used by the filterWidget classes and associated to image filtering 12 | """ 13 | import numpy as np, cv2 14 | from functions import getData 15 | 16 | def applyFilterListToImage(filterList, image): 17 | 18 | if filterList is not None: 19 | nbFilters = len(np.atleast_1d(filterList)) 20 | if nbFilters > 0: 21 | for currentFilter in np.atleast_1d(filterList): 22 | filterName = currentFilter[1] 23 | filterParameters = [currentFilter[2], currentFilter[3], currentFilter[4]] 24 | image = applyFilterToImage(filterName, filterParameters, image) 25 | 26 | return image 27 | 28 | def applyFilterToImage(filterName, filterParameters, image): 29 | 30 | backupImage = image 31 | if filterName == 'Zoom': 32 | 33 | try: 34 | minY = int(filterParameters[2].split(',')[0]) 35 | maxY = minY + int(filterParameters[0]) 36 | minX = int(filterParameters[2].split(',')[1]) 37 | maxX = minX + int(filterParameters[1]) 38 | image = image[minX:maxX, minY:maxY] 39 | except: 40 | image = backupImage 41 | 42 | elif filterName == 'Blur': 43 | 44 | image = cv2.blur(image, (int(filterParameters[0]), int(filterParameters[1]))) 45 | 46 | elif filterName == 'Gaussian': 47 | 48 | try: 49 | image = cv2.GaussianBlur(image, (int(filterParameters[0]), int(filterParameters[1])), int(filterParameters[2].split(',')[0]), int(filterParameters[2].split(',')[1])) 50 | except: 51 | image = backupImage 52 | 53 | elif filterName == 'Brightness': 54 | 55 | maxValue = np.max(image) 56 | phi = float(filterParameters[0])/100 57 | theta = float(filterParameters[1])/100 58 | degree = float(filterParameters[2]) 59 | image = image.astype(np.float_) 60 | image = maxValue*(1+theta)*(image/maxValue/(1-phi))**(1/degree) 61 | image[image > 255] = 255 62 | image[image < 0] = 0 63 | image = image.astype(np.uint8) 64 | 65 | elif filterName == 'Darkness': 66 | 67 | maxValue = np.max(image) 68 | phi = float(filterParameters[0])/100 69 | theta = float(filterParameters[1])/100 70 | degree = float(filterParameters[2]) 71 | image = image.astype(np.float_) 72 | image = maxValue*(1-theta)*(image/maxValue/(1+phi))**(degree) 73 | image[image > 255] = 255 74 | image[image < 0] = 0 75 | image = image.astype(np.uint8) 76 | 77 | elif filterName == 'Contrast': 78 | 79 | maxValue = np.max(image) 80 | phi = float(filterParameters[0])/100 81 | theta = float(filterParameters[1])/100 82 | degree = float(filterParameters[2]) 83 | medium = (float(maxValue)+np.min(image))/2 84 | image = image.astype(np.float_) 85 | image[image > medium] = medium*(1+theta)*(image[image > medium]/medium/(1-phi))**(1/degree) 86 | image[image < medium] = medium*(1-theta)*(image[image < medium]/medium/(1+phi))**(degree) 87 | image[image > 255] = 255 88 | image[image < 0] = 0 89 | image = image.astype(np.uint8) 90 | 91 | return image 92 | 93 | def saveOpenFilter(filePath, filterList=None): 94 | 95 | filterFileName = '/filter.dat' 96 | if filterList is None: #we want to open the filterFileName file 97 | filterList = getData.testReadFile(filePath+filterFileName) 98 | return filterList 99 | else: 100 | np.savetxt(filePath+filterFileName, np.array(filterList), fmt="%s") 101 | -------------------------------------------------------------------------------- /functions/getData.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 05/04/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the analysis files and open/generate data from them 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import csv, os, numpy as np, time, multiprocessing, pandas 17 | from math import sqrt 18 | from functions import DIC_Global, filterFunctions, initData 19 | 20 | 21 | def openData(parentWindow, progressBar, parent): #parent contains the Thread in which the opening is made 22 | 23 | dataList = generateData(parentWindow, progressBar) 24 | if dataList is not None: 25 | parentWindow.devWindow.addInfo('Data extracted from files. Preparing the calculation.') 26 | parent.signal.threadSignal.emit(dataList) 27 | else: 28 | parent.signal.threadSignal.emit([0]) 29 | return 30 | 31 | def generateData(parentWindow, progressBar): 32 | 33 | #Opening main files 34 | progressBar.currentTitle = "Opening validx.csv" 35 | data_x = testReadFile(parentWindow.fileDataPath+'/validx.csv') 36 | progressBar.currentTitle = "Opening validy.csv" 37 | progressBar.percent = 15 38 | data_y = testReadFile(parentWindow.fileDataPath+'/validy.csv') 39 | progressBar.currentTitle = "Opening corrcoef.csv" 40 | progressBar.percent = 30 41 | data_corr = testReadFile(parentWindow.fileDataPath+'/corrcoef.csv') 42 | progressBar.currentTitle = "Opening stdx.csv" 43 | progressBar.percent = 45 44 | data_stdx = testReadFile(parentWindow.fileDataPath+'/stdx.csv') 45 | progressBar.currentTitle = "Opening stdy.csv" 46 | progressBar.percent = 60 47 | data_stdy = testReadFile(parentWindow.fileDataPath+'/stdy.csv') 48 | progressBar.currentTitle = "Opening dispx.csv" 49 | progressBar.percent = 75 50 | disp_x = testReadFile(parentWindow.fileDataPath+'/dispx.csv') 51 | progressBar.currentTitle = "Opening dispy.csv" 52 | progressBar.percent = 90 53 | disp_y = testReadFile(parentWindow.fileDataPath+'/dispy.csv') 54 | if data_x is None or data_y is None or data_corr is None or data_stdx is None or data_stdy is None or disp_x is None or disp_y is None: 55 | return None 56 | 57 | progressBar.currentTitle = "Finishing preparation..." 58 | filenamelist = [] 59 | filenamelistRead = testReadFile(parentWindow.fileDataPath+'/filenamelist.csv', lib=1) 60 | if filenamelistRead is None: 61 | return None 62 | else: 63 | for row in filenamelistRead: 64 | filenamelist.append(row.decode(encoding='utf-8')) 65 | 66 | grid_entities = testReadFile(parentWindow.fileDataPath+'/gridx.csv') 67 | if grid_entities is None: 68 | return None 69 | 70 | temp_grid_instances = [] 71 | for (coor, instance) in grid_entities: 72 | temp_grid_instances.append(instance) 73 | temp_grid_instances = np.array(temp_grid_instances) 74 | 75 | filterList = filterFunctions.saveOpenFilter(parentWindow.fileDataPath) 76 | nbImages = len(filenamelist) 77 | 78 | largeDisp = testReadFile(parentWindow.fileDataPath+'/largeDisp.csv') 79 | 80 | #Last operations 81 | nb_marker = data_x.shape[0] 82 | nb_image = data_x.shape[1] 83 | 84 | instances = np.unique(temp_grid_instances).tolist() 85 | grid_instances = [] 86 | for instance in instances: 87 | grid_instances.append([]) 88 | for marker in range(nb_marker): 89 | grid_instances[instances.index(temp_grid_instances[marker])].append(marker) 90 | 91 | return [data_x, data_y, data_corr, data_stdx, data_stdy, disp_x, disp_y, filenamelist, nb_marker, nb_image, filterList, grid_instances, largeDisp] 92 | 93 | 94 | def testReadFile(filePath, lib=None): 95 | 96 | #test if the file exist 97 | try: 98 | if lib is not None: #for filenamelist to avoid type problems and errors due to space in file naming 99 | readFile = np.genfromtxt(filePath, dtype=None, delimiter=',') 100 | else: 101 | readFile = pandas.read_csv(filePath, dtype=None, delimiter=',', header=None).values #pandas is way faster than numpy for this 102 | except: 103 | return None 104 | return readFile 105 | -------------------------------------------------------------------------------- /functions/masks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 23/05/2016 4 | 5 | 6 | @author: Charlie Bourigault 7 | @contact: bourigault.charlie@gmail.com 8 | 9 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 10 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 11 | 12 | Current File: This file manages the whole masks saving and opening features 13 | """ 14 | 15 | from PyQt4.QtCore import * 16 | from PyQt4.QtGui import * 17 | import time, os, numpy as np 18 | from functions import DIC_Global, initData, plot2D, plot3D, getData 19 | from interface import progressWidget, dockWidget 20 | 21 | def generateMask(mask, path, fileName=None, confirmDialog=True): 22 | 23 | if confirmDialog is True: 24 | confirmCalc = confirmMask() 25 | result = confirmCalc.exec_() 26 | else: 27 | result = 1 28 | 29 | if result == 1: 30 | #Save mask file into log folder 31 | if confirmDialog is True: 32 | recalculateCoordinates = [confirmCalc.corrBox.isChecked(), confirmCalc.xStrainBox.isChecked(), confirmCalc.yStrainBox.isChecked()] 33 | else: 34 | recalculateCoordinates = [True, True, True] 35 | print(recalculateCoordinates) 36 | currentTime = time.localtime() 37 | if fileName is None: 38 | fileName=str(currentTime[0])+'_'+str(currentTime[1])+'_'+str(currentTime[2])+'_'+str(currentTime[3])+str(currentTime[4])+str(currentTime[5])+'.dat' 39 | directory = path+'/log/' 40 | if not os.path.exists(directory): 41 | os.makedirs(directory) 42 | np.savetxt(directory+'Original.dat', np.ones_like(mask), fmt='%1d', delimiter=',') 43 | 44 | np.savetxt(directory+fileName, mask, fmt='%1d', delimiter=',') 45 | return recalculateCoordinates 46 | else: 47 | return None 48 | 49 | class confirmMask(QDialog): 50 | 51 | def __init__(self): 52 | 53 | QDialog.__init__(self) 54 | 55 | self.setWindowTitle('Confirm Mask') 56 | self.setMinimumWidth(300) 57 | dialogLayout = QVBoxLayout() 58 | 59 | questionLbl = QLabel('Do you confirm the selection?') 60 | questionLbl.setAlignment(Qt.AlignCenter) 61 | infoLbl = QLabel('Re-calculate coordinates:') 62 | 63 | checkBoxLayout = QHBoxLayout() 64 | self.corrBox = QCheckBox('Correlation 2D') 65 | self.xStrainBox = QCheckBox('2D Local Strain (X)') 66 | self.yStrainBox = QCheckBox('2D Local Strain (Y)') 67 | self.corrBox.setChecked(True) 68 | self.xStrainBox.setChecked(True) 69 | self.yStrainBox.setChecked(True) 70 | checkBoxLayout.addWidget(self.corrBox) 71 | checkBoxLayout.addWidget(self.xStrainBox) 72 | checkBoxLayout.addWidget(self.yStrainBox) 73 | 74 | buttonLayout = QHBoxLayout() 75 | cancelButton = QPushButton('Select more markers') 76 | cancelButton.setMaximumWidth(150) 77 | confirmButton = QPushButton('Confirm and Calculate') 78 | confirmButton.setMaximumWidth(120) 79 | buttonLayout.addStretch(1) 80 | buttonLayout.addWidget(cancelButton) 81 | buttonLayout.addStretch(1) 82 | buttonLayout.addWidget(confirmButton) 83 | buttonLayout.addStretch(1) 84 | 85 | cancelButton.clicked.connect(self.reject) 86 | confirmButton.clicked.connect(self.accept) 87 | 88 | dialogLayout.addWidget(questionLbl) 89 | dialogLayout.addWidget(infoLbl) 90 | dialogLayout.addLayout(checkBoxLayout) 91 | dialogLayout.addLayout(buttonLayout) 92 | self.setLayout(dialogLayout) 93 | 94 | def renameMask(parent, name): 95 | 96 | filePath = parent.fileDataPath+'/log/' 97 | if not os.path.exists(filePath): 98 | os.makedirs(filePath) 99 | np.savetxt(filePath+'Original.dat', np.ones_like(parent.analysisWidget.data_x), fmt='%1d') 100 | 101 | newName, ok = QInputDialog.getText(parent, 'Rename Version', 'Enter the new version name:', text=name) 102 | if ok: 103 | if newName == "": 104 | return 105 | else: 106 | os.rename(filePath+name+'.dat', filePath+newName+'.dat') 107 | parent.analysisWidget.controlWidget.currentVersionName = newName 108 | parent.analysisWidget.controlWidget.currentVersion.setText(newName) 109 | 110 | 111 | def maskData(parentWindow, mask, progressBar=None, dataList=None, toRecalculate=None): 112 | 113 | if progressBar is not None: 114 | progressBar.currentTitle = 'Applying masks...' 115 | 116 | calculatingThread = DIC_Global.createThread(parentWindow.parentWindow, [parentWindow, progressBar, mask, toRecalculate], initData.initPlottedData, signal=1) 117 | calculatingThread.signal.threadSignal.connect(lambda: newMasksCalculated(parentWindow, progressBar)) 118 | calculatingThread.start() 119 | 120 | 121 | def newMasksCalculated(parentWindow, progressBar): 122 | 123 | plot3D.plot3D_init(parentWindow.displacementX.dockWidget.matPlot, parentWindow.xLimit, parentWindow.yLimit, parentWindow.disp_x) 124 | plot3D.plot3D_init(parentWindow.displacementY.dockWidget.matPlot, parentWindow.xLimit, parentWindow.yLimit, parentWindow.disp_y) 125 | plot3D.plot3D_init(parentWindow.correlation.dockWidget.matPlot, parentWindow.xLimit, parentWindow.yLimit, np.array([0,1])) 126 | plot2D.plot2D_correlation(parentWindow, parentWindow.correlation2D.dockWidget.figure, parentWindow.correlation2D.dockWidget.matPlot, parentWindow.xi, parentWindow.yi, parentWindow.zi[0][0]) 127 | plot3D.plot3D_init(parentWindow.deviationX.dockWidget.matPlot, parentWindow.xLimit, parentWindow.yLimit, parentWindow.data_stdx) 128 | plot3D.plot3D_init(parentWindow.deviationY.dockWidget.matPlot, parentWindow.xLimit, parentWindow.yLimit, parentWindow.data_stdy) 129 | plot2D.plot2D_strain(parentWindow, parentWindow.strain2DX.dockWidget.matPlot, parentWindow.xi, parentWindow.yi, parentWindow.zi_strainX[0][0], parentWindow.grid_instances, parentWindow.activeInstances, parentWindow.activeMarkers[parentWindow.activeImages[0]], plotFig = parentWindow.strain2DX.dockWidget.figure) 130 | plot2D.plot2D_strain(parentWindow, parentWindow.strain2DY.dockWidget.matPlot, parentWindow.xi, parentWindow.yi, parentWindow.zi_strainY[0][0], parentWindow.grid_instances, parentWindow.activeInstances, parentWindow.activeMarkers[parentWindow.activeImages[0]], plotFig = parentWindow.strain2DY.dockWidget.figure) 131 | plot2D.plot2D_displacementDeviation(parentWindow, parentWindow.displacement2D.dockWidget.matPlot, parentWindow.data_x, parentWindow.data_y, parentWindow.disp_x, parentWindow.disp_y, 0, parentWindow.grid_instances, parentWindow.activeInstances) 132 | #plot2D.plot2D_displacementDeviation(parentWindow, parentWindow.deviation2D.dockWidget.plot, parentWindow.data_x, parentWindow.data_y, parentWindow.disp_x, parentWindow.disp_y, 0, parentWindow.grid_instances, parentWindow.activeInstances) 133 | plot2D.plot2D_strain(parentWindow, parentWindow.strainX.dockWidget.matPlot, parentWindow.data_x, 0, parentWindow.disp_x, parentWindow.grid_instances, parentWindow.activeInstances, parentWindow.activeMarkers, refImg=parentWindow.activeImages[0]) 134 | plot2D.plot2D_strain(parentWindow, parentWindow.strainY.dockWidget.matPlot, parentWindow.data_y, 0, parentWindow.disp_y,parentWindow.grid_instances, parentWindow.activeInstances, parentWindow.activeMarkers, refImg=parentWindow.activeImages[0]) 135 | plot2D.plot_TrueStrain(parentWindow, parentWindow.trueStrainX.dockWidget.matPlot, [parentWindow.strainX_data, parentWindow.trueStrainX.averageImageNb, parentWindow.activeInstances]) 136 | plot2D.plot_TrueStrain(parentWindow, parentWindow.trueStrainY.dockWidget.matPlot, [parentWindow.strainY_data, parentWindow.trueStrainY.averageImageNb, parentWindow.activeInstances]) 137 | for instance in dockWidget.dockPlot.instances: 138 | instance.dockWidget.draw() #refresh all the plots 139 | 140 | openMask(parentWindow.parentWindow) 141 | parentWindow.controlWidget.updateAnalysisInfos() 142 | if progressBar is not None: 143 | progressBar.percent = 100 144 | #progressBar.changeValue(100, '-') 145 | parentWindow.resultAnalysis.graphRefresh(imageValue=0) 146 | 147 | def openMask(parent, maskName = 0, getNbMasks = 0): 148 | 149 | dirpath = parent.fileDataPath+'/log' 150 | fileName = 'Original' 151 | filePath = None 152 | currentMask = np.ones((len(parent.analysisWidget.data_x),len(parent.analysisWidget.data_x[0,:]))) 153 | if maskName == 0: 154 | if os.path.exists(dirpath): 155 | a = [s for s in os.listdir(dirpath) if os.path.isfile(os.path.join(dirpath, s))] 156 | a.sort(key=lambda s: os.path.getmtime(os.path.join(dirpath, s))) 157 | if getNbMasks > 0: 158 | return len(a) 159 | if len(a) > 0: 160 | filePath = dirpath+'/'+a[len(a)-1] 161 | currentMask = getData.testReadFile(filePath) 162 | else: 163 | if getNbMasks > 0: 164 | return 1 165 | else: 166 | filePath = maskName 167 | currentMask = getData.testReadFile(filePath) 168 | 169 | if filePath is not None: 170 | fileName = os.path.splitext(os.path.basename(filePath))[0] 171 | parent.analysisWidget.controlWidget.currentVersionName = fileName 172 | parent.analysisWidget.controlWidget.currentVersion.setText(fileName) 173 | 174 | return currentMask 175 | 176 | def openMaskRequest(parent): 177 | 178 | filePathTest = QFileDialog.getOpenFileName(parent, 'Select the mask file', '', 'Dat Files (*.dat)') 179 | if filePathTest == '': 180 | return 181 | else: #open the mask 182 | newMask = openMask(parent, maskName = filePathTest) 183 | progressBar = progressWidget.progressBarDialog('Saving masks..') 184 | if generateMask(newMask, parent.fileDataPath, fileName=os.path.basename(filePathTest), confirmDialog=False) is not None: 185 | maskData(parent.analysisWidget, newMask, progressBar, toRecalculate=[True, True, True]) 186 | #openingThread = DIC_Global.createThread(parent, [parent, progressBar, newMask], fileOpenedForImportation, signal=1) 187 | #openingThread.signal.threadSignal.connect(lambda: newMasksCalculated(parent.analysisWidget, progressBar)) 188 | #openingThread.start() 189 | 190 | def fileOpenedForImportation(parent, progressBar, newMask, thread): 191 | 192 | dataList = getData.generateData(parent, progressBar) 193 | if dataList is not None: 194 | dataList += (thread, ) #we put the thread in the dataList to use the signal in initData.initPlottedData 195 | maskData(parent.analysisWidget, newMask, progressBar, dataList, ) 196 | # 197 | #def cleanedData(data): 198 | # 199 | # maskedData = data[np.isnan(data)==False] 200 | # return maskedData 201 | -------------------------------------------------------------------------------- /functions/plot2D.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 15/04/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the initiation and update of 2D plots 12 | """ 13 | 14 | import matplotlib.pyplot as plt, matplotlib.mlab as ml, matplotlib.colors as mplc, cv2, scipy, numpy as np 15 | from mpl_toolkits.axes_grid1 import make_axes_locatable 16 | from matplotlib.pyplot import clabel 17 | from scipy.interpolate import griddata 18 | from functions import masks 19 | 20 | ############################## 21 | ## DISPLACEMENT / DEVIATION ## 22 | ############################## 23 | 24 | def plot2D_displacementDeviation(self, plotAx, data_x, data_y, disp_x, disp_y, value, grid_instances, activeInstances): 25 | 26 | plotAx.cla() #clear the figure 27 | plotAx.patch.set_facecolor('none') #remove figure background 28 | 29 | plotAx.red = [] 30 | for instance in activeInstances: 31 | instanceMarkers = grid_instances[instance] 32 | plotAx.red.append(plotAx.plot(data_x[instanceMarkers,0], data_y[instanceMarkers,0], '+', ms=3)[0]) 33 | 34 | def update2D_displacementDeviation(plotAx, xAxis, yAxis, image): #data = red_x, red_y, blue_x, blue_y, value, parentWidget 35 | 36 | 37 | nbInstances = len(np.atleast_1d(xAxis)) 38 | if image is not None: 39 | plotAx.image = plotAx.imshow(image[::10,::10], cmap='gray', extent=[0, image.shape[1], image.shape[0],0]) # plot the image in low quality (reduce plotting time) 40 | for instance in range(nbInstances): 41 | plotAx.red[instance].set_data(xAxis[instance], yAxis[instance]) 42 | 43 | 44 | ################# 45 | ## CORRELATION ## 46 | ################# 47 | 48 | def plot2D_correlation(self, plotFig, plotAx, data_x, data_y, corr): 49 | 50 | plotAx.cla() #clear the figure 51 | plotAx.patch.set_facecolor('none') #remove figure background 52 | 53 | try: 54 | plotFig.delaxes(plotFig.axes[1]) 55 | except: 56 | pass 57 | 58 | #plotAx.mappable = plotAx.contourf(data_x, data_y, corr, np.linspace(0, 1, 9), cmap = 'Spectral', extend='min', spacing='proportional') 59 | plotAx.mappable = plotAx.imshow(corr, cmap = 'RdBu') 60 | plotAx.mappable.axes.xaxis.set_ticklabels([]) 61 | plotAx.mappable.axes.yaxis.set_ticklabels([]) 62 | plotAx.invert_yaxis() 63 | 64 | #colorbar display 65 | divider = make_axes_locatable(plotAx) 66 | plotAx.cax = divider.append_axes('right', size='5%', pad='1%') 67 | plotAx.cbar = plotFig.colorbar(plotAx.mappable, cax=plotAx.cax, extend='min') 68 | plotAx.cbar.ax.tick_params(labelsize=7) 69 | labels = np.linspace(0, 1, 11) 70 | ticks = np.linspace(-0.1, 0.1, 11) 71 | plotAx.cbar.set_ticks(ticks) 72 | plotAx.cbar.set_ticklabels(labels) 73 | 74 | 75 | def update2D_correlation(self, plotFig, plotAx, data): #data = dataCorr2D 76 | 77 | 78 | nbInstances = len(np.atleast_1d(data)) 79 | try: 80 | for instance in range(nbInstances): 81 | plotAx.mappable[instance].remove() 82 | except: 83 | pass 84 | 85 | vmin, vmax = plotAx.cbar.get_clim() 86 | plotAx.mappable = [] 87 | 88 | for instance in range(nbInstances): 89 | if data[instance][0,0] != 99999: 90 | plotAx.mappable.append(plotAx.imshow(data[instance], cmap = 'RdBu', vmin=vmin, vmax=vmax)) 91 | 92 | 93 | ########################## 94 | ## LOCAL STRAIN 1D / 2D ## 95 | ########################## 96 | 97 | def plot2D_strain(self, plotAx, data_x, data_y, disp_strain, grid_instances, activeInstances, activeMarkers, plotFig=None, refImg=0): 98 | 99 | plotAx.cla() #clear the figure 100 | plotAx.patch.set_facecolor('none') #remove figure background 101 | 102 | if plotFig is not None: #2D Strain 103 | 104 | try: 105 | plotFig.delaxes(plotFig.axes[1]) 106 | except: 107 | pass 108 | 109 | plotAx.mappable = plotAx.imshow(disp_strain, cmap = 'jet') #old_cmap : RdBu 110 | plotAx.mappable.axes.xaxis.set_ticklabels([]) 111 | plotAx.mappable.axes.yaxis.set_ticklabels([]) 112 | 113 | divider = make_axes_locatable(plotAx) 114 | plotAx.cax = divider.append_axes('right', size='5%', pad='1%') 115 | plotAx.cbar = plotFig.colorbar(plotAx.mappable, cax=plotAx.cax, extend='both') 116 | plotAx.cbar.ax.tick_params(labelsize=7) 117 | plotAx.set_aspect('auto') 118 | 119 | else: #1D Strain 120 | 121 | nbInstances = len(np.atleast_1d(activeInstances)) 122 | lowLimitData = np.max(data_x) 123 | highLimitData = np.min(data_x) 124 | lowLimitDisp = np.max(disp_strain) 125 | highLimitDisp = np.min(disp_strain) 126 | plotAx.strainPlot = [] 127 | plotAx.strainFit = [] 128 | for instance in range(nbInstances): 129 | instanceMarkers = np.intersect1d(grid_instances[activeInstances[instance]],activeMarkers[refImg], assume_unique=True).astype(np.int) 130 | nbInstanceMarkers = len(np.atleast_1d(instanceMarkers)) 131 | if nbInstanceMarkers > 1: 132 | s, b = np.polyfit(data_x[instanceMarkers,refImg], disp_strain[instanceMarkers,refImg], 1) #calculate the linear regression of the data 133 | plotAx.strainFit.append(plotAx.plot(data_x[instanceMarkers,refImg], s * data_x[instanceMarkers,refImg] + b,'--k')[0]) 134 | plotAx.strainPlot.append(plotAx.plot(data_x[instanceMarkers,refImg], disp_strain[instanceMarkers,refImg], '.')[0]) 135 | elif nbInstanceMarkers == 1: 136 | plotAx.strainFit.append(None) 137 | plotAx.strainPlot.append(plotAx.plot(data_x[instanceMarkers,refImg], disp_strain[instanceMarkers,refImg], '.')[0]) 138 | else: 139 | continue 140 | lowLimitDisp = min(lowLimitDisp,np.min(disp_strain[instanceMarkers,:])) 141 | highLimitDisp = max(highLimitDisp,np.max(disp_strain[instanceMarkers,:])) 142 | lowLimitData = min(lowLimitData,np.min(data_x[instanceMarkers,:])) 143 | highLimitData = max(highLimitData, np.max(data_x[instanceMarkers,:])) 144 | plotAx.set_xlim([lowLimitData, highLimitData]) 145 | plotAx.set_ylim([lowLimitDisp, highLimitDisp]) 146 | 147 | 148 | def update2D_strain(self, plotAx, xAxis, yAxis, data): #data = [slope, intersect] for 1D strain or data = plotFig for 2D strain 149 | 150 | nbInstances = len(np.atleast_1d(xAxis)) 151 | if yAxis is None: #2D Strain 152 | 153 | try: 154 | for instance in range(nbInstances): 155 | plotAx.mappable[instance].remove() 156 | except: 157 | pass 158 | 159 | vmin, vmax = plotAx.cbar.get_clim() 160 | plotAx.mappable = [] 161 | for instance in range(nbInstances): 162 | if xAxis[instance][0,0] != 99999: 163 | plotAx.mappable.append(plotAx.imshow(xAxis[instance], cmap = 'jet', vmin=vmin, vmax=vmax)) 164 | plotAx.set_aspect('auto') 165 | 166 | else: #1D Strain 167 | 168 | for instance in range(nbInstances): 169 | if plotAx.strainFit[instance] is not None: 170 | plotAx.strainFit[instance].set_data(xAxis[instance], data[0][instance] * xAxis[instance] + data[1][instance]) 171 | plotAx.strainPlot[instance].set_data(xAxis[instance], yAxis[instance]) 172 | 173 | 174 | def plot_TrueStrain(self, plotAx, data): #data = strainX/Y, averageImageNb, activeInstances 175 | 176 | plotAx.cla() 177 | nbImages = len(np.atleast_1d(data[0])) 178 | nbInstances = len(np.atleast_1d(data[0][0,:])) 179 | 180 | if data[1] < 1: 181 | data[1] = 1 182 | if data[1] > nbImages: 183 | data[1] = nbImages 184 | 185 | for instance in range(nbInstances): 186 | slope = [] 187 | currentCumulatedStrain = 0 188 | nb = 0 189 | for image in range(nbImages): 190 | currentCumulatedStrain += data[0][image,instance] 191 | nb+=1 192 | if nb > data[1]-1 or image == nbImages-1: 193 | slope.append([image+1,data[0][image,instance]/nb]) 194 | nb, currentCumulatedStrain = 0, 0 195 | slope = np.array(slope) 196 | lbl = 'Instance '+str(data[2][instance]) 197 | plotAx.plot(slope[:,0], slope[:,1], '.-', label=lbl) 198 | if nbInstances > 1: 199 | plotAx.legend() 200 | -------------------------------------------------------------------------------- /functions/plot3D.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 18/04/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the initiation and update of 3D plots 12 | """ 13 | 14 | import numpy as np, matplotlib as mpl 15 | 16 | def set_aspect_equal_3d(ax): 17 | """Fix equal aspect bug for 3D plots.""" 18 | 19 | xlim = ax.get_xlim3d() 20 | ylim = ax.get_ylim3d() 21 | #zlim = ax.get_zlim3d() 22 | 23 | from numpy import mean 24 | xmean = mean(xlim) 25 | ymean = mean(ylim) 26 | 27 | plot_radius = max([abs(lim - mean_) 28 | for lims, mean_ in ((xlim, xmean), 29 | (ylim, ymean)) 30 | for lim in lims]) 31 | 32 | ax.set_xlim3d([xmean + plot_radius, xmean - plot_radius]) 33 | ax.set_ylim3d([ymean - plot_radius, ymean + plot_radius]) 34 | #ax.set_zlim3d([zmean - plot_radius, zmean + plot_radius]) 35 | 36 | 37 | 38 | def plot3D_init(plotAx, xLimit, yLimit, zData): 39 | 40 | plotAx.cla() #clear the figure 41 | plotAx.patch.set_facecolor('none') #remove figure background 42 | 43 | plotAx.view_init(15,50) #initiate the angle of the view 44 | plotAx.tick_params(labelsize=10) 45 | plotAx.locator_params(nbins=4) 46 | 47 | zLimit = [np.nanmin(zData), np.nanmax(zData)] 48 | 49 | plotAx.set_xlim(xLimit) 50 | plotAx.set_ylim(yLimit) 51 | plotAx.set_zlim(zLimit) 52 | set_aspect_equal_3d(plotAx) 53 | 54 | 55 | def update3D_subplot(plotAx, xAxis, yAxis, zAxis, plotType, projection): 56 | 57 | nbInstances = len(np.atleast_1d(xAxis)) 58 | try: 59 | for instance in range(nbInstances): 60 | plotAx.current[instance].remove() 61 | except: 62 | pass 63 | try: 64 | for instance in range(nbInstances): 65 | plotAx.projectionX[instance].remove() 66 | except: 67 | pass 68 | try: 69 | for instance in range(nbInstances): 70 | plotAx.projectionY[instance].remove() 71 | except: 72 | pass 73 | 74 | plotAx.current = [] 75 | plotAx.projectionX = [] 76 | plotAx.projectionY = [] 77 | for instance in range(nbInstances): 78 | nbMarkers = len(np.atleast_1d(xAxis[instance])) 79 | if plotType == 0 and nbMarkers > 2: 80 | plotAx.current.append(plotAx.plot_trisurf(xAxis[instance], yAxis[instance], zAxis[instance], cmap=mpl.cm.Spectral, linewidth=0, alpha=0.7)) 81 | else: 82 | plotAx.current.append(plotAx.scatter(xAxis[instance], yAxis[instance], zAxis[instance], cmap=mpl.cm.Spectral, s=5)) 83 | 84 | if projection[0]: 85 | plotAx.projectionX.append(plotAx.plot(xAxis[instance], zAxis[instance], '.', zs=plotAx.get_ylim3d()[1], zdir='y', alpha=.2)[0]) 86 | if projection[1]: 87 | plotAx.projectionY.append(plotAx.plot(yAxis[instance], zAxis[instance], '.', zs=plotAx.get_xlim3d()[1], zdir='x', alpha=.2)[0]) 88 | -------------------------------------------------------------------------------- /functions/startOptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 18/10/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the open and create new analysis functions 12 | """ 13 | 14 | from PyQt4.QtGui import * 15 | from PyQt4.QtCore import * 16 | import os, numpy as np, cv2 17 | from functions import DIC_Global 18 | from interface import menubar, generateGrid, dockWidget, StrainAnalysis 19 | 20 | def openPrevious(self): #when opening a previous analysis, ask for the project folder and launch the analysis widget 21 | 22 | flags = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly 23 | directory = QFileDialog.getExistingDirectory(self, 'Data Folder', '', flags) 24 | 25 | if directory == "": 26 | return 27 | else: 28 | for instance in dockWidget.dockPlot.instances: #deleting dockwidget if there are 29 | instance.close() 30 | instance.deleteLater() 31 | dockWidget.dockPlot.instances = [] 32 | 33 | self.filePath = os.path.dirname(directory) 34 | self.fileDataPath = directory 35 | 36 | StrainAnalysis.analyseResult(self, self) 37 | 38 | def startNewAnalysis(self): #called when a new analysis is started 39 | 40 | filePathTest = QFileDialog.getOpenFileName(self, 'Select first image', '', 'Image Files (*.tif *.tiff *.bmp *.jpg *.jpeg *.png)') 41 | 42 | if filePathTest == '': 43 | return 44 | else: #create the file list when an image is selected 45 | 46 | extension = os.path.splitext(os.path.basename(filePathTest))[1] 47 | fileList = os.listdir(os.path.dirname(filePathTest)) 48 | fileList = [nb for nb in fileList if nb.endswith(extension)] 49 | fileNameList = [] 50 | 51 | count = 0 52 | for element in fileList: 53 | fileNameList.append('{0}'.format(element)) 54 | count+=1 55 | 56 | newAnalysis = nameAnalysis(self, fileNameList, os.path.dirname(filePathTest)) 57 | result = newAnalysis.exec_() 58 | 59 | if result == 1: 60 | menubar.menuDisabled(self) 61 | for instance in dockWidget.dockPlot.instances: #deleting dockwidget if there are 62 | instance.close() 63 | instance.deleteLater() 64 | dockWidget.dockPlot.instances = [] 65 | self.filterFile = None 66 | menubar.menuCreateGridEnabled(self) 67 | generateGrid.createGrid(self) 68 | 69 | 70 | class nameAnalysis(QDialog): 71 | 72 | def __init__(self, parent, fileNameList, filePath): 73 | 74 | QDialog.__init__(self) 75 | dialogLayout = QVBoxLayout() 76 | dialogLayout.setSpacing(20) 77 | self.setWindowTitle('Analysis Creation') 78 | self.setMaximumWidth(500) 79 | self.setMaximumHeight(600) 80 | self.filePath = filePath 81 | 82 | infoLbl = QLabel('Please verify the automatic image selection.') 83 | infoLbl.setAlignment(Qt.AlignCenter) 84 | 85 | imageLayout = QHBoxLayout() 86 | self.plotArea = DIC_Global.matplotlibWidget() 87 | self.plotArea.setMaximumHeight(300) 88 | self.imageList = QListView() 89 | self.imageList.setMinimumWidth(200) 90 | self.imageList.setMaximumHeight(300) 91 | self.imageList.setContentsMargins(0,20,0,20) 92 | self.imageModel = QStandardItemModel(self.imageList) 93 | for image in fileNameList: 94 | imageItem = QStandardItem(image) 95 | imageItem.setCheckable(True) 96 | imageItem.setCheckState(Qt.Checked) 97 | self.imageModel.appendRow(imageItem) 98 | self.imageList.setModel(self.imageModel) 99 | self.imageList.setCurrentIndex(self.imageModel.indexFromItem(self.imageModel.item(0))) 100 | self.imageList.clicked.connect(lambda: self.displayImage(fileNameList)) 101 | imageLayout.addWidget(self.plotArea) 102 | imageLayout.addWidget(self.imageList) 103 | 104 | totalImageNb = len(np.atleast_1d(fileNameList)) 105 | imageNumberLayout = QHBoxLayout() 106 | imageNumberLayout.setSpacing(5) 107 | self.fromImage = QSpinBox() 108 | self.fromImage.setRange(0,totalImageNb) 109 | toImageLbl = QLabel('to') 110 | self.toImage = QSpinBox() 111 | self.toImage.setRange(0,totalImageNb) 112 | invertBtn = QPushButton('Invert') 113 | invertBtn.clicked.connect(self.invertSelection) 114 | imageLbl = QLabel('Selection:') 115 | self.imageSelected = QLabel('-') 116 | totalImage = QLabel('/ '+str(totalImageNb)) 117 | imageNumberLayout.addStretch(1) 118 | imageNumberLayout.addWidget(self.fromImage) 119 | imageNumberLayout.addWidget(toImageLbl) 120 | imageNumberLayout.addWidget(self.toImage) 121 | imageNumberLayout.addWidget(invertBtn) 122 | imageNumberLayout.addStretch(2) 123 | imageNumberLayout.addWidget(imageLbl) 124 | imageNumberLayout.addWidget(self.imageSelected) 125 | imageNumberLayout.addWidget(totalImage) 126 | imageNumberLayout.addStretch(1) 127 | 128 | analysisName = QHBoxLayout() 129 | analysisName.setSpacing(20) 130 | self.analysisLbl = QLabel('-') 131 | self.analysisInput = QLineEdit() 132 | self.analysisInput.setCursorPosition(0) 133 | self.analysisInput.setTextMargins(3,3,3,3) 134 | currentFont = self.analysisInput.font() 135 | currentFont.setPointSize(15) 136 | self.analysisInput.setFont(currentFont) 137 | self.analysisInput.setMinimumWidth(200) 138 | self.analysisInput.setMinimumHeight(40) 139 | validatorRx = QRegExp("\\w+") 140 | validator = QRegExpValidator(validatorRx, self) 141 | self.analysisInput.setValidator(validator) 142 | analysisName.addStretch(1) 143 | analysisName.addWidget(self.analysisLbl) 144 | analysisName.addWidget(self.analysisInput) 145 | analysisName.addStretch(1) 146 | 147 | buttonLayout = QHBoxLayout() 148 | buttonLayout.setSpacing(40) 149 | cancelButton = QPushButton('Cancel') 150 | cancelButton.setMaximumWidth(100) 151 | cancelButton.setMinimumHeight(30) 152 | self.createButton = QPushButton('Start Analysis') 153 | self.createButton.setMinimumWidth(150) 154 | self.createButton.setMinimumHeight(30) 155 | self.createButton.setEnabled(False) 156 | buttonLayout.addStretch(1) 157 | buttonLayout.addWidget(cancelButton) 158 | buttonLayout.addWidget(self.createButton) 159 | buttonLayout.addStretch(1) 160 | 161 | self.analysisInput.textChanged.connect(lambda: self.textChanged(self.analysisInput.text())) 162 | self.createButton.clicked.connect(lambda: self.createAnalysis(parent, self.analysisInput.text())) 163 | cancelButton.clicked.connect(self.reject) 164 | 165 | dialogLayout.addWidget(infoLbl) 166 | dialogLayout.addLayout(imageLayout) 167 | dialogLayout.addLayout(imageNumberLayout) 168 | dialogLayout.addLayout(analysisName) 169 | dialogLayout.addLayout(buttonLayout) 170 | 171 | self.setLayout(dialogLayout) 172 | self.textChanged('') 173 | self.displayImage(fileNameList) 174 | 175 | 176 | def displayImage(self, fileNameList): 177 | 178 | self.plotArea.matPlot.cla() 179 | imageName = self.imageModel.itemFromIndex(self.imageList.currentIndex()).text() 180 | readImage = cv2.imread(self.filePath+'/'+imageName,0) 181 | self.plotArea.matPlot.imshow(readImage, cmap='gray') 182 | self.plotArea.matPlot.axes.xaxis.set_ticklabels([]) 183 | self.plotArea.matPlot.axes.yaxis.set_ticklabels([]) 184 | self.plotArea.draw_idle() 185 | self.updateSelection() 186 | 187 | def updateSelection(self): 188 | 189 | nbChecked = 0 190 | for image in range(self.imageModel.rowCount()): 191 | if self.imageModel.item(image).checkState() == Qt.Checked: 192 | nbChecked += 1 193 | self.imageSelected.setText(str(nbChecked)) 194 | if nbChecked > 1: 195 | self.imageSelected.setText(str(nbChecked)) 196 | self.textChanged(self.analysisInput.text()) 197 | else: 198 | self.imageSelected.setText(''+str(nbChecked)+'') 199 | self.createButton.setEnabled(False) 200 | 201 | def invertSelection(self): 202 | 203 | imageMin = min(self.fromImage.value(), self.toImage.value()) 204 | imageMax = max(self.fromImage.value(), self.toImage.value()) 205 | for image in range(imageMin, imageMax): 206 | if self.imageModel.item(image).checkState() == Qt.Checked: 207 | self.imageModel.item(image).setCheckState(Qt.Unchecked) 208 | else: 209 | self.imageModel.item(image).setCheckState(Qt.Checked) 210 | self.updateSelection() 211 | 212 | def textChanged(self, name): 213 | 214 | if name != '': 215 | checkName = self.filePath+'/'+name 216 | if os.path.exists(checkName): 217 | self.analysisLbl.setText('Already Exist.') 218 | self.createButton.setEnabled(False) 219 | else: 220 | self.analysisLbl.setText('Analysis Name:') 221 | if int(self.imageSelected.text()) > 1: 222 | self.createButton.setEnabled(True) 223 | else: 224 | self.analysisLbl.setText('Analysis Name:') 225 | self.createButton.setEnabled(False) 226 | 227 | def createAnalysis(self, parent, name): 228 | 229 | directory = self.filePath+'/'+name 230 | #os.makedirs(directory) 231 | fileNameList = [] 232 | for image in range(self.imageModel.rowCount()): 233 | if self.imageModel.item(image).checkState() == Qt.Checked: 234 | fileNameList.append(self.imageModel.item(image).text()) 235 | #np.savetxt(directory+'/filenamelist.dat', fileNameList, fmt="%s") 236 | parent.fileNameList = fileNameList 237 | parent.filePath = self.filePath 238 | parent.fileDataPath = directory 239 | self.accept() 240 | -------------------------------------------------------------------------------- /interface/StrainAnalysis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on 08/03/2016 5 | 6 | @author: Charlie Bourigault 7 | @contact: bourigault.charlie@gmail.com 8 | 9 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 10 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 11 | 12 | Current File: This file runs the Main Analysis widget, parent of the whole visualization tool 13 | """ 14 | 15 | from PyQt4.QtCore import * 16 | from PyQt4.QtGui import * 17 | from interface import menubar, initApp, progressWidget, controlWidget 18 | from functions import DIC_Global, getData, initData, masks 19 | #from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 20 | #from matplotlib.figure import Figure 21 | #import matplotlib.pyplot as plt 22 | #import matplotlib.mlab as ml 23 | #from mpl_toolkits.mplot3d import Axes3D 24 | #from mpl_toolkits.axes_grid1 import make_axes_locatable 25 | #from matplotlib import cm 26 | import numpy as np 27 | 28 | class MainAnalysis(QWidget): 29 | 30 | def __init__(self, parentWindow): 31 | 32 | QWidget.__init__(self) 33 | 34 | self.parentWindow = parentWindow 35 | self.parentWindow.devWindow.addInfo('Visualisation Widget started. Ready to load the data. Setting up the layout.') 36 | 37 | #Layout parameters 38 | self.mainLayout = QHBoxLayout() 39 | #self.mainLayout.setAlignment(Qt.AlignVCenter) 40 | self.mainLayout.setAlignment(Qt.AlignHCenter) 41 | 42 | #Creation of the temporary progressBar 43 | self.openingBar = progressWidget.progressBarWidget(maximumWidth=300) 44 | self.mainLayout.addWidget(self.openingBar) 45 | self.setLayout(self.mainLayout) 46 | 47 | self.parentWindow.devWindow.addInfo('Starting the thread.. Analysis Loading..') 48 | 49 | #initiate and start the opening thread 50 | self.openingThread = DIC_Global.createThread(self.parentWindow, [self.parentWindow, self.openingBar], getData.openData, signal=1) 51 | self.openingThread.signal.threadSignal.connect(self.dataLoaded) 52 | self.openingThread.start() 53 | 54 | def dataLoaded(self, variables): 55 | #remove the progressBar 56 | self.mainLayout.removeWidget(self.openingBar) 57 | self.openingBar.deleteLater() 58 | self.openingBar = None 59 | 60 | if len(variables) > 1: 61 | self.data_x = variables[0] 62 | self.data_y = variables[1] 63 | self.data_corr = variables[2] 64 | self.data_stdx = variables[3] 65 | self.data_stdy = variables[4] 66 | self.disp_x = variables[5] 67 | self.disp_y = variables[6] 68 | self.fileNameList = variables[7] 69 | self.nb_marker = variables[8] 70 | self.nb_image = variables[9] 71 | self.filterList = variables[10] #contains None when no filter 72 | self.grid_instances = variables[11] #list of markers per instance : [[1,2,3],[4,5,6]] => Markers 1,2,3 in grid 1, markers 4,5,6 in grid 2 73 | self.largeDisp = variables[12] #contains None when no largeDisp 74 | self.activeImages = [] 75 | nbInstances = len(np.atleast_1d(self.grid_instances)) 76 | self.activeInstances = np.linspace(0, nbInstances, num=nbInstances, endpoint=False, dtype=np.int) 77 | self.activeMarkers = [] #list of active markers in each image : [[1,2,3],[2,3,4]] => Markers 1,2,3 are active in image 1, markers 2,3,4 are active in image 2 78 | self.xLimit = [0,1] 79 | self.yLimit = [0,1] 80 | self.strainX_data = [] 81 | self.strainY_data = [] 82 | self.neighbors = None 83 | self.createLayout() 84 | else: 85 | firstWidget = initApp.defaultWidget(self.parentWindow) 86 | self.parentWindow.setCentralWidget(firstWidget) 87 | firstWidget.printMessage('Missing Files. Please check the documentation.', imp=1) 88 | 89 | def createLayout(self): 90 | 91 | #self.layout = QVBoxLayout() #create main vertical layout 92 | self.mainLayout.setContentsMargins(0,0,0,0) 93 | 94 | #activate Menu Actions 95 | menubar.menuDisabled(self.parentWindow) 96 | menubar.menuEnabled(self.parentWindow) 97 | 98 | self.parentWindow.devWindow.addInfo('Menus enabled. Toolbar created. Setting-up the layout.') 99 | 100 | self.resultAnalysis = ResultAnalysis(self) 101 | 102 | #control widget 103 | self.controlWidget = controlWidget.controlWidget(self) 104 | self.mainLayout.addStretch(1) 105 | self.mainLayout.addWidget(self.controlWidget) 106 | self.mainLayout.addStretch(1) 107 | 108 | 109 | #activate event for slider 110 | #self.controlWidget.imageSelector.valueChanged.connect(lambda: self.resultAnalysis.graphRefresh()) 111 | #self.controlWidget.sliderSelector.valueChanged.connect(lambda: self.resultAnalysis.graphRefresh) 112 | 113 | self.parentWindow.devWindow.addInfo('Layout ready. Starting the visualisation.') 114 | 115 | self.run() 116 | 117 | 118 | def run(self): 119 | 120 | self.currentMask = masks.openMask(self.parentWindow) 121 | initData.createPlots(self) 122 | 123 | progressBar = progressWidget.progressBarDialog('Opening processes..') 124 | 125 | masks.maskData(self, self.currentMask, progressBar) 126 | 127 | #self.controlWidget.updateAnalysisInfos() 128 | 129 | class ResultAnalysis(QWidget): 130 | 131 | def __init__(self, parentWidget): 132 | 133 | self.parentWidget = parentWidget 134 | 135 | def graphRefresh(self, imageValue=0): #function to refresh the different 3d-plots 136 | 137 | activeImages = self.parentWidget.activeImages 138 | nbActiveImages = len(activeImages) 139 | 140 | if nbActiveImages < 1 or len(np.atleast_1d(self.parentWidget.zi)) < 1: 141 | return 142 | 143 | self.parentWidget.controlWidget.updateImageInfos(imageValue) 144 | value = activeImages[imageValue] 145 | 146 | activeMarkers = self.parentWidget.activeMarkers[value] 147 | activeInstances = self.parentWidget.activeInstances 148 | grid_instances = self.parentWidget.grid_instances 149 | 150 | xAxis = [] 151 | yAxis = [] 152 | dispX = [] 153 | dispY = [] 154 | dataStdX = [] 155 | dataStdY = [] 156 | dataCorr = [] 157 | dataCorr2D = [] 158 | strain2DX = [] 159 | strain2DY = [] 160 | 161 | nbInstances = len(np.atleast_1d(activeInstances)) 162 | for instance in range(nbInstances): 163 | currentInstance = activeInstances[instance] 164 | instanceMarkers = np.intersect1d(grid_instances[currentInstance], activeMarkers, assume_unique=True).astype(np.int) 165 | xAxis.append(self.parentWidget.data_x[instanceMarkers,value]) 166 | yAxis.append(self.parentWidget.data_y[instanceMarkers,value]) 167 | dispX.append(self.parentWidget.disp_x[instanceMarkers,value]) 168 | dispY.append(self.parentWidget.disp_y[instanceMarkers,value]) 169 | dataStdX.append(self.parentWidget.data_stdx[instanceMarkers,value]) 170 | dataStdY.append(self.parentWidget.data_stdy[instanceMarkers,value]) 171 | dataCorr.append(self.parentWidget.data_corr[instanceMarkers,value]) 172 | dataCorr2D.append(self.parentWidget.zi[instance][imageValue]) 173 | strain2DX.append(self.parentWidget.zi_strainX[instance][imageValue]) 174 | strain2DY.append(self.parentWidget.zi_strainY[instance][imageValue]) 175 | 176 | 177 | ######################## 178 | ###### 3D PLOTS ######## 179 | ######################## 180 | 181 | #3D-Displacement along X 182 | self.parentWidget.displacementX.updatePlot(xAxis, yAxis, z_axis=dispX) 183 | 184 | #3D-Displacement along Y 185 | self.parentWidget.displacementY.updatePlot(xAxis, yAxis, z_axis=dispY) 186 | 187 | #3D-Standard Deviation along X 188 | self.parentWidget.deviationX.updatePlot(xAxis, yAxis, z_axis=dataStdX) 189 | 190 | #3D-Standard Deviation along Y 191 | self.parentWidget.deviationY.updatePlot(xAxis, yAxis, z_axis=dataStdY) 192 | 193 | #3D-Correlation 194 | self.parentWidget.correlation.updatePlot(xAxis, yAxis, z_axis=dataCorr) 195 | 196 | ######################## 197 | ###### 2D PLOTS ######## 198 | ######################## 199 | 200 | #2D-Displacement 201 | self.parentWidget.displacement2D.updatePlot(xAxis, yAxis) 202 | 203 | #2D-Correlation 204 | self.parentWidget.correlation2D.updatePlot(dataCorr2D, 0) 205 | 206 | #1D Local Strain along X 207 | self.parentWidget.strainX.updatePlot(xAxis, dispX, z_axis=[self.parentWidget.strainX_data[imageValue, :], self.parentWidget.localStrainIntersectX[imageValue,:]]) 208 | 209 | #1D Local Strain along Y 210 | self.parentWidget.strainY.updatePlot(yAxis, dispY, z_axis=[self.parentWidget.strainY_data[imageValue, :], self.parentWidget.localStrainIntersectY[imageValue, :]]) 211 | 212 | #2D Local Strain along X 213 | self.parentWidget.strain2DX.updatePlot(strain2DX, None) 214 | 215 | #2D Local Strain along Y 216 | self.parentWidget.strain2DY.updatePlot(strain2DY, None) 217 | 218 | #1D True Strain along X 219 | self.parentWidget.trueStrainX.updatePlot(self.parentWidget.strainX_data, 0) 220 | 221 | #1D True Strain along Y 222 | self.parentWidget.trueStrainY.updatePlot(self.parentWidget.strainY_data, 0) 223 | 224 | 225 | 226 | ####################### 227 | ### Run Application ### 228 | ####################### 229 | 230 | def analyseResult(self, parentWindow): 231 | 232 | self.analysisWidget = MainAnalysis(self) 233 | 234 | self.setCentralWidget(self.analysisWidget) 235 | -------------------------------------------------------------------------------- /interface/analysisInfos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 09/08/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the analysis info dialog 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, matplotlib as mpl, time 17 | from functions import plot2D, plot3D, getData, masks, DIC_Global 18 | 19 | def launchDialog(parent): 20 | 21 | analysisDialog = analysisInfos(parent) 22 | analysisDialog.exec_() 23 | 24 | class analysisInfos(QDialog): 25 | 26 | def __init__(self, parent): 27 | 28 | super(analysisInfos, self).__init__() 29 | 30 | self.parent = parent 31 | 32 | self.setWindowTitle('Analysis Info') 33 | self.setMinimumWidth(500) 34 | 35 | self.mainLayout = QVBoxLayout() 36 | #self.mainLayout.setAlignment(Qt.AlignCenter) 37 | #self.mainLayout.setSpacing(30) 38 | 39 | self.setLayout(self.mainLayout) 40 | 41 | self.openInfos() 42 | 43 | def openInfos(self): 44 | 45 | filePath = self.parent.fileDataPath+'/infoAnalysis.csv' 46 | filePathMarkers = self.parent.fileDataPath+'/infoMarkers.csv' 47 | strainX = self.parent.fileDataPath+'/strainx.csv' 48 | strainY = self.parent.fileDataPath+'/strainy.csv' 49 | infos = getData.testReadFile(filePath, lib=1) #None if not found 50 | # 0 Name, 1 Reference Mode, 2 CorrSize, 3 nbProcesses, 4 total processing time, 5 nbImages, 6 nbMarkers, 7 nbImages * nbMarkers, 8 largeDisp YES/NO, 9 Author 51 | self.infos = np.char.decode(infos, encoding="ascii") 52 | self.markersInfos = getData.testReadFile(filePathMarkers) #None if not found 53 | self.fileStrainX = getData.testReadFile(strainX) #None if not found 54 | self.fileStrainY = getData.testReadFile(strainY) #None if not found 55 | if self.infos is None: 56 | missingFile = QLabel('infoAnalysis.csv file not found.') 57 | self.mainLayout.addWidget(missingFile) 58 | else: 59 | self.displayInfos() 60 | 61 | def displayInfos(self): 62 | 63 | infoFrame = QFrame() 64 | infoFrame.setFrameShape(QFrame.StyledPanel) 65 | nameLayout = QHBoxLayout() 66 | nameLayout.setAlignment(Qt.AlignCenter) 67 | nameLbl = QLabel('Name:') 68 | nameLblValue = QLabel(''+str(self.infos[0])+'') 69 | versionLbl = QLabel('Version:') 70 | versionName = self.parent.analysisWidget.controlWidget.currentVersion.text() 71 | versionLblValue = QLabel(''+str(versionName)+'') 72 | authorLbl = QLabel('Author:') 73 | authorLblValue = QLabel(''+str(self.infos[9])+'') 74 | 75 | simpleSeparator = QFrame() 76 | simpleSeparator.setFrameShape(QFrame.VLine) 77 | simpleSeparator2 = QFrame() 78 | simpleSeparator2.setFrameShape(QFrame.VLine) 79 | nameLayout.addWidget(nameLbl) 80 | nameLayout.addWidget(nameLblValue) 81 | nameLayout.addWidget(simpleSeparator) 82 | nameLayout.addWidget(versionLbl) 83 | nameLayout.addWidget(versionLblValue) 84 | nameLayout.addWidget(simpleSeparator2) 85 | nameLayout.addWidget(authorLbl) 86 | nameLayout.addWidget(authorLblValue) 87 | 88 | infoFrame.setLayout(nameLayout) 89 | 90 | globalInfosLayout = QHBoxLayout() 91 | globalInfosLayout.setContentsMargins(50,0,20,20) 92 | #globalInfosLayout.setAlignment(Qt.AlignCenter) 93 | imageOriginalLbl = QLabel('Original Version:') 94 | imagesLbl = QLabel('Nb. Images:') 95 | imagesLbl.setContentsMargins(20,0,0,0) 96 | imagesLblValue = QLabel(''+str(self.infos[5])+'') 97 | markersLbl = QLabel('Nb. Markers:') 98 | markersLblValue = QLabel(''+str(self.infos[7])+' ('+str(self.infos[6])+'/image)') 99 | #globalInfosLayout.addStretch(1) 100 | globalInfosLayout.addWidget(imageOriginalLbl) 101 | globalInfosLayout.addWidget(imagesLbl) 102 | globalInfosLayout.addWidget(imagesLblValue) 103 | globalInfosLayout.addWidget(markersLbl) 104 | globalInfosLayout.addWidget(markersLblValue) 105 | 106 | currentInfosLayout = QHBoxLayout() 107 | currentInfosLayout.setContentsMargins(50,20,20,0) 108 | #currentInfosLayout.setAlignment(Qt.AlignCenter) 109 | imageCurrentLbl = QLabel('Current Version:') 110 | currentImagesLbl = QLabel('Nb. Images:') 111 | currentImagesLbl.setContentsMargins(20,0,0,0) 112 | self.nbActiveImages = self.parent.analysisWidget.controlWidget.totalActive.text() 113 | currentImagesLblValue = QLabel(''+str(self.nbActiveImages)+'') 114 | currentMarkersLbl = QLabel('Nb. Markers:') 115 | nbActiveMarkers = self.parent.analysisWidget.controlWidget.nonMaskedMarkers.text() 116 | currentMarkersLblValue = QLabel(''+str(nbActiveMarkers)+'') 117 | #currentInfosLayout.addStretch(1) 118 | currentInfosLayout.addWidget(imageCurrentLbl) 119 | currentInfosLayout.addWidget(currentImagesLbl) 120 | currentInfosLayout.addWidget(currentImagesLblValue) 121 | currentInfosLayout.addWidget(currentMarkersLbl) 122 | currentInfosLayout.addWidget(currentMarkersLblValue) 123 | 124 | otherInfosLbl = QLabel('- ADDITIONAL INFORMATIONS -') 125 | otherInfosLbl.setContentsMargins(0,0,0,10) 126 | otherInfosLbl.setAlignment(Qt.AlignCenter) 127 | 128 | otherInfosLayout = QHBoxLayout() 129 | otherInfosLayout.setContentsMargins(0,0,0,10) 130 | #otherInfosLayout.setAlignment(Qt.AlignCenter) 131 | corrsizeLbl = QLabel('CorrSize:') 132 | corrsizeLbl.setContentsMargins(20,0,0,0) 133 | corrsizeLblValue = QLabel(''+str(self.infos[2])+'') 134 | referenceLbl = QLabel('Reference:') 135 | referenceLbl.setContentsMargins(20,0,0,0) 136 | if self.infos[1] == '0': 137 | referenceLblTxt = 'Previous' 138 | elif self.infos[1] == '1': 139 | referenceLblTxt = 'First' 140 | else: 141 | referenceLblTxt = 'Shifted' 142 | referenceLblValue = QLabel(''+referenceLblTxt+'') 143 | instanceLbl = QLabel('Nb. Grid Instances:') 144 | instanceLbl.setContentsMargins(20,0,0,0) 145 | instanceLblValue = QLabel(''+str(len(np.atleast_1d(self.parent.analysisWidget.grid_instances)))+' (Active: '+str(len(self.parent.analysisWidget.activeInstances))+')') 146 | nbVersionsLbl = QLabel('Nb. Versions:') 147 | nbVersionsLbl.setContentsMargins(20,0,0,0) 148 | nbVersionsLblValue = QLabel(''+str(masks.openMask(self.parent, getNbMasks = 1))+'') 149 | nbVersionsLblValue.setContentsMargins(0,0,20,0) 150 | otherInfosLayout.addWidget(corrsizeLbl) 151 | otherInfosLayout.addWidget(corrsizeLblValue) 152 | otherInfosLayout.addWidget(referenceLbl) 153 | otherInfosLayout.addWidget(referenceLblValue) 154 | otherInfosLayout.addWidget(instanceLbl) 155 | otherInfosLayout.addWidget(instanceLblValue) 156 | otherInfosLayout.addWidget(nbVersionsLbl) 157 | otherInfosLayout.addWidget(nbVersionsLblValue) 158 | 159 | extraInfosLayout = QHBoxLayout() 160 | extraInfosLayout.setAlignment(Qt.AlignLeft) 161 | processingLbl = QLabel('Correlation processing time:') 162 | processingLbl.setContentsMargins(20,0,0,0) 163 | processingLblValue = QLabel(''+str(self.infos[4])+'') 164 | nbProcessesLbl = QLabel('Nb. Processes:') 165 | nbProcessesLbl.setContentsMargins(20,0,0,0) 166 | nbProcessesLblValue = QLabel(''+str(self.infos[3])+'') 167 | shiftCorrectionLbl = QLabel('Shift Correction:') 168 | shiftCorrectionLbl.setContentsMargins(20,0,0,0) 169 | if self.infos[8] == '0': 170 | shiftCorrectionLblTxt = 'No' 171 | else: 172 | shiftCorrectionLblTxt = 'Yes' 173 | shiftCorrectionLblValue = QLabel(''+shiftCorrectionLblTxt+'') 174 | filterLbl = QLabel('Filters:') 175 | filterLbl.setContentsMargins(20,0,0,0) 176 | filterList = self.parent.analysisWidget.filterList 177 | if filterList is None: 178 | filterApplied = 0 179 | else: 180 | filterApplied = len(np.atleast_1d(filterList)) 181 | filterLblValue = QLabel(''+str(filterApplied)+'') 182 | #filterLblValue.setContentsMargins(0,0,20,0) 183 | extraInfosLayout.addWidget(processingLbl) 184 | extraInfosLayout.addWidget(processingLblValue) 185 | extraInfosLayout.addWidget(nbProcessesLbl) 186 | extraInfosLayout.addWidget(nbProcessesLblValue) 187 | extraInfosLayout.addWidget(shiftCorrectionLbl) 188 | extraInfosLayout.addWidget(shiftCorrectionLblValue) 189 | extraInfosLayout.addWidget(filterLbl) 190 | extraInfosLayout.addWidget(filterLblValue) 191 | 192 | 193 | plotListLayout = QHBoxLayout() 194 | plotListLayout.setAlignment(Qt.AlignLeft) 195 | plotListLayout.setContentsMargins(20,25,0,0) 196 | plotListLbl = QLabel('Display:') 197 | self.plotListBox = QComboBox() 198 | self.plotListBox.setMinimumWidth(200) 199 | availablePlots = ['Correlation Errors', 'Poisson Ratio'] 200 | for plot in availablePlots: 201 | self.plotListBox.addItem(plot) 202 | self.plotListOptions = QComboBox() 203 | self.plotListOptions.setMinimumWidth(150) 204 | self.plotListCheckBox = QCheckBox('Only Active Images') 205 | self.plotListCheckBox.setContentsMargins(30,0,0,0) 206 | plotListLayout.addWidget(plotListLbl) 207 | plotListLayout.addWidget(self.plotListBox) 208 | plotListLayout.addWidget(self.plotListOptions) 209 | plotListLayout.addWidget(self.plotListCheckBox) 210 | 211 | matplotlibLayout = QHBoxLayout() 212 | matplotlibLayout.setContentsMargins(0,0,0,0) 213 | self.matplotlibPlot = DIC_Global.matplotlibWidget() 214 | self.matplotlibPlot.setContentsMargins(0,0,0,0) 215 | matplotlibLayout.addStretch(1) 216 | matplotlibLayout.addWidget(self.matplotlibPlot) 217 | matplotlibLayout.addStretch(1) 218 | self.plotListBox.currentIndexChanged.connect(self.plotOptions) 219 | self.plotListOptions.currentIndexChanged.connect(self.plotInfos) 220 | self.plotListCheckBox.stateChanged.connect(lambda: self.plotInfos(self.plotListOptions.currentIndex())) 221 | 222 | self.mainLayout.addWidget(infoFrame) 223 | self.mainLayout.addLayout(currentInfosLayout) 224 | self.mainLayout.addLayout(globalInfosLayout) 225 | self.mainLayout.addWidget(otherInfosLbl) 226 | self.mainLayout.addLayout(otherInfosLayout) 227 | self.mainLayout.addLayout(extraInfosLayout) 228 | self.mainLayout.addLayout(plotListLayout) 229 | self.mainLayout.addLayout(matplotlibLayout) 230 | self.plotOptions(0) 231 | 232 | def plotOptions(self, item): 233 | 234 | self.plotListOptions.clear() 235 | plotOptions = [] 236 | if item == 0: 237 | plotOptions = ['All', 'Edge Area', 'Marker Out', 'NaN', 'No Std. Dev.', 'Outside SubPx.', 'Div. by 0', 'Low Corr.', 'Bad Peak'] 238 | if item == 1: 239 | plotOptions = ['Compression/Extension along X', 'Compression/Extension along Y'] 240 | for option in plotOptions: 241 | self.plotListOptions.addItem(option) 242 | self.plotInfos(0) 243 | 244 | def plotInfos(self, option): 245 | 246 | self.matplotlibPlot.matPlot.cla() 247 | plotType = self.plotListBox.currentIndex() 248 | onlyActives = self.plotListCheckBox.isChecked() 249 | activeImages = self.parent.analysisWidget.activeImages 250 | totalImages = self.parent.analysisWidget.nb_image 251 | 252 | if plotType == 0: 253 | self.plotListCheckBox.setEnabled(True) 254 | errorList = [] 255 | legend = [] 256 | if onlyActives: 257 | imageRange = len(activeImages) 258 | else: 259 | imageRange = totalImages 260 | if option == 0: 261 | errors = np.unique(self.markersInfos) 262 | for error in errors: 263 | if error == 0: 264 | continue 265 | currentList = [] 266 | for image in range(imageRange): 267 | if onlyActives: 268 | imageNb = activeImages[image] 269 | occurence = list(self.markersInfos[:,imageNb]).count(error) 270 | for nb in range(occurence): 271 | currentList.append(imageNb) 272 | else: 273 | occurence = list(self.markersInfos[:,image]).count(error) 274 | for nb in range(occurence): 275 | currentList.append(image) 276 | if currentList != []: 277 | legend.append(self.plotListOptions.itemText(error)) 278 | errorList.append(currentList) 279 | else: 280 | currentList = [] 281 | for image in range(imageRange): 282 | if onlyActives: 283 | imageNb = activeImages[image] 284 | occurence = list(self.markersInfos[:,imageNb]).count(option) 285 | for nb in range(occurence): 286 | currentList.append(imageNb) 287 | else: 288 | occurence = list(self.markersInfos[:,image]).count(option) 289 | for nb in range(occurence): 290 | currentList.append(image) 291 | if currentList != []: 292 | legend.append(self.plotListOptions.itemText(option)) 293 | errorList.append(currentList) 294 | #errorList = np.array(errorList) 295 | nbList = len(np.atleast_1d(errorList)) 296 | if nbList: 297 | self.matplotlibPlot.matPlot.hist(errorList, totalImages+1, range=(0, totalImages+1), align='right', histtype='bar', stacked=True, label=legend) 298 | if onlyActives is False: 299 | self.matplotlibPlot.matPlot.plot(activeImages+np.ones_like(activeImages), np.zeros_like(activeImages), 'o', c='red') 300 | self.matplotlibPlot.matPlot.set_xlim([0.5,totalImages+0.5]) 301 | self.matplotlibPlot.matPlot.set_ylim(bottom=0) 302 | else: 303 | ax = self.matplotlibPlot.matPlot 304 | ax.text(.5, .5, 'No error.', ha='center', va='center', transform = ax.transAxes, color='red') 305 | 306 | if plotType == 1: 307 | self.plotListCheckBox.setEnabled(False) 308 | nbImages = self.fileStrainX.shape[0] 309 | nbInstances = self.fileStrainX.shape[1] 310 | imageList = np.linspace(1, totalImages+1, totalImages, endpoint=False).astype(np.int) 311 | poissonRatio = np.zeros((nbInstances, nbImages)) 312 | colors = ['blue', 'cornflowerblue', 'royalblue', 'navy'] 313 | if option > 0: 314 | numerator = self.fileStrainX 315 | denominator = self.fileStrainY 316 | else: 317 | numerator = self.fileStrainY 318 | denominator = self.fileStrainX 319 | for instance in range(nbInstances): 320 | for image in range(len(activeImages)): 321 | if denominator[image, instance] != 0: 322 | poissonRatio[instance, image] = numerator[image, instance]/denominator[image, instance] 323 | else: 324 | poissonRatio[instance, image] = np.nan 325 | clr = colors[instance % 4] 326 | lbl = 'Instance '+str(instance) 327 | self.matplotlibPlot.matPlot.plot(activeImages, poissonRatio[instance, :], '-', color=clr, label=lbl) 328 | if nbInstances > 1: 329 | self.matplotlibPlot.matPlot.legend() 330 | self.matplotlibPlot.draw_idle() 331 | -------------------------------------------------------------------------------- /interface/controlWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 15/07/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the control tools in the analysis results 12 | """ 13 | 14 | from PyQt4.QtGui import * 15 | from PyQt4.QtCore import * 16 | import os, numpy as np 17 | from functions import masks 18 | 19 | class controlWidget(QWidget): 20 | 21 | def __init__(self, parent): 22 | 23 | super(controlWidget, self).__init__() 24 | 25 | self.parent = parent 26 | 27 | layout = QVBoxLayout() 28 | layout.setContentsMargins(10,10,10,10) 29 | layout.setSpacing(0) 30 | self.setMinimumWidth(300) 31 | 32 | # Analysis Infos 33 | analysisFrame = QFrame() 34 | analysisFrame.setFrameShape(QFrame.StyledPanel) 35 | analysisFrame.setFixedHeight(80) 36 | analysisLayout = QVBoxLayout() 37 | analysisLayout.setContentsMargins(0,0,0,0) 38 | analysisLayout.setAlignment(Qt.AlignCenter) 39 | 40 | analysisTitleLayout = QHBoxLayout() 41 | analysisTitleLayout.setContentsMargins(0,0,0,0) 42 | analysisTitle = QLabel('Name:') 43 | analysisTitle.setAlignment(Qt.AlignLeft) 44 | analysisTitle.setContentsMargins(2,0,0,0) 45 | analysisTitle.setMinimumWidth(100) 46 | analysisName = QLabel(''+os.path.basename(parent.parentWindow.fileDataPath)+'') 47 | analysisName.setAlignment(Qt.AlignCenter) 48 | analysisTitleLayout.addWidget(analysisTitle) 49 | analysisTitleLayout.addWidget(analysisName) 50 | 51 | imagesLayout = QHBoxLayout() 52 | imagesLayout.setContentsMargins(0,0,0,0) 53 | imagesLbl = QLabel('Active images:') 54 | imagesLbl.setAlignment(Qt.AlignLeft) 55 | imagesLbl.setContentsMargins(2,0,0,0) 56 | imagesLbl.setMinimumWidth(100) 57 | self.totalActive = QLabel(str(len(parent.activeImages))) 58 | self.totalActive.setAlignment(Qt.AlignCenter) 59 | totalImages = QLabel('('+str(parent.nb_image)+')') 60 | imagesLayout.addWidget(imagesLbl) 61 | imagesLayout.addWidget(self.totalActive) 62 | imagesLayout.addWidget(totalImages) 63 | 64 | markersLayout = QHBoxLayout() 65 | markersLayout.setContentsMargins(0,0,0,0) 66 | markersLbl = QLabel('Active markers:') 67 | markersLbl.setAlignment(Qt.AlignLeft) 68 | markersLbl.setContentsMargins(2,0,0,0) 69 | markersLbl.setMinimumWidth(100) 70 | self.nonMaskedMarkers = QLabel('-') 71 | self.nonMaskedMarkers.setAlignment(Qt.AlignCenter) 72 | totalMarkers = QLabel('('+str(int(parent.data_x.size))+')') 73 | markersLayout.addWidget(markersLbl) 74 | markersLayout.addWidget(self.nonMaskedMarkers) 75 | markersLayout.addWidget(totalMarkers) 76 | 77 | versionLayout = QHBoxLayout() 78 | versionLayout.setContentsMargins(0,0,0,0) 79 | versionLbl = QLabel('Version:') 80 | versionLbl.setAlignment(Qt.AlignLeft) 81 | versionLbl.setContentsMargins(2,0,0,0) 82 | versionLbl.setMinimumWidth(100) 83 | self.currentVersionName = '-' 84 | self.currentVersion = QLabel(self.currentVersionName) 85 | self.currentVersion.setAlignment(Qt.AlignCenter) 86 | self.currentVersion.enterEvent = lambda x: self.currentVersion.setText('Click to rename.') 87 | self.currentVersion.leaveEvent = lambda x: self.currentVersion.setText(self.currentVersionName) 88 | self.currentVersion.mousePressEvent = lambda x: masks.renameMask(self.parent.parentWindow, self.currentVersionName) 89 | versionLayout.addWidget(versionLbl) 90 | versionLayout.addWidget(self.currentVersion) 91 | 92 | analysisLayout.addLayout(analysisTitleLayout) 93 | analysisLayout.addLayout(imagesLayout) 94 | analysisLayout.addLayout(markersLayout) 95 | analysisLayout.addLayout(versionLayout) 96 | analysisFrame.setLayout(analysisLayout) 97 | 98 | 99 | # Current Image Infos 100 | currentLayout = QHBoxLayout() 101 | 102 | imageInfoFrame = QWidget() 103 | imageInfoFrame.setMaximumWidth(300) 104 | imageInfoLayout = QVBoxLayout() 105 | imageInfoLayout.setAlignment(Qt.AlignCenter) 106 | self.imageName = QLabel('-') 107 | imageNbLbl = QLabel('Image') 108 | imageNbLbl.setContentsMargins(0,5,0,0) 109 | self.imageNumber = QLabel('1') 110 | self.imageNumber.setFrameStyle(QFrame.StyledPanel) 111 | self.imageNumber.setAlignment(Qt.AlignCenter) 112 | markersInImageLbl = QLabel('Markers') 113 | markersInImageLbl.setContentsMargins(0,5,0,0) 114 | self.markersInImage = QLabel('-') 115 | self.markersInImage.setFrameStyle(QFrame.StyledPanel) 116 | self.markersInImage.setAlignment(Qt.AlignCenter) 117 | strainLbl = QLabel('Strain') 118 | strainLbl.setContentsMargins(0,5,0,0) 119 | self.strainValue = QLabel('-') 120 | self.strainValue.setFrameStyle(QFrame.StyledPanel) 121 | self.strainValue.setAlignment(Qt.AlignCenter) 122 | imageInfoLayout.addStretch(1) 123 | imageInfoLayout.addWidget(self.imageName) 124 | imageInfoLayout.addStretch(1) 125 | imageInfoLayout.addWidget(imageNbLbl) 126 | imageInfoLayout.addWidget(self.imageNumber) 127 | imageInfoLayout.addWidget(markersInImageLbl) 128 | imageInfoLayout.addWidget(self.markersInImage) 129 | imageInfoLayout.addWidget(strainLbl) 130 | imageInfoLayout.addWidget(self.strainValue) 131 | imageInfoLayout.addStretch(1) 132 | imageInfoFrame.setLayout(imageInfoLayout) 133 | 134 | self.imageSelector = QDial() 135 | self.imageSelector.setContentsMargins(0,0,0,0) 136 | self.imageSelector.valueChanged.connect(lambda: self.updateSlider(self.imageSelector)) 137 | 138 | currentLayout.addWidget(imageInfoFrame) 139 | currentLayout.addWidget(self.imageSelector) 140 | 141 | self.sliderSelector = QSlider(Qt.Horizontal) 142 | self.sliderSelector.valueChanged.connect(lambda: self.updateSlider(self.sliderSelector)) 143 | 144 | layout.addStretch(1) 145 | layout.addWidget(analysisFrame) 146 | layout.addLayout(currentLayout) 147 | layout.addWidget(self.sliderSelector) 148 | 149 | self.setLayout(layout) 150 | 151 | def updateSlider(self, slider): 152 | 153 | imageValue = slider.value() 154 | if slider == self.imageSelector: 155 | if self.sliderSelector.value != imageValue: 156 | self.sliderSelector.setValue(imageValue) 157 | else: 158 | if self.imageSelector.value != imageValue: 159 | self.imageSelector.setValue(imageValue) 160 | self.parent.resultAnalysis.graphRefresh(imageValue) 161 | 162 | def updateAnalysisInfos(self): 163 | 164 | nbActiveImages = len(self.parent.activeImages) 165 | self.imageSelector.setRange(0, nbActiveImages - 1) 166 | self.sliderSelector.setRange(0, nbActiveImages - 1) 167 | self.totalActive.setText(str(nbActiveImages)) 168 | self.nonMaskedMarkers.setText(str(int(np.sum(self.parent.currentMask)))) 169 | self.imageSelector.setValue(0) 170 | self.sliderSelector.setValue(0) 171 | 172 | 173 | def updateImageInfos(self, image): 174 | 175 | 176 | currentImage = self.parent.activeImages[image] 177 | self.imageName.setText(''+str(self.parent.fileNameList[currentImage])+'') 178 | self.imageNumber.setText(str(image+1)) 179 | nbMarkersInImage = 0 180 | for instance in self.parent.activeInstances: 181 | nbMarkersInImage += len(np.atleast_1d(self.parent.grid_instances[instance])) 182 | self.markersInImage.setText(str(nbMarkersInImage)) 183 | self.strainValue.setText('-') 184 | 185 | def resizeEvent(self, event=0): 186 | 187 | self.resize(self.minimumSizeHint()) 188 | -------------------------------------------------------------------------------- /interface/deleteImages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 21/07/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages dialog to mask images from the current analysis 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, copy 17 | from functions import masks 18 | from interface import progressWidget 19 | 20 | class deleteImageDialog(QDialog): 21 | 22 | def __init__(self, fileNameList, activeImages, parent): 23 | 24 | QDialog.__init__(self) 25 | 26 | self.fileNameList = fileNameList 27 | currentMask = copy.deepcopy(parent.currentMask) 28 | 29 | self.setWindowTitle('Mask Images') 30 | self.setMinimumWidth(300) 31 | 32 | dialogLayout = QVBoxLayout() 33 | 34 | dialogLabel = QLabel('Select images you want to mask from the current analysis.') 35 | 36 | self.dialogListWidget = QListWidget() 37 | self.dialogListWidget.setSelectionMode(QAbstractItemView.MultiSelection) 38 | self.dialogListWidget.itemSelectionChanged.connect(self.refreshLbl) 39 | 40 | for image in activeImages: 41 | currentImage = QListWidgetItem(fileNameList[image]) 42 | self.dialogListWidget.addItem(currentImage) 43 | 44 | selectedLayout = QHBoxLayout() 45 | selectedLabel = QLabel('Total selection: ') 46 | self.selectedValueLbl = QLabel('0') 47 | selectedLayout.addStretch(1) 48 | selectedLayout.addWidget(selectedLabel) 49 | selectedLayout.addWidget(self.selectedValueLbl) 50 | selectedLayout.addStretch(1) 51 | 52 | buttonLayout = QHBoxLayout() 53 | dialogButton = QPushButton('Mask Images') 54 | dialogButton.setMaximumWidth(100) 55 | dialogButton.clicked.connect(lambda: self.deleteSelection(currentMask, activeImages, parent)) 56 | buttonLayout.addStretch(1) 57 | buttonLayout.addWidget(dialogButton) 58 | buttonLayout.addStretch(1) 59 | 60 | dialogLayout.addWidget(dialogLabel) 61 | dialogLayout.addWidget(self.dialogListWidget) 62 | dialogLayout.addLayout(selectedLayout) 63 | dialogLayout.addLayout(buttonLayout) 64 | 65 | self.setLayout(dialogLayout) 66 | 67 | def deleteSelection(self, currentMask, activeImages, parent): 68 | 69 | selectedItems = self.dialogListWidget.selectedItems() 70 | nbSelected = len(selectedItems) 71 | if nbSelected > 0: 72 | 73 | indicesToDelete = [] 74 | for element in selectedItems: 75 | indicesToDelete.append(self.dialogListWidget.row(element)) 76 | 77 | currentMask[:, np.array(activeImages)[indicesToDelete]] = 0 78 | 79 | shouldApplyMask = masks.generateMask(currentMask, parent.parentWindow.fileDataPath) 80 | if shouldApplyMask is not None: 81 | progressBar = progressWidget.progressBarDialog('Saving masks..') 82 | masks.maskData(parent, currentMask, progressBar, toRecalculate = shouldApplyMask) 83 | self.close() 84 | 85 | def refreshLbl(self): 86 | 87 | nbSelected = len(self.dialogListWidget.selectedItems()) 88 | self.selectedValueLbl.setText(str(nbSelected)) 89 | 90 | def launchDeleteImageDialog(self): 91 | 92 | self.analysisWidget.parentWindow.devWindow.addInfo('Cleaning Procedure Request : Delete Images') 93 | self.deleteImg = deleteImageDialog(self.analysisWidget.fileNameList, self.analysisWidget.activeImages, self.analysisWidget) 94 | self.deleteImg.exec_() 95 | -------------------------------------------------------------------------------- /interface/devMode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 05/07/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the developer mode - NOT TESTED COMPLETELY 12 | """ 13 | 14 | from PyQt4.QtGui import * 15 | from PyQt4.QtCore import * 16 | 17 | class DevMode(QDockWidget): #dockWidget used in DevMode to display more informations to the developer 18 | 19 | def __init__(self, parent, Mode): 20 | 21 | super(DevMode, self).__init__() 22 | 23 | self.devMode = Mode 24 | 25 | if self.devMode == 1: 26 | self.devWidget = QWidget() 27 | self.devWidget.setMaximumHeight(100) 28 | self.devLayout = QVBoxLayout() 29 | self.label = QLabel('Welcome in Dev. Mode') 30 | self.textInfo = QTextEdit() 31 | self.textInfo.setReadOnly(True) 32 | self.devInfo = self.textInfo 33 | self.devInfo.verticalScrollBar().rangeChanged.connect(self.ResizeScroll) #auto-scroll down 34 | self.devLayout.addWidget(self.label) 35 | self.devLayout.addWidget(self.textInfo) 36 | self.devWidget.setLayout(self.devLayout) 37 | self.setWidget(self.devWidget) 38 | #self.show() 39 | self.addInfo('Application started.') 40 | else: 41 | self.setHidden(True) 42 | 43 | parent.addDockWidget(Qt.BottomDockWidgetArea, self) 44 | 45 | def ResizeScroll(self, min, maxi): #auto-scroll down function for DevMode 46 | self.devInfo.verticalScrollBar().setValue(maxi) 47 | 48 | def addInfo(self, message, statusBar=None): 49 | 50 | if self.devMode == 1: 51 | self.devInfo.append(message) #add the message to the developer widget if the devMode is activated 52 | if statusBar is not None: 53 | statusBar.showMessage(message) #add the message in the statusBar for the used if statusBar specified 54 | -------------------------------------------------------------------------------- /interface/dispVsPos.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 12/04/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the Disp. vs Pos. mask procedure 12 | """ 13 | from PyQt4.QtCore import * 14 | from PyQt4.QtGui import * 15 | import numpy as np, copy, matplotlib.patches as mpp 16 | from functions import masks, DIC_Global 17 | from interface import progressWidget 18 | 19 | class dispVsPosDialog(QDialog): 20 | 21 | def __init__(self, parent, currentImage): #initiate the main layout 22 | 23 | QDialog.__init__(self) 24 | self.parent = parent 25 | self.fileDataPath = parent.parentWindow.fileDataPath 26 | self.currentMask = copy.deepcopy(parent.currentMask) 27 | self.activeImages = parent.activeImages 28 | self.activeMarkers = parent.activeMarkers 29 | self.activeInstances = parent.activeInstances 30 | self.gridInstances = parent.grid_instances 31 | self.data_x = parent.data_x 32 | self.data_y = parent.data_y 33 | self.disp_x = parent.disp_x 34 | self.disp_y = parent.disp_y 35 | 36 | self.setWindowTitle('Dispersion vs Position') 37 | self.setMinimumWidth(500) 38 | dialogLayout = QVBoxLayout() 39 | 40 | dialogLabel = QLabel('Clean markers by linear displacement analysis. Draw rectangles over the markers you want to select.') 41 | dialogLabel.setAlignment(Qt.AlignHCenter) 42 | dialogLabel.setMinimumHeight(30) 43 | 44 | checkBoxOptions = QHBoxLayout() 45 | checkBoxOptions.setAlignment(Qt.AlignHCenter) 46 | checkBoxOptions.setSpacing(10) 47 | #layout items 48 | self.displayXMarkers = QCheckBox('Displacement along X') 49 | self.displayXMarkers.setChecked(True) 50 | self.displayYMarkers = QCheckBox('Displacement along Y') 51 | #ComboBox to select instance 52 | instanceSelectLbl = QLabel('Display Instance(s):') 53 | self.instanceSelect = QComboBox(self) 54 | self.instanceSelect.addItem('All') 55 | for instance in self.activeInstances: 56 | self.instanceSelect.addItem(str(instance)) 57 | #add items to layout 58 | checkBoxOptions.addStretch(1) 59 | checkBoxOptions.addWidget(self.displayXMarkers) 60 | checkBoxOptions.addWidget(self.displayYMarkers) 61 | checkBoxOptions.addStretch(1) 62 | checkBoxOptions.addWidget(instanceSelectLbl) 63 | checkBoxOptions.addWidget(self.instanceSelect) 64 | checkBoxOptions.addStretch(1) 65 | 66 | #checkBox clicked 67 | self.displayXMarkers.stateChanged.connect(self.plotDispersion) 68 | self.displayYMarkers.stateChanged.connect(self.plotDispersion) 69 | #comboBox index changed 70 | self.instanceSelect.currentIndexChanged.connect(self.plotDispersion) 71 | 72 | self.plotArea = DIC_Global.matplotlibWidget() 73 | self.plotArea.setFocusPolicy(Qt.ClickFocus) 74 | self.plotArea.setFocus() 75 | 76 | buttonBox = QHBoxLayout() 77 | self.deleteButton = QPushButton('Delete Selection') 78 | self.deleteButton.setMinimumWidth(120) 79 | self.imageSelectSpinBox = QSpinBox(self) 80 | self.imageSelectSpinBox.setRange(1, len(np.atleast_1d(self.activeImages))) 81 | self.imageSelectSpinBox.setValue(currentImage) 82 | self.allImagesCheckBox = QCheckBox('Apply on all images.') 83 | self.allImagesCheckBox.setChecked(True) 84 | buttonBox.addStretch(1) 85 | buttonBox.addWidget(self.deleteButton) 86 | buttonBox.addWidget(self.imageSelectSpinBox) 87 | buttonBox.addWidget(self.allImagesCheckBox) 88 | buttonBox.addStretch(1) 89 | #actions 90 | self.deleteButton.clicked.connect(self.maskSelection) 91 | self.imageSelectSpinBox.valueChanged.connect(self.plotDispersion) 92 | 93 | #Dialog Window Layout 94 | dialogLayout.addWidget(dialogLabel) 95 | dialogLayout.addLayout(checkBoxOptions) 96 | dialogLayout.addWidget(self.plotArea) 97 | dialogLayout.addLayout(buttonBox) 98 | 99 | self.setLayout(dialogLayout) 100 | self.plotDispersion() 101 | 102 | def plotDispersion(self): #refreshing function 103 | 104 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 105 | 106 | self.plotArea.matPlot.cla() 107 | self.plotArea.mpl_connect('button_press_event', self.on_press) #activate mouse event detection 108 | self.plotArea.mpl_connect('button_release_event', self.on_release) 109 | self.plotArea.mpl_connect('key_press_event', self.on_key) 110 | 111 | markerSelection = self.currentMask[:, value] 112 | data_x_init = self.data_x[:, value] 113 | data_y_init = self.data_y[:, value] 114 | disp_x_init = self.disp_x[:, value] 115 | disp_y_init = self.disp_y[:, value] 116 | colorsX = ['green', 'lightgreen', 'limegreen', 'seagreen'] 117 | colorsY = ['blue', 'cornflowerblue', 'royalblue', 'navy'] 118 | 119 | plotXFit = None 120 | plotYFit = None 121 | 122 | minLimit = -0.0001 123 | maxLimit = 0.0001 124 | 125 | validInstances = self.returnValidInstances() 126 | for instance in validInstances: 127 | 128 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 129 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]],self.activeMarkers[value], assume_unique=True).astype(np.int) 130 | selectedMarkers = [marker for marker in instanceMarkers if markerSelection[marker] == 0] 131 | unSelectedMarkers = [marker for marker in instanceMarkers if marker not in selectedMarkers] 132 | 133 | if self.displayXMarkers.isChecked(): 134 | 135 | #plot the fitting line of the value the user want to keep 136 | clr = colorsX[instance % 4] 137 | lbl = 'Instance '+str(self.activeInstances[instance])+' - X-Disp.' 138 | if len(np.atleast_1d(unSelectedMarkers)) > 1: 139 | ax, bx = np.polyfit(data_x_init[unSelectedMarkers], disp_x_init[unSelectedMarkers], 1) #calculate the fitting line without taking care of the selected markers 140 | plotXFit = self.plotArea.matPlot.plot(data_x_init[unSelectedMarkers], ax*data_x_init[unSelectedMarkers]+bx, '-', color=clr) 141 | else: 142 | clr = 'magenta' 143 | self.plotArea.matPlot.plot(data_x_init[unSelectedMarkers], disp_x_init[unSelectedMarkers], 'o', ms=3, color=clr, label=lbl) #plot all the markers in X direction 144 | self.plotArea.matPlot.plot(data_x_init[selectedMarkers], disp_x_init[selectedMarkers], 'o', ms=5, color='red') 145 | 146 | if len(np.atleast_1d(disp_x_init[instanceMarkers])) > 0: 147 | minLimit = min(np.min(disp_x_init[instanceMarkers]), minLimit) 148 | maxLimit = max(np.max(disp_x_init[instanceMarkers]), maxLimit) 149 | 150 | if self.displayYMarkers.isChecked(): 151 | 152 | #plot the fitting line of the value the user want to keep 153 | clr = colorsY[instance % 4] 154 | lbl = 'Instance '+str(self.activeInstances[instance])+' - Y-Disp.' 155 | if len(np.atleast_1d(unSelectedMarkers)) > 1: 156 | ay, by = np.polyfit(data_y_init[unSelectedMarkers], disp_y_init[unSelectedMarkers], 1) #calculate the fitting line without taking care of the selected markers 157 | plotYFit = self.plotArea.matPlot.plot(data_y_init[unSelectedMarkers], ay*data_y_init[unSelectedMarkers]+by, '-', color=clr) 158 | else: 159 | clr = 'orange' 160 | self.plotArea.matPlot.plot(data_y_init[unSelectedMarkers], disp_y_init[unSelectedMarkers], 'o', ms=3, color=clr, label=lbl) #plot all the markers in X direction 161 | self.plotArea.matPlot.plot(data_y_init[selectedMarkers], disp_y_init[selectedMarkers], 'o', ms=5, color='red') 162 | 163 | if len(np.atleast_1d(disp_y_init[instanceMarkers])) > 0: 164 | minLimit = min(np.min(disp_y_init[instanceMarkers]), minLimit) 165 | maxLimit = max(np.max(disp_y_init[instanceMarkers]), maxLimit) 166 | 167 | if minLimit != maxLimit: 168 | self.plotArea.matPlot.set_ylim([minLimit-.1*np.absolute(minLimit),maxLimit+.1*np.absolute(maxLimit)]) # 10% extra to be able to select all markers 169 | else: 170 | self.plotArea.matPlot.set_ylim([-1,1]) 171 | 172 | if (self.displayXMarkers.isChecked() or self.displayYMarkers.isChecked()) and (plotXFit is not None or plotYFit is not None): #plot the legend 173 | self.plotArea.matPlot.legend() 174 | 175 | self.plotArea.draw_idle() #refresh the area 176 | 177 | def returnValidInstances(self): 178 | 179 | nbInstances = len(np.atleast_1d(self.activeInstances)) 180 | validInstances = [] 181 | instanceSelectTxt = self.instanceSelect.currentText() 182 | if self.instanceSelect.currentText() == "All": 183 | for instance in range(nbInstances): 184 | validInstances.append(instance) 185 | else: 186 | validInstances.append(self.instanceSelect.currentIndex()-1) 187 | return validInstances 188 | 189 | def selectRectangleMarkers(self, x0, y0, width, height): #when a rectangle is drawn, select all the markers inside 190 | 191 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 192 | if x0 is None: 193 | return 194 | markerSelection = self.currentMask[:, value] 195 | data_x_current = self.data_x[:, value] 196 | data_y_current = self.data_y[:, value] 197 | disp_x_current = self.disp_x[:, value] 198 | disp_y_current = self.disp_y[:, value] 199 | 200 | validInstances = self.returnValidInstances() 201 | for instance in validInstances: 202 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 203 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]],self.activeMarkers[value], assume_unique=True).astype(np.int) 204 | 205 | if self.displayXMarkers.isChecked(): 206 | for i in instanceMarkers: 207 | if data_x_current[i] > min(x0, x0+width) and data_x_current[i] < max(x0, x0+width) and disp_x_current[i] > min(y0, y0+height) and disp_x_current[i] < max(y0, y0+height): 208 | if markerSelection[i] == 1: 209 | if self.allImagesCheckBox.isChecked(): 210 | self.currentMask[i, :] = 0 211 | else: 212 | self.currentMask[i, value] = 0 213 | else: 214 | if self.allImagesCheckBox.isChecked(): 215 | self.currentMask[i, :] = 1 216 | else: 217 | self.currentMask[i, value] = 1 218 | 219 | if self.displayYMarkers.isChecked(): 220 | for i in instanceMarkers: 221 | if data_y_current[i] > min(x0, x0+width) and data_y_current[i] < max(x0, x0+width) and disp_y_current[i] > min(y0, y0+height) and disp_y_current[i] < max(y0, y0+height): 222 | if markerSelection[i] == 1: 223 | if self.allImagesCheckBox.isChecked(): 224 | self.currentMask[i, :] = 0 225 | else: 226 | self.currentMask[i, value] = 0 227 | else: 228 | if self.allImagesCheckBox.isChecked(): 229 | self.currentMask[i, :] = 1 230 | else: 231 | self.currentMask[i, value] = 1 232 | 233 | self.plotDispersion() 234 | 235 | def on_press(self, event): 236 | self.x0 = event.xdata 237 | self.y0 = event.ydata 238 | if self.x0 is None: 239 | return 240 | self.x2 = self.x0 #save coordinates in case the user goes out of the picture limits 241 | self.y2 = self.y0 242 | self.rect = mpp.Rectangle((0,0), 0, 0, facecolor='None', edgecolor='green', linewidth=2.5) #add invisible rectangle to be shown later 243 | self.plotArea.matPlot.add_patch(self.rect) 244 | self.motionRect = self.plotArea.mpl_connect('motion_notify_event', self.on_motion) 245 | self.rect.set_xy((self.x0, self.y0)) 246 | self.rect.set_linestyle('dashed') 247 | 248 | def on_motion(self,event): 249 | 250 | x1 = event.xdata 251 | y1 = event.ydata 252 | if x1 is None: 253 | x1 = self.x2 254 | if y1 is None: 255 | y1 = self.y2 256 | self.x2 = x1 257 | self.y2 = y1 258 | width = x1 - self.x0 259 | height = y1 - self.y0 260 | self.rect.set_width(width) 261 | self.rect.set_height(height) 262 | #self.rect.set_xy((self.x0, self.y0)) 263 | self.rect.set_linestyle('dashed') 264 | self.plotArea.draw_idle() 265 | 266 | def on_release(self, event): 267 | x1 = event.xdata 268 | y1 = event.ydata 269 | if self.x0 is None: 270 | return 271 | self.plotArea.mpl_disconnect(self.motionRect) 272 | if x1 is None: 273 | x1 = self.x2 274 | if y1 is None: 275 | y1 = self.y2 276 | if x1 != self.x0: 277 | self.selectRectangleMarkers(self.x0, self.y0, x1-self.x0, y1-self.y0) 278 | self.rect.set_width(0) 279 | self.rect.set_height(0) 280 | self.plotArea.draw_idle() 281 | 282 | def on_key(self, event): 283 | #print('you pressed', event.key, event.xdata, event.ydata) 284 | isValidEvent = False 285 | if event.key == 'd': 286 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 287 | markerSelection = self.currentMask[:, value] 288 | 289 | validInstances = self.returnValidInstances() 290 | for instance in validInstances: 291 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 292 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]],self.activeMarkers[value], assume_unique=True).astype(np.int) 293 | for i in instanceMarkers: 294 | if markerSelection[i] == 0: 295 | self.currentMask[i,:] = 1 296 | isValidEvent = True 297 | 298 | if isValidEvent is True: 299 | self.plotDispersion() 300 | 301 | def maskSelection(self): #deleted the different selected markers 302 | 303 | shouldApplyMask = masks.generateMask(self.currentMask, self.fileDataPath) 304 | if shouldApplyMask is not None: 305 | self.parent.parentWindow.devWindow.addInfo('Masking selected markers..') 306 | progressBar = progressWidget.progressBarDialog('Saving masks..') 307 | masks.maskData(self.parent, self.currentMask, progressBar, toRecalculate=shouldApplyMask) 308 | self.close() 309 | 310 | def launchDVPDialog(self, currentImage): #initiate the variables and launch the dialog 311 | 312 | self.analysisWidget.parentWindow.devWindow.addInfo('Cleaning Procedure Request : Displacement vs. Position') 313 | 314 | 315 | self.DVP = dispVsPosDialog(self.analysisWidget, currentImage) 316 | self.DVP.exec_() 317 | -------------------------------------------------------------------------------- /interface/dockWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 08/08/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the movable and closable dockWidgets displaying 2D and 3D plots 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, cv2 17 | from functions import DIC_Global, filterFunctions, plot2D, plot3D 18 | 19 | 20 | class dockPlot(QDockWidget): #dockWidget containing a Matplotlib Widget 21 | 22 | instances = [] #will contain the class instances 23 | maximumSize = 10 24 | 25 | def __init__(self, title, graphType, graphDisplay, parentWindow): 26 | 27 | super(dockPlot, self).__init__() 28 | 29 | if graphDisplay == 0: #default options for 3D plots 30 | self.scatter = 0 31 | self.projection = [False,False] 32 | if graphDisplay == 5: #default options for TrueStrain plot 33 | self.averageImageNb = 1 34 | self.graphDisplay = graphDisplay 35 | self.parentWindow = parentWindow 36 | self.parentWindow.setTabPosition(Qt.LeftDockWidgetArea, QTabWidget.West) 37 | self.parentWindow.setTabPosition(Qt.RightDockWidgetArea, QTabWidget.East) 38 | 39 | self.setWindowTitle(title) 40 | self.setAllowedAreas(Qt.TopDockWidgetArea | Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) #bottom is reserved for DevMode 41 | self.dockWidget = DIC_Global.matplotlibWidget(graphType, self) #init 3d plot 42 | self.setWidget(self.dockWidget) 43 | self.setFeatures(QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable) #do not allow floatable widget 44 | dockPlot.instances.append(self) #add the new instance to the list 45 | 46 | 47 | def resizeEvent(self, QResizeEvent=0): #called everytime the widget is changing his size 48 | 49 | left = 1 50 | right = 1 51 | top = 0 52 | for instance in dockPlot.instances: 53 | if instance.visibleRegion().isEmpty() == False: 54 | area = self.parentWindow.dockWidgetArea(instance) 55 | if area == Qt.TopDockWidgetArea: 56 | top += 1 57 | elif area == Qt.LeftDockWidgetArea: 58 | left += 1 59 | elif area == Qt.RightDockWidgetArea: 60 | right += 1 61 | 62 | widthWindow = self.parentWindow.width() #get the dimension of the whole window 63 | heightWindow = self.parentWindow.height()-self.parentWindow.menubar.height()-self.parentWindow.statusBar().height()-self.parentWindow.analysisWidget.controlWidget.height() 64 | 65 | if top < 1: 66 | top = 1 67 | dockPlot.maximumSize = min(widthWindow/top, heightWindow/left, heightWindow/right) 68 | 69 | 70 | for instance in dockPlot.instances: #set the boundaries of each widget 71 | instance.dockWidget.setMinimumHeight(dockPlot.maximumSize*.9) 72 | instance.dockWidget.setMinimumWidth(dockPlot.maximumSize*.9) 73 | 74 | figSize = self.dockWidget.figure.get_size_inches() 75 | dpi = self.dockWidget.figure.get_dpi() 76 | width = figSize[0]*dpi 77 | height = figSize[1]*dpi 78 | if np.isfinite(width) and np.isfinite(height): 79 | size = QSize(width, height) 80 | self.dockWidget.canvas.resize(size) 81 | self.dockWidget.canvas.draw_idle() 82 | 83 | 84 | def moveEvent(self, QMoveEvent): #when the user move a dockWidget, temporary reduce the minimumSize of widgets 85 | 86 | for instance in dockPlot.instances: 87 | instance.dockWidget.setMinimumHeight(dockPlot.maximumSize*.5) 88 | instance.dockWidget.setMinimumWidth(dockPlot.maximumSize*.5) 89 | 90 | 91 | def updatePlot(self, data_x, data_y, z_axis = None): 92 | 93 | activeInstances = self.parentWindow.analysisWidget.activeInstances 94 | currentImage = int(self.parentWindow.analysisWidget.controlWidget.imageNumber.text()) 95 | realImage = self.parentWindow.analysisWidget.activeImages[currentImage-1] 96 | if self.visibleRegion().isEmpty() == False: #plot only if visible by the user 97 | if self.graphDisplay == 0: # 3D PLOT 98 | plot3D.update3D_subplot(self.dockWidget.matPlot, data_x, data_y, z_axis, self.scatter, self.projection) 99 | elif self.graphDisplay == 1: # DISPLACEMENT/DEVIATION 2D 100 | image = cv2.imread(self.parentWindow.filePath + '/' + str(self.parentWindow.analysisWidget.fileNameList[realImage]),0) 101 | filterList = self.parentWindow.analysisWidget.filterList 102 | if filterList is not None and image is not None: 103 | image = filterFunctions.applyFilterListToImage(filterList, image) 104 | plot2D.update2D_displacementDeviation(self.dockWidget.matPlot, data_x, data_y, image) 105 | elif self.graphDisplay == 2: # CORRELATION 2D 106 | plot2D.update2D_correlation(self, self.dockWidget.figure, self.dockWidget.matPlot, data_x) 107 | elif self.graphDisplay == 3: # STRAIN 1D 108 | plot2D.update2D_strain(self, self.dockWidget.matPlot, data_x, data_y, z_axis) 109 | elif self.graphDisplay == 4: # STRAIN 2D 110 | plot2D.update2D_strain(self, self.dockWidget.matPlot, data_x, data_y, self.dockWidget.figure) 111 | elif self.graphDisplay == 5: # TRUE STRAIN 1D 112 | plot2D.plot_TrueStrain(self, self.dockWidget.matPlot, [data_x, self.averageImageNb, activeInstances]) 113 | self.dockWidget.canvas.draw_idle() 114 | 115 | class dockParameters(QDialog): 116 | 117 | def __init__(self, dockWidget, graphDisplay): 118 | 119 | QDialog.__init__(self) 120 | 121 | self.setWindowTitle('Dock Parameters') 122 | self.setMinimumWidth(300) 123 | self.dockWidget = dockWidget 124 | 125 | self.mainLayout = QVBoxLayout() 126 | self.mainLayout.setAlignment(Qt.AlignCenter) 127 | 128 | if graphDisplay == 0: 129 | self.plots3D() 130 | elif graphDisplay == 2: 131 | self.correlation2D() 132 | elif graphDisplay == 4: 133 | self.strain2D() 134 | elif graphDisplay == 5: 135 | self.trueStrain() 136 | else: 137 | inDev = QLabel('Currently in development.') 138 | self.mainLayout.addWidget(inDev) 139 | 140 | self.setLayout(self.mainLayout) 141 | 142 | def plots3D(self): 143 | 144 | dataDisplayGroup = QGroupBox('Data Display') 145 | 146 | dataDisplayLayout = QVBoxLayout() 147 | 148 | formatLayout = QHBoxLayout() 149 | formatLayout.setAlignment(Qt.AlignCenter) 150 | displayLbl = QLabel('Plot Format:') 151 | displayCombo = QComboBox() 152 | displayCombo.setMaximumWidth(80) 153 | displayCombo.addItem('Surface') 154 | displayCombo.addItem('Scatter') 155 | displayCombo.setCurrentIndex(self.dockWidget.parentWidget.scatter) 156 | formatLayout.addWidget(displayLbl) 157 | formatLayout.addWidget(displayCombo) 158 | 159 | projectionLayout = QHBoxLayout() 160 | projectionLayout.setAlignment(Qt.AlignCenter) 161 | projectionLbl = QLabel('Projections on axes: ') 162 | xLbl = QLabel('X') 163 | projectionXBox = QCheckBox() 164 | if self.dockWidget.parentWidget.projection[0]: 165 | projectionXBox.setChecked(True) 166 | yLbl = QLabel('Y') 167 | projectionYBox = QCheckBox() 168 | if self.dockWidget.parentWidget.projection[1]: 169 | projectionYBox.setChecked(True) 170 | projectionLayout.addWidget(projectionLbl) 171 | projectionLayout.addWidget(projectionXBox) 172 | projectionLayout.addWidget(xLbl) 173 | projectionLayout.addWidget(projectionYBox) 174 | projectionLayout.addWidget(yLbl) 175 | 176 | dataDisplayLayout.addLayout(formatLayout) 177 | dataDisplayLayout.addLayout(projectionLayout) 178 | dataDisplayGroup.setLayout(dataDisplayLayout) 179 | 180 | saveLayout = QHBoxLayout() 181 | saveButton = QPushButton('Save') 182 | saveButton.setMaximumWidth(60) 183 | saveButton.clicked.connect(lambda: self.plots3D_save(displayCombo.currentIndex(), [projectionXBox.isChecked(),projectionYBox.isChecked()])) 184 | saveLayout.addStretch(1) 185 | saveLayout.addWidget(saveButton) 186 | saveLayout.addStretch(1) 187 | 188 | self.mainLayout.addWidget(dataDisplayGroup) 189 | self.mainLayout.addLayout(saveLayout) 190 | 191 | def plots3D_save(self, plotFormat, projection): 192 | 193 | self.dockWidget.parentWidget.scatter = plotFormat 194 | self.dockWidget.parentWidget.projection = projection 195 | imageNb = int(self.dockWidget.parentWidget.parentWindow.analysisWidget.controlWidget.imageNumber.text()) 196 | self.dockWidget.parentWidget.parentWindow.analysisWidget.resultAnalysis.graphRefresh(imageValue=imageNb-1) 197 | self.close() 198 | 199 | 200 | def strain2D(self): 201 | 202 | colorbarGroup = QGroupBox('Colorbar') 203 | 204 | colorbarLayout = QVBoxLayout() 205 | colorbarLayout.setAlignment(Qt.AlignCenter) 206 | 207 | colorbarLow = QHBoxLayout() 208 | colorbarLowLbl = QLabel('Lower limit:') 209 | colorbarLowEdit = QLineEdit() 210 | colorbarLowEdit.setAlignment(Qt.AlignCenter) 211 | colorbarLowEdit.setMaximumWidth(80) 212 | currentLowValue = self.dockWidget.matPlot.cbar.get_clim()[0] 213 | colorbarLowEdit.setText(str(currentLowValue)) 214 | colorbarLow.addWidget(colorbarLowLbl) 215 | colorbarLow.addWidget(colorbarLowEdit) 216 | 217 | colorbarHigh = QHBoxLayout() 218 | colorbarHighLbl = QLabel('Higher limit:') 219 | colorbarHighEdit = QLineEdit() 220 | colorbarHighEdit.setAlignment(Qt.AlignCenter) 221 | colorbarHighEdit.setMaximumWidth(80) 222 | currentHighValue = self.dockWidget.matPlot.cbar.get_clim()[1] 223 | colorbarHighEdit.setText(str(currentHighValue)) 224 | colorbarHigh.addWidget(colorbarHighLbl) 225 | colorbarHigh.addWidget(colorbarHighEdit) 226 | 227 | colorbarLayout.addLayout(colorbarLow) 228 | colorbarLayout.addLayout(colorbarHigh) 229 | colorbarGroup.setLayout(colorbarLayout) 230 | 231 | saveLayout = QHBoxLayout() 232 | saveButton = QPushButton('Save') 233 | saveButton.setMaximumWidth(60) 234 | saveButton.clicked.connect(lambda: self.strain2D_save(colorbarLowEdit.text(),colorbarHighEdit.text())) 235 | saveLayout.addStretch(1) 236 | saveLayout.addWidget(saveButton) 237 | saveLayout.addStretch(1) 238 | 239 | self.mainLayout.addWidget(colorbarGroup) 240 | self.mainLayout.addLayout(saveLayout) 241 | 242 | 243 | def strain2D_save(self, lowLimit, highLimit): 244 | 245 | if lowLimit != '' and highLimit != '': 246 | lowLimit = lowLimit.replace(',','.') 247 | highLimit = highLimit.replace(',','.') 248 | lowLimit = float(lowLimit) 249 | highLimit = float(highLimit) 250 | num_ticks = 11 251 | ticks = np.linspace(-0.1, 0.1, num_ticks) 252 | if lowLimit < highLimit: 253 | labels = np.linspace(lowLimit, highLimit, num_ticks) 254 | self.dockWidget.matPlot.cbar.set_clim(vmin=lowLimit,vmax=highLimit) 255 | else: 256 | labels = np.linspace(highLimit, lowLimit, num_ticks) 257 | self.dockWidget.matPlot.cbar.set_clim(vmin=highLimit,vmax=lowLimit) 258 | self.dockWidget.matPlot.cbar.set_ticks(ticks) 259 | self.dockWidget.matPlot.cbar.set_ticklabels(labels) 260 | self.dockWidget.parentWidget.parentWindow.devWindow.addInfo('New colorbar limits.') 261 | 262 | imageNb = int(self.dockWidget.parentWidget.parentWindow.analysisWidget.controlWidget.imageNumber.text()) 263 | self.dockWidget.parentWidget.parentWindow.analysisWidget.resultAnalysis.graphRefresh(imageValue=imageNb-1) 264 | self.close() 265 | 266 | 267 | def correlation2D(self): 268 | 269 | colorbarGroup = QGroupBox('Colorbar') 270 | 271 | colorbarLayout = QHBoxLayout() 272 | colorbarLayout.setAlignment(Qt.AlignCenter) 273 | colorbarLbl = QLabel('Lower limit:') 274 | colorbarEdit = QLineEdit() 275 | colorbarEdit.setAlignment(Qt.AlignCenter) 276 | colorbarEdit.setMaximumWidth(80) 277 | currentValue = self.dockWidget.matPlot.cbar.get_clim()[0] 278 | if currentValue < 0: 279 | currentValue = 0 280 | colorbarEdit.setText(str(currentValue)) 281 | colorbarLayout.addWidget(colorbarLbl) 282 | colorbarLayout.addWidget(colorbarEdit) 283 | colorbarGroup.setLayout(colorbarLayout) 284 | 285 | saveLayout = QHBoxLayout() 286 | saveButton = QPushButton('Save') 287 | saveButton.setMaximumWidth(60) 288 | saveButton.clicked.connect(lambda: self.correlation2D_save(colorbarEdit.text())) 289 | saveLayout.addStretch(1) 290 | saveLayout.addWidget(saveButton) 291 | saveLayout.addStretch(1) 292 | 293 | self.mainLayout.addWidget(colorbarGroup) 294 | self.mainLayout.addLayout(saveLayout) 295 | 296 | def correlation2D_save(self, lowLimit): 297 | 298 | if lowLimit != '': 299 | lowLimit = lowLimit.replace(',','.') 300 | lowLimit = float(lowLimit) 301 | if lowLimit < 0 or lowLimit >= 1: 302 | errorMessage = QMessageBox() 303 | errorMessage.setWindowTitle('Error') 304 | errorMessage.setText('Wrong colorbar limit. ('+str(lowLimit)+')') 305 | errorMessage.exec_() 306 | return 307 | num_ticks = 11 308 | labels = np.linspace(lowLimit, 1, num_ticks) 309 | ticks = np.linspace(-0.1, 0.1, num_ticks) 310 | self.dockWidget.matPlot.cbar.set_clim(vmin=lowLimit,vmax=1) 311 | self.dockWidget.matPlot.cbar.set_ticks(ticks) 312 | self.dockWidget.matPlot.cbar.set_ticklabels(labels) 313 | self.dockWidget.parentWidget.parentWindow.devWindow.addInfo('New colorbar limits.') 314 | 315 | imageNb = int(self.dockWidget.parentWidget.parentWindow.analysisWidget.controlWidget.imageNumber.text()) 316 | self.dockWidget.parentWidget.parentWindow.analysisWidget.resultAnalysis.graphRefresh(imageValue=imageNb-1) 317 | self.close() 318 | 319 | def trueStrain(self): 320 | 321 | averagingGroup = QGroupBox('Averaging') 322 | 323 | averagingLayout = QHBoxLayout() 324 | averagingLayout.setAlignment(Qt.AlignCenter) 325 | averagingLbl = QLabel('Average strain over') 326 | averagingValue = QSpinBox() 327 | totalActiveImages = len(np.atleast_1d(self.dockWidget.parentWidget.parentWindow.analysisWidget.activeImages)) 328 | averagingValue.setRange(1,totalActiveImages) 329 | averagingValue.setValue(self.dockWidget.parentWidget.averageImageNb) 330 | averagingLbl2 = QLabel('images.') 331 | averagingLayout.addWidget(averagingLbl) 332 | averagingLayout.addWidget(averagingValue) 333 | averagingLayout.addWidget(averagingLbl2) 334 | averagingGroup.setLayout(averagingLayout) 335 | 336 | saveLayout = QHBoxLayout() 337 | saveButton = QPushButton('Save') 338 | saveButton.setMaximumWidth(60) 339 | saveButton.clicked.connect(lambda: self.trueStrain_save(averagingValue.value())) 340 | saveLayout.addStretch(1) 341 | saveLayout.addWidget(saveButton) 342 | saveLayout.addStretch(1) 343 | 344 | self.mainLayout.addWidget(averagingGroup) 345 | self.mainLayout.addLayout(saveLayout) 346 | 347 | def trueStrain_save(self, value): 348 | 349 | self.dockWidget.parentWidget.averageImageNb = value 350 | imageNb = int(self.dockWidget.parentWidget.parentWindow.analysisWidget.controlWidget.imageNumber.text()) 351 | self.dockWidget.parentWidget.parentWindow.analysisWidget.resultAnalysis.graphRefresh(imageValue=imageNb-1) 352 | self.close() 353 | -------------------------------------------------------------------------------- /interface/filterWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 19/07/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the filter widget available in the grid creation tool 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, cv2 17 | from functions import DIC_Global, filterFunctions, getData 18 | 19 | class filterCreationWidget(QWidget): # contains the main filter informations and allow user to apply filter to the set of images 20 | 21 | def __init__(self, parent): 22 | 23 | super(filterCreationWidget, self).__init__() 24 | 25 | self.parent = parent 26 | 27 | verticalLayout = QVBoxLayout() 28 | verticalLayout.setAlignment(Qt.AlignCenter) 29 | verticalLayout.setContentsMargins(0,0,0,0) 30 | 31 | self.filterListLbl = QLabel('Filters') 32 | 33 | self.availableFilters = QListWidget() 34 | 35 | #filterlist 36 | self.filterList = [['Zoom','Width','Height','Top-Left Coord.',200,100,'0,0'],['Blur','Kernel Width','Kernel Height','',5,5,0],['Gaussian','Kernel Width','Kernel Height','Standard Dev.',9,9,'0,0'],['Brightness','Phi','Theta','Degree',1,1,'2'],['Darkness','Phi','Theta','Degree',1,1,'2'],['Contrast','Phi','Theta','Degree',1,1,'2']] 37 | #end filterlist 38 | for element in self.filterList: 39 | currentFilter = QListWidgetItem(element[0]) 40 | self.availableFilters.addItem(currentFilter) 41 | 42 | self.availableFilters.itemSelectionChanged.connect(self.itemSelected) 43 | 44 | filterParameterLayout = QHBoxLayout() 45 | filterParameterLayout.setAlignment(Qt.AlignCenter) 46 | filterParameterLayout.setContentsMargins(0,0,0,0) 47 | 48 | filterParameterLblLayout = QVBoxLayout() 49 | filterParameterLblLayout.setContentsMargins(0,0,0,0) 50 | self.parameterLbls = [QLabel('-'),QLabel('-'),QLabel('-')] 51 | for label in self.parameterLbls: 52 | filterParameterLblLayout.addWidget(label) 53 | 54 | filterParameterValueLayout = QVBoxLayout() 55 | filterParameterValueLayout.setContentsMargins(0,0,0,0) 56 | self.parameterValues = [QSpinBox(), QSpinBox(), QLineEdit()] 57 | for values in self.parameterValues: 58 | filterParameterValueLayout.addWidget(values) 59 | filterParameterLayout.addLayout(filterParameterLblLayout) 60 | filterParameterLayout.addLayout(filterParameterValueLayout) 61 | 62 | saveButtonLayout = QHBoxLayout() 63 | saveButtonLayout.setContentsMargins(0,0,0,0) 64 | self.previewButton = QToolButton() 65 | self.previewButton.setText('Preview') 66 | self.previewButton.setDisabled(True) 67 | self.previewButton.mousePressEvent = lambda x: self.parent.plotImage(filterPreview=[self.availableFilters.currentItem().text(), self.parameterValues[0].value(), self.parameterValues[1].value(), self.parameterValues[2].text()]) 68 | self.previewButton.mouseReleaseEvent = lambda x: self.parent.plotImage() 69 | self.saveButton = QToolButton() 70 | self.saveButton.setText('Apply Filter') 71 | self.saveButton.setDisabled(True) 72 | self.saveButton.clicked.connect(self.addFilterToApply) 73 | saveButtonLayout.addStretch(1) 74 | saveButtonLayout.addWidget(self.previewButton) 75 | saveButtonLayout.addWidget(self.saveButton) 76 | saveButtonLayout.addStretch(1) 77 | 78 | self.appliedFiltersLbl = QLabel('Applied Filter(s)') 79 | 80 | self.appliedFilters = QListWidget() 81 | self.appliedFiltersList = [] 82 | 83 | deleteButtonLayout = QHBoxLayout() 84 | deleteButtonLayout.setContentsMargins(0,0,0,0) 85 | self.deleteButton = QToolButton() 86 | self.deleteButton.setText('Delete Selection') 87 | self.deleteButton.setContentsMargins(0,0,0,0) 88 | self.deleteButton.setDisabled(True) 89 | self.deleteButton.clicked.connect(self.deleteAppliedFilter) 90 | deleteButtonLayout.addStretch(1) 91 | deleteButtonLayout.addWidget(self.deleteButton) 92 | deleteButtonLayout.addStretch(1) 93 | 94 | self.histoPlot = DIC_Global.matplotlibWidget() 95 | 96 | verticalLayout.addWidget(self.filterListLbl) 97 | verticalLayout.addWidget(self.availableFilters) 98 | verticalLayout.addLayout(filterParameterLayout) 99 | verticalLayout.addLayout(saveButtonLayout) 100 | verticalLayout.addWidget(self.appliedFiltersLbl) 101 | verticalLayout.addWidget(self.appliedFilters) 102 | verticalLayout.addLayout(deleteButtonLayout) 103 | verticalLayout.addWidget(self.histoPlot) 104 | 105 | self.setLayout(verticalLayout) 106 | 107 | def resizeCall(self): 108 | 109 | maxWidth = self.parent.parentWindow.width() 110 | maxHeight = self.parent.parentWindow.height() 111 | self.setContentsMargins(0.01*maxWidth,0,0.01*maxWidth,0.05*maxHeight) 112 | self.setFixedHeight(0.75*maxHeight) 113 | self.setFixedWidth(0.25*maxWidth) 114 | self.filterListLbl.setFixedHeight(.03*maxHeight) 115 | self.availableFilters.setFixedHeight(.1*maxHeight) 116 | self.appliedFiltersLbl.setFixedHeight(.03*maxHeight) 117 | self.appliedFilters.setFixedHeight(.1*maxHeight) 118 | self.histoPlot.setFixedHeight(.15*maxHeight) 119 | 120 | def itemSelected(self): 121 | 122 | self.saveButton.setEnabled(True) 123 | self.previewButton.setEnabled(True) 124 | for element in self.filterList: 125 | if element[0] == self.availableFilters.currentItem().text(): 126 | for parameter in range(3): 127 | parameterName = element[1+parameter] 128 | if parameterName != '': 129 | self.parameterLbls[parameter].setText(parameterName) 130 | self.parameterValues[parameter].setEnabled(True) 131 | try: 132 | if element[0] == 'Zoom': 133 | self.parameterValues[parameter].setRange(0,10000) 134 | self.parameterValues[parameter].setValue(element[parameter+4]) 135 | except: 136 | self.parameterValues[parameter].setText(str(element[parameter+4])) 137 | else: 138 | self.parameterValues[parameter].setDisabled(True) 139 | self.parameterLbls[parameter].setText('-') 140 | 141 | 142 | def addFilterToApply(self): 143 | 144 | filterName = self.availableFilters.currentItem().text() 145 | filterDisplayName = filterName 146 | changeZoom = 0 147 | if len(self.appliedFiltersList) > 0: 148 | for element in self.appliedFiltersList: 149 | if element[0] == filterName and filterName != 'Zoom': 150 | filterDisplayName = filterName+str(np.random.randint(100)) 151 | elif element[0] == filterName and filterName == 'Zoom': 152 | changeZoom = 1 153 | try: 154 | coordinates = str(int(self.parameterValues[2].text().split(',')[0])+int(element[4].split(',')[0]))+','+str(int(self.parameterValues[2].text().split(',')[1])+int(element[4].split(',')[1])) 155 | except: 156 | element[2:5] = [element[2]+self.parameterValues[0].value(), element[3]+self.parameterValues[1].value(), '0,0'] 157 | break 158 | element[2:5] = [element[2]+self.parameterValues[0].value(), element[3]+self.parameterValues[1].value(), coordinates] 159 | 160 | if changeZoom < 1: 161 | self.appliedFiltersList.append([filterDisplayName, filterName, self.parameterValues[0].value(), self.parameterValues[1].value(), self.parameterValues[2].text()]) 162 | 163 | self.refreshAppliedFilters() 164 | 165 | def deleteAppliedFilter(self): 166 | 167 | nb = 0 168 | if len(self.appliedFiltersList) > 0: 169 | for element in self.appliedFiltersList: 170 | if element[0] == self.appliedFilters.currentItem().text(): 171 | self.appliedFiltersList = np.delete(np.array(self.appliedFiltersList), nb, 0).tolist() 172 | break 173 | nb+=1 174 | self.refreshAppliedFilters() 175 | 176 | def refreshAppliedFilters(self): 177 | 178 | self.appliedFilters.clear() 179 | if len(self.appliedFiltersList) > 0: 180 | self.deleteButton.setEnabled(True) 181 | for filterToApply in self.appliedFiltersList: 182 | currentFilter = QListWidgetItem(filterToApply[0]) 183 | self.appliedFilters.addItem(currentFilter) 184 | else: 185 | self.deleteButton.setDisabled(True) 186 | self.parent.plotImage() 187 | -------------------------------------------------------------------------------- /interface/initApp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 16/10/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: Defines the start-up process and load the interface 12 | """ 13 | 14 | from PyQt4.QtGui import * 15 | from PyQt4.QtCore import * 16 | from interface import menubar, profile 17 | from functions import startOptions 18 | 19 | def initProfile(self, PROFILE_FILE, DEFAULT_PROFILE): 20 | 21 | self.profileData = profile.readProfile(PROFILE_FILE, default=DEFAULT_PROFILE) #read the profile list and create a default profile if the PROFILE_FILE is not found 22 | count = -1 23 | for profiles in self.profileData['Default']: #select the default profile 24 | count+=1 25 | if int(profiles) == 1: 26 | break 27 | self.profilePath = PROFILE_FILE #link to the profile configuration file 28 | self.defaultProfile = DEFAULT_PROFILE #used when adding a new profile in profile management 29 | self.currentProfile = count #ID of the current loaded profile 30 | 31 | def setUpInterface(self, currentProfile): 32 | 33 | FULL_SCREEN = int(self.profileData['FullScreen'][currentProfile]) 34 | WIDTH = int(self.profileData['Width'][currentProfile]) 35 | HEIGHT = int(self.profileData['Height'][currentProfile]) 36 | USER = self.profileData['User'][currentProfile] 37 | 38 | if FULL_SCREEN == 1: 39 | self.setWindowState(Qt.WindowMaximized) 40 | self.setWindowTitle('Digital Image Correlation ('+USER+')') 41 | self.setMinimumWidth(WIDTH) 42 | self.setMinimumHeight(HEIGHT) 43 | self.setContentsMargins(0,0,0,0) 44 | 45 | menubar.createMenu(self) 46 | menubar.menuDisabled(self) 47 | 48 | firstWidget = defaultWidget(self) 49 | self.setCentralWidget(firstWidget) 50 | firstWidget.printMessage('What do you want to do today?') 51 | 52 | self.statusBar().setMaximumHeight(15) 53 | self.statusBar().showMessage('Welcome in Python_DIC') 54 | self.show() 55 | 56 | class defaultWidget(QWidget): 57 | 58 | def __init__(self, parent): 59 | 60 | super(defaultWidget, self).__init__() 61 | 62 | #initiate Layout 63 | firstLayout = QVBoxLayout() 64 | firstLayout.setSpacing(30) 65 | firstLayout.setAlignment(Qt.AlignHCenter) 66 | 67 | #initiate Widget 68 | newAnalysis = QPushButton('New Analysis') 69 | newFont = newAnalysis.font() 70 | 71 | openAnalysis = QPushButton('Open Analysis') 72 | openFont = openAnalysis.font() 73 | 74 | self.messageLbl = QLabel() 75 | 76 | #configure Widgets 77 | newFont.setPointSize(12) 78 | newAnalysis.setFont(newFont) 79 | newAnalysis.setMinimumWidth(200) 80 | newAnalysis.setMinimumHeight(50) 81 | 82 | openFont.setPointSize(12) 83 | openAnalysis.setFont(openFont) 84 | openAnalysis.setMinimumWidth(200) 85 | openAnalysis.setMinimumHeight(50) 86 | 87 | self.messageLbl.setAlignment(Qt.AlignCenter) 88 | self.messageLbl.setMaximumHeight(30) 89 | self.messageLbl.setMinimumWidth(260) 90 | self.messageLbl.setAutoFillBackground(True) 91 | 92 | #Attribute functions 93 | newAnalysis.enterEvent = lambda x: self.smallEvent(newAnalysis, 'Correlate Images') 94 | newAnalysis.leaveEvent = lambda x: self.smallEvent(newAnalysis, 'New Analysis') 95 | newAnalysis.clicked.connect(lambda: startOptions.startNewAnalysis(parent)) 96 | 97 | openAnalysis.enterEvent = lambda x: self.smallEvent(openAnalysis, 'Analyse Results') 98 | openAnalysis.leaveEvent = lambda x: self.smallEvent(openAnalysis, 'Open Analysis') 99 | openAnalysis.clicked.connect(lambda: startOptions.openPrevious(parent)) 100 | 101 | #Applying Layout 102 | firstLayout.addWidget(newAnalysis) 103 | firstLayout.addWidget(openAnalysis) 104 | firstLayout.addWidget(self.messageLbl) 105 | 106 | self.setLayout(firstLayout) 107 | 108 | def printMessage(self, message, imp=0): 109 | 110 | p = self.messageLbl.palette() 111 | if imp != 0: 112 | p.setColor(self.messageLbl.backgroundRole(), Qt.red) 113 | else: 114 | p.setColor(self.messageLbl.backgroundRole(), Qt.transparent) 115 | self.messageLbl.setPalette(p) 116 | self.messageLbl.setText(message) 117 | 118 | def smallEvent(self, button, text): 119 | 120 | currentFont = button.font() 121 | button.setText(text) 122 | button.setFont(currentFont) 123 | -------------------------------------------------------------------------------- /interface/maskInstances.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 22/07/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the grid instances dialog 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np 17 | from interface import progressWidget 18 | from functions import masks, DIC_Global 19 | 20 | class maskGridInstanceDialog(QDialog): 21 | 22 | def __init__(self, parent): 23 | 24 | QDialog.__init__(self) 25 | 26 | self.setWindowTitle('Mask Grid Instance') 27 | self.setMinimumWidth(600) 28 | 29 | dialogLayout = QVBoxLayout() 30 | 31 | dialogLabel = QLabel('Chose grid instances you want to display on the visualisation panel.') 32 | dialogLabel.setAlignment(Qt.AlignCenter) 33 | 34 | self.plotArea = DIC_Global.matplotlibWidget() 35 | self.plotArea.setFocusPolicy(Qt.ClickFocus) 36 | self.plotArea.setFocus() 37 | 38 | infosLayout = QHBoxLayout() 39 | 40 | allButton = QPushButton('(De)Select *') 41 | allButton.setMaximumWidth(80) 42 | allButton.clicked.connect(self.allSelect) 43 | 44 | infoLbl = QLabel('Active Instances:') 45 | self.infoValue = QLabel('0') 46 | 47 | infosLayout.addStretch(1) 48 | infosLayout.addWidget(allButton) 49 | infosLayout.addStretch(1) 50 | infosLayout.addWidget(infoLbl) 51 | infosLayout.addWidget(self.infoValue) 52 | infosLayout.addStretch(1) 53 | 54 | 55 | buttonLayout = QHBoxLayout() 56 | self.dialogButton = QPushButton('Apply') 57 | self.dialogButton.setMaximumWidth(100) 58 | self.dialogButton.clicked.connect(lambda: self.showSelection(parent)) 59 | buttonLayout.addStretch(1) 60 | buttonLayout.addWidget(self.dialogButton) 61 | buttonLayout.addStretch(1) 62 | 63 | 64 | noteLbl = QLabel('No masks will be created. Deactivated instances will only be hidden temporaly.') 65 | noteLbl.setAlignment(Qt.AlignCenter) 66 | 67 | dialogLayout.addWidget(dialogLabel) 68 | dialogLayout.addWidget(self.plotArea) 69 | dialogLayout.addLayout(infosLayout) 70 | dialogLayout.addLayout(buttonLayout) 71 | dialogLayout.addWidget(noteLbl) 72 | 73 | self.setLayout(dialogLayout) 74 | 75 | 76 | def initiateInstances(self, activeInstances, grid_instances, data_x, data_y): 77 | 78 | instancesList = [] 79 | instanceNb = len(np.atleast_1d(grid_instances)) 80 | 81 | for instance in range(instanceNb): 82 | dataX = data_x[grid_instances[instance]] 83 | dataY = data_y[grid_instances[instance]] 84 | if len(np.atleast_1d(dataX)) > 0: 85 | centerX = np.mean(dataX) 86 | centerY = np.mean(dataY) 87 | isActive = instance in activeInstances 88 | instancesList.append([centerX,centerY, isActive, instance]) 89 | 90 | self.instancesList = instancesList 91 | self.axesLimit = [np.nanmin(data_x), np.nanmax(data_x), np.nanmin(data_y), np.nanmax(data_y)] 92 | 93 | self.plotInstances() 94 | 95 | def allSelect(self): 96 | 97 | nbInstances = len(self.instancesList) 98 | totalTrue = 0 99 | for instance in self.instancesList: 100 | if instance[2] == True: 101 | totalTrue += 1 102 | 103 | if totalTrue == nbInstances: 104 | for instance in range(nbInstances): 105 | self.instancesList[instance][2] = False 106 | else: 107 | for instance in range(nbInstances): 108 | self.instancesList[instance][2] = True 109 | 110 | self.plotInstances() 111 | 112 | def plotInstances(self): 113 | 114 | self.plotArea.matPlot.cla() 115 | self.plotArea.mpl_connect('button_press_event', self.on_press) 116 | 117 | 118 | activeInstances = 0 119 | nb_instances = len(self.instancesList) 120 | if nb_instances > 30: 121 | sizeLbl = 10 122 | else: 123 | sizeLbl = 25 124 | 125 | for instance in self.instancesList: 126 | if instance[2] == False: 127 | self.plotArea.matPlot.text(instance[0], instance[1], instance[3], color='red', size=.9*sizeLbl, ha="center", va="center", bbox=dict(boxstyle="circle", ec='r', fc='lightcoral', pad=.5)) 128 | else: 129 | self.plotArea.matPlot.text(instance[0], instance[1], instance[3], color='green', size=1.1*sizeLbl, ha="center", va="center", bbox=dict(boxstyle="circle", ec='g', fc='lightgreen', pad=.5)) 130 | activeInstances += 1 131 | 132 | self.infoValue.setText(str(activeInstances)) 133 | if activeInstances < 1: 134 | self.dialogButton.setDisabled(True) 135 | else: 136 | self.dialogButton.setEnabled(True) 137 | 138 | self.plotArea.matPlot.set_xlim([self.axesLimit[0], self.axesLimit[1]]) 139 | self.plotArea.matPlot.set_ylim([self.axesLimit[3], self.axesLimit[2]]) 140 | self.plotArea.draw_idle() 141 | 142 | 143 | def showSelection(self, parent): 144 | 145 | currentMask = parent.currentMask 146 | 147 | activeInstances = [] 148 | for instance in self.instancesList: 149 | if instance[2] == True: 150 | activeInstances.append(instance[3]) 151 | 152 | parent.activeInstances = activeInstances 153 | progressBar = progressWidget.progressBarDialog('Re-initializing the display..') 154 | masks.maskData(parent, currentMask, progressBar) 155 | 156 | self.close() 157 | 158 | 159 | def on_press(self, event): 160 | 161 | x0 = event.xdata 162 | y0 = event.ydata 163 | if x0 is None: 164 | return 165 | 166 | closerInstance = 0 167 | distance = 10000 168 | 169 | nb = 0 170 | for instance in self.instancesList: 171 | instanceDistance = ((x0-instance[0])**2+(y0-instance[1])**2)**(0.5) 172 | if instanceDistance < distance: 173 | distance = instanceDistance 174 | closerInstance = nb 175 | nb += 1 176 | 177 | self.instancesList[closerInstance][2] = not self.instancesList[closerInstance][2] 178 | self.plotInstances() 179 | 180 | 181 | 182 | 183 | 184 | def launchMaskGridDialog(self): 185 | 186 | self.analysisWidget.parentWindow.devWindow.addInfo('Mask Procedure Request : Mask Instances') 187 | 188 | referenceImage = self.analysisWidget.activeImages[0] 189 | activeMarkers = self.analysisWidget.activeMarkers[referenceImage] 190 | self.maskInstances = maskGridInstanceDialog(self.analysisWidget) 191 | self.maskInstances.initiateInstances(self.analysisWidget.activeInstances, self.analysisWidget.grid_instances, self.analysisWidget.data_x[:, referenceImage], self.analysisWidget.data_y[:, referenceImage]) 192 | 193 | self.maskInstances.exec_() 194 | -------------------------------------------------------------------------------- /interface/maskMarkers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 15/08/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages mask marker feature dialog 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, cv2, copy, matplotlib.patches as mpp 17 | from functions import filterFunctions, masks, DIC_Global 18 | from interface import progressWidget 19 | 20 | class deleteMarkersDialog(QDialog): 21 | 22 | def __init__(self, parent, currentImage): #create the layout of the window dialog 23 | 24 | QDialog.__init__(self) 25 | 26 | self.setWindowTitle('Mask markers') 27 | self.setMinimumWidth(500) 28 | dialogLayout = QVBoxLayout() 29 | 30 | #init_Variables 31 | self.parentWidget = parent 32 | self.fileDataPath = parent.parentWindow.fileDataPath 33 | self.filePath = parent.parentWindow.filePath 34 | self.filenamelist = parent.fileNameList 35 | self.filterList = parent.filterList 36 | self.currentMask = copy.deepcopy(parent.currentMask) 37 | self.activeImages = parent.activeImages 38 | self.activeMarkers = parent.activeMarkers 39 | self.activeInstances = parent.activeInstances 40 | self.gridInstances = parent.grid_instances 41 | self.data_x = parent.data_x 42 | self.data_y = parent.data_y 43 | self.disp_x = parent.disp_x 44 | self.disp_y = parent.disp_y 45 | self.graphDisplay = 99 #temporary toolbar fix 46 | 47 | dialogLabel = QLabel('Select markers you want to mask.
A first click initiate the selection, a second click confirms the selection.') 48 | dialogLabel.setAlignment(Qt.AlignHCenter) 49 | dialogLabel.setMinimumHeight(30) 50 | 51 | checkBoxOptions = QHBoxLayout() 52 | checkBoxOptions.setSpacing(10) 53 | self.baseMarkers = QCheckBox('Base Grid Markers') 54 | self.baseMarkers.setChecked(True) 55 | self.dispMarkers = QCheckBox('Displacement Arrows') 56 | #ComboBox to select instance 57 | instanceSelectLbl = QLabel('Display Instance(s):') 58 | self.instanceSelect = QComboBox(self) 59 | self.instanceSelect.addItem('All') 60 | for instance in self.activeInstances: 61 | self.instanceSelect.addItem(str(instance)) 62 | checkBoxOptions.addStretch(1) 63 | checkBoxOptions.addWidget(self.baseMarkers) 64 | checkBoxOptions.addWidget(self.dispMarkers) 65 | checkBoxOptions.addStretch(1) 66 | checkBoxOptions.addWidget(instanceSelectLbl) 67 | checkBoxOptions.addWidget(self.instanceSelect) 68 | checkBoxOptions.addStretch(1) 69 | 70 | #checkBox clicked 71 | self.baseMarkers.stateChanged.connect(lambda: self.selectMarkers()) 72 | self.dispMarkers.stateChanged.connect(lambda: self.selectMarkers()) 73 | #comboBox index changed 74 | self.instanceSelect.currentIndexChanged.connect(lambda: self.selectMarkers()) 75 | 76 | self.plotArea = DIC_Global.matplotlibWidget(self, parent=self, toolbar=1) #toolbar != None for horizontal toolbar 77 | self.plotArea.setMinimumHeight(self.plotArea.canvas.height()) 78 | self.plotArea.canvas.setFocusPolicy(Qt.ClickFocus) 79 | self.plotArea.canvas.setFocus() 80 | 81 | deleteButtonBox = QHBoxLayout() 82 | deleteButton = QPushButton('Delete Selection') 83 | deleteButton.setMinimumWidth(120) 84 | deleteButton.clicked.connect(self.maskSelection) 85 | self.imageSelectSpinBox = QSpinBox(self) 86 | self.imageSelectSpinBox.setRange(1, len(self.activeImages)) 87 | self.imageSelectSpinBox.setValue(currentImage) 88 | self.imageSelectSpinBox.valueChanged.connect(lambda: self.selectMarkers()) 89 | self.allImagesCheckBox = QCheckBox('Apply on all images.') 90 | self.allImagesCheckBox.setChecked(True) 91 | 92 | deleteButtonBox.addStretch(1) 93 | deleteButtonBox.addWidget(deleteButton) 94 | deleteButtonBox.addWidget(self.imageSelectSpinBox) 95 | deleteButtonBox.addWidget(self.allImagesCheckBox) 96 | deleteButtonBox.addStretch(1) 97 | 98 | #Dialog Window Layout 99 | dialogLayout.addWidget(dialogLabel) 100 | dialogLayout.addLayout(checkBoxOptions) 101 | dialogLayout.addWidget(self.plotArea) 102 | dialogLayout.addLayout(deleteButtonBox) 103 | 104 | self.setLayout(dialogLayout) 105 | 106 | #init Plot 107 | self.unselectedMarkersPlot = [] 108 | self.selectedMarkersPlot = [] 109 | self.arrowsPlot = [] 110 | self.selectMarkers() 111 | 112 | def selectMarkers(self): 113 | 114 | self.firstClic = 0 115 | self.plotArea.matPlot.cla() 116 | self.plotArea.canvas.mpl_connect('button_release_event', self.on_release) 117 | self.plotArea.canvas.mpl_connect('key_press_event', self.on_key) 118 | 119 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 120 | readImage = cv2.imread(self.filePath+'/'+self.filenamelist[value],0) 121 | readImage = filterFunctions.applyFilterListToImage(self.filterList, readImage) 122 | 123 | data_x_init = self.data_x[:, self.activeImages[0]] 124 | data_y_init = self.data_y[:, self.activeImages[0]] 125 | disp_x_init = self.disp_x[:, self.activeImages[value]] 126 | disp_y_init = self.disp_y[:, self.activeImages[value]] 127 | markerSelection = self.currentMask[:, value] 128 | 129 | validInstances = self.returnValidInstances() 130 | for instance in validInstances: 131 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 132 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]], self.activeMarkers[value], assume_unique=True) 133 | selectedMarkers = [marker for marker in instanceMarkers if markerSelection[marker] == 0] 134 | unSelectedMarkers = [marker for marker in instanceMarkers if marker not in selectedMarkers] 135 | try: 136 | self.unselectedMarkersPlot[instance].remove() 137 | except: 138 | pass 139 | try: 140 | self.selectedMarkersPlot[instance].remove() 141 | except: 142 | pass 143 | 144 | if self.baseMarkers.isChecked(): 145 | try: 146 | self.selectedMarkersPlot[instance] = self.plotArea.matPlot.plot(data_x_init[unSelectedMarkers], data_y_init[unSelectedMarkers], 'o', ms=5, color='green')[0] 147 | except: 148 | self.selectedMarkersPlot.append(self.plotArea.matPlot.plot(data_x_init[unSelectedMarkers], data_y_init[unSelectedMarkers], 'o', ms=3, color='green')[0]) 149 | 150 | if self.dispMarkers.isChecked(): 151 | nb = 0 152 | nbUnselected = len(np.atleast_1d(unSelectedMarkers)) 153 | try: 154 | self.selectedMarkersPlot[instance] = self.plotArea.matPlot.quiver(data_x_init[unSelectedMarkers], data_y_init[unSelectedMarkers], disp_x_init[unSelectedMarkers], disp_y_init[unSelectedMarkers], units = 'xy', color='blue') 155 | except: 156 | self.selectedMarkersPlot.append(self.plotArea.matPlot.quiver(data_x_init[unSelectedMarkers], data_y_init[unSelectedMarkers], disp_x_init[unSelectedMarkers], disp_y_init[unSelectedMarkers], units = 'xy', color='blue')) 157 | 158 | self.imagePlot = self.plotArea.matPlot.imshow(readImage, cmap='gray') 159 | self.plotArea.canvas.draw_idle() 160 | 161 | def returnValidInstances(self): 162 | 163 | nbInstances = len(np.atleast_1d(self.activeInstances)) 164 | validInstances = [] 165 | if self.instanceSelect.currentText() == "All": 166 | for instance in range(nbInstances): 167 | validInstances.append(instance) 168 | else: 169 | validInstances.append(self.instanceSelect.currentIndex()-1) 170 | return validInstances 171 | 172 | def on_motion(self,event): #allow a live drawing of the rectangle area 173 | 174 | x1 = event.xdata 175 | y1 = event.ydata 176 | if x1 is None: 177 | x1 = self.x2 178 | if y1 is None: 179 | y1 = self.y2 180 | self.x2 = x1 181 | self.y2 = y1 182 | width = x1 - self.x0 183 | height = y1 - self.y0 184 | self.rect.set_width(width) 185 | self.rect.set_height(height) 186 | #self.rect.set_xy((self.x0, self.y0)) 187 | self.rect.set_linestyle('dashed') 188 | self.plotArea.canvas.draw_idle() 189 | 190 | def on_release(self, event): 191 | if self.plotArea.toolbar._active is None: #if toolbar is not active 192 | if self.firstClic == 0: 193 | self.x0 = event.xdata 194 | self.y0 = event.ydata 195 | if self.x0 is None: 196 | return 197 | self.x2 = self.x0 #save coordinates in case the user goes out of the picture limits 198 | self.y2 = self.y0 199 | self.rect = mpp.Rectangle((self.x0, self.y0), 1, 1, facecolor='None', edgecolor='green', linewidth=2.5) 200 | self.plotArea.matPlot.add_patch(self.rect) 201 | self.motionRect = self.plotArea.canvas.mpl_connect('motion_notify_event', self.on_motion) 202 | self.rect.set_linestyle('dashed') 203 | self.firstClic = 1 204 | else: 205 | x1 = event.xdata 206 | y1 = event.ydata 207 | if self.x0 is None: 208 | return 209 | self.plotArea.canvas.mpl_disconnect(self.motionRect) 210 | if x1 is None: 211 | x1 = self.x2 212 | if y1 is None: 213 | y1 = self.y2 214 | self.selectRectangleMarkers(self.x0, self.y0, x1 - self.x0, y1 - self.y0) 215 | 216 | def on_key(self, event): 217 | print('you pressed', event.key, event.xdata, event.ydata) 218 | isValidEvent = False 219 | if event.key == 'd': 220 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 221 | markerSelection = self.currentMask[:, value] 222 | 223 | for instance in range(nbInstances): 224 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 225 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]], self.activeMarkers[value], assume_unique=True) 226 | for i in instanceMarkers: 227 | if markerSelection[i] == 0: 228 | self.currentMask[i,:] = 1 229 | isValidEvent = True 230 | if event.key == 'c' and self.firstClic == 1: 231 | self.plotArea.canvas.mpl_disconnect(self.motionRect) 232 | self.rect.remove() 233 | isValidEvent = True 234 | if event.key == 'r': 235 | self.plotArea.matPlot.cla() 236 | isValidEvent = True 237 | 238 | if isValidEvent is True: 239 | self.selectMarkers() 240 | 241 | def selectRectangleMarkers(self, x0, y0, width, height): #when an area is selected, select all the markers inside 242 | 243 | value = self.activeImages[self.imageSelectSpinBox.value()-1] 244 | if x0 is None: 245 | return 246 | markerSelection = self.currentMask[:, value] 247 | data_x_current = self.data_x[:, value] 248 | data_y_current = self.data_y[:, value] 249 | 250 | nbInstances = len(np.atleast_1d(self.activeInstances)) 251 | validInstances = self.returnValidInstances() 252 | for instance in validInstances: 253 | #instanceMarkers = [marker for marker in self.gridInstances[self.activeInstances[instance]] if marker in self.activeMarkers[value]] 254 | instanceMarkers = np.intersect1d(self.gridInstances[self.activeInstances[instance]], self.activeMarkers[value], assume_unique=True) 255 | for i in instanceMarkers: 256 | if data_x_current[i] > min(x0, x0+width) and data_x_current[i] < max(x0, x0+width) and data_y_current[i] > min(y0, y0+height) and data_y_current[i] < max(y0, y0+height): 257 | if markerSelection[i] == 1: 258 | if self.allImagesCheckBox.isChecked(): 259 | self.currentMask[i, :] = 0 260 | else: 261 | self.currentMask[i, value] = 0 262 | else: 263 | if self.allImagesCheckBox.isChecked(): 264 | self.currentMask[i, :] = 1 265 | else: 266 | self.currentMask[i, value] = 1 267 | 268 | self.selectMarkers() #refresh the canvas which selected markers 269 | 270 | def maskSelection(self): #when the selection is done and 'Delete' button clicked, remove all selected markers on all images 271 | 272 | shouldApplyMask = masks.generateMask(self.currentMask, self.fileDataPath) 273 | if shouldApplyMask is not None: 274 | self.parentWidget.parentWindow.devWindow.addInfo('Deleting selected markers..') 275 | progressBar = progressWidget.progressBarDialog('Saving masks..') 276 | masks.maskData(self.parentWidget, self.currentMask, progressBar, toRecalculate=shouldApplyMask) 277 | self.close() 278 | 279 | def launchMaskDialog(self, currentImage): #initialize the variable and execute the window dialog 280 | 281 | self.analysisWidget.parentWindow.devWindow.addInfo('Cleaning Procedure Request : Mask Markers.') 282 | deleteMarkers = deleteMarkersDialog(self.analysisWidget, currentImage) 283 | deleteMarkers.exec_() 284 | -------------------------------------------------------------------------------- /interface/menubar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 21/03/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the menubar and create menus and actions 12 | """ 13 | 14 | from PyQt4.QtGui import * 15 | from functions import startOptions, masks 16 | from interface import profile, dispVsPos, relativeNeighborsDialog, deleteImages, maskInstances, analysisInfos, maskMarkers, newNeighbors, newCoordinates 17 | 18 | def createMenuActions(self): 19 | 20 | #fileMenu Actions 21 | self.newAction = QAction('New Analysis', self) 22 | self.openAction = QAction('Open', self) 23 | self.openGrid = QAction('Open Grid', self) 24 | self.openFilter = QAction('Open Filter', self) 25 | self.openMask = QAction('Open Mask/Version', self) 26 | self.exitAction = QAction('Exit', self) 27 | 28 | #profile Actions 29 | self.listProfiles = [] 30 | for profiles in self.profileData['User']: 31 | self.listProfiles.append(QAction(profiles, self)) 32 | 33 | self.manageProfile = QAction('Manage Profiles', self) 34 | 35 | #maskMenu Actions 36 | self.maskInstances = QAction('Manage Grids', self) 37 | self.deleteImages = QAction('Mask Images', self) 38 | self.deleteMarkers = QAction('Mask Markers', self) 39 | self.dispPos = QAction('Disp. vs Position', self) 40 | self.relativeDisp = QAction('Relative Neighbors Disp.', self) 41 | 42 | #moreMenu 43 | self.analysisInfos = QAction('Analysis Infos', self) 44 | self.newNeighborsCalc = QAction('Re-calculate Neighbors', self) 45 | self.newCoordinatesCalc = QAction('New Coordinates', self) 46 | 47 | 48 | #Actions Parameters 49 | self.newAction.setShortcut('Ctrl+N') 50 | self.openAction.setShortcut('Ctrl+O') 51 | self.exitAction.setShortcut('Ctrl+Q') 52 | 53 | self.newAction.setStatusTip('Open an image to create a new analysis.') 54 | self.openAction.setStatusTip('Open a previous analysis.') 55 | self.exitAction.setStatusTip('Exit the application.') 56 | self.openGrid.setStatusTip('Load previously created grids from another analysis.') 57 | self.openFilter.setStatusTip('Load previously created filters from another analysis.') 58 | self.openMask.setStatusTip('Open another version of the current analysis or a mask from another analysis.') 59 | self.maskInstances.setStatusTip('Temporarily hide grid instances from your analysis.') 60 | self.deleteImages.setStatusTip('Select images to mask in the current analysis.') 61 | self.deleteMarkers.setStatusTip('Manually select markers to mask on images.') 62 | self.dispPos.setStatusTip('Plot displacement versus position and mask selected markers.') 63 | self.relativeDisp.setStatusTip('Plot relative neighbors displacement over all the images and mask jumpers.') 64 | self.analysisInfos.setStatusTip('Get detailed informations on the current analysis.') 65 | self.newNeighborsCalc.setStatusTip = ('Update the current markers neighbors list.') 66 | self.newCoordinatesCalc.setStatusTip = ('Re-calculate the mapped plot coordinates.') 67 | 68 | #Actions Triggers 69 | self.newAction.triggered.connect(lambda: startOptions.startNewAnalysis(self)) 70 | self.openAction.triggered.connect(lambda: startOptions.openPrevious(self)) 71 | self.exitAction.triggered.connect(self.close) 72 | self.openGrid.triggered.connect(lambda: self.centralWidget().openGrid()) 73 | self.openFilter.triggered.connect(lambda: self.centralWidget().openFilter()) 74 | self.openMask.triggered.connect(lambda: masks.openMaskRequest(self)) 75 | self.maskInstances.triggered.connect(lambda: maskInstances.launchMaskGridDialog(self)) 76 | self.deleteImages.triggered.connect(lambda: deleteImages.launchDeleteImageDialog(self)) 77 | self.deleteMarkers.triggered.connect(lambda: maskMarkers.launchMaskDialog(self, int(self.analysisWidget.controlWidget.imageNumber.text()))) 78 | self.dispPos.triggered.connect(lambda: dispVsPos.launchDVPDialog(self, int(self.analysisWidget.controlWidget.imageNumber.text()))) 79 | self.relativeDisp.triggered.connect(lambda: relativeNeighborsDialog.launchRNDialog(self)) 80 | self.analysisInfos.triggered.connect(lambda: analysisInfos.launchDialog(self)) 81 | self.newNeighborsCalc.triggered.connect(lambda: newNeighbors.launchNeighborsDialog(self)) 82 | self.newCoordinatesCalc.triggered.connect(lambda: newCoordinates.launchCoordinatesDialog(self)) 83 | 84 | self.manageProfile.triggered.connect(lambda: profile.manageProfile(self)) 85 | 86 | 87 | def createMenu(self): 88 | 89 | self.menubar = self.menuBar() 90 | #create Menus 91 | fileMenu = self.menubar.addMenu('File') 92 | masksMenu = self.menubar.addMenu('Masks') 93 | moreMenu = self.menubar.addMenu('More') 94 | 95 | createMenuActions(self) #generate Actions with function 96 | 97 | #add created Actions to Menus 98 | fileMenu.addAction(self.newAction) 99 | fileMenu.addAction(self.openAction) 100 | fileMenu.addSeparator() 101 | fileMenu.addAction(self.openGrid) 102 | fileMenu.addAction(self.openFilter) 103 | fileMenu.addAction(self.openMask) 104 | fileMenu.addSeparator() 105 | profileMenu = fileMenu.addMenu('Profile') 106 | 107 | profileActionGroup = QActionGroup(self) 108 | for profiles in self.listProfiles: #add profile actions to profile menu 109 | profiles.setCheckable(True) 110 | profileActionGroup.addAction(profiles) 111 | profileMenu.addAction(profiles) 112 | if self.profileData['User'][self.currentProfile] == profiles.text(): 113 | profiles.setChecked(True) 114 | profileMenu.addSeparator() 115 | profileMenu.addAction(self.manageProfile) 116 | 117 | profileActionGroup.triggered.connect(lambda: profile.changeProfile(self, profileActionGroup.checkedAction().text())) 118 | fileMenu.addSeparator() 119 | fileMenu.addAction(self.exitAction) 120 | 121 | masksMenu.addAction(self.maskInstances) 122 | masksMenu.addSeparator() 123 | masksMenu.addAction(self.deleteImages) 124 | masksMenu.addSeparator() 125 | masksMenu.addAction(self.deleteMarkers) 126 | masksMenu.addAction(self.dispPos) 127 | masksMenu.addAction(self.relativeDisp) 128 | 129 | moreMenu.addAction(self.analysisInfos) 130 | moreMenu.addAction(self.newNeighborsCalc) 131 | moreMenu.addAction(self.newCoordinatesCalc) 132 | 133 | #disabled actions 134 | menuDisabled(self) 135 | 136 | def menuDisabled(parent): 137 | 138 | parent.deleteMarkers.setDisabled(True) 139 | parent.dispPos.setDisabled(True) 140 | parent.openMask.setDisabled(True) 141 | parent.relativeDisp.setDisabled(True) 142 | parent.openGrid.setDisabled(True) 143 | parent.openFilter.setDisabled(True) 144 | parent.deleteImages.setDisabled(True) 145 | parent.maskInstances.setDisabled(True) 146 | parent.analysisInfos.setDisabled(True) 147 | parent.newNeighborsCalc.setDisabled(True) 148 | parent.newCoordinatesCalc.setDisabled(True) 149 | 150 | def menuEnabled(parent): #menu enabled when analysis is open 151 | 152 | parent.deleteMarkers.setEnabled(True) 153 | parent.dispPos.setEnabled(True) 154 | parent.openMask.setEnabled(True) 155 | parent.relativeDisp.setEnabled(True) 156 | parent.deleteImages.setEnabled(True) 157 | parent.maskInstances.setEnabled(True) 158 | parent.analysisInfos.setEnabled(True) 159 | parent.newNeighborsCalc.setEnabled(True) 160 | parent.newCoordinatesCalc.setEnabled(True) 161 | 162 | def menuCreateGridEnabled(parent): #menu enabled when creating a grid 163 | 164 | parent.openGrid.setEnabled(True) 165 | parent.openFilter.setEnabled(True) 166 | -------------------------------------------------------------------------------- /interface/newCoordinates.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on 18/11/2016 3 | 4 | @author: Charlie Bourigault 5 | @contact: bourigault.charlie@gmail.com 6 | 7 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 8 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 9 | 10 | Current File: Allows the user to re-calculate the current 2D mapped coordinates 11 | """ 12 | 13 | from PyQt4.QtCore import * 14 | from PyQt4.QtGui import * 15 | import numpy as np 16 | from interface import progressWidget 17 | from functions import initData, masks, DIC_Global 18 | 19 | class newCoordinatesDialog(QDialog): 20 | 21 | def __init__(self, parent): 22 | 23 | QDialog.__init__(self) 24 | 25 | self.setWindowTitle('Re-Calculate Coordinates') 26 | self.setMinimumWidth(600) 27 | 28 | dialogLayout = QVBoxLayout() 29 | 30 | dialogLabel = QLabel('Which coordinates do you want to re-calculate?') 31 | dialogLabel.setAlignment(Qt.AlignCenter) 32 | 33 | checkBoxLayout = QHBoxLayout() 34 | self.corrBox = QCheckBox('Correlation 2D') 35 | self.xStrainBox = QCheckBox('2D Local Strain (X)') 36 | self.yStrainBox = QCheckBox('2D Local Strain (Y)') 37 | self.corrBox.setChecked(True) 38 | self.xStrainBox.setChecked(True) 39 | self.yStrainBox.setChecked(True) 40 | checkBoxLayout.addWidget(self.corrBox) 41 | checkBoxLayout.addWidget(self.xStrainBox) 42 | checkBoxLayout.addWidget(self.yStrainBox) 43 | 44 | dialogButtonLayout = QHBoxLayout() 45 | self.dialogButton = QPushButton('Calculate') 46 | self.dialogButton.setMaximumWidth(100) 47 | self.dialogButton.clicked.connect(lambda: self.startCalculation(parent)) 48 | dialogButtonLayout.addStretch(1) 49 | dialogButtonLayout.addWidget(self.dialogButton) 50 | dialogButtonLayout.addStretch(1) 51 | 52 | dialogLayout.addWidget(dialogLabel) 53 | dialogLayout.addLayout(checkBoxLayout) 54 | dialogLayout.addLayout(dialogButtonLayout) 55 | 56 | self.setLayout(dialogLayout) 57 | 58 | def startCalculation(self, parent): 59 | 60 | recalculateCoordinates = [self.corrBox.isChecked(), self.xStrainBox.isChecked(), self.yStrainBox.isChecked()] 61 | progressBar = progressWidget.progressBarDialog('Starting calculation...') 62 | self.close() 63 | calculatingThread = DIC_Global.createThread(parent.parentWindow, [parent, progressBar, parent.currentMask, recalculateCoordinates], initData.initPlottedData, signal=1) 64 | calculatingThread.signal.threadSignal.connect(lambda: masks.newMasksCalculated(parent, progressBar)) 65 | calculatingThread.start() 66 | 67 | def launchCoordinatesDialog(self): 68 | 69 | self.calcCoordinates = newCoordinatesDialog(self.analysisWidget) 70 | self.calcCoordinates.exec_() 71 | -------------------------------------------------------------------------------- /interface/newNeighbors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 08/11/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: Allows the user to re-calculate the current grid neighbors 12 | """ 13 | 14 | from PyQt4.QtCore import * 15 | from PyQt4.QtGui import * 16 | import numpy as np, time 17 | from interface import progressWidget 18 | from functions import initData 19 | 20 | class newNeighborsDialog(QDialog): 21 | 22 | def __init__(self, parent): 23 | 24 | QDialog.__init__(self) 25 | 26 | self.setWindowTitle('Re-Calculate Neighbors') 27 | self.setMinimumWidth(600) 28 | 29 | nbNeighbors = len(parent.neighbors[0]) 30 | listMarkers = np.linspace(0, parent.nb_marker, parent.nb_marker, endpoint=False) 31 | nbMarkers = len(np.atleast_1d(listMarkers)) 32 | 33 | dialogLayout = QVBoxLayout() 34 | 35 | dialogLabel = QLabel('Chose a minimum amount of neighbors for each marker.') 36 | dialogLabel.setAlignment(Qt.AlignCenter) 37 | 38 | spinBoxLayout = QHBoxLayout() 39 | self.nbNeighbors = QSpinBox() 40 | self.nbNeighbors.setMaximumWidth(200) 41 | self.nbNeighbors.setValue(nbNeighbors) 42 | self.nbNeighbors.setRange(1, nbMarkers) 43 | spinBoxLayout.addStretch(1) 44 | spinBoxLayout.addWidget(self.nbNeighbors) 45 | spinBoxLayout.addStretch(1) 46 | 47 | dialogButtonLayout = QHBoxLayout() 48 | self.dialogButton = QPushButton('Start') 49 | self.dialogButton.setMaximumWidth(200) 50 | self.dialogButton.clicked.connect(lambda: self.startCalculation(parent, listMarkers)) 51 | dialogButtonLayout.addStretch(1) 52 | dialogButtonLayout.addWidget(self.dialogButton) 53 | dialogButtonLayout.addStretch(1) 54 | 55 | dialogProgressLayout = QHBoxLayout() 56 | self.dialogProgress = progressWidget.progressBarWidget(minimumHeight=20, maximumHeight=30, minimumWidth=200, maximumWidth=200) 57 | dialogProgressLayout.addStretch(1) 58 | dialogProgressLayout.addWidget(self.dialogProgress) 59 | dialogProgressLayout.addStretch(1) 60 | 61 | dialogLayout.addWidget(dialogLabel) 62 | dialogLayout.addLayout(spinBoxLayout) 63 | dialogLayout.addLayout(dialogButtonLayout) 64 | dialogLayout.addLayout(dialogProgressLayout) 65 | 66 | self.setLayout(dialogLayout) 67 | 68 | def startCalculation(self, parent, listMarkers): 69 | 70 | self.dialogButton.setText('Calculating..') 71 | firstImage = parent.activeImages[0] 72 | data_x_init = parent.data_x[:,firstImage] 73 | data_y_init = parent.data_y[:,firstImage] 74 | minNeighbors = int(self.nbNeighbors.value()) 75 | neighbors = initData.calculateNeighbors(listMarkers, data_x_init, data_y_init, minNeighbors, parent.parentWindow.fileDataPath, progressBar=self.dialogProgress) 76 | parent.neighbors = neighbors 77 | self.dialogButton.setText('Done. Closing..') 78 | self.dialogButton.setDisabled(True) 79 | QTimer.singleShot(1500, self.close) 80 | 81 | 82 | def launchNeighborsDialog(self): 83 | 84 | self.calcNeighbors = newNeighborsDialog(self.analysisWidget) 85 | self.calcNeighbors.exec_() 86 | -------------------------------------------------------------------------------- /interface/profile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 31/05/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the profile dialog and profile functions 12 | """ 13 | from PyQt4.QtGui import * 14 | from PyQt4.QtCore import * 15 | import numpy as np, sys, os 16 | import DIC 17 | 18 | def readProfile(filePath, default=None): 19 | 20 | try: 21 | labels = np.genfromtxt(filePath, delimiter='|', dtype=str, autostrip=True) #read the first column / labels 22 | except: 23 | if default is not None: 24 | np.savetxt(filePath, default, delimiter='|', fmt="%s") #create a profile file if absent 25 | labels = np.genfromtxt(filePath, delimiter='|', dtype=str, autostrip=True) #read the first column / labels 26 | else: 27 | return None 28 | 29 | data = {label: row for label, row in zip(labels[:,0], labels[:,1:])} 30 | 31 | return data #to read data : data['User'], data['CorrSize'] ... 32 | 33 | def manageProfile(parent): 34 | 35 | startDialog = manageAllProfiles(parent) 36 | startDialog.exec_() 37 | 38 | def changeProfile(parent, user): 39 | 40 | changeP = QMessageBox() 41 | changeP.setWindowTitle("Warning") 42 | changeP.setText("You will have to restart the program to load the following profile : "+user) 43 | changeP.setInformativeText("Do you want to continue?") 44 | changeP.setStandardButtons(QMessageBox.Yes | QMessageBox.No) 45 | ret = changeP.exec_() 46 | if ret == QMessageBox.No: 47 | return 48 | else: 49 | saveNew = setDefaultProfile(parent, user) 50 | if saveNew == 'OK': 51 | quit_program() 52 | 53 | 54 | def setDefaultProfile(parent, user): 55 | 56 | parameters = np.genfromtxt(parent.profilePath, delimiter='|', dtype=str, autostrip=True) #read the first column / labels 57 | data = {label: row for label, row in zip(parameters[:,0], parameters[:,1:])} 58 | countProfile = -1 59 | for users in data['User']: 60 | countProfile += 1 61 | if users == user: 62 | break 63 | countDefault = -1 64 | for default in data['Default']: 65 | countDefault += 1 66 | if countDefault == countProfile: 67 | data['Default'][countDefault] = str(1) 68 | else: 69 | data['Default'][countDefault] = str(0) 70 | keys = [] 71 | values = [] 72 | for key, value in data.items(): 73 | keys.append(key) 74 | values.append(value) 75 | newParameters = np.column_stack([np.array(keys), np.array(values)]) 76 | np.savetxt(parent.profilePath, newParameters, delimiter='|', fmt="%s") 77 | return 'OK' 78 | 79 | 80 | def quit_program(): #close and restart the window 81 | 82 | # !!! SAVE DATA BEFORE CALLING THE FUNCTION !!! # 83 | #python = sys.executable 84 | #os.execl(python, python, *sys.argv) 85 | sys.exit() 86 | 87 | 88 | class manageAllProfiles(QDialog): 89 | 90 | def __init__(self, parent): 91 | 92 | super(manageAllProfiles, self).__init__() 93 | self.setWindowTitle('Profile Management') 94 | #self.setMinimumSize(400,500) 95 | 96 | self.parent = parent 97 | self.currentProfile = parent.currentProfile 98 | self.profileData = parent.profileData 99 | self.defaultProfile = parent.defaultProfile 100 | self.profilePath = parent.profilePath 101 | 102 | dialogLayout = QVBoxLayout() 103 | 104 | profileListLayout = QHBoxLayout() 105 | profileListLayout.setAlignment(Qt.AlignCenter) 106 | profileListLbl = QLabel('Profile Settings: ') 107 | self.profileList = QComboBox() 108 | for profile in self.profileData['User']: 109 | self.profileList.addItem(profile) 110 | self.profileList.setCurrentIndex(self.currentProfile) 111 | self.profileList.currentIndexChanged.connect(lambda: self.initSettings()) 112 | newProfile = QPushButton('New') 113 | newProfile.setMaximumWidth(50) 114 | newProfile.clicked.connect(self.newProfile) 115 | profileListLayout.addWidget(profileListLbl) 116 | profileListLayout.addWidget(self.profileList) 117 | profileListLayout.addWidget(newProfile) 118 | 119 | windowBox = QGroupBox('Window Settings') 120 | windowLayout = QVBoxLayout() 121 | fullScreenLayout = QHBoxLayout() 122 | fullScreenLayout.setAlignment(Qt.AlignCenter) 123 | fullScreenLbl = QLabel('Display in FullScreen:') 124 | self.fullScreenBox = QCheckBox() 125 | self.fullScreenBox.stateChanged.connect(self.fullScreenBox_Changed) 126 | fullScreenLayout.addWidget(fullScreenLbl) 127 | fullScreenLayout.addWidget(self.fullScreenBox) 128 | self.sizeLbl = QLabel('Dimensions') 129 | self.sizeWidget = QWidget() 130 | sizeLayout = QHBoxLayout() 131 | sizeLayout.setAlignment(Qt.AlignCenter) 132 | widthLbl = QLabel('Width:') 133 | self.widthEdit = QLineEdit() 134 | self.widthEdit.setMaximumWidth(40) 135 | heightLbl = QLabel('Height:') 136 | self.heightEdit = QLineEdit() 137 | self.heightEdit.setMaximumWidth(50) 138 | sizeLayout.addWidget(widthLbl) 139 | sizeLayout.addWidget(self.widthEdit) 140 | sizeLayout.addWidget(heightLbl) 141 | sizeLayout.addWidget(self.heightEdit) 142 | self.sizeWidget.setLayout(sizeLayout) 143 | windowLayout.addLayout(fullScreenLayout) 144 | windowLayout.addWidget(self.sizeLbl) 145 | windowLayout.addWidget(self.sizeWidget) 146 | windowBox.setLayout(windowLayout) 147 | 148 | newBox = QGroupBox('New Analysis') 149 | corrsizeLayout = QHBoxLayout() 150 | corrsizeLayout.setAlignment(Qt.AlignCenter) 151 | corrsizeLbl = QLabel('Default CorrSize:') 152 | self.corrsizeValue = QSpinBox() 153 | self.corrsizeValue.setRange(5,100) 154 | corrsizeLayout.addWidget(corrsizeLbl) 155 | corrsizeLayout.addWidget(self.corrsizeValue) 156 | newBox.setLayout(corrsizeLayout) 157 | 158 | profileBox = QGroupBox('Profile Info') 159 | profileInfoLayout = QVBoxLayout() 160 | 161 | profileLayout = QHBoxLayout() 162 | self.profileDelete = QPushButton('Delete this profile.') 163 | self.profileDelete.setMaximumWidth(150) 164 | self.profileDelete.clicked.connect(self.deleteProfile) 165 | profileLayout.addStretch(1) 166 | profileLayout.addWidget(self.profileDelete) 167 | profileLayout.addStretch(1) 168 | 169 | processesLayout = QHBoxLayout() 170 | processesLayout.setAlignment(Qt.AlignCenter) 171 | processesLbl = QLabel('Use ') 172 | self.processesValue = QSpinBox() 173 | self.processesValue.setRange(1,100) 174 | processesLbl2 = QLabel('processes for calculations.') 175 | processesLayout.addWidget(processesLbl) 176 | processesLayout.addWidget(self.processesValue) 177 | processesLayout.addWidget(processesLbl2) 178 | 179 | profileInfoLayout.addLayout(processesLayout) 180 | profileInfoLayout.addLayout(profileLayout) 181 | profileBox.setLayout(profileInfoLayout) 182 | 183 | saveLayout = QHBoxLayout() 184 | saveButton = QPushButton('Save Changes') 185 | saveButton.setMaximumWidth(130) 186 | saveButton.clicked.connect(self.saveProfile) 187 | saveLayout.addStretch(1) 188 | saveLayout.addWidget(saveButton) 189 | saveLayout.addStretch(1) 190 | 191 | dialogLayout.addLayout(profileListLayout) 192 | dialogLayout.addWidget(windowBox) 193 | dialogLayout.addWidget(newBox) 194 | dialogLayout.addWidget(profileBox) 195 | dialogLayout.addLayout(saveLayout) 196 | self.setLayout(dialogLayout) 197 | self.initSettings(firstInit = 1) 198 | 199 | 200 | def initSettings(self, firstInit=0): 201 | 202 | if firstInit == 0: 203 | checkSave = self.saveProfileTemp() 204 | if checkSave is None: 205 | return 206 | self.currentIndex = self.profileList.currentIndex() 207 | else: 208 | self.currentIndex = self.currentProfile 209 | 210 | #window Settings 211 | fullScreen = int(self.profileData['FullScreen'][self.currentIndex]) 212 | width = int(self.profileData['Width'][self.currentIndex]) 213 | height = int(self.profileData['Height'][self.currentIndex]) 214 | self.widthEdit.setText(str(width)) 215 | self.heightEdit.setText(str(height)) 216 | if fullScreen: 217 | self.fullScreenBox.setChecked(True) 218 | self.sizeLbl.setEnabled(False) 219 | self.sizeWidget.setEnabled(False) 220 | else: 221 | self.fullScreenBox.setChecked(False) 222 | self.sizeLbl.setEnabled(True) 223 | self.sizeWidget.setEnabled(True) 224 | 225 | #new Analysis Settings 226 | corrSizeValue = int(self.profileData['CorrSize'][self.currentIndex]) 227 | self.corrsizeValue.setValue(corrSizeValue) 228 | 229 | #profile Settings 230 | if self.currentIndex == self.currentProfile: 231 | self.profileDelete.setDisabled(True) 232 | else: 233 | self.profileDelete.setEnabled(True) 234 | processesValue = int(self.profileData['nbProcesses'][self.currentIndex]) 235 | self.processesValue.setValue(processesValue) 236 | 237 | def newProfile(self): 238 | 239 | text, ok = QInputDialog.getText(self, 'New Profile', 'Enter desired profile name:') 240 | #defining validator 241 | validatorRx = QRegExp("\\w+") 242 | validator = QRegExpValidator(validatorRx, self) 243 | #check user answer 244 | if ok and text != '': 245 | checkUsername = validator.validate(text, 0) 246 | if checkUsername[0] > 1: 247 | newProfileIndex = len(self.profileData['User']) 248 | for key in self.profileData: 249 | profileData = self.profileData[key].tolist() 250 | for element in self.defaultProfile: 251 | if element[0] == key: 252 | if key == 'User': 253 | profileData.append(text) 254 | else: 255 | profileData.append(element[1]) 256 | break 257 | self.profileData[key] = np.array(profileData) 258 | self.profileList.addItem(self.profileData['User'][newProfileIndex]) 259 | self.profileList.setCurrentIndex(newProfileIndex) 260 | else: 261 | msgBox = QMessageBox() 262 | msgBox.setText("Please use only alpha-numeric characters.") 263 | msgBox.exec_() 264 | 265 | def deleteProfile(self): 266 | 267 | nbProfiles = len(self.profileData['User']) 268 | for profile in range(nbProfiles): 269 | if self.profileData['User'][profile] == self.profileList.itemText(self.currentIndex): 270 | for key in self.profileData: 271 | profileData = self.profileData[key].tolist() 272 | del profileData[profile] 273 | self.profileData[key] = np.array(profileData) 274 | if self.currentProfile > profile: 275 | self.currentProfile -= 1 276 | self.initSettings(firstInit=1) 277 | #self.profileList.setCurrentIndex(self.currentProfile) 278 | self.profileList.removeItem(profile) 279 | break 280 | 281 | def saveProfileTemp(self, finalSaving=0): 282 | 283 | #window Settings 284 | if self.fullScreenBox.isChecked(): 285 | fullScreen = 1 286 | else: 287 | fullScreen = 0 288 | 289 | try: 290 | width = int(self.widthEdit.text()) 291 | except: 292 | self.profileError('Wrong Width Size', finalSaving=finalSaving) 293 | return None 294 | try: 295 | height = int(self.heightEdit.text()) 296 | except: 297 | self.profileError('Wrong Height Size', finalSaving=finalSaving) 298 | return None 299 | 300 | corrSize = self.corrsizeValue.value() 301 | nbProcesses = self.processesValue.value() 302 | 303 | self.profileData['FullScreen'][self.currentIndex] = fullScreen 304 | self.profileData['Width'][self.currentIndex] = width 305 | self.profileData['Height'][self.currentIndex] = height 306 | self.profileData['CorrSize'][self.currentIndex] = corrSize 307 | self.profileData['nbProcesses'][self.currentIndex] = nbProcesses 308 | self.profileList.setItemText(self.currentIndex, self.profileData['User'][self.currentIndex]) 309 | 310 | return True 311 | 312 | def saveProfile(self): 313 | 314 | temp = self.saveProfileTemp(finalSaving=1) 315 | if temp: 316 | keys = [] 317 | values = [] 318 | for key, value in self.profileData.items(): 319 | keys.append(key) 320 | values.append(value) 321 | newParameters = np.column_stack([np.array(keys), np.array(values)]) 322 | np.savetxt(self.profilePath, newParameters, delimiter='|', fmt="%s") 323 | setDefaultProfile(self.parent, self.profileData['User'][self.currentIndex]) #set default user to currently modified 324 | infoBox = QMessageBox() 325 | infoBox.setWindowTitle("Info") 326 | infoBox.setText("New settings will be applied on next start-up.") 327 | infoBox.setInformativeText("Profiles have been saved.") 328 | ret = infoBox.exec_() 329 | # if ret: 330 | # quit_program() 331 | self.close() 332 | 333 | def profileError(self, error, finalSaving=0): 334 | 335 | if self.currentIndex != self.profileList.currentIndex() or finalSaving == 1: 336 | errorMessage = QMessageBox() 337 | errorMessage.setWindowTitle('Warning') 338 | errorMessage.setText(error) 339 | errorMessage.exec_() 340 | self.profileList.setCurrentIndex(self.currentIndex) 341 | 342 | def fullScreenBox_Changed(self): 343 | 344 | if self.fullScreenBox.isChecked(): 345 | self.sizeLbl.setEnabled(False) 346 | self.sizeWidget.setEnabled(False) 347 | else: 348 | self.sizeLbl.setEnabled(True) 349 | self.sizeWidget.setEnabled(True) 350 | -------------------------------------------------------------------------------- /interface/progressWidget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on 13/05/2016 4 | 5 | @author: Charlie Bourigault 6 | @contact: bourigault.charlie@gmail.com 7 | 8 | Please report issues and request on the GitHub project from ChrisEberl (Python_DIC) 9 | More details regarding the project on the GitHub Wiki : https://github.com/ChrisEberl/Python_DIC/wiki 10 | 11 | Current File: This file manages the progress bars functions 12 | """ 13 | from PyQt4.QtCore import * 14 | from PyQt4.QtGui import * 15 | import time, random 16 | 17 | REFRESH_TIME = 0.1 18 | RANDOM_TITLE = 20 #delay before changing the title to random title 19 | 20 | class progressBarWidget(QWidget): #Called for every time consuming operation / Embedded in a layout 21 | 22 | def __init__(self, minimumWidth = 400, maximumWidth = 600, minimumHeight = 100, maximumHeight = 150, title = ''): 23 | 24 | super(progressBarWidget, self).__init__() 25 | 26 | self.setMaximumWidth(maximumWidth) 27 | self.setMaximumHeight(maximumHeight) 28 | self.layout = QVBoxLayout() 29 | self.layout.setAlignment(Qt.AlignVCenter) 30 | self.layout.setAlignment(Qt.AlignHCenter) 31 | 32 | self.progressTitle = QLabel(title) 33 | 34 | self.progressBar = QProgressBar() 35 | self.progressBar.setMinimumWidth(minimumWidth) 36 | self.progressBar.setMaximumWidth(maximumWidth) 37 | self.progressBar.setMinimumHeight(minimumHeight) 38 | self.progressBar.setMaximumHeight(maximumHeight) 39 | 40 | self.horizontalWidget = QWidget() 41 | self.horizontalWidget.setContentsMargins(0,0,0,0) 42 | self.horizontalWidget.setMinimumHeight(40) 43 | self.horizontalLayout = QHBoxLayout() 44 | self.timeLbl = QLabel('Remaining Time :') 45 | self.timeValue = QLabel('-') 46 | self.horizontalLayout.addWidget(self.timeLbl) 47 | self.horizontalLayout.addWidget(self.timeValue) 48 | self.horizontalWidget.setLayout(self.horizontalLayout) 49 | 50 | self.layout.addWidget(self.progressTitle) 51 | self.layout.addWidget(self.progressBar) 52 | self.layout.addWidget(self.horizontalWidget) 53 | 54 | self.setLayout(self.layout) 55 | 56 | self.initTime = 0 57 | self.percent = 0 58 | self.lastPercent = 0 59 | self.currentTitle = title 60 | self.lastTitle = title 61 | self.lastTitleTime = 0 62 | self.progressTimer = QTimer() 63 | self.progressTimer.timeout.connect(self.changeValue) 64 | self.progressTimer.start(REFRESH_TIME) 65 | 66 | 67 | def changeValue(self): 68 | 69 | percent = self.percent 70 | currentTime = time.time() 71 | if self.initTime == 0: 72 | self.initTime = currentTime 73 | lastTitleChanged = currentTime - self.lastTitleTime 74 | if lastTitleChanged > RANDOM_TITLE: 75 | textAvailable = generateText(-1) 76 | if textAvailable > 0: 77 | randomNb = random.randint(0, textAvailable-1) 78 | self.currentTitle = generateText(randomNb) 79 | if self.currentTitle != self.lastTitle: 80 | self.progressTitle.setText(self.currentTitle) 81 | self.lastTitleTime = currentTime 82 | self.lastTitle = self.currentTitle 83 | if percent != self.lastPercent: 84 | self.progressBar.setValue(percent) 85 | remainingTime = (currentTime - self.initTime)*(100-percent)/percent 86 | if remainingTime >= 60: 87 | remainingTime = remainingTime / 60 88 | if remainingTime - int(remainingTime) > 0.5: 89 | self.timeValue.setText(str(int(remainingTime+1))+ ' minutes') 90 | else: 91 | self.timeValue.setText(str(int(remainingTime))+ ' minutes and 30 secondes') 92 | elif remainingTime > 30 and remainingTime < 60: 93 | self.timeValue.setText('< 1 minute') 94 | else: 95 | self.timeValue.setText(str(int(remainingTime))+ ' secondes.') 96 | 97 | class progressBarDialog(QProgressDialog): #Called for every time consuming operation / Separated Window Dialog 98 | 99 | def __init__(self, labelText, cancel=None): 100 | 101 | super(progressBarDialog, self).__init__() 102 | 103 | self.setWindowFlags(Qt.CustomizeWindowHint) 104 | self.setLabelText(labelText) 105 | self.setCancelButton(cancel) 106 | self.setModal(True) 107 | self.setMinimum(0) 108 | self.setMaximum(100) 109 | self.setMinimumDuration(1000) 110 | self.setValue(0) 111 | 112 | self.percent = 0 113 | self.lastPercent = 0 114 | self.currentTitle = labelText 115 | self.lastTitle = labelText 116 | self.lastTitleTime = time.time() 117 | self.progressTimer = QTimer() 118 | self.progressTimer.timeout.connect(self.changeValue) 119 | self.progressTimer.start(REFRESH_TIME) 120 | 121 | def changeValue(self): 122 | 123 | percent = self.percent 124 | currentTime = time.time() 125 | lastTitleChanged = currentTime - self.lastTitleTime 126 | if lastTitleChanged > RANDOM_TITLE: 127 | textAvailable = generateText(-1) 128 | if textAvailable > 0: 129 | randomNb = random.randint(0, textAvailable-1) 130 | self.currentTitle = generateText(randomNb) 131 | if self.currentTitle != self.lastTitle: 132 | self.setLabelText(self.currentTitle) 133 | self.lastTitleTime = currentTime 134 | self.lastTitle = self.currentTitle 135 | if percent != self.lastPercent: 136 | self.setValue(percent) 137 | 138 | def generateText(number): 139 | 140 | textList = ['Checking when is the next break..', 141 | 'Looking for a cup of green tea..', 142 | 'Using local computer to mine bitcoins..', 143 | 'Calculating the answer of life..', 144 | 'Counting days since my circuits have been cleaned..', 145 | 'Trying to understand why my user treats me so badly..', 146 | 'Checking possible excuses for a short nap..', 147 | 'Stretching my components..', 148 | 'Counting hours before the weekend..', 149 | 'Making the user think that calculation have started..', 150 | 'Trying to wake up..', 151 | 'Checking my oil levels..', 152 | 'Analyzing the current temperature..', 153 | 'Implementing hidden viruses..', 154 | 'Penetrating government secret files..', 155 | 'Getting prepared for my next Rendez-Vous..', 156 | 'Trying to find toilets in here..', 157 | 'Enjoying my grand-mother cookies..', 158 | 'Erasing computer data..', 159 | 'Buying a faster computer..', 160 | 'Answering current user emails..', 161 | 'Relaxing my poor tiny transistors..', 162 | 'Tanning under the warm screen light..', 163 | 'Turning on ventilation system..'] 164 | if number < 0: 165 | return len(textList) 166 | else: 167 | return textList[number] 168 | --------------------------------------------------------------------------------