pykrait 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pykrait might be problematic. Click here for more details.
- {pykrait-0.1.0 → pykrait-0.2.0}/PKG-INFO +23 -7
- {pykrait-0.1.0 → pykrait-0.2.0}/pyproject.toml +27 -21
- pykrait-0.2.0/src/.python-version +1 -0
- pykrait-0.2.0/src/pykrait/__main__.py +41 -0
- pykrait-0.2.0/src/pykrait/gui/async_workers.py +167 -0
- pykrait-0.2.0/src/pykrait/gui/fileselection.py +384 -0
- pykrait-0.2.0/src/pykrait/gui/mainwindow.py +566 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/io/io.py +27 -31
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/pipeline/pipeline.py +15 -3
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/trace_analysis/peak_analysis.py +16 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/.gitignore +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/LICENSE +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/README.md +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/__init__.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/io/__init__.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/pipeline/__init__.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/preprocessing/__init__.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/preprocessing/segmentation.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/preprocessing/timeseries_extraction.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/trace_analysis/__init__.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/trace_analysis/filtering.py +0 -0
- {pykrait-0.1.0 → pykrait-0.2.0}/src/pykrait/trace_analysis/oscillations.py +0 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pykrait
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python Calcium Recording Analysis and Inference Toolbox
|
|
5
5
|
Author-email: Pentabyteman <22663502+Pentabyteman@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Requires-Python: >=3.
|
|
9
|
-
Requires-Dist: aicspylibczi>=3.
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: aicspylibczi>=3.3.1
|
|
10
|
+
Requires-Dist: bioio-czi>=2.4.0
|
|
11
|
+
Requires-Dist: bioio-ome-tiff>=1.1.0
|
|
12
|
+
Requires-Dist: bioio-tifffile>=1.3.0
|
|
13
|
+
Requires-Dist: bioio>=3.0.0
|
|
10
14
|
Requires-Dist: cellpose>=4.0.5
|
|
11
15
|
Requires-Dist: dask>=2025.5.1
|
|
12
16
|
Requires-Dist: fsspec>=2022.8.0
|
|
13
17
|
Requires-Dist: imagecodecs>=2025.3.30
|
|
14
18
|
Requires-Dist: lxml>=5.4.0
|
|
15
19
|
Requires-Dist: matplotlib>=3.10.3
|
|
20
|
+
Requires-Dist: numcodecs<0.16
|
|
16
21
|
Requires-Dist: numpy<2
|
|
17
22
|
Requires-Dist: ome-types>=0.5.3
|
|
18
23
|
Requires-Dist: ome-zarr>=0.10.2
|
|
@@ -22,11 +27,22 @@ Requires-Dist: pyyaml>=6.0.2
|
|
|
22
27
|
Requires-Dist: resource-backed-dask-array>=0.1.0
|
|
23
28
|
Requires-Dist: scikit-image>=0.25.2
|
|
24
29
|
Requires-Dist: scipy>=1.15.3
|
|
25
|
-
Requires-Dist: tifffile<2023.3.15,>=2021.8.30
|
|
26
|
-
Requires-Dist: wrapt>=1.17.2
|
|
27
30
|
Requires-Dist: xarray>2025
|
|
28
|
-
|
|
29
|
-
Requires-Dist:
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2.2.post1; extra == 'dev'
|
|
33
|
+
Requires-Dist: hatch>=1.14.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: ipywidgets>=8.1.7; extra == 'dev'
|
|
35
|
+
Requires-Dist: pydata-sphinx-theme>=0.16.1; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest>=8.4.1; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff>=0.11.13; extra == 'dev'
|
|
39
|
+
Requires-Dist: sphinx-autoapi>=3.6.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: sphinx-favicon>=1.0.1; extra == 'dev'
|
|
41
|
+
Requires-Dist: sphinx>=8.1.3; extra == 'dev'
|
|
42
|
+
Requires-Dist: tomli>=2.2.1; extra == 'dev'
|
|
43
|
+
Requires-Dist: twine>=6.1.0; extra == 'dev'
|
|
44
|
+
Provides-Extra: gui
|
|
45
|
+
Requires-Dist: pyside6>=6.9.1; extra == 'gui'
|
|
30
46
|
Description-Content-Type: text/markdown
|
|
31
47
|
|
|
32
48
|
# pyKrait - Python Calcium Recording Analysis and Inference Toolbox
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pykrait"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Python Calcium Recording Analysis and Inference Toolbox"
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
license = { text = "MIT" }
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Pentabyteman", email = "22663502+Pentabyteman@users.noreply.github.com" }
|
|
9
9
|
]
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"aicspylibczi>=3.
|
|
12
|
+
"aicspylibczi>=3.3.1",
|
|
13
|
+
"bioio>=3.0.0",
|
|
14
|
+
"bioio-czi>=2.4.0",
|
|
15
|
+
"bioio-ome-tiff>=1.1.0",
|
|
16
|
+
"bioio-tifffile>=1.3.0",
|
|
13
17
|
"cellpose>=4.0.5",
|
|
14
18
|
"dask>=2025.5.1",
|
|
15
19
|
"fsspec>=2022.8.0",
|
|
16
20
|
"imagecodecs>=2025.3.30",
|
|
17
21
|
"lxml>=5.4.0",
|
|
18
22
|
"matplotlib>=3.10.3",
|
|
23
|
+
"numcodecs<0.16",
|
|
19
24
|
"numpy<2",
|
|
20
25
|
"ome-types>=0.5.3",
|
|
21
26
|
"ome-zarr>=0.10.2",
|
|
@@ -25,11 +30,26 @@ dependencies = [
|
|
|
25
30
|
"resource-backed-dask-array>=0.1.0",
|
|
26
31
|
"scikit-image>=0.25.2",
|
|
27
32
|
"scipy>=1.15.3",
|
|
28
|
-
"tifffile>=2021.8.30,<2023.3.15",
|
|
29
|
-
"wrapt>=1.17.2",
|
|
30
33
|
"xarray>2025",
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
gui = [
|
|
38
|
+
"pyside6>=6.9.1"
|
|
39
|
+
]
|
|
40
|
+
dev = [
|
|
41
|
+
"build>=1.2.2.post1",
|
|
42
|
+
"hatch>=1.14.1",
|
|
43
|
+
"ipywidgets>=8.1.7",
|
|
44
|
+
"pydata-sphinx-theme>=0.16.1",
|
|
45
|
+
"ruff>=0.11.13",
|
|
46
|
+
"sphinx>=8.1.3",
|
|
47
|
+
"sphinx-autoapi>=3.6.0",
|
|
48
|
+
"sphinx-favicon>=1.0.1",
|
|
49
|
+
"tomli>=2.2.1",
|
|
50
|
+
"twine>=6.1.0",
|
|
51
|
+
"pytest>=8.4.1",
|
|
52
|
+
"pytest-cov"
|
|
33
53
|
]
|
|
34
54
|
|
|
35
55
|
[build-system]
|
|
@@ -55,17 +75,3 @@ source = ["stavromueller_beta"]
|
|
|
55
75
|
[tool.coverage.report]
|
|
56
76
|
show_missing = true
|
|
57
77
|
skip_covered = true
|
|
58
|
-
|
|
59
|
-
[dependency-groups]
|
|
60
|
-
dev = [
|
|
61
|
-
"build>=1.2.2.post1",
|
|
62
|
-
"hatch>=1.14.1",
|
|
63
|
-
"ipywidgets>=8.1.7",
|
|
64
|
-
"pydata-sphinx-theme>=0.16.1",
|
|
65
|
-
"ruff>=0.11.13",
|
|
66
|
-
"sphinx>=8.1.3",
|
|
67
|
-
"sphinx-autoapi>=3.6.0",
|
|
68
|
-
"sphinx-favicon>=1.0.1",
|
|
69
|
-
"tomli>=2.2.1",
|
|
70
|
-
"twine>=6.1.0",
|
|
71
|
-
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright © 2025 Pentabyteman
|
|
3
|
+
"""
|
|
4
|
+
# src/pykrait/__main__.py
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from PySide6.QtWidgets import QApplication
|
|
8
|
+
from pykrait.gui.fileselection import FileSelectionWindow
|
|
9
|
+
from pykrait.gui.mainwindow import MainWindow
|
|
10
|
+
|
|
11
|
+
class AppController:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.app = QApplication(sys.argv)
|
|
14
|
+
self.file_selection = FileSelectionWindow()
|
|
15
|
+
self.file_selection.analysis_complete.connect(self.on_analysis_complete)
|
|
16
|
+
self.file_selection.show()
|
|
17
|
+
|
|
18
|
+
# Keep references to windows
|
|
19
|
+
self.main_window = None
|
|
20
|
+
|
|
21
|
+
def on_analysis_complete(self, results):
|
|
22
|
+
# Called when analysis is done in FileSelectionWindow
|
|
23
|
+
# Hide file selection window
|
|
24
|
+
self.file_selection.close()
|
|
25
|
+
# Launch main window, keep reference
|
|
26
|
+
self.main_window = MainWindow(
|
|
27
|
+
frames=results["frames"],
|
|
28
|
+
mask=results["masks"],
|
|
29
|
+
mean_intensities=results["mean_intensities"],
|
|
30
|
+
analysis_output=results["analysis_output"],
|
|
31
|
+
analysis_params=results["analysis_parameters"]
|
|
32
|
+
)
|
|
33
|
+
self.main_window.show()
|
|
34
|
+
|
|
35
|
+
def run(self):
|
|
36
|
+
sys.exit(self.app.exec())
|
|
37
|
+
|
|
38
|
+
# In __main__.py
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
controller = AppController()
|
|
41
|
+
controller.run()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from PySide6.QtCore import QObject, Signal, Slot
|
|
2
|
+
import tifffile
|
|
3
|
+
import warnings
|
|
4
|
+
import os
|
|
5
|
+
import traceback
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from pykrait.io.io import load_timelapse_lazy
|
|
9
|
+
from pykrait.preprocessing.segmentation import timelapse_projection, create_cellpose_segmentation
|
|
10
|
+
from pykrait.preprocessing.timeseries_extraction import extract_mean_intensities
|
|
11
|
+
from pykrait.trace_analysis.filtering import detrend_with_sinc_filter
|
|
12
|
+
from pykrait.trace_analysis.peak_analysis import find_peaks
|
|
13
|
+
from pykrait.trace_analysis.oscillations import find_oscillating_rois
|
|
14
|
+
from pykrait.pipeline.pipeline import AnalysisParameters, AnalysisOutput
|
|
15
|
+
|
|
16
|
+
class ExtractIntensitiesWorker(QObject):
|
|
17
|
+
progress_changed = Signal(int, str) # progress percent, status message
|
|
18
|
+
finished = Signal(dict) # analysis results
|
|
19
|
+
error = Signal(str) # error message
|
|
20
|
+
|
|
21
|
+
def __init__(self, analysis_params:AnalysisParameters, output_params:AnalysisOutput, mode:str):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.analysis_parameters = analysis_params
|
|
24
|
+
self.analysis_output = output_params
|
|
25
|
+
self.mode = mode
|
|
26
|
+
|
|
27
|
+
@Slot()
|
|
28
|
+
def run(self):
|
|
29
|
+
try:
|
|
30
|
+
|
|
31
|
+
self.analysis_output.filename = self.analysis_output.filepath.split("/")[-1]
|
|
32
|
+
print(f"Running analysis on {self.analysis_output.filename}")
|
|
33
|
+
# Create analysis folder in the parent directory of the video file
|
|
34
|
+
parent_dir = os.path.dirname(self.analysis_output.filepath)
|
|
35
|
+
filename_wo_ext = os.path.splitext(self.analysis_output.filename)[0]
|
|
36
|
+
analysis_folder = os.path.join(parent_dir, f"Analysis_{filename_wo_ext}")
|
|
37
|
+
os.makedirs(analysis_folder, exist_ok=True)
|
|
38
|
+
self.analysis_output.analysis_folder = analysis_folder
|
|
39
|
+
|
|
40
|
+
self.progress_changed.emit(5, "Loading timelapse...")
|
|
41
|
+
|
|
42
|
+
timelapse_data, frame_interval, self.analysis_output.pixel_interval_y, self.analysis_output.pixel_interval_x = load_timelapse_lazy(file_path = self.analysis_output.filepath)
|
|
43
|
+
|
|
44
|
+
if frame_interval is None and self.analysis_parameters.frame_interval is None:
|
|
45
|
+
warnings.warn("No frame interval provided in the analysis parameters, and no frame interval could be inferred from the metadata. Defaulting to 1 second.")
|
|
46
|
+
self.analysis_output.frame_interval = 1 # Default to 1 second if not provided
|
|
47
|
+
elif self.analysis_parameters.frame_interval is None and frame_interval is not None:
|
|
48
|
+
self.analysis_output.frame_interval = frame_interval
|
|
49
|
+
elif frame_interval is not None and self.analysis_parameters.frame_interval != frame_interval:
|
|
50
|
+
warnings.warn(f"Provided frame_interval ({self.analysis_parameters.frame_interval}) does not match inferred frame_interval ({frame_interval}) from metadata. Using provided value.")
|
|
51
|
+
|
|
52
|
+
if self.mode == "cellpose":
|
|
53
|
+
# perform the tproj
|
|
54
|
+
self.progress_changed.emit(10, "Performing T Projection...")
|
|
55
|
+
tproj = timelapse_projection(timelapse_data, method=self.analysis_parameters.tproj_type, normalize=self.analysis_parameters.CLAHE_normalize, verbose=True)
|
|
56
|
+
tproj_path = os.path.join(analysis_folder, f"{filename_wo_ext}_tproj.ome.tif")
|
|
57
|
+
self.analysis_output.tproj_path = tproj_path
|
|
58
|
+
tifffile.imwrite(tproj_path, tproj.astype(np.uint16), metadata={'axes': 'CYX'}, compression="zlib")
|
|
59
|
+
self.analysis_output.zproj_path = os.path.join(analysis_folder, f"{filename_wo_ext}_zproj.ome.tif")
|
|
60
|
+
|
|
61
|
+
# create cellpose segmentation
|
|
62
|
+
self.progress_changed.emit(30, "Performing Cellpose Segmentation...")
|
|
63
|
+
masks = create_cellpose_segmentation(tproj, cellpose_model_path=self.analysis_parameters.cellpose_model_path)
|
|
64
|
+
mask_path = os.path.join(analysis_folder, f"{filename_wo_ext}_cp_masks.ome.tif")
|
|
65
|
+
self.analysis_output.mask_path = mask_path
|
|
66
|
+
tifffile.imwrite(mask_path, masks.astype(np.uint16), metadata={'axes': 'YX'}, compression="zlib")
|
|
67
|
+
|
|
68
|
+
elif self.mode == "label_image":
|
|
69
|
+
self.progress_changed.emit(20, "Loading label image...")
|
|
70
|
+
masks = tifffile.imread(self.analysis_output.masks_path)
|
|
71
|
+
tproj = None # Not needed in this mode
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError("Unknown mode.")
|
|
75
|
+
|
|
76
|
+
# extracts the mean intensities of the masks
|
|
77
|
+
self.progress_changed.emit(60, "Extracting intensities...")
|
|
78
|
+
mean_intensities = extract_mean_intensities(timelapse_data, masks)
|
|
79
|
+
self.analysis_output.number_of_frames, self.analysis_output.number_of_cells = mean_intensities.shape
|
|
80
|
+
|
|
81
|
+
# All done
|
|
82
|
+
self.progress_changed.emit(100, "Done.")
|
|
83
|
+
|
|
84
|
+
results = {
|
|
85
|
+
'frames': timelapse_data,
|
|
86
|
+
'analysis_output': self.analysis_output,
|
|
87
|
+
'analysis_parameters': self.analysis_parameters,
|
|
88
|
+
'masks': masks,
|
|
89
|
+
'mean_intensities': mean_intensities
|
|
90
|
+
}
|
|
91
|
+
self.finished.emit(results)
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
tb = traceback.format_exc()
|
|
95
|
+
print(f"Error during analysis: {e}\n{tb}")
|
|
96
|
+
self.error.emit(str(e))
|
|
97
|
+
|
|
98
|
+
class DetrendWorker(QObject):
|
|
99
|
+
finished = Signal(np.ndarray)
|
|
100
|
+
error = Signal(Exception)
|
|
101
|
+
|
|
102
|
+
def __init__(self, intensities, sinc_window, frame_interval):
|
|
103
|
+
super().__init__()
|
|
104
|
+
self.intensities = intensities
|
|
105
|
+
self.sinc_window = sinc_window
|
|
106
|
+
self.frame_interval = frame_interval
|
|
107
|
+
|
|
108
|
+
def run(self):
|
|
109
|
+
try:
|
|
110
|
+
# Call your actual function here
|
|
111
|
+
detrended = detrend_with_sinc_filter(signals=self.intensities,
|
|
112
|
+
cutoff_period=self.sinc_window,
|
|
113
|
+
sampling_interval=self.frame_interval)
|
|
114
|
+
self.finished.emit(detrended)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
self.error.emit(e)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class PeakDetectionWorker(QObject):
|
|
120
|
+
finished = Signal(np.ndarray)
|
|
121
|
+
error = Signal(Exception)
|
|
122
|
+
|
|
123
|
+
def __init__(self, detrended_traces, min_width, max_width, prominence, min_height):
|
|
124
|
+
super().__init__()
|
|
125
|
+
self.traces = detrended_traces
|
|
126
|
+
self.min_width = min_width
|
|
127
|
+
self.max_width = max_width
|
|
128
|
+
self.prominence = prominence
|
|
129
|
+
self.min_height = min_height
|
|
130
|
+
|
|
131
|
+
def run(self):
|
|
132
|
+
try:
|
|
133
|
+
peaks = find_peaks(
|
|
134
|
+
peak_min_width=self.min_width,
|
|
135
|
+
peak_max_width=self.max_width,
|
|
136
|
+
peak_prominence=self.prominence,
|
|
137
|
+
peak_min_height=self.min_height,
|
|
138
|
+
detrended_timeseries=self.traces
|
|
139
|
+
)
|
|
140
|
+
self.finished.emit(peaks)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
self.error.emit(e)
|
|
143
|
+
|
|
144
|
+
class PeriodicCellWorker(QObject):
|
|
145
|
+
finished = Signal(float, int, int, float, int, int)
|
|
146
|
+
error = Signal(Exception)
|
|
147
|
+
|
|
148
|
+
def __init__(self, peaks: np.ndarray, frame_interval: float, std_t: float = 0.01, cov_t: float = 0.01):
|
|
149
|
+
super().__init__()
|
|
150
|
+
self.peaks = peaks
|
|
151
|
+
self.frame_interval = frame_interval
|
|
152
|
+
self.std_t = std_t
|
|
153
|
+
self.cov_t = cov_t
|
|
154
|
+
|
|
155
|
+
def run(self):
|
|
156
|
+
try:
|
|
157
|
+
filtered_peak_series = self.peaks[:, np.sum(self.peaks, axis=0) >= 4]
|
|
158
|
+
result = find_oscillating_rois(
|
|
159
|
+
filtered_peak_series,
|
|
160
|
+
std_threshold=self.std_t,
|
|
161
|
+
cov_threshold=self.cov_t,
|
|
162
|
+
frame_interval=self.frame_interval,
|
|
163
|
+
)
|
|
164
|
+
self.finished.emit(*result)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
self.error.emit(e)
|
|
167
|
+
|