Skip to content

Commit a60e935

Browse files
committed
Initial commit
Signed-off-by: jeonghwa yoo <[email protected]>
1 parent 02988b0 commit a60e935

File tree

4 files changed

+393
-2
lines changed

4 files changed

+393
-2
lines changed

MFCM.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#from skimage import data
2+
#from scipy import signal
3+
import sys
4+
import os
5+
from os import listdir
6+
from os.path import isfile, join
7+
import cv2
8+
import numpy as np
9+
import matplotlib.pyplot as plt
10+
import time
11+
12+
13+
def make_spatial_distance_window(windowSize, center_coordi):
14+
center_y = center_coordi[1]
15+
center_x = center_coordi[0]
16+
17+
distance_window = np.zeros(windowSize)
18+
for y in range(windowSize[1]):
19+
for x in range(windowSize[0]):
20+
distance_window[y][x] = np.sqrt((y-center_y)**2+(x-center_x)**2) # Euclidean distance
21+
return distance_window
22+
23+
def find_reliable_set(window, windowSize):
24+
median = np.median(window)
25+
median_window = np.full((windowSize[1],windowSize[0]), median) # shape:(windowSize[1],windowSize[0]), value=median
26+
27+
difference_nei_med = window-median_window # neighbour - med
28+
29+
deviation = np.sqrt((np.mean(pow(difference_nei_med,2))))
30+
31+
reliable_mat = (difference_nei_med<=deviation).astype(int) # 1: reliable, 0: unreliable
32+
return reliable_mat
33+
34+
35+
def sliding_window(image, neighbour_effect, stepSize, windowSize):
36+
'''slide a window across the image'''
37+
38+
center_coordi = (int(windowSize[1]/2),int(windowSize[0]/2)) # center coordination of the window
39+
distance_window = make_spatial_distance_window(windowSize, center_coordi) # Euclidean distance matrix between center coordination and neighbor cordination
40+
41+
filtered_image = []
42+
43+
start = time.time()
44+
for y in range(0, image.shape[0]-int(windowSize[0]/2), stepSize):
45+
for x in range(0, image.shape[1]-int(windowSize[1]/2), stepSize):
46+
cur_window = image[y:y + windowSize[1], x:x + windowSize[0]] # neighbour window
47+
48+
if cur_window.shape[0]<windowSize[0] or cur_window.shape[1]<windowSize[1]:
49+
continue
50+
51+
if np.count_nonzero(cur_window)==0: # Pass if all values in window are zero
52+
filtered_image.append(0)
53+
continue
54+
55+
#--------------Find reliable set matrix--------------
56+
reliable_mat = find_reliable_set(cur_window, windowSize)
57+
58+
#--------------Weighting coefficients about window--------------
59+
center_window = np.full((windowSize[1],windowSize[0]), cur_window[center_coordi[1],center_coordi[0]])
60+
difference_nei_cent = cur_window - center_window
61+
62+
gray_deviation = np.sqrt(np.sum(pow(difference_nei_cent*reliable_mat,2))/np.count_nonzero(reliable_mat))
63+
if gray_deviation==0: gray_deviation = sys.float_info.epsilon
64+
65+
coeff_gray = np.exp(-(pow(difference_nei_cent,2)*reliable_mat)/(neighbour_effect*gray_deviation))
66+
coeff_distance = np.exp(-distance_window*reliable_mat)
67+
68+
coeff_window = coeff_gray*coeff_distance
69+
70+
#--------------New intensity value of the center point in the window--------------
71+
new_gray = np.sum(coeff_window*cur_window)/np.sum(coeff_window)
72+
filtered_image.append(round(new_gray))
73+
74+
filtered_image = np.array(filtered_image)
75+
print("Time :", time.time() - start)
76+
return filtered_image
77+
78+
79+
class MFCM():
80+
def __init__(self, image, n_clusters, m, neighbour_effect, epsilon, max_iter, kernel_size):
81+
'''Modified Fuzzy C-means clustering
82+
83+
<image>: 2D array, grey scale image.
84+
<n_clusters>: int, number of clusters/segments to create.
85+
<m>: float > 1, fuzziness parameter. A large <m> results in smaller
86+
membership values and fuzzier clusters. Commonly set to 2.
87+
<kernel_size>: int >= 1, size of neighborhood.
88+
<neighbour_effect>: float, parameter which controls the influence extent of neighbouring pixels.
89+
<max_iter>: int, max number of iterations.
90+
'''
91+
92+
#-------------------Check inputs-------------------
93+
if np.ndim(image) != 2:
94+
raise Exception("<image> needs to be 2D (gray scale image).")
95+
if n_clusters <= 0 or n_clusters != int(n_clusters):
96+
raise Exception("<n_clusters> needs to be positive integer.")
97+
if m < 1:
98+
raise Exception("<m> needs to be >= 1.")
99+
if kernel_size <=0 or kernel_size != int(kernel_size):
100+
raise Exception("<kernel_size> needs to be positive integer.")
101+
if epsilon <= 0:
102+
raise Exception("<epsilon> needs to be > 0")
103+
104+
self.image = image
105+
self.n_clusters = n_clusters
106+
self.m = m
107+
self.neighbour_effect = neighbour_effect
108+
self.epsilon = epsilon
109+
self.max_iter = max_iter
110+
self.kernel_size = kernel_size
111+
112+
self.shape = image.shape # image shape
113+
self.X = image.flatten().astype('float') # flatted image shape: (number of pixels,1)
114+
self.numPixels = image.size
115+
116+
def initial_U(self):
117+
U=np.zeros((self.num_gray, self.n_clusters))
118+
idx = np.arange(self.num_gray)
119+
for ii in range(self.n_clusters):
120+
idxii = idx%self.n_clusters==ii
121+
U[idxii,ii] = 1
122+
return U
123+
124+
def update_U(self):
125+
'''Compute weights'''
126+
idx = np.arange(self.num_gray)
127+
c_mesh,idx_mesh = np.meshgrid(self.C,idx)
128+
power = -2./(self.m-1)
129+
numerator = abs(idx_mesh-c_mesh)**power
130+
denominator = np.sum(abs(idx_mesh-c_mesh)**power,axis=1)
131+
return numerator/denominator[:,None]
132+
133+
def update_C(self):
134+
'''Compute centroid of clusters'''
135+
idx = np.arange(self.num_gray)
136+
idx_reshape = idx.reshape(len(idx),1)
137+
numerator = np.sum(self.histogram*idx_reshape*pow(self.U,self.m),axis=0)
138+
denominator = np.sum(self.histogram*pow(self.U,self.m),axis=0)
139+
return numerator/denominator
140+
141+
def get_filtered_image(self):
142+
# Create padding image
143+
print("Getting filtered image..(This process can be time consuming.)")
144+
pad_size_y = int(self.kernel_size/2)
145+
pad_size_x = int(self.kernel_size/2)
146+
pad_img = cv2.copyMakeBorder(self.image, pad_size_y, pad_size_y, pad_size_x, pad_size_x, cv2.BORDER_CONSTANT, value=0 ) # zero padding
147+
148+
filtered_image = sliding_window(pad_img, self.neighbour_effect, stepSize=1, windowSize=(self.kernel_size,self.kernel_size))
149+
dtype = self.image.dtype
150+
self.filtered_image = filtered_image.reshape(self.shape).astype(dtype)
151+
152+
def calculate_histogram(self):
153+
hist = cv2.calcHist([self.filtered_image],[0],None,[256],[0,256])
154+
self.num_gray = len(hist)
155+
self.histogram = hist
156+
157+
def form_clusters(self):
158+
self.get_filtered_image()
159+
self.calculate_histogram()
160+
161+
'''Iterative training'''
162+
d = 100
163+
self.U = self.initial_U()
164+
if self.max_iter != -1:
165+
i = 0
166+
while True:
167+
self.C = self.update_C()
168+
old_u = np.copy(self.U)
169+
self.U = self.update_U()
170+
d = np.sum(abs(self.U - old_u))
171+
print("Iteration %d : cost = %f" %(i, d))
172+
173+
if d < self.epsilon or i > self.max_iter:
174+
break
175+
i+=1
176+
else:
177+
i = 0
178+
while d > self.epsilon:
179+
self.C = self.update_C()
180+
old_u = np.copy(self.U)
181+
self.U = self.update_U()
182+
d = np.sum(abs(self.U - old_u))
183+
print("Iteration %d : cost = %f" %(i, d))
184+
185+
if d < self.epsilon or i > self.max_iter:
186+
break
187+
i+=1
188+
self.segmentImage()
189+
190+
191+
def deFuzzify(self):
192+
return np.argmax(self.U, axis = 1)
193+
194+
def segmentImage(self):
195+
'''Segment image based on max weights'''
196+
197+
result = self.deFuzzify()
198+
199+
self.result = np.array(self.filtered_image, copy=True)
200+
for i in range(len(result)):
201+
self.result[self.result==i]=result[i]
202+
203+
self.result = self.result.reshape(self.shape).astype('int')
204+
205+
return self.result
206+
207+
208+
def main(DIRECTORY, args):
209+
IMG_PATH = DIRECTORY['IMG_PATH']
210+
OUTPUT_FILT_IMG_PATH = DIRECTORY['OUTPUT_FILT_IMG_PATH']
211+
OUTPUT_PLOT_PATH = DIRECTORY['OUTPUT_PLOT_PATH']
212+
213+
IS_PLOT = args.plot_show
214+
IS_SAVE = args.plot_save
215+
216+
files = [f for f in listdir(IMG_PATH) if isfile(join(IMG_PATH, f))] # read all files in IMG_PATH
217+
218+
for file in files:
219+
target_img_path = os.path.join(IMG_PATH,file)
220+
try:
221+
#--------------Lord image file--------------
222+
img= cv2.imread(target_img_path, cv2.IMREAD_GRAYSCALE) # cf. 8bit image-> 0~255
223+
224+
#--------------Clustering--------------
225+
cluster = MFCM(img, n_clusters=args.num_cluster, m=args.fuzziness, neighbour_effect=args.neighbour_effect, epsilon=args.epsilon, max_iter=args.max_iteration, kernel_size=args.win_size)
226+
cluster.form_clusters()
227+
result=cluster.result
228+
229+
#-------------------Plot and save result------------------------
230+
if IS_PLOT:
231+
232+
fig=plt.figure(figsize=(12,8),dpi=100)
233+
234+
ax1=fig.add_subplot(1,2,1)
235+
ax1.imshow(img,cmap='gray')
236+
ax1.set_title('image')
237+
238+
ax2=fig.add_subplot(1,2,2)
239+
ax2.imshow(result)
240+
ax2.set_title('segmentation')
241+
242+
plt.show(block=False)
243+
plt.close()
244+
245+
if IS_SAVE:
246+
247+
seg_result_path = os.path.join(OUTPUT_PLOT_PATH,"%s.png"%(os.path.splitext(file)[0]))
248+
plt.imshow(result)
249+
plt.savefig(seg_result_path, dpi=300)
250+
plt.close()
251+
252+
filtered_img_path = os.path.join(OUTPUT_FILT_IMG_PATH,"%s.png"%(os.path.splitext(file)[0]))
253+
plt.imshow(cluster.filtered_image,cmap='gray')
254+
plt.savefig(filtered_img_path, dpi=300)
255+
plt.close()
256+
257+
except IOError:
258+
print("Error")
259+
260+
if __name__ == '__main__':
261+
main()

