MouseLand / cellpose

a generalist algorithm for cellular segmentation with human-in-the-loop capabilities
https://www.cellpose.org/
BSD 3-Clause "New" or "Revised" License
1.24k stars 359 forks source link

[BUG] OpenCV does not support resizing of uint32 data types during model evaluation #937

Open Tobiaspk opened 1 month ago

Tobiaspk commented 1 month ago

Description

If cellpose detects over 65,535 masks, images are cast to uint32 and then rescaled using cv2. However, this is interpreted as type CV_32S (signed integer as opposed to unsigned) by opencv which does not support unsigned 32-bit CV_32U (doc). cv2 does not support resizing images with type CV_32S (doc).

This behaviour is caused in dynamics.py line 845, which casts to uint32 and is later resized in line 784.

Reproduce

Steps to reproduce the behavior in python 3.9.16, both on GPU and CPU

import numpy as np
import cv2

a = np.random.normal(size=(100, 200))

# SUCCEEDS: cast as unsigned int 8 bit
print(cv2.resize(a.astype(np.uint8), (50, 100)).shape)

# SUCCEEDS: cast as unsigned int 16 bit
print(cv2.resize(a.astype(np.uint16), (50, 100)).shape)

# FAILS: cast as unsigned int 32 bit
print(cv2.resize(a.astype(np.uint32), (50, 100)).shape)

This outputs the following log and error

(100, 50)
(100, 50)
---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
Cell In[75], line 13
     10 print(cv2.resize(a.astype(np.uint16), (50, 100)).shape)
     12 # FAILS: cast as unsigned int 32 bit
---> 13 print(cv2.resize(a.astype(np.uint32), (50, 100)).shape)

error: OpenCV(4.9.0) :-1: error: (-5:Bad argument) in function 'resize'
> Overload resolution failed:
>  - src data type = uint32 is not supported
>  - Expected Ptr<cv::UMat> for argument 'src'

System

Full Error

Below is the complete output when running this code:

masks, flows, styles, diams = model.eval(
    img,
    diameter=0,
    flow_threshold=1,
    cellprob_threshold=-1,
    channels=[channel_cyt, channel_nuc],
)

This yields the following log and output

2024-05-13 10:46:54 - ** TORCH CUDA version installed and working. **
2024-05-13 10:46:54 - >>>> using GPU
2024-05-13 10:46:54 - >> cyto << model set to be used
2024-05-13 10:46:55 - >>>> model diam_mean =  30.000 (ROIs rescaled to this size during training)
2024-05-13 10:46:55 - Load Mesmer model
2024-05-13 10:46:55 - channels set to [0, 3]
2024-05-13 10:46:55 - ~~~ ESTIMATING CELL DIAMETER(S) ~~~
2024-05-13 10:59:57 - WARNING: image is very large, not using gpu to compute flows from masks for QC step flow_threshold
2024-05-13 10:59:57 - turn off QC step with flow_threshold=0 if too slow
2024-05-13 11:03:41 - more than 65535 masks in image, masks returned as np.uint32

---------------------------------------------------------------------------
error                                     Traceback (most recent call last)
Cell In[26], line 1
----> 1 xt.cellpose.segment_cellpose(
      2     path_img=xt.data.get_aligned_img(paths),
      3     channel_nuc=3,
      4     channel_cyt=0,
      5     path_out=paths["cellpose"]
      6 )

File /lilac/data/sail/projects/ongoing/triage/xenium_jose/scripts/tobi/xenium_tools/cellpose.py:41, in segment_cellpose(path_img, channel_nuc, channel_cyt, path_out)
     38 logging.info("Normalising image")
     39 img = xt.utils.norm_uint8(img)
---> 41 # Load CellPose model
     42 logging.info("Running Cellpose")
     43 model = models.Cellpose(gpu=core.use_gpu(), model_type=model_type)

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/models.py:164, in Cellpose.eval(self, x, batch_size, channels, channel_axis, invert, normalize, diameter, do_3D, **kwargs)
    162 tic = time.time()
    163 models_logger.info("~~~ ESTIMATING CELL DIAMETER(S) ~~~")
