Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/segmentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ This can be done in multiple colours, by selecting the desired colour before lab

Different filter kernels and classifiers can be chosen using the dropdowns and tick boxes.
The filter kernel parameters can also be altered using the Sigma, High Sigma and Disk Size parameters.
Once areas have been labelled, training can be begun using the 'Update' and 'Train Classifier' Button.
Once areas have been labelled, training can begin using the 'Train Classifier' Button.

When this finishes the labels will be shown on top of the image in the user interface, this can take up to several minutes.
The classifier can then be retrained with additional pixels by by clearing the canvas of the displayed segmentation with `Clear Canvas`, and redrawing previous training labels with `redraw training labels`.
Expand Down Expand Up @@ -182,9 +182,9 @@ This is shown in the example below:

.. code-block:: python

from PIL import Image
import numpy as np
from sklearn.naive_bayes import GaussianNB
import hyperspy.api as hs
import particlespy.api as ps

image = hs.load("image path")
Expand All @@ -195,5 +195,5 @@ This is shown in the example below:
_, clf = ps.cluster_trained(image, mask, clf)

This classifier can then be used to segment images.
The function ``ps.toggle_channels`` is used to convert an RGB image into a 2D indexed array of labels.
The function ``ps.toggle_channels`` is used to convert an RGB image into a 1 channel 2D indexed array of labels.
This can also be used to convert the output of the ``classifier_segment`` into RGB images which can be exported.
36 changes: 33 additions & 3 deletions particlespy/custom_kernels.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
"""

import numpy as np
from skimage.transform import rotate
from skimage import filters
from scipy.ndimage import convolve
from skimage import filters
from skimage.exposure import rescale_intensity
from skimage.transform import rotate


def membrane_projection(image):
"""
Expand All @@ -23,7 +24,8 @@ def membrane_projection(image):

Returns
-------
6 features for
feature_stack: Numy Array
6 membrane projection features

"""
kernel = np.zeros([19,19])
Expand All @@ -46,7 +48,35 @@ def membrane_projection(image):
return feature_stack

def laplacian(image):
"""
creates the laplacian feature

Parameters
----------
image : greyscale image for feature creation.

Returns
-------
feature_stack: Numpy Array
6 membrane projection features

"""
kernel = np.array([[0,-1,0],[-1,4,-1],[0,-1,0]])
return custom_kernel(kernel=kernel)

def custom_kernel(image, kernel):
"""
convolves an image with a custom kernel

Parameters
----------
image : greyscale image for feature creation.

Returns
-------
feature_stack: Numpy Array
6 membrane projection features

"""
convolved = convolve(image,kernel)
return convolved
39 changes: 24 additions & 15 deletions particlespy/particle_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,33 @@
@author: qzo13262
"""

from particlespy.segptcls import process
import inspect
import os
import warnings

import h5py
import numpy as np
from particlespy.ptcl_class import particle, particle_list
from particlespy.custom_kernels import membrane_projection
import pandas as pd
import trackpy
from skimage import filters, morphology
from skimage.measure import label, regionprops, perimeter
from skimage.measure import label, perimeter, regionprops
from sklearn import preprocessing
from sklearn.cluster import DBSCAN, KMeans

import particlespy.find_zoneaxis as zone
import warnings
import h5py
import inspect
import pandas as pd
import trackpy
import os
from particlespy.custom_kernels import membrane_projection
from particlespy.ptcl_class import particle, particle_list
from particlespy.segptcls import process


