3 from os.path import isdir, join
5 from timeit import timeit
7 from pprint import pprint
12 from scipy import ndimage
13 from scipy import misc
16 from decimal import Decimal as d, ROUND_HALF_UP as rhu
19 from helpers import drawcontours
21 np.set_printoptions(edgeitems=372, linewidth=1200)# hey numpy, take advantage of my large screen
23 PNG16_MAX = pow(2, 16) - 1 # here if thinks the heaviest weight bit is for transparency or something not in use with dicom imgs
24 PNG8_MAX = pow(2, 8+1) - 1 # heaviest weight bit is 8 => 2**8, but dont forget the others: the reason of +1
26 INPUT_DIR = '../../Data/Images_anonymous/Case_0002/'
27 OUT_DIR = './generated/'
29 CROP_SIZE = (45, 45) # (width, height)
33 return (int(round(e)) for e in l)
36 # ---------- FTRIM ----------
37 # private func to trim the Matrix, but in one direction, vertically | horizontally
38 # return the slice, don't affect the Matrix
39 # y | : for (top to bottom), x -> : for (left to right)
41 # not my fault, numpy architecture
45 while i < len(Mat):# search by top
52 while i >= 0:# search by bottom
57 # print('y1, y2', y1, y2)
58 return slice(y1, y2+1)# +1 to stop at y2
61 # most use who make implicit call of ftrim twice
62 horizontal = ftrim(Mat)
63 vertical = ftrim(Mat.T)
64 # print('horizontal:vertical', horizontal, vertical)
65 return Mat[horizontal, vertical]
74 {'x': 94.377, 'y': 137.39},
75 {'x': 100.38, 'y': 139.55},
76 {'x': 103.26, 'y': 142.67},
77 {'x': 105.91, 'y': 147.95},
78 {'x': 105.42, 'y': 152.76},
79 {'x': 100.62, 'y': 156.84},
80 {'x': 95.338, 'y': 159.96},
81 {'x': 89.573, 'y': 158.52},
82 {'x': 84.53, 'y': 153},
83 {'x': 82.848, 'y': 149.15},
84 {'x': 82.368, 'y': 142.91},
85 {'x': 85.01, 'y': 138.11},
86 {'x': 89.813, 'y': 137.39},
87 {'x': 94.377, 'y': 137.39}
91 return [(94.377, 137.39), ...]
93 with open(file) as jsonfile:
94 data = json.load(jsonfile)
97 for imgXXX in data: pass # get the value of key ~= "Image00001", cause it's dynamic
99 for obj in data[imgXXX]: pass # get the object that contains the points, cause it's a list
100 points = obj['points']
102 # print("print, ", data)
103 # print("imgXXX, ", imgXXX)
104 # print("points, ", points)
105 # print("imgXXX, ", obj['points'])
106 tmp = [np.array( (round(pt['x']), round(pt['y'])) ).astype(np.int32) for pt in
107 points] # extract x,y. {'x': 94.377, 'y': 137.39} => (94.377, 137.39)
108 return np.array(tmp, dtype=np.int32)
115 xmin, ymin = np.min(r, axis=0)
116 xmax, ymax = np.max(r, axis=0)
118 # print('xmax, ymax', xmax, ymax)
119 # print('xmin, ymin', xmin, ymin)
121 return roundall(xmin, ymin, xmax, ymax)
123 def crop(Mat, maskfile, size=None):
125 size : if filled with a (45, 45), it will search the center of maskfile then crop by center Mat
127 xmin, ymin, xmax, ymax = minmax(maskfile)
129 xcenter = (xmin + xmax) / 2
130 ycenter = (ymin + ymax) / 2
133 ymin, xmin, ymax, xmax = roundall(xcenter - size[0], ycenter - size[0],
134 xcenter + size[1], ycenter + size[1])
136 return Mat[xmin:xmax, ymin:ymax]
138 def contour(Mat, maskfiles, color, thickness=1):
139 # ---------- CONTOUR POLYGON ----------
140 # thickness = -1 => fill it
141 # = 1 => just draw the contour with color
144 contours = [getxy(maskfile) for maskfile in maskfiles] # list of list of coords,
149 # print("log: contours", contours)
150 if contours is not None:
151 # print('type contours', type(contours))
152 # print('contours', contours)
153 msk = np.zeros(Mat.shape, dtype=np.int32) # init all the mask with True...
154 cv2.drawContours(msk, contours, -1, True, thickness) # affect Mat, fill the polygone with False
155 msk = msk.astype(np.bool)
158 # print("bool", timeit(lambda:msk, number=1000))
159 # print("normal", timeit(lambda:msk.astype(np.bool), number=1000))
160 # Mat = cv2.cvtColor(Mat, cv2.COLOR_RGB2GRAY) # apply gray
162 # Mat = drawcontours(Mat, contours)
164 Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector
167 def mask(Mat, maskfile, color=0, thickness=-1):
168 # ---------- MASK POLYGON ----------
169 # thickness = -1 => fill it
170 # = 1 => just draw the contour with color
173 contours = getxy(maskfile)
174 # print("log: contours", contours)
175 if contours is not None:
176 # print('type contours', type(contours))
177 # print('contours', contours)
178 msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True...
179 cv2.drawContours(msk, [contours], -1, False, thickness) # affect msk, fill the polygone with False, => further, don't touch where is False
180 msk = msk.astype(np.bool)
183 # print("bool", timeit(lambda:msk, number=1000))
184 # print("normal", timeit(lambda:msk.astype(np.bool), number=1000))
185 # Mat = cv2.cvtColor(Mat, cv2.COLOR_RGB2GRAY) # apply gray
187 # Mat = drawcontours(Mat, contours)
189 Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector
193 def hollowmask(Mat, epifile, endofile, color=0, thickness=-1):
194 # ---------- MASK POLYGON ----------
195 # thickness = -1 => fill it
196 # = 1 => just draw the contour with color
199 epicontours = getxy(epifile)
200 endocontours = getxy(endofile)
201 if epicontours is not None and endocontours is not None:
202 msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True...
203 cv2.drawContours(msk, [epicontours], -1, False, thickness) # affect msk, fill the polygone with False, => further, don't touch where is False
204 cv2.drawContours(msk, [endocontours], -1, True, thickness) # fill the intern polygone with True, (the holow in the larger polygon), => further, color where is True with black for example
205 msk = msk.astype(np.bool)
207 Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector
211 def sqrmask1(Mat, maskfile):
212 # ---------- SQUARE MASK 1st approach ----------
213 # print( timeit( lambda:sqrmask1(img, epimask), number=1000 ) ) # 0.48110522600000005
215 xmin, ymin, xmax, ymax = minmax(maskfile)
216 # print("xmin, ymin, xmax, ymax", xmin, ymin, xmax, ymax)
219 while i < ymin:# search by top
220 Mat[i].fill(0)# paint the row in black
224 while i > ymax:# search by bottom
225 Mat[i].fill(0)# paint the row in black
229 while i < xmin:# search by top
230 Mat.T[i, ymin:ymax+1].fill(0) # paint the column (row of the Transpose) in black, ymin and ymax to optimize, cause, I previously painted a part
234 while i > xmax:# search by bottom
235 Mat.T[i, ymin:ymax+1].fill(0) # paint the column (row of the Transpose) in black, ymin and ymax to optimize, cause, I previously painted a part
240 def sqrmask2(Mat, maskfile):
241 # ---------- SQUARE MASK 2nd and best approach ----------
242 # print( timeit( lambda:sqrmask2(img, epimask), number=1000 ) ) # 0.3097705270000001
244 xmin, ymin, xmax, ymax = minmax(maskfile)
245 # print("xmin, ymin, xmax, ymax", xmin, ymin, xmax, ymax)
247 msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True...
248 msk = msk.astype(np.bool)
250 msk[ymin:ymax, xmin:xmax] = False # for after, don't touch between min and max region
255 def map16(array):# can be useful in future
259 # return d(str(n)).quantize(d('1'), rounding=rhu)# safe round without
261 def affine(Mat, ab, cd):
263 Affine transformation
264 ab: the 'from' interval (2, 384) or {begin:2, end:384} begin is 0, and end is 1
265 cd: the 'to' interval (0, 1024) {begin:0, end:1024}
270 with np.nditer(Mat, op_flags=['readwrite']) as M:
272 x[...] = max( 0, round( (x-a) * (d-c) / (b-a) + c ) ) # could not be negative
275 def getdir(filepath):
276 return '/'.join(filepath.split('/')[:-1]) + '/'
278 def readpng(inputfile):# just a specific func to preview a "shape = (X,Y,3)" image
279 image = misc.imread(inputfile)
282 print("image.shape", image.shape)
285 for i, row in enumerate(tab):
286 print(row[0]*65536 + row[0]*256 + row[0], end=" " if i % image.shape[0] != 0 else "\n")
288 def topng(inputfile, outfile=None, overwrite=True, verbose=False, epimask='', endomask='', centercrop=None, blackmask=False, square=False, redcontour=False):
290 (verbose) return (64, 64) : the width and height
291 (not verbose) return (img, dicimg) : the image and the dicimg objects
292 centercrop : it's a size (45, 45), to mention I want to crop by center of epimask and by size.
293 blackmask : draw the outside with black
297 dicimg = pydicom.read_file(inputfile) # read dicom image
298 except pydicom.errors.InvalidDicomError as e:
299 # @TODO: log, i can't read this file
301 # img = trim(dicimg.pixel_array)# get image array (12bits), Don't trim FOR THE MOMENT
302 img = dicimg.pixel_array# get image array (12bits)
305 # return img.shape # $$ COMMENT OR REMOVE THIS LINE
307 # print('img', type(img))
308 # print('min', img.min(), 'max', img.max())
309 # dicimg.convert_pixel_data() # same as using dicimg.pixel_array
310 # pixa = dicimg._pixel_array
311 # print('dicimg._pixel_array', pixa)
312 # print('dicimg.pixel_array==pixa', dicimg.pixel_array==pixa)
315 # affine transfo to png 16 bits, func affects img variable
316 maxdepth = pow(2, dicimg.BitsStored) - 1
317 # print('dicimg.BitsStored, (img.min(), img.max())', dicimg.BitsStored, (img.min(), img.max())) # testing..
318 if int(dicimg.BitsStored) < 12:
319 print("\n\n\n-----{} Bits-----\n\n\n\n".format(dicimg.BitsStored))
321 (0, maxdepth), # img.min() replace 0 may be, but not sure it would be good choice
322 (0, PNG16_MAX if maxdepth > PNG8_MAX else PNG8_MAX)
326 # imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
327 # ret,thresh = cv2.threshold(img,127,255,0)
328 # im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
329 # print("im2, contours, hierarchy", im2, contours, hierarchy)
334 # print("log: epimask", epimask)
339 contours = [epimask] # list of list of coords
340 if endomask:contours.append(endomask)
342 img = contour(img, contours, color=RED_COLOR, thickness=1)
344 if blackmask:# if is there a mask => apply
346 img = sqrmask2(img, epimask)
349 img = hollowmask(img, epimask, endomask)
351 img = mask(img, epimask)
354 img = crop(img, epimask, centercrop)
360 # return img, dicimg, minmax(epimask)
364 savepath = (outfile or inputfile) + '.png'
365 savedir = getdir(savepath)
366 if overwrite and not isdir( savedir ):
367 pathlib.Path(savedir).mkdir(parents=True, exist_ok=True)
371 # tmp = np.array(img) # to get eye on the numpy format of img
372 # tmp = np.array(img) # to get eye on the numpy format of img
373 # print("img[0,0]", img[0,0])
375 # tmp.dtype = 'uint32'
376 # np.savetxt(savepath + '.npy', img)
379 if np.count_nonzero(img) > 0: # matrix not full of zero
380 cv2.imwrite(savepath, img, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # write png image
382 # print("ndimage.imread(savepath)", ndimage.imread(savepath).shape)
383 # print( ndimage.imread(savepath) )
384 # print("np.expand_dims(ndimage.imread(savepath), 0)", np.expand_dims(ndimage.imread(savepath), 0).shape)
385 # print(np.expand_dims(ndimage.imread(savepath), 0))
389 return img, dicimg, minmax(epimask)
393 def topngs(inputdir, outdir):
395 inputdir : directory which contains directly dicom files
397 files = [f for f in os.listdir(inputdir)]
400 topng( inputdir + f, join(outdir, f) )
402 if __name__ == '__main__':
403 # topngs( INPUT_DIR, join(OUT_DIR, INPUT_DIR.split('/')[-2]) )
404 readpng(OUT_DIR+'aug_circle.png')
405 # topng(INPUT_DIR+'Image00003', OUT_DIR + INPUT_DIR.split('/')[-2] +'-Image00003', epimask="/Users/user/Desktop/Master/Stage/Data/json/json_GT/0_Case_0002/contours/1.2.3.4.5.6/31/-85.9968/Epicardic.json")