def test_binarize_non_3d(num_rounds, num_chs, num_zplanes=4, ysize=5, xsize=6): data = np.linspace(0, 1, num_rounds * num_chs * num_zplanes * ysize * xsize, dtype=np.float32) data = data.reshape((num_rounds, num_chs, num_zplanes, ysize, xsize)) imagestack = ImageStack.from_numpy(data) binarizer = ThresholdBinarize(0.0) with pytest.raises(ValueError): binarizer.run(imagestack)
def jitter_code() -> ImageStack: """this code has some minor jitter <= 3px at the most distant point""" img = np.zeros((3, 2, 20, 50, 50), dtype=np.float32) # code 1 img[0, 0, 5, 35, 35] = 10 img[1, 1, 5, 34, 35] = 10 img[2, 0, 6, 35, 33] = 10 # blur points gaussian_filter(img, (0, 0, 0.5, 1.5, 1.5), output=img) return ImageStack.from_numpy(img)
def traversing_code() -> ImageStack: """this code walks in a sequential direction, and should only be detectable from some anchors""" img = np.zeros((3, 2, 20, 50, 50), dtype=np.float32) # code 1 img[0, 0, 5, 35, 35] = 10 img[1, 1, 5, 32, 32] = 10 img[2, 0, 5, 29, 29] = 10 # blur points gaussian_filter(img, (0, 0, 0.5, 1.5, 1.5), output=img) return ImageStack.from_numpy(img)
def make_expected_image_stack(): ''' Make the expected image stack result ''' base_calls = np.array( [[[[[0, 0], [0, 0]]], [[[0, 0.755929], [0, 0]]], [[[0, 0], [0.755929, 0]]], [[[0, 0], [0, 0.755929]]]]], dtype='float32') expected_stack = ImageStack.from_numpy(base_calls) return expected_stack
def test_intensity_table_concatenation(): """create two IntensityTables and assert that they are being concatenated properly.""" r, c, z, y, x = 3, 3, 2, 2, 5 data = np.zeros(180, dtype=np.float32).reshape(r, c, z, y, x) image_stack = ImageStack.from_numpy(data) intensities = IntensityTable.from_image_stack(image_stack) intensities2 = intensities.copy() original_shape = intensities.shape expected_shape = list(original_shape) expected_shape[0] *= 2 # only features is concatenated assert np.array_equal( concatenate([intensities, intensities2]).shape, expected_shape) # slice out a single channel and round from both experiments, such that the data no longer match # across all dimensions but the concatenation dimension. The resulting structure should be # 2 (r) * 2 (c) * 5 (z), 2, 2 = 40, 2, 2 i1 = intensities.where(np.logical_and(intensities.r == 0, intensities.c == 0), drop=True) i2 = intensities.where(np.logical_and(intensities.r == 1, intensities.c == 1), drop=True) expected_shape = (i1.shape[0] + i2.shape[0], 2, 2) result = concatenate([i1, i2]) assert expected_shape == result.shape # slice a larger r value for second array, however, there are still only two values, so # shape should be 40, 2, 2 i3 = intensities.where(np.logical_and(intensities.r == 2, intensities.c == 1), drop=True) expected_shape = (i1.shape[0] + i3.shape[0], 2, 2) result = concatenate([i1, i3]) assert expected_shape == result.shape # slice out z in addition to reduce the total feature number by 1/2 i4 = intensities.where(np.logical_and(intensities.r == 0, intensities.z == 1), drop=True) expected_shape = (i1.shape[0] + i4.shape[0], 1, 3) result = concatenate([i1, i4]) assert expected_shape == result.shape
def test_from_fiji_roi_set(): # set up empty dapi imagestack of correct size fake_dapi = ImageStack.from_numpy(array=np.zeros(shape=(1, 1, 1, 2048, 2048))) cwd = os.path.dirname(__file__) file_path = os.path.join(cwd, "RoiSet.zip") # load test roi_set.zip masks = BinaryMaskCollection.from_fiji_roi_set(file_path, fake_dapi) # check data and offsets set correctly assert len(masks) == 4 assert masks._masks[0].offsets == (782, 760) assert masks._masks[1].offsets == (234, 680) assert masks._masks[2].offsets == (10, 980) assert masks._masks[3].offsets == (16, 768)
def test_lmpf_uniform_peak(): data_array = np.zeros(shape=(1, 1, 1, 100, 100), dtype=np.float32) data_array[0, 0, 0, 45:55, 45:55] = 1 imagestack = ImageStack.from_numpy(data_array) # standard local max peak finder, should find spots for all the evenly illuminated pixels. lmpf_no_kwarg = FindSpots.LocalMaxPeakFinder(1, 1, 1, sys.maxsize) peaks = lmpf_no_kwarg.run(imagestack) results_no_kwarg = peaks[{Axes.ROUND: 0, Axes.CH: 0}] assert len(results_no_kwarg.spot_attrs.data) == 100 # local max peak finder, capped at one peak per label. lmpf_kwarg = FindSpots.LocalMaxPeakFinder(1, 1, 1, sys.maxsize, num_peaks_per_label=1) peaks = lmpf_kwarg.run(imagestack) results_kwarg = peaks[{Axes.ROUND: 0, Axes.CH: 0}] assert len(results_kwarg.spot_attrs.data) == 1
def make_image_stack(): """Make a test ImageStack.""" # Make the test image test = np.ones((2, 4, 1, 2, 2), dtype='float32') * 0.1 x = [0, 0, 1, 1] y = [0, 1, 0, 1] for i in range(4): test[0, i, 0, x[i], y[i]] = 1 test[0, 0, 0, 0, 0] = 0.75 # Make the ImageStack test_stack = ImageStack.from_numpy(test) return test_stack
def test_imagestack_deepcopy(nitems: int=10) -> None: """ Instantiate an :py:class:`ImageStack` and deepcopy it. Worker processes reconstitute a numpy array from the buffer and attempts to writes to the numpy array. Writes in the worker process should be visible in the parent process. """ shape = (nitems, 3, 4, 5, 6) dtype = np.float32 source = np.zeros(shape, dtype=np.float32) imagestack = ImageStack.from_numpy(source) imagestack_copy = copy.deepcopy(imagestack) _start_process_to_test_shmem( array_holder=imagestack_copy._data._backing_mp_array, decoder=partial(_decode_imagestack_array_to_numpy_array, shape, dtype), nitems=nitems) for ix in range(nitems): assert (imagestack.xarray[ix] == 0).all() assert np.allclose(imagestack_copy.xarray[ix], ix)
def two_perfect_codes() -> ImageStack: """this code has no jitter""" img = np.zeros((3, 2, 20, 50, 50), dtype=np.float32) # code 1 img[0, 0, 5, 20, 35] = 10 img[1, 1, 5, 20, 35] = 10 img[2, 0, 5, 20, 35] = 10 # code 1 img[0, 0, 5, 40, 45] = 10 img[1, 1, 5, 40, 45] = 10 img[2, 0, 5, 40, 45] = 10 # blur points gaussian_filter(img, (0, 0, 0.5, 1.5, 1.5), output=img) return ImageStack.from_numpy(img)
def test_intensity_table_serialization(): """ Test that an IntensityTable can be saved to disk, and that when it is reloaded, the data is unchanged """ # create an IntensityTable data = np.zeros(100, dtype=np.float32).reshape(1, 5, 2, 2, 5) image_stack = ImageStack.from_numpy(data) intensities = IntensityTable.from_image_stack(image_stack) # dump it to disk tempdir = tempfile.mkdtemp() filename = os.path.join(tempdir, 'test.nc') intensities.to_netcdf(filename) # verify the data has not changed loaded = intensities.open_netcdf(filename) assert intensities.equals(loaded)
def make_expected_image_stack(func): ''' Make the expected image stack result ''' if func == 'max': reduced = np.array( [[[[[0.75, 0.1], [0.1, 0.1]]], [[[0.1, 1], [0.1, 0.1]]], [[[0.1, 0.1], [1, 0.1]]], [[[0.1, 0.1], [0.1, 1]]]]], dtype='float32' ) elif func == 'mean': reduced = np.array( [[[[[0.425, 0.1], [0.1, 0.1]]], [[[0.1, 0.55], [0.1, 0.1]]], [[[0.1, 0.1], [0.55, 0.1]]], [[[0.1, 0.1], [0.1, 0.55]]]]], dtype='float32' ) elif func == 'sum': reduced = np.array( [[[[[0.85, 0.2], [0.2, 0.2]]], [[[0.2, 1], [0.2, 0.2]]], [[[0.2, 0.2], [1, 0.2]]], [[[0.2, 0.2], [0.2, 1]]]]], dtype='float32' ) expected_stack = ImageStack.from_numpy(reduced) return expected_stack
def multiple_possible_neighbors() -> ImageStack: """this image is intended to be tested with anchor_round in {0, 1}, last round has more spots""" img = np.zeros((3, 2, 20, 50, 50), dtype=np.float32) # round 1 img[0, 0, 5, 20, 40] = 10 img[0, 0, 5, 40, 20] = 10 # round 2 img[1, 1, 5, 20, 40] = 10 img[1, 1, 5, 40, 20] = 10 # round 3 img[2, 0, 5, 20, 40] = 10 img[2, 0, 5, 35, 35] = 10 img[2, 0, 5, 40, 20] = 10 # blur points gaussian_filter(img, (0, 0, 0.5, 1.5, 1.5), output=img) return ImageStack.from_numpy(img)
def test_to_mermaid_dataframe(): """ Creates a basic IntensityTable from an ImageStack and verifies that it can be dumped to disk as a DataFrame which MERmaid can load. Does not explicitly load the DataFrame in MERmaid. Verifies that the save function throws an error when target assignments are not present, which are required by MERmaid. """ r, c, z, y, x = 1, 5, 2, 2, 5 data = np.zeros(100, dtype=np.float32).reshape(r, c, z, y, x) image_stack = ImageStack.from_numpy(data) intensities = IntensityTable.from_image_stack(image_stack) # without a target assignment, should raise RuntimeError. with pytest.raises(AttributeError): with TemporaryDirectory() as dir_: intensities.to_mermaid(os.path.join(dir_, 'test.csv.gz')) # assign targets intensities = factories.assign_synthetic_targets(intensities) with TemporaryDirectory() as dir_: intensities.to_mermaid(os.path.join(dir_, 'test.csv.gz'))
def test_binarize(threshold: Number, num_rounds=1, num_chs=1, num_zplanes=4, ysize=5, xsize=6): data = np.linspace(0, 1, num_rounds * num_chs * num_zplanes * ysize * xsize, dtype=np.float32) data = data.reshape((num_rounds, num_chs, num_zplanes, ysize, xsize)) imagestack = ImageStack.from_numpy(data) binarizer = ThresholdBinarize(threshold) binary_mask_collection = binarizer.run(imagestack) assert len(binary_mask_collection) == 1 mask = binary_mask_collection.uncropped_mask(0) expected_value = data[0, 0] >= threshold assert np.array_equal(mask, expected_value)
def stack_from_tif(file_name: string) -> ImageStack: """ Returns a starfish ImageStack object from a tif file Parameters ---------- file_name: string Absolute or relative directory location of a tif file Returns ------- ImageStack """ image = sk.io.imread(file_name) image = sk.img_as_float(image) img_num = np.array(image) img_dim = len(np.shape(img_num)) [img_ch, img_y, img_x] = [1, 1, 1] # Treat stacks of tiff's as multiple channels if img_dim == 3: [img_ch, img_y, img_x] = np.shape(img_num) if img_dim == 2: [img_y, img_x] = np.shape(img_num) img_ch = 1 if not ((len(np.shape(img_num)) == 2) or (len(np.shape(img_num)) == 3)): raise Exception('Dimensionality error: images must be 2D or 2D stacks') stack = np.empty([1, img_ch, 1, img_y, img_x]) if img_dim == 2: stack[0, 0, 0, :, :] = img_num[:, :] if img_dim == 3: stack[0, :, 0, :, :] = img_num[:, :, :] image_stack = ImageStack.from_numpy(stack) return image_stack
def test_blob_doh_error_handling(): """ Test that BlobDetector throws a Value error if a user tries to use blob_doh on 3d data. `skimage.blob_doh` only supports 2d data. """ stack = ImageStack.from_numpy(np.zeros((4, 2, 10, 100, 100), dtype=np.float32)) blob_doh = BlobDetector( min_sigma=1, max_sigma=4, num_sigma=5, threshold=0, detector_method='blob_doh', measurement_type='max', is_volume=True) # Check that Value error gets raised when blob_doh and is_volume=True provided with pytest.raises(ValueError): blob_doh.run(stack) ref_image = stack.reduce({Axes.ROUND, Axes.CH}, func='max') # Check that Value error gets raised when blob_doh and reference image is 3d with pytest.raises(ValueError): blob_doh.run(stack, reference_image=ref_image)
def decoded_intensity_table_factory() -> Tuple[IntensityTable, np.ndarray]: """ Create an IntensityTable that has gene labels, including null labels. The data doesn't matter, so will use np.zeros """ data = np.zeros((1, 1, 2, 3, 3), dtype=np.float32) labels = np.array( [[[0, 1, 1], [0, 2, 2], [1, 1, 1]], [[0, 1, 1], [1, 1, 1], [0, 1, 2]]], dtype='<U3') labels_with_nan = labels.copy() labels_with_nan[labels == '0'] = 'nan' # create an intensity table and add the labels image_stack = ImageStack.from_numpy(data) intensities = IntensityTable.from_image_stack(image_stack) intensities[Features.TARGET] = (Features.AXIS, np.ravel(labels_with_nan)) # label the third column of this data as failing filters passes_filters = np.ones(data.shape, dtype=bool) passes_filters[:, :, :, :, -1] = 0 intensities[Features.PASSES_THRESHOLDS] = (Features.AXIS, np.ravel(passes_filters)) return intensities, labels_with_nan
def test_intensity_table_can_be_constructed_from_an_imagestack(): """ ImageStack has enough information to create an IntensityTable without additional SpotAttributes. Each feature is a pixel, and therefore the SpotAttributes can be extracted from the relative locations. """ r, c, z, y, x = 1, 5, 2, 2, 5 data = np.zeros(100, dtype=np.float32).reshape(r, c, z, y, x) image_stack = ImageStack.from_numpy(data) intensities = IntensityTable.from_image_stack(image_stack) # there should be 100 features assert np.product(intensities.shape) == 100 # the max features should be equal to the array extent (2, 2, 5) minus one, since indices # are being compared and python is zero based # import pdb; pdb.set_trace() assert np.max(intensities[Axes.ZPLANE.value].values) == z - 1 assert np.max(intensities[Axes.Y.value].values) == y - 1 assert np.max(intensities[Axes.X.value].values) == x - 1 # the number of channels and rounds should match the ImageStack assert intensities.sizes[Axes.CH.value] == c assert intensities.sizes[Axes.ROUND.value] == r
def run(self, stack: ImageStack, *args) -> ImageStack: numpy_array = stack.xarray numpy_array = numpy_array * self.multiplicand return ImageStack.from_numpy(numpy_array)
def synthetic_spots( cls, intensities: IntensityTable, num_z: int, height: int, width: int, n_photons_background=1000, point_spread_function=(4, 2, 2), camera_detection_efficiency=0.25, background_electrons=1, graylevel: float = 37000.0 / 2**16, ad_conversion_bits=16, ) -> ImageStack: """Generate a synthetic ImageStack from a set of Features stored in an IntensityTable Parameters ---------- intensities : IntensityTable IntensityTable containing coordinates of fluorophores. Used to position and generate spots in the output ImageStack num_z : int Number of z-planes in the ImageStack height : int Height in pixels of the ImageStack width : int Width in pixels of the ImageStack n_photons_background : int Poisson rate for the number of background photons to add to each pixel of the image. Set this parameter to 0 to eliminate background. (default 1000) point_spread_function : Tuple[int] The width of the gaussian density wherein photons spread around their light source. Set to zero to eliminate this (default (4, 2, 2)) camera_detection_efficiency : float The efficiency of the camera to detect light. Set to 1 to remove this filter (default 0.25) background_electrons : int Poisson rate for the number of spurious electrons detected per pixel during image capture by the camera (default 1) graylevel : float The number of shades of gray displayable by the synthetic camera. Larger numbers will produce higher resolution images (default 37000 / 2 ** 16) ad_conversion_bits : int The number of bits used during analog to visual conversion (default 16) Returns ------- ImageStack : synthetic spots """ # check some params if not 0 < camera_detection_efficiency <= 1: raise ValueError( f'invalid camera_detection_efficiency value: {camera_detection_efficiency}. ' f'Must be in the interval (0, 1].') def select_uint_dtype(array): """choose appropriate dtype based on values of an array""" max_val = np.max(array) for dtype in (np.uint8, np.uint16, np.uint32): if max_val <= np.iinfo(dtype).max: return array.astype(dtype) raise ValueError( 'value exceeds dynamic range of largest skimage-supported type' ) # make sure requested dimensions are large enough to support intensity values axis_to_size = zip((Axes.ZPLANE.value, Axes.Y.value, Axes.X.value), (num_z, height, width)) for axis, requested_size in axis_to_size: required_size = intensities.coords[axis].values.max() + 1 if required_size > requested_size: raise ValueError( f'locations of intensities contained in table exceed the size of requested ' f'axis {axis}. Required size {required_size} > {requested_size}.' ) # create an empty array of the correct size image = np.zeros( (intensities.sizes[Axes.ROUND.value], intensities.sizes[Axes.CH.value], num_z, height, width), dtype=np.uint32) # starfish uses float images, but the logic here requires uint. We cast, and will cast back # at the end of the function intensities.values = img_as_uint(intensities) for ch, round_ in product(*(range(s) for s in intensities.shape[1:])): spots = intensities[:, ch, round_] # numpy deprecated casting a specific way of casting floats that is triggered in xarray with warnings.catch_warnings(): warnings.simplefilter('ignore', FutureWarning) values = spots.where(spots, drop=True) image[round_, ch, values.z, values.y, values.x] = values intensities.values = img_as_float32(intensities) # add imaging noise image += np.random.poisson(n_photons_background, size=image.shape).astype(np.uint32) # blur image over coordinates, but not over round_/channels (dim 0, 1) sigma = (0, 0) + point_spread_function image = gaussian_filter(image, sigma=sigma, mode='nearest') image = image * camera_detection_efficiency image += np.random.normal(scale=background_electrons, size=image.shape) # mimic analog to digital conversion image = (image / graylevel).astype(int).clip(0, 2**ad_conversion_bits) # clip in case we've picked up some negative values image = np.clip(image, 0, a_max=None) # set the smallest int datatype that supports the data's intensity range image = select_uint_dtype(image) # convert to float for ImageStack with warnings.catch_warnings(): # possible precision loss when casting from uint to float is acceptable warnings.simplefilter('ignore', UserWarning) image = img_as_float32(image) return ImageStack.from_numpy(image)
from starfish.core.spots.FindSpots import FindSpotsAlgorithm from starfish.core.test.factories import ( two_spot_informative_blank_coded_data_factory, two_spot_one_hot_coded_data_factory, two_spot_sparse_coded_data_factory, ) from starfish.types import Axes, Features, FunctionSource # verify all spot finders handle different coding types _, ONE_HOT_IMAGESTACK, ONE_HOT_MAX_INTENSITY = two_spot_one_hot_coded_data_factory() _, SPARSE_IMAGESTACK, SPARSE_MAX_INTENSITY = two_spot_sparse_coded_data_factory() _, BLANK_IMAGESTACK, BLANK_MAX_INTENSITY = two_spot_informative_blank_coded_data_factory() # make sure that all spot finders handle empty arrays EMPTY_IMAGESTACK = ImageStack.from_numpy(np.zeros((4, 2, 10, 100, 100), dtype=np.float32)) def simple_gaussian_spot_detector() -> BlobDetector: """create a basic gaussian spot detector""" return BlobDetector( min_sigma=1, max_sigma=4, num_sigma=5, threshold=0, measurement_type='max') # initialize spot detectors gaussian_spot_detector = simple_gaussian_spot_detector()
def run(self, stack: ImageStack, *args) -> ImageStack: numpy_array = stack.xarray numpy_array = numpy_array + stack.xarray return ImageStack.from_numpy(numpy_array)
def generate_default_data(): data = np.random.rand(2, 2, 2, 40, 50).astype(np.float32) return ImageStack.from_numpy(data)