From 4a49711b3f8beaa9811ef25ed837438b07e89214 Mon Sep 17 00:00:00 2001 From: Robert Cudmore Date: Thu, 16 Jan 2025 12:32:39 -0800 Subject: [PATCH] before merge j-dev 20250116 --- .../interface2/core/search_widget.py | 13 ++ src/pymapmanager/interface2/mainMenus.py | 44 ++++--- .../interface2/mapWidgets/mapTableWidget.py | 43 ++++++- .../interface2/mapWidgets/mapWidget.py | 15 ++- .../interface2/pyMapManagerApp2.py | 68 +++++++--- .../interface2/runInterfaceBob.py | 4 +- .../interface2/stackWidgets/base/mmWidget2.py | 116 +++++++++++++++++- .../stackWidgets/imagePlotWidget2.py | 26 +++- .../interface2/stackWidgets/stackWidget2.py | 108 ++-------------- src/pymapmanager/stack.py | 3 - src/pymapmanager/timeseriesCore.py | 28 +++-- 11 files changed, 306 insertions(+), 162 deletions(-) diff --git a/src/pymapmanager/interface2/core/search_widget.py b/src/pymapmanager/interface2/core/search_widget.py index 6d68371..5379396 100644 --- a/src/pymapmanager/interface2/core/search_widget.py +++ b/src/pymapmanager/interface2/core/search_widget.py @@ -169,6 +169,19 @@ def data(self, index, role) -> str: Returns the data stored under the given role for the item referred to by the index. (in str form) """ + + # abb adding segment color + if role == Qt.BackgroundRole: + row = index.row() + col = index.column() + # Get the corresponding row within the actual dataframe + # They could be different values due to deleting + rowLabel = self._data.index.tolist()[row] + # logger.warning(f'row:{row} rowLabel:{rowLabel}') + if col==0 and rowLabel == 3: + _color = QtGui.QColor('#38ff2a') + return QtGui.QBrush(_color) + # print("data", self.rowCount(None)) # print("role: ", type(role)) if role == Qt.DisplayRole: diff --git a/src/pymapmanager/interface2/mainMenus.py b/src/pymapmanager/interface2/mainMenus.py index 137f541..cdc2e26 100644 --- a/src/pymapmanager/interface2/mainMenus.py +++ b/src/pymapmanager/interface2/mainMenus.py @@ -3,8 +3,9 @@ from qtpy import QtWidgets from pymapmanager._logger import logger, setLogLevel -from pymapmanager.interface2.stackWidgets import stackWidget2 -from pymapmanager.interface2.mapWidgets import mapWidget +# from pymapmanager.interface2.stackWidgets import stackWidget2 +from pymapmanager.interface2.stackWidgets.base.mmWidget2 import mmWidget2 +from pymapmanager.interface2.mapWidgets.mapWidget import mapWidget class PyMapManagerMenus: """Main app menus including loaded map and stack widgets. @@ -304,7 +305,7 @@ def _refreshEditMenu(self): # from pymapmanager.interface2.stackWidgets import stackWidget2 frontWindow = self.getApp().getFrontWindow() - if isinstance(frontWindow, (stackWidget2, mapWidget)): + if isinstance(frontWindow, (mmWidget2, mapWidget)): nextUndo = frontWindow.getUndoRedo().nextUndoStr() nextRedo = frontWindow.getUndoRedo().nextRedoStr() enableUndo = frontWindow.getUndoRedo().numUndo() > 0 @@ -418,32 +419,33 @@ def _refreshFileMenu(self): # self.fileMenu.addAction(loadFolderAction) # self.fileMenu.addSeparator() - # abj - enableUndo = False - enableRedo = False - isDirty = False + # enableUndo = False + # enableRedo = False + enableSave = False frontWindow = self.getApp().getFrontWindow() - if isinstance(frontWindow, stackWidget2): - enableUndo = frontWindow.getUndoRedo().numUndo() > 0 - enableRedo = frontWindow.getUndoRedo().numRedo() > 0 - isDirty = frontWindow.getDirty() - logger.info(f"isDirty: {isDirty}") + if isinstance(frontWindow, (mmWidget2, mapWidget)): + if frontWindow.getPath().endswith('.mmap.zip'): + enableSave = False + else: + # enableUndo = frontWindow.getUndoRedo().numUndo() > 0 + # enableRedo = frontWindow.getUndoRedo().numRedo() > 0 + enableSave = frontWindow.getDirty() # save saveFileAction = QtWidgets.QAction("Save", self.getApp()) saveFileAction.setCheckable(False) # setChecked is True by default? saveFileAction.setShortcut("Ctrl+S") # saveFileAction.setEnabled(enableUndo and isDirty) - saveFileAction.setEnabled(isDirty) + saveFileAction.setEnabled(enableSave) saveFileAction.triggered.connect(self.getApp().saveFile) self.fileMenu.addAction(saveFileAction) # save as saveAsFileAction = QtWidgets.QAction("Save As", self.getApp()) saveAsFileAction.setCheckable(False) # setChecked is True by default? - saveAsFileAction.triggered.connect(self.getApp().saveAsFile) + saveAsFileAction.triggered.connect(self.getApp().saveAs) self.fileMenu.addAction(saveAsFileAction) self.fileMenu.addSeparator() @@ -463,16 +465,28 @@ def _refreshFileMenu(self): self.settingsMenu.aboutToShow.connect(self._refreshSettingsMenu) self.fileMenu.addSeparator() - #abj analysisParametersAction = QtWidgets.QAction('App Analysis Parameters', self.getApp()) analysisParametersAction.triggered.connect(self.getApp()._showAnalysisParameters) self.fileMenu.addAction(analysisParametersAction) self.fileMenu.addSeparator() importNewTIFAction = QtWidgets.QAction('Import new TIF (channel)', self.getApp()) + importNewTIFAction.setEnabled(isinstance(frontWindow, mmWidget2)) importNewTIFAction.triggered.connect(self.getApp().importNewTIF) self.fileMenu.addAction(importNewTIFAction) + # open some mapmanagercore sample data (download and store locally with pooch) + self.fileMenu.addSeparator() + self.sampleDataMenu = QtWidgets.QMenu("Sample Data ...") + importList = ['Tiff File Ch1', 'Tiff File Ch2', 'mmap with spines and segments'] + for importType in importList: + loadSampleAction = QtWidgets.QAction(importType, self.getApp()) + loadSampleAction.triggered.connect( + partial(self.getApp().loadSampleData, importType) + ) + self.sampleDataMenu.addAction(loadSampleAction) + self.fileMenu.addMenu(self.sampleDataMenu) + def _refreshOpenRecent(self): """Dynamically generate the open recent stack/map menu. diff --git a/src/pymapmanager/interface2/mapWidgets/mapTableWidget.py b/src/pymapmanager/interface2/mapWidgets/mapTableWidget.py index 5b6c582..0d57471 100644 --- a/src/pymapmanager/interface2/mapWidgets/mapTableWidget.py +++ b/src/pymapmanager/interface2/mapWidgets/mapTableWidget.py @@ -1,12 +1,12 @@ +import os import sys from typing import List, Union # , Callable, Iterator, Optional from qtpy import QtGui, QtCore, QtWidgets +from mapmanagercore import IMPORT_FILE_EXTENSIONS from pymapmanager.interface2.core.search_widget import myQTableView - from pymapmanager.timeseriesCore import TimeSeriesCore - from pymapmanager._logger import logger class mapTableWidget(QtWidgets.QWidget): @@ -29,7 +29,44 @@ def __init__(self, timeSeriesCore : TimeSeriesCore): self.slot_switchMap(timeSeriesCore) - # self._setModel() + self.setAcceptDrops(True) + + def dragEnterEvent(self, event): + """Accept drag/drop of tiff file and append to _timeSeriesCore. + """ + if event.mimeData().hasUrls(): + urlList = event.mimeData().urls() + url = urlList[0] + file_path = url.toLocalFile() + _path, _ext = os.path.splitext(file_path) + if _ext in IMPORT_FILE_EXTENSIONS: + event.acceptProposedAction() + else: + logger.warning(f'did not understand ext "{_ext}" path "{_path}"') + + def dropEvent(self, event): + """When user drops a tiff file, append it to the timeseries. + """ + urlList = event.mimeData().urls() + url = urlList[0] + file_path = url.toLocalFile() + # self.label.setText(f"Dropped file: {file_path}") + # self._timeSeriesCore.loadInNewChannel(file_path) # todo specify append (default to tp 0) + # append the tiff file to the _timeSeriesCore + logger.info(f'file_path:{file_path}') + timePoint = self._timeSeriesCore.numSessions + channel = 0 + name = 'xxx imported channel' + + logger.info('adding new timepoint from tif, timePoint:{timePoint}') + + from mapmanagercore.lazy_geo_pd_images.loader.imageio import MultiImageLoader + _loader = MultiImageLoader() + _loader.read(file_path, time=timePoint, channel=channel, name=name) + self._timeSeriesCore._fullMap.loader.merge(_loader) + + # update gui + self._setModel() def slot_switchMap(self, timeSeriesCore : TimeSeriesCore): self._timeSeriesCore = timeSeriesCore diff --git a/src/pymapmanager/interface2/mapWidgets/mapWidget.py b/src/pymapmanager/interface2/mapWidgets/mapWidget.py index d126afc..90633a7 100644 --- a/src/pymapmanager/interface2/mapWidgets/mapWidget.py +++ b/src/pymapmanager/interface2/mapWidgets/mapWidget.py @@ -59,7 +59,6 @@ class mapWidget(MainWindow): _widgetName = 'Map Widget' - # def __init__(self, mmMap : pmm.mmMap): def __init__(self, timeseriescore : TimeSeriesCore): super().__init__(mapWidget=self, iAmMapWidget=True) @@ -77,8 +76,10 @@ def __init__(self, timeseriescore : TimeSeriesCore): self.setWindowTitle(os.path.split(self._map.path)[1]) - # def getMapSelection(self) -> MapSelection: - # return self._mapSelection + # over-ride from mmWIdget2, which returns stack + # using this to share some api like save and save as + def getStack(self) -> TimeSeriesCore: + return self._map def zoomToPointAnnotation(self, idx : int, @@ -102,6 +103,10 @@ def getMap(self): def getPath(self): return self._map.path + # over-ride mmWidget2 + def getDirty(self): + return self._map.getDirty() + def emitUndoEvent(self): """ """ @@ -186,6 +191,10 @@ def openStackRun(self, if spineID and _multipleTp: self.linkOpenPlots(link=True) + @property + def numSessions(self): + return self.getMap().numSessions + def getNumSessions(self): return self.getMap().numSessions diff --git a/src/pymapmanager/interface2/pyMapManagerApp2.py b/src/pymapmanager/interface2/pyMapManagerApp2.py index 47489d1..9429947 100644 --- a/src/pymapmanager/interface2/pyMapManagerApp2.py +++ b/src/pymapmanager/interface2/pyMapManagerApp2.py @@ -50,9 +50,14 @@ def _importPlugins(pluginType : str, verbose = False): elif pluginType == 'mapWidgets': _modulePath = 'pymapmanager.interface2.mapWidgets' # _folderPath = 'mapWidgets' - + else: + logger.error(f'did not understand pluginType:"{pluginType}" expecting one of (stackWidgets, mapWidgets)') + return + numAdded = 0 + # logger.warning(f'verbose:{verbose}') + # CRITICAL: abb this list is not complete in pyinstaller invalidate_caches() # ??? m1 = import_module(_modulePath) @@ -149,7 +154,7 @@ def loadPlugins(pluginType : str, verbose = False) -> dict: pluginDict = {} - _importPlugins(pluginType, pluginType == 'stackWidgets') + _importPlugins(pluginType) if pluginType == 'stackWidgets': members = inspect.getmembers(pymapmanager.interface2.stackWidgets) @@ -336,17 +341,23 @@ def updateMapPathDict(self, aWidget): # self.pathDict["lastSaveTime"] = self._timeSeriesCore.getLastSaveTime() path = aWidget.getPath() - refreshTimeSeriesCore = TimeSeriesCore(path) - # lastSaveTime = aWidget.getLastSaveTime() # still showing old one, need to create new time series core to refresh? - lastSaveTime = refreshTimeSeriesCore.getLastSaveTime() - numTimepoints = refreshTimeSeriesCore.numSessions + + # abb why are we making TimeSeriesCore + logger.error('do not create TimeSeriesCore') + + lastSaveTime = aWidget.getLastSaveTime() # still showing old one, need to create new time series core to refresh? + numTimepoints = aWidget.numSessions + + # refreshTimeSeriesCore = TimeSeriesCore(path) + # lastSaveTime = refreshTimeSeriesCore.getLastSaveTime() + # numTimepoints = refreshTimeSeriesCore.numSessions pathDict = {"Path": path, "Last Save Time": str(lastSaveTime), # needs to be updated "Timepoints": str(numTimepoints)} self._app.getConfigDict().addMapPathDict(pathDict) def save(self, aWidget): - # logger.info(f'TODO: save widget: {aWidget}') + # abb only stackwidget2 has save(), e.g. map widgets do not logger.info(f'save widget: {aWidget}') aWidget.save() @@ -356,10 +367,11 @@ def save(self, aWidget): # self.pathDict["lastSaveTime"] = lastSaveTime def saveAs(self, aWidget): - # logger.info(f'TODO: save as widget: {aWidget}') + # abb only stackwidget2 has fileSaveAs(), e.g. map widgets do not logger.info(f'save as widget: {aWidget}') - aWidget.fileSaveAs() - self.updateMapPathDict(aWidget) # abj + _saved = aWidget.saveAs() + if _saved: + self.updateMapPathDict(aWidget) # abj def _checkWidgetExists(self, path): """ Check if a widget exists in the widget dict list @@ -587,7 +599,7 @@ def saveFile(self): _frontWidget = self.getFrontWindow() self._openWidgetList.save(_frontWidget) - def saveAsFile(self): + def saveAs(self): """ Save as a new file """ _frontWidget = self.getFrontWindow() @@ -666,6 +678,28 @@ def getScreenGrid(self, numItems : int, itemsPerRow : int) -> List[List[int]]: def getOpenWidgetDict(self): return self._openWidgetList.getDict() + def loadSampleData(self, sampleName): + if sampleName == 'Tiff File Ch1': + import mapmanagercore.data + from mapmanagercore.data import getTiffChannel_1 + tiffPath = getTiffChannel_1() + if os.path.isfile(tiffPath): + self.loadStackWidget(tiffPath) + elif sampleName == 'Tiff File Ch2': + import mapmanagercore.data + from mapmanagercore.data import getTiffChannel_2 + tiffPath = getTiffChannel_2() + if os.path.isfile(tiffPath): + self.loadStackWidget(tiffPath) + elif sampleName == 'mmap with spines and segments': + import mapmanagercore.data + from mapmanagercore.data import getSingleTimepointMap + tiffPath = getSingleTimepointMap() + if os.path.isfile(tiffPath): + self.loadStackWidget(tiffPath) + else: + logger.warning(f'did not understand "{sampleName}"') + def loadStackWidget(self, path : str = None) -> Union[stackWidget2, mapWidget]: """Load a stack from a path and open a stackWidget2 or mapWidget @@ -690,7 +724,7 @@ def loadStackWidget(self, path : str = None) -> Union[stackWidget2, mapWidget]: dialog = QtWidgets.QFileDialog(None) # dialog.setFileMode(QtWidgets.QFileDialog.Directory) - dialog.setNameFilter("zarr directory (*.mmap)") + dialog.setNameFilter("MapManager Files (*.mmap, *.zip)") # openFilePath = dialog.getExistingDirectory(None) # dialog.setOptions(options) openFilePath = dialog.getExistingDirectory() @@ -701,11 +735,11 @@ def loadStackWidget(self, path : str = None) -> Union[stackWidget2, mapWidget]: _ext = os.path.splitext(openFilePath)[1] window = self.activeWindow() if openFilePath == "": - logger.warning("openFilePath is Empty") + # logger.warning("openFilePath is Empty") # QtWidgets.QMessageBox.critical(window, "Error", "File Path is Empty") return - elif _ext != '.mmap': # could make this into a for loop until user inputs .mmap - logger.warning(f"incorrect directory type, must be of extension: (.mmap)") + elif _ext not in ['.mmap', '.zip']: # could make this into a for loop until user inputs .mmap + logger.warning(f"incorrect directory type, must be of extension: .mmap or .zip") QtWidgets.QMessageBox.critical(window, "Error", "Incorrect directory type, must be of extension: (.mmap)") return @@ -804,9 +838,11 @@ def clearRecentFiles(self): self._openFirstWindow.refreshUI() def importNewTIF(self): - pass frontStackWindow = self.getFrontWindow() + if not isinstance(frontStackWindow, stackWidget2): + return + # bring up directory # get newTifPath diff --git a/src/pymapmanager/interface2/runInterfaceBob.py b/src/pymapmanager/interface2/runInterfaceBob.py index 2c8872f..8adaf43 100644 --- a/src/pymapmanager/interface2/runInterfaceBob.py +++ b/src/pymapmanager/interface2/runInterfaceBob.py @@ -12,7 +12,7 @@ def run(): app = PyMapManagerApp(sys.argv) # open a single timepoint map with segments and spines - path = mapmanagercore.data.getSingleTimepointMap() + # path = mapmanagercore.data.getSingleTimepointMap() # a single timepoint tif file (import) # path = mapmanagercore.data.getTiffChannel_1() @@ -33,7 +33,7 @@ def run(): # a mmap with multiple timepoints, connects segments and spines # path = '/Users/cudmore/Desktop/multi_timepoint_map_seg_spine_connected.mmap' - # path = mapmanagercore.data.getMultiTimepointMap() + path = mapmanagercore.data.getMultiTimepointMap() # path = '/Users/cudmore/Desktop/yyy4.mmap' # path = '/Users/cudmore/Desktop/single_timepoint.mmap' diff --git a/src/pymapmanager/interface2/stackWidgets/base/mmWidget2.py b/src/pymapmanager/interface2/stackWidgets/base/mmWidget2.py index 4c7ffcf..fef4bda 100644 --- a/src/pymapmanager/interface2/stackWidgets/base/mmWidget2.py +++ b/src/pymapmanager/interface2/stackWidgets/base/mmWidget2.py @@ -10,6 +10,7 @@ import copy from enum import Enum, auto from typing import List, Optional, Tuple, TypedDict, Self +import os from qtpy import QtGui, QtCore, QtWidgets @@ -563,12 +564,6 @@ def __init__(self, self._stackWidget = stackWidget # parent stack widget self._mapWidget = mapWidget # parent map widget - # 20240905, TimeSeriesCore() holds one undo manager for all - # 20240904 moved from stackWidget2 - # from pymapmanager.interface2.stackWidgets.event.undoRedo import UndoRedoEvent - # # self._undoRedo = UndoRedoEvent(self) - # self._undoRedo = UndoRedoEvent() - # to show as a widget self._showSelf: bool = True @@ -595,6 +590,115 @@ def __init__(self, self._signalPmmEvent.connect(mapWidget.slot_pmmEvent) mapWidget._signalPmmEvent.connect(self.slot_pmmEvent) + def save(self): + """ Stack Widget saves changes to its Zarr file + """ + + path = self.getStack().getPath() + ext = os.path.splitext(path)[1] + # logger.info(f"ext {ext}") + if ext == ".mmap": + self.getStack().save() + self.setDirtyFalse() + elif ext == ".tif": # users start with tif file, but must begin using .mmap after saving + self.saveAs() + else: + logger.info("Extension not understood, nothing is saved") + + def saveAs(self): + """Save a single timepoint to a new file. + + Prompts user for file name. + """ + # ('C:/Users/johns/Documents/GitHub/MapManagerCore/data/test', 'All Files (*)') + + # filter = "(*.mmap)" + _path = self.getPath() + # defaultPath, defaultFileName = os.path.split(_path) + filters = 'MapManager files (*.mmap, *.zip)' + + saveAsPath, _ = QtWidgets.QFileDialog.getSaveFileName(self, + caption='Save mmap File', + dir=_path, + filter=filters, + # selectedFilter='*.mmap' + ) + + logger.info(f'saveAsPath: "{saveAsPath}"') + + if not saveAsPath: + return False + + ext = os.path.splitext(saveAsPath)[1] + if ext not in ['.mmap', '.zip']: + logger.error(f'map must have extension ".mmap" or ".zip", got "{ext}" -->> did not save.') + QtWidgets.QMessageBox.critical(self,"Error: Incorrect Extension", "Please use .mmap as the file extension to save") + return False + else: + self.getStack().saveAs(saveAsPath) + self.setWindowTitle(self.getStack().getFileName()) + + return True + + def getPath(self) -> str: + return self.getStack().getPath() + + def getFileName(self) -> str: + return self.getStack().getPath() + + def getLastSaveTime(self): + return self.getStack().getLastSaveTime() + + def setDirtyFalse(self): + """ Set dirty as False after a save + """ + pa = self.getStack().getPointAnnotations() + la = self.getStack().getLineAnnotations() + + pa._setDirty(False) + la._setDirty(False) + + def setDirtyTrue(self): + """ Set dirty as False after a save + """ + + # TODO: add support with line annotations + # after updating stack.undo() + pa = self.getStack().getPointAnnotations() + # la = self.getStack().getLineAnnotations() + + pa._setDirty(True) + # la._setDirty(False) + + #abj + def getDirty(self): + """Check if spineannotations or lineannotations are dirty + + Return: + True if dirty + False if not + """ + + # access stack + pa = self.getStack().getPointAnnotations() + la = self.getStack().getLineAnnotations() + + isPaDirty = pa.getDirty() + isLaDirty = la.getDirty() + + if isPaDirty or isLaDirty: + return True + else: + return False + + # abj + def getAnalysisParams(self): + """ Get analysis Params from MapManagerCore + """ + # pass + + return self.getStack().getAnalysisParameters() + def getUndoRedo(self): if self._iAmStackWidget: logger.info(f' getting from stack widget') diff --git a/src/pymapmanager/interface2/stackWidgets/imagePlotWidget2.py b/src/pymapmanager/interface2/stackWidgets/imagePlotWidget2.py index 81ccabe..cadb407 100644 --- a/src/pymapmanager/interface2/stackWidgets/imagePlotWidget2.py +++ b/src/pymapmanager/interface2/stackWidgets/imagePlotWidget2.py @@ -1,7 +1,10 @@ +import os import numpy as np -import pyqtgraph as pg from qtpy import QtGui, QtCore, QtWidgets +import pyqtgraph as pg + +from mapmanagercore import IMPORT_FILE_EXTENSIONS import pymapmanager import pymapmanager.annotations @@ -71,6 +74,8 @@ def __init__(self, stackWidget): self._buildUI() + self.setAcceptDrops(True) + # self.setFocus() def wheelEvent_monkey_patch(self, event): @@ -984,6 +989,25 @@ def setColorChannelEvent(self, event : pmmEvent): self._setChannel(colorChannel, doEmit=False) self.refreshSlice() + def dragEnterEvent(self, event): + # accept drag/drop of tiff file + if event.mimeData().hasUrls(): + urlList = event.mimeData().urls() + url = urlList[0] + file_path = url.toLocalFile() + _, _ext = os.path.splitext(file_path) + if _ext in IMPORT_FILE_EXTENSIONS: + event.acceptProposedAction() + + def dropEvent(self, event): + urlList = event.mimeData().urls() + url = urlList[0] + file_path = url.toLocalFile() + # self.label.setText(f"Dropped file: {file_path}") + if self.getStack() is not None: + self.getStackWidget().loadInNewChannel(file_path) + logger.info(f'file_path:{file_path}') + class StackSlider(QtWidgets.QSlider): """Slider to set the stack image slice. diff --git a/src/pymapmanager/interface2/stackWidgets/stackWidget2.py b/src/pymapmanager/interface2/stackWidgets/stackWidget2.py index b2bc1b7..ed408f8 100644 --- a/src/pymapmanager/interface2/stackWidgets/stackWidget2.py +++ b/src/pymapmanager/interface2/stackWidgets/stackWidget2.py @@ -3,7 +3,7 @@ # see: https://stackoverflow.com/questions/39740632/python-type-hinting-without-cyclic-imports from __future__ import annotations import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from shapely import Point @@ -14,12 +14,10 @@ from pymapmanager.interface2.pyMapManagerApp2 import PyMapManagerApp from pymapmanager.interface2.appDisplayOptions import AppDisplayOptions -from typing import Optional # List, Union, Tuple - -# import numpy as np - from qtpy import QtGui, QtCore, QtWidgets +from mapmanagercore import IMPORT_FILE_EXTENSIONS + import pymapmanager from pymapmanager.interface2.stackWidgets.base.mmWidget2 import mmWidget2, pmmEventType, pmmStates, pmmEvent, StackSelection from .base.stacktoolbar import StackToolBar @@ -87,7 +85,7 @@ def __init__(self, self._displayOptionsDict : pymapmanager.interface2.AppDisplayOptions = pymapmanager.interface2.AppDisplayOptions() # self._displayOptionsDict : AppDisplayOptions = AppDisplayOptions() - self.setWindowTitle(self._stack.getFileName()) + self.setWindowTitle(self.getStack().getFileName()) self._buildUI() self._buildMenus() @@ -1187,98 +1185,6 @@ def emitRedoEvent(self): _redoEvent = RedoSpineEvent(self, None) self.slot_pmmEvent(_redoEvent) - def getPath(self) -> str: - return self.getStack().getPath() - - def save(self): - """ Stack Widget saves changes to its Zarr file - """ - - path = self.getStack().getPath() - ext = os.path.splitext(path)[1] - # logger.info(f"ext {ext}") - if ext == ".mmap": - self.getStack().save() - self.setDirtyFalse() - elif ext == ".tif": # users start with tif file, but must begin using .mmap after saving - self.fileSaveAs() - else: - logger.info("Extension not understood, nothing is saved") - - def fileSaveAs(self): - # ('C:/Users/johns/Documents/GitHub/MapManagerCore/data/test', 'All Files (*)') - - # filter = "(*.mmap)" - saveAsPath = QtWidgets.QFileDialog.getSaveFileName(None, 'Save File')[0] - logger.info(f"name {saveAsPath}") - - ext = os.path.splitext(saveAsPath)[1] - if ext != '.mmap': - logger.error(f'map must have extension ".mmap", got "{ext}" -->> did not save.') - QtWidgets.QMessageBox.critical(self, "Error: Incorrect Extension", "Please use .mmap as the file extension to save") - return - else: - self.getStack().saveAs(saveAsPath) - self.setWindowTitle(self._stack.getFileName()) - - def getLastSaveTime(self): - return self.getStack().getLastSaveTime() - - def setDirtyFalse(self): - """ Set dirty as False after a save - """ - pa = self.getStack().getPointAnnotations() - la = self.getStack().getLineAnnotations() - - pa._setDirty(False) - la._setDirty(False) - - def setDirtyTrue(self): - """ Set dirty as False after a save - """ - - # TODO: add support with line annotations - # after updating stack.undo() - pa = self.getStack().getPointAnnotations() - # la = self.getStack().getLineAnnotations() - - pa._setDirty(True) - # la._setDirty(False) - - #abj - def getDirty(self): - """Check if spineannotations or lineannotations are dirty - - Return: - True if dirty - False if not - """ - - # access stack - pa = self.getStack().getPointAnnotations() - la = self.getStack().getLineAnnotations() - - isPaDirty = pa.getDirty() - isLaDirty = la.getDirty() - - if isPaDirty or isLaDirty: - return True - else: - return False - - # abj - def getAnalysisParams(self): - """ Get analysis Params from MapManagerCore - """ - # pass - - return self.getStack().getAnalysisParameters() - - # def saveAnalysisParamsDict(self): - # """ Save analysis Params changes to zarr directory using MapManagerCore - # """ - # pass - # abj def _old_updateDFwithNewParams(self): """ Rebult line and point dataframes after analysis params changes are applied @@ -1366,9 +1272,9 @@ def loadInNewChannel(self, path = None): newTifPath = path # check to ensure it is a tif file, Note: might need to expand to list of supported files - ext = os.path.splitext(newTifPath)[1] - if ext != '.tif': - logger.error(f'map must have extension ".tif", got "{ext}" -->> did not load.') + _path, _ext = os.path.splitext(newTifPath) + if _ext not in IMPORT_FILE_EXTENSIONS: + logger.error(f'import must have extension "{IMPORT_FILE_EXTENSIONS}", got "{_ext}" -->> did not load.') QtWidgets.QMessageBox.critical(self, "Error: Incorrect Extension", "Please use .tif as the file extension to save") return diff --git a/src/pymapmanager/stack.py b/src/pymapmanager/stack.py index 577a8be..716f27b 100644 --- a/src/pymapmanager/stack.py +++ b/src/pymapmanager/stack.py @@ -12,9 +12,6 @@ class stack: - # the file types that we can load - loadTheseExtension = ['.mmap', '.tif'] - channelColors = ['g', 'r', 'b'] def __init__(self, diff --git a/src/pymapmanager/timeseriesCore.py b/src/pymapmanager/timeseriesCore.py index 5476ac8..6cc2e29 100644 --- a/src/pymapmanager/timeseriesCore.py +++ b/src/pymapmanager/timeseriesCore.py @@ -11,11 +11,13 @@ import pandas as pd import numpy as np +from mapmanagercore import LOAD_SAVE_EXTENSIONS # , IMPORT_FILE_EXTENSIONS from mapmanagercore import MapAnnotations, MultiImageLoader from mapmanagercore.analysis_params import AnalysisParams from mapmanagercore.schemas import Spine, Segment from mapmanagercore.lazy_geo_pd_images.loader.zarr import ZarrLoader from mapmanagercore.lazy_geo_pd_images.loader.imageio import MultiImageLoader +from mapmanagercore.annotations.single_time_point import SingleTimePointAnnotations from pymapmanager._logger import logger @@ -144,8 +146,8 @@ def __init__(self, path : str): # TODO just use endswith(), splitext does not handle '.ome.zarr' _ext = os.path.splitext(path)[1] - - if path.endswith('.mmap') or path.endswith('.mmap/'): + # if path.endswith('.mmap') or path.endswith('.mmap/') or path.endswith('.mmap.zip'): + if _ext in LOAD_SAVE_EXTENSIONS: self._load_zarr() elif path.endswith('.tif'): self._import_tiff() @@ -165,19 +167,17 @@ def __init__(self, path : str): # every mutation sets to True # TODO only .mmap ext is not dirty (all other path ext were import) - if _ext == '.mmap': + if _ext in LOAD_SAVE_EXTENSIONS: self._isDirty = False else: + # assuming import self._isDirty = True - # if _ext == '.tif' or path.endswith('.ome.zarr'): - # self._isDirty = True - # else: - # self._isDirty = False - self._undoRedoManager = UndoRedoManager() - from mapmanagercore.annotations.single_time_point import SingleTimePointAnnotations + def getFileName(self): + return self._path + def getTimepoint(self, timepoint : int) -> SingleTimePointAnnotations: return self._fullMap.getTimePoint(timepoint) @@ -194,6 +194,9 @@ def getSegments(self): def isDirty(self): return self._isDirty + def getDirty(self): + return self._isDirty + def setDirty(self, dirty=True): self._isDirty = dirty @@ -369,15 +372,16 @@ def saveAs(self, path : str): """ ext = os.path.splitext(path)[1] - if ext != '.mmap': - logger.error(f'map must have extension ".mmap", got "{ext}" -->> did not save.') + if ext not in ['.mmap', '.zip']: + logger.error(f'map must have extension ".mmap" or ".zip", got "{ext}" -->> did not save.') return self._fullMap.save(path) - # abb 20241221 self._path = path + return True + def undo(self): logger.info('-->> PERFORMING UNDO') self._fullMap.undo()