From bae1268e0f0b1624b1a305091568a5256fd96334 Mon Sep 17 00:00:00 2001 From: HV Date: Thu, 2 Apr 2020 18:56:46 -0600 Subject: [PATCH] - Added TestPickerModel(default picker model) and TestPickFilModel(filament model) for test - Tested default picking and filament pincking - Fixed errors from datavis picker model refactoring --- emvis/__init__.py | 1 + emvis/models/_empicker.py | 16 +- emvis/models/_emtable_model.py | 3 +- emvis/tests/__init__.py | 2 + emvis/tests/test_common.py | 208 ++++++++++++++++++++++++ emvis/tests/test_dualimagelistview.py | 113 ++++--------- emvis/tests/test_pickingviewdefault.py | 14 +- emvis/tests/test_pickingviewfilament.py | 14 +- emvis/tests/test_tvconfig.py | 54 ------ emvis/views/_views_factory.py | 5 +- 10 files changed, 272 insertions(+), 158 deletions(-) create mode 100644 emvis/tests/test_common.py delete mode 100644 emvis/tests/test_tvconfig.py diff --git a/emvis/__init__.py b/emvis/__init__.py index 41bd6d8..5e92461 100644 --- a/emvis/__init__.py +++ b/emvis/__init__.py @@ -8,3 +8,4 @@ from . import utils from . import models from . import views +from . import tests diff --git a/emvis/models/_empicker.py b/emvis/models/_empicker.py index 9493193..2c18ad8 100644 --- a/emvis/models/_empicker.py +++ b/emvis/models/_empicker.py @@ -188,15 +188,19 @@ def getParams(self): [scoreThreshold, useColor] ]) - def changeParam(self, micId, paramName, paramValue, getValuesFunc): + def onParamChanged(self, micId, paramName, paramValues): # Most cases here will modify the current coordinates - r = self.Result(currentCoordsChanged=True, tableModelChanged=True) + d = {'micId': micId, + 'currentCoordsChanged': True, + 'tableModelChanged': True} + n = True if paramName == 'scoreThreshold': - self._scoreThreshold = getValuesFunc()['scoreThreshold'] + self._scoreThreshold = paramValues['scoreThreshold'] elif paramName == 'useColor': - self._useColor = getValuesFunc()['useColor'] + self._useColor = paramValues['useColor'] else: - r = self.Result() # No modification + n = False # No modification - return r \ No newline at end of file + if n: + self.notifyEvent(type=dv.models.PICK_EVT_DATA_CHANGED, info=d) \ No newline at end of file diff --git a/emvis/models/_emtable_model.py b/emvis/models/_emtable_model.py index 8a97715..c5bd103 100644 --- a/emvis/models/_emtable_model.py +++ b/emvis/models/_emtable_model.py @@ -24,7 +24,8 @@ def __init__(self, tableSource, **kwargs): * string: This should be the path from where to read the table(s). The first table will be loaded by default. * tuple (string, string): Here you can specify the path and - the name of the table that you want to be loaded by default. + the name of the table that you want to be loaded by + default. Keyword Arguments: imageManager=value Provide an ImageManager that can be used diff --git a/emvis/tests/__init__.py b/emvis/tests/__init__.py index e69de29..b9651a8 100644 --- a/emvis/tests/__init__.py +++ b/emvis/tests/__init__.py @@ -0,0 +1,2 @@ + +from .test_common import TestPickerModel, TestPickFilModel diff --git a/emvis/tests/test_common.py b/emvis/tests/test_common.py new file mode 100644 index 0000000..cae7ba2 --- /dev/null +++ b/emvis/tests/test_common.py @@ -0,0 +1,208 @@ + +import os +from random import randint + +import datavis as dv +import emvis.models as em_models + +class TestPickerModel(em_models.EmPickerModel): + """ Em picker data model with direct access to ImageManager """ + + def __init__(self, inputDir, coordDir, imageManager=None): + """ + Create a new instance of :class:`~TestPickerModel`. + + Args: + inputDir: Directory with the resulting picking dir + imageManager: optional :class:`~emvis.utils.ImageManager` class to + read micrographs from disk. + """ + em_models.EmPickerModel.__init__(self, imageManager=imageManager) + self._inputDir = inputDir + self._coordDir = coordDir + self._scoreThreshold = 0.0 + self._useColor = False + + self._loadData() + + def _parseEmPickCoordinates(self, path): + """ Parse (x, y) coordinates from a text file assuming + that the first two columns on each line are x and y. + Other specifications can be: + - x y label + - x1 y1 width height + - x1 y1 width height label + """ + with open(path) as f: + Coord = dv.models.Coordinate + index = 0 + for line in f: + li = line.strip() + index += 1 + t = randint(0, 10) # FIXME[hv] use random threshold + if li: + parts = li.strip().split() + size = len(parts) + if size == 2 or size == 4: # (x, y) or (x1,y1,width,height) + yield Coord(int(parts[0]), int(parts[1]), "", + threshold=t) + elif size == 3: # (x, y, label) + yield Coord(int(parts[0]), int(parts[1]), str(parts[2]), + threshold=t) + elif size == 5: # (x1, y1, width, height, label): + yield Coord(int(parts[0]), int(parts[1]), str(parts[4]), + threshold=t) + else: + raise Exception( + "Unsupported coordinate specification:\n" + "path: %s" % path % "line: %d" % index) + + def _initLabels(self): + """ Initialize the labels for this PickerModel. """ + colors = [ + "#1EFF00", + "#DD0014" + ] + for i, c in enumerate(colors): + name = str(i) + self._labels[name] = self.Label(name=name, color=c) + self._labels['D'] = self._labels['0'] + + def _input(self, filename): + return os.path.join(self._inputDir, filename) + + def _coord(self, filename): + return os.path.join(self._coordDir, filename) + + def _loadData(self): + self._loadedMics = set() + + if not os.path.exists(self._inputDir): + raise Exception("Expecting directory '%s'" % self._inputDir) + + if not os.path.exists(self._coordDir): + raise Exception("Expecting directory '%s'" % self._coordDir) + + micId = 0 + for micName in os.listdir(self._inputDir): + micId += 1 + mic = dv.models.Micrograph(micId=micId, path=self._input(micName)) + self.addMicrograph(mic) + self.beginReadCoordinates(micId) + + def readCoordinates(self, micId): + if not micId in self._loadedMics: + self._loadedMics.add(micId) + mic = self.getMicrograph(micId) + micName = os.path.split(mic.getPath())[-1] + coordPath = self._coord(os.path.splitext(micName)[0] + '.box') + + if os.path.exists(coordPath): + return self._parseEmPickCoordinates(coordPath) + return [] + + def createCoordinate(self, x, y, label, **kwargs): + """ + Return a Coordinate object. This is the preferred way to create + Coordinates objects, ensuring that the object contains all + the additional properties related to the model. + Subclasses should implement this method + """ + return dv.models.Coordinate(x, y, 'M', threshold=1, **kwargs) + + def iterCoordinates(self, micId): + # Re-implement this to show only these above the threshold + # or with a different color (label) + for coord in self._getCoordsList(micId): + good = coord.threshold > self._scoreThreshold + coord.label = '0' if good else '1' + if good or self._useColor: + yield coord + + def getColumns(self): + """ Return a Column list that will be used to display micrographs. """ + return [ + dv.models.ColumnConfig('Micrograph', dataType=dv.models.TYPE_STRING, + editable=False), + dv.models.ColumnConfig('Coordinates', dataType=dv.models.TYPE_INT, + editable=False), + dv.models.ColumnConfig('Id', dataType=dv.models.TYPE_INT, + editable=False, visible=False), + ] + + def getValue(self, row, col): + """ Return the value in this (row, column) from the micrographs table. + """ + mic = self.getMicrographByIndex(row) + + if col == 0: # Name + return os.path.basename(mic.getPath()) + elif col == 1: # Coordinates + # Use real coordinates number for already loaded micrographs + return len(mic) + elif col == 2: # Id + return mic.getId() + else: + raise Exception("Invalid column value '%s'" % col) + + def getParams(self): + Param = dv.models.Param + scoreThreshold = Param('scoreThreshold', 'int', value=5, + display='slider', range=(0, 10), + label='Score threshold', + help='Display coordinates with score above ' + 'this value.') + + useColor = Param('useColor', 'bool', value=self._useColor, + label='Color coordinates \nwith score above?') + + return dv.models.Form([ + [scoreThreshold, useColor] + ]) + + def onParamChanged(self, micId, paramName, paramValues): + # Most cases here will modify the current coordinates + d = {'micId': micId, + 'currentCoordsChanged': True, + 'tableModelChanged': True} + n = True + + if paramName == 'scoreThreshold': + self._scoreThreshold = paramValues['scoreThreshold'] + elif paramName == 'useColor': + self._useColor = paramValues['useColor'] + else: + n = False # No modification + + if n: + self.notifyEvent(type=dv.models.PICK_EVT_DATA_CHANGED, info=d) + + +class TestPickFilModel(TestPickerModel): + + def _parseEmPickCoordinates(self, path): + """ Parse (x1, y1, x2, y2) coordinates from a text file assuming + that the first four columns on each line are x1, y1, x2, y2. + Other specifications can be: + - x1 y1 x2 y2 label + """ + with open(path) as f: + Coord = dv.models.Coordinate + index = 0 + for line in f: + li = line.strip() + index += 1 + t = randint(0, 10) # FIXME[hv] use random threshold + if li: + parts = li.strip().split() + size = len(parts) + if size == 4: # (x1, y1, x2, y2) + yield Coord(int(parts[0]), int(parts[1]), "", + threshold=t, x2=parts[2], y2=parts[3]) + elif size == 5: # (x1, y1, x2, y2, label): + yield Coord(int(parts[0]), int(parts[1]), str(parts[4]), + threshold=t, x2=parts[2], y2=parts[3]) + else: + raise Exception( + "Unsupported coordinate specification:\n" + "path: %s" % path % "line: %d" % index) diff --git a/emvis/tests/test_dualimagelistview.py b/emvis/tests/test_dualimagelistview.py index 3f7a861..11a645e 100644 --- a/emvis/tests/test_dualimagelistview.py +++ b/emvis/tests/test_dualimagelistview.py @@ -16,92 +16,43 @@ def getDataPaths(self): ] def createView(self): - tool_params1 = [ - [ - { - 'name': 'threshold', - 'type': 'float', - 'value': 0.55, - 'label': 'Quality threshold', - 'help': 'If this is ... bla bla bla', - 'display': 'default' - }, - { - 'name': 'thresholdBool', - 'type': 'bool', - 'value': True, - 'label': 'Quality checked', - 'help': 'If this is a boolean param' - } - ], - [ - { - 'name': 'threshold543', - 'type': 'float', - 'value': 0.67, - 'label': 'Quality', - 'help': 'If this is ... bla bla bla', - 'display': 'default' - }, - { - 'name': 'threshold', - 'type': 'float', - 'value': 14.55, - 'label': 'Quality threshold2', - 'help': 'If this is ... bla bla bla', - 'display': 'default' - } - ], - { - 'name': 'threshold2', - 'type': 'string', - 'value': 'Explanation text', - 'label': 'Threshold ex', - 'help': 'If this is ... bla bla bla 2', - 'display': 'default' - }, - { - 'name': 'text', - 'type': 'string', - 'value': 'Text example', - 'label': 'Text', - 'help': 'If this is ... bla bla bla for text' - }, - { - 'name': 'threshold4', - 'type': 'float', - 'value': 1.5, - 'label': 'Quality', - 'help': 'If this is ... bla bla bla for quality' - }, - { - 'name': 'picking-method', - 'type': 'enum', # or 'int' or 'string' or 'enum', - 'choices': ['LoG', 'Swarm', 'SVM'], - 'value': 1, # values in enum are int, in this case it is 'LoG' - 'label': 'Picking method', - 'help': 'Select the picking strategy that you want to use. ', - # display should be optional, for most params, a textbox is the - # default - # for enum, a combobox is the default, other options could be - # sliders - 'display': 'combo' # or 'combo' or 'vlist' or 'hlist' or - # 'slider' - }, - { - 'name': 'threshold3', - 'type': 'bool', - 'value': True, - 'label': 'Checked', - 'help': 'If this is a boolean param' - } - ] + Param = dv.models.Param + brightness = Param('brightness', 'int', value=50, display='slider', + range=(1, 100), label='Brightness', + help='Adjust image brightness.') + + threshold = Param('threshold', 'float', value=0.55, + label='Quality threshold', + help='If this is...bla bla bla') + thresholdBool = Param('threshold', 'bool', value=True, + label='Quality checked', + help='If this is a boolean param') + + threshold5 = Param('threshold5', 'string', value='Another text', + label='Another text example', + help='Showing more text') + + threshold6 = Param('threshold6', 'float', value=1.5, + label='Another float', + help='Just another float example') + + apply = Param('apply', 'button', label='Operation1') + + form = dv.models.Form([ + brightness, + [threshold, thresholdBool], + threshold5, + threshold6, + [apply] + ]) + return dv.views.DualImageListView( emv.models.ModelsFactory.createListModel(self.getDataPaths()), - options=tool_params1, method=printFunc) + form=form, method=printFunc) def printFunc(*args): + print('Example function. Print the arguments:') print(args) diff --git a/emvis/tests/test_pickingviewdefault.py b/emvis/tests/test_pickingviewdefault.py index 7d1c92d..46317ac 100644 --- a/emvis/tests/test_pickingviewdefault.py +++ b/emvis/tests/test_pickingviewdefault.py @@ -120,24 +120,26 @@ def __parseFiles(self, values): def getDataPaths(self): return [ - self.getPath("tmv_helix", "micrographs", "TMV_Krios_Falcon") + self.getPath("tmv_helix", "micrographs"), + self.getPath("tmv_helix", "coords") ] def createView(self): kwargs = dict() kwargs['selectionMode'] = dv.views.PagingView.SINGLE_SELECTION kwargs['pickerParams'] = tool_params1 - kwargs['boxSize'] = 300 + kwargs['boxSize'] = 170 kwargs['pickerMode'] = dv.views.DEFAULT_MODE kwargs['shape'] = dv.views.SHAPE_CIRCLE kwargs['removeRois'] = True kwargs['roiAspectLocked'] = True kwargs['roiCentered'] = True dataPaths = self.getDataPaths() - kwargs['sources'] = self.__parseFiles(["%s*" % dataPaths[0]]) - files = [micPath for (micPath, _) in kwargs['sources'].values()] - return emv.views.ViewsFactory.createPickerView(files, **kwargs) + + model = emv.tests.TestPickerModel(dataPaths[0], dataPaths[1]) + v = dv.views.PickerView(model, **kwargs) + return v if __name__ == '__main__': - TestPickerView().runApp() \ No newline at end of file + TestPickerView().runApp() diff --git a/emvis/tests/test_pickingviewfilament.py b/emvis/tests/test_pickingviewfilament.py index f8787ee..51f6322 100644 --- a/emvis/tests/test_pickingviewfilament.py +++ b/emvis/tests/test_pickingviewfilament.py @@ -42,24 +42,24 @@ def __parseFiles(self, values): def getDataPaths(self): return [ - self.getPath("tmv_helix", "coords", "TMV_Krios_Falcon"), - self.getPath("tmv_helix", "micrographs", "TMV_Krios_Falcon") + self.getPath("tmv_helix", "micrographs"), + self.getPath("tmv_helix", "coords") ] def createView(self): kwargs = dict() kwargs['selectionMode'] = dv.views.PagingView.SINGLE_SELECTION - kwargs['boxSize'] = 300 + kwargs['boxSize'] = 170 kwargs['pickerMode'] = dv.views.FILAMENT_MODE kwargs['shape'] = dv.views.SHAPE_CIRCLE kwargs['removeRois'] = True kwargs['roiAspectLocked'] = True kwargs['roiCentered'] = True dataPaths = self.getDataPaths() - kwargs['sources'] = self.__parseFiles(["%s*" % dataPaths[1], - "%s*" % dataPaths[0]]) - files = [micPath for (micPath, _) in kwargs['sources'].values()] - return emv.views.ViewsFactory.createPickerView(files, **kwargs) + + model = emv.tests.TestPickFilModel(dataPaths[0], dataPaths[1]) + v = dv.views.PickerView(model, **kwargs) + return v if __name__ == '__main__': diff --git a/emvis/tests/test_tvconfig.py b/emvis/tests/test_tvconfig.py deleted file mode 100644 index fdeea24..0000000 --- a/emvis/tests/test_tvconfig.py +++ /dev/null @@ -1,54 +0,0 @@ - -# Quick and dirty test script for TableModel class -# TODO: Improve it and use a proper test class - -import os - -import emcore as emc -import datavis as dv -import emvis as emv -from datavis.views import TableModel - - -testDataPath = os.environ.get("EM_TEST_DATA", None) - -print("hasImpl('star'): ", emc.TableFile.hasImpl('star')) - -if testDataPath is not None: - # use the code below when yue have a properly configured environment - fn1 = os.path.join(testDataPath, "relion_tutorial", "import", "refine3d", - "extra", "relion_it025_data.star") - print("Reading star: ", fn1) - - t = emc.Table() - tio = emc.TableFile() - tio.open(fn1) - tio.read("images", t) - tio.close() - - refColNames = [ - "rlnVoltage", "rlnDefocusU", "rlnSphericalAberration", - "rlnAmplitudeContrast", "rlnImageName", "rlnNormCorrection", - "rlnMicrographName", "rlnGroupNumber", "rlnOriginX", - "rlnOriginY", "rlnAngleRot", "rlnAngleTilt", "rlnAnglePsi", - "rlnClassNumber", "rlnLogLikeliContribution", - "rlnNrOfSignificantSamples", "rlnMaxValueProbDistribution" - ] - - model = emv.models.ModelsFactory.createTableModel(fn1) - tvc1 = dv.models.TableModel.fromTable(t) - #print(tvc1) - - colsConfigs = [ - "rlnVoltage", - "rlnDefocusU", - "rlnSphericalAberration", - "rlnAmplitudeContrast", - ("rlnImageName", {'label': 'ImageName', - 'renderable': True - }) - ] - - tvc2 = TableModel.fromTable(t, colsConfigs) - print(tvc2) - diff --git a/emvis/views/_views_factory.py b/emvis/views/_views_factory.py index de707d7..6e63236 100644 --- a/emvis/views/_views_factory.py +++ b/emvis/views/_views_factory.py @@ -76,7 +76,6 @@ def createPickerView(micFiles, **kwargs): - parseCoordFunc: The parser function for coordinates file """ model = ModelsFactory.createPickerModel( - files=micFiles, boxSize=kwargs.get('boxSize', 100), - sources=kwargs.get('sources'), - parseCoordFunc=kwargs.get('parseCoordFunc')) + inputMics=micFiles, boxSize=kwargs.get('boxSize', 100), + sources=kwargs.get('sources')) return dv.views.PickerView(model, **kwargs)