From 7728c2207953c3b36e2fa315562694cd8332045a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mamede?= Date: Wed, 27 Sep 2023 21:37:44 -0500 Subject: [PATCH] updates, functional --- src/napari_trackpy/_widget.py | 52 +++- src/napari_trackpy/libs/support_libraries.py | 237 +++++++++++++++++++ 2 files changed, 277 insertions(+), 12 deletions(-) create mode 100644 src/napari_trackpy/libs/support_libraries.py diff --git a/src/napari_trackpy/_widget.py b/src/napari_trackpy/_widget.py index 8df3833..50d2288 100644 --- a/src/napari_trackpy/_widget.py +++ b/src/napari_trackpy/_widget.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: import napari + class IdentifyQWidget(QWidget): # your QWidget.__init__ can optionally request the napari viewer instance # in one of two ways: @@ -134,7 +135,7 @@ def __init__(self, napari_viewer): file_browse = QPushButton('Browse') file_browse.clicked.connect(self.open_file_dialog) self.filename_edit = QLineEdit() - self.filename_edit.setText("/tmp/test2.csv") + self.filename_edit.setText(self._get_open_filename()+"_Spots.csv") grid_layout = QGridLayout() grid_layout.addWidget(QLabel('File:'), 0, 0) grid_layout.addWidget(self.filename_edit, 0, 1) @@ -144,24 +145,42 @@ def __init__(self, napari_viewer): self._populate_layers() - self.viewer.layers.events.removed.connect(self._populate_layers) - self.viewer.layers.events.inserted.connect(self._populate_layers) - self.viewer.layers.events.reordered.connect(self._populate_layers) + self.viewer.layers.events.removed.connect(self._refresh_layers) + self.viewer.layers.events.inserted.connect(self._refresh_layers) + self.viewer.layers.events.reordered.connect(self._refresh_layers) # self._connect_layer() - + def _get_open_filename(self): + from napari.utils import history + _last_folder = history.get_open_history()[0] + for i in range(len(self.viewer.layers)-1,-1,-1): + if self.viewer.layers[i]._type_string == 'image': + _filename = self.viewer.layers[i].name.split(" :: ")[0] + _filename = _last_folder +"/"+ _filename + break + return _filename + def _populate_layers(self): + # self.layersbox.clear() + for layer in self.viewer.layers: + if layer._type_string == 'image': + self.layersbox.addItem(layer.name) + + def _refresh_layers(self): + i = self.layersbox.currentIndex() self.layersbox.clear() for layer in self.viewer.layers: if layer._type_string == 'image': self.layersbox.addItem(layer.name) + self.layersbox.setCurrentIndex(i) # def _connect_layer(self): # self.viewer.layers.events.changed.connect(self._populate_layers) def open_file_dialog(self): from pathlib import Path - filename, ok = QFileDialog.getSaveFileName( + filename, ok = QFileDialog.getSave + FileName( self, "Select a File", "/tmp/", @@ -198,7 +217,7 @@ def _on_click(self): self.f = self.f[ (self.f['ecc'] < self.ecc_input.value()) ] if self.size_filter_tick.isChecked(): - self.f = self.f[ (self.f['ecc'] < self.size_filter_input.value()) + self.f = self.f[ (self.f['size'] < self.size_filter_input.value()) ] print(self.f) @@ -224,7 +243,7 @@ def _on_click2(self): f2 = f2[ (f2['ecc'] < self.ecc_input.value()) ] if self.size_filter_tick.isChecked(): - f2 = f2[ (f2['ecc'] < self.size_filter_input.value()) + f2 = f2[ (f2['size'] < self.size_filter_input.value()) ] f2 = f2[(self.f['mass'] > self.mass_slider.value()) ] @@ -252,8 +271,6 @@ def _save_results(self): for key in b.keys(): df[key] = b[key] df.to_csv(self.filename_edit.text()) - - class LinkingQWidget(QWidget): # your QWidget.__init__ can optionally request the napari viewer instance @@ -319,7 +336,7 @@ def __init__(self, napari_viewer): file_browse = QPushButton('Browse') file_browse.clicked.connect(self.open_file_dialog) self.filename_edit_links = QLineEdit() - self.filename_edit_links.setText("/tmp/test3.csv") + self.filename_edit_links.setText(self._get_open_filename()+"_Tracks.csv") grid_layout = QGridLayout() grid_layout.addWidget(QLabel('File:'), 0, 0) grid_layout.addWidget(self.filename_edit_links, 0, 1) @@ -343,6 +360,16 @@ def open_file_dialog(self): path = Path(filename) self.filename_edit.setText(str(path)) + def _get_open_filename(self): + from napari.utils import history + _last_folder = history.get_open_history()[0] + for i in range(len(self.viewer.layers)-1,-1,-1): + if self.viewer.layers[i]._type_string == 'image': + _filename = self.viewer.layers[i].name.split(" :: ")[0] + _filename = _last_folder +"/"+ _filename + break + return _filename + def _save_results_links(self): import pandas as pd self.links.to_csv(self.filename_edit_links.text()) @@ -379,4 +406,5 @@ def _track(self): self.viewer.add_tracks(_tracks,name='trackpy') self.links = links - print(links) \ No newline at end of file + print(links) + diff --git a/src/napari_trackpy/libs/support_libraries.py b/src/napari_trackpy/libs/support_libraries.py new file mode 100644 index 0000000..e75d7f6 --- /dev/null +++ b/src/napari_trackpy/libs/support_libraries.py @@ -0,0 +1,237 @@ +import scipy.ndimage as ndi +#import cv2 +import numpy as np +#import matplotlib.pyplot as plt +#import pims +#import libtiff +#import time +#import csv +_8bit = float(2**8-1) +_16bit = float(2**16-1) +ratio = _8bit /_16bit + +def points_in_mask(coords,mask): + ''' + Input: + coords: list of coordinates (numpy) + mask: masked image with numbered labels (numpy) + + Return: + values_at_coords: with boolean array reporting which mask does the coordinate belongs to + ''' + import numpy as np + # coords_int = np.round(coords).astype(int) # or np.floor, depends + coords_int = np.floor(coords).astype(int) # or np.floor, depends + try: + values_at_coords = mask[tuple(coords_int.T)] + except: values_at_coords = [] + # .astype(np.int) + # values_at_coords = mask[tuple(coords_int)].astype(np.int) + print(values_at_coords) + return values_at_coords + + +def multiply(real_events, image): + import numpy as np + image = real_events*image + return image + +##keep for now for the #make masks section +def make_labels_trackpy(image,mass,size=9,_separation=3,_numba=True,max_mass=0,_round=True): + import trackpy as tp + import scipy.ndimage as ndi + from scipy.ndimage.morphology import binary_dilation + + if image.ndim == 2: + _size = size + elif image.ndim == 3: + _size = (3, size, size) + # _size = (9, size, size) + # ~ dotrack(ficheiro, plotgraph=True,_numba=True,massa=1500,tamanho=13,dist=250,memoria=1,stub=3,frame=19,colourIDX=0): + if image.ndim == 2: + if _numba: + f = tp.locate(image,diameter=size,separation=_separation,minmass=mass,engine='numba') + else: + f = tp.locate(image,diameter=size,separation=_separation, minmass=mass) + elif image.ndim == 3: + if _numba: + f = tp.locate(image,diameter=_size,separation = (3, 3, 3), + minmass=mass,engine='numba') + else: + f = tp.locate(image,diameter=_size,separation = (3, 3, 3), + minmass=mass) + # size = (11, 13, 13) + + if max_mass > 0: + f = f[f['mass'] <= max_mass] + #outputsomehow is 3D, we want 2 + if image.ndim == 2: + coords = np.dstack((round(f.y),round(f.x)))[0].astype(int) + elif image.ndim == 3: + coords = np.dstack((round(f.z),round(f.y),round(f.x)))[0].astype(int) + + + + #this is super slow + # ~ masks = tp.masks.mask_image(coords,np.ones(image.shape),size/2) + + #This is faster + if image.ndim == 2: + r = (size-1)/2 # Radius of circles + #make 3D compat + disk_mask = tp.masks.binary_mask(r,image.ndim) + # Initialize output array and set the maskcenters as 1s + out = np.zeros(image.shape,dtype=bool) + #check if there's a problem with subpixel masking + out[coords[:,0],coords[:,1]] = 1 + # Use binary dilation to get the desired output + out = binary_dilation(out,disk_mask) + labels, nb = ndi.label(out) + + if _round: + return labels, coords + else: + if image.ndim == 2: + coords = np.dstack((f.y,f.x))[0] + return labels, coords + elif image.ndim == 3: + coords = np.dstack((f.z,f.y,f.x))[0] + return None, coords + + + +def make_labels_trackpy_links(image,j,size=5): + import trackpy as tp + import scipy.ndimage as ndi + from scipy.ndimage.morphology import binary_dilation + + + #outputsomehow is 3D, we want 2 + coords = np.dstack((round(j.y),round(j.x)))[0].astype(int) + + #this is super slow + # ~ masks = tp.masks.mask_image(coords,np.ones(image.shape),size/2) + + #This is faster + r = (size-1)/2 # Radius of circles + #make 3D compat + disk_mask = tp.masks.binary_mask(r,image.ndim) + # Initialize output array and set the maskcenters as 1s + out = np.zeros(image.shape,dtype=bool) + #check if there's a problem with subpixel masking + out[coords[:,0],coords[:,1]] = 1 + # Use binary dilation to get the desired output + out = binary_dilation(out,disk_mask) + + + labels, nb = ndi.label(out) + + return labels, coords + +def simple_labels(image): + labels, nb = ndi.label(image) + return labels + +def mean_calc(values,value_max): + import numpy as np + #Only the ones above threshold will be averaged don't worry about + #the b>value_max because the threshold is the same. + a = np.asarray([value['MeanIntensity'] for value in values]) + b = np.asarray([value['MaxIntensity'] for value in values]) + return np.mean(a[b > value_max]) + +def mean_calc_total(values): + import numpy as np + return np.mean([value['MeanIntensity'] for value in values]) + +def max_calc_total(values): + import numpy as np + return np.mean([value['MaxIntensity'] for value in values]) + +def listify(data,datatype): + if datatype == 'max': datatype = 'MaxIntensity' + elif datatype == 'mean': datatype = 'MeanIntensity' + return np.asarray([value[datatype] for value in data]) + +def rebin(arr, new_shape): + from PIL import Image + return np.array(Image.fromarray(arr).resize(new_shape,resample=Image.NEAREST)) + +#~ @v.parallel(block=True) +def othercolor(colour,labels): + import numpy as np + import scipy.ndimage as ndi + #This gives an array with max mean and stdev values per each label + + data = {} + data['max'] = ndi.maximum(colour,labels,np.arange(1,labels.max()+1)) + data['mean'] = ndi.mean(colour,labels,np.arange(1,labels.max()+1)) + data['stdev'] = ndi.standard_deviation(colour,labels,np.arange(1,labels.max()+1)) + data['median'] = ndi.median(colour,labels,np.arange(1,labels.max()+1)) + try: + temp = np.array(ndi.measurements.center_of_mass( + colour,labels,np.arange(1,labels.max()+1))) + # ~ print(temp[:,0]) + data['COMY'] = temp[:,0] + data['COMX'] = temp[:,1] + except: 'Something went wrong with centroids' + return data + + +def othercolor2(colour,labels,treshold): + import numpy as np + import scipy.ndimage as ndi + '''Output: A dict() with max mean and stdev values per each label''' + + data = {} + data['max'] = ndi.maximum(colour,labels,np.arange(1,labels.max()+1)) + data['mean'] = ndi.mean(colour,labels,np.arange(1,labels.max()+1)) + data['stdev'] = ndi.standard_deviation(colour,labels,np.arange(1,labels.max()+1)) + data['median'] = ndi.median(colour,labels,np.arange(1,labels.max()+1)) + data['mean_pos'] = data['mean'][data['max'] > treshold] + data['median_pos'] = data['median'][data['max'] > treshold] + data['stdev_pos'] = data['stdev'][data['max'] > treshold] + data['max_pos'] = data['max'][data['max'] > treshold] + + return data + +def contrast_img(img,min_,max_ ): + img[img>max_]=max_ + img[img