utils.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. """General utility functions"""
  2. import numpy as np
  3. from skimage import color
  4. def crop_zeros(x, y=None, value=0.0):
  5. """Crop an array, `x` to its non-zero elements, or optionally to those of a second array `y`
  6. Parameters
  7. ----------
  8. x : ndarray
  9. The array that should be cropped.
  10. y: ndarray, optional
  11. The array (identical in shape to `x`) by whose non-zero elements `x` should be cropped. If `None`, will use non-zero elements of `x`.
  12. value: float
  13. The (background) value that should be cropped out.
  14. Returns
  15. -------
  16. ndarray
  17. `x`, cropped to the non-zero elements of `x` or, if specified, `y`
  18. Examples
  19. --------
  20. >>> a = np.arange(16).reshape((4, 4))
  21. >>> a[0, :] = 0
  22. >>> a[:, 3] = 0
  23. >>> crop_zeros(a)
  24. array([[ 4, 5, 6],
  25. [ 8, 9, 10],
  26. [12, 13, 14]])
  27. >>> b = np.arange(16).reshape(4, 4)
  28. >>> crop_zeros(b, a)
  29. array([[ 4, 5, 6],
  30. [ 8, 9, 10],
  31. [12, 13, 14]])
  32. """
  33. # find non-zero elements
  34. nonzero_idx = np.where(x!=value) if y is None else np.where(y!=value)
  35. # create list of slices by which x should be indexed
  36. crop_idx = [slice(np.min(d), np.max(d)+1) for d in nonzero_idx]
  37. x_crop = x[tuple(crop_idx)]
  38. return(x_crop)
  39. def pad_translate_mat(x, dim0, dim1, value=0):
  40. """Translate a matrix with padding
  41. Parameters
  42. ----------
  43. x : ndarray
  44. The matrix to pad.
  45. dim0 : float
  46. The translation to apply in axis 0.
  47. dim1 : float
  48. The translation to apply in axis 1.
  49. value : float
  50. The value to pad with when inserting rows/columns.
  51. Returns
  52. -------
  53. ndarray
  54. `x`, with values translated.
  55. Examples
  56. --------
  57. >>> a = np.zeros((4, 4))
  58. >>> a[1, :] = 1
  59. >>> a
  60. array([[0., 0., 0., 0.],
  61. [0., 0., 0., 0.],
  62. [1., 1., 1., 1.],
  63. [0., 0., 0., 0.]])
  64. >>> pad_translate_mat(a, 1, 0)
  65. array([[0., 0., 0., 0.],
  66. [1., 1., 1., 1.],
  67. [0., 0., 0., 0.],
  68. [0., 0., 0., 0.]])
  69. """
  70. # if dim0 < 0:
  71. # x = np.pad(x, [(abs(dim0), value), (value, value)])
  72. # x = x[0:dim0, :]
  73. # elif dim0 > 0:
  74. # x = np.pad(x, [(value, dim0), (value, value)])
  75. # x = x[dim0:, :]
  76. # if dim1 < 0:
  77. # x = np.pad(x, [(value, value), (abs(dim1), value)])
  78. # x = x[:, 0:dim1]
  79. # elif dim1 > 0:
  80. # x = np.pad(x, [(value, value), (value, dim1)])
  81. # x = x[:, dim1:]
  82. other_dims = [(0,0)] * (len(x.shape) - 2)
  83. if dim0 < 0:
  84. pad0 = [(abs(dim0), value), (value, value)]
  85. pad0.extend(other_dims)
  86. x = np.pad(x, pad0)
  87. x = x[0:dim0, :]
  88. elif dim0 > 0:
  89. pad0 = [(value, dim0), (value, value)]
  90. pad0.extend(other_dims)
  91. x = np.pad(x, pad0)
  92. x = x[dim0:, :]
  93. if dim1 < 0:
  94. pad1 = [(value, value), (abs(dim1), value)]
  95. pad1.extend(other_dims)
  96. x = np.pad(x, pad1)
  97. x = x[:, 0:dim1]
  98. elif dim1 > 0:
  99. pad1 = [(value, value), (value, dim1)]
  100. pad1.extend(other_dims)
  101. x = np.pad(x, pad1)
  102. x = x[:, dim1:]
  103. return(x)
  104. def pad_for_translation(mat1, mat2, pad=True, constant_values=0):
  105. """Get two matrices to be the same size, with zero-padding for translation. Space for translation will mean that both matrices are made to be 9 times as large (3x in both dimensions) as the largest of the two.
  106. Parameters
  107. ----------
  108. mat1 : ndarray
  109. mat2 : ndarray
  110. pad : bool
  111. If `True`, will pad with space for translation. If `False`, will just pad to be the same size with the smaller matrix's non-zero elements centred on those of the larger.
  112. constant_values : float or list or array
  113. Passed to `np.pad`. Will usually want to be the value for the background (default = 0).
  114. Returns
  115. -------
  116. tuple
  117. A tuple in the form `(mat1, mat2)`, containing the padded matrices.
  118. Examples
  119. --------
  120. >>> a = np.arange(16).reshape((4, 4))
  121. >>> b = np.arange(9).reshape((3, 3))
  122. >>> a_pad, b_pad = pad_for_translation(a, b)
  123. >>> (a_pad.shape, b_pad.shape)
  124. """
  125. assert len(mat1.shape)==2
  126. assert len(mat2.shape)==2
  127. # get the max dimensions of both
  128. max_dims = [np.max([mat1.shape[i], mat2.shape[i]]) for i in range(2)]
  129. # get the amount of padding needed
  130. mat1_diff = np.abs([mat1.shape[i] - max_dims[i] for i in range(len(max_dims))])
  131. mat2_diff = np.abs([mat2.shape[i] - max_dims[i] for i in range(len(max_dims))])
  132. # if any odd numbers, add 1 to the diff for each array
  133. mat1_modulo = mat1_diff % 2
  134. mat2_modulo = mat2_diff % 2
  135. mat1_diff += mat1_modulo
  136. mat2_diff += mat2_modulo
  137. # account for possible difference in size
  138. mat1 = np.pad(mat1, [(0, mat2_modulo[0]), (0, mat2_modulo[1])], constant_values=constant_values)
  139. mat2 = np.pad(mat2, [(0, mat1_modulo[0]), (0, mat1_modulo[1])], constant_values=constant_values)
  140. # pad to be the same size
  141. mat1_pad_size = [(int(mat1_diff[i]/2), int(mat1_diff[i]/2)) for i in range(len(mat1_diff))]
  142. mat2_pad_size = [(int(mat2_diff[i]/2), int(mat2_diff[i]/2)) for i in range(len(mat2_diff))]
  143. mat1 = np.pad(mat1, mat1_pad_size, constant_values=constant_values)
  144. mat2 = np.pad(mat2, mat2_pad_size, constant_values=constant_values)
  145. # check they are the same size
  146. assert mat1.shape == mat2.shape
  147. if pad:
  148. # now pad to allow all possible translations
  149. mat1 = np.pad(mat1, [(mat1.shape[0], mat1.shape[0]), (mat1.shape[1], mat1.shape[1])], constant_values=constant_values)
  150. mat2 = np.pad(mat2, [(mat2.shape[0], mat2.shape[0]), (mat2.shape[1], mat2.shape[1])], constant_values=constant_values)
  151. # and pad the starts to make even
  152. mat1_modulo_b = np.array(mat1.shape) % 2
  153. mat2_modulo_b = np.array(mat2.shape) % 2
  154. mat1 = np.pad(mat1, [(mat1_modulo_b[0], 0), (mat1_modulo_b[1], 0)], constant_values=constant_values)
  155. mat2 = np.pad(mat2, [(mat2_modulo_b[0], 0), (mat2_modulo_b[1], 0)], constant_values=constant_values)
  156. # return the two matrices as a tuple
  157. return(mat1, mat2)
  158. def chunk_list(l, n):
  159. """Chunk a list, `l`, into `n` chunks of (as close to as possible) equal length, keeping original sequential order. Written by https://stackoverflow.com/a/54802737
  160. Parameters
  161. ----------
  162. l : list
  163. The list to be chunked.
  164. n : int
  165. The number of chunks to produce.
  166. Returns
  167. -------
  168. generator object
  169. A generator object with the list chunked.
  170. """
  171. d, r = divmod(len(l), n)
  172. for i in range(n):
  173. si = (d+1)*(i if i < r else r) + d*(0 if i < r else i - r)
  174. yield l[si:si+(d+1 if i < r else d)]
  175. def rotate_rgb_hue(rgb, hue_shift=0.5):
  176. """Rotate the hue of the colour channel of a numpy array. RGB should be stored in the final dimension.
  177. Parameters
  178. ----------
  179. rgb : np.array
  180. The array with colour values whose hue should be shifted.
  181. hue_shift : float
  182. A value from 0 to 1 that dictates the degrees to which the hue should be rotated. Degrees are equivalent to `hue_shuft*360`.
  183. Returns
  184. -------
  185. np.array
  186. The original np.array, but with colour values in the rgb channel rotated.
  187. """
  188. hsv = color.rgb2hsv(rgb)
  189. hsv[:, :, 0] += hue_shift
  190. hsv[:, :, 0][hsv[:, :, 0] > 1] -= hsv[:, :, 0].min()
  191. return color.hsv2rgb(hsv)
  192. def logistic(x):
  193. x = np.array(x)
  194. return np.log( (x+1) / (1-x) )
  195. def inv_logistic(x):
  196. x = np.array(x)
  197. return ( np.exp(x)-1 ) / ( np.exp(x)+1 )