Skip to content
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

Improvements to hypothesis tab #378

Merged
merged 29 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8c001e1
Rename tab
paddyroddy Aug 7, 2023
ba88a7e
Rename functions to reflect name change
paddyroddy Aug 7, 2023
c54584e
Add flag to enable optimisation
paddyroddy Aug 7, 2023
df6754d
Add optimisation button
paddyroddy Aug 7, 2023
aec5f0d
Add optimise to reset button test
paddyroddy Aug 7, 2023
d9f7e1d
Merge branch 'main' into fix-issue-371
paddyroddy Aug 7, 2023
127b20e
Merge branch 'main' into fix-issue-371
paddyroddy Aug 7, 2023
ac59272
Change the order of update methods to reflect new tabs
paddyroddy Aug 8, 2023
aa2ff36
Rename enable optimisation
paddyroddy Aug 8, 2023
3b33318
Add `enable_optimisation` to the `pydantic` model
paddyroddy Aug 8, 2023
2eca6af
Re-use constant
paddyroddy Aug 8, 2023
ce3f8fe
Merge branch 'main' into fix-issue-371
paddyroddy Aug 8, 2023
ff0a886
`|` for the win
paddyroddy Aug 8, 2023
9f2a0c4
Add back in `relax` and use `isChecked` rather than `value`
paddyroddy Aug 8, 2023
ea947a8
Change to `.checkState(`
paddyroddy Aug 8, 2023
622a82b
Restore the test value to `0.001`
paddyroddy Aug 8, 2023
c332ad2
Add a warning if the user incorrectly optimises
paddyroddy Aug 8, 2023
9abda19
Rename tab to `Basic`
paddyroddy Aug 8, 2023
162bd97
Fix `prob_not_assign` coming from `motion_model`
paddyroddy Aug 8, 2023
deec4ce
Volume doesn't seem to be set correctly
paddyroddy Aug 8, 2023
17ea87b
Fix names in the test
paddyroddy Aug 8, 2023
c8de9c9
Revert volume change
paddyroddy Aug 8, 2023
14b2ed8
Adjust config file before running `configure`
paddyroddy Aug 8, 2023
2b5e0d3
Update widget tooltip
paddyroddy Aug 9, 2023
fc9bb9c
Make logo smaller
paddyroddy Aug 9, 2023
09b4568
Move scale variable
paddyroddy Aug 9, 2023
39c5193
Callback to disable optimiser tab by name
paddyroddy Aug 9, 2023
6964f3d
Capitals for the win
paddyroddy Aug 9, 2023
1dad9cb
Move track widget above config
paddyroddy Aug 9, 2023
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
4 changes: 4 additions & 0 deletions btrack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class TrackerConfig(BaseModel):
tracking_updates : list
A list of features to be used for tracking, such as MOTION or VISUAL.
Must have at least one entry.
enable_optimisation
A flag which, if `False`, will report a warning to the user if they then
subsequently run the `BayesianTracker.optimise()` step.

Notes
-----
Expand All @@ -92,6 +95,7 @@ class TrackerConfig(BaseModel):
) = [
constants.BayesianUpdateFeatures.MOTION,
]
enable_optimisation = True

