Skip to content

Commit

Permalink
Implement new-style grouping for form
Browse files Browse the repository at this point in the history
  • Loading branch information
pkcakeout committed May 30, 2024
1 parent f51a1ba commit 61011af
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 470 deletions.
84 changes: 11 additions & 73 deletions app/grandchallenge/workstation_configs/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,15 @@
KEY_BINDINGS_SCHEMA,
OVERLAY_SEGMENTS_SCHEMA,
WorkstationConfig,
VisualGroups,
)

from .visual_field_ordering import FORM_FIELDS, GROUPS


class WorkstationConfigForm(ModelForm):
class Meta:
model = WorkstationConfig
fields = (
"title",
"description",
"image_context",
"window_presets",
"default_window_preset",
"default_slab_thickness_mm",
"default_slab_render_method",
"default_orientation",
"default_overlay_alpha",
"overlay_luts",
"default_overlay_lut",
"default_image_interpolation",
"default_limit_view_area_to_image_volume",
"default_overlay_interpolation",
"ghosting_slice_depth",
"overlay_segments",
"key_bindings",
"default_zoom_scale",
"default_brush_size",
"default_annotation_color",
"default_annotation_line_width",
"auto_jump_center_of_gravity",
"show_image_info_plugin",
"show_display_plugin",
"show_image_switcher_plugin",
"show_algorithm_output_plugin",
"show_overlay_plugin",
"show_annotation_statistics_plugin",
"show_swivel_tool",
"show_invert_tool",
"show_flip_tool",
"show_window_level_tool",
"show_reset_tool",
"show_overlay_selection_tool",
"show_lut_selection_tool",
"show_annotation_counter_tool",
"enable_contrast_enhancement",
"link_images",
"link_panning",
"link_zooming",
"link_slicing",
"link_orienting",
"link_windowing",
"link_inverting",
"link_flipping",
)
fields = FORM_FIELDS

widgets = {
"overlay_segments": JSONEditorWidget(
Expand Down Expand Up @@ -94,32 +49,15 @@ class Meta:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

ordered_fields = list(self.fields.keys())
DEFAULT_NAME = None
field_set_groups: Dict[Optional[str], list] = OrderedDict(
(
(DEFAULT_NAME, []),
*[(k, []) for k in VisualGroups.group_map.keys()],
)
)
for field_name in ordered_fields:
for group_name, field_list in reversed(field_set_groups.items()):
if (
DEFAULT_NAME == group_name
or field_name in VisualGroups.group_map[group_name].names
):
field_list.append(field_name)
break

helper_fields = []
for group_name, field_list in field_set_groups.items():
if group := VisualGroups.group_map.get(group_name):
helper_fields.append(HTML(f"<h2>{group.title}</h2>"))
if desc := group.description:
helper_fields.append(
HTML(f'<p class="text-muted">{desc}</p>')
)
helper_fields.extend(field_list)
for group in GROUPS:
filtered_fields = [fn for fn in group.names if fn in self.fields]
if not filtered_fields:
continue
helper_fields.append(HTML(f"<h2>{group.title}</h2>"))
if desc := group.description:
helper_fields.append(HTML(f'<p class="text-muted">{desc}</p>'))
helper_fields.extend(filtered_fields)

self.helper = FormHelper()
self.helper.layout = Layout(*helper_fields)
Expand Down
88 changes: 0 additions & 88 deletions app/grandchallenge/workstation_configs/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from collections import OrderedDict
from functools import cached_property
from typing import Sequence, Dict, List, Optional

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.validators import (
Expand Down Expand Up @@ -131,56 +127,6 @@
},
}


class Group:
title: str
description: Optional[str]
items: List[models.Field]

@cached_property
def names(self):
return tuple(i.name for i in self.items)

def __init__(self, *, title, description=None):
self.title = title
self.description = description
self.items = []

def __set_name__(self, owner, name):
owner.group_map[name] = self

def add_to_group(self, other):
self.items.append(other)
if "names" in vars(self):
del vars(self)["names"]


class VisualGroups:
__instance = None

group_map: Dict[str, Group] = OrderedDict()
_default = Group(title="")
annotations = Group(
title="Annotations and Overlays",
description="Behavior or visualization settings for annotations and overlays.",
)
plugins_tools = Group(
title="Plugin and Tools",
description="Plugins are components of the viewer, whereas tools are "
"(generally) contained within plugins.",
)
linking = Group(
title="Linking Configuration",
description="Linked images share tool interactions and display properties, "
"it is possible to manually (un)link them during viewing.",
)

def __new__(cls) -> "VisualGroups":
if not (result := cls.__instance):
cls.__instance = result = super().__new__(cls)
return result


class WorkstationConfig(TitleSlugDescriptionModel, UUIDModel):
class Orientation(models.TextChoices):
AXIAL = "A", "Axial"
Expand Down Expand Up @@ -260,7 +206,6 @@ class ImageInterpolationType(models.TextChoices):
blank=False,
help_text="The number of slices a polygon annotation should remain visible for on slices surrounding the annotation slice.",
)
VisualGroups().annotations.add_to_group(ghosting_slice_depth)

