Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating to Keras 3.0 and migrating to PyTorch #418

Merged
merged 26 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3046294
remove pytest-lazy-fixture as dev dependency and skip test (with WG t…
sfmig Feb 5, 2024
99cbda0
Test Keras is present (#374)
sfmig Feb 7, 2024
29f8555
Migrate to Keras 3.0 with TF backend (#373)
sfmig Feb 8, 2024
5ad5c1b
Replace TF references in comments and warning messages (#378)
sfmig Feb 8, 2024
b47e5dc
Cellfinder with Keras 3.0 and jax backend (#379)
sfmig Feb 12, 2024
ca80c6d
Run cellfinder with JAX in Windows tests in CI (#382)
sfmig Feb 16, 2024
32a0a56
Merge branch 'main' into cellfinder-to-keras-3
IgorTatarnikov Apr 16, 2024
5a0152b
It/keras3 pytorch (#396)
IgorTatarnikov May 10, 2024
0150d07
Set pooling padding to valid by default on all MaxPooling3D layers
IgorTatarnikov May 10, 2024
7731b3c
Removed tf error suppression and other tf related functions
IgorTatarnikov May 10, 2024
13770d3
Merge branch 'main' into cellfinder-to-keras-3
IgorTatarnikov May 10, 2024
b4799ac
Force torch to use cpu device when CELLFINDER_TEST_DEVICE env variabl…
IgorTatarnikov May 17, 2024
4c07fc6
Added nev variable to test step
IgorTatarnikov May 17, 2024
1027b59
Use the GITHUB ACTIONS environemntal variable instead
IgorTatarnikov May 17, 2024
a891005
Added docstring for fixture setting device to cpu on arm based mac
IgorTatarnikov May 17, 2024
1a56198
Revert changes to no_free_cpus being fixture, and default param
IgorTatarnikov May 20, 2024
cdbfb42
Fixed typo in test_and_deploy.yml
IgorTatarnikov May 20, 2024
9ff10d9
Set multiprocessing to false for the data generators
IgorTatarnikov May 23, 2024
0339a8f
Merge branch 'main' into cellfinder-to-keras-3
IgorTatarnikov May 23, 2024
2ce45fa
Update all cache steps to match
IgorTatarnikov May 23, 2024
e98e0af
Remove reference to TF
adamltyson May 23, 2024
d4b7ecd
Make sure tests can run locally when GITHUB_ACTIONS env variable is m…
IgorTatarnikov May 23, 2024
68ed410
Removed warning when backend is not configured
IgorTatarnikov May 23, 2024
bebfce9
Set the label tensor to be float32 to ensure compatibility with mps
IgorTatarnikov May 24, 2024
7ad06c6
Always set KERAS_BACKEND to torch on init
IgorTatarnikov May 28, 2024
666749f
Remove code in __init__ checking for if backend is installed
IgorTatarnikov May 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ jobs:
name: Run package tests
timeout-minutes: 60
runs-on: ${{ matrix.os }}
env:
KERAS_BACKEND: torch
CELLFINDER_TEST_DEVICE: cpu
strategy:
matrix:
# Run all supported Python versions on linux
os: [ubuntu-latest]
python-version: ["3.9", "3.10"]
# Include one windows, one macos run each for M1 (latest) and Intel (13)
python-version: ["3.9", "3.10", "3.11"]
# Include one windows and two macOS (intel based and arm based) runs
include:
- os: macos-13
python-version: "3.10"
Expand Down Expand Up @@ -80,11 +83,13 @@ jobs:
NUMBA_DISABLE_JIT: "1"

steps:
- name: Cache tensorflow model
- name: Cache brainglobe directory
uses: actions/cache@v3
with:
path: "~/.cellfinder"
key: models-${{ hashFiles('~/.brainglobe/**') }}
path: | # ensure we don't cache any interrupted atlas download and extraction, if e.g. we cancel the workflow manually
~/.brainglobe
!~/.brainglobe/atlas.tar.gz
key: brainglobe
# Setup pyqt libraries
- name: Setup qtpy libraries
uses: tlambert03/setup-qt-libs@v1
Expand All @@ -104,13 +109,17 @@ jobs:
name: Run brainmapper tests to check for breakages
timeout-minutes: 60
runs-on: ubuntu-latest
env:
KERAS_BACKEND: torch
CELLFINDER_TEST_DEVICE: cpu
steps:
- name: Cache tensorflow model
- name: Cache brainglobe directory
uses: actions/cache@v3
with:
path: "~/.cellfinder"
key: models-${{ hashFiles('~/.brainglobe/**') }}

path: | # ensure we don't cache any interrupted atlas download and extraction, if e.g. we cancel the workflow manually
~/.brainglobe
!~/.brainglobe/atlas.tar.gz
key: brainglobe
- name: Checkout brainglobe-workflows
uses: actions/checkout@v3
with:
Expand All @@ -124,8 +133,9 @@ jobs:
- name: Install test dependencies
run: |
python -m pip install --upgrade pip wheel
# Install latest SHA on this brainglobe-workflows branch
python -m pip install git+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA
# Install cellfinder from the latest SHA on this branch
python -m pip install "cellfinder @ git+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA"

# Install checked out copy of brainglobe-workflows
python -m pip install .[dev]

Expand Down
21 changes: 9 additions & 12 deletions .github/workflows/test_include_guard.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Test Tensorflow include guards
# These tests check that the include guards checking for tensorflow's availability
name: Test Keras include guards
# These tests check that the include guards checking for Keras availability
# behave as expected on ubuntu and macOS.

on:
Expand All @@ -9,7 +9,7 @@ on:
- main

jobs:
tensorflow_guards:
keras_guards:
name: Test include guards
strategy:
matrix:
Expand All @@ -24,24 +24,21 @@ jobs:
with:
python-version: '3.10'

- name: Install via pip
run: python -m pip install -e .
- name: Install cellfinder via pip
run: python -m pip install -e "."

- name: Test (working) import
uses: jannekem/run-python-script-action@v1
env:
KERAS_BACKEND: torch
with:
fail-on-error: true
script: |
import cellfinder.core
import cellfinder.napari

- name: Uninstall tensorflow-macos on Mac M1
if: matrix.os == 'macos-latest'
run: python -m pip uninstall -y tensorflow-macos

- name: Uninstall tensorflow on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: python -m pip uninstall -y tensorflow
- name: Uninstall keras
run: python -m pip uninstall -y keras

- name: Test (broken) import
id: broken_import
Expand Down
28 changes: 17 additions & 11 deletions cellfinder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import os
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

# Check cellfinder is installed
try:
__version__ = version("cellfinder")
except PackageNotFoundError as e:
raise PackageNotFoundError("cellfinder package not installed") from e

# If tensorflow is not present, tools cannot be used.
# If Keras is not present, tools cannot be used.
# Throw an error in this case to prevent invocation of functions.
try:
TF_VERSION = version("tensorflow")
KERAS_VERSION = version("keras")
except PackageNotFoundError as e:
try:
TF_VERSION = version("tensorflow-macos")
except PackageNotFoundError as e:
raise PackageNotFoundError(
f"cellfinder tools cannot be invoked without tensorflow. "
f"Please install tensorflow into your environment to use cellfinder tools. "
f"For more information, please see "
f"https://github.com/brainglobe/brainglobe-meta#readme."
) from e
raise PackageNotFoundError(

Check warning on line 16 in cellfinder/__init__.py

View check run for this annotation

Codecov / codecov/patch

cellfinder/__init__.py#L16

Added line #L16 was not covered by tests
f"cellfinder tools cannot be invoked without Keras. "
f"Please install Keras with a backend into your environment "
f"to use cellfinder tools. "
f"For more information on Keras backends, please see "
f"https://keras.io/getting_started/#installing-keras-3."
f"For more information on brainglobe, please see "
f"https://github.com/brainglobe/brainglobe-meta#readme."
) from e


# Set the Keras backend to torch
os.environ["KERAS_BACKEND"] = "torch"

__author__ = "Adam Tyson, Christian Niedworok, Charly Rousseau"
__license__ = "BSD-3-Clause"
Expand Down
11 changes: 5 additions & 6 deletions cellfinder/core/classify/classify.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import os
from typing import Any, Callable, Dict, List, Optional, Tuple

import keras
import numpy as np
from brainglobe_utils.cells.cells import Cell
from brainglobe_utils.general.system import get_num_processes
from tensorflow import keras

from cellfinder.core import logger, types
from cellfinder.core.classify.cube_generator import CubeGeneratorFromFile
Expand Down Expand Up @@ -48,9 +48,7 @@ def main(
callbacks = None

# Too many workers doesn't increase speed, and uses huge amounts of RAM
workers = get_num_processes(
min_free_cpu_cores=n_free_cpus, n_max_processes=max_workers
)
workers = get_num_processes(min_free_cpu_cores=n_free_cpus)

logger.debug("Initialising cube generator")
inference_generator = CubeGeneratorFromFile(
Expand All @@ -63,6 +61,8 @@ def main(
cube_width=cube_width,
cube_height=cube_height,
cube_depth=cube_depth,
use_multiprocessing=False,
workers=workers,
)

model = get_model(
Expand All @@ -73,10 +73,9 @@ def main(
)

logger.info("Running inference")
# in Keras 3.0 multiprocessing params are specified in the generator
predictions = model.predict(
inference_generator,
use_multiprocessing=True,
workers=workers,
verbose=True,
callbacks=callbacks,
)
Expand Down
34 changes: 25 additions & 9 deletions cellfinder/core/classify/cube_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
from random import shuffle
from typing import Dict, List, Optional, Tuple, Union

import keras
import numpy as np
import tensorflow as tf
from brainglobe_utils.cells.cells import Cell, group_cells_by_z
from brainglobe_utils.general.numerical import is_even
from keras.utils import Sequence
from scipy.ndimage import zoom
from skimage.io import imread
from tensorflow.keras.utils import Sequence

from cellfinder.core import types
from cellfinder.core.classify.augment import AugmentationParameters, augment
Expand Down Expand Up @@ -56,7 +56,14 @@
translate: Tuple[float, float, float] = (0.05, 0.05, 0.05),
shuffle: bool = False,
interpolation_order: int = 2,
*args,
**kwargs,
):
# pass any additional arguments not specified in signature to the
# constructor of the superclass (e.g.: `use_multiprocessing` or
# `workers`)
super().__init__(*args, **kwargs)

self.points = points
self.signal_array = signal_array
self.background_array = background_array
Expand Down Expand Up @@ -218,10 +225,10 @@

if self.train:
batch_labels = [cell.type - 1 for cell in cell_batch]
batch_labels = tf.keras.utils.to_categorical(
batch_labels = keras.utils.to_categorical(

Check warning on line 228 in cellfinder/core/classify/cube_generator.py

View check run for this annotation

Codecov / codecov/patch

cellfinder/core/classify/cube_generator.py#L228

Added line #L228 was not covered by tests
batch_labels, num_classes=self.classes
)
return images, batch_labels
return images, batch_labels.astype(np.float32)

Check warning on line 231 in cellfinder/core/classify/cube_generator.py

View check run for this annotation

Codecov / codecov/patch

cellfinder/core/classify/cube_generator.py#L231

Added line #L231 was not covered by tests
elif self.extract:
batch_info = self.__get_batch_dict(cell_batch)
return images, batch_info
Expand Down Expand Up @@ -252,7 +259,8 @@
(number_images,)
+ (self.cube_height, self.cube_width, self.cube_depth)
+ (self.channels,)
)
),
dtype=np.float32,
)

for idx, cell in enumerate(cell_batch):
Expand Down Expand Up @@ -350,7 +358,14 @@
translate: Tuple[float, float, float] = (0.2, 0.2, 0.2),
train: bool = False, # also return labels
interpolation_order: int = 2,
*args,
**kwargs,
):
# pass any additional arguments not specified in signature to the
# constructor of the superclass (e.g.: `use_multiprocessing` or
# `workers`)
super().__init__(*args, **kwargs)

self.im_shape = shape
self.batch_size = batch_size
self.labels = labels
Expand Down Expand Up @@ -410,10 +425,10 @@

if self.train and self.labels is not None:
batch_labels = [self.labels[k] for k in indexes]
batch_labels = tf.keras.utils.to_categorical(
batch_labels = keras.utils.to_categorical(
batch_labels, num_classes=self.classes
)
return images, batch_labels
return images, batch_labels.astype(np.float32)
else:
return images

Expand All @@ -424,7 +439,8 @@
) -> np.ndarray:
number_images = len(list_signal_tmp)
images = np.empty(
((number_images,) + self.im_shape + (self.channels,))
((number_images,) + self.im_shape + (self.channels,)),
dtype=np.float32,
)

for idx, signal_im in enumerate(list_signal_tmp):
Expand All @@ -433,7 +449,7 @@
images, idx, signal_im, background_im
)

return images.astype(np.float16)
return images

def __populate_array_with_cubes(
self,
Expand Down
15 changes: 9 additions & 6 deletions cellfinder/core/classify/resnet.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Callable, Dict, List, Literal, Optional, Tuple, Union

from tensorflow import Tensor
from tensorflow.keras import Model
from tensorflow.keras.initializers import Initializer
from tensorflow.keras.layers import (
from keras import (
KerasTensor as Tensor,
)
from keras import Model
from keras.initializers import Initializer
from keras.layers import (
Activation,
Add,
BatchNormalization,
Expand All @@ -14,7 +16,7 @@
MaxPooling3D,
ZeroPadding3D,
)
from tensorflow.keras.optimizers import Adam, Optimizer
from keras.optimizers import Adam, Optimizer

#####################################################################
# Define the types of ResNet
Expand Down Expand Up @@ -113,7 +115,7 @@ def non_residual_block(
activation: str = "relu",
use_bias: bool = False,
bn_epsilon: float = 1e-5,
pooling_padding: str = "same",
pooling_padding: str = "valid",
axis: int = 3,
) -> Tensor:
"""
Expand All @@ -131,6 +133,7 @@ def non_residual_block(
)(x)
x = BatchNormalization(axis=axis, epsilon=bn_epsilon, name="conv1_bn")(x)
x = Activation(activation, name="conv1_activation")(x)

x = MaxPooling3D(
max_pool_size,
strides=strides,
Expand Down
Loading
Loading