@validator("volume", pre=True, always=True)
def _parse_volume(cls, v):
Expand Down
3 changes: 3 additions & 0 deletions btrack/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ def optimise(self, options: Optional[dict] = None) -> list[hypothesis.Hypothesis
optimiser and then performs track merging, removal of track fragments,
renumbering and assignment of branches.
"""
if not self.configuration.enable_optimisation:
logger.warning("The `enable_optimisation` flag is set to False")

logger.info(f"Loading hypothesis model: {self.hypothesis_model.name}")

logger.info(f"Calculating hypotheses (relax: {self.hypothesis_model.relax})...")
Expand Down
23 changes: 18 additions & 5 deletions btrack/napari/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def create_btrack_widget() -> btrack.napari.widgets.BtrackWidget:
lambda selected: select_config(btrack_widget, all_configs, selected),
)

# Disable the Optimiser tab if unchecked
for tab in range(btrack_widget._tabs.count()):
if btrack_widget._tabs.tabText(tab) == "Optimiser":
break
btrack_widget.enable_optimisation.toggled.connect(
lambda is_checked: btrack_widget._tabs.setTabEnabled(tab, is_checked)
)
Comment on lines +83 to +89
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the best way I could get it without relying on index @p-j-smith

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paddyroddy I think that's an okay way to do it. Or if you prefer you can get the index of one of the tabs using the widget we put in that tab. As we're not storing the widget itself, you could use one of the child widgets (e.g. hypotheses) and find its parent:

optimiser_tab_index = btrack_widget._tabs.indexOf(btrack_widget.hypotheses.parentWidget())

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I saw that, but I'm not convinced it's any better! Especially as some widgets have moved tabs


btrack_widget.call_button.clicked.connect(
lambda: run(btrack_widget, all_configs),
)
Expand Down Expand Up @@ -264,7 +272,11 @@ def _run_tracker(
"""
Runs BayesianTracker with given segmentation and configuration.
"""
with btrack.BayesianTracker() as tracker, napari.utils.progress(total=5) as pbr:
num_steps = 5 if tracker_config.enable_optimisation else 4

with btrack.BayesianTracker() as tracker, napari.utils.progress(
total=num_steps
) as pbr:
pbr.set_description("Initialising the tracker")
tracker.configure(tracker_config)
pbr.update(1)
Expand All @@ -287,10 +299,11 @@ def _run_tracker(
tracker.track(step_size=100)
pbr.update(1)

# generate hypotheses and run the global optimizer
pbr.set_description("Run optimisation")
tracker.optimize()
pbr.update(1)
if tracker.enable_optimisation:
# generate hypotheses and run the global optimizer
pbr.set_description("Run optimisation")
tracker.optimize()
pbr.update(1)

# get the tracks in a format for napari visualization
pbr.set_description("Convert to napari tracks layer")
Expand Down
73 changes: 40 additions & 33 deletions btrack/napari/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,54 @@ def update_config_from_widgets(
btrack_widget: btrack.napari.widgets.BtrackWidget,
) -> UnscaledTrackerConfig:
"""Update an UnscaledTrackerConfig with the current widget values."""

# Update MotionModel matrix scaling factors
sigmas: Sigmas = unscaled_config.sigmas
for matrix_name in sigmas:
sigmas[matrix_name] = btrack_widget[f"{matrix_name}_sigma"].value()

# Update TrackerConfig values
## Retrieve model configs
config = unscaled_config.tracker_config
update_method_index = btrack_widget.update_method.currentIndex()
motion_model = config.motion_model
hypothesis_model = config.hypothesis_model

config.update_method = update_method_index
## Update widgets from the Method tab
config.update_method = btrack_widget.update_method.currentIndex()
config.max_search_radius = btrack_widget.max_search_radius.value()

# Update MotionModel values
motion_model = config.motion_model
motion_model.accuracy = btrack_widget.accuracy.value()
motion_model.max_lost = btrack_widget.max_lost.value()
motion_model.prob_not_assign = btrack_widget.prob_not_assign.value()
config.enable_optimisation = (
btrack_widget.enable_optimisation.checkState() == QtCore.Qt.CheckState.Checked
)

# Update HypothesisModel.hypotheses values
hypothesis_model = config.hypothesis_model
## Update widgets from the Motion tab
sigmas: Sigmas = unscaled_config.sigmas
for matrix_name in sigmas:
sigmas[matrix_name] = btrack_widget[f"{matrix_name}_sigma"].value()
motion_model.accuracy = btrack_widget.accuracy.value()

## Update widgets from the Optimiser tab
# HypothesisModel.hypotheses values
hypothesis_model.hypotheses = [
hypothesis
for i, hypothesis in enumerate(btrack.optimise.hypothesis.H_TYPES)
if btrack_widget["hypotheses"].item(i).checkState()
== QtCore.Qt.CheckState.Checked
]

# Update HypothesisModel scaling factors
# HypothesisModel scaling factors
for scaling_factor in btrack.napari.constants.HYPOTHESIS_SCALING_FACTORS:
setattr(
hypothesis_model,
scaling_factor,
btrack_widget[scaling_factor].value(),
)

# Update HypothesisModel thresholds
# HypothesisModel thresholds
for threshold in btrack.napari.constants.HYPOTHESIS_THRESHOLDS:
setattr(hypothesis_model, threshold, btrack_widget[threshold].value())

# other
hypothesis_model.segmentation_miss_rate = (
btrack_widget.segmentation_miss_rate.value()
)
hypothesis_model.relax = (
btrack_widget.relax.checkState() == QtCore.Qt.CheckState.Checked
)

return unscaled_config

Expand All @@ -75,25 +80,26 @@ def update_widgets_from_config(
Update the widgets in a btrack_widget with the values in an
UnscaledTrackerConfig.
"""

# Update widgets from MotionModel matrix scaling factors
sigmas: Sigmas = unscaled_config.sigmas
for matrix_name in sigmas:
btrack_widget[f"{matrix_name}_sigma"].setValue(sigmas[matrix_name])

# Update widgets from TrackerConfig values
## Retrieve model configs
config = unscaled_config.tracker_config
motion_model = config.motion_model
hypothesis_model = config.hypothesis_model

## Update widgets from the Method tab
btrack_widget.update_method.setCurrentText(config.update_method.name)
btrack_widget.max_search_radius.setValue(config.max_search_radius)

# Update widgets from MotionModel values
motion_model = config.motion_model
btrack_widget.accuracy.setValue(motion_model.accuracy)
btrack_widget.max_lost.setValue(motion_model.max_lost)
btrack_widget.prob_not_assign.setValue(motion_model.prob_not_assign)
btrack_widget.enable_optimisation.setChecked(config.enable_optimisation)

# Update widgets from HypothesisModel.hypotheses values
hypothesis_model = config.hypothesis_model
## Update widgets from the Motion tab
sigmas: Sigmas = unscaled_config.sigmas
for matrix_name in sigmas:
btrack_widget[f"{matrix_name}_sigma"].setValue(sigmas[matrix_name])
btrack_widget.accuracy.setValue(motion_model.accuracy)

## Update widgets from the Optimiser tab
# HypothesisModel.hypotheses values
for i, hypothesis in enumerate(btrack.optimise.hypothesis.H_TYPES):
is_checked = (
QtCore.Qt.CheckState.Checked
Expand All @@ -102,19 +108,20 @@ def update_widgets_from_config(
)
btrack_widget["hypotheses"].item(i).setCheckState(is_checked)

# Update widgets from HypothesisModel scaling factors
# HypothesisModel scaling factors
for scaling_factor in btrack.napari.constants.HYPOTHESIS_SCALING_FACTORS:
new_value = getattr(hypothesis_model, scaling_factor)
btrack_widget[scaling_factor].setValue(new_value)

# Update widgets from HypothesisModel thresholds
# HypothesisModel thresholds
for threshold in btrack.napari.constants.HYPOTHESIS_THRESHOLDS:
new_value = getattr(hypothesis_model, threshold)
btrack_widget[threshold].setValue(new_value)

btrack_widget.relax.setChecked(hypothesis_model.relax)
# other
btrack_widget.segmentation_miss_rate.setValue(
hypothesis_model.segmentation_miss_rate
)
btrack_widget.relax.setChecked(hypothesis_model.relax)

return btrack_widget
29 changes: 23 additions & 6 deletions btrack/napari/widgets/_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ def create_logo_widgets() -> dict[str, QtWidgets.QWidget]:
widgets = {"title": title}

logo = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(
str(Path(__file__).resolve().parents[1] / "assets" / "btrack_logo.png")
)
logo.setAlignment(QtCore.Qt.AlignHCenter)
scale = 0.8
logo.setPixmap(
QtGui.QPixmap(
str(Path(__file__).resolve().parents[1] / "assets" / "btrack_logo.png")
pixmap.scaled(
int(pixmap.width() * scale),
int(pixmap.height() * scale),
QtCore.Qt.KeepAspectRatio,
)
)
widgets["logo"] = logo
Expand Down Expand Up @@ -52,7 +59,7 @@ def create_input_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
return widgets


def create_update_method_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
def create_basic_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
"""Create widgets for selecting the update method"""

update_method = QtWidgets.QComboBox()
Expand Down Expand Up @@ -98,6 +105,16 @@ def create_update_method_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
not_assign,
)

optimise = QtWidgets.QCheckBox()
optimise.setChecked(True) # noqa: FBT003
optimise.setToolTip(
"Enable the track optimisation.\n"
"This means that tracks will be optimised using the hypotheses"
"specified in the optimiser tab."
)
optimise.setTristate(False) # noqa: FBT003
widgets["enable_optimisation"] = ("enable optimisation", optimise)

return widgets


Expand All @@ -114,9 +131,9 @@ def create_config_widgets() -> dict[str, QtWidgets.QWidget]:
"reset_button",
]
labels = [
"Load configuration",
"Save configuration",
"Reset defaults",
"Load Configuration",
"Save Configuration",
"Reset Defaults",
]
tooltips = [
"Load a TrackerConfig json file.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def _create_hypotheses_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:

widget = QtWidgets.QListWidget()
widget.addItems([f"{h.replace('_', '(')})" for h in hypotheses])
flags = QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsUserCheckable + QtCore.Qt.ItemIsEnabled)
flags = QtCore.Qt.ItemFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
for i, tooltip in enumerate(tooltips):
widget.item(i).setFlags(flags)
widget.item(i).setToolTip(tooltip)
Expand All @@ -37,12 +37,7 @@ def _create_hypotheses_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
def _create_scaling_factor_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
"""Create widgets for setting the scaling factors of the HypothesisModel"""

names = [
"lambda_time",
"lambda_dist",
"lambda_link",
"lambda_branch",
]
names = btrack.napari.constants.HYPOTHESIS_SCALING_FACTORS
labels = [
"λ time",
"λ distance",
Expand Down Expand Up @@ -118,7 +113,7 @@ def _create_bin_size_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
return widgets


def create_hypothesis_model_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
def create_optimiser_widgets() -> dict[str, tuple[str, QtWidgets.QWidget]]:
"""Create widgets for setting parameters of the HypothesisModel"""

widgets = {
Expand Down
24 changes: 12 additions & 12 deletions btrack/napari/widgets/create_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from napari.viewer import Viewer

from btrack.napari.widgets._general import (
create_basic_widgets,
create_config_widgets,
create_input_widgets,
create_logo_widgets,
create_track_widgets,
create_update_method_widgets,
)
from btrack.napari.widgets._hypothesis import create_hypothesis_model_widgets
from btrack.napari.widgets._motion import create_motion_model_widgets
from btrack.napari.widgets._optimiser import create_optimiser_widgets


class BtrackWidget(QtWidgets.QScrollArea):
Expand Down Expand Up @@ -46,13 +46,13 @@ def __init__(self, napari_viewer: Viewer) -> None:

self._add_logo_widgets()
self._add_input_widgets()
# This must be added after the input widgets
self._add_track_widgets()
# This must be added after the track widget
self._main_layout.addWidget(self._tabs, stretch=0)
self._add_update_method_widgets()
self._add_basic_widgets()
self._add_motion_model_widgets()
self._add_hypothesis_model_widgets()
self._add_optimiser_widgets()
self._add_config_widgets()
self._add_track_widgets()

# Expand the main widget
self._main_layout.addStretch(stretch=1)
Expand Down Expand Up @@ -87,9 +87,9 @@ def _add_input_widgets(self) -> None:
widget_holder.setLayout(layout)
self._main_layout.addWidget(widget_holder, stretch=0)

def _add_update_method_widgets(self) -> None:
def _add_basic_widgets(self) -> None:
"""Create update method widgets and add to main layout"""
labels_and_widgets = create_update_method_widgets()
labels_and_widgets = create_basic_widgets()
self._widgets.update(
{key: value[1] for key, value in labels_and_widgets.items()}
)
Expand All @@ -102,7 +102,7 @@ def _add_update_method_widgets(self) -> None:

tab = QtWidgets.QWidget()
tab.setLayout(layout)
self._tabs.addTab(tab, "Method")
self._tabs.addTab(tab, "Basic")

def _add_motion_model_widgets(self) -> None:
"""Create motion model widgets and add to main layout"""
Expand All @@ -121,9 +121,9 @@ def _add_motion_model_widgets(self) -> None:
tab.setLayout(layout)
self._tabs.addTab(tab, "Motion")

def _add_hypothesis_model_widgets(self) -> None:
def _add_optimiser_widgets(self) -> None:
"""Create hypothesis model widgets and add to main layout"""
labels_and_widgets = create_hypothesis_model_widgets()
labels_and_widgets = create_optimiser_widgets()
self._widgets.update(
{key: value[1] for key, value in labels_and_widgets.items()}
)
Expand All @@ -136,7 +136,7 @@ def _add_hypothesis_model_widgets(self) -> None:

tab = QtWidgets.QWidget()
tab.setLayout(layout)
self._tabs.addTab(tab, "Hypothesis")
self._tabs.addTab(tab, "Optimiser")

def _add_config_widgets(self) -> None:
"""Creates the IO widgets related to the user config"""
Expand Down
4 changes: 3 additions & 1 deletion tests/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ def full_tracker_example(
"""Set up a full tracker example. kwargs can supply configuration options."""
# run the tracking
tracker = btrack.BayesianTracker()
tracker.configure(CONFIG_FILE)
cfg = btrack.config.load_config(CONFIG_FILE)
cfg.motion_model.prob_not_assign = 0.001
tracker.configure(cfg)
for cfg_key, cfg_value in kwargs.items():
setattr(tracker, cfg_key, cfg_value)
tracker.append(objects)
Expand Down
Loading
Loading