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.
Files changed (28) hide show
  1. {microlive-1.0.2 → microlive-1.0.4}/.gitignore +1 -0
  2. {microlive-1.0.2 → microlive-1.0.4}/PKG-INFO +58 -1
  3. {microlive-1.0.2 → microlive-1.0.4}/microlive/__init__.py +7 -1
  4. {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/app.py +163 -33
  5. microlive-1.0.4/microlive/gui/main.py +86 -0
  6. {microlive-1.0.2 → microlive-1.0.4}/pyproject.toml +4 -3
  7. microlive-1.0.2/microlive/gui/main.py +0 -54
  8. {microlive-1.0.2 → microlive-1.0.4}/LICENSE +0 -0
  9. {microlive-1.0.2 → microlive-1.0.4}/README.md +0 -0
  10. {microlive-1.0.2 → microlive-1.0.4}/microlive/data/__init__.py +0 -0
  11. {microlive-1.0.2 → microlive-1.0.4}/microlive/data/icons/__init__.py +0 -0
  12. {microlive-1.0.2 → microlive-1.0.4}/microlive/data/icons/icon_micro.png +0 -0
  13. {microlive-1.0.2 → microlive-1.0.4}/microlive/data/models/__init__.py +0 -0
  14. {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/__init__.py +0 -0
  15. {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/micro_mac.command +0 -0
  16. {microlive-1.0.2 → microlive-1.0.4}/microlive/gui/micro_windows.bat +0 -0
  17. {microlive-1.0.2 → microlive-1.0.4}/microlive/imports.py +0 -0
  18. {microlive-1.0.2 → microlive-1.0.4}/microlive/microscopy.py +0 -0
  19. {microlive-1.0.2 → microlive-1.0.4}/microlive/ml_spot_detection.py +0 -0
  20. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/__init__.py +0 -0
  21. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_FRAP.py +0 -0
  22. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_folding_efficiency.py +0 -0
  23. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_particle_tracking.py +0 -0
  24. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_particle_tracking_old.py +0 -0
  25. {microlive-1.0.2 → microlive-1.0.4}/microlive/pipelines/pipeline_spot_detection_no_tracking.py +0 -0
  26. {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/__init__.py +0 -0
  27. {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/device.py +0 -0
  28. {microlive-1.0.2 → microlive-1.0.4}/microlive/utils/resources.py +0 -0
@@ -85,3 +85,4 @@ writing/
85
85
  docs/paper_microlive_12112025/
86
86
  merged
87
87
  tests/
88
+ video_youtube/
@@ -1,19 +1,76 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microlive
3
- Version: 1.0.2
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.2"
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
- # OME-XML metadata
2106
- try:
2107
- root = ET.fromstring(desc_text)
2108
- ns = {'ome': root.tag.split('}')[0].strip('{')}
2109
- pixels = root.find('.//ome:Pixels', ns)
2110
- if pixels is not None:
2111
- attrib = pixels.attrib
2112
- if 'PhysicalSizeX' in attrib:
2113
- voxel_x_nm = float(attrib['PhysicalSizeX']) * 1000.0
2114
- if 'PhysicalSizeY' in attrib:
2115
- voxel_y_nm = float(attrib['PhysicalSizeY']) * 1000.0
2116
- if 'PhysicalSizeZ' in attrib:
2117
- voxel_z_nm = float(attrib['PhysicalSizeZ']) * 1000.0
2118
- if 'TimeIncrement' in attrib:
2119
- dt_seconds = float(attrib['TimeIncrement'])
2120
- channel_elems = pixels.findall('ome:Channel', ns)
2121
- detected_channel_names = [ch.attrib.get('Name') for ch in channel_elems if 'Name' in ch.attrib]
2122
- except ET.ParseError:
2123
- print("Error parsing OME-XML ImageDescription metadata")
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
- voxel_x_nm = float(num) / float(den) * 1000.0
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
- voxel_z_nm = float(num) / float(den) * 1000.0
2136
- # If essential metadata is missing, prompt user (as per original logic)
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
- voxel_x_nm = None
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
- QMessageBox.information(self, "Merge Error", "No image loaded to merge channels.")
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 button
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
- display_right_layout.addWidget(self.close_file_button)
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 that we're using entire_image mode
6168
- QMessageBox.information(self, "Using Entire Image",
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