--> 164 diams, _ = self.sz.eval(x, channels=channels, channel_axis=channel_axis,
    165                         batch_size=batch_size, normalize=normalize,
    166                         invert=invert)
    167 diameter = None
    168 models_logger.info("estimated cell diameter(s) in %0.2f sec" %
    169                    (time.time() - tic))

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/models.py:678, in SizeModel.eval(self, x, channels, channel_axis, normalize, invert, augment, tile, batch_size, progress)
    674 diam_style = self._size_estimation(np.array(styles))
    675 diam_style = self.diam_mean if (diam_style == 0 or
    676                                 np.isnan(diam_style)) else diam_style
--> 678 masks = self.cp.eval(
    679     x, compute_masks=True, channels=channels, channel_axis=channel_axis,
    680     normalize=normalize, invert=invert, augment=augment, tile=tile,
    681     batch_size=batch_size, resample=False,
    682     rescale=self.diam_mean / diam_style if self.diam_mean > 0 else 1,
    683     diameter=None, interp=False)[0]
    685 diam = utils.diameters(masks)[0]
    686 diam = self.diam_mean if (diam == 0 or np.isnan(diam)) else diam

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/models.py:410, in CellposeModel.eval(self, x, batch_size, resample, channels, channel_axis, z_axis, normalize, invert, rescale, diameter, flow_threshold, cellprob_threshold, do_3D, anisotropy, stitch_threshold, min_size, niter, augment, tile, tile_overlap, bsize, interp, compute_masks, progress)
    407     diameter = self.diam_labels
    408     rescale = self.diam_mean / diameter
--> 410 masks, styles, dP, cellprob, p = self._run_cp(
    411     x, compute_masks=compute_masks, normalize=normalize, invert=invert,
    412     rescale=rescale, resample=resample, augment=augment, tile=tile,
    413     tile_overlap=tile_overlap, bsize=bsize, flow_threshold=flow_threshold,
    414     cellprob_threshold=cellprob_threshold, interp=interp, min_size=min_size,
    415     do_3D=do_3D, anisotropy=anisotropy, niter=niter,
    416     stitch_threshold=stitch_threshold)
    418 flows = [plot.dx_to_circ(dP), dP, cellprob, p]
    419 return masks, flows, styles

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/models.py:517, in CellposeModel._run_cp(self, x, compute_masks, normalize, invert, niter, rescale, resample, augment, tile, tile_overlap, cellprob_threshold, bsize, flow_threshold, min_size, interp, anisotropy, do_3D, stitch_threshold)
    514 iterator = trange(nimg, file=tqdm_out,
    515                   mininterval=30) if nimg > 1 else range(nimg)
    516 for i in iterator:
--> 517     outputs = dynamics.resize_and_compute_masks(
    518         dP[:, i],
    519         cellprob[i],
    520         niter=niter,
    521         cellprob_threshold=cellprob_threshold,
    522         flow_threshold=flow_threshold,
    523         interp=interp,
    524         resize=resize,
    525         min_size=min_size if stitch_threshold == 0 or nimg == 1 else
    526         -1,  # turn off for 3D stitching
    527         device=self.device if self.gpu else None)
    528     masks.append(outputs[0])
    529     p.append(outputs[1])

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/dynamics.py:785, in resize_and_compute_masks(dP, cellprob, p, niter, cellprob_threshold, flow_threshold, interp, do_3D, min_size, resize, device)
    779 mask, p = compute_masks(dP, cellprob, p=p, niter=niter,
    780                         cellprob_threshold=cellprob_threshold,
    781                         flow_threshold=flow_threshold, interp=interp, do_3D=do_3D,
    782                         min_size=min_size, device=device)
    784 if resize is not None:
--> 785     mask = transforms.resize_image(mask, resize[0], resize[1],
    786                                    interpolation=cv2.INTER_NEAREST)
    787     p = np.array([
    788         transforms.resize_image(pi, resize[0], resize[1],
    789                                 interpolation=cv2.INTER_NEAREST) for pi in p
    790     ])
    792 return mask, p

File /data/sail/krauset/.pyenv/versions/xenium/lib/python3.9/site-packages/cellpose/transforms.py:727, in resize_image(img0, Ly, Lx, rsz, interpolation, no_channels)
    725         imgs[i] = cv2.resize(img, (Lx, Ly), interpolation=interpolation)
    726 else:
--> 727     imgs = cv2.resize(img0, (Lx, Ly), interpolation=interpolation)
    728 return imgs

error: OpenCV(4.9.0) :-1: error: (-5:Bad argument) in function 'resize'
> Overload resolution failed:
>  - src data type = uint32 is not supported
>  - Expected Ptr<cv::UMat> for argument 'src'