Background detect fraction as a thing
This commit is contained in:
parent
cb916bb4a3
commit
a53062af83
1 changed files with 27 additions and 14 deletions
|
|
@ -182,6 +182,15 @@ class BackgroundDetectThing(Thing):
|
||||||
def tolerance(self, value: float) -> None:
|
def tolerance(self, value: float) -> None:
|
||||||
self.thing_settings["tolerance"] = value
|
self.thing_settings["tolerance"] = value
|
||||||
|
|
||||||
|
@thing_property
|
||||||
|
def fraction(self) -> float:
|
||||||
|
"""How much of the image needs to be not background to label as sample"""
|
||||||
|
return self.thing_settings.get("fraction", 7)
|
||||||
|
|
||||||
|
@fraction.setter
|
||||||
|
def fraction(self, value: float) -> None:
|
||||||
|
self.thing_settings["fraction"] = value
|
||||||
|
|
||||||
def background_mask(self, image: np.ndarray) -> np.ndarray:
|
def background_mask(self, image: np.ndarray) -> np.ndarray:
|
||||||
"""Calculate a binary image, showing whether each pixel is background
|
"""Calculate a binary image, showing whether each pixel is background
|
||||||
|
|
||||||
|
|
@ -204,17 +213,24 @@ class BackgroundDetectThing(Thing):
|
||||||
This action will acquire a new image from the preview stream, then
|
This action will acquire a new image from the preview stream, then
|
||||||
evaluate whether it is foreground or background, by comparing it
|
evaluate whether it is foreground or background, by comparing it
|
||||||
too the saved statistics. This is done on a per-pixel basis, and
|
too the saved statistics. This is done on a per-pixel basis, and
|
||||||
the returned value (between 0 and 1) is the fraction of the image
|
the returned value (between 0 and 100) is the fraction of the image
|
||||||
that is background.
|
that is background.
|
||||||
"""
|
"""
|
||||||
background = cam.grab_jpeg()
|
current_image = cam.grab_jpeg()
|
||||||
background = np.array(Image.open(background.open()))
|
current_image = np.array(Image.open(current_image.open()))
|
||||||
|
|
||||||
# we're working in the LUV colourspace as it collect colours together in a human-intuitive way
|
# we're working in the LUV colourspace as it collect colours together in a human-intuitive way
|
||||||
background_LUV = cv2.cvtColor(background, cv2.COLOR_RGB2LUV)
|
current_image_LUV = cv2.cvtColor(current_image, cv2.COLOR_RGB2LUV)
|
||||||
mask = self.background_mask(background_LUV)
|
mask = self.background_mask(current_image_LUV)
|
||||||
return np.count_nonzero(mask) / np.prod(mask.shape)
|
return np.count_nonzero(mask) / np.prod(mask.shape) * 100
|
||||||
|
|
||||||
|
@thing_action
|
||||||
|
def image_is_sample(self, cam: CamDep) -> bool:
|
||||||
|
"""Label the current image as either background or sample"""
|
||||||
|
b_fraction = self.background_fraction(cam)
|
||||||
|
fraction_threshold = self.fraction
|
||||||
|
|
||||||
|
return (100 - b_fraction) > fraction_threshold
|
||||||
|
|
||||||
@thing_action
|
@thing_action
|
||||||
def set_background(self, cam: CamDep):
|
def set_background(self, cam: CamDep):
|
||||||
|
|
@ -253,6 +269,7 @@ class BackgroundDetectThing(Thing):
|
||||||
return {
|
return {
|
||||||
"background_distributions": bd.model_dump() if bd else None,
|
"background_distributions": bd.model_dump() if bd else None,
|
||||||
"tolerance": self.tolerance,
|
"tolerance": self.tolerance,
|
||||||
|
"fraction": self.fraction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -352,7 +369,6 @@ class SmartScanThing(Thing):
|
||||||
logger.info(f"Based on an overlap of {overlap}, we will make steps of {dx}, {dy}")
|
logger.info(f"Based on an overlap of {overlap}, we will make steps of {dx}, {dy}")
|
||||||
|
|
||||||
dz = 3000 # This is used for autofocus - make configurable?
|
dz = 3000 # This is used for autofocus - make configurable?
|
||||||
sample_coverage = 7 # TODO: make this configurable
|
|
||||||
|
|
||||||
# construct a 2D scan path
|
# construct a 2D scan path
|
||||||
path = [[stage.position["x"], stage.position["y"]]]
|
path = [[stage.position["x"], stage.position["y"]]]
|
||||||
|
|
@ -391,15 +407,12 @@ class SmartScanThing(Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the image is background
|
# Check if the image is background
|
||||||
background_fraction = background_detect.background_fraction()
|
image_is_sample = background_detect.image_is_sample()
|
||||||
background_coverage = round(
|
|
||||||
100 * background_fraction, 1
|
|
||||||
)
|
|
||||||
|
|
||||||
# if more than 92% of the image is background, treat it as background and continue
|
# if more than 92% of the image is background, treat it as background and continue
|
||||||
if 100 - background_coverage < sample_coverage:
|
if not image_is_sample:
|
||||||
category = "background"
|
category = "background"
|
||||||
logger.info(f"Skipping {stage.position} as it is {background_coverage}% background.")
|
logger.info(f"Skipping {stage.position} as it is {round(background_detect.background_fraction(),0)}% background.")
|
||||||
else:
|
else:
|
||||||
# if not, it's sample. run an autofocus and use the updated height
|
# if not, it's sample. run an autofocus and use the updated height
|
||||||
new_pos = [
|
new_pos = [
|
||||||
|
|
@ -484,7 +497,7 @@ class SmartScanThing(Thing):
|
||||||
if len(true_path) > 750:
|
if len(true_path) > 750:
|
||||||
break
|
break
|
||||||
except InvocationCancelledError:
|
except InvocationCancelledError:
|
||||||
logger.error("Stopping scan because it was cancelled.", exc_info=1)
|
logger.error("Stopping scan because it was cancelled.", exc_info=0)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Stopping scan because of an IOError (most likely a full disk): {e}",
|
f"Stopping scan because of an IOError (most likely a full disk): {e}",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue