image_processing.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. from PIL import Image
  2. from matplotlib import cm
  3. import random
  4. from skimage import color
  5. import numpy as np
  6. import ipfml.metrics as metrics
  7. import cv2
  8. from scipy import signal
  9. def fig2data(fig):
  10. """
  11. @brief Convert a Matplotlib figure to a 3D numpy array with RGB channels and return it
  12. @param fig a matplotlib figure
  13. @return a numpy 3D array of RGB values
  14. """
  15. # draw the renderer
  16. fig.canvas.draw()
  17. # Get the RGBA buffer from the figure
  18. w,h = fig.canvas.get_width_height()
  19. buf = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8)
  20. buf.shape = (w, h, 3)
  21. # canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
  22. buf = np.roll(buf, 3, axis=2)
  23. return buf
  24. def fig2img(fig):
  25. """
  26. @brief Convert a Matplotlib figure to a PIL Image in RGB format and return it
  27. @param fig a matplotlib figure
  28. @return a Python Imaging Library (PIL) image : default size (480,640,3)
  29. """
  30. # put the figure pixmap into a numpy array
  31. buf = fig2data(fig)
  32. w, h, d = buf.shape
  33. return Image.frombytes("RGB", (w, h), buf.tostring())
  34. def get_LAB_L_SVD(image):
  35. """
  36. @brief Returns Singular values from LAB L Image information
  37. @param fig a matplotlib figure
  38. @return a Python Imaging Library (PIL) image : default size (480,640,3)
  39. Usage :
  40. >>> from PIL import Image
  41. >>> from ipfml import image_processing
  42. >>> img = Image.open('./images/test_img.png')
  43. >>> U, s, V = image_processing.get_LAB_L_SVD(img)
  44. >>> U.shape
  45. (200, 200)
  46. >>> len(s)
  47. 200
  48. >>> V.shape
  49. (200, 200)
  50. """
  51. L = metrics.get_LAB_L(image)
  52. return metrics.get_SVD(L)
  53. def get_LAB_L_SVD_s(image):
  54. """
  55. @brief Returns s (Singular values) SVD from L of LAB Image information
  56. @param PIL Image
  57. @return vector of singular values
  58. Usage :
  59. >>> from PIL import Image
  60. >>> from ipfml import image_processing
  61. >>> img = Image.open('./images/test_img.png')
  62. >>> s = image_processing.get_LAB_L_SVD_s(img)
  63. >>> len(s)
  64. 200
  65. """
  66. L = metrics.get_LAB_L(image)
  67. return metrics.get_SVD_s(L)
  68. def get_LAB_L_SVD_U(image):
  69. """
  70. @brief Returns U SVD from L of LAB Image information
  71. @param PIL Image
  72. @return vector of singular values
  73. Usage :
  74. >>> from PIL import Image
  75. >>> from ipfml import image_processing
  76. >>> img = Image.open('./images/test_img.png')
  77. >>> U = image_processing.get_LAB_L_SVD_U(img)
  78. >>> U.shape
  79. (200, 200)
  80. """
  81. L = metrics.get_LAB_L(image)
  82. return metrics.get_SVD_U(L)
  83. def get_LAB_L_SVD_V(image):
  84. """
  85. @brief Returns V SVD from L of LAB Image information
  86. @param PIL Image
  87. @return vector of singular values
  88. Usage :
  89. >>> from PIL import Image
  90. >>> from ipfml import image_processing
  91. >>> img = Image.open('./images/test_img.png')
  92. >>> V = image_processing.get_LAB_L_SVD_V(img)
  93. >>> V.shape
  94. (200, 200)
  95. """
  96. L = metrics.get_LAB_L(image)
  97. return metrics.get_SVD_V(L)
  98. def divide_in_blocks(image, block_size, pil=True):
  99. '''
  100. @brief Divide image into equal size blocks
  101. @param img - PIL Image or numpy array
  102. @param block - tuple (width, height) representing the size of each dimension of the block
  103. @param pil - kind block type (PIL by default or Numpy array)
  104. @return list containing all 2D numpy blocks (in RGB or not)
  105. Usage :
  106. >>> import numpy as np
  107. >>> from PIL import Image
  108. >>> from ipfml import image_processing
  109. >>> from ipfml import metrics
  110. >>> image_values = np.random.randint(255, size=(800, 800, 3))
  111. >>> blocks = divide_img_in_blocks(image_values, (20, 20))
  112. >>> len(blocks)
  113. 1600
  114. >>> blocks[0].width
  115. 20
  116. >>> blocks[0].height
  117. 20
  118. >>> img_l = Image.open('./images/test_img.png')
  119. >>> L = metrics.get_LAB_L(img_l)
  120. >>> blocks_L = divide_in_blocks(L, (100, 100))
  121. >>> len(blocks_L)
  122. 4
  123. >>> blocks_L[0].width
  124. 100
  125. '''
  126. blocks = []
  127. mode = 'RGB'
  128. # check input type (PIL Image or numpy array) and convert it if necessary
  129. if hasattr(image, 'filename'):
  130. image_array = np.array(image)
  131. else:
  132. image_array = image
  133. # check dimension of input image
  134. if image_array.ndim != 3:
  135. mode = 'L'
  136. image_width, image_height = image_array.shape
  137. else:
  138. image_width, image_height, _ = image_array.shape
  139. # check size compatibility
  140. width, height = block_size
  141. if(image_width % width != 0):
  142. raise "Width size issue, block size not compatible"
  143. if(image_height % height != 0):
  144. raise "Height size issue, block size not compatible"
  145. nb_block_width = image_width / width
  146. nb_block_height = image_height / height
  147. for i in range(int(nb_block_width)):
  148. begin_x = i * width
  149. for j in range(int(nb_block_height)):
  150. begin_y = j * height
  151. # getting sub block information
  152. current_block = image_array[begin_x:(begin_x + width), begin_y:(begin_y + height)]
  153. if pil:
  154. blocks.append(Image.fromarray(current_block.astype('uint8'), mode))
  155. else:
  156. blocks.append(current_block)
  157. return blocks
  158. def normalize_arr(arr):
  159. '''
  160. @brief Normalize data of 1D array shape
  161. @param array - array data of 1D shape
  162. Usage :
  163. >>> from ipfml import image_processing
  164. >>> import numpy as np
  165. >>> arr = np.arange(11)
  166. >>> arr_normalized = image_processing.normalize_arr(arr)
  167. >>> arr_normalized[1]
  168. 0.1
  169. '''
  170. output_arr = []
  171. max_value = max(arr)
  172. min_value = min(arr)
  173. for v in arr:
  174. output_arr.append((v - min_value) / (max_value - min_value))
  175. return output_arr
  176. def normalize_arr_with_range(arr, min, max):
  177. '''
  178. @brief Normalize data of 1D array shape
  179. @param array - array data of 1D shape
  180. Usage :
  181. >>> from ipfml import image_processing
  182. >>> import numpy as np
  183. >>> arr = np.arange(11)
  184. >>> arr_normalized = image_processing.normalize_arr_with_range(arr, 0, 20)
  185. >>> arr_normalized[1]
  186. 0.05
  187. '''
  188. output_arr = []
  189. for v in arr:
  190. output_arr.append((v - min) / (max - min))
  191. return output_arr
  192. # TODO : add test to this method
  193. def rgb_to_mscn(image):
  194. """
  195. @brief Convert RGB Image into Mean Subtracted Contrast Normalized (MSCN)
  196. @param 3D RGB image numpy array or PIL RGB image
  197. """
  198. # check if PIL image or not
  199. img_arr = np.array(image)
  200. # convert rgb image to gray
  201. im = np.array(color.rgb2gray(img_arr)*255, 'uint8')
  202. s = 7/6
  203. blurred = cv2.GaussianBlur(im, (7, 7), s) # apply gaussian blur to the image
  204. blurred_sq = blurred * blurred
  205. sigma = cv2.GaussianBlur(im * im, (7, 7), s) # switch to -3, 3 (7, 7) before..
  206. sigma = abs(sigma - blurred_sq) ** 0.5
  207. sigma = sigma + 1.0/255 # to make sure the denominator doesn't give DivideByZero Exception
  208. structdis = (im - blurred)/sigma # final MSCN(i, j) image
  209. return structdis
  210. # TODO : Check this method too...
  211. def get_random_active_block(blocks, threshold = 0.1):
  212. """
  213. @brief Find an active block from blocks and return it (randomly way)
  214. @param 2D numpy array
  215. @param threshold 0.1 by default
  216. """
  217. active_blocks = []
  218. for id, block in enumerate(blocks):
  219. arr = np.asarray(block)
  220. variance = np.var(arr.flatten())
  221. if variance >= threshold:
  222. active_blocks.append(id)
  223. r_id = random.choice(active_blocks)
  224. return np.asarray(blocks[r_id])
  225. # TODO : check this method and check how to use active block
  226. def segment_relation_in_block(block, active_block):
  227. """
  228. @brief Return bêta value to quantity relation between central segment and surrouding regions into block
  229. @param 2D numpy array
  230. """
  231. if block.ndim != 2:
  232. raise "Numpy array dimension is incorrect, expected 2."
  233. # getting middle information of numpy array
  234. x, y = block.shape
  235. if y < 4:
  236. raise "Block size too small needed at least (x, 4) shape"
  237. middle = int(y / 2)
  238. # get central segments
  239. central_segments = block[:, middle-1:middle+1]
  240. # getting surrouding parts
  241. left_part = block[:, 0:middle-1]
  242. right_part = block[:, middle+1:]
  243. surrounding_parts = np.concatenate([left_part, right_part])
  244. std_sur = np.std(surrounding_parts.flatten())
  245. std_cen = np.std(central_segments.flatten())
  246. std_block = np.std(block.flatten())
  247. print("CEN " + str(std_cen))
  248. print("SUR " + str(std_sur))
  249. print("BLOCK " + str(std_block))
  250. std_q = std_cen / std_sur
  251. # from article, it says that block if affected with noise if (std_block > 2 * beta)
  252. beta = abs(std_q - std_block) / max(std_q, std_block)
  253. return beta
  254. def normalize_2D_arr(arr):
  255. """
  256. @brief Return array normalize from its min and max values
  257. @param 2D numpy array
  258. """
  259. # getting min and max value from 2D array
  260. max_value = arr.max(axis=1).max()
  261. min_value = arr.min(axis=1).min()
  262. # lambda computation to normalize
  263. g = lambda x : (x - min_value) / (max_value - min_value)
  264. f = np.vectorize(g)
  265. return f(arr)
  266. ### other way to compute MSCN :
  267. # TODO : Temp code, check to remove or use it
  268. def normalize_kernel(kernel):
  269. return kernel / np.sum(kernel)
  270. def gaussian_kernel2d(n, sigma):
  271. Y, X = np.indices((n, n)) - int(n/2)
  272. gaussian_kernel = 1 / (2 * np.pi * sigma ** 2) * np.exp(-(X ** 2 + Y ** 2) / (2 * sigma ** 2))
  273. return normalize_kernel(gaussian_kernel)
  274. def local_mean(image, kernel):
  275. return signal.convolve2d(image, kernel, 'same')
  276. def local_deviation(image, local_mean, kernel):
  277. "Vectorized approximation of local deviation"
  278. sigma = image ** 2
  279. sigma = signal.convolve2d(sigma, kernel, 'same')
  280. return np.sqrt(np.abs(local_mean ** 2 - sigma))
  281. def calculate_mscn_coefficients(image, kernel_size=6, sigma=7/6):
  282. # check if PIL image or not
  283. img_arr = np.array(image)
  284. #im = np.array(color.rgb2gray(img_arr)*255, 'uint8')
  285. #im = np.asarray(cv2.imread(image.filename, 0)) # read as gray scale
  286. print(img_arr.shape)
  287. C = 1/255
  288. kernel = gaussian_kernel2d(kernel_size, sigma=sigma)
  289. local_mean = signal.convolve2d(img_arr, kernel, 'same')
  290. local_var = local_deviation(img_arr, local_mean, kernel)
  291. return (img_arr - local_mean) / (local_var + C)