Skip to content

Commit

Permalink
Merge pull request #154 from RichieHakim/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
RichieHakim authored Jan 3, 2024
2 parents f7c11b1 + e5af4a7 commit f6aab33
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 83 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ jobs:
platform: [
# ubuntu-latest,
ubuntu-22.04,
# ubuntu-20.04,
ubuntu-20.04,
# # windows-latest,
windows-2022,
# windows-2019,
windows-2019,
# # macos-latest,
macos-12.0,
# macos-11.0,
# macos-12.0,
macos-11.0,
# macos-10.15,
]
python-version: [
Expand Down
2 changes: 1 addition & 1 deletion docs/.readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sphinx:
python:
# version: "3.11"
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
- method: pip
path: .
extra_requirements:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 3 additions & 24 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,9 @@ Contents

howto

roicat


Installation
------------
For detailed instructions, see :doc:`installation`

1. **Recommended: Create a new conda environment**

.. literalinclude:: ../helpers/create_env.txt
inputsAndOutputs

You will need to activate the environment with ``conda activate roicat`` each
time you want to use ROICaT.

2. **Install ROICaT**

.. literalinclude:: ../helpers/pip_install.txt

Note: if you are using a zsh terminal, add quotes around the pip install
command, i.e. ``pip install "roicat[all]"``

3. **Clone the repo to get the scripts and notebooks**

.. literalinclude:: ../helpers/clone_repo.txt
roicat


Indices and tables
Expand All @@ -59,4 +38,4 @@ Indices and tables
* :ref:`modindex`
* :ref:`search`

.. _github: https://github.com/RichieHakim/ROICaT
.. _github: https://github.com/RichieHakim/ROICaT
80 changes: 80 additions & 0 deletions docs/source/inputsAndOutputs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Inputs and Outputs
==================

Inputs
######

- **Suite2p output files:** ``stat.npy`` and ``ops.npy`` files only.
- **Other data formats:** Support for formats like CaImAn, custom ROIs, etc., can be facilitated through a custom data importing notebook found `here <https://github.com/RichieHakim/ROICaT/blob/main/notebooks/jupyter/other/demo_data_importing.ipynb>`_.

Outputs
#######

The outputs of ROICaT are encapsulated in a ``results.pkl`` file, which is a Python dictionary containing the following fields:

Clusters
~~~~~~~~

- **labels:** Unique Cluster IDs (aka **'UCIDs'**) for each ROI. These are integer labels indicating which cluster each ROI belongs to. ``-1`` indicates an ROI that was not clustered. Array of shape: ``(n_ROIs_total,)``.
- **labels_bySession:** UCIDs for each ROI, by session. List of length ``n_sessions``, where each element is an array of shape ``(n_ROIs_session,)``.
- **labels_bool:** Sparse boolean matrix describing which ROIs are in which clusters. Rows are ROI indices, columns are UCIDs + 1.
- **labels_bool_bySession:** Same as ``labels_bool``, but by session.
- **labels_dict:** Dictionary mapping UCIDs to ROI indices. Keys are UCIDs, values are lists of ROI indices.

ROIs
~~~~

- **ROIs_aligned:** Images of all ROIs, aligned by session.
- **ROIs_raw:** Raw spatial footprints of the ROIs.
- **frame_height, frame_width:** Dimensions of the Field of View (FOV).
- **idx_roi_session:** Session-wise ROI indices.
- **n_sessions:** Number of sessions.

Quality Metrics
~~~~~~~~~~~~~~~

- **cs_min:** Intra-cluster minimum similarity. Defined as the lowest pairwise similarity within a cluster. *shape:* (n_clusters,).

.. image:: ../media/cluster_quality_metric_images/cs_min.png
:align: right
:width: 100
:alt: cs_min

|
- **cs_max:** Intra-cluster maximum similarity. Defined as the highest similarity within a cluster. *shape:* (n_clusters,).

.. image:: ../media/cluster_quality_metric_images/cs_max.png
:align: right
:width: 100
:alt: cs_max

|
- **cs_mean:** Mean intra-cluster similarity. Defined as the average similarity within a cluster. *shape:* (n_clusters,).

.. image:: ../media/cluster_quality_metric_images/cs_mean.png
:align: right
:width: 100
:alt: cs_mean

|
- **cs_sil:** Cluster silhouette score. A measure of how similar an ROI is to its own cluster compared to other clusters, which can be indicative of the appropriateness of the cluster assignment. Defined as ``(intra - inter) / np.maximum(intra, inter)`` where ``intra=cs_intra_mean`` and ``inter=cs_inter_maxOfMaxes``. *shape:* (n_clusters,).

.. image:: ../media/cluster_quality_metric_images/cs_sil.png
:align: right
:width: 100
:alt: cs_sil

|
- **sample_sil:** Sample silhouette score. A measure of how well each ROI is clustered with its label, providing a perspective on the overall clustering quality. Defined using ``sklearn.metrics.silhouette_score``. *shape:* (n_ROIs_total,).

.. image:: ../media/cluster_quality_metric_images/sample_sil.png
:align: right
:width: 100
:alt: sample_sil

|
12 changes: 6 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ natsort==8.4.0
numpy==1.26.2
opencv_contrib_python==4.8.1.78
optuna==3.5.0
Pillow==10.1.0
Pillow==10.2.0
pytest==7.4.3
scikit_learn==1.3.2
scipy==1.11.4
Expand All @@ -17,16 +17,16 @@ tqdm==4.66.1
umap-learn==0.5.5
xxhash==3.4.1
bokeh==3.3.2
psutil==5.9.6
psutil==5.9.7
py-cpuinfo==9.0.0
GPUtil==1.4.0
PyYAML==6.0.1
mat73==0.62
torch==2.1.1
torchvision==0.16.1
torchaudio==2.1.1
torch==2.1.2
torchvision==0.16.2
torchaudio==2.1.2
selenium==4.16.0
skl2onnx==1.15.0
skl2onnx==1.16.0
onnx==1.15.0
onnxruntime==1.16.3
jupyter-bokeh==3.0.7
2 changes: 1 addition & 1 deletion roicat/ROInet.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def resize_ROIs(
ROI_images_rs (np.ndarray):
The resized ROI images.
"""
scale_forRS = 0.7 * um_per_pixel ## hardcoded for now sorry
scale_forRS = 1.2 * um_per_pixel * (ROI_images.shape[1] / 36) ## hardcoded for now sorry
return np.stack([resize_affine(img, scale=scale_forRS, clamp_range=True) for img in ROI_images], axis=0)


Expand Down
2 changes: 1 addition & 1 deletion roicat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
for pkg in __all__:
exec('from . import ' + pkg)

__version__ = '1.1.26'
__version__ = '1.1.27'
10 changes: 5 additions & 5 deletions roicat/classification/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Autotuner_regression(util.ROICaT_Module):
cv (Type[sklearn.model_selection._split.BaseCrossValidator]):
A Scikit-Learn cross-validator class.
Must have: \n
* Call signature: ``idx_train, idx_test = next(self.cv.split(self.X, self.y))``
* Call signature: ``idx_train, idx_test = next(self.cv.split(self.X, self.y))`` \n
fn_loss (Callable):
Function to compute the loss.
Must have: \n
Expand Down Expand Up @@ -88,10 +88,10 @@ class Autotuner_regression(util.ROICaT_Module):
.. highlight:: python
.. code-block:: python
params = {
'C': {'type': 'real', 'kwargs': {'log': True, 'low': 1e-4, 'high': 1e4}},
'penalty': {'type': 'categorical', 'kwargs': {'choices': ['l1', 'l2']}},
}
params = {
'C': {'type': 'real', 'kwargs': {'log': True, 'low': 1e-4, 'high': 1e4}},
'penalty': {'type': 'categorical', 'kwargs': {'choices': ['l1', 'l2']}},
}
"""
def __init__(
self,
Expand Down
4 changes: 2 additions & 2 deletions roicat/data_importing.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class Data_roicat(util.ROICaT_Module):
numpy array (first dimension) is an ROI.
spatialFootprints (List[object]):
A list of scipy.sparse.csr_matrix objects, each with shape *(n_roi,
FOV_height*FOV_width)*. Each element represents an imaging session.
FOV_height \* FOV_width)*. Each element represents an imaging session.
class_labels_raw (List[np.ndarray]):
A list of numpy arrays, each with shape *(n_roi,)*, where each
element is an integer. Each element of the list is an imaging
Expand All @@ -86,7 +86,7 @@ class Data_roicat(util.ROICaT_Module):
element is an integer. Each element of the list is an imaging
session and each element of the numpy array is the index of the
class label obtained from passing the raw class label through
np.unique(*, return_inverse=True).
np.unique(\*, return_inverse=True).
um_per_pixel (float):
The conversion factor from pixels to microns. This is used to scale
the ROI_images to a common size.
Expand Down
103 changes: 64 additions & 39 deletions roicat/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import numpy as np
import torch
import torchvision
import scipy.sparse
import sparse
from tqdm import tqdm
Expand Down Expand Up @@ -1092,7 +1093,6 @@ def fn_check_pathLike(obj):
bytes,
memoryview,
np.bytes_,
np.unicode_,
re.Pattern,
re.Match,
)):
Expand Down Expand Up @@ -3838,72 +3838,97 @@ def add_text_to_images(


def resize_images(
images: Union[np.ndarray, List[np.ndarray]],
images: Union[np.ndarray, List[np.ndarray], torch.Tensor, List[torch.Tensor]],
new_shape: Tuple[int, int] = (100,100),
interpolation: str = 'linear',
interpolation: str = 'BILINEAR',
antialias: bool = False,
align_corners: bool = False,
device: str = 'cpu',
return_numpy: bool = True,
) -> np.ndarray:
"""
Resizes images using the ``torch.nn.functional.interpolate`` method.
Resizes images using the ``torchvision.transforms.Resize`` method.
RH 2023
Args:
images (Union[np.ndarray, List[np.ndarray]]):
Frames of video or images. Can be 2D, 3D, or 4D.
images (Union[np.ndarray, List[np.ndarray]], torch.Tensor, List[torch.Tensor]):
Images or frames of a video. Can be 2D, 3D, or 4D.
* For a 2D array: shape is *(height, width)*
* For a 3D array: shape is *(n_frames, height, width)*
* For a 4D array: shape is *(n_frames, height, width, n_channels)*
* For a 4D array: shape is *(n_frames, n_channels, height, width)*
new_shape (Tuple[int, int]):
The desired height and width of resized images as a tuple.
(Default is *(100, 100)*)
interpolation (str):
The interpolation method to use. See ``torch.nn.functional.interpolate``
for options. (Default is ``'linear'``)
The interpolation method to use. See ``torchvision.transforms.Resize``
for options.
* ``'NEAREST'``: Nearest neighbor interpolation
* ``'NEAREST_EXACT'``: Nearest neighbor interpolation
* ``'BILINEAR'``: Bilinear interpolation
* ``'BICUBIC'``: Bicubic interpolation
antialias (bool):
If ``True``, antialiasing will be used. (Default is ``False``)
align_corners (bool):
If ``True``, the corners will be aligned. See ``torch.nn.functional.interpolate``
for details. (Default is ``False``)
device (str):
The device to use for ``torch.nn.functional.interpolate``.
The device to use for ``torchvision.transforms.Resize``.
(Default is ``'cpu'``)
return_numpy (bool):
If ``True``, then will return a numpy array. Otherwise, will return
a torch tensor on the defined device. (Default is ``True``)
Returns:
(np.ndarray):
images_resized (np.ndarray):
Frames of video or images with overlay added.
"""
## Convert images to torch tensor
if isinstance(images, list):
images = np.stack(images, axis=0)

if images.ndim == 2:
images = images[None,:,:]
elif images.ndim == 3:
images = images
elif images.ndim == 4:
images = images.transpose(0,3,1,2)
if isinstance(images[0], np.ndarray):
images = torch.stack([torch.as_tensor(im, device=device) for im in images], dim=0)
elif isinstance(images, np.ndarray):
images = torch.as_tensor(images, device=device)
elif isinstance(images, torch.Tensor):
images = images.to(device=device)
else:
raise ValueError('images must be 2D, 3D, or 4D.')
raise ValueError(f"images must be a np.ndarray or torch.Tensor or a list of np.ndarray or torch.Tensor. Got {type(images)}")

images_torch = torch.as_tensor(images, device=device)
images_torch = torch.nn.functional.interpolate(
images_torch,
size=tuple(np.array(new_shape, dtype=int)),
mode=interpolation,
align_corners=align_corners,
recompute_scale_factor=None,
antialias=antialias,
)
images_resized = images_torch.cpu().numpy()
## Convert images to 4D
def pad_to_4D(ims):
if ims.ndim == 2:
ims = ims[None, None, :, :]
elif ims.ndim == 3:
ims = ims[None, :, :, :]
elif ims.ndim != 4:
raise ValueError(f"images must be a 2D, 3D, or 4D array. Got shape {ims.shape}")
return ims
ndim_orig = images.ndim
images = pad_to_4D(images)

if images.ndim == 2:
images_resized = images_resized[0,:,:]
elif images.ndim == 3:
images_resized = images_resized
elif images.ndim == 4:
images_resized = images_resized.transpose(0,2,3,1)
## Get interpolation method
try:
interpolation = getattr(torchvision.transforms.InterpolationMode, interpolation.upper())
except Exception as e:
raise Exception(f"Invalid interpolation method. See torchvision.transforms.InterpolationMode for options. Error: {e}")

resizer = torchvision.transforms.Resize(
size=new_shape,
interpolation=interpolation,
antialias=antialias,
).to(device=device)
images_resized = resizer(images)

## Convert images back to original shape
def unpad_to_orig(ims, ndim_orig):
if ndim_orig == 2:
ims = ims[0,0,:,:]
elif ndim_orig == 3:
ims = ims[0,:,:,:]
elif ndim_orig != 4:
raise ValueError(f"images must be a 2D, 3D, or 4D array. Got shape {ims.shape}")
return ims
images_resized = unpad_to_orig(images_resized, ndim_orig)

## Convert images to numpy
if return_numpy:
images_resized = images_resized.detach().cpu().numpy()

return images_resized

Expand Down
Loading

0 comments on commit f6aab33

Please sign in to comment.