def particle_analysis(acquisition,parameters,particles=None,mask=np.zeros((1))):
"""
Perform segmentation and analysis of images of particles.

Parameters
----------
acquisition: Hyperpsy signal object or list of hyperspy signal objects.
Hyperpsy signal object containing a nanoparticle image or a list of signal
acquisition: Hyperspy signal object or list of hyperspy signal objects.
Hyperspy signal object containing a nanoparticle image or a list of signal
objects that contains an image at position 0 and other datasets following.
parameters: Dictionary of parameters
The parameters can be input manually in to a dictionary or can be generated
Expand Down Expand Up @@ -391,7 +394,8 @@ def __init__(self,gaussian = [True,1], diff_gaussian = [True,[False,1],1,16],
median = [True,[False,1],20], minimum = [True,[False,1],20],
maximum = [True,[False,1],20], sobel = [True,[True,1]],
hessian = [False,[False,1]], laplacian = [False,[False,1]],
membrane = [[False,1],True,False,False,False,False,False]):
membrane = [[False,1],True,False,False,False,False,False],
custom = [False,[False,1],[[0,0,0],[0,1,0],[0,0,0]]]):
self.gaussian = gaussian
self.diff_gaussian = diff_gaussian
self.median = median
Expand All @@ -401,6 +405,7 @@ def __init__(self,gaussian = [True,1], diff_gaussian = [True,[False,1],1,16],
self.hessian = hessian
self.laplacian = laplacian
self.membrane = membrane
self.custom = custom

def set_global_sigma(self, sigma):
self.gaussian[1] = sigma
Expand All @@ -426,7 +431,8 @@ def set_global_prefilter(self, sigma):
self.sobel[1] = [True,sigma]
self.hessian[1] = [True,sigma]
self.laplacian[1] = [True,sigma]
self.membrane[0] = [True,sigma]
self.membrane[0] = [True,sigma]
self.custom[1] = [True,sigma]

def set_gaussian(self,enabled = True, sigma = 1):
self.gaussian=[enabled,sigma]
Expand All @@ -452,5 +458,8 @@ def set_hessian(self,enabled = True, prefilter=True, prefilter_sigma = 1):
def set_laplacian(self,enabled = True, prefilter=True, prefilter_sigma = 1):
self.laplacian=[enabled,[prefilter,prefilter_sigma]]

def set_membrane(self,enabled = True, prefilter=True, prefilter_sigma = 1,summ = True,mean = True, stddev = True, maximum = True, minimum = True, median = True):
def set_membrane(self, prefilter=True, prefilter_sigma = 1,summ = True,mean = True, stddev = True, maximum = True, minimum = True, median = True):
self.membrane=[[prefilter, prefilter_sigma], summ, mean, stddev, maximum, minimum, median]

def set_custom(self, enabled=True, prefilter = False, prefilter_sigma = 1, conv_matrix=[[0,0,0],[0,1,0],[0,0,0]]):
self.custom = [enabled,[prefilter,prefilter_sigma],conv_matrix]
40 changes: 27 additions & 13 deletions particlespy/seg_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,6 @@ def __init__(self,im_hs,height):
im_lay = QVBoxLayout()

self.button_lay = QVBoxLayout()
self.button_lay.setSpacing(0)
self.button_lay.setContentsMargins(0,0,0,0)

lay3.addLayout(self.button_lay)
lay3.addLayout(im_lay)
Expand Down Expand Up @@ -258,20 +256,17 @@ def __init__(self,im_hs,height):
self.button_lay.addLayout(self.colour_lay)
im_lay.addWidget(self.canvas2)

self.kerneltxt = QLabel(self)
self.kerneltxt.setText('Classifier')
self.button_lay.addWidget(self.kerneltxt)

self.clfBox = QComboBox(self)
self.clfBox.addItem("Random Forest")
self.clfBox.addItem("Nearest Neighbours")
self.clfBox.addItem("Naive Bayes")
self.clfBox.addItem("QDA")
self.clfBox.activated[str].connect(self.classifier_choice)
self.button_lay.addWidget(self.clfBox)









fk_lay = QVBoxLayout(self)
fk_lay.setVerticalSpacing(0)
self.kerneltxt = QLabel(self)
self.kerneltxt.setText('Filter Kernels')
fk_lay.addWidget(self.kerneltxt)
Expand All @@ -295,6 +290,25 @@ def __init__(self,im_hs,height):

self.button_lay.addLayout(fk_lay)


self.clf_lay = QVBoxLayout()
self.kerneltxt = QLabel(self)
self.kerneltxt.setText('Classifier')
self.clf_lay.addWidget(self.kerneltxt)

self.clfBox = QComboBox(self)
self.clfBox.addItem("Random Forest")
self.clfBox.addItem("Nearest Neighbours")
self.clfBox.addItem("Naive Bayes")
self.clfBox.addItem("QDA")
self.clfBox.activated[str].connect(self.classifier_choice)
self.clf_lay.addWidget(self.clfBox)

self.button_lay.addLayout(self.clf_lay)




fkp_lay = QVBoxLayout()
self.ql1 = QLabel(self)
self.ql1.setText('Sigma')
Expand Down
66 changes: 60 additions & 6 deletions particlespy/segimgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from skimage.measure import label, perimeter, regionprops
from sklearn import preprocessing

from particlespy.custom_kernels import laplacian, membrane_projection
from particlespy.custom_kernels import (custom_kernel, laplacian,
membrane_projection)
from particlespy.particle_analysis import trainable_parameters


Expand All @@ -17,6 +18,8 @@ def create_features(image, parameters=None):
----------
image : greyscale image for segmentation
trainable segmentation parameters

parameters : trainable parameters object for setting filter kernels and their parameters

Returns
-------
Expand Down Expand Up @@ -90,11 +93,10 @@ def create_features(image, parameters=None):

if parameters.laplacian[0]:
par = parameters.laplacian
blur = image
if par[1][0]:
blur = filters.gaussian(image,par[1][1])
new_layer = np.reshape(laplacian(blur),shape)
else:
new_layer = np.reshape(filters.laplacian(image),shape)
new_layer = np.reshape(laplacian(blur),shape)
image_stack = np.concatenate((image_stack, new_layer), axis=2)

if True in parameters.membrane[1:]:
Expand All @@ -110,6 +112,14 @@ def create_features(image, parameters=None):
mem_layers = np.squeeze(mem_layers)
image_stack = np.append(image_stack, mem_layers, axis=2)

if parameters.custom[0]:
par = parameters.custom
blur = image
if par[1][0]:
blur = filters.gaussian(image,par[1][1])
new_layer = np.reshape(custom_kernel(blur,np.asarray(par[2])), shape)
image_stack = np.append(image_stack, new_layer, axis=2)

return image_stack[:,:,1:]

def cluster_learn(images, clust, parameters = None):
Expand Down Expand Up @@ -253,7 +263,7 @@ def classifier_segment(classifier, image, parameters = None):

Returns
-------
mask of labels (1channel)
mask of labels (1 channel, indexed)
"""
features = create_features(image, parameters=parameters)
features = np.rot90(np.rot90(features, axes=(2,0)), axes=(1,2))
Expand All @@ -267,6 +277,19 @@ def classifier_segment(classifier, image, parameters = None):


