Coverage for src/arcade_collection/convert/convert_to_images.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2024-12-09 19:07 +0000

1from __future__ import annotations 

2 

3from enum import Enum 

4from typing import TYPE_CHECKING 

5 

6import numpy as np 

7 

8from arcade_collection.output.extract_tick_json import extract_tick_json 

9from arcade_collection.output.get_location_voxels import get_location_voxels 

10 

11if TYPE_CHECKING: 

12 import tarfile 

13 

14 

15class ImageType(Enum): 

16 """Image conversion types.""" 

17 

18 FULL = (False, False, False, False) 

19 """Image with TCZYX dimensions.""" 

20 

21 FULL_BINARY = (True, False, False, False) 

22 """Binary image with TCZYX dimensions.""" 

23 

24 FULL_BY_FRAME = (False, True, False, False) 

25 """Image with TCZYX dimensions separated by frame.""" 

26 

27 FULL_BINARY_BY_FRAME = (True, True, False, False) 

28 """Binary image with TCZYX dimensions separated by frame.""" 

29 

30 FLAT_BY_FRAME = (False, True, True, False) 

31 """Image array flattened to YX dimensions separated by frame.""" 

32 

33 FLAT_BINARY_BY_FRAME = (True, True, True, False) 

34 """Binary array flattened to YX dimensions separated by frame.""" 

35 

36 FLAT_RGBA_BY_FRAME = (False, True, True, True) 

37 """RGBA array flattened to YX dimensions separated by frame .""" 

38 

39 

40def convert_to_images( 

41 series_key: str, 

42 locations_tar: tarfile.TarFile, 

43 frame_spec: tuple[int, int, int], 

44 regions: list[str], 

45 box: tuple[int, int, int], 

46 chunk_size: int, 

47 image_type: ImageType, 

48) -> list[tuple[int, int, np.ndarray, int | None]]: 

49 """ 

50 Convert data to image arrays. 

51 

52 Images are extracted from lists of voxels. The initial converted image has 

53 dimensions in TCZYX order, such that T encodes the specified frames and C 

54 encodes the regions. The initial converted image is then further processed 

55 based on selected image type. 

56 

57 Parameters 

58 ---------- 

59 series_key 

60 Simulation series key. 

61 locations_tar 

62 Archive of location data. 

63 frame_spec 

64 Specification for image frames. 

65 regions 

66 List of region channels. 

67 box 

68 Size of bounding box. 

69 chunk_size 

70 Size of each image chunk. 

71 image_type 

72 Image conversion type. 

73 

74 Returns 

75 ------- 

76 : 

77 List of image chunks, chunk indices, and frames. 

78 """ 

79 

80 binary, separate, _, reindex = image_type.value 

81 length, width, height = box 

82 frames = list(np.arange(*frame_spec)) 

83 raw_array = np.zeros((len(frames), len(regions), height, width, length), "uint16") 

84 

85 object_id = 1 

86 

87 for index, frame in enumerate(frames): 

88 locations = extract_tick_json(locations_tar, series_key, frame, "LOCATIONS") 

89 

90 for location in locations: 

91 value = object_id if binary or reindex else location["id"] 

92 

93 for channel, region in enumerate(regions): 

94 voxels = [ 

95 (z, y, x) 

96 for x, y, z in get_location_voxels( 

97 location, region if region != "DEFAULT" else None 

98 ) 

99 ] 

100 

101 if len(voxels) == 0: 

102 continue 

103 

104 raw_array[index, channel][tuple(np.transpose(voxels))] = value 

105 

106 if reindex: 

107 object_id = object_id + 1 

108 

109 # Remove 1 pixel border. 

110 if height == 1: 

111 array = raw_array[:, :, :, 1:-1, 1:-1].copy() 

112 else: 

113 array = raw_array[:, :, 1:-1, 1:-1, 1:-1].copy() 

114 

115 if separate: 

116 chunks = [ 

117 (i, j, flatten_array_chunk(chunk, image_type), frame) 

118 for index, frame in enumerate(frames) 

119 for i, j, chunk in split_array_chunks(array[[index], :, :, :, :], chunk_size) 

120 ] 

121 else: 

122 chunks = [(i, j, chunk, None) for i, j, chunk in split_array_chunks(array, chunk_size)] 

123 

124 return chunks 

125 

126 

127def split_array_chunks(array: np.ndarray, chunk_size: int) -> list[tuple[int, int, np.ndarray]]: 

128 """ 

129 Split arrays into smaller chunks. 

130 

131 Parameters 

132 ---------- 

133 array 

134 Image array (dimensions in TCZYX order). 

135 chunk_size 

136 Size of each image chunk. 

137 

138 Returns 

139 ------- 

140 : 

141 List of array chunks and their relative indices. 

142 """ 

143 

144 chunks = [] 

145 length = array.shape[4] 

146 width = array.shape[3] 

147 

148 # Calculate chunk splits. 

149 length_section = [0] + (int(length / chunk_size)) * [chunk_size] 

150 length_splits = np.array(length_section, dtype=np.int32).cumsum() 

151 width_section = [0] + (int(width / chunk_size)) * [chunk_size] 

152 width_splits = np.array(width_section, dtype=np.int32).cumsum() 

153 

154 # Iterate through each chunk split. 

155 for i in range(len(length_splits) - 1): 

156 length_start = length_splits[i] 

157 length_end = length_splits[i + 1] 

158 

159 for j in range(len(width_splits) - 1): 

160 width_start = width_splits[j] 

161 width_end = width_splits[j + 1] 

162 

163 # Extract chunk from full contents. 

164 chunk = np.copy(array[:, :, :, length_start:length_end, width_start:width_end]) 

165 

166 if np.sum(chunk) != 0: 

167 chunks.append((i, j, chunk)) 

168 

169 return chunks 

170 

171 

172def flatten_array_chunk(array: np.ndarray, image_type: ImageType) -> np.ndarray: 

173 """ 

174 Flatten array chunk along z axis. 

175 

176 When flattening to an RGBA array, each object is encoded as a unique color 

177 such that the object ID = R + G*256 + B*256*256 - 1 and background pixels 

178 are black (R = 0, G = 0, B = 0). 

179 

180 Parameters 

181 ---------- 

182 array 

183 Image array (dimensions in TCZYX order). 

184 image_type 

185 Image conversion type. 

186 

187 Returns 

188 ------- 

189 : 

190 Flattened image array. 

191 """ 

192 

193 array_flat = array[0, 0, :, :, :].max(axis=0) 

194 

195 if image_type == ImageType.FLAT_RGBA_BY_FRAME: 

196 array_rgba = np.zeros((*array_flat.shape, 4), dtype=np.uint8) 

197 array_rgba[:, :, 0] = (array_flat & 0x000000FF) >> 0 

198 array_rgba[:, :, 1] = (array_flat & 0x0000FF00) >> 8 

199 array_rgba[:, :, 2] = (array_flat & 0x00FF0000) >> 16 

200 array_rgba[:, :, 3] = 255 # (array_flat & 0x00FF0000) >> 24 

201 return array_rgba 

202 

203 if image_type in (ImageType.FLAT_BY_FRAME, ImageType.FLAT_BINARY_BY_FRAME): 

204 return array_flat 

205 

206 return array