diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..bb5b2966 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,51 @@ +{ + "creators": [ + { + "orcid": "0000-0001-9386-4753", + "affiliation": "Gothenburg University", + "name": "Midtvedt, Benjamin" + }, + { + "orcid": "0000-0002-9197-3451", + "affiliation": "Gothenburg University", + "name": "Pineda, Jesus" + }, + { + "orcid": "0000-0001-7275-6921", + "affiliation": "Chalmers University of Technology", + "name": "Klein Morberg, Henrik" + }, + { + "orcid": "0000-0002-8625-0996", + "affiliation": "University of Vic", + "name": "Manzo, Carlo" + }, + { + "orcid": "0000-0001-5057-1846", + "affiliation": "Gothenburg University", + "name": "Volpe, Giovanni" + } + ], + + "title": "DeepTrack2", + + "related_identifiers": [ + { + "scheme": "doi", + "identifier": "10.1063/5.0034891", + "relation": "isDocumentedBy", + "resource_type": "publication-article" + } + ], + + "description": "A Python software platform for microscopy enhanced by deep learning." , + + "keywords": ["Deep Learning", "Software", "Microscopy", "Particle Tracking", "Python"], + + "upload_type": "software", + + "communities": [ + {"identifier": "www.deeptrack.org"}, + {"identifier": "https://github.com/softmatterlab/DeepTrack2"} + ] +} diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..bb6025a1 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,30 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: DeepTrack2 +message: >- + If you use this software, please cite it through + this publication: Benjamin Midtvedt, Saga + Helgadottir, Aykut Argun, Jesús Pineda, Daniel + Midtvedt, Giovanni Volpe. "Quantitative Digital + Microscopy with Deep Learning." Applied Physics + Reviews 8 (2021), 011310. + https://doi.org/10.1063/5.0034891 +type: software +authors: + - given-names: Benjamin + family-names: Midtvedt + orcid: 'https://orcid.org/0000-0001-9386-4753' + - given-names: Jesus + family-names: Pineda + orcid: 'https://orcid.org/0000-0002-9197-3451' + - given-names: Henrik + family-names: Klein Morberg + orcid: 'https://orcid.org/0000-0001-7275-6921' + - given-names: Carlo + family-names: Manzo + orcid: 'https://orcid.org/0000-0002-8625-0996' + - given-names: Giovanni + family-names: Volpe + orcid: 'https://orcid.org/0000-0001-5057-1846' diff --git a/deeptrack/backend/pint_definition.py b/deeptrack/backend/pint_definition.py index 74ab31b2..f8137bd0 100644 --- a/deeptrack/backend/pint_definition.py +++ b/deeptrack/backend/pint_definition.py @@ -306,7 +306,8 @@ reciprocal_centimeter = 1 / cm = cm_1 = kayser # Velocity -[velocity] = [length] / [time] = [speed] +[velocity] = [length] / [time] +[speed] = [velocity] knot = nautical_mile / hour = kt = knot_international = international_knot mile_per_hour = mile / hour = mph = MPH kilometer_per_hour = kilometer / hour = kph = KPH diff --git a/deeptrack/datasets/__init__.py b/deeptrack/datasets/__init__.py index ec675718..9941d71c 100644 --- a/deeptrack/datasets/__init__.py +++ b/deeptrack/datasets/__init__.py @@ -3,4 +3,5 @@ segmentation_ssTEM_drosophila, regression_holography_nanoparticles, segmentation_fluorescence_u2os, + detection_holography_nanoparticles, ) \ No newline at end of file diff --git a/deeptrack/datasets/detection_holography_nanoparticles/__init__.py b/deeptrack/datasets/detection_holography_nanoparticles/__init__.py new file mode 100644 index 00000000..47648605 --- /dev/null +++ b/deeptrack/datasets/detection_holography_nanoparticles/__init__.py @@ -0,0 +1,3 @@ +"""detection_holography_nanoparticles dataset.""" + +from .detection_holography_nanoparticles import DetectionHolographyNanoparticles diff --git a/deeptrack/datasets/detection_holography_nanoparticles/checksums.tsv b/deeptrack/datasets/detection_holography_nanoparticles/checksums.tsv new file mode 100644 index 00000000..cd65db4e --- /dev/null +++ b/deeptrack/datasets/detection_holography_nanoparticles/checksums.tsv @@ -0,0 +1,3 @@ +# TODO(detection_holography_nanoparticles): If your dataset downloads files, then the checksums +# will be automatically added here when running +# `tfds build --register_checksums`. diff --git a/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles.py b/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles.py new file mode 100644 index 00000000..440b106e --- /dev/null +++ b/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles.py @@ -0,0 +1,77 @@ +"""detection_holography_nanoparticles dataset.""" + +import tensorflow_datasets as tfds +import tensorflow as tf +import numpy as np + +# TODO(detection_holography_nanoparticles): Markdown description that will appear on the catalog page. +_DESCRIPTION = """ +""" + +# TODO(detection_holography_nanoparticles): BibTeX citation +_CITATION = """ +""" + + +class DetectionHolographyNanoparticles(tfds.core.GeneratorBasedBuilder): + """DatasetBuilder for detection_holography_nanoparticles dataset.""" + + VERSION = tfds.core.Version("1.0.2") + RELEASE_NOTES = { + "1.0.0": "Initial release.", + } + + def _info(self) -> tfds.core.DatasetInfo: + """Returns the dataset metadata.""" + # TODO(detection_holography_nanoparticles): Specifies the tfds.core.DatasetInfo object + return tfds.core.DatasetInfo( + builder=self, + description=_DESCRIPTION, + features=tfds.features.FeaturesDict( + { + # These are the features of your dataset like images, labels ... + "image": tfds.features.Tensor( + shape=(972, 729, 2), dtype=tf.float64 + ), + "label": tfds.features.Tensor(shape=(None, 7), dtype=tf.float64), + } + ), + # If there's a common (input, target) tuple from the + # features, specify them here. They'll be used if + # `as_supervised=True` in `builder.as_dataset`. + supervised_keys=("image", "label"), # Set to `None` to disable + homepage="https://dataset-homepage/", + citation=_CITATION, + disable_shuffling=True, + ) + + def _split_generators(self, dl_manager: tfds.download.DownloadManager): + """Returns SplitGenerators.""" + # TODO(detection_holography_nanoparticles): Downloads the data and defines the splits + path = dl_manager.download_and_extract( + "https://drive.google.com/u/1/uc?id=1uAZVr9bldhZhxuXAXvdd1-Ks4m9HPRtM&export=download" + ) + + # TODO(detection_holography_nanoparticles): Returns the Dict[split names, Iterator[Key, Example]] + return { + "train": self._generate_examples(path), + } + + def _generate_examples(self, path): + """Yields examples.""" + # TODO(detection_holography_nanoparticles): Yields (key, example) tuples from the dataset + + fields = path.glob("f*.npy") + labels = path.glob("d*.npy") + + # sort the files + fields = sorted(fields, key=lambda x: int(x.stem[1:])) + labels = sorted(labels, key=lambda x: int(x.stem[1:])) + + for field, label in zip(fields, labels): + field_data = np.load(field) + field_data = np.stack((field_data.real, field_data.imag), axis=-1) + yield field.stem, { + "image": field_data, + "label": np.load(label), + } diff --git a/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles_test.py b/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles_test.py new file mode 100644 index 00000000..8c96c6b3 --- /dev/null +++ b/deeptrack/datasets/detection_holography_nanoparticles/detection_holography_nanoparticles_test.py @@ -0,0 +1,24 @@ +"""detection_holography_nanoparticles dataset.""" + +import tensorflow_datasets as tfds +from . import detection_holography_nanoparticles + + +class DetectionHolographyNanoparticlesTest(tfds.testing.DatasetBuilderTestCase): + """Tests for detection_holography_nanoparticles dataset.""" + # TODO(detection_holography_nanoparticles): + DATASET_CLASS = detection_holography_nanoparticles.DetectionHolographyNanoparticles + SPLITS = { + 'train': 3, # Number of fake train example + 'test': 1, # Number of fake test example + } + + # If you are calling `download/download_and_extract` with a dict, like: + # dl_manager.download({'some_key': 'http://a.org/out.txt', ...}) + # then the tests needs to provide the fake output paths relative to the + # fake data directory + # DL_EXTRACT_RESULT = {'some_key': 'output_file1.txt', ...} + + +if __name__ == '__main__': + tfds.testing.test_main() diff --git a/deeptrack/datasets/detection_holography_nanoparticles/dummy_data/TODO-add_fake_data_in_this_directory.txt b/deeptrack/datasets/detection_holography_nanoparticles/dummy_data/TODO-add_fake_data_in_this_directory.txt new file mode 100644 index 00000000..e69de29b diff --git a/deeptrack/extras/datasets.py b/deeptrack/extras/datasets.py index 031c3d77..df5b06a7 100644 --- a/deeptrack/extras/datasets.py +++ b/deeptrack/extras/datasets.py @@ -46,7 +46,12 @@ "CellData": ("1CJW7msDiI7xq7oMce4l9tRkNN6O5eKtj", "CellData", ""), "CellMigData": ("1vRsWcxjbTz6rffCkrwOfs_ezPvUjPwGw", "CellMigData", ""), "BFC2Cells": ("1lHgJdG5I3vRnU_DRFwTr_c69nx1Xkd3X", "BFC2Cells", ""), - "STrajCh": ("1wXCSzvHuLwz1dywxUu2aQXlqbgf2V8r3", "STrajCh", "") + "STrajCh": ("1wXCSzvHuLwz1dywxUu2aQXlqbgf2V8r3", "STrajCh", ""), + "TrajectoryDiffusion": ( + "1YhECLQrWPZgc_TVY2Sl2OwDcNxmA_jR5", + "TrajectoryDiffusion", + "", + ), } @@ -109,7 +114,9 @@ def load(key): # If the extracted folder is another folder with the same name, move it. if os.path.isdir(f"datasets/{folder_name}/{folder_name}"): - os.rename(f"datasets/{folder_name}/{folder_name}", f"datasets/{folder_name}") + os.rename( + f"datasets/{folder_name}/{folder_name}", f"datasets/{folder_name}" + ) def load_model(key): @@ -171,7 +178,9 @@ def load_model(key): # If the extracted folder is another folder with the same name, move it. if os.path.isdir(f"models/{folder_name}/{folder_name}"): - os.rename(f"models/{folder_name}/{folder_name}", f"models/{folder_name}") + os.rename( + f"models/{folder_name}/{folder_name}", f"models/{folder_name}" + ) return f"models/{folder_name}" diff --git a/deeptrack/models/convolutional.py b/deeptrack/models/convolutional.py index 5f055b1c..f8ca84c4 100644 --- a/deeptrack/models/convolutional.py +++ b/deeptrack/models/convolutional.py @@ -244,6 +244,123 @@ def __init__( super().__init__(model, **kwargs) +class TimeDistributedFullyConvolutional(KerasModel): + """A fully convolutional neural network. + + Parameters + ---------- + input_shape : tuple + The shape of the input. + conv_layers_dimensions : tuple of int or tuple of tuple of int + The number of filters in each convolutional layer. Examples: + - (32, 64, 128) results in + 1. Conv2D(32, 3, activation='relu', padding='same') + 2. MaxPooling2D() + 3. Conv2D(64, 3, activation='relu', padding='same') + 4. MaxPooling2D() + 5. Conv2D(128, 3, activation='relu', padding='same') + 6. MaxPooling2D() + 7. Conv2D(number_of_outputs, 3, activation=output_activation, padding='same') + + - ((32, 32), (64, 64), (128, 128)) results in + 1. Conv2D(32, 3, activation='relu', padding='same') + 2. Conv2D(32, 3, activation='relu', padding='same') + 3. MaxPooling2D() + 4. Conv2D(64, 3, activation='relu', padding='same') + 5. Conv2D(64, 3, activation='relu', padding='same') + 6. MaxPooling2D() + 7. Conv2D(128, 3, activation='relu', padding='same') + 8. Conv2D(128, 3, activation='relu', padding='same') + 9. MaxPooling2D() + 10. Conv2D(number_of_outputs, 3, activation=output_activation, padding='same') + omit_last_pooling : bool + If True, the last MaxPooling2D layer is omitted. Default is False + number_of_outputs : int + The number of output channels. + output_activation : str + The activation function of the output layer. + output_kernel_size : int + The kernel size of the output layer. + return_sequences : bool + If True, the output of the last layer is flattened and returned as a + sequence with shape (batch_size, timesteps, flattened_output_dim). + conv_layers_kwargs : dict + Keyword arguments passed to the convolutional layers. + flatten_block : tf.keras.layers.Layer + The layer used to flatten the output of the last convolutional layer + if return_sequences is True. By default, a Flatten layer is used. + Returns + ------- + model : tf.keras.models.Model + The compiled model. + """ + + def __init__( + self, + input_shape, + conv_layers_dimensions, + omit_last_pooling=False, + number_of_outputs=1, + output_activation="sigmoid", + output_kernel_size=3, + return_sequences=False, + conv_layers_kwargs={}, + flatten_block: layers.Layer = layers.Flatten(), + **kwargs, + ): + + # INITIALIZE DEEP LEARNING NETWORK + if isinstance(input_shape, list): + network_input = [layers.Input(shape) for shape in input_shape] + inputs = layers.Concatenate(axis=-1)(network_input) + else: + network_input = layers.Input(input_shape) + inputs = network_input + + layer = inputs + + # CONVOLUTIONAL BASIS + convolutional_kwargs = { + "kernel_size": 3, + "activation": "relu", + "padding": "same", + } + convolutional_kwargs.update(conv_layers_kwargs) + for idx, depth_dimensions in enumerate(conv_layers_dimensions): + + if isinstance(depth_dimensions, int): + depth_dimensions = (depth_dimensions,) + + for conv_layer_dimension in depth_dimensions: + layer = layers.TimeDistributed( + layers.Conv2D(conv_layer_dimension, **convolutional_kwargs) + )(layer) + + # add pooling layer + if idx < len(conv_layers_dimensions) - 1 or not omit_last_pooling: + layer = layers.TimeDistributed(layers.MaxPooling2D(2, 2))( + layer + ) + + # OUTPUT + if return_sequences: + output_layer = layers.TimeDistributed(flatten_block)(layer) + else: + output_layer = layers.TimeDistributed( + layers.Conv2D( + number_of_outputs, + kernel_size=output_kernel_size, + activation=output_activation, + padding="same", + name="output", + ) + )(layer) + + model = models.Model(network_input, output_layer) + + super().__init__(model, **kwargs) + + class UNet(KerasModel): """Creates and compiles a U-Net. @@ -560,12 +677,16 @@ def __init__( )(layer, **transformer_input_kwargs) # Extract global representation - cls_rep = layers.Lambda(lambda x: x[:, 0], name="RetrieveClassToken")(layer) + cls_rep = layers.Lambda(lambda x: x[:, 0], name="RetrieveClassToken")( + layer + ) # Process cls features cls_layer = cls_rep if cls_layer_dimension is not None: - cls_layer = dense_block(cls_layer_dimension, name="cls_mlp")(cls_layer) + cls_layer = dense_block(cls_layer_dimension, name="cls_mlp")( + cls_layer + ) cls_output = layers.Dense( number_of_cls_outputs, @@ -686,15 +807,21 @@ def __init__( norm_kwargs={"epsilon": 1e-6}, ), positional_embedding_block=Identity(), + use_transformer_mask=False, + **kwargs, ): dense_block = as_block(dense_block) - transformer_input, transformer_mask = ( - layers.Input(shape=(None, number_of_node_features)), - layers.Input(shape=(None, 2), dtype="int32"), - ) + transformer_input = layers.Input(shape=(None, number_of_node_features)) + Inputs = [transformer_input] + + if use_transformer_mask: + transformer_mask = layers.Input(shape=(None, 2), dtype="int32") + Inputs.append(transformer_mask) + else: + transformer_mask = None layer = transformer_input # Encoder for input features @@ -735,7 +862,7 @@ def __init__( )(layer) model = models.Model( - [transformer_input, transformer_mask], + Inputs, output_layer, ) diff --git a/deeptrack/models/layers.py b/deeptrack/models/layers.py index b16d4638..471d5a52 100644 --- a/deeptrack/models/layers.py +++ b/deeptrack/models/layers.py @@ -450,7 +450,7 @@ def compute_attention_mask(self, x, edges, batch_size=None, **kwargs): def softmax(self, x, axis=-1): exp = tf.exp(x - tf.reduce_max(x, axis=axis, keepdims=True)) - + if self.clip_scores_by_value: exp = tf.clip_by_value(exp, *self.clip_scores_by_value) @@ -730,12 +730,16 @@ class TransformerEncoder(tf.keras.layers.Layer): Activation function of the layer. See keras docs for accepted strings. normalization : str or normalization function or layer Normalization function of the layer. See keras and tfa docs for accepted strings. - use_gates : bool, optional + use_gates : bool, optional [Deprecated] Whether to use gated self-attention layers as update layer. Defaults to False. use_bias: bool, optional Whether to use bias in the dense layers of the attention layers. Defaults to False. norm_kwargs : dict Arguments for the normalization function. + multi_head_attention_layer : tf.keras.layers.Layer + Layer to use for the multi-head attention. Defaults to dt.layers.MultiHeadSelfAttention. + multi_head_attention_kwargs : dict + Arguments for the multi-head attention layer. kwargs : dict Additional arguments. """ @@ -750,6 +754,10 @@ def __init__( use_gates=False, use_bias=False, norm_kwargs={}, + multi_head_attention_layer: layers.Layer = None, + multi_head_attention_kwargs={}, + fwd_mlp_layer: layers.Layer = None, + fwd_mlp_kwargs={}, **kwargs, ): super().__init__(**kwargs) @@ -764,16 +772,38 @@ def __init__( self.normalization = normalization - self.MultiHeadAttLayer = ( - MultiHeadGatedSelfAttention - if self.use_gates - else MultiHeadSelfAttention - )( - number_of_heads=self.number_of_heads, - use_bias=self.use_bias, - return_attention_weights=True, - name="MultiHeadAttLayer", - ) + if multi_head_attention_layer is None: + # Raise deprecation warning + warnings.warn( + "The use_gates argument is deprecated and will be removed in a future version. " + "Please use the multi_head_attention_layer argument instead.", + DeprecationWarning, + ) + + self.MultiHeadAttLayer = ( + MultiHeadGatedSelfAttention + if self.use_gates + else MultiHeadSelfAttention + )( + number_of_heads=self.number_of_heads, + use_bias=self.use_bias, + return_attention_weights=True, + name="MultiHeadAttLayer", + ) + else: + self.MultiHeadAttLayer = multi_head_attention_layer( + **multi_head_attention_kwargs + ) + + if fwd_mlp_layer is None: + self.FwdMlpLayer = layers.Dense( + self.fwd_mlp_dim, + name=f"{self.name}/Dense_0", + ) + else: + self.FwdMlpLayer = fwd_mlp_layer(**fwd_mlp_kwargs) + + self.norm_0, self.norm_1 = ( as_normalization(normalization)(**norm_kwargs), as_normalization(normalization)(**norm_kwargs), @@ -783,10 +813,7 @@ def __init__( def build(self, input_shape): self.feed_forward_layer = tf.keras.Sequential( [ - layers.Dense( - self.fwd_mlp_dim, - name=f"{self.name}/Dense_0", - ), + self.FwdMlpLayer, as_activation(self.activation), layers.Dropout(self.dropout), layers.Dense(input_shape[-1], name=f"{self.name}/Dense_1"), @@ -827,7 +854,7 @@ def TransformerEncoderLayer( Activation function of the layer. See keras docs for accepted strings. normalization : str or normalization function or layer Normalization function of the layer. See keras and tfa docs for accepted strings. - use_gates : bool, optional + use_gates : bool, optional [Deprecated] Whether to use gated self-attention layers as update layer. Defaults to False. use_bias: bool, optional Whether to use bias in the dense layers of the attention layers. Defaults to True. diff --git a/deeptrack/models/lodestar/equivariances.py b/deeptrack/models/lodestar/equivariances.py index f5055b7b..a3083225 100644 --- a/deeptrack/models/lodestar/equivariances.py +++ b/deeptrack/models/lodestar/equivariances.py @@ -21,22 +21,32 @@ class Equivariance(Feature): Multiplicative equivariance add : float, array-like Additive equivariance - indexes : optional, int or slice + indices : optional, int or slice Index of related predicted value(s) """ - def __init__(self, mul, add, indexes=slice(None, None, 1), **kwargs): - super().__init__(mul=mul, add=add, indexes=indexes, **kwargs) + def __init__(self, mul, add, indices=slice(None, None, 1), indexes=None, **kwargs): + if indexes is not None: + indices = indexes - def get(self, matvec, mul, add, indexes, **kwargs): + super().__init__(mul=mul, add=add, indices=indices, **kwargs) + + def get(self, matvec, mul, add, indices, **kwargs): A, b = matvec._value mulf = np.eye(len(b)) addf = np.zeros((len(b), 1)) - mulf[indexes, indexes] = mul - addf[indexes] = add + + addf[indices] = add + + if isinstance(indices, (slice, int)): + mulf[indices, indices] = mul + else: + for i in indices: + for j in indices: + mulf[i, j] = mul[i, j] A = mulf @ A b = mulf @ b @@ -54,11 +64,11 @@ class TranslationalEquivariance(Equivariance): """ - def __init__(self, translation, indexes=None): - if indexes is None: - indexes = self.get_indexes + def __init__(self, translation, indices=None): + if indices is None: + indices = self.get_indices super().__init__( - translation=translation, add=self.get_add, mul=self.get_mul, indexes=indexes + translation=translation, add=self.get_add, mul=self.get_mul, indices=indices ) def get_add(self, translation): @@ -67,7 +77,7 @@ def get_add(self, translation): def get_mul(self, translation): return np.eye(len(translation)) - def get_indexes(self, translation): + def get_indices(self, translation): return slice(len(translation)) @@ -81,11 +91,11 @@ class Rotational2DEquivariance(Equivariance): """ - def __init__(self, rotate, indexes=None): - if indexes is None: - indexes = self.get_indexes + def __init__(self, rotate, indices=None): + if indices is None: + indices = self.get_indices super().__init__( - rotate=rotate, add=self.get_add, mul=self.get_mul, indexes=indexes + rotate=rotate, add=self.get_add, mul=self.get_mul, indices=indices ) def get_add(self): @@ -95,7 +105,7 @@ def get_mul(self, rotate): s, c = np.sin(rotate), np.cos(rotate) return np.array([[c, s], [-s, c]]) - def get_indexes(self): + def get_indices(self): return slice(2) @@ -109,11 +119,11 @@ class ScaleEquivariance(Equivariance): """ - def __init__(self, scale, indexes=None): - if indexes is None: - indexes = self.get_indexes + def __init__(self, scale, indices=None): + if indices is None: + indices = self.get_indices super().__init__( - scale=scale, add=self.get_add, mul=self.get_mul, indexes=indexes + scale=scale, add=self.get_add, mul=self.get_mul, indices=indices ) def get_add(self, scale): @@ -122,7 +132,7 @@ def get_add(self, scale): def get_mul(self, scale): return np.diag(scale) - def get_indexes(self, scale): + def get_indices(self, scale): return slice(len(scale)) @@ -138,11 +148,11 @@ class LogScaleEquivariance(Equivariance): """ - def __init__(self, scale, indexes=None): - if indexes is None: - indexes = self.get_indexes + def __init__(self, scale, indices=None): + if indices is None: + indices = self.get_indices super().__init__( - scale=scale, add=self.get_add, mul=self.get_mul, indexes=indexes + scale=scale, add=self.get_add, mul=self.get_mul, indices=indices ) def get_add(self, scale): @@ -151,5 +161,5 @@ def get_add(self, scale): def get_mul(self, scale): return np.eye(len(scale)) - def get_indexes(self, scale): + def get_indices(self, scale): return slice(len(scale)) \ No newline at end of file diff --git a/deeptrack/models/lodestar/models.py b/deeptrack/models/lodestar/models.py index 8f92df5b..b84fa6ac 100644 --- a/deeptrack/models/lodestar/models.py +++ b/deeptrack/models/lodestar/models.py @@ -282,7 +282,7 @@ def default_model(self, input_shape): return model def predict_and_detect( - self, data, alpha=0.5, beta=0.5, cutoff=0.98, mode="quantile" + self, data, alpha=0.5, beta=0.5, cutoff=0.98, mode="quantile", **predict_kwargs ): """Evaluates the model on a batch of data, and detects objects in each frame @@ -296,9 +296,11 @@ def predict_and_detect( Treshholding parameters. Mode can be either "quantile" or "ratio" or "constant". If "quantile", then `ratio` defines the quantile of scores to accept. If "ratio", then cutoff defines the ratio of the max score as threshhold. If constant, the cutoff is used directly as treshhold. + predict_kwargs: dict + Additional arguments to pass to the predict method. """ - pred, weight = self.predict(data) + pred, weight = self.predict(data, **predict_kwargs) detections = [ self.detect(p, w, alpha=alpha, beta=beta, cutoff=cutoff, mode=mode) for p, w in zip(pred, weight) @@ -307,7 +309,7 @@ def predict_and_detect( def predict_and_pool(self, data, mask=1): """Evaluates the model on a batch of data, and pools the predictions in each frame to a single value. - + Used when it's known a-priori that there is only one object per image. Parameters diff --git a/requirements.txt b/requirements.txt index e136ef78..97f7ab0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,6 @@ tensorflow-probability tensorflow-datasets pydeepimagej more_itertools -pint +pint<0.20 pandas tqdm \ No newline at end of file diff --git a/setup.py b/setup.py index 6f54ac41..f0b657b6 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,20 @@ import setuptools -import subprocess import pkg_resources with open("README.md", "r") as fh: long_description = fh.read() -required = [ - "tensorflow", - "tensorflow-probability", - "numpy", - "scipy", - "pint", - "pandas", - "tqdm", - "scikit-image>=0.18.0", - "pydeepimagej", - "more_itertools", -] +with open("requirements.txt", "r") as fh: + required = fh.read().splitlines() + +# Remove sphinx from requirements +required = [x for x in required if not x.startswith("Sphinx")] +required = [x for x in required if not x.startswith("pydata-sphinx-theme")] + installed = [pkg.key for pkg in pkg_resources.working_set] if ( - not "tensorflow" in installed + "tensorflow" not in installed or pkg_resources.working_set.by_key["tensorflow"].version[0] == "2" ): required.append("tensorflow_addons") @@ -28,7 +22,7 @@ setuptools.setup( name="deeptrack", # Replace with your own username - version="1.4.0a8", + version="1.5.0a5", author="Benjamin Midtvedt", author_email="benjamin.midtvedt@physics.gu.se", description="A deep learning oriented microscopy image simulation package",