microlive 1.0.14__py3-none-any.whl → 1.0.15__py3-none-any.whl
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/__init__.py +1 -1
- microlive/gui/app.py +290 -27
- microlive/microscopy.py +5 -3
- {microlive-1.0.14.dist-info → microlive-1.0.15.dist-info}/METADATA +1 -1
- {microlive-1.0.14.dist-info → microlive-1.0.15.dist-info}/RECORD +9 -9
- {microlive-1.0.14.dist-info → microlive-1.0.15.dist-info}/WHEEL +0 -0
- {microlive-1.0.14.dist-info → microlive-1.0.15.dist-info}/entry_points.txt +0 -0
- {microlive-1.0.14.dist-info → microlive-1.0.15.dist-info}/licenses/LICENSE +0 -0
microlive/__init__.py
CHANGED
|
@@ -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.15"
|
|
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)
|
microlive/gui/app.py
CHANGED
|
@@ -9054,7 +9054,7 @@ class GUI(QMainWindow):
|
|
|
9054
9054
|
# Cluster radius
|
|
9055
9055
|
self.cluster_radius_input = QSpinBox()
|
|
9056
9056
|
self.cluster_radius_input.setMinimum(100)
|
|
9057
|
-
self.cluster_radius_input.setMaximum(
|
|
9057
|
+
self.cluster_radius_input.setMaximum(6000)
|
|
9058
9058
|
self.cluster_radius_input.setValue(self.cluster_radius_nm)
|
|
9059
9059
|
self.cluster_radius_input.valueChanged.connect(self.update_cluster_radius)
|
|
9060
9060
|
params_layout.addRow("Cluster radius (nm):", self.cluster_radius_input)
|
|
@@ -11701,9 +11701,13 @@ class GUI(QMainWindow):
|
|
|
11701
11701
|
layout = QVBoxLayout(self.coloc_verify_distance_widget)
|
|
11702
11702
|
layout.setContentsMargins(10, 5, 10, 5)
|
|
11703
11703
|
|
|
11704
|
-
# Info label
|
|
11705
|
-
info_label = QLabel(
|
|
11704
|
+
# Info label explaining what is displayed
|
|
11705
|
+
info_label = QLabel(
|
|
11706
|
+
"Review unique particle tracks. Each row shows a time-averaged crop. "
|
|
11707
|
+
"A track is marked colocalized (✓) if ANY frame is within the distance threshold."
|
|
11708
|
+
)
|
|
11706
11709
|
info_label.setStyleSheet("font-style: italic; color: #999;")
|
|
11710
|
+
info_label.setWordWrap(True)
|
|
11707
11711
|
layout.addWidget(info_label)
|
|
11708
11712
|
|
|
11709
11713
|
# Top bar with stats and buttons
|
|
@@ -12553,6 +12557,9 @@ class GUI(QMainWindow):
|
|
|
12553
12557
|
channels=(ch1, ch2)
|
|
12554
12558
|
)
|
|
12555
12559
|
|
|
12560
|
+
# Reset sorted flag so Sort button can be used
|
|
12561
|
+
self._verify_visual_sorted = False
|
|
12562
|
+
|
|
12556
12563
|
# Update stats label
|
|
12557
12564
|
self._update_verify_visual_stats()
|
|
12558
12565
|
|
|
@@ -12570,20 +12577,76 @@ class GUI(QMainWindow):
|
|
|
12570
12577
|
)
|
|
12571
12578
|
|
|
12572
12579
|
def sort_verify_visual(self):
|
|
12573
|
-
"""Sort Verify Visual results by prediction value (lowest to highest)."""
|
|
12580
|
+
"""Sort Verify Visual results by prediction value (lowest to highest for review)."""
|
|
12574
12581
|
if not hasattr(self, 'verify_visual_checkboxes') or len(self.verify_visual_checkboxes) == 0:
|
|
12582
|
+
QMessageBox.information(self, "No Data", "No spots to sort. Please click Populate first.")
|
|
12583
|
+
return
|
|
12584
|
+
|
|
12585
|
+
if not hasattr(self, 'colocalization_results') or not self.colocalization_results:
|
|
12586
|
+
QMessageBox.warning(self, "No Results", "No colocalization results available.")
|
|
12575
12587
|
return
|
|
12576
12588
|
|
|
12577
|
-
|
|
12589
|
+
results = self.colocalization_results
|
|
12590
|
+
values = results.get('prediction_values_vector')
|
|
12591
|
+
mean_crop = results.get('mean_crop_filtered')
|
|
12592
|
+
crop_size = results.get('crop_size', 15)
|
|
12593
|
+
flag_vector = results.get('flag_vector')
|
|
12594
|
+
ch1 = results.get('ch1_index', 0)
|
|
12595
|
+
ch2 = results.get('ch2_index', 1)
|
|
12596
|
+
|
|
12578
12597
|
if values is None or len(values) == 0:
|
|
12579
12598
|
QMessageBox.information(self, "Cannot Sort", "No prediction values available for sorting.")
|
|
12580
12599
|
return
|
|
12581
12600
|
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12601
|
+
if mean_crop is None:
|
|
12602
|
+
QMessageBox.warning(self, "No Data", "Crop data not available for sorting.")
|
|
12603
|
+
return
|
|
12604
|
+
|
|
12605
|
+
# Check if already sorted (compare to original order)
|
|
12606
|
+
if hasattr(self, '_verify_visual_sorted') and self._verify_visual_sorted:
|
|
12607
|
+
QMessageBox.information(self, "Already Sorted", "Spots are already sorted by prediction value.")
|
|
12608
|
+
return
|
|
12609
|
+
|
|
12610
|
+
# Get current checkbox states before sorting
|
|
12611
|
+
current_states = [chk.isChecked() for chk in self.verify_visual_checkboxes]
|
|
12612
|
+
|
|
12613
|
+
# Create sorted indices (ascending by prediction value - uncertain first)
|
|
12614
|
+
num_spots = len(values)
|
|
12615
|
+
sorted_indices = np.argsort(values)
|
|
12616
|
+
|
|
12617
|
+
# Re-order checkbox states to match new sort order
|
|
12618
|
+
sorted_states = [current_states[i] if i < len(current_states) else False for i in sorted_indices]
|
|
12619
|
+
|
|
12620
|
+
# Re-order crops - each spot is crop_size rows in the mean_crop array
|
|
12621
|
+
num_crop_spots = mean_crop.shape[0] // crop_size
|
|
12622
|
+
if num_crop_spots < num_spots:
|
|
12623
|
+
num_spots = num_crop_spots
|
|
12624
|
+
sorted_indices = sorted_indices[:num_spots]
|
|
12625
|
+
|
|
12626
|
+
sorted_crop = np.zeros_like(mean_crop[:num_spots*crop_size])
|
|
12627
|
+
for new_idx, old_idx in enumerate(sorted_indices[:num_spots]):
|
|
12628
|
+
if old_idx < num_crop_spots:
|
|
12629
|
+
sorted_crop[new_idx*crop_size:(new_idx+1)*crop_size] = \
|
|
12630
|
+
mean_crop[old_idx*crop_size:(old_idx+1)*crop_size]
|
|
12631
|
+
|
|
12632
|
+
# Re-create verification crops with sorted data
|
|
12633
|
+
self._create_verification_crops(
|
|
12634
|
+
scroll_area=self.verify_visual_scroll_area,
|
|
12635
|
+
checkboxes_list_attr='verify_visual_checkboxes',
|
|
12636
|
+
mean_crop=sorted_crop,
|
|
12637
|
+
crop_size=crop_size,
|
|
12638
|
+
flag_vector=sorted_states, # Use previously checked states after reorder
|
|
12639
|
+
stats_label=self.verify_visual_stats_label,
|
|
12640
|
+
num_channels=2,
|
|
12641
|
+
channels=(ch1, ch2)
|
|
12642
|
+
)
|
|
12643
|
+
|
|
12644
|
+
# Mark as sorted
|
|
12645
|
+
self._verify_visual_sorted = True
|
|
12646
|
+
self._verify_visual_sort_indices = sorted_indices
|
|
12647
|
+
|
|
12648
|
+
# Update stats
|
|
12649
|
+
self._update_verify_visual_stats()
|
|
12587
12650
|
|
|
12588
12651
|
def cleanup_verify_visual(self):
|
|
12589
12652
|
"""Clear all checkboxes in Verify Visual subtab."""
|
|
@@ -12630,7 +12693,11 @@ class GUI(QMainWindow):
|
|
|
12630
12693
|
# === Verify Distance Subtab Methods ===
|
|
12631
12694
|
|
|
12632
12695
|
def populate_verify_distance(self):
|
|
12633
|
-
"""Populate the Verify Distance subtab with Distance colocalization results.
|
|
12696
|
+
"""Populate the Verify Distance subtab with Distance colocalization results.
|
|
12697
|
+
|
|
12698
|
+
Calculates and stores the minimum distance from each reference channel spot
|
|
12699
|
+
to its nearest partner in the target channel for sorting purposes.
|
|
12700
|
+
"""
|
|
12634
12701
|
if not hasattr(self, 'distance_coloc_results') or not self.distance_coloc_results:
|
|
12635
12702
|
QMessageBox.warning(self, "No Results",
|
|
12636
12703
|
"Please run Distance colocalization first.")
|
|
@@ -12641,8 +12708,10 @@ class GUI(QMainWindow):
|
|
|
12641
12708
|
ch0 = results.get('channel_0', 0)
|
|
12642
12709
|
ch1 = results.get('channel_1', 1)
|
|
12643
12710
|
df_coloc = results.get('df_colocalized', pd.DataFrame())
|
|
12711
|
+
df_ch1_all = results.get('df_ch1_all', pd.DataFrame())
|
|
12644
12712
|
threshold_px = results.get('threshold_distance_px', 2.0)
|
|
12645
12713
|
threshold_nm = results.get('threshold_distance_nm', 130.0)
|
|
12714
|
+
use_3d = results.get('use_3d', False)
|
|
12646
12715
|
|
|
12647
12716
|
# We need to create crops from tracking data
|
|
12648
12717
|
if not hasattr(self, 'df_tracking') or self.df_tracking.empty:
|
|
@@ -12684,28 +12753,129 @@ class GUI(QMainWindow):
|
|
|
12684
12753
|
|
|
12685
12754
|
num_spots = mean_crop.shape[0] // crop_size
|
|
12686
12755
|
|
|
12687
|
-
#
|
|
12688
|
-
#
|
|
12689
|
-
|
|
12756
|
+
# Build set of colocalized coordinates for matching
|
|
12757
|
+
# Use a tolerance-based approach instead of exact coordinate matching
|
|
12758
|
+
coloc_coords_array = np.empty((0, 4)) # z, y, x, cell_id
|
|
12690
12759
|
if not df_coloc.empty:
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12760
|
+
if 'z' in df_coloc.columns and use_3d:
|
|
12761
|
+
coloc_coords_array = df_coloc[['z', 'y', 'x', 'cell_id']].values
|
|
12762
|
+
else:
|
|
12763
|
+
# Add dummy z=0 for 2D matching
|
|
12764
|
+
coloc_coords_array = np.column_stack([
|
|
12765
|
+
np.zeros(len(df_coloc)),
|
|
12766
|
+
df_coloc['y'].values,
|
|
12767
|
+
df_coloc['x'].values,
|
|
12768
|
+
df_coloc['cell_id'].values
|
|
12769
|
+
])
|
|
12770
|
+
|
|
12771
|
+
# Calculate minimum distances for each spot in ch0 to nearest spot in ch1
|
|
12772
|
+
# This will be used for sorting (ascending = closest to threshold = most uncertain)
|
|
12773
|
+
distance_values = []
|
|
12695
12774
|
flag_vector = []
|
|
12696
|
-
|
|
12775
|
+
|
|
12776
|
+
# Get ch1 coordinates for distance calculation
|
|
12777
|
+
ch1_coords = None
|
|
12778
|
+
if not df_ch1_all.empty and 'x' in df_ch1_all.columns and 'y' in df_ch1_all.columns:
|
|
12779
|
+
if use_3d and 'z' in df_ch1_all.columns:
|
|
12780
|
+
ch1_coords = df_ch1_all[['z', 'y', 'x']].values
|
|
12781
|
+
else:
|
|
12782
|
+
ch1_coords = df_ch1_all[['y', 'x']].values
|
|
12783
|
+
|
|
12784
|
+
# Get anisotropic scaling for 3D
|
|
12785
|
+
voxel_z_nm = results.get('voxel_z_nm', 300.0)
|
|
12786
|
+
voxel_xy_nm = results.get('voxel_xy_nm', 130.0)
|
|
12787
|
+
z_scale = voxel_z_nm / voxel_xy_nm if use_3d and voxel_xy_nm > 0 else 1.0
|
|
12788
|
+
|
|
12789
|
+
# Use the same particle column identification as CropArray
|
|
12790
|
+
# This ensures our iteration matches the crop order
|
|
12791
|
+
df_ch0_copy = df_ch0.copy()
|
|
12792
|
+
if 'unique_particle' in df_ch0_copy.columns:
|
|
12793
|
+
particle_col = 'unique_particle'
|
|
12794
|
+
elif 'cell_id' in df_ch0_copy.columns:
|
|
12795
|
+
if 'spot_type' in df_ch0_copy.columns:
|
|
12796
|
+
df_ch0_copy['unique_particle'] = (
|
|
12797
|
+
df_ch0_copy['cell_id'].astype(str) + '_' +
|
|
12798
|
+
df_ch0_copy['spot_type'].astype(str) + '_' +
|
|
12799
|
+
df_ch0_copy['particle'].astype(str)
|
|
12800
|
+
)
|
|
12801
|
+
else:
|
|
12802
|
+
df_ch0_copy['unique_particle'] = (
|
|
12803
|
+
df_ch0_copy['cell_id'].astype(str) + '_' +
|
|
12804
|
+
df_ch0_copy['particle'].astype(str)
|
|
12805
|
+
)
|
|
12806
|
+
particle_col = 'unique_particle'
|
|
12807
|
+
else:
|
|
12808
|
+
particle_col = 'particle'
|
|
12809
|
+
|
|
12810
|
+
# Helper function to check if a spot coordinate is in the colocalized set
|
|
12811
|
+
def is_coord_colocalized(z, y, x, cell_id, coloc_arr, tolerance=1.0):
|
|
12812
|
+
"""Check if a spot is in the colocalized set using coordinate tolerance."""
|
|
12813
|
+
if len(coloc_arr) == 0:
|
|
12814
|
+
return False
|
|
12815
|
+
# Filter by cell_id first for efficiency
|
|
12816
|
+
cell_mask = coloc_arr[:, 3].astype(int) == int(cell_id)
|
|
12817
|
+
cell_coloc = coloc_arr[cell_mask]
|
|
12818
|
+
if len(cell_coloc) == 0:
|
|
12819
|
+
return False
|
|
12820
|
+
# Check distance to each colocalized spot
|
|
12821
|
+
for cz, cy, cx, _ in cell_coloc:
|
|
12822
|
+
dist_xy = np.sqrt((x - cx)**2 + (y - cy)**2)
|
|
12823
|
+
dist_z = abs(z - cz) if use_3d else 0
|
|
12824
|
+
if dist_xy <= tolerance and dist_z <= tolerance:
|
|
12825
|
+
return True
|
|
12826
|
+
return False
|
|
12827
|
+
|
|
12828
|
+
# Iterate unique particles in the same order as CropArray
|
|
12829
|
+
unique_particles = df_ch0_copy[particle_col].unique()
|
|
12830
|
+
|
|
12831
|
+
for i, particle_id in enumerate(unique_particles):
|
|
12697
12832
|
if i >= num_spots:
|
|
12698
12833
|
break
|
|
12699
|
-
|
|
12700
|
-
|
|
12834
|
+
|
|
12835
|
+
df_particle = df_ch0_copy[df_ch0_copy[particle_col] == particle_id]
|
|
12836
|
+
|
|
12837
|
+
# Check if ANY observation of this particle is colocalized
|
|
12838
|
+
is_coloc = False
|
|
12839
|
+
min_dist_all = threshold_px * 10.0 # Large default
|
|
12840
|
+
|
|
12841
|
+
for _, row in df_particle.iterrows():
|
|
12842
|
+
x_val, y_val = row['x'], row['y']
|
|
12843
|
+
z_val = row.get('z', 0)
|
|
12844
|
+
cell_id = row.get('cell_id', 0)
|
|
12845
|
+
|
|
12846
|
+
# Check if this observation is in the colocalized set
|
|
12847
|
+
if len(coloc_coords_array) > 0:
|
|
12848
|
+
if is_coord_colocalized(z_val, y_val, x_val, cell_id, coloc_coords_array, tolerance=1.0):
|
|
12849
|
+
is_coloc = True
|
|
12850
|
+
|
|
12851
|
+
# Calculate minimum distance to any ch1 spot for this observation
|
|
12852
|
+
if ch1_coords is not None and len(ch1_coords) > 0:
|
|
12853
|
+
if use_3d and ch1_coords.shape[1] == 3:
|
|
12854
|
+
spot_coord = np.array([[z_val * z_scale, y_val, x_val]])
|
|
12855
|
+
ch1_scaled = ch1_coords.copy().astype(float)
|
|
12856
|
+
ch1_scaled[:, 0] = ch1_scaled[:, 0] * z_scale # Scale Z
|
|
12857
|
+
else:
|
|
12858
|
+
spot_coord = np.array([[y_val, x_val]])
|
|
12859
|
+
ch1_scaled = ch1_coords
|
|
12860
|
+
|
|
12861
|
+
from scipy.spatial.distance import cdist
|
|
12862
|
+
distances = cdist(spot_coord, ch1_scaled, metric='euclidean')
|
|
12863
|
+
obs_min_dist = float(np.min(distances))
|
|
12864
|
+
if obs_min_dist < min_dist_all:
|
|
12865
|
+
min_dist_all = obs_min_dist
|
|
12866
|
+
|
|
12867
|
+
flag_vector.append(is_coloc)
|
|
12868
|
+
distance_values.append(min_dist_all)
|
|
12701
12869
|
|
|
12702
|
-
# Pad
|
|
12870
|
+
# Pad vectors if needed (shouldn't happen, but just in case)
|
|
12703
12871
|
while len(flag_vector) < num_spots:
|
|
12704
12872
|
flag_vector.append(False)
|
|
12873
|
+
distance_values.append(threshold_px * 10.0)
|
|
12705
12874
|
|
|
12706
|
-
# Store for later use
|
|
12875
|
+
# Store for later use (sorting, etc.)
|
|
12707
12876
|
self.verify_distance_mean_crop = mean_crop
|
|
12708
12877
|
self.verify_distance_crop_size = crop_size
|
|
12878
|
+
self.verify_distance_values = np.array(distance_values) # For sorting by distance
|
|
12709
12879
|
|
|
12710
12880
|
# Create spot crops with checkboxes
|
|
12711
12881
|
self._create_verification_crops(
|
|
@@ -12719,6 +12889,9 @@ class GUI(QMainWindow):
|
|
|
12719
12889
|
channels=(ch0, ch1)
|
|
12720
12890
|
)
|
|
12721
12891
|
|
|
12892
|
+
# Reset sorted flag so Sort button can be used
|
|
12893
|
+
self._verify_distance_sorted = False
|
|
12894
|
+
|
|
12722
12895
|
# Update stats label
|
|
12723
12896
|
self._update_verify_distance_stats()
|
|
12724
12897
|
|
|
@@ -12743,9 +12916,88 @@ class GUI(QMainWindow):
|
|
|
12743
12916
|
)
|
|
12744
12917
|
|
|
12745
12918
|
def sort_verify_distance(self):
|
|
12746
|
-
"""Sort Verify Distance results
|
|
12747
|
-
|
|
12748
|
-
|
|
12919
|
+
"""Sort Verify Distance results by distance value (ascending - closest to threshold first).
|
|
12920
|
+
|
|
12921
|
+
Similar to Visual method's certainty-based sorting, but uses the measured
|
|
12922
|
+
distance to nearest partner. Spots with distances closest to the colocalization
|
|
12923
|
+
threshold are shown first as they represent the most uncertain classifications.
|
|
12924
|
+
"""
|
|
12925
|
+
if not hasattr(self, 'verify_distance_checkboxes') or len(self.verify_distance_checkboxes) == 0:
|
|
12926
|
+
QMessageBox.information(self, "No Data", "No spots to sort. Please click Populate first.")
|
|
12927
|
+
return
|
|
12928
|
+
|
|
12929
|
+
if not hasattr(self, 'verify_distance_mean_crop') or self.verify_distance_mean_crop is None:
|
|
12930
|
+
QMessageBox.warning(self, "No Data", "Crop data not available for sorting.")
|
|
12931
|
+
return
|
|
12932
|
+
|
|
12933
|
+
# Check if distance values are available
|
|
12934
|
+
if not hasattr(self, 'verify_distance_values') or self.verify_distance_values is None:
|
|
12935
|
+
QMessageBox.warning(self, "No Distance Data",
|
|
12936
|
+
"Distance values not available. Please re-run Populate.")
|
|
12937
|
+
return
|
|
12938
|
+
|
|
12939
|
+
# Check if already sorted
|
|
12940
|
+
if hasattr(self, '_verify_distance_sorted') and self._verify_distance_sorted:
|
|
12941
|
+
QMessageBox.information(self, "Already Sorted", "Spots are already sorted by distance value.")
|
|
12942
|
+
return
|
|
12943
|
+
|
|
12944
|
+
mean_crop = self.verify_distance_mean_crop
|
|
12945
|
+
crop_size = self.verify_distance_crop_size
|
|
12946
|
+
distance_values = self.verify_distance_values
|
|
12947
|
+
|
|
12948
|
+
# Get current checkbox states before sorting
|
|
12949
|
+
current_states = [chk.isChecked() for chk in self.verify_distance_checkboxes]
|
|
12950
|
+
num_spots = len(current_states)
|
|
12951
|
+
|
|
12952
|
+
# Sort ascending by distance (closest to threshold = most uncertain first)
|
|
12953
|
+
# This matches the Visual method's approach of showing uncertain cases first
|
|
12954
|
+
sorted_indices = np.argsort(distance_values)
|
|
12955
|
+
|
|
12956
|
+
# Re-order states and distances
|
|
12957
|
+
sorted_states = [current_states[i] if i < len(current_states) else False for i in sorted_indices]
|
|
12958
|
+
sorted_distances = distance_values[sorted_indices]
|
|
12959
|
+
|
|
12960
|
+
# Re-order crops
|
|
12961
|
+
num_crop_spots = mean_crop.shape[0] // crop_size
|
|
12962
|
+
if num_crop_spots < num_spots:
|
|
12963
|
+
num_spots = num_crop_spots
|
|
12964
|
+
sorted_indices = sorted_indices[:num_spots]
|
|
12965
|
+
|
|
12966
|
+
sorted_crop = np.zeros_like(mean_crop[:num_spots*crop_size])
|
|
12967
|
+
for new_idx, old_idx in enumerate(sorted_indices[:num_spots]):
|
|
12968
|
+
if old_idx < num_crop_spots:
|
|
12969
|
+
sorted_crop[new_idx*crop_size:(new_idx+1)*crop_size] = \
|
|
12970
|
+
mean_crop[old_idx*crop_size:(old_idx+1)*crop_size]
|
|
12971
|
+
|
|
12972
|
+
# Get channels from distance results
|
|
12973
|
+
results = self.distance_coloc_results if hasattr(self, 'distance_coloc_results') else {}
|
|
12974
|
+
ch0 = results.get('channel_0', 0)
|
|
12975
|
+
ch1 = results.get('channel_1', 1)
|
|
12976
|
+
image = self.corrected_image if self.corrected_image is not None else self.image_stack
|
|
12977
|
+
num_channels = image.shape[-1] if image is not None and image.ndim == 5 else 1
|
|
12978
|
+
|
|
12979
|
+
# Re-create verification crops with sorted data
|
|
12980
|
+
self._create_verification_crops(
|
|
12981
|
+
scroll_area=self.verify_distance_scroll_area,
|
|
12982
|
+
checkboxes_list_attr='verify_distance_checkboxes',
|
|
12983
|
+
mean_crop=sorted_crop,
|
|
12984
|
+
crop_size=crop_size,
|
|
12985
|
+
flag_vector=sorted_states,
|
|
12986
|
+
stats_label=self.verify_distance_stats_label,
|
|
12987
|
+
num_channels=num_channels,
|
|
12988
|
+
channels=(ch0, ch1)
|
|
12989
|
+
)
|
|
12990
|
+
|
|
12991
|
+
# Update stored data after sorting for consistency
|
|
12992
|
+
self.verify_distance_mean_crop = sorted_crop
|
|
12993
|
+
self.verify_distance_values = sorted_distances
|
|
12994
|
+
self._verify_distance_sort_indices = sorted_indices # Store for reference
|
|
12995
|
+
|
|
12996
|
+
# Mark as sorted
|
|
12997
|
+
self._verify_distance_sorted = True
|
|
12998
|
+
|
|
12999
|
+
# Update stats
|
|
13000
|
+
self._update_verify_distance_stats()
|
|
12749
13001
|
|
|
12750
13002
|
def cleanup_verify_distance(self):
|
|
12751
13003
|
"""Clear all checkboxes in Verify Distance subtab."""
|
|
@@ -12833,7 +13085,12 @@ class GUI(QMainWindow):
|
|
|
12833
13085
|
|
|
12834
13086
|
# Checkbox
|
|
12835
13087
|
chk = QCheckBox(f"Spot {i+1}")
|
|
12836
|
-
|
|
13088
|
+
# Safely get the flag value (handle numpy arrays, lists, etc.)
|
|
13089
|
+
try:
|
|
13090
|
+
flag_val = bool(flag_vector[i]) if i < len(flag_vector) else False
|
|
13091
|
+
except (TypeError, IndexError):
|
|
13092
|
+
flag_val = False
|
|
13093
|
+
chk.setChecked(flag_val)
|
|
12837
13094
|
chk.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
12838
13095
|
|
|
12839
13096
|
# Connect to stats update
|
|
@@ -15475,6 +15732,7 @@ class GUI(QMainWindow):
|
|
|
15475
15732
|
self.verify_visual_checkboxes = []
|
|
15476
15733
|
if hasattr(self, 'verify_visual_stats_label'):
|
|
15477
15734
|
self.verify_visual_stats_label.setText("Run Visual colocalization first, then click Populate")
|
|
15735
|
+
self._verify_visual_sorted = False
|
|
15478
15736
|
|
|
15479
15737
|
# Reset Verify Distance
|
|
15480
15738
|
if hasattr(self, 'verify_distance_scroll_area'):
|
|
@@ -15483,6 +15741,11 @@ class GUI(QMainWindow):
|
|
|
15483
15741
|
self.verify_distance_checkboxes = []
|
|
15484
15742
|
if hasattr(self, 'verify_distance_stats_label'):
|
|
15485
15743
|
self.verify_distance_stats_label.setText("Run Distance colocalization first, then click Populate")
|
|
15744
|
+
# Reset stored distance data for sorting
|
|
15745
|
+
self.verify_distance_mean_crop = None
|
|
15746
|
+
self.verify_distance_crop_size = None
|
|
15747
|
+
self.verify_distance_values = None
|
|
15748
|
+
self._verify_distance_sorted = False
|
|
15486
15749
|
|
|
15487
15750
|
def reset_cellpose_tab(self):
|
|
15488
15751
|
"""Reset Cellpose tab state, masks, and UI controls to defaults."""
|
microlive/microscopy.py
CHANGED
|
@@ -3948,7 +3948,8 @@ class BigFISH():
|
|
|
3948
3948
|
|
|
3949
3949
|
# Select isolated spots (cluster_id < 0) and set cluster_size to 1
|
|
3950
3950
|
spots_no_clusters = clusters_and_spots_big_fish[clusters_and_spots_big_fish[:,-1] < 0].copy()
|
|
3951
|
-
spots_no_clusters
|
|
3951
|
+
if len(spots_no_clusters) > 0:
|
|
3952
|
+
spots_no_clusters[:,-1] = 1 # Replace cluster_id with cluster_size=1
|
|
3952
3953
|
|
|
3953
3954
|
# Select cluster centroids with cluster_size > 1
|
|
3954
3955
|
clusters_no_spots = clusters[clusters[:,-2] > 1]
|
|
@@ -5012,6 +5013,7 @@ class DataProcessing():
|
|
|
5012
5013
|
self.fast_gaussian_fit = fast_gaussian_fit
|
|
5013
5014
|
# This number represent the number of columns that doesnt change with the number of color channels in the image
|
|
5014
5015
|
self.NUMBER_OF_CONSTANT_COLUMNS_IN_DATAFRAME = 18
|
|
5016
|
+
|
|
5015
5017
|
def get_dataframe(self):
|
|
5016
5018
|
'''
|
|
5017
5019
|
This method extracts data from the class SpotDetection and returns the data as a dataframe.
|
|
@@ -5162,7 +5164,7 @@ class DataProcessing():
|
|
|
5162
5164
|
array_spots_nuc[:,10:13] = spots_nuc[:,:3] # populating coord
|
|
5163
5165
|
array_spots_nuc[:,13] = 1 # is_nuc
|
|
5164
5166
|
array_spots_nuc[:,14] = 0 # is_cluster
|
|
5165
|
-
array_spots_nuc[:,15] =
|
|
5167
|
+
array_spots_nuc[:,15] = spots_nuc[:,3] # cluster_size (use actual detected value)
|
|
5166
5168
|
array_spots_nuc[:,16] = spot_type # spot_type
|
|
5167
5169
|
array_spots_nuc[:,17] = is_cell_in_border # is_cell_fragmented
|
|
5168
5170
|
|
|
@@ -5171,7 +5173,7 @@ class DataProcessing():
|
|
|
5171
5173
|
array_spots_cytosol_only[:,10:13] = spots_cytosol_only[:,:3] # populating coord
|
|
5172
5174
|
array_spots_cytosol_only[:,13] = 0 # is_nuc
|
|
5173
5175
|
array_spots_cytosol_only[:,14] = 0 # is_cluster
|
|
5174
|
-
array_spots_cytosol_only[:,15] =
|
|
5176
|
+
array_spots_cytosol_only[:,15] = spots_cytosol_only[:,3] # cluster_size (use actual detected value)
|
|
5175
5177
|
array_spots_cytosol_only[:,16] = spot_type # spot_type
|
|
5176
5178
|
array_spots_cytosol_only[:,17] = is_cell_in_border # is_cell_fragmented
|
|
5177
5179
|
if (detected_cyto_clusters == True): #(detected_cyto == True) and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: microlive
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.15
|
|
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
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
microlive/__init__.py,sha256=
|
|
1
|
+
microlive/__init__.py,sha256=7MuUee2Gl8qB6BxHVh-WCCBShmt7ZNkKgYRLAvTVT9A,1385
|
|
2
2
|
microlive/imports.py,sha256=VAAMavSLIKO0LooadTXfCdZiv8LQbV_wITeIv8IHwxM,7531
|
|
3
|
-
microlive/microscopy.py,sha256=
|
|
3
|
+
microlive/microscopy.py,sha256=OFqf0JXJW4-2cLHvXnwwp_SfMFsUXwp5lDKbkCRR4ok,710841
|
|
4
4
|
microlive/ml_spot_detection.py,sha256=pVbOSGNJ0WWMuPRML42rFwvjKVZ0B1fJux1179OIbAg,10603
|
|
5
5
|
microlive/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
microlive/data/icons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
microlive/data/icons/icon_micro.png,sha256=b5tFv4E6vUmLwYmYeM4PJuxLV_XqEzN14ueolekTFW0,370236
|
|
8
8
|
microlive/data/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
microlive/data/models/spot_detection_cnn.pth,sha256=Np7vpPJIbKQmuKY0Hx-4IkeEDsnks_QEgs7TqaYgZmI,8468580
|
|
10
9
|
microlive/gui/__init__.py,sha256=tB-CdDC7x5OwYFAQxLOUvfVnUThaXKXVRsB68YP0Y6Q,28
|
|
11
|
-
microlive/gui/app.py,sha256=
|
|
10
|
+
microlive/gui/app.py,sha256=bloU7fOVHB09SCtpmRMtoPzEwE64zGgI2SVRsQD6Hug,800266
|
|
12
11
|
microlive/gui/main.py,sha256=b66W_2V-pclGKOozfs75pwrCGbL_jkVU3kFt8RFMZIc,2520
|
|
13
12
|
microlive/gui/micro_mac.command,sha256=TkxYOO_5A2AiNJMz3_--1geBYfl77THpOLFZnV4J2ac,444
|
|
14
13
|
microlive/gui/micro_windows.bat,sha256=DJUKPhDbCO4HToLwSMT-QTYRe9Kr1wn5A2Ijy2klIrw,773
|
|
@@ -21,8 +20,9 @@ microlive/utils/__init__.py,sha256=metAf2zPS8w23d8dyM7-ld1ovrOKBdx3y3zu5IVrzIg,5
|
|
|
21
20
|
microlive/utils/device.py,sha256=tcPMU8UiXL-DuGwhudUgrbjW1lgIK_EUKIOeOn0U6q4,2533
|
|
22
21
|
microlive/utils/model_downloader.py,sha256=EruviTEh75YBekpznn1RZ1Nj8lnDmeC4TKEnFLOow6Y,9448
|
|
23
22
|
microlive/utils/resources.py,sha256=Jz7kPI75xMLCBJMyX7Y_3ixKi_UgydfQkF0BlFtLCKs,1753
|
|
24
|
-
microlive
|
|
25
|
-
microlive-1.0.
|
|
26
|
-
microlive-1.0.
|
|
27
|
-
microlive-1.0.
|
|
28
|
-
microlive-1.0.
|
|
23
|
+
microlive/data/models/spot_detection_cnn.pth,sha256=Np7vpPJIbKQmuKY0Hx-4IkeEDsnks_QEgs7TqaYgZmI,8468580
|
|
24
|
+
microlive-1.0.15.dist-info/METADATA,sha256=s3CJducpiRGKEiKt6iodAbjo72wZGO1fOQBBU6Jkmb0,12434
|
|
25
|
+
microlive-1.0.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
26
|
+
microlive-1.0.15.dist-info/entry_points.txt,sha256=Zqp2vixyD8lngcfEmOi8fkCj7vPhesz5xlGBI-EubRw,54
|
|
27
|
+
microlive-1.0.15.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
28
|
+
microlive-1.0.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|