Skip to content

Commit

Permalink
Merge pull request #32 from juglab/refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
tibuch authored May 8, 2020
2 parents 7312e15 + 8d67c53 commit d19658a
Show file tree
Hide file tree
Showing 103 changed files with 61,511 additions and 697 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dist/
docs/build/
examples/**/models/

*.png
*.npz
*.npy
*.tif*
Expand Down
4 changes: 2 additions & 2 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
License for Noise2Seg
License for DenoiSeg

BSD 3-Clause License

Copyright (c) 2019 JugLab
Copyright (c) 2020 JugLab
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
126 changes: 92 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,93 @@
# DenoiSeg
Microscopy image analysis often requires the segmentation of objects, but training data for such a task is hard to obtain.
Here we propose DenoiSeg, a new method that can be trained end-to-end on only a few annotated ground truth segmentations.
We achieve this by extending Noise2Void, a self-supervised denoising scheme that can be trained on noisy images, to also predict dense 3-class segmentations.
The reason for the success of our method is that segmentation can profit from denoising especially when performed within the same network jointly.
The network becomes a denoising expert by seeing all available raw data, while co-learning to segment even if only a few segmentation labels are available.
This hypothesis is additionally fueled by our observation that the best segmentation results on high quality (virtually noise free) raw data are performed when moderate amounts of synthetic noise are added.
This renders the denoising-task non-trivial and unleashes the co-learning effect.
We believe that DenoiSeg offers a viable way to circumvent the tremendous hunger for high quality training data and effectively enables few-shot learning of dense segmentations.

## How to run
### Conda-Env
You can either install the necessary packages into a conda-env with the setup.py file by typing `pip install .`
from within this cloned git-repo or you use a singularity container.

### Build Singularity Container
1. Install Singularity
2. `sudo singularity build noise2seg.sim singularity/noise2seg.Singularity`

### Run Singularity Container
Now you can run a jupyter-server with this container:
`singularity run --nv -B singularity/user:/run/user -B ./:/notebooks singularity/noise2seg.simg`

### Run Singularity Headless
1. Install PyInquirer
2. `python3 run_noise2seg.py`

