diff --git a/caliban_toolbox/reshape_data.py b/caliban_toolbox/reshape_data.py index 4d7529c..7a1eba5 100644 --- a/caliban_toolbox/reshape_data.py +++ b/caliban_toolbox/reshape_data.py @@ -33,15 +33,18 @@ import xarray as xr from caliban_toolbox.utils import crop_utils, slice_utils, io_utils +from caliban_toolbox.utils.crop_utils import compute_crop_indices, crop_helper -def crop_multichannel_data(X_data, y_data, crop_size, overlap_frac, test_parameters=False): +def crop_multichannel_data(X_data, y_data, crop_size=None, crop_num=None, overlap_frac=0.1, + test_parameters=False): """Reads in a stack of images and crops them into small pieces for easier annotation Args: X_data: xarray containing raw images to be cropped y_data: xarray containing labeled images to be chopped - crop_size: (row_crop, col_crop) tuple specifying shape of the crop + crop_size: (row_crop, col_crop) tuple specifying the length of the crop, including overlap + crop_num: (row_num, col_num) tuple specifying number of crops overlap_frac: fraction that crops will overlap each other on each edge test_parameters: boolean to determine whether to run all fovs, or only the first @@ -72,24 +75,33 @@ def crop_multichannel_data(X_data, y_data, crop_size, overlap_frac, test_paramet X_data, y_data = X_data[:1, ...], y_data[:1, ...] # compute the start and end coordinates for the row and column crops - row_starts, row_ends, row_padding = crop_utils.compute_crop_indices(img_len=X_data.shape[4], - crop_size=crop_size[0], - overlap_frac=overlap_frac) - - col_starts, col_ends, col_padding = crop_utils.compute_crop_indices(img_len=X_data.shape[5], - crop_size=crop_size[1], - overlap_frac=overlap_frac) + if crop_size is not None: + row_starts, row_ends, row_padding = compute_crop_indices(img_len=X_data.shape[4], + crop_size=crop_size[0], + overlap_frac=overlap_frac) + + col_starts, col_ends, col_padding = compute_crop_indices(img_len=X_data.shape[5], + crop_size=crop_size[1], + overlap_frac=overlap_frac) + else: + row_starts, row_ends, row_padding = compute_crop_indices(img_len=X_data.shape[4], + crop_num=crop_num[0], + overlap_frac=overlap_frac) + + col_starts, col_ends, col_padding = compute_crop_indices(img_len=X_data.shape[5], + crop_num=crop_num[1], + overlap_frac=overlap_frac) # crop images - X_data_cropped, padded_shape = crop_utils.crop_helper(X_data, row_starts=row_starts, - row_ends=row_ends, - col_starts=col_starts, col_ends=col_ends, - padding=(row_padding, col_padding)) - - y_data_cropped, padded_shape = crop_utils.crop_helper(y_data, row_starts=row_starts, - row_ends=row_ends, - col_starts=col_starts, col_ends=col_ends, - padding=(row_padding, col_padding)) + X_data_cropped, padded_shape = crop_helper(X_data, row_starts=row_starts, + row_ends=row_ends, + col_starts=col_starts, col_ends=col_ends, + padding=(row_padding, col_padding)) + + y_data_cropped, padded_shape = crop_helper(y_data, row_starts=row_starts, + row_ends=row_ends, + col_starts=col_starts, col_ends=col_ends, + padding=(row_padding, col_padding)) # save relevant parameters for reconstructing image log_data = {} diff --git a/caliban_toolbox/reshape_data_test.py b/caliban_toolbox/reshape_data_test.py index 78af6ae..0215cb2 100644 --- a/caliban_toolbox/reshape_data_test.py +++ b/caliban_toolbox/reshape_data_test.py @@ -57,8 +57,9 @@ def test_crop_multichannel_data(): overlap_frac=overlap_frac, test_parameters=False) - expected_crop_num = len(crop_utils.compute_crop_indices(row_len, crop_size[0], - overlap_frac)[0]) ** 2 + expected_crop_num = len(crop_utils.compute_crop_indices(img_len=row_len, + crop_size=crop_size[0], + overlap_frac=overlap_frac)[0]) ** 2 assert (X_data_cropped.shape == (fov_len, stack_len, expected_crop_num, slice_num, crop_size[0], crop_size[1], channel_len)) diff --git a/caliban_toolbox/utils/crop_utils.py b/caliban_toolbox/utils/crop_utils.py index 20685aa..31fa825 100644 --- a/caliban_toolbox/utils/crop_utils.py +++ b/caliban_toolbox/utils/crop_utils.py @@ -35,12 +35,15 @@ import xarray as xr -def compute_crop_indices(img_len, crop_size, overlap_frac): +def compute_crop_indices(img_len, crop_size=None, crop_num=None, overlap_frac=0): """Determine how to crop the image across one dimension. Args: img_len: length of the image for given dimension - crop_size: size in pixels of the crop in given dimension + crop_size: size in pixels of the crop in given dimension; must be specified if + crop_num not provided + crop_num: number of crops in the given dimension; must be specified if + crop_size not provided overlap_frac: fraction that adjacent crops will overlap each other on each side Returns: @@ -49,8 +52,23 @@ def compute_crop_indices(img_len, crop_size, overlap_frac): int: number of pixels of padding at start and end of image in given dimension """ - # compute overlap fraction in pixels - overlap_pix = math.floor(crop_size * overlap_frac) + # compute indices based on fixed number of pixels per crop + if crop_size is not None: + + # compute overlap fraction in pixels + overlap_pix = math.floor(crop_size * overlap_frac) + + # compute indices based on fixed number of crops + elif crop_num is not None: + # number of pixels in non-overlapping portion of crop + non_overlap_crop_size = np.ceil(img_len / crop_num) + + # Technically this is the fraction the non-overlap, rather than fraction of the whole, + # but we're going to visually crop overlays anyway to make sure value is appropriate + overlap_pix = math.floor(non_overlap_crop_size * overlap_frac) + + # total crop size + crop_size = non_overlap_crop_size + overlap_pix # the crops start at pixel 0, and are spaced crop_size - overlap_pix away from each other start_indices = np.arange(0, img_len - overlap_pix, crop_size - overlap_pix) diff --git a/caliban_toolbox/utils/crop_utils_test.py b/caliban_toolbox/utils/crop_utils_test.py index a33cc23..e49e181 100644 --- a/caliban_toolbox/utils/crop_utils_test.py +++ b/caliban_toolbox/utils/crop_utils_test.py @@ -61,7 +61,7 @@ def _blank_data_xr(fov_len, stack_len, crop_num, slice_num, row_len, col_len, ch return test_stack_xr -def test_compute_crop_indices(): +def test_compute_crop_indices_crop_size(): # test corner case of only one crop img_len, crop_size, overlap_frac = 100, 100, 0.2 starts, ends, padding = crop_utils.compute_crop_indices(img_len=img_len, crop_size=crop_size, @@ -89,6 +89,30 @@ def test_compute_crop_indices(): assert (padding == 0) +def test_compute_crop_indices_num_crops(): + # test corner case of only one crop + img_len, crop_num, overlap_frac = 100, 1, 0.2 + starts, ends, padding = crop_utils.compute_crop_indices(img_len=img_len, crop_num=crop_num, + overlap_frac=overlap_frac) + assert (len(starts) == 1) + assert (len(ends) == 1) + + # test num crops that don't divide evenly into image size + img_len, crop_num, overlap_frac = 105, 3, 0.2 + starts, ends, padding = crop_utils.compute_crop_indices(img_len=img_len, crop_num=crop_num, + overlap_frac=overlap_frac) + assert len(starts) == crop_num + assert ends[-1] == img_len + padding + + # test overlap of 0 between crops + img_len, crop_num, overlap_frac = 200, 10, 0 + starts, ends, padding = crop_utils.compute_crop_indices(img_len=img_len, crop_num=crop_num, + overlap_frac=overlap_frac) + assert (np.all(starts == range(0, 200, 20))) + assert (np.all(ends == range(20, 201, 20))) + assert (padding == 0) + + def test_crop_helper(): # img params fov_len, stack_len, crop_num, slice_num, row_len, col_len, chan_len = 2, 1, 1, 1, 200, 200, 1