예제 #1
0
def cluster_blobs(img_path, suffix=None):
    """Cluster blobs and save to Numpy archive.
    
    Args:
        img_path (str): Base path from which registered labels and blobs files
            will be found and output blobs file save location will be
            constructed.
        suffix (str): Suffix for ``path``; defaults to None.

    Returns:

    """
    mod_path = img_path
    if suffix is not None:
        mod_path = libmag.insert_before_ext(img_path, suffix)
    labels_img_np = sitk_io.load_registered_img(
        mod_path, config.RegNames.IMG_LABELS.value)
    blobs = detector.Blobs().load_blobs(np_io.img_to_blobs_path(img_path))
    scaling, res = np_io.find_scaling(img_path, labels_img_np.shape)
    if blobs is None:
        libmag.warn("unable to load nuclei coordinates")
        return

    # append label IDs to blobs and scale to make isotropic
    blobs_clus = ClusterByLabel.cluster_by_label(blobs.blobs[:, :3],
                                                 labels_img_np, scaling, res)
    print(blobs_clus)
    out_path = libmag.combine_paths(mod_path, config.SUFFIX_BLOB_CLUSTERS)
    np.save(out_path, blobs_clus)
예제 #2
0
def plot_knns(img_paths, suffix=None, show=False, names=None):
    """Plot k-nearest-neighbor distances for multiple sets of blobs,
    overlaying on a single plot.

    Args:
        img_paths (List[str]): Base paths from which registered labels and
            blobs files will be found and output blobs file save location
            will be constructed.
        suffix (str): Suffix for ``path``; defaults to None.
        show (bool): True to plot the distances; defaults to False.
        names (List[str]): Sequence of names corresponding to ``img_paths``
            for the plot legend.

    """
    cluster_settings = config.atlas_profile[profiles.RegKeys.METRICS_CLUSTER]
    knn_n = cluster_settings[profiles.RegKeys.KNN_N]
    if not knn_n:
        knn_n = cluster_settings[profiles.RegKeys.DBSCAN_MINPTS] - 1
    print("Calculating k-nearest-neighbor distances and plotting distances "
          "for neighbor {}".format(knn_n))

    # set up combined data frames for all samples at each zoom level
    df_keys = ("ov", "zoom")
    dfs_comb = {key: [] for key in df_keys}
    names_disp = names if names else []
    for i, img_path in enumerate(img_paths):
        # load blobs associated with image
        mod_path = img_path
        if suffix is not None:
            mod_path = libmag.insert_before_ext(img_path, suffix)
        labels_img_np = sitk_io.load_registered_img(
            mod_path, config.RegNames.IMG_LABELS.value)
        blobs = detector.Blobs().load_blobs(np_io.img_to_blobs_path(img_path))
        scaling, res = np_io.find_scaling(img_path, labels_img_np.shape)
        if blobs is None:
            libmag.warn("unable to load nuclei coordinates for", img_path)
            continue
        # convert to physical units and display k-nearest-neighbors for nuclei
        blobs_phys = np.multiply(blobs.blobs[:, :3], res)
        # TESTING: given the same blobs, simply shift
        #blobs = np.multiply(blobs[i*10000000:, :3], res)
        _, _, dfs = knn_dist(blobs_phys, knn_n, 2, 1000000, False)
        if names is None:
            # default to naming from filename
            names_disp.append(os.path.basename(mod_path))
        for j, df in enumerate(dfs):
            dfs_comb[df_keys[j]].append(df)

    for key in dfs_comb:
        # combine data frames at each zoom level, save, and plot with
        # different colors for each image
        df = df_io.join_dfs(dfs_comb[key], "point")
        dist_cols = [col for col in df.columns if col.startswith("dist")]
        rename_cols = {col: name for col, name in zip(dist_cols, names_disp)}
        df = df.rename(rename_cols, axis=1)
        out_path = "knn_dist_combine_{}".format(key)
        df_io.data_frames_to_csv(df, out_path)
        plot_2d.plot_lines(out_path,
                           "point",
                           rename_cols.values(),
                           df=df,
                           show=show,
                           title=config.plot_labels[config.PlotLabels.TITLE])