overlay_luts = models.ManyToManyField(
to="LookUpTable",
Expand All @@ -269,7 +214,6 @@ class ImageInterpolationType(models.TextChoices):
help_text="The preset look-up tables options that are used to project overlay-image values to "
"displayed pixel colors",
)
VisualGroups().annotations.add_to_group(overlay_luts)

default_overlay_lut = models.ForeignKey(
to="LookUpTable",
Expand All @@ -278,7 +222,6 @@ class ImageInterpolationType(models.TextChoices):
on_delete=models.SET_NULL,
help_text="The look-up table that is applied when an overlay image is first shown",
)
VisualGroups().annotations.add_to_group(default_overlay_lut)

default_overlay_interpolation = models.CharField(
max_length=2,
Expand All @@ -287,7 +230,6 @@ class ImageInterpolationType(models.TextChoices):
blank=True,
help_text="The method used to interpolate multiple voxels of overlay images and project them to screen pixels",
)
VisualGroups().annotations.add_to_group(default_overlay_interpolation)

default_image_interpolation = models.CharField(
max_length=2,
Expand All @@ -314,15 +256,13 @@ class ImageInterpolationType(models.TextChoices):
],
help_text="The alpha value used for setting the degree of opacity for displayed pixels of overlay images",
)
VisualGroups().annotations.add_to_group(default_overlay_alpha)

overlay_segments = models.JSONField(
default=list,
blank=True,
validators=[JSONValidator(schema=OVERLAY_SEGMENTS_SCHEMA)],
help_text="The schema that defines how categories of values in the overlay images are differentiated",
)
VisualGroups().annotations.add_to_group(overlay_segments)

key_bindings = models.JSONField(
default=list,
Expand All @@ -348,168 +288,140 @@ class ImageInterpolationType(models.TextChoices):
validators=[MinValueValidator(limit_value=1e-6)], # 1 nm
help_text="Default brush diameter in millimeters for creating annotations",
)
VisualGroups().annotations.add_to_group(default_brush_size)

default_annotation_color = HexColorField(
blank=True,
null=True,
help_text="Default color for displaying and creating annotations",
)
VisualGroups().annotations.add_to_group(default_annotation_color)

default_annotation_line_width = PositiveSmallIntegerField(
blank=True,
null=True,
help_text="Default line width in pixels for displaying and creating annotations",
)
VisualGroups().annotations.add_to_group(default_annotation_line_width)

show_image_info_plugin = models.BooleanField(
default=True,
help_text="A plugin that shows meta-data information derived from image headers "
"as well as any configured case text for reader studies",
)
VisualGroups().plugins_tools.add_to_group(show_image_info_plugin)

show_display_plugin = models.BooleanField(
default=True,
help_text="A plugin that allows control over display properties such as window preset, "
"slab thickness, or orientation",
)
VisualGroups().plugins_tools.add_to_group(show_display_plugin)

show_image_switcher_plugin = models.BooleanField(
default=True,
help_text="A plugin that allows switching images when viewing algorithm outputs",
)
VisualGroups().plugins_tools.add_to_group(show_image_switcher_plugin)

show_algorithm_output_plugin = models.BooleanField(
default=True,
help_text="A plugin that shows algorithm outputs, including navigation controls",
)
VisualGroups().plugins_tools.add_to_group(show_image_switcher_plugin)

