Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
carsen-stringer committed Sep 13, 2023
2 parents 2a07103 + d951936 commit 4565e2a
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 54 deletions.
100 changes: 55 additions & 45 deletions cellpose/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,8 @@ def remove_stroke(self, delete_points=True, stroke_ind=-1):
if self.outlinesOn:
self.layerz[stroke[outpix,1],stroke[outpix,2]] = np.array(self.outcolor)
if delete_points:
self.current_point_set = self.current_point_set[:-1*(stroke[:,-1]==1).sum()]
# self.current_point_set = self.current_point_set[:-1*(stroke[:,-1]==1).sum()]
del self.current_point_set[stroke_ind]
self.update_layer()

del self.strokes[stroke_ind]
Expand Down Expand Up @@ -1590,10 +1591,9 @@ def update_crosshairs(self):

def add_set(self):
if len(self.current_point_set) > 0:
self.current_point_set = np.array(self.current_point_set)
while len(self.strokes) > 0:
self.remove_stroke(delete_points=False)
if len(self.current_point_set) > 8:
if len(self.current_point_set[0]) > 8:
color = self.colormap[self.ncells,:3]
median = self.add_mask(points=self.current_point_set, color=color)
if median is not None:
Expand All @@ -1610,51 +1610,61 @@ def add_set(self):
self.current_point_set = []
self.update_layer()

def add_mask(self, points=None, color=(100,200,50)):
def add_mask(self, points=None, color=(100,200,50), dense=True):
# points is list of strokes

points_all = np.concatenate(points, axis=0)

# loop over z values
median = []
if points.shape[1] < 3:
points = np.concatenate((np.zeros((points.shape[0],1), "int32"), points), axis=1)
zdraw = np.unique(points[:,0])
zdraw = np.unique(points_all[:,0])
zrange = np.arange(zdraw.min(), zdraw.max()+1, 1, int)
zmin = zdraw.min()
pix = np.zeros((2,0), "uint16")
mall = np.zeros((len(zrange), self.Ly, self.Lx), "bool")
k=0
for z in zdraw:
iz = points[:,0] == z
vr = points[iz,1]
vc = points[iz,2]
# get points inside drawn points
mask = np.zeros((np.ptp(vr)+4, np.ptp(vc)+4), np.uint8)
pts = np.stack((vc-vc.min()+2,vr-vr.min()+2), axis=-1)[:,np.newaxis,:]
mask = cv2.fillPoly(mask, [pts], (255,0,0))
ar, ac = np.nonzero(mask)
ar, ac = ar+vr.min()-2, ac+vc.min()-2
# get dense outline
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
pvc, pvr = contours[-2][0].squeeze().T
vr, vc = pvr + vr.min() - 2, pvc + vc.min() - 2
# concatenate all points
ar, ac = np.hstack((np.vstack((vr, vc)), np.vstack((ar, ac))))
# if these pixels are overlapping with another cell, reassign them
ioverlap = self.cellpix[z][ar, ac] > 0
if (~ioverlap).sum() < 8:
print('ERROR: cell too small without overlaps, not drawn')
return None
elif ioverlap.sum() > 0:
ar, ac = ar[~ioverlap], ac[~ioverlap]
# compute outline of new mask
mask = np.zeros((np.ptp(ar)+4, np.ptp(ac)+4), np.uint8)
mask[ar-ar.min()+2, ac-ac.min()+2] = 1
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
pvc, pvr = contours[-2][0].squeeze().T
vr, vc = pvr + ar.min() - 2, pvc + ac.min() - 2
self.draw_mask(z, ar, ac, vr, vc, color)

median.append(np.array([np.median(ar), np.median(ac)]))
ars, acs, vrs, vcs = np.zeros(0, "int"), np.zeros(0, "int"), np.zeros(0, "int"), np.zeros(0, "int")
for stroke in points:
stroke = np.concatenate(stroke, axis=0).reshape(-1, 4)
iz = stroke[:,0] == z
vr = stroke[iz,1]
vc = stroke[iz,2]
if iz.sum() > 0:
# get points inside drawn points
mask = np.zeros((np.ptp(vr)+4, np.ptp(vc)+4), np.uint8)
pts = np.stack((vc-vc.min()+2,vr-vr.min()+2), axis=-1)[:,np.newaxis,:]
mask = cv2.fillPoly(mask, [pts], (255,0,0))
ar, ac = np.nonzero(mask)
ar, ac = ar+vr.min()-2, ac+vc.min()-2
# get dense outline
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
pvc, pvr = contours[-2][0].squeeze().T
vr, vc = pvr + vr.min() - 2, pvc + vc.min() - 2
# concatenate all points
ar, ac = np.hstack((np.vstack((vr, vc)), np.vstack((ar, ac))))
# if these pixels are overlapping with another cell, reassign them
ioverlap = self.cellpix[z][ar, ac] > 0
if (~ioverlap).sum() < 8:
print('ERROR: cell too small without overlaps, not drawn')
return None
elif ioverlap.sum() > 0:
ar, ac = ar[~ioverlap], ac[~ioverlap]
# compute outline of new mask
mask = np.zeros((np.ptp(ar)+4, np.ptp(ac)+4), np.uint8)
mask[ar-ar.min()+2, ac-ac.min()+2] = 1
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
pvc, pvr = contours[-2][0].squeeze().T
vr, vc = pvr + ar.min() - 2, pvc + ac.min() - 2
ars = np.concatenate((ars, ar), axis=0)
acs = np.concatenate((acs, ac), axis=0)
vrs = np.concatenate((vrs, vr), axis=0)
vcs = np.concatenate((vcs, vc), axis=0)
self.draw_mask(z, ars, acs, vrs, vcs, color)