예제 #3
0
def make_density_image(
    img_path: str,
    scale: Optional[float] = None,
    shape: Optional[Sequence[int]] = None,
    suffix: Optional[str] = None,
    labels_img_sitk: Optional[sitk.Image] = None,
    channel: Optional[Sequence[int]] = None,
    matches: Dict[Tuple[int, int], "colocalizer.BlobMatch"] = None,
    atlas_profile: Optional["atlas_prof.AtlasProfile"] = None
) -> Tuple[np.ndarray, str]:
    """Make a density image based on associated blobs.
    
    Uses the size and resolutions of the original image stores in the blobs
    if available to determine scaling between the blobs and the output image.
    Otherwise, uses the shape of the registered labels image to set 
    the voxel sizes for the blobs.
    
    If ``matches`` is given, a heat map will be generated for each set
    of channels given in the dictionary. Otherwise, if the loaded blobs
    file has intensity-based colocalizations, a heat map will be generated
    for each combination of channels.
    
    Args:
        img_path: Path to image, which will be used to indentify the blobs file.
        scale: Scaling factor between the blobs' space and the output space;
            defaults to None to use the register. Scaling is found by
            :meth:`magmap.np_io.find_scaling`.
        shape: Output shape, used for scaling; defaults to None.
        suffix: Modifier to append to end of ``img_path`` basename for 
            registered image files that were output to a modified name; 
            defaults to None.
        labels_img_sitk: Labels image; defaults to None to load from a
            registered labels image.
        channel: Sequence of channels to include in density image. For
            multiple channels, blobs from all these channels are combined
            into one heatmap.  Defaults to None to use all channels.
        matches: Dictionary of channel combinations to blob matches; defaults
            to None.
        atlas_profile: Atlas profile, used for scaling; defaults to None.
    
    Returns:
        Tuple of the density image as a Numpy array in the
        same shape as the opened image and the original and ``img_path``
        to track such as for multiprocessing.
    
    """
    def make_heat_map():
        # build heat map to store densities per label px and save to file
        coord_scaled = ontology.scale_coords(blobs_chl[:, :3], scaling,
                                             labels_img.shape)
        _logger.debug("Scaled coords:\n%s", coord_scaled)
        return cv_nd.build_heat_map(labels_img.shape, coord_scaled)

    # set up paths and get labels image
    _logger.info("\n\nGenerating heat map from blobs")
    mod_path = img_path
    if suffix is not None:
        mod_path = libmag.insert_before_ext(img_path, suffix)

    # load blobs
    blobs = detector.Blobs().load_blobs(np_io.img_to_blobs_path(img_path))

    is_2d = False
    if (shape is not None and blobs.roi_size is not None
            and blobs.resolutions is not None):
        # prepare output image and scaling factor from it to the blobs
        scaling = np.divide(shape, blobs.roi_size)
        labels_spacing = np.divide(blobs.resolutions[0], scaling)
        labels_img = np.zeros(shape, dtype=np.uint8)
        labels_img_sitk = sitk.GetImageFromArray(labels_img)
        labels_img_sitk.SetSpacing(labels_spacing[::-1])

    else:
        # default to use labels image as the size of the output image
        if labels_img_sitk is None:
            labels_img_sitk = sitk_io.load_registered_img(
                mod_path, config.RegNames.IMG_LABELS.value, get_sitk=True)
        labels_img = sitk.GetArrayFromImage(labels_img_sitk)

        is_2d = labels_img.ndim == 2
        if is_2d:
            # temporarily convert 2D images to 3D
            labels_img = labels_img[None]

        # find the scaling between the blobs and the labels image
        target_size = (None if atlas_profile is None else
                       atlas_profile["target_size"])
        scaling = np_io.find_scaling(img_path, labels_img.shape, scale,
                                     target_size)[0]

        if shape is not None:
            # scale blob coordinates and heat map to an alternative final shape
            scaling = np.divide(shape, np.divide(labels_img.shape, scaling))
            labels_spacing = np.multiply(labels_img_sitk.GetSpacing()[::-1],
                                         np.divide(labels_img.shape, shape))
            labels_img = np.zeros(shape, dtype=labels_img.dtype)
            labels_img_sitk.SetSpacing(labels_spacing[::-1])
    _logger.debug("Using image scaling: {}".format(scaling))

    # annotate blobs based on position
    blobs_chl = blobs.blobs
    if channel is not None:
        _logger.info(
            "Using blobs from channel(s), combining if multiple channels: %s",
            channel)
        blobs_chl = blobs_chl[np.isin(
            detector.Blobs.get_blobs_channel(blobs_chl), channel)]
    heat_map = make_heat_map()
    if is_2d:
        # convert back to 3D
        heat_map = heat_map[0]
    imgs_write = {
        config.RegNames.IMG_HEAT_MAP.value:
        sitk_io.replace_sitk_with_numpy(labels_img_sitk, heat_map)
    }

    heat_colocs = None
    if matches:
        # create heat maps for match-based colocalization combos
        heat_colocs = []
        for chl_combo, chl_matches in matches.items():
            _logger.info(
                "Generating match-based colocalization heat map "
                "for channel combo: %s", chl_combo)
            # use blobs in first channel of each channel pair for simplicity
            blobs_chl = chl_matches.get_blobs(1)
            heat_colocs.append(make_heat_map())

    elif blobs.colocalizations is not None:
        # create heat map for each intensity-based colocalization combo
        # as a separate channel in output image
        blob_chls = range(blobs.colocalizations.shape[1])
        blob_chls_len = len(blob_chls)
        if blob_chls_len > 1:
            # get all channel combos that include given channels
            combos = []
            chls = blob_chls if channel is None else channel
            for r in range(2, blob_chls_len + 1):
                combos.extend([
                    tuple(c) for c in itertools.combinations(blob_chls, r)
                    if all([h in c for h in chls])
                ])

            heat_colocs = []
            for combo in combos:
                _logger.info(
                    "Generating intensity-based colocalization heat map "
                    "for channel combo: %s", combo)
                blobs_chl = blobs.blobs[np.all(np.equal(
                    blobs.colocalizations[:, combo], 1),
                                               axis=1)]
                heat_colocs.append(make_heat_map())

    if heat_colocs is not None:
        # combine heat maps into single image
        heat_colocs = np.stack(heat_colocs, axis=3)
        imgs_write[config.RegNames.IMG_HEAT_COLOC.value] = \
            sitk_io.replace_sitk_with_numpy(
                labels_img_sitk, heat_colocs)

    # write images to file
    sitk_io.write_reg_images(imgs_write, mod_path)
    return heat_map, img_path
