Skip to content

Commit

Permalink
Simplify laser position and speed detection
Browse files Browse the repository at this point in the history
For big images the laser position and speed detection took a long time so it was simplified to only be manual.
  • Loading branch information
faymanns committed Oct 11, 2023
1 parent 8945d6f commit 7517e45
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 110 deletions.
52 changes: 23 additions & 29 deletions src/napari_melt_pool_tracker/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@

import numpy as np
import pandas as pd
import scipy
import skimage
import sklearn.svm


def determine_laser_speed_and_position(
stack,
kernel_size_img=5,
kernel_size_max=9,
):
def determine_laser_speed_and_position(stack):
"""
Infers the laser position and speed by fitting a
line in the spatio tempol resliced version of the
Expand All @@ -22,39 +16,39 @@ def determine_laser_speed_and_position(
stack: np.ndarray
The full size original images with one laser pass
from right to left.
kernel_size_img: int
The length of the median filter applied along the
direction of the laser.
kernel_size_max: int
The length of the median filter applied to the maxima.
Returns
-------
proj_resliced : numpy array
Resliced image with the height of the image cores
coef : float
The coefficient determining the slope of the line fitted.
intecept : float
The intercept of the line fitted.
"""
# stack_median = stack / np.median(stack, axis=0)[np.newaxis, :, :]
stack_mean = stack / np.mean(stack, axis=0)[np.newaxis, :, :]
resliced = np.swapaxes(stack, 0, 2)
# resliced_median = np.swapaxes(stack_median, 0, 2)
resliced_mean = np.swapaxes(stack_mean, 0, 2)
proj_resliced = np.max(resliced, axis=1)
footprint = (
np.eye(kernel_size_img, dtype=np.uint8)[:, ::-1]
+ np.eye(kernel_size_img, dtype=np.uint8, k=1)[:, ::-1]
+ np.eye(kernel_size_img, dtype=np.uint8, k=-1)[:, ::-1]
# proj_resliced_median = np.max(resliced_median, axis=1)
proj_resliced_mean = np.max(resliced_mean, axis=1)
proj_resliced_median = (
proj_resliced / np.median(proj_resliced, axis=1)[:, np.newaxis]
)
# proj_resliced_mean = (
# proj_resliced / np.mean(proj_resliced, axis=1)[:, np.newaxis]
# )
intercept = 0
coef = proj_resliced.shape[0] / proj_resliced.shape[1]
return (
proj_resliced,
proj_resliced_median,
proj_resliced_mean,
coef,
intercept,
)
proj_resliced = skimage.filters.median(proj_resliced, footprint=footprint)
proj_resliced = skimage.filters.sobel_h(proj_resliced)
# proj_resliced = proj_resliced[10:]
maxima = proj_resliced.argmax(0) # + 10
maxima = scipy.signal.medfilt(maxima, kernel_size=kernel_size_max)

x = np.arange(len(maxima))
svm = sklearn.svm.SVR(kernel="linear")
svm.fit(x[:, np.newaxis], maxima)

intercept = svm.intercept_[0]
coef = svm.coef_[0][0]
return proj_resliced, maxima, coef, intercept


def reslice_with_moving_window(
Expand Down
142 changes: 61 additions & 81 deletions src/napari_melt_pool_tracker/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,7 @@ def __init__(self, napari_viewer):
#####################
self.speed_pos_groupbox = StepWidget(
name="2. Determine laser speed and position",
include_auto_run_and_overwrite=True,
sliders={
"Median filter img": (1, 49, 5),
"Median filter max": (1, 49, 9),
},
)
self.speed_pos_groupbox.sliders[
"Median filter img"
].valueChanged.connect(self._determine_laser_speed_and_position)
self.speed_pos_groupbox.sliders[
"Median filter max"
].valueChanged.connect(self._determine_laser_speed_and_position)
self.speed_pos_groupbox.btn.clicked.connect(
self._determine_laser_speed_and_position
)
Expand All @@ -103,16 +92,15 @@ def __init__(self, napari_viewer):
name="3. Reslice with moving window",
include_auto_run_and_overwrite=True,
sliders={
"Left margin": (10, 100, 30),
"Right margin": (10, 200, 100),
"Left margin": (10, 350, 30),
"Right margin": (10, 350, 100),
},
)
self.window_groupbox.sliders["Left margin"].valueChanged.connect(
self._reslice_with_moving_window
)
self.window_groupbox.sliders["Right margin"].valueChanged.connect(
self._reslice_with_moving_window
self.window_groupbox.auto_run_cb.stateChanged.connect(
self._reslice_auto_run
)
# Connect slider to match default of auto run checked
self._reslice_auto_run()
self.window_groupbox.btn.clicked.connect(
self._reslice_with_moving_window
)
Expand All @@ -129,15 +117,11 @@ def __init__(self, napari_viewer):
"Kernel x": (1, 15, 3),
},
)
self.filter_groupbox.sliders["Kernel t"].valueChanged.connect(
self._filter
)
self.filter_groupbox.sliders["Kernel y"].valueChanged.connect(
self._filter
)
self.filter_groupbox.sliders["Kernel x"].valueChanged.connect(
self._filter
self.filter_groupbox.auto_run_cb.stateChanged.connect(
self._filter_auto_run
)
# Connect slider to match default of auto run checked
self._filter_auto_run()
self.filter_groupbox.btn.clicked.connect(self._filter)

#####################
Expand Down Expand Up @@ -223,69 +207,36 @@ def _determine_laser_speed_and_position(self):
layer = selected_layers.pop()
self.parameters["name"] = layer.name

if self.speed_pos_groupbox.overwrite_cb.isChecked():
layer_names = [
f"{self.parameters['name']}_proj",
f"{self.parameters['name']}_points",
f"{self.parameters['name']}_line",
]
for layer_name in layer_names:
if layer_name in self.viewer.layers:
self.viewer.layers.remove(layer_name)
layer_names = [
f"{self.parameters['name']}_proj",
f"{self.parameters['name']}_line",
]
for layer_name in layer_names:
if layer_name in self.viewer.layers:
self.viewer.layers.remove(layer_name)

stack = self.viewer.layers[self.parameters["name"]].data

kernel_size_img = self.speed_pos_groupbox.sliders[
"Median filter img"
].value()
kernel_size_max = self.speed_pos_groupbox.sliders[
"Median filter max"
].value()
# Make kernel sizes odd
if kernel_size_img % 2 == 0:
kernel_size_img -= 1
if kernel_size_max % 2 == 0:
kernel_size_max -= 1

(
proj_resliced,
maxima,
proj_resliced_median,
proj_resliced_avg,
coef,
intercept,
) = _utils.determine_laser_speed_and_position(
stack,
kernel_size_img=kernel_size_img,
kernel_size_max=kernel_size_max,
)
) = _utils.determine_laser_speed_and_position(stack)

