Skip to content

Commit 1dd3913

Browse files
Merge branch 'Development' into main
2 parents 13b4ec7 + 1f00c8e commit 1dd3913

15 files changed

+414
-717
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,5 @@ dmypy.json
129129

130130
projects/
131131
lib/remoteFunctions/
132+
lib/blobDetection/
132133
config.yml

config.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
debug: true
1+
debug: false
2+
exportedSaDir: /home/pepv/Practiques/Segm/MRIs/Tracked
23
projectDir: /home/pepv/Practiques/Segm/Software/sa_poly2pixel/projects
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
###############################
2+
### File Naming Convention ###
3+
###############################
4+
5+
File names follow the following structure:
6+
7+
pppp-ssss_cccc.bmp
8+
9+
- pppp: MRI identificator
10+
- ssss: Slice identificator
11+
- cccc: Class (muscle) identificator
12+
13+
Use the slice identificator to locate the error in the MRI stack
14+
15+
###############################
16+
#### Error types ####
17+
###############################
18+
19+
######### repeated_class ########
20+
21+
We assume that only one instance of a class (muscle) can exist in a slice.
22+
When more than one instance of a class is found in a slice, the program
23+
raises an error flag.
24+
25+
== False Positives ==
26+
Ocasionally, some pixels of the mask might be isolated from the rest.This
27+
happens on fine detailed selections. The program is able to detect most
28+
false positives, and marks them with the flag '[FALSE POSITIVE?]'.
29+
30+
######### incoherence_class ########
31+
32+
We assume that the classes (muscles) are continous between slices.
33+
For example, given 6 consecutive slices (a,b,c,d,e,f):
34+
35+
- If a class is present in slices 'a,b,c,e' and 'f' then it should also
36+
be in slice 'd'. If this is not the case, the program raises an error
37+
and marks it with the '[MISSING]' flag.
38+
39+
- If the slices 'a,b,c,e' and 'f' are empty, then slice 'd' should also
40+
be. If this is not the case, the program raises an error and marks it
41+
with the '[UNEXPECTED]' flag.
42+
43+
== False Positives ==
44+
There are plenty of special cases where the program could raise a false
45+
positive. The program is not cappable of recognising any of them, thus
46+
making necessary some manual checking.
47+
48+
######### displacement_class ########
49+
50+
We assume that classes (muscles) between slices are spatially continous.
51+
For example, assume we have a class 'C' present in 2 continous slices, 'a'
52+
and 'b'. The position of 'C' will be similar in both slices, then some of
53+
the pixels of 'a' that are part of 'C' will remain in 'b':
54+
55+
a b
56+
· · · · · · · · · · · · · · · · · ·
57+
· · C C C C C · · · · · C C C · · ·
58+
· C C C C C C C · · · C C C C C · ·
59+
· C C C C C C C · · · C C C C C · ·
60+
· C C C C C C · · · · C C C C · · ·
61+
· C C C C C · · · · · · C C · · · ·
62+
· · C C C · · · · · · · · · · · · ·
63+
· · · · · · · · · · · · · · · · · ·
64+
65+
If less than 5% of the pixels are shared, the program raises an error.
66+
The error will be followed with the amount of shared pixels in the from
67+
'[x%]'.
68+
69+
== False Positives ==
70+
71+
The program will raise LOTS of false positives. The program is not
72+
cappable of recognising any of them, thus making necessary some manual
73+
checking.
74+
75+
76+
###############################
77+
#### Error images coloring ####
78+
###############################
79+
80+
######### repeated_img ########
81+
82+
- Yellow: Indicates the repeated muscles
83+
- Red: Indicates the region that caused a (possible) false positive
84+
85+
######### incoherence_img ########
86+
87+
(If there is an unexpected muscle)
88+
- Yellow: Indicates an unexpected muscle
89+
90+
(If there is a missing muscle)
91+
- Red: Indicates the position of the muscle in the previous slice
92+
- Blue: Indicates the position of the muscle in the next slice
93+
94+
######### displacement_img ########
95+
96+
- Red: Indicates the position of the muscle in the previous slice
97+
- Blue: Indicates the position of the muscle in the current slice
98+
- Green: Indicates the overlapping pixels of the muscles in the previous
99+
and current slices
100+
101+
Images also display the percentage of the current mask that overlaps with
102+
the last mask.

