Skip to content

Commit 1b29581

Browse files
committed
Merge branch 'main' of https://github.com/winawerlab/nyu-annotate into main
2 parents 54e89d0 + f9392e3 commit 1b29581

File tree

7 files changed

+406
-44
lines changed

7 files changed

+406
-44
lines changed

config/config.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,58 @@ figures:
316316
cod: |
317317
ny.cortex_plot(target['flatmap'], color='cod', axes=axes, cmap='hot',
318318
vmin=0, vmax=1)
319+
# We also want to have thresholded versions of these plots.
320+
polar_angle_thresh: |
321+
ny.cortex_plot(target['flatmap'], color='polar_angle', axes=axes,
322+
mash=('cod', 0.1, 1), cmap='hot', vmin=0, vmax=1)
323+
eccentricity_thresh: |
324+
ny.cortex_plot(target['flatmap'], color='eccentricity', axes=axes,
325+
mash=('cod', 0.1, 1), cmap='hot', vmin=0, vmax=1)
319326
# The rest are PRF properties.
320327
_: |
321328
ny.cortex_plot(target['flatmap'], color=key, axes=axes)
329+
330+
review: |
331+
#from annotate import watershed_contours
332+
#im = watershed_contours(annotations.values(), max_depth=1)
333+
#axes.imshow(im, cmap='hsv', vmin=0, vmax=1.5*np.max(im))
334+
import numpy as np
335+
from matplotlib.pyplot import Polygon
336+
# V1:
337+
v1uvm = annotations['V1 UVM (ventral)']
338+
v1lvm = annotations['V1 LVM (dorsal)']
339+
v1per = annotations['V1 Periphery']
340+
v1pol = Polygon(
341+
np.vstack([v1uvm, v1per, np.flipud(v1lvm)]),
342+
closed=True,
343+
color='red',
344+
alpha=0.5,
345+
fill=True)
346+
axes.add_patch(v1pol)
347+
# V2:
348+
v2uvm = annotations['V2 UVM (ventral)']
349+
v2lvm = annotations['V2 LVM (dorsal)']
350+
v2pev = annotations['V2 Ventral Periphery']
351+
v2ped = annotations['V2 Dorsal Periphery']
352+
v2pol = Polygon(
353+
np.vstack(
354+
[v2uvm, v2pev, np.flipud(v1uvm), v1lvm, v2ped, np.flipud(v2lvm)]),
355+
closed=True,
356+
color='cyan',
357+
alpha=0.5,
358+
fill=True)
359+
axes.add_patch(v2pol)
360+
# V3:
361+
v3uvm = annotations['V3 UVM (ventral)']
362+
v3lvm = annotations['V3 LVM (dorsal)']
363+
v3pev = annotations['V3 Ventral Periphery']
364+
v3ped = annotations['V3 Dorsal Periphery']
365+
v3pol = Polygon(
366+
np.vstack(
367+
[v3uvm, v3pev, np.flipud(v2uvm), v2lvm, v3ped, np.flipud(v3lvm)]),
368+
color='black',
369+
alpha=0.5,
370+
closed=True)
371+
axes.add_patch(v3pol)
372+
axes.axis('equal')
373+
axes.axis('off')

src/annotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
# Imports ######################################################################
2020

21-
from ._util import (delay, ldict)
21+
from ._util import (delay, ldict, watershed_contours)
2222
from ._core import AnnotationTool
2323

2424