def toggle_channels(image, colors = ['#A30015', '#6DA34D', '#51E5FF', '#BD2D87', '#F5E663']):
"""
changes a 3 channel RGB image into a 1 channel indexed image, and vice versa

Parameters
----------
image : 1/3 channel image for conversion
colors : ordered list of colors to index labels by

Returns
----------
toggled 1/3 channel image

"""
#colors are in RGB format
shape = image.shape

Expand All @@ -285,12 +308,43 @@ def toggle_channels(image, colors = ['#A30015', '#6DA34D', '#51E5FF', '#BD2D87',
return toggled

def remove_large_objects(ar, max_size=200, connectivity=1, in_place=False):
"""Remove objects larger than the specified size.

Expects ar to be an array with labeled objects, and removes objects
larger than max_size. If `ar` is bool, the image is first labeled.
This leads to potentially different behavior for bool and 0-and-1
arrays.

Parameters
----------
ar : ndarray (arbitrary shape, int or bool type)
The array containing the objects of interest. If the array type is
int, the ints must be non-negative.
max_size : int, optional (default: 200)
The largest allowable object size.
connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1)
The connectivity defining the neighborhood of a pixel. Used during
labelling if `ar` is bool.
in_place : bool, optional (default: False)
If ``True``, remove the objects in the input array itself.
Otherwise, make a copy.

Raises
------
TypeError
If the input array is of an invalid type, such as float or string.
ValueError
If the input array contains negative values.

Returns
-------
out : ndarray, same shape and type as input `ar`
The input array with small connected components removed.
# Raising type error if not int or bool
if not (ar.dtype == bool or np.issubdtype(ar.dtype, np.integer)):
raise TypeError("Only bool or integer image types are supported. "
"Got %s." % ar.dtype)

"""
if in_place:
out = ar
else:
Expand Down