microlive 1.0.2__tar.gz → 1.0.4__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.
- {microlive-1.0.2 → microlive-1.0.4}/.gitignore +1 -0
- {microlive-1.0.2 → microlive-1.0.4}/PKG-INFO +58 -1
- {microlive-1.0.2 → microlive-1.0.4}/microlive/__init__.py +7 -1
- {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/app.py +163 -33
- microlive-1.0.4/microlive/gui/main.py +86 -0
- {microlive-1.0.2 → microlive-1.0.4}/pyproject.toml +4 -3
- microlive-1.0.2/microlive/gui/main.py +0 -54
- {microlive-1.0.2 → microlive-1.0.4}/LICENSE +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/README.md +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/data/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/data/icons/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/data/icons/icon_micro.png +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/data/models/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/micro_mac.command +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/micro_windows.bat +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/imports.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/microscopy.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/ml_spot_detection.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_FRAP.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_folding_efficiency.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_particle_tracking.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_particle_tracking_old.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_spot_detection_no_tracking.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/__init__.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/device.py +0 -0
- {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/resources.py +0 -0
|
@@ -1,19 +1,76 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microlive
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Live-cell microscopy image analysis and single-molecule measurements
|
|
5
5
|
Project-URL: Homepage, https://github.com/ningzhaoAnschutz/microlive
|
|
6
6
|
Project-URL: Documentation, https://github.com/ningzhaoAnschutz/microlive/blob/main/docs/user_guide.md
|
|
7
7
|
Project-URL: Repository, https://github.com/ningzhaoAnschutz/microlive
|
|
8
8
|
Project-URL: Issues, https://github.com/ningzhaoAnschutz/microlive/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/ningzhaoAnschutz/microlive/releases
|
|
10
|
+
Author: Luis U. Aguilera, William S. Raymond, Rhiannon M. Sears, Nathan L. Nowling, Brian Munsky, Ning Zhao
|
|
10
11
|
License-Expression: GPL-3.0-or-later
|
|
11
12
|
License-File: LICENSE
|
|
13
|
+
Keywords: PyQt5,bioimaging,cellpose,image-analysis,live-cell,microscopy,particle-tracking,single-molecule,tracking
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Requires-Dist: big-fish>=0.6
|
|
31
|
+
Requires-Dist: bioio-lif>=1.2
|
|
32
|
+
Requires-Dist: bioio>=1.0
|
|
33
|
+
Requires-Dist: biopython>=1.81
|
|
34
|
+
Requires-Dist: brokenaxes
|
|
35
|
+
Requires-Dist: cellpose>=3.0
|
|
36
|
+
Requires-Dist: dask
|
|
37
|
+
Requires-Dist: dna-features-viewer>=3.1
|
|
38
|
+
Requires-Dist: fpdf>=1.7
|
|
39
|
+
Requires-Dist: imagecodecs
|
|
40
|
+
Requires-Dist: imageio>=2.31
|
|
41
|
+
Requires-Dist: joblib>=1.3
|
|
42
|
+
Requires-Dist: joypy
|
|
43
|
+
Requires-Dist: matplotlib-scalebar
|
|
44
|
+
Requires-Dist: matplotlib>=3.7
|
|
45
|
+
Requires-Dist: natsort
|
|
46
|
+
Requires-Dist: numba>=0.58
|
|
47
|
+
Requires-Dist: numpy<2.0,>=1.24
|
|
48
|
+
Requires-Dist: opencv-python-headless>=4.8
|
|
49
|
+
Requires-Dist: openpyxl>=3.1
|
|
50
|
+
Requires-Dist: pandas>=2.0
|
|
51
|
+
Requires-Dist: pillow>=10.0
|
|
52
|
+
Requires-Dist: psutil
|
|
53
|
+
Requires-Dist: pydantic>=2.0
|
|
54
|
+
Requires-Dist: pyqt5>=5.15
|
|
55
|
+
Requires-Dist: pystackreg>=0.2
|
|
56
|
+
Requires-Dist: pyyaml>=6.0
|
|
57
|
+
Requires-Dist: readlif>=0.6
|
|
58
|
+
Requires-Dist: roifile
|
|
59
|
+
Requires-Dist: scikit-image>=0.21
|
|
60
|
+
Requires-Dist: scikit-learn>=1.3
|
|
61
|
+
Requires-Dist: scipy>=1.10
|
|
62
|
+
Requires-Dist: seaborn>=0.12
|
|
63
|
+
Requires-Dist: snapgene-reader>=0.1
|
|
64
|
+
Requires-Dist: statsmodels>=0.14
|
|
65
|
+
Requires-Dist: tifffile>=2023.7
|
|
66
|
+
Requires-Dist: tqdm>=4.66
|
|
67
|
+
Requires-Dist: trackpy>=0.6
|
|
12
68
|
Provides-Extra: dev
|
|
13
69
|
Requires-Dist: black>=23.0; extra == 'dev'
|
|
14
70
|
Requires-Dist: build; extra == 'dev'
|
|
15
71
|
Requires-Dist: isort>=5.12; extra == 'dev'
|
|
16
72
|
Requires-Dist: mypy>=1.5; extra == 'dev'
|
|
73
|
+
Requires-Dist: pdoc; extra == 'dev'
|
|
17
74
|
Requires-Dist: pytest-qt>=4.0; extra == 'dev'
|
|
18
75
|
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
19
76
|
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
@@ -23,7 +23,7 @@ Authors:
|
|
|
23
23
|
Nathan L. Nowling, Brian Munsky, Ning Zhao
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
__version__ = "1.0.
|
|
26
|
+
__version__ = "1.0.4"
|
|
27
27
|
__author__ = "Luis U. Aguilera, William S. Raymond, Rhiannon M. Sears, Nathan L. Nowling, Brian Munsky, Ning Zhao"
|
|
28
28
|
|
|
29
29
|
# Package name (for backward compatibility)
|
|
@@ -41,4 +41,10 @@ name = 'microlive'
|
|
|
41
41
|
__all__ = [
|
|
42
42
|
"__version__",
|
|
43
43
|
"__author__",
|
|
44
|
+
# Submodules (listed for pdoc documentation discovery)
|
|
45
|
+
"microscopy",
|
|
46
|
+
"imports",
|
|
47
|
+
"pipelines",
|
|
48
|
+
"utils",
|
|
49
|
+
"gui",
|
|
44
50
|
]
|
|
@@ -2102,38 +2102,68 @@ class GUI(QMainWindow):
|
|
|
2102
2102
|
except Exception:
|
|
2103
2103
|
print(f"Error parsing JSON ImageDescription metadata: {desc_text}")
|
|
2104
2104
|
else:
|
|
2105
|
-
#
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2105
|
+
# Check if it's ImageJ format (starts with "ImageJ=")
|
|
2106
|
+
if desc_stripped.startswith('ImageJ='):
|
|
2107
|
+
# Parse ImageJ metadata
|
|
2108
|
+
# ImageJ doesn't store XY pixel size in description, only spacing (Z)
|
|
2109
|
+
# We'll try to get pixel size from XResolution tag later
|
|
2110
|
+
pass # ImageJ metadata doesn't contain pixel size in description
|
|
2111
|
+
elif desc_stripped.startswith('<') or desc_stripped.startswith('<?xml'):
|
|
2112
|
+
# OME-XML metadata (starts with < or <?xml)
|
|
2113
|
+
try:
|
|
2114
|
+
root = ET.fromstring(desc_text)
|
|
2115
|
+
ns = {'ome': root.tag.split('}')[0].strip('{')}
|
|
2116
|
+
pixels = root.find('.//ome:Pixels', ns)
|
|
2117
|
+
if pixels is not None:
|
|
2118
|
+
attrib = pixels.attrib
|
|
2119
|
+
if 'PhysicalSizeX' in attrib:
|
|
2120
|
+
voxel_x_nm = float(attrib['PhysicalSizeX']) * 1000.0
|
|
2121
|
+
if 'PhysicalSizeY' in attrib:
|
|
2122
|
+
voxel_y_nm = float(attrib['PhysicalSizeY']) * 1000.0
|
|
2123
|
+
if 'PhysicalSizeZ' in attrib:
|
|
2124
|
+
voxel_z_nm = float(attrib['PhysicalSizeZ']) * 1000.0
|
|
2125
|
+
if 'TimeIncrement' in attrib:
|
|
2126
|
+
dt_seconds = float(attrib['TimeIncrement'])
|
|
2127
|
+
channel_elems = pixels.findall('ome:Channel', ns)
|
|
2128
|
+
detected_channel_names = [ch.attrib.get('Name') for ch in channel_elems if 'Name' in ch.attrib]
|
|
2129
|
+
except ET.ParseError:
|
|
2130
|
+
# Not valid XML - that's okay, will try XResolution tags
|
|
2131
|
+
pass
|
|
2124
2132
|
else:
|
|
2125
2133
|
print("No ImageDescription found in TIFF metadata.")
|
|
2134
|
+
# Try to get pixel size from XResolution tag if not found yet
|
|
2126
2135
|
if voxel_x_nm is None:
|
|
2127
2136
|
x_res = page0.tags.get('XResolution')
|
|
2128
2137
|
if x_res:
|
|
2129
2138
|
num, den = x_res.value
|
|
2130
|
-
|
|
2139
|
+
resolution = float(num) / float(den) # pixels per unit
|
|
2140
|
+
# Check ResolutionUnit: 1=None, 2=inch, 3=centimeter
|
|
2141
|
+
res_unit = page0.tags.get('ResolutionUnit')
|
|
2142
|
+
res_unit_val = res_unit.value if res_unit else 1
|
|
2143
|
+
if resolution > 0:
|
|
2144
|
+
# XResolution is pixels per unit, so pixel size = 1/XResolution
|
|
2145
|
+
pixel_size_in_unit = 1.0 / resolution
|
|
2146
|
+
if res_unit_val == 2: # inch
|
|
2147
|
+
candidate_nm = pixel_size_in_unit * 25400 * 1000 # inch to nm
|
|
2148
|
+
elif res_unit_val == 3: # centimeter
|
|
2149
|
+
candidate_nm = pixel_size_in_unit * 10000 * 1000 # cm to nm
|
|
2150
|
+
else: # None or unknown - assume µm for microscopy images
|
|
2151
|
+
candidate_nm = pixel_size_in_unit * 1000 # µm to nm
|
|
2152
|
+
# Sanity check: microscopy pixel sizes are typically 10-5000 nm
|
|
2153
|
+
# Reject values outside this range as likely corrupted/invalid
|
|
2154
|
+
if 10 <= candidate_nm <= 5000:
|
|
2155
|
+
voxel_x_nm = candidate_nm
|
|
2131
2156
|
if voxel_z_nm is None:
|
|
2132
2157
|
z_res = page0.tags.get('ZResolution')
|
|
2133
2158
|
if z_res:
|
|
2134
2159
|
num, den = z_res.value
|
|
2135
|
-
|
|
2136
|
-
|
|
2160
|
+
resolution = float(num) / float(den)
|
|
2161
|
+
if resolution > 0:
|
|
2162
|
+
candidate_nm = (1.0 / resolution) * 1000 # assume µm
|
|
2163
|
+
# Sanity check for Z: 50-10000 nm is reasonable
|
|
2164
|
+
if 50 <= candidate_nm <= 10000:
|
|
2165
|
+
voxel_z_nm = candidate_nm
|
|
2166
|
+
# If essential metadata is missing, prompt user only for what's missing
|
|
2137
2167
|
missing = []
|
|
2138
2168
|
if voxel_x_nm is None:
|
|
2139
2169
|
missing.append("voxel size X (nm)")
|
|
@@ -2142,10 +2172,7 @@ class GUI(QMainWindow):
|
|
|
2142
2172
|
if dt_seconds is None:
|
|
2143
2173
|
missing.append("time increment (s)")
|
|
2144
2174
|
if missing:
|
|
2145
|
-
|
|
2146
|
-
voxel_z_nm = None
|
|
2147
|
-
dt_seconds = None
|
|
2148
|
-
missing = ["voxel size X (nm)", "voxel size Z (nm)", "time increment (s)"]
|
|
2175
|
+
# Only prompt for what's actually missing, keep found values
|
|
2149
2176
|
self.ask_for_metadata_from_user(missing)
|
|
2150
2177
|
# Set voxel sizes and time interval if available
|
|
2151
2178
|
if voxel_x_nm is not None:
|
|
@@ -2823,6 +2850,105 @@ class GUI(QMainWindow):
|
|
|
2823
2850
|
# Reset current indices
|
|
2824
2851
|
self.current_frame = 0
|
|
2825
2852
|
self.current_channel = 0
|
|
2853
|
+
|
|
2854
|
+
# Reset merged mode
|
|
2855
|
+
self.merged_mode = False
|
|
2856
|
+
|
|
2857
|
+
def close_all_files(self):
|
|
2858
|
+
"""
|
|
2859
|
+
Remove all loaded files (LIF and TIFF) from the tree and free their memory.
|
|
2860
|
+
Clears all data and resets the GUI to its initial empty state.
|
|
2861
|
+
"""
|
|
2862
|
+
# Get all top-level items count
|
|
2863
|
+
num_files = self.image_tree.topLevelItemCount()
|
|
2864
|
+
if num_files == 0:
|
|
2865
|
+
return
|
|
2866
|
+
|
|
2867
|
+
# Clear all loaded file data
|
|
2868
|
+
self.loaded_lif_files.clear()
|
|
2869
|
+
|
|
2870
|
+
# Remove all items from tree view
|
|
2871
|
+
self.image_tree.clear()
|
|
2872
|
+
|
|
2873
|
+
# Clear core data
|
|
2874
|
+
self.image_stack = None
|
|
2875
|
+
self.data_folder_path = None
|
|
2876
|
+
self.colocalization_results = None
|
|
2877
|
+
self.current_total_plots = None
|
|
2878
|
+
|
|
2879
|
+
# Use unified reset for all tabs and state
|
|
2880
|
+
self.reset_all_state()
|
|
2881
|
+
|
|
2882
|
+
# Clear info labels
|
|
2883
|
+
labels_to_clear = [
|
|
2884
|
+
'file_label', 'frames_label', 'z_scales_label',
|
|
2885
|
+
'y_pixels_label', 'x_pixels_label', 'channels_label',
|
|
2886
|
+
'voxel_yx_size_label', 'voxel_z_nm_label',
|
|
2887
|
+
'bit_depth_label', 'time_interval_label',
|
|
2888
|
+
'laser_lines_label', 'intensities_label', 'wave_ranges_label'
|
|
2889
|
+
]
|
|
2890
|
+
for lbl_name in labels_to_clear:
|
|
2891
|
+
if hasattr(self, lbl_name):
|
|
2892
|
+
getattr(self, lbl_name).setText("")
|
|
2893
|
+
|
|
2894
|
+
# Clear channel controls
|
|
2895
|
+
if hasattr(self, 'channelControlsTabs'):
|
|
2896
|
+
self.channelControlsTabs.clear()
|
|
2897
|
+
|
|
2898
|
+
# Clear channel buttons
|
|
2899
|
+
for btn_list in [getattr(self, 'channel_buttons_display', []),
|
|
2900
|
+
getattr(self, 'channel_buttons_tracking', []),
|
|
2901
|
+
getattr(self, 'channel_buttons_tracking_vis', []),
|
|
2902
|
+
getattr(self, 'segmentation_channel_buttons', [])]:
|
|
2903
|
+
for btn in btn_list:
|
|
2904
|
+
if btn:
|
|
2905
|
+
btn.setParent(None)
|
|
2906
|
+
|
|
2907
|
+
# Reset button lists
|
|
2908
|
+
self.channel_buttons_display = []
|
|
2909
|
+
self.channel_buttons_tracking = []
|
|
2910
|
+
if hasattr(self, 'channel_buttons_tracking_vis'):
|
|
2911
|
+
self.channel_buttons_tracking_vis = []
|
|
2912
|
+
if hasattr(self, 'segmentation_channel_buttons'):
|
|
2913
|
+
self.segmentation_channel_buttons = []
|
|
2914
|
+
|
|
2915
|
+
# Clear channel checkboxes for correlation
|
|
2916
|
+
if hasattr(self, 'channel_checkboxes'):
|
|
2917
|
+
for cb in self.channel_checkboxes:
|
|
2918
|
+
if cb:
|
|
2919
|
+
cb.setParent(None)
|
|
2920
|
+
self.channel_checkboxes = []
|
|
2921
|
+
|
|
2922
|
+
# Clear combo boxes
|
|
2923
|
+
if hasattr(self, 'intensity_channel_combo'):
|
|
2924
|
+
self.intensity_channel_combo.clear()
|
|
2925
|
+
if hasattr(self, 'time_course_channel_combo'):
|
|
2926
|
+
self.time_course_channel_combo.clear()
|
|
2927
|
+
if hasattr(self, 'channel_combo_box_1'):
|
|
2928
|
+
self.channel_combo_box_1.clear()
|
|
2929
|
+
if hasattr(self, 'channel_combo_box_2'):
|
|
2930
|
+
self.channel_combo_box_2.clear()
|
|
2931
|
+
|
|
2932
|
+
# Disable controls
|
|
2933
|
+
if hasattr(self, 'time_slider_display'):
|
|
2934
|
+
self.time_slider_display.setEnabled(False)
|
|
2935
|
+
self.time_slider_display.setValue(0)
|
|
2936
|
+
if hasattr(self, 'play_button_display'):
|
|
2937
|
+
self.play_button_display.setEnabled(False)
|
|
2938
|
+
if hasattr(self, 'time_slider_tracking'):
|
|
2939
|
+
self.time_slider_tracking.setValue(0)
|
|
2940
|
+
if hasattr(self, 'time_slider_tracking_vis'):
|
|
2941
|
+
self.time_slider_tracking_vis.setValue(0)
|
|
2942
|
+
|
|
2943
|
+
# Stop any playing timers
|
|
2944
|
+
self.stop_all_playback()
|
|
2945
|
+
|
|
2946
|
+
# Reset current indices
|
|
2947
|
+
self.current_frame = 0
|
|
2948
|
+
self.current_channel = 0
|
|
2949
|
+
|
|
2950
|
+
# Reset merged mode
|
|
2951
|
+
self.merged_mode = False
|
|
2826
2952
|
|
|
2827
2953
|
def on_tree_current_item_changed(self, current, previous):
|
|
2828
2954
|
"""
|
|
@@ -2913,7 +3039,7 @@ class GUI(QMainWindow):
|
|
|
2913
3039
|
|
|
2914
3040
|
def merge_color_channels(self):
|
|
2915
3041
|
if self.image_stack is None:
|
|
2916
|
-
|
|
3042
|
+
# Silently return if no image - this can happen when closing files in merge mode
|
|
2917
3043
|
return
|
|
2918
3044
|
merged_img = self.compute_merged_image()
|
|
2919
3045
|
if merged_img is None:
|
|
@@ -3145,10 +3271,15 @@ class GUI(QMainWindow):
|
|
|
3145
3271
|
self.image_tree.itemClicked.connect(self.on_tree_item_clicked)
|
|
3146
3272
|
self.image_tree.currentItemChanged.connect(self.on_tree_current_item_changed)
|
|
3147
3273
|
display_right_layout.addWidget(self.image_tree)
|
|
3148
|
-
# Close file
|
|
3274
|
+
# Close file buttons (using horizontal layout for both buttons)
|
|
3275
|
+
close_buttons_layout = QHBoxLayout()
|
|
3149
3276
|
self.close_file_button = QPushButton("Close File", self)
|
|
3150
3277
|
self.close_file_button.clicked.connect(self.close_selected_file)
|
|
3151
|
-
|
|
3278
|
+
close_buttons_layout.addWidget(self.close_file_button)
|
|
3279
|
+
self.close_all_files_button = QPushButton("Close All", self)
|
|
3280
|
+
self.close_all_files_button.clicked.connect(self.close_all_files)
|
|
3281
|
+
close_buttons_layout.addWidget(self.close_all_files_button)
|
|
3282
|
+
display_right_layout.addLayout(close_buttons_layout)
|
|
3152
3283
|
# Visualization controls
|
|
3153
3284
|
self.control_panel_image_properties(display_right_layout)
|
|
3154
3285
|
# Group box for image info
|
|
@@ -6164,9 +6295,8 @@ class GUI(QMainWindow):
|
|
|
6164
6295
|
# (Cellpose masks are labeled, not suitable for photobleaching mask input)
|
|
6165
6296
|
if has_cellpose_mask and not has_segmentation_mask:
|
|
6166
6297
|
mode = 'entire_image'
|
|
6167
|
-
# Inform user
|
|
6168
|
-
|
|
6169
|
-
"Cellpose masks detected. Photobleaching will be calculated using the entire image.")
|
|
6298
|
+
# Inform user via status bar (non-blocking)
|
|
6299
|
+
self.statusBar().showMessage("Cellpose detected: using entire image for photobleaching calculation.", 5000)
|
|
6170
6300
|
|
|
6171
6301
|
self.photobleaching_mode = mode
|
|
6172
6302
|
radius = self.radius_spinbox.value()
|
|
@@ -8106,7 +8236,7 @@ class GUI(QMainWindow):
|
|
|
8106
8236
|
if self.image_stack is None:
|
|
8107
8237
|
QMessageBox.warning(self, "No Image Loaded", "Please load an image first.")
|
|
8108
8238
|
return
|
|
8109
|
-
if not hasattr(self, 'user_selected_threshold') or self.user_selected_threshold <= 0:
|
|
8239
|
+
if not hasattr(self, 'user_selected_threshold') or self.user_selected_threshold is None or self.user_selected_threshold <= 0:
|
|
8110
8240
|
QMessageBox.warning(self, "Tracking Aborted", "Threshold is zero; please adjust the threshold slider before running tracking.")
|
|
8111
8241
|
return
|
|
8112
8242
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Entry point for MicroLive GUI application.
|
|
2
|
+
|
|
3
|
+
This module provides the command-line entry point for launching
|
|
4
|
+
the MicroLive graphical user interface.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
$ microlive
|
|
8
|
+
|
|
9
|
+
Or programmatically:
|
|
10
|
+
from microlive.gui.main import main
|
|
11
|
+
main()
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
"""Launch the MicroLive GUI application."""
|
|
20
|
+
# Ensure proper Qt platform on macOS
|
|
21
|
+
if sys.platform == "darwin":
|
|
22
|
+
os.environ.setdefault("QT_MAC_WANTS_LAYER", "1")
|
|
23
|
+
|
|
24
|
+
# Import Qt after environment setup
|
|
25
|
+
from PyQt5.QtWidgets import QApplication
|
|
26
|
+
from PyQt5.QtGui import QIcon, QFont, QPalette, QColor
|
|
27
|
+
from PyQt5.QtCore import Qt
|
|
28
|
+
import matplotlib.pyplot as plt
|
|
29
|
+
|
|
30
|
+
# Import the main application window
|
|
31
|
+
from .app import GUI
|
|
32
|
+
|
|
33
|
+
# Get icon path
|
|
34
|
+
from ..utils.resources import get_icon_path
|
|
35
|
+
|
|
36
|
+
# Create application
|
|
37
|
+
app = QApplication(sys.argv)
|
|
38
|
+
app.setStyle('Fusion')
|
|
39
|
+
|
|
40
|
+
# Set modern font based on platform
|
|
41
|
+
if sys.platform == 'win32':
|
|
42
|
+
app.setFont(QFont("Segoe UI", 11))
|
|
43
|
+
elif sys.platform == 'darwin':
|
|
44
|
+
app.setFont(QFont("SF Pro", 11))
|
|
45
|
+
else:
|
|
46
|
+
app.setFont(QFont("Inter", 11))
|
|
47
|
+
|
|
48
|
+
# Set dark matplotlib style
|
|
49
|
+
plt.style.use('dark_background')
|
|
50
|
+
|
|
51
|
+
# Set dark palette for the application
|
|
52
|
+
palette = QPalette()
|
|
53
|
+
palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
54
|
+
palette.setColor(QPalette.WindowText, Qt.white)
|
|
55
|
+
palette.setColor(QPalette.Base, QColor(35, 35, 35))
|
|
56
|
+
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
|
|
57
|
+
palette.setColor(QPalette.ToolTipBase, Qt.white)
|
|
58
|
+
palette.setColor(QPalette.ToolTipText, Qt.white)
|
|
59
|
+
palette.setColor(QPalette.Text, Qt.white)
|
|
60
|
+
palette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
61
|
+
palette.setColor(QPalette.ButtonText, Qt.white)
|
|
62
|
+
palette.setColor(QPalette.BrightText, Qt.red)
|
|
63
|
+
palette.setColor(QPalette.Highlight, QColor(142, 45, 197).lighter())
|
|
64
|
+
palette.setColor(QPalette.HighlightedText, Qt.black)
|
|
65
|
+
app.setPalette(palette)
|
|
66
|
+
|
|
67
|
+
# Set application metadata
|
|
68
|
+
app.setApplicationName("MicroLive")
|
|
69
|
+
app.setApplicationDisplayName("MicroLive")
|
|
70
|
+
app.setOrganizationName("Zhao Lab")
|
|
71
|
+
|
|
72
|
+
# Set application icon
|
|
73
|
+
icon_path = get_icon_path()
|
|
74
|
+
if icon_path and icon_path.exists():
|
|
75
|
+
app.setWindowIcon(QIcon(str(icon_path)))
|
|
76
|
+
|
|
77
|
+
# Create and show main window
|
|
78
|
+
window = GUI(icon_path=icon_path)
|
|
79
|
+
window.show()
|
|
80
|
+
|
|
81
|
+
# Run event loop
|
|
82
|
+
sys.exit(app.exec_())
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
main()
|
|
@@ -8,9 +8,6 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "Live-cell microscopy image analysis and single-molecule measurements"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
11
|
-
|
|
12
|
-
[tool.hatch.version]
|
|
13
|
-
path = "microlive/__init__.py"
|
|
14
11
|
requires-python = ">=3.10"
|
|
15
12
|
authors = [
|
|
16
13
|
{ name = "Luis U. Aguilera" },
|
|
@@ -115,6 +112,7 @@ dev = [
|
|
|
115
112
|
"ruff>=0.1",
|
|
116
113
|
"build",
|
|
117
114
|
"twine",
|
|
115
|
+
"pdoc",
|
|
118
116
|
]
|
|
119
117
|
notebooks = [
|
|
120
118
|
"jupyter>=1.0",
|
|
@@ -137,6 +135,9 @@ Changelog = "https://github.com/ningzhaoAnschutz/microlive/releases"
|
|
|
137
135
|
# Build Configuration
|
|
138
136
|
# =============================================================================
|
|
139
137
|
|
|
138
|
+
[tool.hatch.version]
|
|
139
|
+
path = "microlive/__init__.py"
|
|
140
|
+
|
|
140
141
|
[tool.hatch.build.targets.wheel]
|
|
141
142
|
packages = ["microlive"]
|
|
142
143
|
exclude = [
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""Entry point for MicroLive GUI application.
|
|
2
|
-
|
|
3
|
-
This module provides the command-line entry point for launching
|
|
4
|
-
the MicroLive graphical user interface.
|
|
5
|
-
|
|
6
|
-
Usage:
|
|
7
|
-
$ microlive
|
|
8
|
-
|
|
9
|
-
Or programmatically:
|
|
10
|
-
from microlive.gui.main import main
|
|
11
|
-
main()
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import sys
|
|
15
|
-
import os
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def main():
|
|
19
|
-
"""Launch the MicroLive GUI application."""
|
|
20
|
-
# Ensure proper Qt platform on macOS
|
|
21
|
-
if sys.platform == "darwin":
|
|
22
|
-
os.environ.setdefault("QT_MAC_WANTS_LAYER", "1")
|
|
23
|
-
|
|
24
|
-
# Import Qt after environment setup
|
|
25
|
-
from PyQt5.QtWidgets import QApplication
|
|
26
|
-
from PyQt5.QtGui import QIcon
|
|
27
|
-
|
|
28
|
-
# Import the main application window
|
|
29
|
-
from .app import GUI
|
|
30
|
-
|
|
31
|
-
# Get icon path
|
|
32
|
-
from ..utils.resources import get_icon_path
|
|
33
|
-
|
|
34
|
-
# Create application
|
|
35
|
-
app = QApplication(sys.argv)
|
|
36
|
-
app.setApplicationName("MicroLive")
|
|
37
|
-
app.setApplicationVersion("1.0.0")
|
|
38
|
-
app.setOrganizationName("Zhao Lab")
|
|
39
|
-
|
|
40
|
-
# Set application icon
|
|
41
|
-
icon_path = get_icon_path()
|
|
42
|
-
if icon_path and icon_path.exists():
|
|
43
|
-
app.setWindowIcon(QIcon(str(icon_path)))
|
|
44
|
-
|
|
45
|
-
# Create and show main window
|
|
46
|
-
window = GUI(icon_path=icon_path)
|
|
47
|
-
window.show()
|
|
48
|
-
|
|
49
|
-
# Run event loop
|
|
50
|
-
sys.exit(app.exec_())
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if __name__ == "__main__":
|
|
54
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_spot_detection_no_tracking.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|