median.append(np.array([np.median(ars), np.median(acs)]))
mall[z-zmin, ar, ac] = True
pix = np.append(pix, np.vstack((ar, ac)), axis=-1)
pix = np.append(pix, np.vstack((ars, acs)), axis=-1)

mall = mall[:, pix[0].min():pix[0].max()+1, pix[1].min():pix[1].max()+1].astype(np.float32)
ymin, xmin = pix[0].min(), pix[1].min()
Expand Down Expand Up @@ -1688,12 +1698,12 @@ def draw_mask(self, z, ar, ac, vr, vc, color, idx=None):
self.cellpix[z, vr, vc] = idx
self.cellpix[z, ar, ac] = idx
self.outpix[z, vr, vc] = idx
self.layerz[ar, ac, :3] = color
if self.masksOn:
self.layerz[ar, ac, -1] = self.opacity
if self.outlinesOn:
self.layerz[vr, vc] = np.array(self.outcolor)

if z==self.currentZ:
self.layerz[ar, ac, :3] = color
if self.masksOn:
self.layerz[ar, ac, -1] = self.opacity
if self.outlinesOn:
self.layerz[vr, vc] = np.array(self.outcolor)

def compute_scale(self):
self.diameter = float(self.Diameter.text())
Expand Down
6 changes: 2 additions & 4 deletions cellpose/gui/guiparts.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,6 @@ def hoverEvent(self, ev):
self.drawAt(ev.pos())
if self.is_at_start(ev.pos()):
self.end_stroke()

else:
ev.acceptClicks(QtCore.Qt.RightButton)
#ev.acceptClicks(QtCore.Qt.LeftButton)
Expand Down Expand Up @@ -501,11 +500,11 @@ def end_stroke(self):
self.parent.stroke_appended = True
self.parent.current_stroke = np.array(self.parent.current_stroke)
ioutline = self.parent.current_stroke[:,3]==1
self.parent.current_point_set.extend(list(self.parent.current_stroke[ioutline]))
self.parent.current_point_set.append(list(self.parent.current_stroke[ioutline]))
self.parent.current_stroke = []
if self.parent.autosave:
self.parent.add_set()
if len(self.parent.current_point_set) > 0 and self.parent.autosave:
if len(self.parent.current_point_set) and len(self.parent.current_point_set[0]) > 0 and self.parent.autosave:
self.parent.add_set()
self.parent.in_stroke = False

Expand All @@ -517,7 +516,6 @@ def tabletEvent(self, ev):

def drawAt(self, pos, ev=None):
mask = self.strokemask
set = self.parent.current_point_set
stroke = self.parent.current_stroke
pos = [int(pos.y()), int(pos.x())]
dk = self.drawKernel
Expand Down
25 changes: 20 additions & 5 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ For multi-channel, multi-Z tiff's, the expected format is Z x channels x Ly x Lx
image with ``_seg.npy`` appended, make sure you have WRITE access
in the folder, otherwise the file will not save.

.. image:: https://www.cellpose.org/static/images/cellpose_gui.png
:width: 600

Using the GUI
~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -48,20 +51,32 @@ Main GUI mouse controls (works in all views):
- Start draw mask = right-click
- End draw mask = right-click, or return to circle at beginning

Drawing masks
~~~~~~~~~~~~~~~~~~~~~~

Masks are started with right-click, then hover your mouse (do not hold it down),
and return it to the red circle to complete the mask. The mask should now be completed.

Overlaps in masks are NOT allowed. If you draw a mask on top of another
mask, it is cropped so that it doesn't overlap with the old mask. Masks
in 2D should be single strokes (if *single_stroke* is checked).

If you want to draw masks in 3D, then you can turn *single_stroke*
option off and draw a stroke on each plane with the cell and then press
ENTER.

.. image:: https://www.cellpose.org/static/images/cellpose_gui.png
:width: 600
ENTER. You can also draw multiple strokes on the same plane for
complex cell shapes, but do not do this in 2D if you plan to train a cellpose model
(the cell flows will not work correctly).

.. note::
3D labelling will fill in unlabelled z-planes so that you do not
have to densely label, for example you can skip some planes.
have to densely label, for example you can skip some planes, and the
cell will be interpolated between planes.

After each mask is drawn in 2D, it is saved to the ``_seg.npy``. If this is slow (for
large images), this "autosave" option can be turned off in the "File" menu
("Disable autosave _seg.npy file"). In 3D,
the mask is never auto-saved, instead save masks by clicking CTRL+S, or "Save" in the
"File" menu.

Bulk Mask Deletion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down

0 comments on commit 4565e2a

Please sign in to comment.