]> AND Private Git Repository - myo-class.git/blobdiff - topng.py
Logo AND Algorithmique Numérique Distribuée

Private GIT Repository
integrate distinction_word, for deployment
[myo-class.git] / topng.py
index 0000a58988c2daea60b800241cbc17080cd69436..fde569ba6509bdf19efb0a28c282d4ec4b8b1dfb 100644 (file)
--- 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")