diff --git a/cellacdc/_warnings.py b/cellacdc/_warnings.py
index e313b347..4d6ee500 100644
--- a/cellacdc/_warnings.py
+++ b/cellacdc/_warnings.py
@@ -202,6 +202,34 @@ def warnPromptSegmentPointsLayerNotInit(qparent=None):
)
return msg.cancel
+def warnNoIDsInS(qparent=None):
+ from cellacdc import widgets
+ txt = html_utils.paragraph(f"""
+ None of the IDs present at this frame
+ are in 'S' phase.
+ This tool can be used only for mother-bud pairs.
+ Thank you for your patience!
+ """)
+ msg = widgets.myMessageBox(wrapText=False)
+ msg.warning(
+ qparent, 'No cells in "S" phase', txt,
+ )
+ return msg.cancel
+
+def warnSelectedIDisNotInS(ID, qparent=None):
+ from cellacdc import widgets
+ txt = html_utils.paragraph(f"""
+ The selected ID {ID} cell cycle stage is not 'S'!
+ Make sure you are hovering a cell ID in 'S' (mother of bud),
+ when activating this mode.
+ Thank you for your patience!
+ """)
+ msg = widgets.myMessageBox(wrapText=False)
+ msg.warning(
+ qparent, 'Selected ID not in S', txt,
+ )
+ return msg.cancel
+
def warnPromptSegmentModelNotInit(qparent=None):
from cellacdc import widgets
txt = html_utils.paragraph(f"""
diff --git a/cellacdc/cca_functions.py b/cellacdc/cca_functions.py
index cd87389b..2ca15a5a 100755
--- a/cellacdc/cca_functions.py
+++ b/cellacdc/cca_functions.py
@@ -864,7 +864,41 @@ def add_generation_num_of_relative_ID(
acdc_df_with_col = acdc_df_by_frame_i.reset_index()
return acdc_df_with_col
+
+def get_IDs_gen_num_will_divide(cca_df):
+ """Get a list of (ID, gen_num) of cells that require `will_divide`>0
+
+ Parameters
+ ----------
+ cca_df : pd.DataFrame
+ DataFrame with cc annotations for every frame and Cell_ID
+ Returns
+ -------
+ list of tuples
+ List of (ID, gen_num) of cells that require `will_divide`>0
+ """
+
+ cca_df_buds = cca_df.query('relationship == "bud"')
+ IDs_gen_num_will_divide = []
+ for budID, bud_cca_df in cca_df_buds.groupby('Cell_ID'):
+ all_gen_nums = cca_df.query(f'Cell_ID == {budID}')['generation_num']
+ if not (all_gen_nums > 0).any():
+ # bud division is annotated in the future
+ continue
+
+ mothID = int(bud_cca_df['relative_ID'].iloc[0])
+ first_frame_bud = bud_cca_df['frame_i'].iloc[0]
+ gen_num_moth = cca_df.query(
+ f'(frame_i == {first_frame_bud}) & (Cell_ID == {mothID})'
+ )['generation_num'].iloc[0]
+
+ IDs_gen_num_will_divide.append((mothID, gen_num_moth))
+ IDs_gen_num_will_divide.append((budID, 0))
+
+ return IDs_gen_num_will_divide
+
+
def get_IDs_gen_num_will_divide_wrong(global_cca_df):
"""Get a list of (ID, gen_num) of cells whose `will_divide`>0 but the
next generation does not exist (i.e., `will_divide` is wrong)
diff --git a/cellacdc/docs/source/tooltips.rst b/cellacdc/docs/source/tooltips.rst
index 8a040489..8c169ab7 100644
--- a/cellacdc/docs/source/tooltips.rst
+++ b/cellacdc/docs/source/tooltips.rst
@@ -159,6 +159,12 @@
:height: 16px
:width: 16px
+.. |annotateSingleMotherBudPairButton| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/refs/heads/main/cellacdc/resources/icons/lock_id_annotate_future.svg
+ :target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/lock_id_annotate_future.svg
+ :alt: annotateSingleMotherBudPairButton icon
+ :height: 16px
+ :width: 16px
+
.. |segmentToolAction| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/refs/heads/main/cellacdc/resources/icons/segment.svg
:target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/segment.svg
:alt: segmentToolAction icon
@@ -485,6 +491,12 @@ Edit tools: Cell cycle analysis
* **Automatically assign bud to mother (** |assignBudMothAutoAction| **):** Automatically assign buds to mothers using YeastMate.
* **Manually edit cell cycle annotations table (** |editCcaToolAction| **"Ctrl+Shift+P"):** Manually edit cell cycle annotations table.
* **Re-initialize cell cycle annotations table (** |reInitCcaAction| **):** Re-initialize cell cycle annotations table from this frame onward. NOTE: This will erase all the already annotated future frames information (from the current session not the saved information).
+* **Annotate one mother-bud pair at the time (** |annotateSingleMotherBudPairButton| **"Y"):**
+ 1. Activate to annotate a single mother-bud pair at the time.
+ 2. Annotate past and future frames
+ 3. Deactivate to go back to the frame you were annotating before activating this tool.
+
+ NOTE: When annotating future frames, the other cells will not be displayed and they will be ignored.
Edit tools: Normal division: Lineage tree
-----------------------------------------
diff --git a/cellacdc/gui.py b/cellacdc/gui.py
index 0a448430..4b4374ef 100755
--- a/cellacdc/gui.py
+++ b/cellacdc/gui.py
@@ -1039,6 +1039,25 @@ def gui_createToolBars(self):
self.checkableQButtonsGroup.addButton(self.setIsHistoryKnownButton)
self.functionsNotTested3D.append(self.setIsHistoryKnownButton)
+ self.annotateSingleMotherBudPairButton = QToolButton(self)
+ self.annotateSingleMotherBudPairButton.setIcon(
+ QIcon(":lock_id_annotate_future.svg")
+ )
+ self.annotateSingleMotherBudPairButton.setCheckable(True)
+ self.annotateSingleMotherBudPairButton.setShortcut('Y')
+ self.annotateSingleMotherBudPairButton.setVisible(False)
+ self.annotateSingleMotherBudPairButton.action = ccaToolBar.addWidget(
+ self.annotateSingleMotherBudPairButton
+ )
+ self.checkableButtons.append(self.annotateSingleMotherBudPairButton)
+ self.widgetsWithShortcut['Annotate one mother-bud pair at the time'] = (
+ self.annotateSingleMotherBudPairButton
+ )
+ self.checkableQButtonsGroup.addButton(
+ self.annotateSingleMotherBudPairButton
+ )
+ self.functionsNotTested3D.append(self.annotateSingleMotherBudPairButton)
+
ccaToolBar.addAction(self.assignBudMothAutoAction)
ccaToolBar.addAction(self.editCcaToolAction)
ccaToolBar.addAction(self.reInitCcaAction)
@@ -1589,6 +1608,7 @@ def autoSaveWorkerTimerCallback(self, worker, posData):
worker._enqueue(posData)
def autoSaveWorkerDone(self):
+ self.logger.info('Autosaving done.')
self.setStatusBarLabel(log=False)
def ccaCheckerWorkerDone(self):
@@ -3439,9 +3459,13 @@ def gui_connectEditActions(self):
self.whitelistIDsToolbar.sigViewOGIDs.connect(self.whitelistViewOGIDs)
- self.whitelistIDsToolbar.sigAddNewIDs.connect(self.whitelistAddNewIDsToggled)
+ self.whitelistIDsToolbar.sigAddNewIDs.connect(
+ self.whitelistAddNewIDsToggled
+ )
- self.whitelistIDsToolbar.sigLoadOGLabs.connect(self.whitelistLoadOGLabs_cb)
+ self.whitelistIDsToolbar.sigLoadOGLabs.connect(
+ self.whitelistLoadOGLabs_cb
+ )
self.whitelistIDsToolbar.sigTrackOGagainstPreviousFrame.connect(
self.whitelistTrackOGagainstPreviousFrame_cb
@@ -3463,6 +3487,10 @@ def gui_connectEditActions(self):
self.addScaleBarAction.toggled.connect(self.addScaleBar)
self.addTimestampAction.toggled.connect(self.addTimestamp)
self.saveLabColormapAction.triggered.connect(self.saveLabelsColormap)
+
+ self.annotateSingleMotherBudPairButton.toggled.connect(
+ self.annotateSingleMotherBudPair_cb
+ )
self.enableSmartTrackAction.toggled.connect(self.enableSmartTrack)
# Brush/Eraser size action
@@ -3474,18 +3502,26 @@ def gui_connectEditActions(self):
self.modeComboBox.activated.connect(self.clearComboBoxFocus)
self.equalizeHistPushButton.toggled.connect(self.equalizeHist)
- self.editOverlayColorAction.triggered.connect(self.toggleOverlayColorButton)
- self.editTextIDsColorAction.triggered.connect(self.toggleTextIDsColorButton)
+ self.editOverlayColorAction.triggered.connect(
+ self.toggleOverlayColorButton
+ )
+ self.editTextIDsColorAction.triggered.connect(
+ self.toggleTextIDsColorButton
+ )
self.overlayColorButton.sigColorChanging.connect(self.changeOverlayColor)
self.overlayColorButton.sigColorChanged.connect(self.saveOverlayColor)
- self.textIDsColorButton.sigColorChanging.connect(self.updateTextAnnotColor)
+ self.textIDsColorButton.sigColorChanging.connect(
+ self.updateTextAnnotColor
+ )
self.textIDsColorButton.sigColorChanged.connect(self.saveTextIDsColors)
self.setMeasurementsAction.triggered.connect(self.showSetMeasurements)
self.addCustomMetricAction.triggered.connect(self.addCustomMetric)
self.addCombineMetricAction.triggered.connect(self.addCombineMetric)
- self.labelsGrad.colorButton.sigColorChanging.connect(self.updateBkgrColor)
+ self.labelsGrad.colorButton.sigColorChanging.connect(
+ self.updateBkgrColor
+ )
self.labelsGrad.colorButton.sigColorChanged.connect(self.saveBkgrColor)
self.labelsGrad.sigGradientChangeFinished.connect(self.updateLabelsCmap)
self.labelsGrad.sigGradientChanged.connect(self.ticksCmapMoved)
@@ -13253,6 +13289,130 @@ def copyLostObjContour_cb(self, checked):
self.lostObjImage = np.zeros_like(self.currentLab2D)
self.updateLostContoursImage(0)
+ def getHoveredMotherBudIDs(self):
+ posData = self.data[self.pos_i]
+
+ hoverID = self.getLastHoveredID()
+ try:
+ ccs = posData.cca_df.loc[hoverID, 'cell_cycle_stage']
+ except Exception as err:
+ ccs = 'G1'
+
+ cca_df = posData.cca_df
+ cca_df_S = cca_df[cca_df['cell_cycle_stage'] == 'S']
+ IDs_in_S = cca_df_S.index.to_list()
+
+ if not IDs_in_S:
+ _warnings.warnNoIDsInS(qparent=self)
+ return
+
+ while ccs != 'S':
+ win = apps.QLineEditDialog(
+ title='Selected ID not in S',
+ msg='The cell cycle stage of the selected ID '
+ 'is not "S".
'
+ 'Enter the ID (mother or bud) '
+ 'that you want to annotate.',
+ parent=self,
+ isInteger=True,
+ allowedValues=IDs_in_S,
+ defaultTxt=IDs_in_S[0]
+ )
+ win.exec_()
+ if win.cancel:
+ return
+
+ hoverID = win.EntryID
+ ccs = cca_df.loc[hoverID, 'cell_cycle_stage']
+
+ relationship = posData.cca_df.loc[hoverID, 'relationship']
+ relative_ID = posData.cca_df.loc[hoverID, 'relative_ID']
+ mothID = hoverID if relationship == 'mother' else relative_ID
+ budID = hoverID if relationship == 'bud' else relative_ID
+
+ return int(mothID), int(budID)
+
+ def annotateSingleMotherBudPair_cb(self, checked):
+ posData = self.data[self.pos_i]
+ self.modeComboBox.setDisabled(checked)
+ if checked:
+ self.store_data()
+
+ self.annotateSingleMothBudPairState = {}
+ mothIDbudID = self.getHoveredMotherBudIDs()
+
+ if mothIDbudID is None:
+ self.annotateSingleMotherBudPairButton.setChecked(False)
+ return
+
+ mothID, budID = mothIDbudID
+
+ self.logger.info(
+ 'Setting annotation mode for single mother-bud pair = '
+ f'{(mothID, budID)}, at frame n. {posData.frame_i+1}'
+ )
+
+ self.annotateSingleMothBudPairState['doWarnLostObj'] = (
+ self.warnLostCellsAction.isChecked()
+ )
+ self.annotateSingleMothBudPairState['doAnnotateLostObjs'] = (
+ self.annotLostObjsToggle.isChecked()
+ )
+
+ self.annotateSingleMothBudPairState['mother_ID'] = (
+ mothID
+ )
+ self.annotateSingleMothBudPairState['bud_ID'] = (
+ budID
+ )
+ self.annotateSingleMothBudPairState['frame_i_to_restore'] = (
+ posData.frame_i
+ )
+ self.annotateSingleMothBudPairState['last_cca_frame_i'] = (
+ self.navigateScrollBar.maximum()-1
+ )
+
+ self.warnLostCellsAction.setChecked(False)
+ self.annotLostObjsToggle.setChecked(False)
+
+ self.ax1.sigRangeChanged.connect(self.highlightManualAnnotMode)
+ self.ax1.setHighlighted(True, color='green')
+ else:
+ frame_to_restore = self.annotateSingleMothBudPairState.get(
+ 'frame_i_to_restore'
+ )
+ if frame_to_restore is None:
+ return
+
+ self.store_single_mother_bud_pair_data()
+
+ self.logger.info(
+ f'Restoring view to frame n. {posData.frame_i+1}...'
+ )
+ posData.frame_i = frame_to_restore
+ last_cca_frame_i_to_restore = (
+ self.annotateSingleMothBudPairState['last_cca_frame_i']
+ )
+ self.annotateSingleMotherBudPairRestoreLastCcaFrame(
+ last_cca_frame_i_to_restore
+ )
+ self.get_data()
+ self.updateAllImages()
+ self.updateScrollbars()
+ self.ax1.sigRangeChanged.disconnect()
+ self.ax1.setHighlighted(False)
+
+ self.warnLostCellsAction.setChecked(
+ self.annotateSingleMothBudPairState['doWarnLostObj']
+ )
+ self.annotLostObjsToggle.setChecked(
+ self.annotateSingleMothBudPairState['doAnnotateLostObjs']
+ )
+
+ self.annotateSingleMothBudPairState = {}
+
+ QTimer.singleShot(150, self.autoRange)
+
def manualAnnotPast_cb(self, checked):
posData = self.data[self.pos_i]
if checked:
@@ -13347,18 +13507,32 @@ def highlightManualAnnotMode(self, viewBox, viewRange):
self.ax1.setHighlighted(True)
def updateHighlightedAxis(self):
- if not self.manualAnnotPastButton.isChecked():
- return
+ color = None
+ if self.manualAnnotPastButton.isChecked():
+ frame_to_restore = self.manualAnnotState.get('frame_i_to_restore')
+ posData = self.data[self.pos_i]
+ if posData.frame_i == frame_to_restore:
+ color = 'green'
+ elif posData.frame_i < frame_to_restore:
+ color = 'gold'
+ else:
+ color = 'red'
- frame_to_restore = self.manualAnnotState.get('frame_i_to_restore')
- posData = self.data[self.pos_i]
- if posData.frame_i == frame_to_restore:
- color = 'green'
- elif posData.frame_i < frame_to_restore:
- color = 'gold'
- else:
- color = 'red'
+ if self.annotateSingleMotherBudPairButton.isChecked():
+ frame_to_restore = self.annotateSingleMothBudPairState.get(
+ 'frame_i_to_restore'
+ )
+ posData = self.data[self.pos_i]
+ if posData.frame_i == frame_to_restore:
+ color = 'green'
+ elif posData.frame_i < frame_to_restore:
+ color = 'gold'
+ else:
+ color = 'red'
+ if color is None:
+ return
+
self.ax1.setHighlightingRectItemsColor(color)
def updateLostNewCurrentIDs(self):
@@ -18810,8 +18984,16 @@ def manualAnnotRestoreLastTrackedFrame(self, last_tracked_i_to_restore):
posData.allData_li[frame_i] = data_frame_i
- self.navigateScrollBar.setMaximum(last_tracked_i_to_restore+1)
- self.navSpinBox.setMaximum(last_tracked_i_to_restore+1)
+ posData.last_tracked_i = last_tracked_i_to_restore
+ self.setNavigateScrollBarMaximum()
+
+ def annotateSingleMotherBudPairRestoreLastCcaFrame(
+ self, cca_frame_i_to_restore
+ ):
+ if self.navigateScrollBar.maximum()-1 <= cca_frame_i_to_restore:
+ return
+
+ self.resetCcaFuture(cca_frame_i_to_restore+1)
def setNavigateScrollBarMaximum(self):
posData = self.data[self.pos_i]
@@ -20686,6 +20868,8 @@ def initGlobalAttr(self):
self.timestampDialog = None
self.scaleBarDialog = None
self.countObjsWindow = None
+
+ self.annotateSingleMothBudPairState = {}
self.initLabelRoiModelDialog = None
# Second channel used by cellpose
@@ -21068,6 +21252,40 @@ def store_manual_annot_data(
# data_frame_i['manually_edited_lab']['zoom_slice'] = zoom_slice
+ def store_single_mother_bud_pair_data(self, cca_df=None):
+ posData = self.data[self.pos_i]
+ data_frame_i = posData.allData_li[posData.frame_i]
+
+ if cca_df is None:
+ cca_df = data_frame_i['acdc_df']
+
+ if cca_df is None:
+ return
+
+ mothID = self.annotateSingleMothBudPairState.get('mother_ID')
+ if mothID is None:
+ return
+
+ budID = self.annotateSingleMothBudPairState['bud_ID']
+ single_moth_bud_pair_cca = cca_df.loc[[mothID, budID]].copy()
+ stored_moth_bud_pairs_cca = data_frame_i.get('moth_bud_pairs_cca')
+ if stored_moth_bud_pairs_cca is None:
+ data_frame_i['moth_bud_pairs_cca'] = single_moth_bud_pair_cca
+ else:
+ if mothID in stored_moth_bud_pairs_cca.index:
+ stored_moth_bud_pairs_cca = stored_moth_bud_pairs_cca.drop(
+ index=mothID
+ )
+
+ if budID in stored_moth_bud_pairs_cca.index:
+ stored_moth_bud_pairs_cca = stored_moth_bud_pairs_cca.drop(
+ index=budID
+ )
+
+ data_frame_i['moth_bud_pairs_cca'] = pd.concat(
+ [stored_moth_bud_pairs_cca, single_moth_bud_pair_cca],
+ ).drop_duplicates()
+
@exception_handler
def store_data(
self, pos_i=None, enforce=True, debug=False, mainThread=True,
@@ -21164,6 +21382,7 @@ def store_data(
if not mainThread:
self.pointsLayerDataToDf(posData)
+
self.store_cca_df(
pos_i=pos_i, mainThread=mainThread, autosave=autosave,
store_cca_df_copy=store_cca_df_copy
@@ -21395,7 +21614,7 @@ def checkCcaPastFramesNewIDs(self):
# Remove IDs found in past frames from new_IDs list
newIDs = np.array(posData.new_IDs, dtype=np.uint32)
- mask_index = np.in1d(newIDs, cca_df_i.index)
+ mask_index = np.isin(newIDs, cca_df_i.index)
posData.new_IDs = list(newIDs[~mask_index])
if not posData.new_IDs:
return found_cca_df_IDs
@@ -21526,6 +21745,28 @@ def _getCcaCostMatrix(
return cost
+ def getSingleMotherBudPairCca_df(self):
+ if not self.annotateSingleMotherBudPairButton.isChecked():
+ return
+
+ posData = self.data[self.pos_i]
+ start_frame_i = (
+ self.annotateSingleMothBudPairState['frame_i_to_restore']
+ )
+
+ if posData.frame_i <= start_frame_i:
+ # Past frame --> no need to get cca_df
+ return
+
+ mothID = self.annotateSingleMothBudPairState['mother_ID']
+ budID = self.annotateSingleMothBudPairState['bud_ID']
+
+ # Get previous dataframe
+ acdc_df = posData.allData_li[posData.frame_i-1]['acdc_df']
+ prev_cca_df = acdc_df[self.cca_df_colnames].loc[[mothID, budID]].copy()
+
+ return prev_cca_df
+
def autoCca_df(self, enforceAll=False):
"""
Assign each bud to a mother with scipy linear sum assignment
@@ -21551,6 +21792,13 @@ def autoCca_df(self, enforceAll=False):
proceed = self.warnFrameNeverVisitedSegmMode()
return notEnoughG1Cells, proceed
+ # Get temporary cca_df with single mother-bud pair mode active
+ cca_df = self.getSingleMotherBudPairCca_df()
+ if cca_df is not None:
+ posData.cca_df = cca_df
+ self.store_single_mother_bud_pair_data(cca_df=cca_df)
+ return notEnoughG1Cells, proceed
+
# Determine if this is the last visited frame for repeating
# bud assignment on non manually correct (corrected_on_frame_i>0) buds.
# The idea is that the user could have assigned division on a cell
@@ -21561,15 +21809,33 @@ def autoCca_df(self, enforceAll=False):
curr_df, enforceAll=enforceAll
)
- frameAlreadyAnnotated = (
+ # Get previous dataframe
+ acdc_df = posData.allData_li[posData.frame_i-1]['acdc_df']
+ prev_cca_df = acdc_df[self.cca_df_colnames].copy()
+
+ # When using "single mother-bud pair annotation" mode,
+ # there might be some IDs that do not have annotations yet
+ # --> not fully annotated frame
+ missingIDs = []
+ if posData.cca_df is not None:
+ missingIDs = [
+ ID for ID in posData.IDs
+ if ID not in posData.cca_df.index
+ and ID in prev_cca_df.index
+ ]
+
+ frameAlreadyFullyAnnotated = (
posData.cca_df is not None
and not enforceAll
and not isLastVisitedAgain
+ and not missingIDs
)
# Use stored cca_df and do not modify it with automatic stuff
- if frameAlreadyAnnotated:
+ if frameAlreadyFullyAnnotated:
return notEnoughG1Cells, proceed
+ self.updateLostNewCurrentIDs()
+
# Keep only correctedAssignIDs if requested
# For the last visited frame we perform assignment again only on
# IDs where we didn't manually correct assignment
@@ -21595,22 +21861,24 @@ def autoCca_df(self, enforceAll=False):
notEnoughG1Cells = False
proceed = False
return notEnoughG1Cells, proceed
-
- # Get previous dataframe
- acdc_df = posData.allData_li[posData.frame_i-1]['acdc_df']
- prev_cca_df = acdc_df[self.cca_df_colnames].copy()
-
+
if posData.cca_df is None:
posData.cca_df = prev_cca_df.copy()
else:
posData.cca_df = curr_df[self.cca_df_colnames].copy()
+ if missingIDs:
+ # Add missing IDs from previous cca_df
+ posData.cca_df.loc[missingIDs] = prev_cca_df.loc[missingIDs]
+
+ posData.cca_df = posData.cca_df.dropna()
+
# concatenate new IDs found in past frames (before frame_i-1)
if found_cca_df_IDs is not None:
cca_df = pd.concat([posData.cca_df, *found_cca_df_IDs])
unique_idx = ~cca_df.index.duplicated(keep='first')
posData.cca_df = cca_df[unique_idx]
-
+
# If there are no new IDs we are done
if not posData.new_IDs:
proceed = True
@@ -21919,7 +22187,7 @@ def set_2Dlab(self, lab2D):
else:
posData.lab = lab2D
- def get_labels(
+ def get_labels_array(
self,
from_store=False,
frame_i=None,
@@ -22013,7 +22281,7 @@ def _get_editID_info(self, df):
for row in manually_edited_df.itertuples()
]
return editID_info
-
+
def apply_manual_edits_to_lab_if_needed(self, lab):
posData = self.data[self.pos_i]
data_frame_i = posData.allData_li[posData.frame_i]
@@ -22057,7 +22325,7 @@ def _get_data_unvisited(self, posData, debug=False, lin_tree_init=True,):
elif str(self.modeComboBox.currentText()) == 'Normal division: Lineage tree':
# Warn that we are visiting a frame that was never segm-checked
- # on cell cycle analysis mode
+ # on Normal division: Lineage tree mode
msg = widgets.myMessageBox()
txt = html_utils.paragraph(
'Segmentation and Tracking was never checked from '
@@ -22073,10 +22341,8 @@ def _get_data_unvisited(self, posData, debug=False, lin_tree_init=True,):
return proceed_cca, never_visited
# Requested frame was never visited before. Load from HDD
- labels = self.get_labels()
- posData.lab = self.apply_manual_edits_to_lab_if_needed(
- labels
- )
+ lab = self.get_labels_array()
+ posData.lab = self.apply_manual_edits_to_lab_if_needed(lab)
posData.rp = skimage.measure.regionprops(posData.lab)
self.setManualBackgroundLab()
@@ -22119,7 +22385,7 @@ def _get_data_unvisited(self, posData, debug=False, lin_tree_init=True,):
def _get_data_visited(self, posData, debug=False, lin_tree_init=True,):
# Requested frame was already visited. Load from RAM.
never_visited = False
- posData.lab = self.get_labels(from_store=True)
+ posData.lab = self.get_labels_array(from_store=True)
posData.rp = skimage.measure.regionprops(posData.lab)
df = posData.allData_li[posData.frame_i]['acdc_df']
if df is None:
@@ -22340,8 +22606,8 @@ def initCca(self):
'No, stay on current frame')
)
if goToFrameButton == msg.clickedButton:
- self.addMissingIDs_cca_df(posData)
- self.store_cca_df()
+ # self.addMissingIDs_cca_df(posData)
+ # self.store_cca_df()
msg = 'Looking good!'
self.last_cca_frame_i = last_cca_frame_i
posData.frame_i = last_cca_frame_i
@@ -22605,7 +22871,9 @@ def resetWillDivideInfo(self):
if global_cca_df is None:
return
+ global_cca_df.to_csv('global_cca_df_with_single_moth_bud_pair.csv')
global_cca_df = load._fix_will_divide(global_cca_df)
+
self.storeFromConcatCcaDf(global_cca_df)
def ccaCheckerStopChecking(self):
@@ -22738,9 +23006,8 @@ def get_cca_df(self, frame_i=None, return_df=False, debug=False):
cca_df = None
i = posData.frame_i if frame_i is None else frame_i
df = posData.allData_li[i]['acdc_df']
- if df is not None:
- if 'cell_cycle_stage' in df.columns:
- cca_df = df[self.cca_df_colnames].copy()
+ if df is not None and 'cell_cycle_stage' in df.columns:
+ cca_df = df[self.cca_df_colnames].copy()
if cca_df is None and self.isSnapshot:
cca_df = self.getBaseCca_df()
@@ -22748,6 +23015,12 @@ def get_cca_df(self, frame_i=None, return_df=False, debug=False):
if cca_df is not None:
cca_df = cca_df.dropna()
+ else:
+ moth_bud_pairs_cca = (
+ posData.allData_li[i].get('moth_bud_pairs_cca', None)
+ )
+ if moth_bud_pairs_cca is not None:
+ cca_df = moth_bud_pairs_cca
if return_df:
return cca_df
@@ -22888,6 +23161,9 @@ def store_cca_df(
if store_cca_df_copy and cca_df is not None:
posData.allData_li[i]['cca_df'] = cca_df.copy()
+ if self.annotateSingleMotherBudPairButton.isChecked():
+ self.store_single_mother_bud_pair_data(cca_df=cca_df)
+
if autosave:
self.enqAutosave()
self.enqCcaIntegrityChecker()
@@ -28401,7 +28677,7 @@ def getPrevFrameIDs(self, current_frame_i=None):
return prevIDs
# IDs in previous frame were not stored --> load prev lab from HDD
- prev_lab = self.get_labels(
+ prev_lab = self.get_labels_array(
from_store=False,
frame_i=prev_frame_i,
return_copy=False
@@ -28457,6 +28733,24 @@ def setTitleText(
self, lost_IDs=None, new_IDs=None, IDs_with_holes=None,
tracked_lost_IDs=None
):
+ if self.annotateSingleMotherBudPairButton.isChecked():
+ mothID = self.annotateSingleMothBudPairState.get(
+ 'mother_ID'
+ )
+ budID = self.annotateSingleMothBudPairState.get(
+ 'bud_ID'
+ )
+ frame_to_restore = self.annotateSingleMothBudPairState.get(
+ 'frame_i_to_restore'
+ )
+ txt = (
+ f'Annotating mother-bud pair {(mothID, budID)} '
+ f'since frame n. {frame_to_restore+1}'
+ )
+ htmlTxt = f'{txt}'
+ self.titleLabel.setText(htmlTxt)
+ return
+
if self.manualAnnotPastButton.isChecked():
lockedID = self.editIDspinbox.value()
frame_to_restore = self.manualAnnotState.get('frame_i_to_restore')
@@ -29000,7 +29294,7 @@ def getTrackedLostIDs(self, prev_lab=None, IDs_in_frames=None, frame_i=None):
frame_i = posData.frame_i
if prev_lab is None:
- prev_lab = self.get_labels(
+ prev_lab = self.get_labels_array(
from_store=True,
frame_i=posData.frame_i-1,
return_existing=False,
diff --git a/cellacdc/load.py b/cellacdc/load.py
index 2e72a546..91356640 100755
--- a/cellacdc/load.py
+++ b/cellacdc/load.py
@@ -515,8 +515,7 @@ def _fix_corrected_assignment_i(acdc_df: pd.DataFrame):
def _fix_will_divide(acdc_df):
"""Resetting annotaions in GUI sometimes does not fully reset `will_divide`
- column. Here we set `will_divide` back to 0 for those cells whose
- next generation does not exist (division was not annotated)
+ column. Here we reset `will_divide`
Parameters
----------
@@ -532,19 +531,22 @@ def _fix_will_divide(acdc_df):
if 'cell_cycle_stage' not in acdc_df.columns:
return acdc_df
- required_cols = ['frame_i', 'Cell_ID', 'generation_num', 'will_divide']
+ required_cols = [
+ 'frame_i', 'Cell_ID', 'generation_num', 'will_divide', 'relationship',
+ 'relative_ID'
+ ]
cca_df_mask = ~acdc_df['cell_cycle_stage'].isna()
cca_df = acdc_df[cca_df_mask].reset_index()[required_cols]
- IDs_will_divide_wrong = (
- cca_functions.get_IDs_gen_num_will_divide_wrong(cca_df)
- )
- if not IDs_will_divide_wrong:
+ IDs_gen_num_will_divide = cca_functions.get_IDs_gen_num_will_divide(cca_df)
+
+ if not IDs_gen_num_will_divide:
return acdc_df
- cca_df = cca_df.reset_index().set_index(['Cell_ID', 'generation_num'])
- cca_df.loc[IDs_will_divide_wrong, 'will_divide'] = 0
+ cca_df['will_divide'] = 0.0
+ cca_df = cca_df.reset_index().set_index(['Cell_ID', 'generation_num'])
+ cca_df.loc[IDs_gen_num_will_divide, 'will_divide'] = 1.0
cca_df = cca_df.reset_index()
acdc_df = acdc_df.reset_index()
@@ -554,6 +556,21 @@ def _fix_will_divide(acdc_df):
cca_df_index = cca_df_mask[cca_df_mask].index
acdc_df.loc[cca_df_index, 'will_divide'] = cca_df['will_divide']
+ # IDs_will_divide_wrong = (
+ # cca_functions.get_IDs_gen_num_will_divide_wrong(cca_df)
+ # )
+ # if IDs_will_divide_wrong:
+ # cca_df = cca_df.reset_index().set_index(['Cell_ID', 'generation_num'])
+ # cca_df.loc[IDs_will_divide_wrong, 'will_divide'] = 0
+ # cca_df = cca_df.reset_index()
+ # acdc_df = acdc_df.reset_index()
+
+ # cca_df = cca_df.set_index(['frame_i', 'Cell_ID'])
+ # acdc_df = acdc_df.set_index(['frame_i', 'Cell_ID'])
+
+ # cca_df_index = cca_df_mask[cca_df_mask].index
+ # acdc_df.loc[cca_df_index, 'will_divide'] = cca_df['will_divide']
+
return acdc_df
def _add_missing_columns(acdc_df):
diff --git a/cellacdc/myutils.py b/cellacdc/myutils.py
index 8fad6fdd..5806ea69 100644
--- a/cellacdc/myutils.py
+++ b/cellacdc/myutils.py
@@ -5001,7 +5001,8 @@ def get_empty_stored_data_dict():
'rois': [], 'delMasks': [], 'delIDsROI': [], 'state': []
},
'IDs': [],
- 'manually_edited_lab': {'lab': {}, 'zoom_slice': None}
+ 'manually_edited_lab': {'lab': {}, 'zoom_slice': None},
+ 'single_moth_bud_pair_cca': None,
}
def iterate_along_axes(arr, axes, arr_ndim=None):
diff --git a/cellacdc/workers.py b/cellacdc/workers.py
index 917bca9d..7dff64e0 100755
--- a/cellacdc/workers.py
+++ b/cellacdc/workers.py
@@ -842,7 +842,7 @@ def __init__(self, mutex, waitCond, savedSegmData):
self.stopSaving = False
self.isSaving = False
self.isPaused = False
- self.dataQ = deque(maxlen=5)
+ self.dataQ = deque(maxlen=2)
self.isAutoSaveON = False
self.isAutoSaveAnnotON = True
self.debug = False