lib/analyseProject_functions/analyseProject_CheckErrors.py

+119-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
1-
import os, sys, cv2
1+
import os, sys, cv2, shutil
22
import numpy as np
33
from PIL import Image, ImageDraw
4+
from fpdf import FPDF
45

56
from lib.aux import *
67
from lib.showSequence import *
78
from lib.aux import *
89

910

1011
def ap_checkErrors(prj):
12+
13+
if not os.path.exists(f'projects/{prj.name}/logs'):
14+
os.makedirs(f'projects/{prj.name}/logs')
15+
16+
if not os.path.exists(f'projects/{prj.name}/logs/READ_ME.pdf'):
17+
#shutil.copyfile('lib/analyseProject_functions/CheckErrors_Documentation.txt', f'projects/{prj.name}/logs/READ_ME.txt')
18+
19+
# save FPDF() class into
20+
# a variable pdf
21+
pdf = FPDF()
22+
23+
# Add a page
24+
pdf.add_page()
25+
26+
# set style and size of font
27+
# that you want in the pdf
28+
pdf.set_font('Courier', size = 11)
29+
30+
# open the text file in read mode
31+
f = open('lib/analyseProject_functions/CheckErrors_Documentation.txt', 'r')
32+
33+
# insert the texts in pdf
34+
for x in f:
35+
pdf.cell(w=0, h=5, txt = x, ln = 1, align = 'L')
36+
37+
# save the pdf with name .pdf
38+
pdf.output(f'projects/{prj.name}/logs/READ_ME.pdf')
39+
1140
while True:
1241
os.system("clear")
1342
printHeader()
@@ -19,6 +48,7 @@ def ap_checkErrors(prj):
1948
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2049
┃ 1 : Search class errors ┃
2150
┃ 2 : Search incoherence errors ┃
51+
┃ 3 : Search displacement errors ┃
2252
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
2353
2454
0 : Exit""")
@@ -36,16 +66,23 @@ def ap_checkErrors(prj):
3666
if not os.path.exists(f'projects/{prj.name}/logs/repeated_img'):
3767
os.makedirs(f'projects/{prj.name}/logs/repeated_img')
3868

69+
dirs = sorted(os.listdir(f'projects/{prj.name}/individual'))
70+
files_aux= sorted(os.listdir(f'projects/{prj.name}/individual/{dirs[0]}'))
71+
printProgressBar(0, len(dirs)*len(files_aux), prefix = 'Analysing individual masks:', suffix = 'Complete', length = 50)
72+
3973
with open(f'projects/{prj.name}/logs/repeated_class.txt', 'w') as log:
40-
for cls in sorted(os.listdir(f'projects/{prj.name}/individual')):
74+
75+
for i,cls in enumerate(dirs):
76+
4177
error_flag = False
4278

43-
for i,file in enumerate(sorted(os.listdir(f'projects/{prj.name}/individual/{cls}'))):
79+
files= sorted(os.listdir(f'projects/{prj.name}/individual/{cls}'))
80+
for j,file in enumerate(files):
4481
img = cv2.imread(f'projects/{prj.name}/individual/{cls}/{file}',cv2.IMREAD_GRAYSCALE)
4582
contours, hierarchy = cv2.findContours(img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
4683

4784
if len(contours) > 1:
48-
print(f'{bcolors.FAIL}{cls} ## {file} ## {len(contours)} ## {bcolors.ENDC}')
85+
print(f'\r{bcolors.FAIL}{cls} ## {file} ## {len(contours)}{bcolors.ENDC}{"".join([*(" " for k in range(68))])}')
4986

5087
if not error_flag:
5188
log.write(f'### {prj.classes[int(cls)-1].name} ({cls}) ###\n')
@@ -74,9 +111,12 @@ def ap_checkErrors(prj):
74111
cv2.imwrite(f'projects/{prj.name}/logs/repeated_img/{file}', result)
75112

76113
log.write('\n')
77-
if i+1 == len(sorted(os.listdir(f'projects/{prj.name}/individual/{cls}'))) and error_flag: log.write('\n')
78114

79-
input('Continue')
115+
printProgressBar(i*len(files) + j + 1, len(dirs)*len(files), prefix = 'Analysing individual masks:', suffix = 'Complete', length = 50)
116+
117+
if j+1 == len(sorted(os.listdir(f'projects/{prj.name}/individual/{cls}'))) and error_flag: log.write('\n')
118+
119+
input('\nContinue')
80120

81121
elif choice == '2':
82122

@@ -181,5 +221,78 @@ def ap_checkErrors(prj):
181221

182222
input('\nContinue')
183223

224+
elif choice == '3':
225+
226+
if not os.path.exists(f'projects/{prj.name}/logs/displacement_img'):
227+
os.makedirs(f'projects/{prj.name}/logs/displacement_img')
228+
229+
with open(f'projects/{prj.name}/logs/displacement_class.txt', 'w') as log:
230+
dirs = sorted(os.listdir(f'projects/{prj.name}/individual'))
231+
232+
files_aux= sorted(os.listdir(f'projects/{prj.name}/individual/{dirs[0]}'))
233+
printProgressBar(0, len(dirs)*len(files_aux), prefix = 'Analysing individual masks:', suffix = 'Complete', length = 50)
234+
235+
for i,cls in enumerate(dirs):
236+
error_flag = False
237+
238+
files= sorted(os.listdir(f'projects/{prj.name}/individual/{cls}'))
239+
for j,file in enumerate(files):
240+
241+
pre_img = cv2.imread(f'projects/{prj.name}/individual/{cls}/{files[j-1]}',cv2.IMREAD_GRAYSCALE)
242+
243+
current_img = cv2.imread(f'projects/{prj.name}/individual/{cls}/{file}',cv2.IMREAD_GRAYSCALE)
244+
245+
contours_pre_img, hierarchy = cv2.findContours(pre_img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
246+
contours_current_img, hierarchy = cv2.findContours(current_img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
247+
248+
if j==0 or len(contours_pre_img) == 0 or len(contours_current_img) == 0:
249+
printProgressBar(i*len(files) + j + 1, len(dirs)*len(files), prefix = 'Analysing individual masks:', suffix = 'Complete', length = 50)
250+
continue
251+
252+
else:
253+
overlap = cv2.bitwise_and(pre_img,current_img)
254+
contours_overlap, hierarchy = cv2.findContours(overlap,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
255+
256+
if len(contours_overlap)==0: overlap_percentage = 0.00
257+
else:
258+
area_overlap = cv2.countNonZero(overlap)
259+
area_current_img = cv2.countNonZero(current_img)
260+
261+
overlap_percentage = round((area_overlap / area_current_img) * 100, 2)
262+
263+
if overlap_percentage < 5 and error_flag: error_flag = not error_flag
264+
elif overlap_percentage < 5 and not error_flag:
265+
266+
print(f'\r{bcolors.FAIL}{cls} ## {file} ## {overlap_percentage}%{bcolors.ENDC}{"".join([*(" " for k in range(69))])}')
267+
268+
im = cv2.imread(f'projects/{prj.name}/img/{file.split("_")[0]}.jpg',cv2.IMREAD_GRAYSCALE)
269+
im_rgb = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
270+
271+
im_rgb_copy = im_rgb.copy()
272+
273+
for cont in contours_pre_img: im_rgb_copy = cv2.drawContours(im_rgb_copy, [cont], -1, (0, 0, 255), -1)
274+
for cont in contours_current_img: im_rgb_copy = cv2.drawContours(im_rgb_copy, [cont], -1, (255, 0, 0), -1)
275+
for cont in contours_overlap: im_rgb_copy = cv2.drawContours(im_rgb_copy, [cont], -1, (0, 255, 0), -1)
276+
277+
alpha = 0.8
278+
filled = cv2.addWeighted(im_rgb, alpha, im_rgb_copy, 1-alpha, 0)
279+
280+
for cont in contours_pre_img: filled = cv2.drawContours(filled, [cont], -1, (0, 0, 255), 0)
281+
for cont in contours_current_img: filled = cv2.drawContours(filled, [cont], -1, (255, 0, 0), 0)
282+
for cont in contours_overlap: filled = cv2.drawContours(filled, [cont], -1, (0, 255, 0), 0)
283+
284+
image = cv2.putText(filled, f'{overlap_percentage}%', (40, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
285+
286+
log.write(f'\t{file}\t[{overlap_percentage}%]')
287+
288+
cv2.imwrite(f'projects/{prj.name}/logs/displacement_img/{file}', filled)
289+
290+
log.write('\n')
291+
292+
printProgressBar(i*len(files) + j + 1, len(dirs)*len(files), prefix = 'Analysing individual masks:', suffix = 'Complete', length = 50)
293+
294+
295+
input('\nContinue')
296+
184297
else:
185298
input(f'\n{bcolors.FAIL}Unexpected option, press a key to continue...{bcolors.ENDC}')

lib/analyseProject_functions/analyseProject_EditProject.py

+28-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import os, sys, cv2
2-
import numpy as np
3-
from PIL import Image, ImageDraw
1+
import os, json
42

53
from lib.aux import *
64
from lib.showSequence import *
@@ -16,9 +14,10 @@ def ap_editProject(prj):
1614
print("""
1715
\nChoose an option:
1816
19-
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
20-
┃ 1 : Shift class id ┃
21-
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
17+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
18+
┃ 1 : Shift class id (imported) ┃
19+
┃ 2 : Shift class id (unimported) ┃
20+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
2221
2322
0 : Exit""")
2423

@@ -31,6 +30,7 @@ def ap_editProject(prj):
3130
if choice == '0':
3231
return
3332

33+
## Shifts the class Id of the polygons of the poly2pixel project
3434
elif choice == '1':
3535
shift = int(input('Shift classes id by:'))
3636

@@ -56,5 +56,27 @@ def ap_editProject(prj):
5656

5757
input(f'Continue...')
5858

59+
## Shifts the class Id of the annotations files of the exported SA project
60+
elif choice == '2':
61+
62+
shift = int(input('Shift classes id by:'))
63+
64+
if prj.classes[0].id + shift < 1:
65+
input(f'The resulting first class will have an index of {str(prj.classes[0].id + shift)}. That is not possible.\nContinue...')
66+
else:
67+
annotation_files = sorted(os.listdir(f'{prj.projectDir}/annotations'))
68+
69+
printProgressBar(0, len(annotation_files), prefix = 'Shifting id:', suffix = 'Complete', length = 50)
70+
71+
# For each annotation file, apply shift to id and save
72+
for i,file in enumerate(annotation_files):
73+
with open(f'{prj.projectDir}/annotations/{file}') as f: data = json.load(f)
74+
for d in data['instances']: d["classId"] += shift
75+
with open(f'{prj.projectDir}/annotations/{file}', "w+") as f: f.write(json.dumps(data))
76+
77+
printProgressBar(i+1, len(annotation_files), prefix = 'Shifting id:', suffix = 'Complete', length = 50)
78+
79+
input(f'\nContinue...')
80+
5981
else:
6082
input(f'\n{bcolors.FAIL}Unexpected option, press a key to continue...{bcolors.ENDC}')

lib/augmentateData.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ def augmentateData(prj, config):
8585
printHeader()
8686
printLoadedProject(prj)
8787

88-
print(f'{bcolors.BOLD}Nº of images:{bcolors.ENDC} {len(prj.images)}')
89-
print(f'{bcolors.BOLD}Permutations:{bcolors.ENDC} {permutations}')
88+
print(f'{bcolors.BOLD} Nº of input images:{bcolors.ENDC} {len(prj.images)}')
89+
print(f'{bcolors.BOLD} Permutations:{bcolors.ENDC} {permutations}')
90+
print(f'{bcolors.BOLD}Nº of output images:{bcolors.ENDC} {len(prj.images) * permutations}')
9091

9192
print("""
9293
\n

0 commit comments

Comments
 (0)