x0, x1 = 0, proj_resliced.shape[1]
y0 = coef * x0 + intercept
y1 = coef * x1 + intercept

y0 = 0
x0 = (y0 - intercept) / coef
if x0 < 0:
x0 = 0
y0 = intercept
elif x0 > proj_resliced.shape[1]:
x0 = proj_resliced.shape[1]
y0 = coef * x0 + intercept

y1 = proj_resliced.shape[0]
x1 = (y1 - intercept) / coef
if x1 < 0:
x1 = 0
y1 = intercept
elif x1 > proj_resliced.shape[0]:
x1 = proj_resliced.shape[0]
y1 = coef * x1 + intercept

self.points = np.stack([maxima, np.arange(len(maxima))], axis=-1)
self.viewer.add_image(
proj_resliced, name=f"{self.parameters['name']}_proj"
)
self.viewer.add_points(
data=self.points,
face_color="cyan",
size=8,
opacity=0.2,
name=f"{self.parameters['name']}_points",
self.viewer.add_image(
proj_resliced_median, name=f"{self.parameters['name']}_proj_median"
)
self.viewer.add_image(
proj_resliced_avg, name=f"{self.parameters['name']}_proj_avg"
)
self.viewer.add_shapes(
data=[[y0, x0], [y1, x1]],
Expand All @@ -297,6 +248,22 @@ def _determine_laser_speed_and_position(self):
)
self.viewer.layers[self.parameters["name"]].visible = False

def _reslice_auto_run(self):
if self.window_groupbox.auto_run_cb.isChecked():
self.window_groupbox.sliders["Left margin"].valueChanged.connect(
self._reslice_with_moving_window
)
self.window_groupbox.sliders["Right margin"].valueChanged.connect(
self._reslice_with_moving_window
)
else:
self.window_groupbox.sliders[
"Left margin"
].valueChanged.disconnect()
self.window_groupbox.sliders[
"Right margin"
].valueChanged.disconnect()

def _reslice_with_moving_window(self):
stack = self.viewer.layers[f"{self.parameters['name']}"].data

Expand Down Expand Up @@ -355,7 +322,6 @@ def _reslice_with_moving_window(self):
)
self.viewer.layers[f"{self.parameters['name']}"].visible = False
self.viewer.layers[f"{self.parameters['name']}_proj"].visible = False
self.viewer.layers[f"{self.parameters['name']}_points"].visible = False
self.viewer.layers[f"{self.parameters['name']}_line"].visible = False

def _filter(self):
Expand All @@ -374,12 +340,27 @@ def _filter(self):
)
self.viewer.layers[f"{self.parameters['name']}"].visible = False
self.viewer.layers[f"{self.parameters['name']}_proj"].visible = False
self.viewer.layers[f"{self.parameters['name']}_points"].visible = False
self.viewer.layers[f"{self.parameters['name']}_line"].visible = False
self.viewer.layers[
f"{self.parameters['name']}_resliced"
].visible = False

def _filter_auto_run(self):
if self.filter_groupbox.auto_run_cb.isChecked():
self.filter_groupbox.sliders["Kernel t"].valueChanged.connect(
self._filter
)
self.filter_groupbox.sliders["Kernel y"].valueChanged.connect(
self._filter
)
self.filter_groupbox.sliders["Kernel x"].valueChanged.connect(
self._filter
)
else:
self.filter_groupbox.sliders["Kernel t"].valueChanged.disconnect()
self.filter_groupbox.sliders["Kernel y"].valueChanged.disconnect()
self.filter_groupbox.sliders["Kernel x"].valueChanged.disconnect()

def _calculate_radial_gradient(self):
stack = self.viewer.layers[
f"{self.parameters['name']}_resliced_filtered"
Expand All @@ -393,7 +374,6 @@ def _calculate_radial_gradient(self):
)
self.viewer.layers[f"{self.parameters['name']}"].visible = False
self.viewer.layers[f"{self.parameters['name']}_proj"].visible = False
self.viewer.layers[f"{self.parameters['name']}_points"].visible = False
self.viewer.layers[f"{self.parameters['name']}_line"].visible = False
self.viewer.layers[
f"{self.parameters['name']}_resliced"
Expand Down

0 comments on commit 7517e45

Please sign in to comment.