예제 #4
0
def make_density_image(img_path, scale=None, shape=None, suffix=None, 
                       labels_img_sitk=None, channel=None, matches=None):
    """Make a density image based on associated blobs.
    
    Uses the shape of the registered labels image by default to set 
    the voxel sizes for the blobs.
    
    If ``matches`` is given, a heat map will be generated for each set
    of channels given in the dictionary. Otherwise, if the loaded blobs
    file has intensity-based colocalizations, a heat map will be generated
    for each combination of channels.
    
    Args:
        img_path: Path to image, which will be used to indentify the blobs file.
        scale: Rescaling factor as a scalar value to find the corresponding 
            full-sized image. Defaults to None to use the register 
            setting ``target_size`` instead if available, falling back 
            to load the full size image to find its shape if necessary.
        shape: Final shape size; defaults to None to use the shape of 
            the labels image.
        suffix: Modifier to append to end of ``img_path`` basename for 
            registered image files that were output to a modified name; 
            defaults to None.
        labels_img_sitk: Labels image as a SimpleITK ``Image`` object; 
            defaults to None, in which case the registered labels image file 
            corresponding to ``img_path`` with any ``suffix`` modifier 
            will be opened.
        channel (List[int]): Sequence of channels to include in density image;
            defaults to None to combine blobs from all channels.
        matches (dict[tuple[int, int], :class:`magmap.cv.colocalizer`):
            Dictionary of channel combinations to blob matches; defaults to
            None.
    
    Returns:
        :obj:`np.ndarray`, str: The density image as a Numpy array in the
        same shape as the opened image and the original and ``img_path``
        to track such as for multiprocessing.
    """
    def make_heat_map():
        # build heat map to store densities per label px and save to file
        coord_scaled = ontology.scale_coords(
            blobs_chl[:, :3], scaling, labels_img.shape)
        print("coords", coord_scaled)
        return cv_nd.build_heat_map(labels_img.shape, coord_scaled)
    
    # set up paths and get labels image
    mod_path = img_path
    if suffix is not None:
        mod_path = libmag.insert_before_ext(img_path, suffix)
    if labels_img_sitk is None:
        labels_img_sitk = sitk_io.load_registered_img(
            mod_path, config.RegNames.IMG_LABELS.value, get_sitk=True)
    labels_img = sitk.GetArrayFromImage(labels_img_sitk)
    
    # load blobs
    blobs = detector.Blobs().load_blobs(np_io.img_to_blobs_path(img_path))
    scaling = np_io.find_scaling(img_path, labels_img.shape, scale)[0]
    if shape is not None:
        # scale blob coordinates and heat map to an alternative final shape
        scaling = np.divide(shape, np.divide(labels_img.shape, scaling))
        labels_spacing = np.multiply(
            labels_img_sitk.GetSpacing()[::-1], 
            np.divide(labels_img.shape, shape))
        labels_img = np.zeros(shape, dtype=labels_img.dtype)
        labels_img_sitk.SetSpacing(labels_spacing[::-1])
    print("using scaling: {}".format(scaling))
    
    # annotate blobs based on position
    blobs_chl = blobs.blobs
    if channel is not None:
        blobs_chl = blobs_chl[np.isin(detector.get_blobs_channel(
            blobs_chl), channel)]
    heat_map = make_heat_map()
    print("heat map", heat_map.shape, heat_map.dtype, labels_img.shape)
    imgs_write = {
        config.RegNames.IMG_HEAT_MAP.value:
            sitk_io.replace_sitk_with_numpy(labels_img_sitk, heat_map)}
    
    heat_colocs = None
    if matches:
        # create heat maps for match-based colocalization combos
        heat_colocs = []
        for chl_combo, chl_matches in matches.items():
            print("Generating match-based colocalization heat map "
                  "for channel combo:", chl_combo)
            # use blobs in first channel of each channel pair for simplicity
            blobs_chl = chl_matches.get_blobs(1)
            heat_colocs.append(make_heat_map())
    
    elif blobs.colocalizations is not None:
        # create heat map for each intensity-based colocalization combo
        # as a separate channel in output image
        blob_chls = range(blobs.colocalizations.shape[1])
        blob_chls_len = len(blob_chls)
        if blob_chls_len > 1:
            # get all channel combos that include given channels
            combos = []
            chls = blob_chls if channel is None else channel
            for r in range(2, blob_chls_len + 1):
                combos.extend(
                    [tuple(c) for c in itertools.combinations(blob_chls, r)
                     if all([h in c for h in chls])])
            
            heat_colocs = []
            for combo in combos:
                print("Generating intensity-based colocalization heat map "
                      "for channel combo:", combo)
                blobs_chl = blobs.blobs[np.all(np.equal(
                    blobs.colocalizations[:, combo], 1), axis=1)]
                heat_colocs.append(make_heat_map())
    
    if heat_colocs is not None:
        # combine heat maps into single image
        heat_colocs = np.stack(heat_colocs, axis=3)
        imgs_write[config.RegNames.IMG_HEAT_COLOC.value] = \
            sitk_io.replace_sitk_with_numpy(
                labels_img_sitk, heat_colocs)
    
    # write images to file
    sitk_io.write_reg_images(imgs_write, mod_path)
    return heat_map, img_path