-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generalization in annotator, helps and use of the plugin #34
base: devel
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,23 +24,36 @@ | |
# ************************************************************************** | ||
import glob | ||
from enum import Enum | ||
from os.path import join, basename | ||
|
||
from pwem.protocols import EMProtocol | ||
from pyworkflow.object import Integer | ||
from pyworkflow.protocol import PointerParam | ||
from pyworkflow.utils import removeBaseExt | ||
from pyworkflow.utils import removeBaseExt, makePath, createLink, replaceBaseExt | ||
from tomo.objects import SetOfTomoMasks, TomoMask | ||
|
||
from tomosegmemtv.viewers_interactive.memb_annotator_tomo_viewer import MembAnnotatorDialog | ||
from tomosegmemtv.viewers_interactive.memb_annotator_tree import MembAnnotatorProvider | ||
|
||
EXT_MRC = '.mrc' | ||
FLT_SUFFIX = '_flt' | ||
|
||
class outputObjects(Enum): | ||
tomoMasks = SetOfTomoMasks | ||
|
||
|
||
class ProtAnnotateMembranes(EMProtocol): | ||
""" Manual annotation tool for segmented membranes | ||
""" Manual annotation tool for segmented membranes\n | ||
|
||
The annotation tool will open a graphical interface that will allow to manually | ||
label the set of tomo mask. The graphical interface will call the function membseg2 | ||
for supervising the segmentation. This graphical interface was slightly modified | ||
in collaboration with the autor for simplifying its use. | ||
|
||
A complete tutorial about the use of this tool can be seen in: | ||
|
||
https://scipion-em.github.io/docs/release-3.0.0/docs/user/denoising_mbSegmentation_pysegDirPicking/tomosegmemTV-pySeg-workflow.html#membrane-annotation | ||
|
||
""" | ||
_label = 'annotate segmented membranes' | ||
_possibleOutputs = outputObjects | ||
|
@@ -49,7 +62,7 @@ def __init__(self, **kwargs): | |
EMProtocol.__init__(self, **kwargs) | ||
self._objectsToGo = Integer() | ||
self._provider = None | ||
self._tomoList = None | ||
self._tomoMaskDict = None | ||
|
||
def _defineParams(self, form): | ||
|
||
|
@@ -61,12 +74,56 @@ def _defineParams(self, form): | |
allowsNull=False, | ||
help='Select the Tomogram Masks (segmented tomograms) for the membrane annotation.') | ||
|
||
form.addParam('inputTomos', PointerParam, | ||
label="Tomograms (Optional, only used for visualization)", | ||
pointerClass='SetOfTomograms', | ||
allowsNull=True, | ||
help='Select the the set of tomogram used for obtaining the Tomo Masks. This set will' | ||
'only be used for visualization purpose in order to simplify the annotation. Of the ' | ||
'tomo masks.') | ||
|
||
# --------------------------- INSERT steps functions ---------------------- | ||
def _insertAllSteps(self): | ||
|
||
self._initialize() | ||
self._insertFunctionStep(self.convertInputStep) | ||
self._insertFunctionStep(self.runMembraneAnnotator, interactive=True) | ||
|
||
# --------------------------- STEPS functions ----------------------------- | ||
def convertInputStep(self): | ||
''' | ||
In this convert we perform two things: | ||
1) A folder per tomogram is created. The name of the folder is the TsId | ||
2) Symbolic link are created: The annotator expects two files the tomogram and the tomomask. | ||
The files must have .mrc extension. The tomomask should also contain the suffix _flt. | ||
The function will create two symbolic links, one for the tomo mask and one for the tomogram. | ||
Due to the tomograms are not mandatory in the form. If the tomogram is not provided the second | ||
symbolic link will also point to the tomomask. | ||
''' | ||
|
||
for tsId, tomoMask in self._tomoMaskDict.items(): | ||
tsIdPath = self._getExtraPath(tsId) | ||
makePath(tsIdPath) | ||
|
||
createLink(tomoMask.getFileName(), self.getfltFile(tomoMask, FLT_SUFFIX + EXT_MRC)) | ||
if self.inputTomos.get(): | ||
tomo = self._tomoDict.get(tsId, None) | ||
if tomo: | ||
createLink(tomo.getFileName(), self.getTomoMaskFile(tomo)) | ||
else: | ||
createLink(tomoMask.getFileName(), self.getTomoMaskFile(tomoMask)) | ||
else: | ||
createLink(tomoMask.getFileName(), self.getfltFile(tomoMask) + EXT_MRC) | ||
|
||
def getfltFile(self, tomoMask, suffix=''): | ||
tsId = tomoMask.getTsId() | ||
return self._getExtraPath(tsId, removeBaseExt(tomoMask.getFileName().replace('_flt', '')) + suffix) | ||
|
||
def getTomoMaskFile(self, tomoMask): | ||
tsId = tomoMask.getTsId() | ||
return self._getExtraPath(tsId, basename(tomoMask.getFileName())) | ||
|
||
|
||
def runMembraneAnnotator(self): | ||
# There are still some objects which haven't been annotated --> launch GUI | ||
self._getAnnotationStatus() | ||
|
@@ -94,17 +151,31 @@ def _summary(self): | |
summary.append('All segmentations have been already annotated.') | ||
return summary | ||
|
||
def _validate(self): | ||
error = [] | ||
# This is a tolerance in the sampling rate to ensure that tomoMask and tomograms have similar pixel size | ||
tolerance = 0.001 | ||
if self.inputTomos.get(): | ||
if abs(self.inputTomos.get().getSamplingRate() - self.inputTomoMasks.get().getSamplingRate()) > tolerance: | ||
error.append('The sampling rate of the tomograms does not match the sampling rate of the input masks') | ||
|
||
return error | ||
|
||
# --------------------------- UTIL functions ----------------------------------- | ||
|
||
def _initialize(self): | ||
self._tomoList = [tomo.clone() for tomo in self.inputTomoMasks.get().iterItems()] | ||
self._provider = MembAnnotatorProvider(self._tomoList, self._getExtraPath(), 'membAnnotator') | ||
import time | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we do not want this delay? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not, solved |
||
time.sleep(12) | ||
self._tomoMaskDict = {tomoMask.getTsId(): tomoMask.clone() for tomoMask in self.inputTomoMasks.get().iterItems()} | ||
if self.inputTomos.get(): | ||
self._tomoDict = {tomo.getTsId(): tomo.clone() for tomo in self.inputTomos.get().iterItems()} | ||
pconesa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._provider = MembAnnotatorProvider(list(self._tomoMaskDict.values()), self._getExtraPath(), 'membAnnotator') | ||
self._getAnnotationStatus() | ||
|
||
def _getAnnotationStatus(self): | ||
"""Check if all the tomo masks have been annotated and store current status in a text file""" | ||
doneTomes = [self._provider.getObjectInfo(tomo)['tags'] == 'done' for tomo in self._tomoList] | ||
self._objectsToGo.set(len(self._tomoList) - sum(doneTomes)) | ||
doneTomes = [self._provider.getObjectInfo(tomo)['tags'] == 'done' for tomo in list(self._tomoMaskDict.values())] | ||
self._objectsToGo.set(len(self._tomoMaskDict) - sum(doneTomes)) | ||
|
||
def _getCurrentTomoMaskFile(self, inTomoFile): | ||
baseName = removeBaseExt(inTomoFile) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,7 +51,35 @@ class outputObjects(Enum): | |
|
||
|
||
class ProtTomoSegmenTV(EMProtocol): | ||
"""Segment membranes in tomograms""" | ||
"""TomoSegMemTV is a software suite for segmenting membranes in tomograms. The method | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow! Very detailed. |
||
is based on (1) a Gaussian-like model of membrane profile, (2) a local differential structure | ||
approach and (3) anisotropic propagation of the local structural information using the tensor | ||
voting algorithm. In particular, it makes use of the next steps\n | ||
|
||
_1 Scale-space_: This stage allows isolation of the information according to the spatial | ||
scale by filtering out features with a size smaller than the given scale. It basically | ||
consists of a Gaussian filtering.\n | ||
_2 Dense Tensor voting_: In this stage, the voxels of the input tomogram communicate among | ||
themselves by propagating local structural information between each other. The local | ||
information is encoded in a second order tensor, called vote. The local properties at each | ||
voxel are then refined according to the information received from the neighbors. | ||
Voxels belonging to the same geometric feature will have strengthened each other and their | ||
tensors will have been modified to enhance the underlying global structure.\n | ||
|
||
_3 Surfaceness/Saliency_: This stage applies a local detector based on the | ||
Gaussian membrane model. The local detector relies on differential information, | ||
as it has to analyze local structure. In order to make it invariant to the membrane | ||
direction, the detector is established along the normal to the membrane at the local | ||
scale. An eigen-analysis of the Hessian tensor is well suited to determine such | ||
direction and provide the membrane-strength (M) for each voxel. Only voxels with | ||
membrane-strength higher than a threshold are considered and subjected to a non-maximum | ||
supression (NMS) operation so as to give a 1-voxel-thick surface. The final output map | ||
consists in planarity descriptors that represent the actual probability of | ||
belonging to a true surface (hence surfaceness).\n | ||
|
||
Once these protocols ends, it is neccesary to threshold the output map. | ||
|
||
""" | ||
|
||
_label = 'tomogram segmentation' | ||
_possibleOutputs = outputObjects | ||
|
@@ -67,31 +95,40 @@ def _defineParams(self, form): | |
form.addParam('inTomograms', PointerParam, | ||
pointerClass='SetOfTomograms', | ||
allowsNull=False, | ||
label='Input tomograms') | ||
label='Input tomograms', | ||
help='This is the set of tomograms to be segmented obtaining tomo Masks') | ||
|
||
form.addParam('mbThkPix', IntParam, | ||
allowsNull=False, | ||
default=1, | ||
validators=[GT(0)], | ||
label='Membrane thickness (voxels)', | ||
help='It basically represents the standard deviation of a Gaussian filtering. ' | ||
'This parameter should represent the thickness (in pixels) of the membranes sought.' | ||
'So, visual inspection of the tomogram helps the user to find out a proper value.' | ||
'In general, any value in a range around that thickness works well. Too low ' | ||
'values may make spurious details produce false positives at the local membrane ' | ||
'detector while too high values may excessively smear out the membranes, which ' | ||
'in turn may produce discontinuities in the segmentation results.' | ||
) | ||
'in turn may produce discontinuities in the segmentation results. ' | ||
'This parameter is used in the scale-space step') | ||
|
||
form.addParam('mbScaleFactor', IntParam, | ||
allowsNull=False, | ||
default=10, | ||
validators=[GT(0)], | ||
label='Membrane scale factor (voxels)', | ||
help='This defines the effective neighborhood involved in the voting process. ' | ||
'Depending on the thickness of the membranes in the tomogram, lower (for ' | ||
'thinner membranes) or higher values (for thicker ones) may be more appropriate.' | ||
help='This parameter is used for tensor voting. This defines the effective neighborhood ' | ||
'involved in the voting process. Depending on the thickness of the membranes in ' | ||
'the tomogram, lower (for thinner membranes) or higher values (for thicker ones)' | ||
' may be more appropriate.' | ||
) | ||
|
||
form.addParam('blackOverWhite', BooleanParam, | ||
label='Is black over white?', | ||
default=True | ||
default=True, | ||
help = 'By default, the program assumes that the features to detect (foreground/membranes) ' | ||
'are black (darker) over white (lighter) background. This is normally the case ' | ||
'in cryo-tomography.' | ||
) | ||
group = form.addGroup('Membrane delineation', | ||
expertLevel=LEVEL_ADVANCED) | ||
|
@@ -101,13 +138,14 @@ def _defineParams(self, form): | |
validators=[GT(0)], | ||
expertLevel=LEVEL_ADVANCED, | ||
label='Membrane-strength threshold', | ||
help='Allow the user tune the amount of output membrane points and remove false positives. ' | ||
'Only voxels with values of membrane-strength threshold higher than this value ' | ||
help='Allows the user to specify a threshold for the membrane-strength. ' | ||
'Only voxels with values than the membrane-strength threshold ' | ||
'will be considered as potential membrane points, and planarity descriptors will ' | ||
'be calculated for them. Higher values will generate less membrane points, at the ' | ||
'risk of producing gaps in the membranes. Lower values will provide more membrane ' | ||
'points, at the risk of generating false positives.' | ||
) | ||
'points, at the risk of generating false positives.\n' | ||
'Check the gray level of the membranes of the input images to introduce a proper ' | ||
'value.') | ||
group.addParam('sigmaS', FloatParam, | ||
label='Sigma for the initial gaussian filtering', | ||
default=1, | ||
|
@@ -143,7 +181,7 @@ def _defineParams(self, form): | |
' - Saliency --> *filename%s.mrc*' % (S2, TV, SURF, TV2, FLT) | ||
) | ||
|
||
form.addParallelSection(threads=8, mpi =1) | ||
form.addParallelSection(threads=8, mpi=1) | ||
|
||
def _insertAllSteps(self): | ||
self._insertFunctionStep(self.convertInputStep) | ||
|
@@ -220,7 +258,7 @@ def _summary(self): | |
def _validate(self): | ||
if not os.path.exists(Plugin.getProgram(SCALE_SPACE)): | ||
return ["%s is not at %s. Review installation. Please go to %s for instructions." % | ||
(SCALE_SPACE, Plugin.getProgram(SCALE_SPACE),Plugin.getUrl())] | ||
(SCALE_SPACE, Plugin.getProgram(SCALE_SPACE), Plugin.getUrl())] | ||
|
||
# --------------------------- UTIL functions ----------------------------------- | ||
|
||
|
@@ -257,4 +295,3 @@ def _getSalCmd(self, inputFile, outputFile, Nthreads): | |
outputCmd += '%s ' % outputFile | ||
outputCmd += ' -t %i' % Nthreads | ||
return outputCmd | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we bump the version in the init and release this?