src/annotate/_config.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,50 @@ def __init__(self, yaml, init, all_figures):
511511
init)
512512
# Same these into the dictionary.
513513
self.update(res)
514+
class ReviewConfig:
515+
"""An object that stores the configuration of the review panel.
516+
517+
The `ReviewConfig` type stores information from the `review` section of the
518+
`config.yaml` file for the `cortex-annotate` project. This section stores
519+
only a function that generates the figure to plot in the `Review` tab of the
520+
display panel. The review function requires the arguments `target`,
521+
`annotations`, `figure`, and `axes`, and it must draw the desired graphics
522+
on the given matplotlib axes. The `annotations` argument is a dictionary
523+
whose keys are the annotation names and whose values are the drawn
524+
annotation. Annotations may be missing if the user opens the review tab
525+
before completing the annotations. If an error is raised from this function,
526+
then the error message is printed to the display.
527+
"""
528+
__slots__ = ('code', 'function', 'figsize', 'dpi')
529+
@staticmethod
530+
def _compile(code, initcfg):
531+
return _compile_fn(
532+
"target, annotations, figure, axes",
533+
f"{code}\n",
534+
initcfg)
535+
def __init__(self, yaml, init):
536+
if yaml is None:
537+
self.code = None
538+
self.function = None
539+
self.figsize = None
540+
self.dpi = None
541+
return
542+
elif isinstance(yaml, str):
543+
yaml = {'function': yaml}
544+
elif not isinstance(yaml, dict):
545+
raise ConfigError(
546+
"review",
547+
"review section must contain a Python code string or a dict",
548+
yaml)
549+
self.code = yaml.get('function')
550+
if self.code is None:
551+
raise ConfigError(
552+
"review",
553+
"review section must contain the key function",
554+
yaml)
555+
self.figsize = yaml.get('figsize', (3,3))
556+
self.dpi = yaml.get('dpi', 256)
557+
self.function = ReviewConfig._compile(self.code, init)
514558
class Config:
515559
"""The configuration object for the `cortex-annotate` project.
516560
@@ -521,8 +565,9 @@ class Config:
521565
not recognized by `Config` are not parsed, but they are available in the
522566
`Config.yaml` member variable.
523567
"""
524-
__slots__ = ('config_path', 'yaml', 'display', 'init', 'targets', 'figures',
525-
'annotations', 'builtin_annotations', 'annotation_types')
568+
__slots__ = (
569+
'config_path', 'yaml', 'display', 'init', 'targets', 'figures',
570+
'annotations', 'builtin_annotations', 'review', 'annotation_types')
526571
def __init__(self, config_path='/config/config.yaml'):
527572
self.config_path = config_path
528573
with open(config_path, 'rt') as f:
@@ -534,15 +579,20 @@ def __init__(self, config_path='/config/config.yaml'):
534579
# Parse the targets section.
535580
self.targets = TargetsConfig(self.yaml.get('targets', None), self.init)
536581
# Parse the annotations section.
537-
self.annotations = AnnotationsConfig(self.yaml.get('annotations', None),
538-
self.init)
582+
self.annotations = AnnotationsConfig(
583+
self.yaml.get('annotations', None),
584+
self.init)
539585
# Parse the builtin_annotations section.
540586
self.builtin_annotations = BuiltinAnnotationsConfig(
541587
self.yaml.get('builtin_annotations', None),
542588
self.init)
543589
# Parse the figures section.
544-
self.figures = FiguresConfig(self.yaml.get('figures', None), self.init,
545-
self.annotations.all_figures)
590+
self.figures = FiguresConfig(
591+
self.yaml.get('figures', None),
592+
self.init,
593+
self.annotations.all_figures)
594+
# Parse the review section.
595+
self.review = ReviewConfig(self.yaml.get('review', None), self.init)
546596
# Make the annotation types dictionary.
547597
d = self.annotations.types.copy()
548598
d.update(self.builtin_annotations.types)

src/annotate/_control.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,31 @@ def __init__(self, state,
333333
self.imagesize_slider = self._make_imagesize_slider(imagesize)
334334
self.selection_panel = SelectionPanel(state)
335335
self.style_panel = StylePanel(state)
336+
self.review_button = ipw.Button(
337+
description='Review',
338+
button_style='',
339+
tooltip='Review the annotations.')
336340
self.save_button = ipw.Button(
337341
description='Save',
338342
button_style='',
339-
tooltip='Save all annotations and preferences.',
340-
layout={'margin':"3% 33% 3% 33%", "width": "34%"})
343+
tooltip='Save all annotations and preferences.')
344+
self.edit_button = ipw.Button(
345+
description='Edit',
346+
button_style='',
347+
tooltip='Continue editing annotation.')
348+
if state.config.review.function is not None:
349+
buttons = [self.review_button, self.save_button, self.edit_button]
350+
self.review_button.disabled = False
351+
self.save_button.disabled = True
352+
self.edit_button.disabled = True
353+
layout = {'margin':"3% 3% 3% 3%", "width": "94%"}
354+
else:
355+
buttons = [self.save_button]
356+
self.review_button.disabled = True
357+
self.save_button.disabled = False
358+
self.edit_button.disabled = True
359+
layout = {'margin':"3% 33% 3% 33%", "width": "34%"}
360+
self.button_box = ipw.HBox(buttons, layout=layout)
341361
self.info_message = self._make_infomsg()
342362
hline = self._make_hline()
343363
self.vbox_children = [
@@ -348,7 +368,7 @@ def __init__(self, state,
348368
hline,
349369
self.style_panel,
350370
hline,
351-
self.save_button,
371+
self.button_box,
352372
hline,
353373
self.info_message]
354374
vbox_layout = {'width': '250px'}
@@ -433,10 +453,24 @@ def observe_imagesize(self, fn):
433453
def observe_save(self, fn):
434454
"""Registers the argument to be called when the save button is clicked.
435455
436-
The function is called with a single argument, which is the save buttom
456+
The function is called with a single argument, which is the save button
437457
instance.
438458
"""
439459
self.save_button.on_click(fn)
460+
def observe_review(self, fn):
461+
"""Registers the argument to be called when the save button is clicked.
462+
463+
The function is called with a single argument, which is the review
464+
button instance.
465+
"""
466+
self.review_button.on_click(fn)
467+
def observe_edit(self, fn):
468+
"""Registers the argument to be called when the edit button is clicked.
469+
470+
The function is called with a single argument, which is the edit button
471+
instance.
472+
"""
473+
self.edit_button.on_click(fn)
440474
@property
441475
def target(self):
442476
"""Compute the current target selection."""

0 commit comments

Comments
 (0)