Skip to content

Commit 4fc3c5d

Browse files
author
SPENCE
committed
adding 2D gaussian ROI fitting to settings windows
1 parent 7d35fd4 commit 4fc3c5d

10 files changed

+123
-41
lines changed
File renamed without changes.
File renamed without changes.

imageanalysis/atomChecker.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class atom_window(QMainWindow):
4545
image_shape -- shape of the images being taken, in pixels (x,y).
4646
name -- an ID for this window, prepended to saved files.
4747
"""
48-
event_im = pyqtSignal(np.ndarray)
48+
event_im = pyqtSignal(np.ndarray) # image taken by the camera as np array
49+
roi_values = pyqtSignal(list) # list of ROIs (x, y, w, h, threshold)
4950

5051
def __init__(self, last_im_path='.', rois=[(1,1,1,1)], num_plots=4,
5152
image_shape=(512,512), name=''):
@@ -56,6 +57,7 @@ def __init__(self, last_im_path='.', rois=[(1,1,1,1)], num_plots=4,
5657
self.init_UI(num_plots) # adjust widgets from main_window
5758
self.event_im.connect(self.rh.process)
5859
self.event_im.connect(self.update_plots)
60+
self.checking = False # whether the atom checker is active or not
5961

6062
def init_UI(self, num_plots=4):
6163
"""Create all the widgets and position them in the layout"""
@@ -123,14 +125,23 @@ def init_UI(self, num_plots=4):
123125
layout.addWidget(self.plots[-1], 1+(i//k)*3, 7+(i%k)*6, 2,6) # allocate space in the grid
124126
try:
125127
r = self.rh.ROIs[i]
126-
remove_slot(r.threshedit.textEdited, self.update_plots, True)
127128
self.plots[i].setTitle('ROI '+str(r.i))
128129
# line edits with ROI x, y, w, h, threshold, auto update threshold
129130
for j, label in enumerate(list(r.edits.values())+[r.threshedit, r.autothresh]):
130131
layout.addWidget(label, (i//k)*3, 7+(i%k)*6+j, 1,1)
131132
except IndexError as e: pass # logger.warning('Atom Checker has more plots than ROIs')
132133

133134
self.display_rois() # put ROIs on the image
135+
136+
#### extra buttons ####
137+
# send the ROIs used here to the image analysis settings window
138+
self.roi_matching = QPushButton('Send ROIs to analysis', self)
139+
self.roi_matching.clicked.connect(self.send_rois)
140+
layout.addWidget(self.roi_matching, 2+num_plots//k*3,7, 1,1)
141+
# the user can trigger the experiment early by pressing this button
142+
self.trigger_button = QPushButton('Manual trigger experiment', self)
143+
self.trigger_button.clicked.connect(self.send_trigger)
144+
layout.addWidget(self.trigger_button, 2+num_plots//k*3,8, 1,1)
134145
#
135146
self.setWindowTitle(self.name+' - Atom Checker -')
136147
self.setWindowIcon(QIcon('docs/atomcheckicon.png'))
@@ -155,6 +166,14 @@ def user_roi(self, roi):
155166
for key, val in zip(r.edits.keys(), [xc, yc, w, h]):
156167
r.edits[key].setText(str(val))
157168

169+
def send_rois(self, toggle=0):
170+
"""Emit the signal with the list of ROIs"""
171+
self.roi_values.emit([[r.x, r.y, r.w, r.h, r.t] for r in self.rh.ROIs])
172+
173+
def send_trigger(self, toggle=0):
174+
"""Emit the roi_handler's trigger signal to start the experiment"""
175+
if self.checking: self.rh.trigger.emit(1)
176+
158177
#### #### canvas functions #### ####
159178

160179
def display_rois(self, n=''):
@@ -173,6 +192,7 @@ def display_rois(self, n=''):
173192
for i, r in enumerate(self.rh.ROIs):
174193
if r.roi not in viewbox.allChildren():
175194
remove_slot(r.roi.sigRegionChangeFinished, self.user_roi, True)
195+
remove_slot(r.threshedit.textEdited, self.update_plots, True)
176196
r.roi.setZValue(10) # make sure the ROI is drawn above the image
177197
viewbox.addItem(r.roi)
178198
viewbox.addItem(r.label)

imageanalysis/default.config

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pic_width=13
22
pic_height=13
3-
ROIs=[[5, 7, 1, 1], [8, 7, 1, 1]]
3+
ROIs=[[3, 5, 1, 1, 30], [8, 7, 1, 1, 30]]
44
bias=697
55
image_path=.\\
66
results_path=.\\

imageanalysis/roiHandler.py

+43-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import logging
2222
logger = logging.getLogger(__name__)
2323
from maingui import int_validator, nat_validator
24+
from fitCurve import fit
2425

2526
#### #### #### ####
2627

@@ -76,15 +77,42 @@ def create_rect_mask(self, image_shape=None):
7677
else: logger.warning('ROI tried to create invalid mask.\n' +
7778
'shape %s, x %s, y %s, w %s, h %s'%(self.s, self.x, self.y, self.w, self.h))
7879

