diff --git a/qim3d/filters/_common_filter_methods.py b/qim3d/filters/_common_filter_methods.py index 59b0be1291ff2f3480b44dc3df524c9794a2c6ed..026d292691b0d00946c78e832fb1e8571226fd80 100644 --- a/qim3d/filters/_common_filter_methods.py +++ b/qim3d/filters/_common_filter_methods.py @@ -8,9 +8,10 @@ from skimage import morphology import dask.array as da import dask_image.ndfilters as dask_ndfilters -from qim3d.utils._logger import log +from qim3d.utils import log __all__ = [ + "FilterBase", "Gaussian", "Median", "Maximum", diff --git a/qim3d/gui/annotation_tool.py b/qim3d/gui/annotation_tool.py index 5ebae5f76cefe4d983f9dd1ac1dd24e5127feb62..79910e763048887c0f8f0fe1bd24d7fe7deaf6d3 100644 --- a/qim3d/gui/annotation_tool.py +++ b/qim3d/gui/annotation_tool.py @@ -27,10 +27,7 @@ import tempfile import gradio as gr import numpy as np from PIL import Image - -from qim3d.io import load, save -from qim3d.operations._common_operations_methods import overlay_rgb_images -from qim3d.gui.interface import BaseInterface +import qim3d # TODO: img in launch should be self.img @@ -68,7 +65,7 @@ class Interface(BaseInterface): for temp_file in temp_path_list: mask_file = os.path.basename(temp_file) mask_name = os.path.splitext(mask_file)[0] - masks[mask_name] = load(temp_file) + masks[mask_name] = qim3d.io.load(temp_file) return masks @@ -98,7 +95,7 @@ class Interface(BaseInterface): def create_preview(self, img_editor: gr.ImageEditor) -> np.ndarray: background = img_editor["background"] masks = img_editor["layers"][0] - overlay_image = overlay_rgb_images(background, masks) + overlay_image = qim3d.operations.overlay_rgb_images(background, masks) return overlay_image def cerate_download_list(self, img_editor: gr.ImageEditor) -> list[str]: @@ -122,7 +119,7 @@ class Interface(BaseInterface): filepath = os.path.join(self.temp_dir, filename) files_list.append(filepath) - save(filepath, mask, replace=True) + qim3d.io.save(filepath, mask, replace=True) self.temp_files.append(filepath) return files_list diff --git a/qim3d/gui/iso3d.py b/qim3d/gui/iso3d.py index c7d4620ff1ebdf0df84255cf9a733de047b54d9e..2725403f094dd58e89030f23dba06982551491f4 100644 --- a/qim3d/gui/iso3d.py +++ b/qim3d/gui/iso3d.py @@ -22,9 +22,8 @@ import numpy as np import plotly.graph_objects as go from scipy import ndimage -from qim3d.io import load +import qim3d from qim3d.utils._logger import log - from qim3d.gui.interface import InterfaceWithExamples @@ -46,7 +45,7 @@ class Interface(InterfaceWithExamples): def load_data(self, gradiofile: gr.File): try: - self.vol = load(gradiofile.name) + self.vol = qim3d.io.load(gradiofile.name) assert self.vol.ndim == 3 except AttributeError: raise gr.Error("You have to select a file") diff --git a/qim3d/gui/layers2d.py b/qim3d/gui/layers2d.py index febd16a506bcba477eed1d0f42ba6e20b763d610..8b7ae4419f2fc388c8e020d2b39556c83d585ff4 100644 --- a/qim3d/gui/layers2d.py +++ b/qim3d/gui/layers2d.py @@ -21,7 +21,6 @@ import os import gradio as gr import numpy as np - from .interface import BaseInterface # from qim3d.processing import layers2d as l2d @@ -358,7 +357,7 @@ class Interface(BaseInterface): raise gr.Error("Invalid file path") try: - self.data = load( + self.data = qim3d.io.load( file_path, progress_bar=False ) @@ -403,7 +402,7 @@ class Interface(BaseInterface): if self.is_transposed(slicing_axis, segmenting_axis): slice = np.rot90(slice) - self.__dict__[seg_key] = segment_layers(slice, inverted = inverted, n_layers = n_layers, delta = delta, min_margin = min_margin, wrap = wrap) + self.__dict__[seg_key] = qim3d.processing.segment_layers(slice, inverted = inverted, n_layers = n_layers, delta = delta, min_margin = min_margin, wrap = wrap) return process @@ -456,13 +455,13 @@ class Interface(BaseInterface): seg = np.rot90(seg, k = 3) # slice = 255 * (slice/np.max(slice)) # return image_with_overlay(np.repeat(slice[..., None], 3, -1), seg, alpha) - return overlay_rgb_images(slice, seg, alpha) + return qim3d.operations.overlay_rgb_images(slice, seg, alpha) else: - lines = get_lines(seg) + lines = qim3d.processing.get_lines(seg) if self.is_transposed(slicing_axis, segmenting_axis): - return image_with_lines(np.rot90(slice), lines, line_thickness).rotate(270, expand = True) + return qim3d.viz.image_with_lines(np.rot90(slice), lines, line_thickness).rotate(270, expand = True) else: - return image_with_lines(slice, lines, line_thickness) + return qim3d.viz.image_with_lines(slice, lines, line_thickness) return plot_output_img diff --git a/qim3d/gui/local_thickness.py b/qim3d/gui/local_thickness.py index 461f3e1774fedb3ac61e0f916b3c72bdafff6f92..4a4cbf474ae5283a73ed9305b9969e3dfd0b812b 100644 --- a/qim3d/gui/local_thickness.py +++ b/qim3d/gui/local_thickness.py @@ -40,12 +40,11 @@ import gradio as gr import numpy as np import tifffile import localthickness as lt +import qim3d -from qim3d.io import load -from qim3d.gui.interface import InterfaceWithExamples -class Interface(InterfaceWithExamples): +class Interface(qim3d.gui.interface.InterfaceWithExamples): def __init__(self, img: np.ndarray = None, verbose:bool = False, @@ -79,7 +78,7 @@ class Interface(InterfaceWithExamples): file_idx = np.argmax(creation_time_list) # Load the temporary file - vol_lt = load(temp_path_list[file_idx]) + vol_lt = qim3d.io.load(temp_path_list[file_idx]) return vol_lt @@ -251,7 +250,7 @@ class Interface(InterfaceWithExamples): def process_input(self, data: np.ndarray, dark_objects: bool): # Load volume try: - self.vol = load(data.name) + self.vol = qim3d.io.load(data.name) assert self.vol.ndim == 3 except AttributeError: self.vol = data diff --git a/qim3d/io/_convert.py b/qim3d/io/_convert.py index 0d776822689430b733dfa1467ecdff0b48d87bb1..55ab80cb14218ccae8184b2a95488a0de8801431 100644 --- a/qim3d/io/_convert.py +++ b/qim3d/io/_convert.py @@ -10,7 +10,7 @@ from tqdm import tqdm import zarr.core from qim3d.utils._misc import stringify_path -from qim3d.io._saving import save +from qim3d.io import save class Convert: diff --git a/qim3d/io/_downloader.py b/qim3d/io/_downloader.py index 18e36ca591ad2bf0859cc2b05f18d0033de3f8ee..c77d61ccb016e3745c7a15736a179699a2c6cf9a 100644 --- a/qim3d/io/_downloader.py +++ b/qim3d/io/_downloader.py @@ -8,7 +8,7 @@ from tqdm import tqdm from pathlib import Path from qim3d.io import load -from qim3d.utils._logger import log +from qim3d.utils import log import outputformat as ouf diff --git a/qim3d/io/_loading.py b/qim3d/io/_loading.py index e3d7d45bc220a04c0e96e4efd5fbb2f94cc3c99a..b34ac1521daa4c3dee1fbd6f8efba0402ea62c38 100644 --- a/qim3d/io/_loading.py +++ b/qim3d/io/_loading.py @@ -23,9 +23,9 @@ from dask import delayed from PIL import Image, UnidentifiedImageError import qim3d -from qim3d.utils._logger import log +from qim3d.utils import log from qim3d.utils._misc import get_file_size, sizeof, stringify_path -from qim3d.utils._system import Memory +from qim3d.utils import Memory from qim3d.utils._progress_bar import FileLoadingProgressBar import trimesh @@ -718,7 +718,7 @@ class DataLoader: # Fails else: # Find the closest matching path to warn the user - similar_paths = qim3d.utils.misc.find_similar_paths(path) + similar_paths = qim3d.utils._misc.find_similar_paths(path) if similar_paths: suggestion = similar_paths[0] # Get the closest match diff --git a/qim3d/io/_ome_zarr.py b/qim3d/io/_ome_zarr.py index 36976316b8811380d68cbe1b7f5f43e5c001c4f9..6573831a1e29bbb02607fab777145f2838c8b6d0 100644 --- a/qim3d/io/_ome_zarr.py +++ b/qim3d/io/_ome_zarr.py @@ -31,7 +31,7 @@ from skimage.transform import ( resize, ) -from qim3d.utils._logger import log +from qim3d.utils import log from qim3d.utils._progress_bar import OmeZarrExportProgressBar from qim3d.utils._ome_zarr import get_n_chunks diff --git a/qim3d/io/_saving.py b/qim3d/io/_saving.py index a7053c00fde4b7d4541b1a01286f18ad669e811d..3ef6457f9ae26554cd810547b8a1911072492f7a 100644 --- a/qim3d/io/_saving.py +++ b/qim3d/io/_saving.py @@ -37,7 +37,7 @@ from pydicom.dataset import FileDataset, FileMetaDataset from pydicom.uid import UID import trimesh -from qim3d.utils._logger import log +from qim3d.utils import log from qim3d.utils._misc import sizeof, stringify_path diff --git a/qim3d/io/_sync.py b/qim3d/io/_sync.py index 953bf15b2e5ad6690bd6a077cd29b7b430b22c40..48c17a8cb8d8fba2c3ededc93f252196087b6fc2 100644 --- a/qim3d/io/_sync.py +++ b/qim3d/io/_sync.py @@ -2,7 +2,7 @@ import os import subprocess import outputformat as ouf -from qim3d.utils._logger import log +from qim3d.utils import log from pathlib import Path diff --git a/qim3d/ml/_data.py b/qim3d/ml/_data.py index d54f1bf6d7a457457b8fea638fd8057410d2a853..6050001eef37a7491ab7f2e6d88dec660bf38102 100644 --- a/qim3d/ml/_data.py +++ b/qim3d/ml/_data.py @@ -1,7 +1,7 @@ """Provides a custom Dataset class for building a PyTorch dataset.""" from pathlib import Path from PIL import Image -from qim3d.utils._logger import log +from qim3d.utils import log import torch import numpy as np from typing import Optional, Callable diff --git a/qim3d/ml/models/_unet.py b/qim3d/ml/models/_unet.py index 27ee78a4009b235253662c8af50c76baca4870f4..a396d88d38a5ff9809f6e2a0bd1139df1f48eb40 100644 --- a/qim3d/ml/models/_unet.py +++ b/qim3d/ml/models/_unet.py @@ -2,7 +2,7 @@ import torch.nn as nn -from qim3d.utils._logger import log +from qim3d.utils import log class UNet(nn.Module): diff --git a/qim3d/operations/_common_operations_methods.py b/qim3d/operations/_common_operations_methods.py index 5975c30ea3c43f95995559597c8a61f02790809a..26959254dfd8783f28ad560de6103e3c9f70ce7d 100644 --- a/qim3d/operations/_common_operations_methods.py +++ b/qim3d/operations/_common_operations_methods.py @@ -1,6 +1,6 @@ import numpy as np import qim3d.filters as filters -from qim3d.utils._logger import log +from qim3d.utils import log __all__ = ["remove_background", "fade_mask", "overlay_rgb_images"] diff --git a/qim3d/processing/_local_thickness.py b/qim3d/processing/_local_thickness.py index 947f8865cfdbdbed4438d0f1e416498502de0e4a..eb02bb3e04dee57c63a1dabb4a4a8dda4634e991 100644 --- a/qim3d/processing/_local_thickness.py +++ b/qim3d/processing/_local_thickness.py @@ -2,7 +2,7 @@ import numpy as np from typing import Optional -from qim3d.utils._logger import log +from qim3d.utils import log import qim3d diff --git a/qim3d/processing/_structure_tensor.py b/qim3d/processing/_structure_tensor.py index f6dc98cd253f05bd89c4a8a8c5cdf9a8fd228c2d..08a6f20ec1cf96c3432831e2914017c58b6cd777 100644 --- a/qim3d/processing/_structure_tensor.py +++ b/qim3d/processing/_structure_tensor.py @@ -3,7 +3,7 @@ from typing import Tuple import logging import numpy as np -from qim3d.utils._logger import log +from qim3d.utils import log def structure_tensor( @@ -108,7 +108,7 @@ def structure_tensor( val, vec = st.eig_special_3d(s_vol, full=full) if visualize: - from qim3d.viz._structure_tensor import vectors + from qim3d.viz import vectors display(vectors(vol, vec, **viz_kwargs)) diff --git a/qim3d/tests/processing/test_filters.py b/qim3d/tests/filters/test_filters.py similarity index 70% rename from qim3d/tests/processing/test_filters.py rename to qim3d/tests/filters/test_filters.py index 5a6d0993fb4ac6cecaf9c26d06b450eeb2a9f893..adbfd0130713b469c9a624a2be5ef72596d209dd 100644 --- a/qim3d/tests/processing/test_filters.py +++ b/qim3d/tests/filters/test_filters.py @@ -1,11 +1,10 @@ import qim3d -from qim3d.filters import * import numpy as np import pytest import re def test_filter_base_initialization(): - filter_base = qim3d.processing.filters.FilterBase(3,size=2) + filter_base = qim3d.filters.FilterBase(3,size=2) assert filter_base.args == (3,) assert filter_base.kwargs == {'size': 2} @@ -13,10 +12,10 @@ def test_gaussian_filter(): input_image = np.random.rand(50, 50) # Testing the function - filtered_image_fn = gaussian(input_image,sigma=1.5) + filtered_image_fn = qim3d.filters.gaussian(input_image,sigma=1.5) # Testing the class method - gaussian_filter_cls = Gaussian(sigma=1.5) + gaussian_filter_cls = qim3d.filters.Gaussian(sigma=1.5) filtered_image_cls = gaussian_filter_cls(input_image) # Assertions @@ -28,10 +27,10 @@ def test_median_filter(): input_image = np.random.rand(50, 50) # Testing the function - filtered_image_fn = median(input_image, size=3) + filtered_image_fn = qim3d.filters.median(input_image, size=3) # Testing the class method - median_filter_cls = Median(size=3) + median_filter_cls = qim3d.filters.Median(size=3) filtered_image_cls = median_filter_cls(input_image) # Assertions @@ -43,10 +42,10 @@ def test_maximum_filter(): input_image = np.random.rand(50, 50) # Testing the function - filtered_image_fn = maximum(input_image, size=3) + filtered_image_fn = qim3d.filters.maximum(input_image, size=3) # Testing the class method - maximum_filter_cls = Maximum(size=3) + maximum_filter_cls = qim3d.filters.Maximum(size=3) filtered_image_cls = maximum_filter_cls(input_image) # Assertions @@ -58,10 +57,10 @@ def test_minimum_filter(): input_image = np.random.rand(50, 50) # Testing the function - filtered_image_fn = minimum(input_image, size=3) + filtered_image_fn = qim3d.filters.minimum(input_image, size=3) # Testing the class method - minimum_filter_cls = Minimum(size=3) + minimum_filter_cls = qim3d.filters.Minimum(size=3) filtered_image_cls = minimum_filter_cls(input_image) # Assertions @@ -73,16 +72,16 @@ def test_sequential_filter_pipeline(): input_image = np.random.rand(50, 50) # Individual filters - gaussian_filter = Gaussian(sigma=1.5) - median_filter = Median(size=3) - maximum_filter = Maximum(size=3) + gaussian_filter = qim3d.filters.Gaussian(sigma=1.5) + median_filter = qim3d.filters.Median(size=3) + maximum_filter = qim3d.filters.Maximum(size=3) # Testing the sequential pipeline - sequential_pipeline = Pipeline(gaussian_filter, median_filter, maximum_filter) + sequential_pipeline = qim3d.filters.Pipeline(gaussian_filter, median_filter, maximum_filter) filtered_image_pipeline = sequential_pipeline(input_image) # Testing the equivalence to maximum(median(gaussian(input,**kwargs),**kwargs),**kwargs) - expected_output = maximum(median(gaussian(input_image, sigma=1.5), size=3), size=3) + expected_output = qim3d.filters.maximum(qim3d.filters.median(qim3d.filters.gaussian(input_image, sigma=1.5), size=3), size=3) # Assertions assert filtered_image_pipeline.shape == expected_output.shape == input_image.shape @@ -93,16 +92,16 @@ def test_sequential_filter_appending(): input_image = np.random.rand(50, 50) # Individual filters - gaussian_filter = Gaussian(sigma=1.5) - median_filter = Median(size=3) - maximum_filter = Maximum(size=3) + gaussian_filter = qim3d.filters.Gaussian(sigma=1.5) + median_filter = qim3d.filters.Median(size=3) + maximum_filter = qim3d.filters.Maximum(size=3) # Sequential pipeline with filter initialized at the beginning - sequential_pipeline_initial = Pipeline(gaussian_filter, median_filter, maximum_filter) + sequential_pipeline_initial = qim3d.filters.Pipeline(gaussian_filter, median_filter, maximum_filter) filtered_image_initial = sequential_pipeline_initial(input_image) # Sequential pipeline with filter appended - sequential_pipeline_appended = Pipeline(gaussian_filter, median_filter) + sequential_pipeline_appended = qim3d.filters.Pipeline(gaussian_filter, median_filter) sequential_pipeline_appended.append(maximum_filter) filtered_image_appended = sequential_pipeline_appended(input_image) @@ -113,7 +112,7 @@ def test_sequential_filter_appending(): def test_assertion_error_not_filterbase_subclass(): # Get valid filter classes - valid_filters = [subclass.__name__ for subclass in qim3d.processing.filters.FilterBase.__subclasses__()] + valid_filters = [subclass.__name__ for subclass in qim3d.filters.FilterBase.__subclasses__()] # Create invalid object invalid_filter = object() # An object that is not an instance of FilterBase @@ -124,4 +123,4 @@ def test_assertion_error_not_filterbase_subclass(): # Use pytest.raises to catch the AssertionError with pytest.raises(AssertionError, match=re.escape(message)): - sequential_pipeline = Pipeline(invalid_filter) \ No newline at end of file + sequential_pipeline = qim3d.filters.Pipeline(invalid_filter) \ No newline at end of file diff --git a/qim3d/tests/io/test_downloader.py b/qim3d/tests/io/test_downloader.py index 6fc0846405462a20bd19eb90160889ea573e1fbb..a6791aa0a60b0133824e62e68146f469a48f9c9c 100644 --- a/qim3d/tests/io/test_downloader.py +++ b/qim3d/tests/io/test_downloader.py @@ -1,40 +1,61 @@ import qim3d import os +import pytest +from pathlib import Path +import shutil -def test_download(): - folder = 'Cowry_Shell' - file = 'Cowry_DOWNSAMPLED.tif' - path = os.path.join(folder,file) +@pytest.fixture +def setup_temp_folder(): + """Fixture to create and clean up a temporary folder for tests.""" + folder = "Cowry_Shell" + file = "Cowry_DOWNSAMPLED.tif" + path = Path(folder) / file - dl = qim3d.io.Downloader() + # Ensure clean environment before running tests + if Path(folder).exists(): + shutil.rmtree(folder) + yield folder, path - dl.Cowry_Shell.Cowry_DOWNSAMPLED() + # Cleanup after tests + if path.exists(): + path.unlink() + if Path(folder).exists(): + shutil.rmtree(folder) - img = qim3d.io.load(path) +def test_download(setup_temp_folder): + folder, path = setup_temp_folder - # Remove temp file - os.remove(path) - os.rmdir(folder) + dl = qim3d.io.Downloader() + dl.Cowry_Shell.Cowry_DOWNSAMPLED() - assert img.shape == (500,350,350) + # Verify the file was downloaded correctly + assert path.exists(), f"{path} does not exist after download." -def test_get_file_size_right(): - coal_file = 'https://archive.compute.dtu.dk/download/public/projects/viscomp_data_repository/Coal/CoalBrikett.tif' - size = qim3d.io.downloader._get_file_size(coal_file) + img = qim3d.io.load(str(path)) + assert img.shape == (500, 350, 350) - assert size == 2_400_082_900 + # Cleanup is handled by the fixture -def test_get_file_size_wrong(): - file_to_folder = 'https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository/' - size = qim3d.io.downloader._get_file_size(file_to_folder) +def test_get_file_size(): + """Tests for correct and incorrect file size retrieval.""" + coal_file = "https://archive.compute.dtu.dk/download/public/projects/viscomp_data_repository/Coal/CoalBrikett.tif" + folder_url = "https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository/" - assert size == -1 + # Correct file size + size = qim3d.io._downloader._get_file_size(coal_file) + assert size == 2_400_082_900, f"Expected size mismatch for {coal_file}." + + # Wrong URL (not a file) + size = qim3d.io._downloader._get_file_size(folder_url) + assert size == -1, "Expected size -1 for non-file URL." def test_extract_html(): - url = 'https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository' - html = qim3d.io.downloader._extract_html(url) - - assert 'data-path="/files/public/projects/viscomp_data_repository"' in html + url = "https://archive.compute.dtu.dk/files/public/projects/viscomp_data_repository" + html = qim3d.io._downloader._extract_html(url) + + assert 'data-path="/files/public/projects/viscomp_data_repository"' in html, \ + "Expected HTML content not found in extracted HTML." + diff --git a/qim3d/tests/models/test_unet.py b/qim3d/tests/ml/models/test_unet.py similarity index 64% rename from qim3d/tests/models/test_unet.py rename to qim3d/tests/ml/models/test_unet.py index 80b17605329af78f3b04cdcedd35de60608577f6..9f32bfce12be3a21238f2d2d388e59f873a8ef1c 100644 --- a/qim3d/tests/models/test_unet.py +++ b/qim3d/tests/ml/models/test_unet.py @@ -3,13 +3,13 @@ import torch # unit tests for UNet() def test_starting_unet(): - unet = qim3d.models.UNet() + unet = qim3d.ml.models.UNet() assert unet.size == 'medium' def test_forward_pass(): - unet = qim3d.models.UNet() + unet = qim3d.ml.models.UNet() # Size: B x C x H x W x = torch.ones([1,1,256,256]) @@ -19,14 +19,14 @@ def test_forward_pass(): # unit tests for Hyperparameters() def test_hyper(): - unet = qim3d.models.UNet() - hyperparams = qim3d.models.Hyperparameters(unet) + unet = qim3d.ml.models.UNet() + hyperparams = qim3d.ml.models.Hyperparameters(unet) assert hyperparams.n_epochs == 10 def test_hyper_dict(): - unet = qim3d.models.UNet() - hyperparams = qim3d.models.Hyperparameters(unet) + unet = qim3d.ml.models.UNet() + hyperparams = qim3d.ml.models.Hyperparameters(unet) hyper_dict = hyperparams() diff --git a/qim3d/tests/models/test_models.py b/qim3d/tests/ml/test_models.py similarity index 62% rename from qim3d/tests/models/test_models.py rename to qim3d/tests/ml/test_models.py index 83543b76299d400ac6c848fd6e6213a428e1bfd0..547310f303e5b78ca0036dd2be464673e0975384 100644 --- a/qim3d/tests/models/test_models.py +++ b/qim3d/tests/ml/test_models.py @@ -12,16 +12,16 @@ def test_model_summary(): folder = "folder_data" temp_data(folder, img_shape=img_shape, n=n) - unet = qim3d.models.UNet(size="small") - augment = qim3d.models.Augmentation(transform_train=None) - train_set, val_set, test_set = qim3d.models.prepare_datasets( + unet = qim3d.ml.models.UNet(size="small") + augment = qim3d.ml.Augmentation(transform_train=None) + train_set, val_set, test_set = qim3d.ml.prepare_datasets( folder, 1 / 3, unet, augment ) - _, val_loader, _ = qim3d.models.prepare_dataloaders( + _, val_loader, _ = qim3d.ml.prepare_dataloaders( train_set, val_set, test_set, batch_size=1, num_workers=1, pin_memory=False ) - summary = qim3d.models.model_summary(val_loader, unet) + summary = qim3d.ml.model_summary(val_loader, unet) assert summary.input_size[0] == (1, 1) + img_shape @@ -33,11 +33,11 @@ def test_inference(): folder = "folder_data" temp_data(folder) - unet = qim3d.models.UNet(size="small") - augment = qim3d.models.Augmentation(transform_train=None) - train_set, _, _ = qim3d.models.prepare_datasets(folder, 1 / 3, unet, augment) + unet = qim3d.ml.models.UNet(size="small") + augment = qim3d.ml.Augmentation(transform_train=None) + train_set, _, _ = qim3d.ml.prepare_datasets(folder, 1 / 3, unet, augment) - _, targ, _ = qim3d.models.inference(train_set, unet) + _, targ, _ = qim3d.ml.inference(train_set, unet) assert tuple(targ[0].unique()) == (0, 1) @@ -49,11 +49,11 @@ def test_inference_tuple(): folder = "folder_data" temp_data(folder) - unet = qim3d.models.UNet(size="small") + unet = qim3d.ml.models.UNet(size="small") data = [1, 2, 3] with pytest.raises(ValueError, match="Data items must be tuples"): - qim3d.models.inference(data, unet) + qim3d.ml.inference(data, unet) temp_data(folder, remove=True) @@ -63,11 +63,11 @@ def test_inference_tensor(): folder = "folder_data" temp_data(folder) - unet = qim3d.models.UNet(size="small") + unet = qim3d.ml.models.UNet(size="small") data = [(1, 2)] with pytest.raises(ValueError, match="Data items must consist of tensors"): - qim3d.models.inference(data, unet) + qim3d.ml.inference(data, unet) temp_data(folder, remove=True) @@ -77,12 +77,12 @@ def test_inference_dim(): folder = "folder_data" temp_data(folder) - unet = qim3d.models.UNet(size="small") + unet = qim3d.ml.models.UNet(size="small") data = [(ones(1), ones(1))] # need the r"" for special characters with pytest.raises(ValueError, match=r"Input image must be \(C,H,W\) format"): - qim3d.models.inference(data, unet) + qim3d.ml.inference(data, unet) temp_data(folder, remove=True) @@ -94,17 +94,17 @@ def test_train_model(): n_epochs = 1 - unet = qim3d.models.UNet(size="small") - augment = qim3d.models.Augmentation(transform_train=None) - hyperparams = qim3d.models.Hyperparameters(unet, n_epochs=n_epochs) - train_set, val_set, test_set = qim3d.models.prepare_datasets( + unet = qim3d.ml.models.UNet(size="small") + augment = qim3d.ml.Augmentation(transform_train=None) + hyperparams = qim3d.ml.Hyperparameters(unet, n_epochs=n_epochs) + train_set, val_set, test_set = qim3d.ml.prepare_datasets( folder, 1 / 3, unet, augment ) - train_loader, val_loader, _ = qim3d.models.prepare_dataloaders( + train_loader, val_loader, _ = qim3d.ml.prepare_dataloaders( train_set, val_set, test_set, batch_size=1, num_workers=1, pin_memory=False ) - train_loss, _ = qim3d.models.train_model( + train_loss, _ = qim3d.ml.train_model( unet, hyperparams, train_loader, val_loader, plot=False, return_loss=True ) diff --git a/qim3d/tests/processing/test_connected_components.py b/qim3d/tests/segmentation/test_connected_components.py similarity index 100% rename from qim3d/tests/processing/test_connected_components.py rename to qim3d/tests/segmentation/test_connected_components.py diff --git a/qim3d/tests/utils/test_augmentations.py b/qim3d/tests/utils/test_augmentations.py index 289ebcb4a723c6ab9432ca375d51e4a4cd01479a..809e7ddbe2771519a5c71d4032edd7ebbd029d7f 100644 --- a/qim3d/tests/utils/test_augmentations.py +++ b/qim3d/tests/utils/test_augmentations.py @@ -5,13 +5,13 @@ import pytest # unit tests for Augmentation() def test_augmentation(): - augment_class = qim3d.models.Augmentation() + augment_class = qim3d.ml.Augmentation() assert augment_class.resize == "crop" def test_augment(): - augment_class = qim3d.models.Augmentation() + augment_class = qim3d.ml.Augmentation() album_augment = augment_class.augment(256, 256) @@ -26,11 +26,11 @@ def test_resize(): ValueError, match=f"Invalid resize type: {resize_str}. Use either 'crop', 'resize' or 'padding'.", ): - augment_class = qim3d.models.Augmentation(resize=resize_str) + augment_class = qim3d.ml.Augmentation(resize=resize_str) def test_levels(): - augment_class = qim3d.models.Augmentation() + augment_class = qim3d.ml.Augmentation() level = "Not a valid level" diff --git a/qim3d/tests/utils/test_data.py b/qim3d/tests/utils/test_data.py index 32b290a86b55a0c23a7d75d551559437b3df7b7e..81beef5334ddf4bbdd20de34b6d52f55a13f6307 100644 --- a/qim3d/tests/utils/test_data.py +++ b/qim3d/tests/utils/test_data.py @@ -10,7 +10,7 @@ def test_dataset(): folder = 'folder_data' temp_data(folder, img_shape = img_shape) - images = qim3d.models.Dataset(folder) + images = qim3d.ml.Dataset(folder) assert images[0][0].shape == img_shape @@ -19,19 +19,19 @@ def test_dataset(): # unit tests for check_resize() def test_check_resize(): - h_adjust,w_adjust = qim3d.models.data.check_resize(240,240,resize = 'crop',n_channels = 6) + h_adjust,w_adjust = qim3d.ml._data.check_resize(240,240,resize = 'crop',n_channels = 6) assert (h_adjust,w_adjust) == (192,192) def test_check_resize_pad(): - h_adjust,w_adjust = qim3d.models.data.check_resize(16,16,resize = 'padding',n_channels = 6) + h_adjust,w_adjust = qim3d.ml._data.check_resize(16,16,resize = 'padding',n_channels = 6) assert (h_adjust,w_adjust) == (64,64) def test_check_resize_fail(): with pytest.raises(ValueError,match="The size of the image is too small compared to the depth of the UNet. Choose a different 'resize' and/or a smaller model."): - h_adjust,w_adjust = qim3d.models.data.check_resize(16,16,resize = 'crop',n_channels = 6) + h_adjust,w_adjust = qim3d.ml._data.check_resize(16,16,resize = 'crop',n_channels = 6) # unit tests for prepare_datasets() @@ -42,9 +42,9 @@ def test_prepare_datasets(): folder = 'folder_data' img = temp_data(folder,n = n) - my_model = qim3d.models.UNet() - my_augmentation = qim3d.models.Augmentation(transform_test='light') - train_set, val_set, test_set = qim3d.models.prepare_datasets(folder,validation,my_model,my_augmentation) + my_model = qim3d.ml.models.UNet() + my_augmentation = qim3d.ml.Augmentation(transform_test='light') + train_set, val_set, test_set = qim3d.ml.prepare_datasets(folder,validation,my_model,my_augmentation) assert (len(train_set),len(val_set),len(test_set)) == (int((1-validation)*n), int(n*validation), n) @@ -56,7 +56,7 @@ def test_validation(): validation = 10 with pytest.raises(ValueError,match = "The validation fraction must be a float between 0 and 1."): - augment_class = qim3d.models.prepare_datasets('folder',validation,'my_model','my_augmentation') + augment_class = qim3d.ml.prepare_datasets('folder',validation,'my_model','my_augmentation') # unit test for prepare_dataloaders() @@ -65,11 +65,11 @@ def test_prepare_dataloaders(): temp_data(folder) batch_size = 1 - my_model = qim3d.models.UNet() - my_augmentation = qim3d.models.Augmentation() - train_set, val_set, test_set = qim3d.models.prepare_datasets(folder,1/3,my_model,my_augmentation) + my_model = qim3d.ml.models.UNet() + my_augmentation = qim3d.ml.Augmentation() + train_set, val_set, test_set = qim3d.ml.prepare_datasets(folder,1/3,my_model,my_augmentation) - _,val_loader,_ = qim3d.models.prepare_dataloaders(train_set,val_set,test_set, + _,val_loader,_ = qim3d.ml.prepare_dataloaders(train_set,val_set,test_set, batch_size,num_workers = 1, pin_memory = False) diff --git a/qim3d/tests/utils/test_doi.py b/qim3d/tests/utils/test_doi.py index 0db54357db9366f416a1e72cbef0ef10c640dc0a..859ebdb92baefca4eba721b3ac71fb00e1d5dec6 100644 --- a/qim3d/tests/utils/test_doi.py +++ b/qim3d/tests/utils/test_doi.py @@ -4,12 +4,12 @@ doi = "https://doi.org/10.1007/s10851-021-01041-3" def test_get_bibtex(): - bibtext = qim3d.utils.doi.get_bibtex(doi) + bibtext = qim3d.utils._doi.get_bibtex(doi) assert "Measuring Shape Relations Using r-Parallel Sets" in bibtext def test_get_reference(): - reference = qim3d.utils.doi.get_reference(doi) + reference = qim3d.utils._doi.get_reference(doi) assert "Stephensen" in reference diff --git a/qim3d/tests/utils/test_helpers.py b/qim3d/tests/utils/test_helpers.py index 04ae8e63db39d1b47a9fe1f6ae24aecc29c29ac4..cdbbedc4589f9801077444605874916a8558f0c1 100644 --- a/qim3d/tests/utils/test_helpers.py +++ b/qim3d/tests/utils/test_helpers.py @@ -33,7 +33,7 @@ def test_get_local_ip(): else: return False - local_ip = qim3d.utils.misc.get_local_ip() + local_ip = qim3d.utils._misc.get_local_ip() assert validate_ip(local_ip) == True @@ -42,7 +42,7 @@ def test_stringify_path1(): """Test that the function converts os.PathLike objects to strings""" blobs_path = Path(qim3d.__file__).parents[0] / "img_examples" / "blobs_256x256.tif" - assert str(blobs_path) == qim3d.utils.misc.stringify_path(blobs_path) + assert str(blobs_path) == qim3d.utils._misc.stringify_path(blobs_path) def test_stringify_path2(): @@ -50,4 +50,4 @@ def test_stringify_path2(): # Create test_path test_path = os.path.join("this", "path", "doesnt", "exist.tif") - assert test_path == qim3d.utils.misc.stringify_path(test_path) + assert test_path == qim3d.utils._misc.stringify_path(test_path) diff --git a/qim3d/tests/viz/test_img.py b/qim3d/tests/viz/test_img.py index ec9599105a9cdccb3d44fdfa048f0a452cd47c8a..d33d00cc54e0d989cf870e474343e2787e069cfd 100644 --- a/qim3d/tests/viz/test_img.py +++ b/qim3d/tests/viz/test_img.py @@ -36,11 +36,11 @@ def test_grid_pred(): n = 4 temp_data(folder, n=n) - model = qim3d.models.UNet() - augmentation = qim3d.models.Augmentation() - train_set, _, _ = qim3d.models.prepare_datasets(folder, 0.1, model, augmentation) + model = qim3d.ml.models.UNet() + augmentation = qim3d.ml.Augmentation() + train_set, _, _ = qim3d.ml.prepare_datasets(folder, 0.1, model, augmentation) - in_targ_pred = qim3d.models.inference(train_set, model) + in_targ_pred = qim3d.ml.inference(train_set, model) fig = qim3d.viz.grid_pred(in_targ_pred) @@ -52,7 +52,7 @@ def test_grid_pred(): # unit tests for slices function def test_slices_numpy_array_input(): example_volume = np.ones((10, 10, 10)) - fig = qim3d.viz.slices_grid(example_volume, n_slices=1) + fig = qim3d.viz.slices_grid(example_volume, num_slices=1) assert isinstance(fig, plt.Figure) @@ -77,7 +77,7 @@ def test_slices_wrong_position_format1(): ValueError, match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".', ): - qim3d.viz.slices_grid(example_volume, position="invalid_slice") + qim3d.viz.slices_grid(example_volume, slice_positions="invalid_slice") def test_slices_wrong_position_format2(): @@ -86,7 +86,7 @@ def test_slices_wrong_position_format2(): ValueError, match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".', ): - qim3d.viz.slices_grid(example_volume, position=1.5) + qim3d.viz.slices_grid(example_volume, slice_positions=1.5) def test_slices_wrong_position_format3(): @@ -95,16 +95,16 @@ def test_slices_wrong_position_format3(): ValueError, match='Position not recognized. Choose an integer, list of integers or one of the following strings: "start", "mid" or "end".', ): - qim3d.viz.slices_grid(example_volume, position=[1, 2, 3.5]) + qim3d.viz.slices_grid(example_volume, slice_positions=[1, 2, 3.5]) def test_slices_invalid_axis_value(): example_volume = np.ones((10, 10, 10)) with pytest.raises( ValueError, - match="Invalid value for 'axis'. It should be an integer between 0 and 2", + match=f"Invalid value for 'slice_axis'. It should be an integer between 0 and {example_volume.ndim - 1}.", ): - qim3d.viz.slices_grid(example_volume, axis=3) + qim3d.viz.slices_grid(example_volume, slice_axis=3) def test_slices_interpolation_option(): @@ -113,8 +113,8 @@ def test_slices_interpolation_option(): interpolation_method = "bilinear" fig = qim3d.viz.slices_grid( example_volume, - n_slices=1, - img_width=img_width, + num_slices=1, + image_width=img_width, interpolation=interpolation_method, ) @@ -128,27 +128,27 @@ def test_slices_interpolation_option(): def test_slices_multiple_slices(): example_volume = np.ones((10, 10, 10)) - img_width = 3 - n_slices = 3 - fig = qim3d.viz.slices_grid(example_volume, n_slices=n_slices, img_width=img_width) + image_width = 3 + num_slices = 3 + fig = qim3d.viz.slices_grid(example_volume, num_slices=num_slices, image_width=image_width) # Add assertions for the expected number of subplots in the figure - assert len(fig.get_axes()) == n_slices + assert len(fig.get_axes()) == num_slices def test_slices_axis_argument(): # Non-symmetric input example_volume = np.arange(1000).reshape((10, 10, 10)) - img_width = 3 + image_width = 3 # Call the function with different values of the axis fig_axis_0 = qim3d.viz.slices_grid( - example_volume, n_slices=1, img_width=img_width, axis=0 + example_volume, num_slices=1, image_width=image_width, slice_axis=0 ) fig_axis_1 = qim3d.viz.slices_grid( - example_volume, n_slices=1, img_width=img_width, axis=1 + example_volume, num_slices=1, image_width=image_width, slice_axis=1 ) fig_axis_2 = qim3d.viz.slices_grid( - example_volume, n_slices=1, img_width=img_width, axis=2 + example_volume, num_slices=1, image_width=image_width, slice_axis=2 ) # Ensure that different axes result in different plots @@ -216,7 +216,7 @@ def test_orthogonal_with_numpy_array(): # Create a sample NumPy array vol = np.random.rand(10, 10, 10) # Call the orthogonal function with the NumPy array - orthogonal_obj = qim3d.viz.orthogonal(vol) + orthogonal_obj = qim3d.viz.slicer_orthogonal(vol) # Assert that the orthogonal object is created successfully assert isinstance(orthogonal_obj, widgets.HBox) @@ -225,28 +225,28 @@ def test_orthogonal_with_torch_tensor(): # Create a sample PyTorch tensor vol = torch.rand(10, 10, 10) # Call the orthogonal function with the PyTorch tensor - orthogonal_obj = qim3d.viz.orthogonal(vol) + orthogonal_obj = qim3d.viz.slicer_orthogonal(vol) # Assert that the orthogonal object is created successfully assert isinstance(orthogonal_obj, widgets.HBox) def test_orthogonal_with_different_parameters(): # Test with different colormaps - for cmap in ["viridis", "gray", "plasma"]: - orthogonal_obj = qim3d.viz.orthogonal(np.random.rand(10, 10, 10), cmap=cmap) + for color_map in ["viridis", "gray", "plasma"]: + orthogonal_obj = qim3d.viz.slicer_orthogonal(np.random.rand(10, 10, 10), color_map=color_map) assert isinstance(orthogonal_obj, widgets.HBox) # Test with different image sizes - for img_height, img_width in [(2, 2), (4, 4)]: - orthogonal_obj = qim3d.viz.orthogonal( - np.random.rand(10, 10, 10), img_height=img_height, img_width=img_width + for image_height, image_width in [(2, 2), (4, 4)]: + orthogonal_obj = qim3d.viz.slicer_orthogonal( + np.random.rand(10, 10, 10), image_height=image_height, image_width=image_width ) assert isinstance(orthogonal_obj, widgets.HBox) # Test with show_position set to True and False - for show_position in [True, False]: - orthogonal_obj = qim3d.viz.orthogonal( - np.random.rand(10, 10, 10), show_position=show_position + for display_positions in [True, False]: + orthogonal_obj = qim3d.viz.slicer_orthogonal( + np.random.rand(10, 10, 10), display_positions=display_positions ) assert isinstance(orthogonal_obj, widgets.HBox) @@ -255,7 +255,7 @@ def test_orthogonal_initial_slider_value(): # Create a sample NumPy array vol = np.random.rand(10, 7, 19) # Call the orthogonal function with the NumPy array - orthogonal_obj = qim3d.viz.orthogonal(vol) + orthogonal_obj = qim3d.viz.slicer_orthogonal(vol) for idx, slicer in enumerate(orthogonal_obj.children): assert slicer.children[0].value == vol.shape[idx] // 2 @@ -264,7 +264,7 @@ def test_orthogonal_slider_description(): # Create a sample NumPy array vol = np.random.rand(10, 10, 10) # Call the orthogonal function with the NumPy array - orthogonal_obj = qim3d.viz.orthogonal(vol) + orthogonal_obj = qim3d.viz.slicer_orthogonal(vol) for idx, slicer in enumerate(orthogonal_obj.children): assert slicer.children[0].description == ["Z", "Y", "X"][idx] diff --git a/qim3d/utils/__init__.py b/qim3d/utils/__init__.py index 0c7d5b30a7b3daf15b80d5995e6d0e4a1f18e5cc..fab0c339cfa63a4fd86f5420cc0f90e4a2738597 100644 --- a/qim3d/utils/__init__.py +++ b/qim3d/utils/__init__.py @@ -1,6 +1,8 @@ from . import _doi from ._system import Memory +from ._logger import log + from ._misc import ( get_local_ip, port_from_str, @@ -13,4 +15,4 @@ from ._misc import ( scale_to_float16, ) -from ._server import start_http_server \ No newline at end of file +from ._server import start_http_server diff --git a/qim3d/utils/_misc.py b/qim3d/utils/_misc.py index 3d8660b445a65577ef3b4c7d16af613b44785aac..090672d2fc5370d16bd56c28c384b9efb4c528e7 100644 --- a/qim3d/utils/_misc.py +++ b/qim3d/utils/_misc.py @@ -153,7 +153,7 @@ def get_file_size(file_path: str) -> int: try: file_size = os.path.getsize(file_path) except FileNotFoundError: - similar_paths = qim3d.utils.misc.find_similar_paths(file_path) + similar_paths = qim3d.utils._misc.find_similar_paths(file_path) if similar_paths: suggestion = similar_paths[0] # Get the closest match