For the headless to work you have to set following parameters in the file `run_noise2seg.py`:
```
singularity_path = "/projects/Noise2Seg/singularity/"
base_dir = "/projects/Noise2Seg"
gitrepo_path = join(base_dir, 'Noise2Seg')
base_path_data = join(base_dir, "data")
base_path_exp = join(base_dir, "experiments")
![Teaser: DenoiSeg](resources/teaser.png)
# DenoiSeg: Joint Denoising and Segmentation
Tim-Oliver Buchholz<sup>\*,1,2</sup>, Mangal Prakash<sup>\*,1,2,</sup>, Alexander Krull<sup>1,2,3</sup>,
and Florian Jug<sup>1,2,^</sup>

<sup>1</sup> Max Planck Institute of Molecular Cell Biology and Genetics, Dresden, Germany <br />
<sup>2</sup> Center for Systems Biology, Dresden, Germany <br />
<sup>3</sup> Max Planck Institute for Physics of Complex Systems, Dresden, Germany <br />
<sup>^</sup> <code>[email protected]</code> <br />
<sup>*</sup> Equal contribution (alphabetical order).

Microscopy image analysis often requires the segmentation of objects,
but training data for this task is typically scarce and hard to obtain.
Here we propose DenoiSeg, a new method that can be trained end-to-end
on only a few annotated ground truth segmentations.
We achieve this by extending [Noise2Void](https://github.com/juglab/n2v),
a self-supervised denoising scheme that can be trained on noisy images alone,
to also predict dense 3-class segmentations. The reason for the success
of our method is that segmentation can profit from denoising, especially
when performed jointly within the same network. The network becomes a
denoising expert by seeing all available raw data, while co-learning to
segment, even if only a few segmentation labels are available. This
hypothesis is additionally fueled by our observation that the best
segmentation results on high quality (very low noise) raw data are obtained
when moderate amounts of synthetic noise are added. This renders the
denoising-task non-trivial and unleashes the desired co-learning effect.
We believe that DenoiSeg offers a viable way to circumvent the tremendous
hunger for high quality training data and effectively enables few-shot learning
of dense segmentations.

Paper: [https://arxiv.org/abs/2005.02987](https://arxiv.org/abs/2005.02987)

## Installation
This implementation requires [Tensorflow](https://www.tensorflow.org/install/).
We have tested DenoiSeg on LinuxMint 19 using python 3.6 and 3.7 and tensorflow-gpu 1.15.

#### If you start from scratch...
We recommend using [miniconda](https://docs.conda.io/en/latest/miniconda.html).
If you do not yet have a strong opinion, just use it too!

After installing Miniconda, the following lines might are likely the easiest way to get Tensorflow and CuDNN installed on your machine (_Note:_ Macs are not supported, and if you sit on a Windows machine all this might also require some modifications.):

```
$ conda create -n 'denoiSeg' python=3.7
$ source activate denoiSeg
$ conda install tensorflow-gpu=1.15 keras=2.2.5
$ pip install jupyter
$ conda install nb_conda
```

Note: it is very important that the version of keras be 2.2.4 or 2.2.5, hence the explicit installation above.
Once this is done (or you had tensorflow et al. installed already), you can install DenoiSeg with one of the following two options:

#### Option 1: PIP (current stable release)
```
$ pip install denoiseg
```

#### Option 2: Git-Clone and install from sources (current master-branch version)
This option is ideal if you want to edit the code. Clone the repository:

```
$ git clone https://github.com/juglab/DenoiSeg.git
```
Change into its directory and install it:

```
$ cd DenoiSeg
$ pip install -e .
```
You are now ready to run DenoiSeg.

## How to use it?
Have a look at our jupyter notebook:
* [Example: DSB2018](https://github.com/juglab/DenoiSeg/tree/master/examples/DenoiSeg_2D/DSB2018_DenoiSeg_Example.ipynb)
* [Example: Fly Wing](https://github.com/juglab/DenoiSeg/tree/master/examples/DenoiSeg_2D/FlyWing_DenoiSeg_Example.ipynb)
* [Example: Mouse Nuclei](https://github.com/juglab/DenoiSeg/tree/master/examples/DenoiSeg_2D/MouseNuclei_DenoiSeg_Example.ipynb)

## How to cite:
```
@inproceedings{BuchholzPrakash2020DenoiSeg,
title={DenoiSeg: Joint Denoising and Segmentation},
author={Tim-Oliver Buchholz and Mangal Prakash and Alexander Krull and Florian Jug},
year={2020}
}
```

## Reproducibility
The current release and master is a refactored version of the code used for the paper.
This refactored version produces the same number as reported in the paper, but if you
wish to use the exact code used in the paper, please continue [here](scripts/reproducibility/README.md).

Further results (qualitative and quantitative) can be found on the [wiki](https://github.com/juglab/DenoiSeg/wiki).
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np

class N2S_DataWrapper(Sequence):
class DenoiSeg_DataWrapper(Sequence):
def __init__(self, X, n2v_Y, seg_Y, batch_size, perc_pix, shape, value_manipulation):
assert X.shape[0] == n2v_Y.shape[0]
assert X.shape[0] == seg_Y.shape[0]
Expand Down
File renamed without changes.
25 changes: 9 additions & 16 deletions noise2seg/internals/losses.py → denoiseg/internals/losses.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,25 @@ def seg_crossentropy(class_targets, y_pred):
return seg_crossentropy


def loss_noise2seg(alpha=0.5, relative_weights=[1.0, 1.0, 5.0]):
def loss_denoiseg(alpha=0.5, relative_weights=[1.0, 1.0, 5.0]):
"""
Calculate noise2seg loss which is a weighted sum of segmentation- and
Calculate DenoiSeg loss which is a weighted sum of segmentation- and
noise2void-loss
:param lambda_: relative weighting, 0 means denoising, 1 means segmentation; (Default: 0.5)
:param relative_weights: Segmentation class weights (background, foreground, border); (Default: [1.0, 1.0, 5.0])
:return: noise2seg loss
:return: DenoiSeg loss
"""
denoise_loss = noise2seg_denoise_loss(weight=alpha)
seg_loss = noise2seg_seg_loss(weight=(1 - alpha), relative_weights=relative_weights)
denoise_loss = denoiseg_denoise_loss(weight=alpha)
seg_loss = denoiseg_seg_loss(weight=(1 - alpha), relative_weights=relative_weights)

def noise2seg(y_true, y_pred):
def denoiseg(y_true, y_pred):
return seg_loss(y_true, y_pred) + denoise_loss(y_true, y_pred)

return noise2seg
return denoiseg


def noise2seg_seg_loss(weight=0.5, relative_weights=[1.0, 1.0, 5.0]):
def denoiseg_seg_loss(weight=0.5, relative_weights=[1.0, 1.0, 5.0]):
class_weights = tf.constant([relative_weights])

def seg_loss(y_true, y_pred):
Expand All @@ -69,7 +69,7 @@ def seg_loss(y_true, y_pred):
return seg_loss


def noise2seg_denoise_loss(weight=0.5):
def denoiseg_denoise_loss(weight=0.5):
n2v_mse_loss = n2v_loss()

def denoise_loss(y_true, y_pred):
Expand All @@ -80,10 +80,3 @@ def denoise_loss(y_true, y_pred):
return weight * n2v_mse_loss(tf.concat([target, mask], axis=channel_axis), denoised)

return denoise_loss

def seg_denoise_ratio_monitor():

def seg_denoise_ratio(y_true, y_pred):
return tf.reduce_mean(tf.reduce_sum(y_true[...,2:], axis=-1)[:,0,0])

return seg_denoise_ratio
4 changes: 4 additions & 0 deletions denoiseg/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# imports

from .denoiseg_config import DenoiSegConfig
from .denoiseg_standard import DenoiSeg
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@


# This class is a adapted version of csbdeep.models.config.py.
class Noise2SegConfig(argparse.Namespace):
class DenoiSegConfig(argparse.Namespace):
"""
Default configuration for a trainable segmentation (Noise2Seg) model.
This class is meant to be used with :class:`Noise2Seg`.
Default configuration for a trainable segmentation (DenoiSeg) model.
This class is meant to be used with :class:`DenoiSeg`.
Parameters
----------
Expand All @@ -21,7 +21,7 @@ class Noise2SegConfig(argparse.Namespace):
Example
-------
>>> n2s_config = Noise2SegConfig(X, unet_n_depth=3)
>>> denoiseg_config = DenoiSegConfig(X, unet_n_depth=3)
Attributes
----------
Expand Down Expand Up @@ -52,7 +52,7 @@ class Noise2SegConfig(argparse.Namespace):
train_reduce_lr : dict
Parameter :class:`dict` of ReduceLROnPlateau_ callback; set to ``None`` to disable. Default: ``{'monitor': 'val_seg_loss', 'factor': 0.5, 'patience': 10}``
train_loss : str
Switch between seg- or noise2seg-loss; Default: ``noise2seg``
Switch between seg- or denoiseg-loss; Default: ``denoiseg``
n2v_perc_pix : float
Percentage of pixel to manipulate per patch. Default: ``1.5``
n2v_patch_shape : tuple
Expand All @@ -61,7 +61,7 @@ class Noise2SegConfig(argparse.Namespace):
Noise2Void pixel value manipulator. Default: ``uniform_withCP``
n2v_neighborhood_radius : int
Neighborhood radius for n2v_old manipulator. Default: ``5``
n2s_alpha : float
denoiseg_alpha : float
Factor modulating the contribution of denoising and segmentation. alpha * denoising + (1-alpha) * segmentation: Default: ``0.5``
.. _ReduceLROnPlateau: https://keras.io/callbacks/#reducelronplateau
Expand Down Expand Up @@ -117,7 +117,7 @@ def __init__(self, X, **kwargs):
# fixed parameters
self.n_channel_in = 1
self.n_channel_out = 4
self.train_loss = 'noise2seg'
self.train_loss = 'denoiseg'

# default config (can be overwritten by kwargs below)

Expand All @@ -141,13 +141,13 @@ def __init__(self, X, **kwargs):
self.train_checkpoint = 'weights_best.h5'
self.train_checkpoint_last = 'weights_last.h5'
self.train_checkpoint_epoch = 'weights_now.h5'
self.train_reduce_lr = {'monitor': 'val_seg_loss', 'factor': 0.5, 'patience': 10}
self.train_reduce_lr = {'monitor': 'val_loss', 'factor': 0.5, 'patience': 10}
self.batch_norm = True
self.n2v_perc_pix = 1.5
self.n2v_patch_shape = (64, 64) if self.n_dim == 2 else (64, 64, 64)
self.n2v_manipulator = 'uniform_withCP'
self.n2v_neighborhood_radius = 5
self.n2s_alpha = 0.5
self.denoiseg_alpha = 0.5

# disallow setting 'probabilistic' manually
try:
Expand Down Expand Up @@ -189,7 +189,7 @@ def _is_int(v, low=None, high=None):
ok['n_channel_in'] = _is_int(self.n_channel_in, 1)
ok['n_channel_out'] = _is_int(self.n_channel_out, 4)
ok['train_loss'] = (
(self.train_loss in ('seg', 'noise2seg'))
(self.train_loss in ('seg', 'denoiseg'))
)
ok['unet_n_depth'] = _is_int(self.unet_n_depth, 1)
ok['relative_weights'] = isinstance(self.relative_weights, list) and len(self.relative_weights) == 3 and all(
Expand Down Expand Up @@ -223,7 +223,7 @@ def _is_int(v, low=None, high=None):
ok['n2v_manipulator'] = self.n2v_manipulator in ['normal_withoutCP', 'uniform_withCP', 'normal_additive',
'normal_fitted', 'identity']
ok['n2v_neighborhood_radius'] = _is_int(self.n2v_neighborhood_radius, 0)
ok['n2s_alpha'] = isinstance(self.n2s_alpha, float) and self.n2s_alpha >= 0.0
ok['denoiseg_alpha'] = isinstance(self.denoiseg_alpha, float) and self.denoiseg_alpha >= 0.0 and self.denoiseg_alpha <= 1.0

if return_invalid:
return all(ok.values()), tuple(k for (k, v) in ok.items() if not v)
Expand Down
Loading

0 comments on commit d19658a

Please sign in to comment.