show_overlay_plugin = models.BooleanField(
default=True,
help_text="A plugin that contains overlay-related controls, "
"such as the overlay-selection tool and overlay-segmentation visibility",
)
VisualGroups().plugins_tools.add_to_group(show_overlay_plugin)

show_annotation_statistics_plugin = models.BooleanField(
default=False,
help_text="A plugin that allows analysis of segmentations. It shows voxel value "
"statistics of annotated areas.",
)
VisualGroups().plugins_tools.add_to_group(
show_annotation_statistics_plugin
)

show_swivel_tool = models.BooleanField(
default=False,
help_text="A tool that allows swiveling the image around axes to view a custom orientation",
)
VisualGroups().plugins_tools.add_to_group(show_swivel_tool)

show_invert_tool = models.BooleanField(
default=True,
help_text="A tool/button that allows inverting the displayed pixel colors of an image",
)
VisualGroups().plugins_tools.add_to_group(show_invert_tool)

show_flip_tool = models.BooleanField(
default=True,
help_text="A tool/button that allows vertical flipping/mirroring of an image",
)
VisualGroups().plugins_tools.add_to_group(show_flip_tool)

show_window_level_tool = models.BooleanField(
default=True,
help_text="A tool that allows selection of window presets and changing the window width/center",
)
VisualGroups().plugins_tools.add_to_group(show_window_level_tool)

show_reset_tool = models.BooleanField(
default=True,
help_text="A tool/button that resets all display properties of the images to defaults",
)
VisualGroups().plugins_tools.add_to_group(show_reset_tool)

show_overlay_selection_tool = models.BooleanField(
default=True,
help_text="A tool that allows switching overlay images when viewing algorithm outputs",
)
VisualGroups().plugins_tools.add_to_group(show_overlay_selection_tool)

show_lut_selection_tool = models.BooleanField(
default=True,
verbose_name="Show overlay-lut selection tool",
help_text="A tool that allows switching between the overlay-lut presets",
)
VisualGroups().plugins_tools.add_to_group(show_lut_selection_tool)

show_annotation_counter_tool = models.BooleanField(
default=True,
help_text="A tool that can be used to show summary statistics of annotations within an area",
)
VisualGroups().plugins_tools.add_to_group(show_lut_selection_tool)

link_images = models.BooleanField(
default=True,
help_text="Start with the images linked",
)
VisualGroups().linking.add_to_group(link_images)

link_panning = models.BooleanField(
default=True,
help_text="When panning and the images are linked, they share any new position",
)
VisualGroups().linking.add_to_group(link_panning)

link_zooming = models.BooleanField(
default=True,
help_text="When zooming and the images are linked, they share any new zoom level",
)
VisualGroups().linking.add_to_group(link_zooming)

link_slicing = models.BooleanField(
default=True,
help_text="When scrolling and the images are linked, they share any slice changes",
)
VisualGroups().linking.add_to_group(link_slicing)

link_orienting = models.BooleanField(
default=True,
help_text="When orienting and the images are linked, they share any new orientation",
)
VisualGroups().linking.add_to_group(link_orienting)

link_windowing = models.BooleanField(
default=True,
help_text="When changing window setting and the images are linked, they share any new window width/center",
)
VisualGroups().linking.add_to_group(link_windowing)

link_inverting = models.BooleanField(
default=True,
help_text="When inverting images and the images are linked, they share any new invert state",
)
VisualGroups().linking.add_to_group(link_inverting)

link_flipping = models.BooleanField(
default=True,
help_text="When flipping images and the images are linked, they share any new flip state",
)
VisualGroups().linking.add_to_group(link_flipping)

enable_contrast_enhancement = models.BooleanField(
default=False,
verbose_name="Contrast-enhancement preprocessing tool",
help_text="A tool that uses image preprocessing to enhance contrast. "
"It is mainly used for viewing eye-fundus images",
)
VisualGroups().plugins_tools.add_to_group(enable_contrast_enhancement)

auto_jump_center_of_gravity = models.BooleanField(
default=True,
Expand Down
Loading

0 comments on commit 61011af

Please sign in to comment.