def _assign( label_image: np.ndarray, intensities: IntensityTable, in_place: bool, ) -> IntensityTable: if len(label_image.shape) == 3: cell_ids = label_image[ intensities[Axes.ZPLANE.value].values, intensities[Axes.Y.value].values, intensities[Axes.X.value].values ] elif len(label_image.shape) == 2: cell_ids = label_image[ intensities[Axes.Y.value].values, intensities[Axes.X.value].values ] else: raise ValueError( f"`label_image` must be 2 or 3 dimensional, not {len(label_image.shape)}D." ) if not in_place: intensities = intensities.copy() intensities[Features.CELL_ID] = (Features.AXIS, cell_ids) return intensities
def _max_intensity_table_maintain_dims( intensity_table: IntensityTable, dimensions: Set[Axes], ) -> IntensityTable: """ Maximum project an IntensityTable over dimensions, retaining singletons in place of dimensions reduced by the max operation. For example, xarray.max(Axes.CH.value) would usually produce a 2-d (features, rounds) output. This function ensures that the output is instead (features, 1, rounds), by inserting singleton dimensions in place of any projected axes. Parameters ---------- intensity_table : IntensityTable Input intensities dimensions : Set[Axes] Dimensions to project Returns ------- IntensityTable : full-dimensionality (3-d) IntensityTable """ str_dimensions = _normalize_axes(dimensions) initial_dimensions = OrderedDict(intensity_table.sizes) projected_intensities = intensity_table.max(str_dimensions) expanded_intensities = projected_intensities.expand_dims(str_dimensions) return expanded_intensities.transpose(*tuple(initial_dimensions.keys()))
def _cli(ctx, label_image, intensities, output): print('Assigning targets to cells...') ctx.obj = dict(component=TargetAssignment, output=output, intensity_table=IntensityTable.load(intensities), label_image=imread(label_image))
def run( self, stack: ImageStack, ) -> Tuple[IntensityTable, ConnectedComponentDecodingResult]: """decode pixels and combine them into spots using connected component labeling Parameters ---------- stack : ImageStack ImageStack containing spots Returns ------- IntensityTable : IntensityTable containing decoded spots ConnectedComponentDecodingResult : Results of connected component labeling """ pixel_intensities = IntensityTable.from_image_stack( stack, crop_x=self.crop_x, crop_y=self.crop_y, crop_z=self.crop_z) decoded_intensities = self.codebook.metric_decode( pixel_intensities, max_distance=self.distance_threshold, min_intensity=self.magnitude_threshold, norm_order=self.norm_order, metric=self.metric ) caf = CombineAdjacentFeatures( min_area=self.min_area, max_area=self.max_area, mask_filtered_features=True ) decoded_spots, image_decoding_results = caf.run(intensities=decoded_intensities) return decoded_spots, image_decoding_results
def intensities(self, codebook=None) -> IntensityTable: if codebook is None: codebook = self.codebook() intensities = IntensityTable.synthetic_intensities( codebook, self.n_z, self.height, self.width, self.n_spots, self.mean_fluor_per_spot, self.mean_photons_per_fluor) assert intensities.dtype == np.float32 and intensities.max() <= 1 return intensities
def _cli(ctx, input, output, codebook): ctx.obj = dict( component=Decoder, input=input, output=output, intensities=IntensityTable.load(input), codebook=Codebook.from_json(codebook), )
def test_run_pipline(self): tempdir = exec.stages(self.stages, self.subdirs, keep_data=True) intensities = IntensityTable.load( os.path.join(tempdir, "results", self.spots_file)) self.verify_results(intensities) if os.getenv("TEST_KEEP_DATA") is None: shutil.rmtree(tempdir)
def test_imagestack_to_intensity_table_no_noise( synthetic_spot_pass_through_stack): codebook, intensity_table, image = synthetic_spot_pass_through_stack pixel_intensities = IntensityTable.from_image_stack(image) pixel_intensities = codebook.metric_decode(pixel_intensities, max_distance=0, min_intensity=1000, norm_order=2) assert isinstance(pixel_intensities, IntensityTable)
def _cli(ctx, input, output, codebook): """assign genes to spots""" ctx.obj = dict( component=Decoder, input=input, output=output, intensities=IntensityTable.load(input), codebook=codebook, )
def test_imagestack_to_intensity_table(): codebook, intensity_table, image = codebook_intensities_image_for_single_synthetic_spot( ) pixel_intensities = IntensityTable.from_image_stack(image) pixel_intensities = codebook.metric_decode(pixel_intensities, max_distance=0, min_intensity=1000, norm_order=2) assert isinstance(pixel_intensities, IntensityTable)
def measure_spot_intensities( data_image: ImageStack, spot_attributes: SpotAttributes, measurement_function: Callable[[Sequence], Number], radius_is_gyration: bool = False, ) -> IntensityTable: """given spots found from a reference image, find those spots across a data_image Parameters ---------- data_image : ImageStack ImageStack containing multiple volumes for which spots' intensities must be calculated spot_attributes : pd.Dataframe Locations and radii of spots measurement_function : Callable[[Sequence], Number]) Function to apply over the spot volumes to identify the intensity (e.g. max, mean, ...) radius_is_gyration : bool if True, indicates that the radius corresponds to radius of gyration, which is a function of spot intensity, but typically is a smaller unit than the sigma generated by blob_log. In this case, the spot's bounding box is rounded up instead of down when measuring intensity. (default False) Returns ------- IntensityTable : 3d tensor of (spot, channel, round) information for each coded spot """ # determine the shape of the intensity table n_ch = data_image.shape[Axes.CH] n_round = data_image.shape[Axes.ROUND] # construct the empty intensity table intensity_table = IntensityTable.empty_intensity_table( spot_attributes=spot_attributes, n_ch=n_ch, n_round=n_round, ) # if no spots were detected, return the empty IntensityTable if intensity_table.sizes[Features.AXIS] == 0: return intensity_table # fill the intensity table indices = product(range(n_ch), range(n_round)) for c, r in indices: image, _ = data_image.get_slice({Axes.CH: c, Axes.ROUND: r}) blob_intensities: pd.Series = measure_spot_intensity( image, spot_attributes, measurement_function, radius_is_gyration=radius_is_gyration) intensity_table[:, c, r] = blob_intensities return intensity_table
def testing_data(): np.random.seed(1) codebook = test_utils.codebook_array_factory() num_z, height, width = 3, 4, 5 intensities = IntensityTable.synthetic_intensities(codebook, num_z=num_z, height=height, width=width, n_spots=4) return intensities
def test_reshaping_between_stack_and_intensities(): """ transform an pixels of an ImageStack into an IntensityTable and back again, then verify that the created Imagestack is the same as the original """ np.random.seed(777) image = ImageStack.from_numpy_array( np.random.rand(1, 2, 3, 4, 5).astype(np.float32)) pixel_intensities = IntensityTable.from_image_stack(image, 0, 0, 0) image_shape = (image.shape['z'], image.shape['y'], image.shape['x']) image_from_pixels = pixel_intensities_to_imagestack( pixel_intensities, image_shape) assert np.array_equal(image.xarray, image_from_pixels.xarray)
def transfer_physical_coords_from_imagestack_to_intensity_table(image_stack: ImageStack, intensity_table: IntensityTable ) -> IntensityTable: """ Transfers physical coordinates from an Imagestack's coordinates xarray to an intensity table 1. Creates three new coords on the intensity table (xc, yc, zc) 2. For every spot: - Get pixel x,y values - Calculate the physical x,y values - Assign those values to the coords arrays for this spot """ # Add three new coords to xarray (xc, yc, zc) num_features = intensity_table.sizes[Features.AXIS] intensity_table[Coordinates.X.value] = xr.DataArray(np.zeros(num_features, np.float32), dims='features') intensity_table[Coordinates.Y.value] = xr.DataArray(np.zeros(num_features, np.float32), dims='features') intensity_table[Coordinates.Z.value] = xr.DataArray(np.zeros(num_features, np.float32), dims='features') for ind, spot in intensity_table.groupby(Features.AXIS): # Iterate through r, ch per spot for ch, _round in np.ndindex(spot.data.shape): # if non zero value set coords if spot[ch][_round].data == 0: continue # get pixel coords of this tile pixel_x = spot.coords[Axes.X].data pixel_y = spot.coords[Axes.Y].data pixel_z = spot.coords[Axes.ZPLANE].data tile_indices = { Axes.ROUND.value: _round, Axes.CH.value: ch, Axes.ZPLANE.value: pixel_z, } # Get corresponding physical coordinates physical_coords = get_physical_coordinates_of_spot( image_stack._coordinates, tile_indices, pixel_x, pixel_y, image_stack._tile_shape) # Assign to coordinates arrays intensity_table[Coordinates.X.value][ind] = physical_coords[0] intensity_table[Coordinates.Y.value][ind] = physical_coords[1] intensity_table[Coordinates.Z.value][ind] = physical_coords[2] break return intensity_table
def run( self, primary_image: ImageStack, n_processes: Optional[int] = None, *args, ) -> Tuple[IntensityTable, ConnectedComponentDecodingResult]: """decode pixels and combine them into spots using connected component labeling Parameters ---------- primary_image : ImageStack ImageStack containing spots n_processes : Optional[int] The number of processes to use for CombineAdjacentFeatures. If None, uses the output of os.cpu_count() (default = None). Returns ------- IntensityTable : IntensityTable containing decoded spots ConnectedComponentDecodingResult : Results of connected component labeling """ pixel_intensities = IntensityTable.from_image_stack( primary_image, crop_x=self.crop_x, crop_y=self.crop_y, crop_z=self.crop_z) decoded_intensities = self.codebook.metric_decode( pixel_intensities, max_distance=self.distance_threshold, min_intensity=self.magnitude_threshold, norm_order=self.norm_order, metric=self.metric ) caf = CombineAdjacentFeatures( min_area=self.min_area, max_area=self.max_area, mask_filtered_features=True ) decoded_spots, image_decoding_results = caf.run(intensities=decoded_intensities, n_processes=n_processes) transfer_physical_coords_from_imagestack_to_intensity_table(image_stack=primary_image, intensity_table=decoded_spots) return decoded_spots, image_decoding_results
def concatenate_spot_attributes_to_intensities( spot_attributes: Sequence[Tuple[SpotAttributes, Dict[Indices, int]]] ) -> IntensityTable: """ Merge multiple spot attributes frames into a single IntensityTable without merging across channels and imaging rounds Parameters ---------- spot_attributes : Sequence[Tuple[SpotAttributes, Dict[Indices, int]]] A sequence of SpotAttribute objects and the Indices (channel, round) that each object is associated with. Returns ------- IntensityTable : concatenated input SpotAttributes, converted to an IntensityTable object """ n_ch: int = max(inds[Indices.CH] for _, inds in spot_attributes) + 1 n_round: int = max(inds[Indices.ROUND] for _, inds in spot_attributes) + 1 all_spots = pd.concat([sa.data for sa, inds in spot_attributes]) # this drop call ensures only x, y, z, radius, and quality, are passed to the IntensityTable features_coordinates = all_spots.drop(['spot_id', 'intensity'], axis=1) intensity_table = IntensityTable.empty_intensity_table( SpotAttributes(features_coordinates), n_ch, n_round, ) i = 0 for attrs, inds in spot_attributes: for _, row in attrs.data.iterrows(): intensity_table[i, inds[Indices.CH], inds[Indices.ROUND]] = row['intensity'] i += 1 return intensity_table
def small_intensity_table(): intensities = np.array([ [[0, 1], [1, 0]], [[1, 0], [0, 1]], [[0, 0], [1, 1]], [ [0.5, 0.5], # this one should fail decoding [0.5, 0.5] ], [[0.1, 0], [0, 0.1]], # this one is a candidate for intensity filtering ]) spot_attributes = pd.DataFrame( data={ Indices.X.value: [0, 1, 2, 3, 4], Indices.Y.value: [3, 4, 5, 6, 7], Indices.Z.value: [0, 0, 0, 0, 0], Features.SPOT_RADIUS: [0.1, 2, 3, 2, 1] }) return IntensityTable.from_spot_data(intensities, spot_attributes)
def _calculate_mean_pixel_traces( label_image: np.ndarray, intensities: IntensityTable, ) -> IntensityTable: """ For all pixels that contribute to a connected component, calculate the mean value for each (ch, round), producing an average "trace" of a feature across the imaging experiment Parameters ---------- label_image : np.ndarray An image where all pixels of a connected component share the same integer ID intensities : IntensityTable decoded intensities Returns ------- IntensityTable : an IntensityTable where the number of features equals the number of connected components and the intensities of each each feature is its mean trace. """ pixel_labels = label_image.reshape(-1) intensities['spot_id'] = (Features.AXIS, pixel_labels) mean_pixel_traces = intensities.groupby('spot_id').mean(Features.AXIS) mean_distances = intensities[Features.DISTANCE].groupby('spot_id').mean(Features.AXIS) mean_pixel_traces[Features.DISTANCE] = ( 'spot_id', np.ravel(mean_distances) ) # the 0th pixel trace corresponds to background. If present, drop it. try: mean_pixel_traces = mean_pixel_traces.drop(0, dim='spot_id') except KeyError: pass return mean_pixel_traces
def _build_intensity_table( round_dataframes: Dict[int, pd.DataFrame], dist: pd.DataFrame, ind: pd.DataFrame, channels: Sequence[int], rounds: Sequence[int], search_radius: int, anchor_round: int, ) -> IntensityTable: """Construct an intensity table from the results of a local search over detected spots Parameters ---------- round_dataframes : Dict[int, pd.DataFrame] Output from _merge_spots_by_round, contains mapping of image volumes from each round to all the spots detected in them. dist, ind : pd.DataFrame Output from _match_spots, contains distances and indices to the nearest spot for each spot in anchor_round. channels, rounds : Sequence[int] Channels and rounds present in the ImageStack from which spots were detected. search_radius : int The maximum (euclidean) distance in pixels for a spot to be considered matching in a round subsequent to the anchor round. anchor_round : int The imaging round to seed the local search from. """ anchor_df = round_dataframes[anchor_round] # create empty IntensityTable filled with np.nan data = np.full((dist.shape[0], len(channels), len(rounds)), fill_value=np.nan) dims = (Features.AXIS, Axes.CH.value, Axes.ROUND.value) coords = { Features.SPOT_RADIUS: (Features.AXIS, anchor_df[Features.SPOT_RADIUS]), Axes.ZPLANE.value: (Features.AXIS, anchor_df[Axes.ZPLANE]), Axes.Y.value: (Features.AXIS, anchor_df[Axes.Y]), Axes.X.value: (Features.AXIS, anchor_df[Axes.X]), Axes.ROUND.value: (Axes.ROUND.value, rounds), Axes.CH.value: (Axes.CH.value, channels) } intensity_table = IntensityTable(data=data, dims=dims, coords=coords) # fill IntensityTable for r in rounds: # get intensity data and indices spot_indices = ind[r] intensity_data = round_dataframes[r].loc[spot_indices, 'intensity'] channel_index = round_dataframes[r].loc[spot_indices, Axes.CH] round_index = np.full(ind.shape[0], fill_value=r, dtype=int) feature_index = np.arange(ind.shape[0], dtype=int) # mask spots that are outside the search radius mask = np.asarray( dist[r] < search_radius) # indices need not match feature_index = feature_index[mask] channel_index = channel_index[mask] round_index = round_index[mask] intensity_data = intensity_data[mask] # need numpy indexing to set values in vectorized manner intensity_table.values[feature_index, channel_index, round_index] = intensity_data return intensity_table
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 indices = zip((Indices.Z.value, Indices.Y.value, Indices.X.value), (num_z, height, width)) for index, requested_size in indices: required_size = intensities.coords[index].values.max() if required_size > requested_size: raise ValueError( f'locations of intensities contained in table exceed the size of requested ' f'dimension {index}. Required size {required_size} > {requested_size}.' ) # create an empty array of the correct size image = np.zeros( (intensities.sizes[Indices.ROUND.value], intensities.sizes[Indices.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 cls.from_numpy_array(image)
def metric_decode(self, intensities: IntensityTable, max_distance: Number, min_intensity: Number, norm_order: int, metric: str = 'euclidean') -> IntensityTable: """Assign the closest target by euclidean distance to each feature in an intensity table Normalizes both the codes and the features to be unit vectors and finds the closest code for each feature Parameters ---------- intensities : IntensityTable features to be decoded max_distance : Number maximum distance between a feature and its closest code for which the coded target will be assigned. min_intensity : Number minimum intensity for a feature to receive a target annotation norm_order : int the scipy.linalg norm to apply to normalize codes and intensities metric : str the sklearn metric string to pass to NearestNeighbors See Also -------- The available norms for this function can be found at the following link: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.linalg.norm.html Returns ------- IntensityTable : Intensity table containing normalized intensities, target assignments, distances to the nearest code, and the filtering status of each feature. """ self._validate_decode_intensity_input_matches_codebook_shape( intensities) # normalize both the intensities and the codebook norm_intensities, norms = self._normalize_features( intensities, norm_order=norm_order) norm_codes, _ = self._normalize_features(self, norm_order=norm_order) metric_outputs, targets = self._approximate_nearest_code( norm_codes, norm_intensities, metric=metric) # only targets with low distances and high intensities should be retained passes_filters = np.logical_and(norms >= min_intensity, metric_outputs <= max_distance, dtype=np.bool) # set targets, distances, and filtering results norm_intensities[Features.TARGET] = (Features.AXIS, targets) norm_intensities[Features.DISTANCE] = (Features.AXIS, metric_outputs) norm_intensities[Features.PASSES_THRESHOLDS] = (Features.AXIS, passes_filters) # norm_intensities is a DataArray, make it back into an IntensityTable return IntensityTable(norm_intensities)
def run( self, intensities: IntensityTable ) -> Tuple[IntensityTable, ConnectedComponentDecodingResult]: """ Execute the combine_adjacent_features method on an IntensityTable containing pixel intensities Parameters ---------- intensities : IntensityTable Pixel intensities of an imaging experiment Returns ------- IntensityTable : Table whose features comprise sets of adjacent pixels that decoded to the same target ConnectedComponentDecodingResult : NamedTuple containing : region_properties : the properties of each connected component, in the same order as the IntensityTable label_image : np.ndarray An image where all pixels of a connected component share the same integer ID decoded_image : np.ndarray Image whose pixels correspond to the targets that the given position in the ImageStack decodes to. """ # map target molecules to integers so they can be reshaped into an image that can # be subjected to a connected-component algorithm to find adjacent pixels with the # same targets targets = intensities[Features.TARGET].values target_map = TargetsMap(targets) # create the decoded_image decoded_image = self._intensities_to_decoded_image( intensities, target_map, self._mask_filtered, ) # label the decoded image to extract connected component features label_image: np.ndarray = label(decoded_image, connectivity=self._connectivity) # calculate properties of each feature props: List = regionprops(np.squeeze(label_image)) # calculate mean intensities across the pixels of each feature mean_pixel_traces = self._calculate_mean_pixel_traces( label_image, intensities, ) # Create SpotAttributes and determine feature filtering outcomes spot_attributes, passes_filter = self._create_spot_attributes( props, decoded_image, target_map, ) # augment the SpotAttributes with filtering results and distances from nearest codes spot_attributes.data[Features.DISTANCE] = mean_pixel_traces[ Features.DISTANCE] spot_attributes.data[Features.PASSES_THRESHOLDS] = passes_filter # create new indexes for the output IntensityTable channel_index = mean_pixel_traces.indexes[Axes.CH] round_index = mean_pixel_traces.indexes[Axes.ROUND] coords = IntensityTable._build_xarray_coords(spot_attributes, channel_index, round_index) # create the output IntensityTable dims = (Features.AXIS, Axes.CH.value, Axes.ROUND.value) intensity_table = IntensityTable(data=mean_pixel_traces, coords=coords, dims=dims) # combine the various non-IntensityTable results into a NamedTuple before returning ccdr = ConnectedComponentDecodingResult(props, label_image, decoded_image) return intensity_table, ccdr
def synthetic_intensity_table(loaded_codebook) -> IntensityTable: return IntensityTable.synthetic_intensities(loaded_codebook, n_spots=2)
def decode_per_round_max(self, intensities: IntensityTable) -> IntensityTable: """decode each feature by selecting the per-imaging-round max-valued channel Notes ----- - If no code matches the per-round maximum for a feature, it will be assigned 'nan' instead of a target value - Numpy's argmax breaks ties by picking the first channel -- this can lead to unexpected results where some features with "tied" channels will decode, but others will be assigned 'nan'. Parameters ---------- intensities : IntensityTable features to be decoded Returns ------- IntensityTable : intensity table containing additional data variables for target assignments """ def _view_row_as_element(array: np.ndarray) -> np.ndarray: """view an entire code as a single element This view allows vectors (codes) to be compared for equality without need for multiple comparisons by casting the data in each code to a structured dtype that registers as a single value Parameters ---------- array : np.ndarray 2-dimensional numpy array of shape (n_observations, (n_ch * n_round)) where observations may be either features or codes. Returns ------- np.ndarray : 1-dimensional vector of shape n_observations """ nrows, ncols = array.shape dtype = { 'names': ['f{}'.format(i) for i in range(ncols)], 'formats': ncols * [array.dtype] } return array.view(dtype) self._validate_decode_intensity_input_matches_codebook_shape( intensities) max_channels = intensities.argmax(Indices.CH.value) codes = self.argmax(Indices.CH.value) # TODO ambrosejcarr, dganguli: explore this quality score further # calculate distance scores by evaluating the fraction of signal in each round that is # found in the non-maximal channels. max_intensities = intensities.max(Indices.CH.value) round_intensities = intensities.sum(Indices.CH.value) distance = 1 - (max_intensities / round_intensities).mean( Indices.ROUND.value) a = _view_row_as_element(codes.values.reshape(self.shape[0], -1)) b = _view_row_as_element( max_channels.values.reshape(intensities.shape[0], -1)) targets = np.full(intensities.shape[0], fill_value=np.nan, dtype=object) # decode the intensities for i in np.arange(codes.shape[0]): targets[np.where(a[i] == b)[0]] = codes[Features.TARGET][i] # a code passes filters if it decodes successfully passes_filters = ~pd.isnull(targets) intensities[Features.TARGET] = (Features.AXIS, targets.astype('U')) intensities[Features.DISTANCE] = (Features.AXIS, distance) intensities[Features.PASSES_THRESHOLDS] = (Features.AXIS, passes_filters) return intensities