Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
# Conflicts:
#	poetry.lock
#	pyproject.toml
  • Loading branch information
juliomateoslangerak committed Oct 16, 2024
2 parents edf5a9d + 9680ce3 commit 146d6fc
Show file tree
Hide file tree
Showing 22 changed files with 2,407 additions and 1,063 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ repos:
# pre-commit's default_language_version, see
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.11

files: ^(src|test)/.*\.py$

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
name: isort (python)
files: ^(src|test)/.*\.py$
7 changes: 4 additions & 3 deletions docs/examples/new_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
See official documentation at https://scikit-image.org/docs/0.7.0/api/skimage.transform.html#probabilistic-hough
The procedure to follow is, in short:
- import everything from the samples module
- import everything from the analyses module
- import the types that you might be using from the typing module
- import any necessary libraries that you will need for your analysis
- Create one or more subclasses of the Analysis abstract class of samples. Within each class:
- Create one or more subclasses of the Analysis abstract class of analyses. Within each class:
- define your input requirements
- define a 'run' method that will implement the logic of your analysis
- if desired, define a 'plot' method returning a plot showing the results of the analysis
"""

from math import atan2

# import the types that you may be using
Expand All @@ -25,7 +26,7 @@
from skimage.transform import probabilistic_hough_line

# import the sample functionality
from microscopemetrics.samples import *
from microscopemetrics.analyses import *


class DetectLinesAnalysis(
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks/microscope-metrics_tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -805,4 +805,4 @@
},
"nbformat": 4,
"nbformat_minor": 1
}
}
2,072 changes: 2,009 additions & 63 deletions poetry.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ documentation = "https://github.com/MontpellierRessourcesImagerie/microscope-met
keywords = ["microscopy", "metrology"]

[tool.pytest.ini_options]
addopts = "--hypothesis-show-statistics --hypothesis-profile=dev --workers=auto"
addopts = "--hypothesis-show-statistics --hypothesis-profile=dev" # --workers=auto"
testpaths = [
"tests"
]
Expand All @@ -30,13 +30,17 @@ filterwarnings = [
"ignore:.*urllib3 v2 only supports OpenSSL.*:Warning",
]

[tool.isort]
profile = "black"

[tool.poetry.dependencies]
python = ">=3.10.12 <3.12"
python = ">=3.9.18 <3.12"
numpy = "^1"
scikit-image = "^0"
scipy = "^1"
pandas = "^1"
microscopemetrics-schema = {git = "https://github.com/juliomateoslangerak/microscopemetrics-schema.git"}
matplotlib = "^3"
microscopemetrics-schema = {git = "https://github.com/juliomateoslangerak/microscopemetrics-schema.git", branch = "dev"}

[tool.poetry.group.test.dependencies]
pytest = "^7"
Expand All @@ -48,6 +52,7 @@ pytest-parallel = "^0.1.1"
[tool.poetry.group.dev.dependencies]
black = "^23"
poetry = "^1"
jupyter = "^1"
isort = "^5"
pre-commit = "^3"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Main samples module defining the sample superclass
# Main analyses module defining the sample superclass
import logging
from abc import ABC, abstractmethod
from datetime import datetime
Expand All @@ -8,51 +8,8 @@
import numpy as np
import pandas as pd

# We are defining some global dictionaries to register the different analysis types
IMAGE_ANALYSIS_REGISTRY = {}
DATASET_ANALYSIS_REGISTRY = {}
PROGRESSION_ANALYSIS_REGISTRY = {}


# Decorators to register exposed analysis functions
def register_image_analysis(cls):
IMAGE_ANALYSIS_REGISTRY[cls.__name__] = cls
return cls


def register_dataset_analysis(cls):
DATASET_ANALYSIS_REGISTRY[cls.__name__] = cls
return cls


def register_progression_analysis(cls):
PROGRESSION_ANALYSIS_REGISTRY[cls.__name__] = cls
return cls


# Create a logging service
logger = logging.getLogger(__name__)
# TODO: work on the loggers


# def get_references(
# objects: Union[mm_schema.MetricsObject, list[mm_schema.MetricsObject]]
# ) -> List[mm_schema.DataReference]:
# """Get the references of a metrics object or a list of metrics objects"""
# if isinstance(objects, mm_schema.MetricsObject):
# return mm_schema.DataReference(
# data_uri=objects.data_uri,
# # HACK: This is a temporary fix to get the first element of the tuple
# omero_host=objects.omero_host[0],
# omero_port=objects.omero_port[0],
# omero_object_type=objects.omero_object_type[0],
# omero_object_id=objects.omero_object_id,
# )
#
# elif isinstance(objects, list):
# return [get_references(obj) for obj in objects]
# else:
# raise ValueError("Input should be a metrics object or a list of metrics objects")


def get_object_id(
Expand Down Expand Up @@ -152,72 +109,6 @@ def numpy_to_mm_image(
)


# def numpy_to_mask_inlined(
# array: np.ndarray,
# name: str = None,
# description: str = None,
# image_url: str = None,
# source_image_url: Union[str, List[str]] = None,
# ) -> mm_schema.ImageMask:
# """Converts a bool numpy array with dimensions order yx to an inlined mask"""
# if array.ndim != 2:
# raise ValueError("Input array should be 2D")
# if array.dtype != bool and array.dtype == np.uint8:
# try:
# array = array.astype(bool)
# except ValueError:
# raise ValueError("Input array could not be casted to type bool")
# if array.dtype != bool:
# raise ValueError("Input array should be of type bool")
#
# return mm_schema.ImageMask(
# name=name,
# description=description,
# image_url=image_url,
# source_image_url=source_image_url,
# data=array.flatten().tolist(),
# shape_y=array.shape[0],
# shape_x=array.shape[1],
# )
#
#
# def numpy_to_image_inlined(
# array: np.ndarray,
# name: str = None,
# description: str = None,
# image_url: str = None,
# source_image_url: Union[str, List[str]] = None,
# ) -> mm_schema.ImageInline:
# """Converts a numpy array with dimensions order tzyxc or yx to an inlined image"""
# if array.ndim == 5:
# return mm_schema.Image5D(
# name=name,
# description=description,
# image_url=image_url,
# source_image_url=source_image_url,
# data=array.flatten().tolist(),
# shape_t=array.shape[0],
# shape_z=array.shape[1],
# shape_y=array.shape[2],
# shape_x=array.shape[3],
# shape_c=array.shape[4],
# )
# elif array.ndim == 2:
# return mm_schema.Image2D(
# name=name,
# description=description,
# image_url=image_url,
# source_image_url=source_image_url,
# data=array.flatten().tolist(),
# shape_y=array.shape[0],
# shape_x=array.shape[1],
# )
# else:
# raise NotImplementedError(
# f"Array of dimension {array.ndim} is not supported by this function"
# )


def _create_table(
data: Union[dict[str, list], pd.DataFrame],
name: str,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
from datetime import datetime
from itertools import product
from typing import Any, Dict, List, Tuple, Union
from typing import Dict, List, Tuple

import microscopemetrics_schema.datamodel as mm_schema
import numpy as np
import pandas as pd
from numpy import float64, int64, ndarray
from pandas import DataFrame
from scipy.interpolate import griddata
from scipy.optimize import curve_fit
from scipy.signal import find_peaks
from skimage.transform import hough_line # hough_line_peaks, probabilistic_hough_line

from microscopemetrics.analysis.tools import (
from microscopemetrics.analyses import logger, numpy_to_mm_image, validate_requirements
from microscopemetrics.analyses.tools import (
airy_fun,
compute_distances_matrix,
compute_spots_properties,
is_saturated,
multi_airy_fun,
segment_image,
)
from microscopemetrics.samples import logger, numpy_to_mm_image, validate_requirements
from microscopemetrics.utilities.utilities import airy_fun, is_saturated, multi_airy_fun


def _profile_to_columns(profile: ndarray, channel: int) -> List[Dict[str, Dict[str, List[float]]]]:
Expand Down Expand Up @@ -223,12 +224,12 @@ def _compute_resolution(
)


def analise_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
def analyse_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
validate_requirements()

# Check image shape
logger.info("Checking image shape...")
image = dataset.input.argolight_b_image.data
image = dataset.input_data.argolight_b_image.data
if len(image.shape) != 5:
logger.error("Image must be 5D")
return False
Expand All @@ -239,8 +240,8 @@ def analise_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
for c in range(image.shape[-1]):
if is_saturated(
channel=image[:, :, :, :, c],
threshold=dataset.input.saturation_threshold,
detector_bit_depth=dataset.input.bit_depth,
threshold=dataset.input_parameters.saturation_threshold,
detector_bit_depth=dataset.input_parameters.bit_depth,
):
logger.error(f"Channel {c} is saturated")
saturated_channels.append(c)
Expand All @@ -249,32 +250,36 @@ def analise_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
return False

# Calculating the distance between spots in pixels with a security margin
min_distance = round(dataset.input.spots_distance * 0.3)
min_distance = round(dataset.input_parameters.spots_distance * 0.3)

# Calculating the maximum tolerated distance in microns for the same spot in a different channels
max_distance = dataset.input.spots_distance * 0.4
max_distance = dataset.input_parameters.spots_distance * 0.4

labels = segment_image(
image=image,
min_distance=min_distance,
sigma=(dataset.input.sigma_z, dataset.input.sigma_y, dataset.input.sigma_x),
sigma=(
dataset.input_parameters.sigma_z,
dataset.input_parameters.sigma_y,
dataset.input_parameters.sigma_x,
),
method="local_max",
low_corr_factors=dataset.input.lower_threshold_correction_factors,
high_corr_factors=dataset.input.upper_threshold_correction_factors,
low_corr_factors=dataset.input_parameters.lower_threshold_correction_factors,
high_corr_factors=dataset.input_parameters.upper_threshold_correction_factors,
)

dataset.output.spots_labels_image = numpy_to_mm_image( # TODO: this should be a mask
array=labels,
name=f"{dataset.input.argolight_b_image.name}_spots_labels",
description=f"Spots labels of {dataset.input.argolight_b_image.image_url}",
image_url=dataset.input.argolight_b_image.image_url,
source_image_url=dataset.input.argolight_b_image.image_url,
name=f"{dataset.input_data.argolight_b_image.name}_spots_labels",
description=f"Spots labels of {dataset.input_data.argolight_b_image.image_url}",
image_url=dataset.input_data.argolight_b_image.image_url,
source_image_url=dataset.input_data.argolight_b_image.image_url,
)

spots_properties, spots_positions = compute_spots_properties(
image=image,
labels=labels,
remove_center_cross=dataset.input.remove_center_cross,
remove_center_cross=dataset.input_parameters.remove_center_cross,
)

distances_df = compute_distances_matrix(
Expand Down Expand Up @@ -338,7 +343,7 @@ def analise_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
spots_centroids.append(
mm_schema.Roi(
label=f"Centroids_ch{ch:02}",
image=dataset.input.argolight_b_image.image_url,
image=dataset.input_data.argolight_b_image.image_url,
shapes=channel_shapes,
)
)
Expand Down Expand Up @@ -398,7 +403,7 @@ def analise_argolight_b(dataset: mm_schema.ArgolightBDataset) -> bool:
return True


def analise_argolight_e(dataset: mm_schema.ArgolightEDataset) -> bool:
def analyse_argolight_e(dataset: mm_schema.ArgolightEDataset) -> bool:
validate_requirements()

# Check image shape
Expand All @@ -410,11 +415,11 @@ def analise_argolight_e(dataset: mm_schema.ArgolightEDataset) -> bool:
# Check for axis value
pass # TODO: implement

image = dataset.input.argolight_e_image.data
image = dataset.input_data.argolight_e_image.data
image = image[0] # if there is a time dimension, take the first one
axis = dataset.input.orientation_axis
pixel_size = dataset.input.argolight_e_image.voxel_size_x_micron
measured_band = dataset.input.measured_band
axis = dataset.input_parameters.orientation_axis
pixel_size = dataset.input_data.argolight_e_image.voxel_size_x_micron
measured_band = dataset.input_parameters.measured_band

(
profiles,
Expand All @@ -428,7 +433,7 @@ def analise_argolight_e(dataset: mm_schema.ArgolightEDataset) -> bool:
image=image,
axis=axis,
measured_band=measured_band,
prominence=dataset.input.prominence_threshold,
prominence=dataset.input_parameters.prominence_threshold,
do_angle_refinement=False, # TODO: implement angle refinement
)
key_values = {
Expand Down Expand Up @@ -498,7 +503,7 @@ def analise_argolight_e(dataset: mm_schema.ArgolightEDataset) -> bool:
rois.append(
mm_schema.Roi(
label=f"ch{ch:02}_peaks",
image=dataset.input.argolight_e_image.image_url,
image=dataset.input_data.argolight_e_image.image_url,
shapes=shapes,
)
)
Expand Down
Loading

0 comments on commit 146d6fc

Please sign in to comment.