X-Git-Url: https://bilbo.iut-bm.univ-fcomte.fr/and/gitweb/myo-class.git/blobdiff_plain/1d3fb87f139a53b55ceb52f3dc2db49ac2b0a0ac..bdfe02afe03428e59b3c85b6cae19d579d82f55c:/topng.py diff --git a/topng.py b/topng.py index 0000a58..fde569b 100644 --- a/topng.py +++ b/topng.py @@ -2,35 +2,55 @@ import cv2 import os from os.path import isdir, join from os import mkdir +from timeit import timeit import pydicom from pprint import pprint import numpy as np import pathlib +import json + +from scipy import ndimage +from scipy import misc + from decimal import Decimal as d, ROUND_HALF_UP as rhu +# locals +from helpers import drawcontours + +np.set_printoptions(edgeitems=372, linewidth=1200)# hey numpy, take advantage of my large screen + PNG16_MAX = pow(2, 16) - 1 # here if thinks the heaviest weight bit is for transparency or something not in use with dicom imgs PNG8_MAX = pow(2, 8+1) - 1 # heaviest weight bit is 8 => 2**8, but dont forget the others: the reason of +1 -INPUT_DIR = '../../Data/Images_anonymous/Case_0350/' +INPUT_DIR = '../../Data/Images_anonymous/Case_0002/' OUT_DIR = './generated/' -#os.mkdir(outdir) + +CROP_SIZE = (45, 45) # (width, height) +RED_COLOR = 100 + +def roundall(*l): + return (int(round(e)) for e in l) def ftrim(Mat): + # ---------- FTRIM ---------- # private func to trim the Matrix, but in one direction, vertically | horizontally # return the slice, don't affect the Matrix - # y | : for column, x -> : for rows + # y | : for (top to bottom), x -> : for (left to right) # v # not my fault, numpy architecture + y1 = y2 = i = 0 + while i < len(Mat):# search by top - if max(Mat[i]) > 0: + if Mat[i].max() > 0: y1 = i break i += 1 + i = len(Mat) - 1 while i >= 0:# search by bottom - if max(Mat[i]) > 0: + if Mat[i].max() > 0: y2 = i break i -= 1 @@ -44,6 +64,194 @@ def trim(Mat): # print('horizontal:vertical', horizontal, vertical) return Mat[horizontal, vertical] + +def getxy(file): + """ + { + 'Image00001': [{ + 'color': '#ff0000', + 'points': [ + {'x': 94.377, 'y': 137.39}, + {'x': 100.38, 'y': 139.55}, + {'x': 103.26, 'y': 142.67}, + {'x': 105.91, 'y': 147.95}, + {'x': 105.42, 'y': 152.76}, + {'x': 100.62, 'y': 156.84}, + {'x': 95.338, 'y': 159.96}, + {'x': 89.573, 'y': 158.52}, + {'x': 84.53, 'y': 153}, + {'x': 82.848, 'y': 149.15}, + {'x': 82.368, 'y': 142.91}, + {'x': 85.01, 'y': 138.11}, + {'x': 89.813, 'y': 137.39}, + {'x': 94.377, 'y': 137.39} + ] + }] + } + return [(94.377, 137.39), ...] + """ + with open(file) as jsonfile: + data = json.load(jsonfile) + # pprint(data) + + for imgXXX in data: pass # get the value of key ~= "Image00001", cause it's dynamic + + for obj in data[imgXXX]: pass # get the object that contains the points, cause it's a list + points = obj['points'] + + # print("print, ", data) + # print("imgXXX, ", imgXXX) + # print("points, ", points) + # print("imgXXX, ", obj['points']) + tmp = [np.array( (round(pt['x']), round(pt['y'])) ).astype(np.int32) for pt in + points] # extract x,y. {'x': 94.377, 'y': 137.39} => (94.377, 137.39) + return np.array(tmp, dtype=np.int32) + # return tmp + +def minmax(file): + r = getxy(file) + if r is not None: + # print(r) # log + xmin, ymin = np.min(r, axis=0) + xmax, ymax = np.max(r, axis=0) + + # print('xmax, ymax', xmax, ymax) + # print('xmin, ymin', xmin, ymin) + + return roundall(xmin, ymin, xmax, ymax) + +def crop(Mat, maskfile, size=None): + """ + size : if filled with a (45, 45), it will search the center of maskfile then crop by center Mat + """ + xmin, ymin, xmax, ymax = minmax(maskfile) + if size: + xcenter = (xmin + xmax) / 2 + ycenter = (ymin + ymax) / 2 + + # crop coords + ymin, xmin, ymax, xmax = roundall(xcenter - size[0], ycenter - size[0], + xcenter + size[1], ycenter + size[1]) + + return Mat[xmin:xmax, ymin:ymax] + +def contour(Mat, maskfiles, color, thickness=1): + # ---------- CONTOUR POLYGON ---------- + # thickness = -1 => fill it + # = 1 => just draw the contour with color + # + # return new Mat + contours = [getxy(maskfile) for maskfile in maskfiles] # list of list of coords, + # [ + # [ (3,43), (3,4) ], + # [ (33,43) ] + # ] + # print("log: contours", contours) + if contours is not None: + # print('type contours', type(contours)) + # print('contours', contours) + msk = np.zeros(Mat.shape, dtype=np.int32) # init all the mask with True... + cv2.drawContours(msk, contours, -1, True, thickness) # affect Mat, fill the polygone with False + msk = msk.astype(np.bool) + # print('msk') + # print(msk) + # print("bool", timeit(lambda:msk, number=1000)) + # print("normal", timeit(lambda:msk.astype(np.bool), number=1000)) + # Mat = cv2.cvtColor(Mat, cv2.COLOR_RGB2GRAY) # apply gray + + # Mat = drawcontours(Mat, contours) + # pass + Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector + return Mat + +def mask(Mat, maskfile, color=0, thickness=-1): + # ---------- MASK POLYGON ---------- + # thickness = -1 => fill it + # = 1 => just draw the contour with color + # + # return new Mat + contours = getxy(maskfile) + # print("log: contours", contours) + if contours is not None: + # print('type contours', type(contours)) + # print('contours', contours) + msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True... + cv2.drawContours(msk, [contours], -1, False, thickness) # affect msk, fill the polygone with False, => further, don't touch where is False + msk = msk.astype(np.bool) + # print('msk') + # print(msk) + # print("bool", timeit(lambda:msk, number=1000)) + # print("normal", timeit(lambda:msk.astype(np.bool), number=1000)) + # Mat = cv2.cvtColor(Mat, cv2.COLOR_RGB2GRAY) # apply gray + + # Mat = drawcontours(Mat, contours) + # pass + Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector + return Mat + + +def hollowmask(Mat, epifile, endofile, color=0, thickness=-1): + # ---------- MASK POLYGON ---------- + # thickness = -1 => fill it + # = 1 => just draw the contour with color + # + # return new Mat + epicontours = getxy(epifile) + endocontours = getxy(endofile) + if epicontours is not None and endocontours is not None: + msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True... + cv2.drawContours(msk, [epicontours], -1, False, thickness) # affect msk, fill the polygone with False, => further, don't touch where is False + 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 + msk = msk.astype(np.bool) + + Mat[msk] = color # each True in msk means 0 in Mat. msk is like a selector + return Mat + + +def sqrmask1(Mat, maskfile): + # ---------- SQUARE MASK 1st approach ---------- + # print( timeit( lambda:sqrmask1(img, epimask), number=1000 ) ) # 0.48110522600000005 + # return new Mat + xmin, ymin, xmax, ymax = minmax(maskfile) + # print("xmin, ymin, xmax, ymax", xmin, ymin, xmax, ymax) + + i = 0 + while i < ymin:# search by top + Mat[i].fill(0)# paint the row in black + i += 1 + + i = len(Mat) - 1 + while i > ymax:# search by bottom + Mat[i].fill(0)# paint the row in black + i -= 1 + + i = 0 + while i < xmin:# search by top + 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 + i += 1 + + i = len(Mat.T) - 1 + while i > xmax:# search by bottom + 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 + i -= 1 + + return Mat + +def sqrmask2(Mat, maskfile): + # ---------- SQUARE MASK 2nd and best approach ---------- + # print( timeit( lambda:sqrmask2(img, epimask), number=1000 ) ) # 0.3097705270000001 + # return new Mat + xmin, ymin, xmax, ymax = minmax(maskfile) + # print("xmin, ymin, xmax, ymax", xmin, ymin, xmax, ymax) + + msk = np.ones(Mat.shape, dtype=np.int32) # init all the mask with True... + msk = msk.astype(np.bool) + + msk[ymin:ymax, xmin:xmax] = False # for after, don't touch between min and max region + Mat[msk] = 0 + + return Mat + def map16(array):# can be useful in future return array * 16 @@ -67,16 +275,31 @@ def affine(Mat, ab, cd): def getdir(filepath): return '/'.join(filepath.split('/')[:-1]) + '/' -def topng(inputfile, outfile=None, overwrite=True): +def readpng(inputfile):# just a specific func to preview a "shape = (X,Y,3)" image + image = misc.imread(inputfile) + + print("image") + print("image.shape", image.shape) + + for tab in image: + for i, row in enumerate(tab): + print(row[0]*65536 + row[0]*256 + row[0], end=" " if i % image.shape[0] != 0 else "\n") + +def topng(inputfile, outfile=None, overwrite=True, verbose=False, epimask='', endomask='', centercrop=None, blackmask=False, square=False, redcontour=False): """ - return (64, 64) : the width and height + (verbose) return (64, 64) : the width and height + (not verbose) return (img, dicimg) : the image and the dicimg objects + centercrop : it's a size (45, 45), to mention I want to crop by center of epimask and by size. + blackmask : draw the outside with black + """ try: dicimg = pydicom.read_file(inputfile) # read dicom image except pydicom.errors.InvalidDicomError as e: # @TODO: log, i can't read this file return - img = trim(dicimg.pixel_array)# get image array (12bits) + # img = trim(dicimg.pixel_array)# get image array (12bits), Don't trim FOR THE MOMENT + img = dicimg.pixel_array# get image array (12bits) # test << # return img.shape # $$ COMMENT OR REMOVE THIS LINE @@ -91,12 +314,53 @@ def topng(inputfile, outfile=None, overwrite=True): # affine transfo to png 16 bits, func affects img variable maxdepth = pow(2, dicimg.BitsStored) - 1 - print('dicimg.BitsStored, PNG8_MAX', dicimg.BitsStored, PNG8_MAX) # testing.. + # print('dicimg.BitsStored, (img.min(), img.max())', dicimg.BitsStored, (img.min(), img.max())) # testing.. + if int(dicimg.BitsStored) < 12: + print("\n\n\n-----{} Bits-----\n\n\n\n".format(dicimg.BitsStored)) affine(img, (0, maxdepth), # img.min() replace 0 may be, but not sure it would be good choice (0, PNG16_MAX if maxdepth > PNG8_MAX else PNG8_MAX) ) + # test << + # imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) + # ret,thresh = cv2.threshold(img,127,255,0) + # im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) + # print("im2, contours, hierarchy", im2, contours, hierarchy) + # test >> + + # return img + + # print("log: epimask", epimask) + + if epimask: + if redcontour: + + contours = [epimask] # list of list of coords + if endomask:contours.append(endomask) + + img = contour(img, contours, color=RED_COLOR, thickness=1) + + if blackmask:# if is there a mask => apply + if square: + img = sqrmask2(img, epimask) + else: + if endomask: + img = hollowmask(img, epimask, endomask) + else: + img = mask(img, epimask) + + if centercrop: + img = crop(img, epimask, centercrop) + + + # return + # test + # if verbose: + # return img, dicimg, minmax(epimask) + # else: + # return img.shape + savepath = (outfile or inputfile) + '.png' savedir = getdir(savepath) if overwrite and not isdir( savedir ): @@ -112,10 +376,19 @@ def topng(inputfile, outfile=None, overwrite=True): # np.savetxt(savepath + '.npy', img) # test >> - cv2.imwrite(savepath, img, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # write png image + if np.count_nonzero(img) > 0: # matrix not full of zero + cv2.imwrite(savepath, img, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # write png image + + # print("ndimage.imread(savepath)", ndimage.imread(savepath).shape) + # print( ndimage.imread(savepath) ) + # print("np.expand_dims(ndimage.imread(savepath), 0)", np.expand_dims(ndimage.imread(savepath), 0).shape) + # print(np.expand_dims(ndimage.imread(savepath), 0)) # test - return img.shape + if verbose: + return img, dicimg, minmax(epimask) + else: + return img.shape def topngs(inputdir, outdir): """ @@ -127,5 +400,6 @@ def topngs(inputdir, outdir): topng( inputdir + f, join(outdir, f) ) if __name__ == '__main__': - topngs( INPUT_DIR, join(OUT_DIR, INPUT_DIR.split('/')[-2]) ) - # topng(INPUT_DIR+'Image00001', OUT_DIR + INPUT_DIR.split('/')[-2] +'-Image00001') + # topngs( INPUT_DIR, join(OUT_DIR, INPUT_DIR.split('/')[-2]) ) + readpng(OUT_DIR+'aug_circle.png') + # 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")