Skip to content

Commit ca05867

Browse files
authored
Merge pull request #76 from WeisongZhao/master
Temporal color-code and bleach correction
2 parents 3202bfc + 4c6568b commit ca05867

File tree

8 files changed

+195
-4
lines changed

8 files changed

+195
-4
lines changed

imagepy/core/util/fileio.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
def show_img(img, title):
99
if isinstance(img, list):
1010
return IPy.show_img(img, title)
11-
if img.dtype!=np.uint8 and img.ndim>2 and img.shape[2]!=3:
11+
# if img.dtype!=np.uint8 and img.ndim>2 and img.shape[2]!=3:
12+
if img.ndim>2 and img.shape[2]!=3:
1213
return IPy.show_img(img, title)
1314
if img.dtype==np.uint8 and img.ndim==3 and img.shape[2]==4:
1415
img = img[:,:,:3].copy()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
catlog = ['threshold_plg','graystairs_plg','brightcons_plg','curve_plg','-','colorbalance_plg','colorstairs_plg','-','histogram_plgs']
1+
catlog = ['threshold_plg','graystairs_plg','brightcons_plg','curve_plg','-','bleachCorrection_plg','enhanceContrast_plg','normalize_plg','-','colorbalance_plg','colorstairs_plg','-','histogram_plgs']
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Created on Sun Jan 23 11:53:00 2020
3+
@author: weisong
4+
"""
5+
from imagepy.core.engine import Simple
6+
import numpy as np
7+
from imagepy import IPy
8+
from scipy.optimize import curve_fit
9+
from skimage.exposure import histogram_matching
10+
import matplotlib.pyplot as plt
11+
import pandas as pd
12+
13+
def copy(imgs):
14+
if isinstance(imgs, list):
15+
return [np.zeros_like(imgs[0])]
16+
else: return np.zeros_like(imgs)
17+
18+
def exponential_func(t, ref, k, offset):
19+
return ref * np.exp(- k * t) + offset
20+
21+
def simple_ratio(imgs, back=0, inplace=True, out=print):
22+
if isinstance(back, int): back=imgs[back]
23+
buf = imgs if inplace else copy(imgs)
24+
z, (x, y) = len(imgs), imgs[0].shape
25+
values, k0 = np.zeros(z), back.sum()/x/y
26+
lim = 255 if imgs[0].dtype.type==np.uint8 else 65535
27+
for i in range(z):
28+
values[i] = imgs[i].sum()/x/y
29+
np.clip(imgs[i], 0, lim/(k0/values[i]), out=buf[i])
30+
np.multiply(buf[i], k0/values[i], out=buf[i], casting='unsafe')
31+
out(i, z)
32+
return buf, values, k0/values
33+
34+
def exponential_fit(imgs, inplace=True, out=print):
35+
buf = imgs if inplace else copy(imgs)
36+
z, (x, y) = len(imgs), imgs[0].shape
37+
intensity = [i.sum()/x/y for i in imgs]
38+
popt, pcov = curve_fit(exponential_func, np.arange(z), intensity)
39+
k0 = exponential_func(0, popt[0], popt[1], popt[2])
40+
rst = exponential_func(np.arange(z), popt[0], popt[1], popt[2])
41+
lim = 255 if imgs[0].dtype.type==np.uint8 else 65535
42+
for i in range(z):
43+
np.clip(imgs[i], 0, lim/(rst[i]/k0), out=buf[i])
44+
np.multiply(buf[i], rst[i]/k0, out=buf[i], casting='unsafe')
45+
out(i, z)
46+
return buf, popt, intensity, rst
47+
48+
def histogram_match(imgs, back=0, inplace=True, out=print):
49+
if isinstance(back, int): back=imgs[back]
50+
buf = imgs if inplace else copy(imgs)
51+
z, (x, y) = len(imgs), imgs[0].shape
52+
for i in range(z):
53+
buf[i] = histogram_matching.match_histograms(imgs[i], back)
54+
out(i, z)
55+
return buf
56+
57+
def plot(popt, intensity, fitresult):
58+
t = np.arange(len(intensity))
59+
plt.plot(t, intensity,'r.',label='Experiment')
60+
plt.plot(t, fitresult,'k',label=
61+
'Exponential fitted curve\n y=a*exp(-bx)+c\n a=%f\n b=%f\n c=%f'%tuple(popt))
62+
plt.title('Exponential fitted result')
63+
plt.legend()
64+
plt.show()
65+
66+
def plot_after(popt, intensity, fitresult):
67+
import wx
68+
wx.CallAfter(plot, popt, intensity, fitresult)
69+
70+
class Plugin(Simple):
71+
title = 'Bleach Correction'
72+
note = ['8-bit','16-bit','stack']
73+
para = {'method':'Simple Ratio', 'new':True}
74+
view = [(list, 'method', ['Simple Ratio','Exponential Fit','Histogram Match'],
75+
str, 'Correction Method',''),
76+
(bool, 'new', 'show new window'),
77+
('lab', 'lab', 'Correct intensity based on your current slice!')]
78+
79+
def run(self, ips, imgs, para = None):
80+
if para['method'] == 'Simple Ratio':
81+
rst, value, ratio = simple_ratio(imgs, ips.img, not para['new'], self.progress)
82+
body = pd.DataFrame({'Mean value': value, 'Ratio':ratio})
83+
IPy.show_table(body, '%s-simple ratio'%ips.title)
84+
if para['method'] == 'Exponential Fit':
85+
rst, popt, intensity, fitrst = exponential_fit(imgs, not para['new'], self.progress)
86+
plot_after(popt, intensity, fitrst)
87+
body = {'Intensity':intensity, 'Fit':fitrst}
88+
IPy.show_table(pd.DataFrame(body), '%s-exp fit'%ips.title)
89+
if para['method'] == 'Histogram Match':
90+
rst = histogram_match(imgs, ips.img, not para['new'], self.progress)
91+
if para['new']: IPy.show_img(rst, '%s-corrected'%ips.title)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
Created on Sun Jan 25 17:00:00 2020
3+
@author: weisong
4+
"""
5+
from imagepy.core.engine import Filter
6+
from skimage import exposure
7+
import numpy as np
8+
9+
class Plugin(Filter):
10+
title = 'Enhance contrast'
11+
note = ['all', 'auto_msk', 'auto_snap','preview']
12+
para = {'percentage': 0.3}
13+
view = [(float, 'percentage', (0,100), 4, 'Saturated pixels', '%')]
14+
15+
def run(self, ips, snap, img, para = None):
16+
up, down = np.percentile(snap, (0, 100 - para['percentage']))
17+
return exposure.rescale_intensity(snap, in_range=(up, down))
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Created on Sun Jan 25 9:00:00 2020
3+
@author: weisong
4+
"""
5+
from imagepy.core.engine import Simple
6+
import numpy as np
7+
from imagepy import IPy
8+
9+
class Plugin(Simple):
10+
title = 'Normalize'
11+
note = ['8-bit','16-bit','float','stack']
12+
para = {'if3d': False, 'Sb':False}
13+
view = [(bool, 'if3d', '3D stack'),
14+
(bool, 'Sb', 'Subtract background')]
15+
16+
def run(self, ips, imgs, para = None):
17+
imgs_= np.array(imgs).astype('float64').transpose(1,2,0)
18+
if para['if3d']:
19+
if para['Sb']:
20+
imgs_ -= imgs_.min()
21+
imgs_ = imgs_ / imgs_.max()
22+
else:
23+
if para['Sb']:
24+
for i in range(z):
25+
imgs_[:,:,i] -= imgs_[:,:,i].min()
26+
for i in range(z):
27+
imgs_[:,:,i] = imgs_[:,:,i] / imgs_[:,:,i].max()
28+
if imgs.dtype == np.uint8:
29+
imgs[:] = (255*imgs_).astype(imgs.dtype).transpose(2,0,1)
30+
elif imgs.dtype == np.uint16:
31+
imgs[:] = (65535*imgs_).astype(imgs.dtype).transpose(2,0,1)
32+
else:
33+
imgs[:] = (imgs_).astype(imgs.dtype).transpose(2,0,1)

imagepy/menus/Image/duplicate_plg.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@ class Duplicate(Simple):
1414
title = 'Duplicate'
1515
note = ['all']
1616

17-
para = {'name':'Undefined','stack':True}
17+
para = {'name':'Undefined','start':1,'end':2,'stack':True}
1818

1919
def load(self, ips):
20+
self.slength = len(ips.imgs)
2021
self.para['name'] = ips.title+'-copy'
22+
self.para['end'] = self.slength
2123
self.view = [(str, 'name', 'Name', '')]
2224
if ips.get_nslices()>1:
25+
self.view.append((int, 'start',
26+
(1,self.slength),0,'Start slice','1~%d'%self.slength))
27+
self.view.append((int, 'end',
28+
(1,self.slength),0,'End slice','1~%d'%self.slength))
2329
self.view.append((bool, 'stack', 'duplicate stack'))
2430
return True
2531
#process
@@ -42,6 +48,7 @@ def run(self, ips, imgs, para = None):
4248
ipsd.backimg = ips.backimg[sr, sc]
4349
'''
4450
elif ips.get_nslices()>1 and self.para['stack']:
51+
imgs=imgs[para['start']-1: para['end']]
4552
if ips.roi == None:
4653
if ips.is3d:imgs=imgs.copy()
4754
else:imgs = [i.copy() for i in imgs]

imagepy/menus/Plugins/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
catlog = ['New', 'Macros', 'Manager', '-', 'Install', 'Contribute', 'update_plg', '-', 'StackReg', 'Games', 'screencap_plg']
1+
catlog = ['New', 'Macros', 'Manager', '-', 'Install', 'Contribute', 'update_plg', '-', 'temporal_plg','StackReg', 'Games', 'screencap_plg']
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
Created on Sun Jan 22 12:56:00 2020
3+
@author: weisong
4+
"""
5+
from imagepy.core.engine import Simple
6+
import numpy as np
7+
from imagepy.core.manager import ColorManager
8+
from imagepy import IPy
9+
10+
def color_code(img, lut):
11+
idx = np.linspace(0,255,len(img)).astype(int)
12+
cs = lut[idx].astype(np.uint32)
13+
buf = np.zeros(img[0].shape+(3,), dtype=np.uint32)
14+
for im, c in zip(img, cs):
15+
buf += im.reshape(im.shape+(-1,)) * c
16+
k = 255/buf.max(axis=(0,1)).reshape((1,1,3))
17+
return (buf * k).astype(np.uint8)
18+
19+
class Plugin(Simple):
20+
title = 'Temporal color-code'
21+
note = ['all', 'stack']
22+
para = {'LUT':'Jet',
23+
'Start image':1,
24+
'End image': 2,
25+
'Creatbar':True}
26+
def load(self, ips):
27+
self.slength = len(ips.imgs)
28+
self.para['End image'] = self.slength
29+
self.view = [(list, 'LUT', list(ColorManager.luts.keys()), str, 'LUT',''),
30+
(int, 'Start image', (1,self.slength),0,'Start image','1~%d'%self.slength),
31+
(int, 'End image', (2,self.slength),0,'End image','start~%d'%self.slength),
32+
(bool, 'Creatbar', 'Creat time color scale bar')]
33+
return True
34+
35+
def run(self, ips, imgs, para = None):
36+
cmap = ColorManager.luts[ para['LUT'] ]
37+
imglut = color_code(imgs[para['Start image']-1: para['End image']], cmap)
38+
IPy.show_img([imglut],'Color-coded %s'%ips.title)
39+
if para['Creatbar']:
40+
cmapshow = np.ones([32,256,3])*cmap
41+
IPy.show_img([cmapshow.astype('uint8')],'Color bar')
42+

0 commit comments

Comments
 (0)