From d067f04567dc07b81e114205259f6741fa182400 Mon Sep 17 00:00:00 2001 From: Nicholas-Schaub Date: Wed, 12 Jan 2022 21:58:05 -0500 Subject: [PATCH 1/8] Added benchmark library asv --- asv.conf.json | 160 +++++++++++++++++++++++++++++++++++++++ benchmarks/__init__.py | 1 + benchmarks/benchmarks.py | 40 ++++++++++ 3 files changed, 201 insertions(+) create mode 100644 asv.conf.json create mode 100644 benchmarks/__init__.py create mode 100644 benchmarks/benchmarks.py diff --git a/asv.conf.json b/asv.conf.json new file mode 100644 index 00000000..af222490 --- /dev/null +++ b/asv.conf.json @@ -0,0 +1,160 @@ +{ + // The version of the config file format. Do not change, unless + // you know what you are doing. + "version": 1, + + // The name of the project being benchmarked + "project": "PyBaSiC", + + // The project's homepage + "project_url": "https://github.com/peng-lab/PyBaSiC", + + // The URL or local path of the source code repository for the + // project being benchmarked + "repo": ".", + + // The Python project's subdirectory in your repo. If missing or + // the empty string, the project is assumed to be located at the root + // of the repository. + // "repo_subdir": "", + + // Customizable commands for building, installing, and + // uninstalling the project. See asv.conf.json documentation. + // + // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], + // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], + // "build_command": [ + // "python setup.py build", + // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" + // ], + + // List of branches to benchmark. If not provided, defaults to "master" + // (for git) or "default" (for mercurial). + "branches": ["main","dev"], // for git + // "branches": ["default"], // for mercurial + + // The DVCS being used. If not set, it will be automatically + // determined from "repo" by looking at the protocol in the URL + // (if remote), or by looking for special directories, such as + // ".git" (if local). + // "dvcs": "git", + + // The tool to use to create environments. May be "conda", + // "virtualenv" or other value depending on the plugins in use. + // If missing or the empty string, the tool will be automatically + // determined by looking for tools on the PATH environment + // variable. + "environment_type": "virtualenv", + + // timeout in seconds for installing any dependencies in environment + // defaults to 10 min + //"install_timeout": 600, + + // the base URL to show a commit for the project. + // "show_commit_url": "http://github.com/owner/project/commit/", + + // The Pythons you'd like to test against. If not provided, defaults + // to the current version of Python used to run `asv`. + // "pythons": ["2.7", "3.6"], + + // The list of conda channel names to be searched for benchmark + // dependency packages in the specified order + // "conda_channels": ["conda-forge", "defaults"], + + // The matrix of dependencies to test. Each key is the name of a + // package (in PyPI) and the values are version numbers. An empty + // list or empty string indicates to just test against the default + // (latest) version. null indicates that the package is to not be + // installed. If the package to be tested is only available from + // PyPi, and the 'environment_type' is conda, then you can preface + // the package name by 'pip+', and the package will be installed via + // pip (with all the conda available packages installed first, + // followed by the pip installed packages). + // + // "matrix": { + // "numpy": ["1.6", "1.7"], + // "six": ["", null], // test with and without six installed + // "pip+emcee": [""], // emcee is only available for install with pip. + // }, + + // Combinations of libraries/python versions can be excluded/included + // from the set to test. Each entry is a dictionary containing additional + // key-value pairs to include/exclude. + // + // An exclude entry excludes entries where all values match. The + // values are regexps that should match the whole string. + // + // An include entry adds an environment. Only the packages listed + // are installed. The 'python' key is required. The exclude rules + // do not apply to includes. + // + // In addition to package names, the following keys are available: + // + // - python + // Python version, as in the *pythons* variable above. + // - environment_type + // Environment type, as above. + // - sys_platform + // Platform, as in sys.platform. Possible values for the common + // cases: 'linux2', 'win32', 'cygwin', 'darwin'. + // + // "exclude": [ + // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows + // {"environment_type": "conda", "six": null}, // don't run without six on conda + // ], + // + // "include": [ + // // additional env for python2.7 + // {"python": "2.7", "numpy": "1.8"}, + // // additional env if run on windows+conda + // {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""}, + // ], + + // The directory (relative to the current directory) that benchmarks are + // stored in. If not provided, defaults to "benchmarks" + // "benchmark_dir": "benchmarks", + + // The directory (relative to the current directory) to cache the Python + // environments in. If not provided, defaults to "env" + "env_dir": ".asv/env", + + // The directory (relative to the current directory) that raw benchmark + // results are stored in. If not provided, defaults to "results". + "results_dir": ".asv/results", + + // The directory (relative to the current directory) that the html tree + // should be written to. If not provided, defaults to "html". + "html_dir": ".asv/html", + + // The number of characters to retain in the commit hashes. + // "hash_length": 8, + + // `asv` will cache results of the recent builds in each + // environment, making them faster to install next time. This is + // the number of builds to keep, per environment. + // "build_cache_size": 2, + + // The commits after which the regression search in `asv publish` + // should start looking for regressions. Dictionary whose keys are + // regexps matching to benchmark names, and values corresponding to + // the commit (exclusive) after which to start looking for + // regressions. The default is to start from the first commit + // with results. If the commit is `null`, regression detection is + // skipped for the matching benchmark. + // + // "regressions_first_commits": { + // "some_benchmark": "352cdf", // Consider regressions only after this commit + // "another_benchmark": null, // Skip regression detection altogether + // }, + + // The thresholds for relative change in results, after which `asv + // publish` starts reporting regressions. Dictionary of the same + // form as in ``regressions_first_commits``, with values + // indicating the thresholds. If multiple entries match, the + // maximum is taken. If no entry matches, the default is 5%. + // + // "regressions_thresholds": { + // "some_benchmark": 0.01, // Threshold of 1% + // "another_benchmark": 0.5, // Threshold of 50% + // }, +} diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/benchmarks/__init__.py @@ -0,0 +1 @@ + diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py new file mode 100644 index 00000000..cbb03922 --- /dev/null +++ b/benchmarks/benchmarks.py @@ -0,0 +1,40 @@ +# Write the benchmarking functions here. +# See "Writing benchmarks" in the asv docs for more information. +import numpy +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent.resolve().absolute())) +print(sys.path) +from pybasic.tools import dct2d, idct2d + +REPLICATES = 1024 + + +class TimeSuite: + """ + An example benchmark that times the performance of various kinds + of iterating over dictionaries in Python. + """ + + def setup(self): + self.matrix = numpy.random.rand(1024 ** 2).reshape(1024, 1024) + + def time_dct2(self): + for _ in range(REPLICATES): + null = dct2d(self.matrix) + + def time_idct2(self): + for _ in range(REPLICATES): + null = idct2d(self.matrix) + + +class PeakMemSuite: + def setup(self): + self.matrix = numpy.random.rand(1024 ** 2).reshape(1024, 1024) + + def peakmem_dct2(self): + return dct2d(self.matrix) + + def peakmem_idct2(self): + null = idct2d(self.matrix) From 398a92358240be926b8ab806e253dedb2d719c60 Mon Sep 17 00:00:00 2001 From: Nicholas-Schaub Date: Wed, 12 Jan 2022 22:03:28 -0500 Subject: [PATCH 2/8] Revert "Delete CONTRIBUTING.rst" This reverts commit 01d8fe1ae86f2a09ced2cd710bd3e043036deea0. --- CONTRIBUTING.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..c4057483 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,2 @@ +Contributing guide +~~~~~~~~~~~~~~~~~~ \ No newline at end of file From d236563aaa213c176de4b5de9b26f3b3e88284dc Mon Sep 17 00:00:00 2001 From: Nicholas-Schaub Date: Wed, 12 Jan 2022 22:13:10 -0500 Subject: [PATCH 3/8] Revert "Added benchmark library asv" This reverts commit d067f04567dc07b81e114205259f6741fa182400. --- asv.conf.json | 160 --------------------------------------- benchmarks/__init__.py | 1 - benchmarks/benchmarks.py | 40 ---------- 3 files changed, 201 deletions(-) delete mode 100644 asv.conf.json delete mode 100644 benchmarks/__init__.py delete mode 100644 benchmarks/benchmarks.py diff --git a/asv.conf.json b/asv.conf.json deleted file mode 100644 index af222490..00000000 --- a/asv.conf.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - // The version of the config file format. Do not change, unless - // you know what you are doing. - "version": 1, - - // The name of the project being benchmarked - "project": "PyBaSiC", - - // The project's homepage - "project_url": "https://github.com/peng-lab/PyBaSiC", - - // The URL or local path of the source code repository for the - // project being benchmarked - "repo": ".", - - // The Python project's subdirectory in your repo. If missing or - // the empty string, the project is assumed to be located at the root - // of the repository. - // "repo_subdir": "", - - // Customizable commands for building, installing, and - // uninstalling the project. See asv.conf.json documentation. - // - // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], - // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], - // "build_command": [ - // "python setup.py build", - // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" - // ], - - // List of branches to benchmark. If not provided, defaults to "master" - // (for git) or "default" (for mercurial). - "branches": ["main","dev"], // for git - // "branches": ["default"], // for mercurial - - // The DVCS being used. If not set, it will be automatically - // determined from "repo" by looking at the protocol in the URL - // (if remote), or by looking for special directories, such as - // ".git" (if local). - // "dvcs": "git", - - // The tool to use to create environments. May be "conda", - // "virtualenv" or other value depending on the plugins in use. - // If missing or the empty string, the tool will be automatically - // determined by looking for tools on the PATH environment - // variable. - "environment_type": "virtualenv", - - // timeout in seconds for installing any dependencies in environment - // defaults to 10 min - //"install_timeout": 600, - - // the base URL to show a commit for the project. - // "show_commit_url": "http://github.com/owner/project/commit/", - - // The Pythons you'd like to test against. If not provided, defaults - // to the current version of Python used to run `asv`. - // "pythons": ["2.7", "3.6"], - - // The list of conda channel names to be searched for benchmark - // dependency packages in the specified order - // "conda_channels": ["conda-forge", "defaults"], - - // The matrix of dependencies to test. Each key is the name of a - // package (in PyPI) and the values are version numbers. An empty - // list or empty string indicates to just test against the default - // (latest) version. null indicates that the package is to not be - // installed. If the package to be tested is only available from - // PyPi, and the 'environment_type' is conda, then you can preface - // the package name by 'pip+', and the package will be installed via - // pip (with all the conda available packages installed first, - // followed by the pip installed packages). - // - // "matrix": { - // "numpy": ["1.6", "1.7"], - // "six": ["", null], // test with and without six installed - // "pip+emcee": [""], // emcee is only available for install with pip. - // }, - - // Combinations of libraries/python versions can be excluded/included - // from the set to test. Each entry is a dictionary containing additional - // key-value pairs to include/exclude. - // - // An exclude entry excludes entries where all values match. The - // values are regexps that should match the whole string. - // - // An include entry adds an environment. Only the packages listed - // are installed. The 'python' key is required. The exclude rules - // do not apply to includes. - // - // In addition to package names, the following keys are available: - // - // - python - // Python version, as in the *pythons* variable above. - // - environment_type - // Environment type, as above. - // - sys_platform - // Platform, as in sys.platform. Possible values for the common - // cases: 'linux2', 'win32', 'cygwin', 'darwin'. - // - // "exclude": [ - // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows - // {"environment_type": "conda", "six": null}, // don't run without six on conda - // ], - // - // "include": [ - // // additional env for python2.7 - // {"python": "2.7", "numpy": "1.8"}, - // // additional env if run on windows+conda - // {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""}, - // ], - - // The directory (relative to the current directory) that benchmarks are - // stored in. If not provided, defaults to "benchmarks" - // "benchmark_dir": "benchmarks", - - // The directory (relative to the current directory) to cache the Python - // environments in. If not provided, defaults to "env" - "env_dir": ".asv/env", - - // The directory (relative to the current directory) that raw benchmark - // results are stored in. If not provided, defaults to "results". - "results_dir": ".asv/results", - - // The directory (relative to the current directory) that the html tree - // should be written to. If not provided, defaults to "html". - "html_dir": ".asv/html", - - // The number of characters to retain in the commit hashes. - // "hash_length": 8, - - // `asv` will cache results of the recent builds in each - // environment, making them faster to install next time. This is - // the number of builds to keep, per environment. - // "build_cache_size": 2, - - // The commits after which the regression search in `asv publish` - // should start looking for regressions. Dictionary whose keys are - // regexps matching to benchmark names, and values corresponding to - // the commit (exclusive) after which to start looking for - // regressions. The default is to start from the first commit - // with results. If the commit is `null`, regression detection is - // skipped for the matching benchmark. - // - // "regressions_first_commits": { - // "some_benchmark": "352cdf", // Consider regressions only after this commit - // "another_benchmark": null, // Skip regression detection altogether - // }, - - // The thresholds for relative change in results, after which `asv - // publish` starts reporting regressions. Dictionary of the same - // form as in ``regressions_first_commits``, with values - // indicating the thresholds. If multiple entries match, the - // maximum is taken. If no entry matches, the default is 5%. - // - // "regressions_thresholds": { - // "some_benchmark": 0.01, // Threshold of 1% - // "another_benchmark": 0.5, // Threshold of 50% - // }, -} diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/benchmarks/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py deleted file mode 100644 index cbb03922..00000000 --- a/benchmarks/benchmarks.py +++ /dev/null @@ -1,40 +0,0 @@ -# Write the benchmarking functions here. -# See "Writing benchmarks" in the asv docs for more information. -import numpy -import sys -from pathlib import Path - -sys.path.append(str(Path(__file__).parent.parent.resolve().absolute())) -print(sys.path) -from pybasic.tools import dct2d, idct2d - -REPLICATES = 1024 - - -class TimeSuite: - """ - An example benchmark that times the performance of various kinds - of iterating over dictionaries in Python. - """ - - def setup(self): - self.matrix = numpy.random.rand(1024 ** 2).reshape(1024, 1024) - - def time_dct2(self): - for _ in range(REPLICATES): - null = dct2d(self.matrix) - - def time_idct2(self): - for _ in range(REPLICATES): - null = idct2d(self.matrix) - - -class PeakMemSuite: - def setup(self): - self.matrix = numpy.random.rand(1024 ** 2).reshape(1024, 1024) - - def peakmem_dct2(self): - return dct2d(self.matrix) - - def peakmem_idct2(self): - null = idct2d(self.matrix) From 1999224cce71d231c79f043a9fd8e3c2eee0935c Mon Sep 17 00:00:00 2001 From: Nicholas-Schaub Date: Wed, 12 Jan 2022 22:13:36 -0500 Subject: [PATCH 4/8] Deleted CONTRIBUTING.rst --- CONTRIBUTING.rst | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index c4057483..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,2 +0,0 @@ -Contributing guide -~~~~~~~~~~~~~~~~~~ \ No newline at end of file From 5c935f5c0fff42e9e7461fd892c3895438966e21 Mon Sep 17 00:00:00 2001 From: Nicholas Schaub Date: Wed, 30 Nov 2022 08:49:54 -0500 Subject: [PATCH 5/8] Cleaned up code and logging. --- src/basicpy/basicpy.py | 91 +++++++++++++++++++---------------------- src/basicpy/datasets.py | 1 + 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/basicpy/basicpy.py b/src/basicpy/basicpy.py index ff0301fc..0e52ae06 100644 --- a/src/basicpy/basicpy.py +++ b/src/basicpy/basicpy.py @@ -1,5 +1,4 @@ -"""Main BaSiC class. -""" +"""Main BaSiC class.""" # Core modules from __future__ import annotations @@ -11,7 +10,7 @@ from enum import Enum from multiprocessing import cpu_count from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import jax.numpy as jnp @@ -20,10 +19,7 @@ from jax import device_put from jax.image import ResizeMethod from jax.image import resize as jax_resize - -# FIXME change this to jax.xla.XlaRuntimeError -# when https://github.com/google/jax/pull/10676 gets merged -from pydantic import BaseModel, Field, PrivateAttr +from pydantic import BaseModel, Field, PrivateAttr, root_validator from skimage.transform import resize as skimage_resize from basicpy._jax_routines import ApproximateFit, LadmapFit @@ -34,9 +30,6 @@ newax = jnp.newaxis -# from basicpy.tools.dct2d_tools import dct2d, idct2d -# from basicpy.tools.inexact_alm import inexact_alm_rspca_l1 - # Get number of available threads to limit CPU thrashing # From preadator: https://pypi.org/project/preadator/ if hasattr(os, "sched_getaffinity"): @@ -54,6 +47,7 @@ class Device(Enum): + """Device selection enum.""" cpu: str = "cpu" gpu: str = "gpu" @@ -61,12 +55,14 @@ class Device(Enum): class FittingMode(str, Enum): + """Fit method enum.""" ladmap: str = "ladmap" approximate: str = "approximate" class ResizeMode(str, Enum): + """Resize method enum.""" jax: str = "jax" skimage: str = "skimage" @@ -74,12 +70,13 @@ class ResizeMode(str, Enum): class TimelapseTransformMode(str, Enum): + """Timelapse transformation enum.""" additive: str = "additive" multiplicative: str = "multiplicative" -# multiple channels should be handled by creating a `basic` object for each chan +# multiple channels should be handled by creating a `basic` object for each channel class BaSiC(BaseModel): """A class for fitting and applying BaSiC illumination correction profiles.""" @@ -101,7 +98,6 @@ class BaSiC(BaseModel): fitting_mode: FittingMode = Field( FittingMode.ladmap, description="Must be one of ['ladmap', 'approximate']" ) - epsilon: float = Field( 0.1, description="Weight regularization term.", @@ -194,29 +190,23 @@ class BaSiC(BaseModel): _profiles_fname = "profiles.npy" class Config: + """Pydantic class configuration.""" arbitrary_types_allowed = True extra = "forbid" - def __init__(self, **kwargs) -> None: - """Initialize BaSiC with the provided settings.""" - - log_str = f"Initializing BaSiC {id(self)} with parameters: \n" - for k, v in kwargs.items(): - log_str += f"{k}: {v}\n" - logger.info(log_str) - - super().__init__(**kwargs) - - if self.device is not Device.cpu: - # TODO: sanity checks on device selection - pass + @root_validator(pre=True) + def debug_log_values(cls, values: Dict[str, Any]): + """Use a validator to echo input values.""" + logger.debug("Initializing BaSiC with parameters:") + for k, v in values.items(): + logger.debug(f"{k}: {v}") + return values def __call__( self, images: np.ndarray, timelapse: bool = False ) -> Union[Tuple[np.ndarray, np.ndarray], np.ndarray]: - """Shortcut for BaSiC.transform""" - + """Shortcut for `BaSiC.transform`.""" return self.transform(images, timelapse) def _resize(self, Im, target_shape): @@ -225,11 +215,13 @@ def _resize(self, Im, target_shape): resize_params.update(self.resize_params) Im = device_put(Im).astype(jnp.float32) return jax_resize(Im, target_shape, **resize_params) + elif self.resize_mode == ResizeMode.skimage: Im = skimage_resize( Im, target_shape, preserve_range=True, **self.resize_params ) return device_put(Im).astype(jnp.float32) + elif self.resize_mode == ResizeMode.skimage_dask: assert np.array_equal(target_shape[:-2], Im.shape[:-2]) import dask.array as da @@ -252,9 +244,7 @@ def _resize(self, Im, target_shape): return device_put(Im).astype(jnp.float32) def _resize_to_working_size(self, Im): - """ - Resize the images to the working size. - """ + """Resize the images to the working size.""" if self.working_size is not None: if np.isscalar(self.working_size): working_shape = [self.working_size] * (Im.ndim - 2) @@ -273,8 +263,7 @@ def _resize_to_working_size(self, Im): def fit( self, images: np.ndarray, fitting_weight: Optional[np.ndarray] = None ) -> None: - """ - Generate illumination correction profiles from images. + """Generate illumination correction profiles from images. Args: images: Input images to fit shading model. @@ -293,7 +282,6 @@ def fit( >>> basic.fit(images) """ - ndim = images.ndim if images.ndim == 3: images = images[:, np.newaxis, ...] @@ -352,9 +340,9 @@ def fit( self._smoothness_darkfield = self.smoothness_darkfield self._sparse_cost_darkfield = self.sparse_cost_darkfield - logger.info(f"_smoothness_flatfield set to {self._smoothness_flatfield}") - logger.info(f"_smoothness_darkfield set to {self._smoothness_darkfield}") - logger.info(f"_sparse_cost_darkfield set to {self._sparse_cost_darkfield}") + logger.debug(f"_smoothness_flatfield set to {self._smoothness_flatfield}") + logger.debug(f"_smoothness_darkfield set to {self._smoothness_darkfield}") + logger.debug(f"_sparse_cost_darkfield set to {self._sparse_cost_darkfield}") # spectral_norm = jnp.linalg.norm(Im.reshape((Im.shape[0], -1)), ord=2) _temp = jnp.linalg.svd(Im2.reshape((Im2.shape[0], -1)), full_matrices=False) @@ -393,7 +381,7 @@ def fit( fitting_step = ApproximateFit(**fit_params) for i in range(self.max_reweight_iterations): - logger.info(f"reweighting iteration {i}") + logger.debug(f"reweighting iteration {i}") if self.fitting_mode == FittingMode.approximate: S = jnp.zeros(Im2.shape[1:], dtype=jnp.float32) else: @@ -415,11 +403,11 @@ def fit( B, I_R, ) - logger.info(f"single-step optimization score: {norm_ratio}.") - logger.info(f"mean of S: {float(jnp.mean(S))}.") + logger.debug(f"single-step optimization score: {norm_ratio}.") + logger.debug(f"mean of S: {float(jnp.mean(S))}.") self._score = norm_ratio if not converged: - logger.warning("single-step optimization did not converge.") + logger.debug("single-step optimization did not converge.") if S.max() == 0: logger.error("S is zero. Please try to decrease smoothness_darkfield.") raise RuntimeError( @@ -441,7 +429,7 @@ def fit( self._weight_dark = W_D self._residual = I_R - logger.info(f"Iteration {i} finished.") + logger.debug(f"Iteration {i} finished.") if last_S is not None: mad_flatfield = jnp.sum(jnp.abs(S - last_S)) / jnp.sum(np.abs(last_S)) if self.get_darkfield: @@ -451,8 +439,11 @@ def fit( self._reweight_score = max(mad_flatfield, mad_darkfield) else: self._reweight_score = mad_flatfield - logger.info(f"reweighting score: {self._reweight_score}") - logger.info(f"elapsed time: {time.monotonic() - start_time} seconds") + logger.debug(f"reweighting score: {self._reweight_score}") + logger.info( + f"Iteration {i} elapsed time: " + + f"{time.monotonic() - start_time} seconds" + ) if self._reweight_score <= self.reweighting_tol: logger.info("Reweighting converged.") @@ -472,7 +463,7 @@ def fit( if self.fitting_mode == FittingMode.approximate: B = jnp.mean(Im, axis=(1, 2, 3)) I_R = jnp.zeros(Im.shape, dtype=jnp.float32) - logger.info(f"reweighting iteration for baseline {i}") + logger.debug(f"reweighting iteration for baseline {i}") I_R, B, norm_ratio, converged = fitting_step.fit_baseline( Im, W, @@ -486,7 +477,7 @@ def fit( W = fitting_step.calc_weights_baseline(I_B, I_R) * Ws self._weight = W self._residual = I_R - logger.info(f"Iteration {i} finished.") + logger.debug(f"Iteration {i} finished.") self.flatfield = skimage_resize(S, images.shape[1:]) self.darkfield = skimage_resize(D, images.shape[1:]) @@ -507,7 +498,7 @@ def transform( images: input images to correct. See `fit`. timelapse: If `True`, corrects the timelapse/photobleaching offsets, assuming that the residual is the product of flatfield and - the object fluorescence. Also accepts "multplicative" + the object fluorescence. Also accepts "multiplicative" (the same as `True`) or "additive" (residual is the object fluorescence). @@ -518,7 +509,6 @@ def transform( >>> basic.fit(images) >>> corrected = basic.transform(images) """ - if self.baseline is None: raise RuntimeError("BaSiC object is not initialized") @@ -598,12 +588,12 @@ def fit_transform( @property def score(self): - """The BaSiC fit final score""" + """The BaSiC fit final score.""" return self._score @property def reweight_score(self): - """The BaSiC fit final reweighting score""" + """The BaSiC fit final reweighting score.""" return self._reweight_score @property @@ -622,7 +612,8 @@ def save_model(self, model_dir: PathLike, overwrite: bool = False) -> None: model_dir: path to model directory Raises: - FileExistsError: if model directory already exists""" + FileExistsError: if model directory already exists + """ path = Path(model_dir) try: diff --git a/src/basicpy/datasets.py b/src/basicpy/datasets.py index 2539d9af..9736a2e4 100644 --- a/src/basicpy/datasets.py +++ b/src/basicpy/datasets.py @@ -1,3 +1,4 @@ +"""Datasets used for testing.""" import glob from os import path From d6423bbe5261a3c97108c6e38b21f9936d2db215 Mon Sep 17 00:00:00 2001 From: Nicholas Schaub Date: Thu, 1 Dec 2022 07:08:04 -0500 Subject: [PATCH 6/8] Changed error message comment --- src/basicpy/basicpy.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/basicpy/basicpy.py b/src/basicpy/basicpy.py index 0e52ae06..09bd2fa0 100644 --- a/src/basicpy/basicpy.py +++ b/src/basicpy/basicpy.py @@ -409,9 +409,13 @@ def fit( if not converged: logger.debug("single-step optimization did not converge.") if S.max() == 0: - logger.error("S is zero. Please try to decrease smoothness_darkfield.") + logger.error( + "Estimated flatfield is zero. " + + "Please try to decrease smoothness_darkfield." + ) raise RuntimeError( - "S is zero. Please try to decrease smoothness_darkfield." + "Estimated flatfield is zero. " + + "Please try to decrease smoothness_darkfield." ) self._S = S self._D_R = D_R From 2bdfb1afba691351d27eea76211248f0a865a06d Mon Sep 17 00:00:00 2001 From: "Yohsuke T. Fukai" Date: Thu, 1 Dec 2022 21:21:33 +0900 Subject: [PATCH 7/8] Add single-step convergence warning after the loop --- src/basicpy/basicpy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basicpy/basicpy.py b/src/basicpy/basicpy.py index 09bd2fa0..bb75d275 100644 --- a/src/basicpy/basicpy.py +++ b/src/basicpy/basicpy.py @@ -457,6 +457,9 @@ def fit( last_S = S last_D = D + if not converged: + logger.warning("Single-step optimization did not converge at the last reweighting step.") + assert S is not None assert D is not None assert B is not None From 7da6fddee53c0353d7448311a121a21acfbfbedc Mon Sep 17 00:00:00 2001 From: Nicholas Schaub Date: Thu, 1 Dec 2022 07:47:00 -0500 Subject: [PATCH 8/8] Change default log level to warning --- src/basicpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicpy/__init__.py b/src/basicpy/__init__.py index aab90d0c..b337f0a5 100644 --- a/src/basicpy/__init__.py +++ b/src/basicpy/__init__.py @@ -7,7 +7,7 @@ from basicpy.basicpy import BaSiC # Set logger level from environment variable -logging_level = os.getenv("BASIC_LOG_LEVEL", default="INFO").upper() +logging_level = os.getenv("BASIC_LOG_LEVEL", default="WARNING").upper() logger = logging.getLogger(__name__) logger.setLevel(logging_level)