README.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,79 @@
1-
# MFCM_segmentation
2-
Tissue Segmentation Using Modified Fuzzy C-Means Algorithm on Mammography (Image segmentation)
1+
2+
# MFCM_segmentation (Python 3.5)
3+
### Tissue Segmentation Using Modified Fuzzy C-Means Algorithm on Mammography (Image segmentation)
4+
5+
This code uses modified fuzzy c-means algorithm (MFCM) to do tissue segmentation on mammography. This code is a implementation of The paper:
6+
7+
[1] Z. Chen and R. Zwiggelaar, 2010. 'A Modified Fuzzy C Means Algorithm for Breast Tissue Density Segmentation in Mammograms’, IEEE/Information Technology and Applications in Biomedicine (ITAB) (https://ieeexplore.ieee.org/document/5687751)
8+
9+
## How to run?
10+
11+
### Run on mini data as default option
12+
You just run <u>main.py<\u> in editor or enter <u>python main.py<\u> in command prompt.
13+
(In <i>img<\i> directory, there are 2 images for the test. These images are part of mini-MIAS database. (http://peipa.essex.ac.uk/info/mias.html) )
14+
15+
### User mode
16+
If you want to replace the mini data with your own data, put your images to img directory or edit path for your direrectory in <u>main.py<\u>.
17+
If you change parameters of your experiment, you can change parameters by changing the default value of the argument in <u>main.py<\u> or you can you the command line in command prompt.
18+
- You can see all the adjustable parameters and usage. </pre> python main.py --help</pre>
19+
(For example, if you want to cluster with 5 clusters, enter this command in your command prompt. </u>python main.py -c 5<\u>)
20+
21+
## Results
22+
23+
<pre>
24+
Getting filtered image..(This process can be time consuming.)
25+
Time : 53.16970419883728
26+
Iteration 0 : cost = 383.503160
27+
Iteration 1 : cost = 130.096540
28+
Iteration 2 : cost = 52.057745
29+
Iteration 3 : cost = 36.463114
30+
Iteration 4 : cost = 33.625992
31+
Iteration 5 : cost = 40.136341
32+
Iteration 6 : cost = 42.462534
33+
Iteration 7 : cost = 29.869172
34+
Iteration 8 : cost = 16.829851
35+
Iteration 9 : cost = 10.073875
36+
Iteration 10 : cost = 6.756716
37+
Iteration 11 : cost = 4.957472
38+
Iteration 12 : cost = 3.912657
39+
Iteration 13 : cost = 3.158448
40+
Iteration 14 : cost = 2.573333
41+
Iteration 15 : cost = 2.098461
42+
Iteration 16 : cost = 1.710719
43+
Iteration 17 : cost = 1.394245
44+
Iteration 18 : cost = 1.136231
45+
Iteration 19 : cost = 0.925994
46+
Iteration 20 : cost = 0.754842
47+
Iteration 21 : cost = 0.615388
48+
Iteration 22 : cost = 0.501762
49+
Iteration 23 : cost = 0.409169
50+
Iteration 24 : cost = 0.333715
51+
Iteration 25 : cost = 0.272221
52+
Iteration 26 : cost = 0.222079
53+
Iteration 27 : cost = 0.181186
54+
Iteration 28 : cost = 0.147834
55+
Iteration 29 : cost = 0.120628
56+
Iteration 30 : cost = 0.098434
57+
Iteration 31 : cost = 0.080326
58+
Iteration 32 : cost = 0.065552
59+
Iteration 33 : cost = 0.053496
60+
Iteration 34 : cost = 0.043659
61+
</pre>
62+
63+
|Image| Result |
64+
|:---:|:---: |
65+
|mdb321.jpg|<img src = 'result.png'>|
66+
|mdb322.jpg|<img src = 'result2.png'>|
67+
68+
69+
## Troubleshooting:
70+
1. If it is not an 8-bit image, the code needs to be modified.
71+
2. When there is a problem with the environment, you can try this command line in your command prompt. <pre> pip install -r requirements.txt </pre>
72+
73+
## References
74+
* [1] [Z. Chen and R. Zwiggelaar "A Modified Fuzzy C Means Algorithm for Breast Tissue Density Segmentation in Mammograms." IEEE/Information Technology and Applications in Biomedicine (ITAB) 2010.](https://ieeexplore.ieee.org/document/5687751)
75+
* [2] [J. Song and Z. Zhang "A Modified Robust FCM Model with Spatial Constraints for Brain MR Image Segmentation." Information 2019.](https://www.researchgate.net/publication/331278874_A_Modified_Robust_FCM_Model_with_Spatial_Constraints_for_Brain_MR_Image_Segmentation)
76+
* https://github.com/ab93/SIFCM
77+
78+
## TODO:
79+
- [ ] It takes a lot of time to get the filtered image. This problem can be solved by using numpy.lib.stride_tricks.as_strided.

main.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import os
2+
import argparse
3+
4+
import MFCM
5+
6+
WORKSPACE = os.path.split(os.getcwd())[-1] # current workspace
7+
IMG_PATH = os.path.join(os.getcwd(),"img") # path for input image directory
8+
OUTPUT_PATH = os.path.join(os.getcwd(),"output") # path for output directory
9+
OUTPUT_PLOT_PATH = os.path.join(OUTPUT_PATH,'segmentation') # path for output (plot directory)
10+
OUTPUT_FILT_IMG_PATH = os.path.join(OUTPUT_PATH,'filtered_img') # path for output (filtered image directory)
11+
12+
def get_args():
13+
parser = argparse.ArgumentParser(description="Tissue Segmentation Using Modified Fuzzy C-Means Algorithm on Mammography.")
14+
15+
#-----------------Fundamental parameter-----------------
16+
parser.add_argument('-c', '--num_cluster', default='4', type=int,
17+
help="Number of cluster")
18+
parser.add_argument('-m', '--fuzziness', default='2', type=int,
19+
help="fuzziness degree")
20+
parser.add_argument('-i', '--max_iteration', default='100', type=int,
21+
help="max number of iterations.")
22+
parser.add_argument('-e', '--epsilon', default='0.05', type=float,
23+
help="threshold to check convergence.")
24+
#-----------------User parameter-----------------
25+
parser.add_argument('--plot_show', default=1, choices=[0,1],
26+
help="Show plot about result")
27+
parser.add_argument('--plot_save', default=1, choices=[0,1],
28+
help="Save plot about result")
29+
#-----------------MFCM parameter-----------------
30+
parser.add_argument('-w', '--win_size', default='5', type=int,
31+
help="Window size of MFCM algorithm")
32+
parser.add_argument('-n', '--neighbour_effect', default='3', type=float,
33+
help="Effect factor of the graylevel which controls the influence extent of neighbouring pixels.")
34+
35+
args = parser.parse_args()
36+
return args
37+
38+
if __name__ == '__main__':
39+
40+
args = get_args()
41+
42+
DIRECTORY = {}
43+
DIRECTORY['WORKSPACE'] = WORKSPACE
44+
DIRECTORY['IMG_PATH'] = IMG_PATH
45+
DIRECTORY['OUTPUT_PATH'] = OUTPUT_PATH
46+
DIRECTORY['OUTPUT_PLOT_PATH'] = OUTPUT_PLOT_PATH
47+
DIRECTORY['OUTPUT_FILT_IMG_PATH'] = OUTPUT_FILT_IMG_PATH
48+
49+
MFCM.main(DIRECTORY, args)

0 commit comments

Comments
 (0)