79-
def resize(self, xc, yc, width, height):
80+
def create_gauss_mask(self, im=0):
81+
"""Fit a 2D Gaussian to the given image and use it to create a mask.
82+
The Gaussian is fitted around the maximum intensity pixel."""
83+
try:
84+
if np.size(np.shape(im)) == 2:
85+
self.s = np.shape(im)
86+
self.mask = np.zeros(np.shape(im))
87+
xc, yc = np.unravel_index(np.argmax(im), im.shape)
88+
d = round(np.size(im[im > self.t])**0.5) # guess PSF size from pixels > threshold
89+
im2 = im[xc-d:xc+d, yc-d:yc+d] # better for fitting to use zoom in
90+
w = []
91+
for i in range(2): # do Gaussian fit along each axis
92+
vals = np.sum(im2, axis=i)
93+
f = fit(np.arange(len(vals)), vals)
94+
f.estGaussParam()
95+
f.p0 = f.p0 + [np.min(vals)]
96+
f.getBestFit(f.offGauss) # only interested in the width
97+
w.append(round(f.ps[2]) if f.ps[2]>0 and np.isfinite(f.ps[2]) else 1)
98+
xy = np.meshgrid(list(reversed(range(-int(w[1]*2), int(w[1]*2)+1))),
99+
range(-int(w[0]*2), int(w[0]*2)+1)) # make grid of (x,y) coordinates
100+
# only include values of the Gaussian within 2 1/e^2 widths
101+
self.mask[xc - int(w[0]*2) : xc + int(w[0]*2) + 1,
102+
yc - int(w[1]*2) : yc + int(w[1]*2) + 1] = np.exp( # fill in 2D Gaussian
103+
-2*xy[0]**2 / w[0]**2 -2*xy[1]**2 / w[1]**2) /np.pi/w[0]/w[1]*2
104+
self.resize(*map(int, [xc, yc, w[0], w[1]]), False) # update stored ROI values
105+
except Exception as e: logger.error('ROI %s failed to set Gaussian mask\n'%self.i+str(e))
106+
107+
def resize(self, xc, yc, width, height, create_sq_mask=True):
80108
"""Reset the position and dimensions of the ROI"""
81109
self.x, self.y, self.w, self.h = xc, yc, width, height
82110
self.roi.setPos(xc - width//2, yc - height//2)
83111
self.roi.setSize((width, height))
84112
self.label.setPos(xc, yc)
85113
for key, val in zip(self.edits.keys(), [xc, yc, width, height]):
86114
self.edits[key].setText(str(val))
87-
self.create_rect_mask()
115+
if create_sq_mask: self.create_rect_mask()
88116

89117
def atom(self):
90118
"""A list of whether the counts are above threshold"""
@@ -131,11 +159,12 @@ class roi_handler(QWidget):
131159
im_shape -- dimensions of the image in pixels"""
132160
trigger = pyqtSignal(int)
133161

134-
def __init__(self, rois=[(1,1,1,1)], im_shape=(512,512)):
162+
def __init__(self, rois=[(1,1,1,1,1)], im_shape=(512,512)):
135163
super().__init__()
136164
self.ROIs = [ROI(im_shape,*r, ID=i) for i, r in enumerate(rois)]
137-
self.shape = im_shape
138-
self.delim = ' '
165+
self.shape = im_shape # image dimensions in pixels
166+
self.bias = 697 # bias offset to subtract from image counts
167+
self.delim = ' ' # delimiter used to save/load files
139168

140169
def create_rois(self, n):
141170
"""Change the list of ROIs to have length n"""
@@ -146,7 +175,10 @@ def create_rois(self, n):
146175
def resize_rois(self, ROIlist):
147176
"""Convenience function for setting multiple ROIs"""
148177
for i, roi in enumerate(ROIlist):
149-
try: self.ROIs[i].resize(*roi)
178+
try:
179+
self.ROIs[i].resize(*roi[:-1])
180+
self.ROIs[i].t = roi[-1]
181+
self.ROIs[i].threshedit.setText(str(roi[-1]))
150182
except (IndexError, ValueError) as e: logger.warning(
151183
"Failed to resize ROI "+str(i)+": %s\n"%roi + str(e))
152184

@@ -162,7 +194,7 @@ def process(self, im, include=True):
162194
success = 1
163195
for r in self.ROIs:
164196
try:
165-
counts = np.sum(im * r.mask)
197+
counts = np.sum(im * r.mask) - self.bias
166198
success = 1 if abs(counts) // r.t and success else 0
167199
r.c.append(counts)
168200
except ValueError as e:
@@ -185,6 +217,10 @@ def set_pic_size(self, im_name):
185217
self.shape = np.genfromtxt(im_name, delimiter=self.delim).shape
186218
try: self.cam_pic_size_changed(self.shape[1]-1, self.shape[0])
187219
except IndexError: self.cam_pic_size_changed(self.shape[0]-1, 1)
220+
221+
def set_bias(self, bias):
222+
"""Update the bias offset subtracted from all image counts."""
223+
self.bias = bias
188224

189225
def cam_pic_size_changed(self, width, height):
190226
"""Receive new image dimensions from Andor camera"""

0 commit comments

Comments
 (0)