Skip to content

Commit d029db4

Browse files
authored
Create handles for Tasks + Projects (#574)
* [WIP] create handles for Tasks + Projects * add new routes from swagger, task example * add user info by id, use in project * rename project_info_by_id, add by_name * fix imports * fix type errors * move to “administration” pacakge * get tasks for project * fetch annotation infos by task id * [WIP] pagination * fetch all using pagination if requested * create tasks from files * pass BytesIO directly * task state * create new task from downloaded annotation * experiences are varying, binary is property * fix optional field * format * remove name field from annotation layer response * undo wrong renaming. IDE tried to be clever... * one more usage... * document new example * changelog * docs * implement pr feedback (part 1) * lru cache instead of cachedproperty * new generated client * format * fix generated client * format again after poetry install * add missing call parentheses * lru cache python37 compatible (hopefully)
1 parent 356646f commit d029db4

File tree

79 files changed

+5248
-237
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+5248
-237
lines changed

docs/mkdocs.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ nav:
7272
- webknossos-py/examples/learned_segmenter.md
7373
- webknossos-py/examples/skeleton_synapse_candidates.md
7474
- webknossos-py/examples/user_times.md
75+
- webknossos-py/examples/annotation_project_administration.md
7576
- API Reference:
7677
- Overview: api/webknossos.md
7778
- Geometry:
@@ -90,8 +91,10 @@ nav:
9091
- Node: api/webknossos/skeleton/node.md
9192
- Annotation: api/webknossos/annotation/annotation.md
9293
- Authentication & Server Context: api/webknossos/client/context.md
93-
- Admin Resources:
94-
- User: api/webknossos/client/user.md
94+
- Administration:
95+
- User: api/webknossos/administration/user.md
96+
- Project: api/webknossos/administration/project.md
97+
- Task: api/webknossos/administration/task.md
9598
- Getting Help:
9699
- webknossos-py/development.md
97100
- Community Support: https://forum.image.sc/tag/webknossos" target="_blank
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Annotation Project Administration
2+
3+
This example uses the [`Project` class](../../api/webknossos/administration/project.md#Project) and [`Task` class](../../api/webknossos/administration/task.md#task) to check annotation task status and submit new tasks.
4+
5+
```python
6+
--8<--
7+
webknossos/examples/annotation_project_administration.py
8+
--8<--
9+
```

docs/src/webknossos-py/examples/user_times.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Logged User Times
22

3-
This example uses the [`User` class](../../api/webknossos/client/user.md#User) to load the logged times
3+
This example uses the [`User` class](../../api/webknossos/administration/user.md#User) to load the logged times
44
for all users of whom the current user is admin or team-manager.
55

66
*This example additionally needs the pandas and tabulate packages.*

webknossos/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ For upgrade instructions, please check the respective *Breaking Changes* section
1212
### Breaking Changes
1313

1414
### Added
15+
- Added AnnotationInfo, Project and Task classes for handling annotation information and annotation project administration. [#574](https://github.com/scalableminds/webknossos-libs/pull/574):
1516

1617
### Changed
1718

webknossos/__generate_client.py

Lines changed: 108 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
_get_project_for_url_or_path,
1717
)
1818

19-
from webknossos.client.context import _get_generated_client
2019
from webknossos.utils import snake_to_camel_case
2120

22-
SCHEMA_URL = "https://master.webknossos.xyz/swagger.json"
23-
# SCHEMA_URL = "http://localhost:9000/swagger.json"
21+
# SCHEMA_URL = "https://master.webknossos.xyz/swagger.json"
22+
SCHEMA_URL = "http://localhost:9000/swagger.json"
2423
CONVERTER_URL = "https://converter.swagger.io/api/convert"
2524

2625

@@ -64,37 +63,114 @@ def add_api_prefix_for_non_data_paths(openapi_schema: Dict) -> None:
6463

6564

6665
def iterate_request_ids_with_responses() -> Iterable[Tuple[str, bytes]]:
66+
"""Send requests to webKnossos and record the schema of their replies"""
6767
from webknossos.client._generated.api.default import (
6868
annotation_info,
69+
annotation_infos_by_task_id,
6970
build_info,
7071
current_user_info,
7172
dataset_info,
7273
datastore_list,
7374
generate_token_for_data_store,
75+
project_info_by_id,
76+
project_info_by_name,
77+
task_info,
78+
task_infos_by_project_id,
79+
user_info_by_id,
7480
user_list,
7581
user_logged_time,
7682
)
83+
from webknossos.client.context import _get_generated_client
84+
85+
# webKnossos.org setup:
86+
# explorative_annotation_id = "6114d9410100009f0096c640"
87+
# organization_name = "scalable_minds",
88+
# dataset_name = "l4dense_motta_et_al_demo"
89+
# task_id = "61f151c10100000a01249afe"
90+
# user_id = "5b5dd2fb1c00008230ec8174"
91+
# project_id = "61f1515e0100002f01249afa"
92+
# project_name = "sampleProject"
93+
# local setup, probably long gone by the time you read this:
94+
explorative_annotation_id = "62011da6fa0100b202ec50db"
95+
organization_name = "sample_organization"
96+
dataset_name = "l4_sample"
97+
task_id = "62011dddfa0100ad02ec50de"
98+
user_id = "6200df39f70100f70157d983"
99+
project_id = "6200df45f70100440257d987"
100+
project_name = "sampleProject"
77101

78102
d = datetime.utcnow()
79103
unixtime = calendar.timegm(d.utctimetuple())
80104
client = _get_generated_client(enforce_auth=True)
81105

82-
annotation_info_response = annotation_info.sync_detailed(
83-
typ="Explorational",
84-
id="6114d9410100009f0096c640",
85-
client=client,
86-
timestamp=unixtime,
106+
yield extract_200_response(
107+
"annotationInfo",
108+
annotation_info.sync_detailed(
109+
typ="Explorational",
110+
id=explorative_annotation_id,
111+
client=client,
112+
timestamp=unixtime,
113+
),
87114
)
88-
assert annotation_info_response.status_code == 200
89-
yield "annotationInfo", annotation_info_response.content
90115

91-
dataset_info_response = dataset_info.sync_detailed(
92-
organization_name="scalable_minds",
93-
data_set_name="l4dense_motta_et_al_demo",
94-
client=client,
116+
yield extract_200_response(
117+
"datasetInfo",
118+
dataset_info.sync_detailed(
119+
organization_name=organization_name,
120+
data_set_name=dataset_name,
121+
client=client,
122+
),
123+
)
124+
125+
yield extract_200_response(
126+
"taskInfo",
127+
task_info.sync_detailed(
128+
id=task_id,
129+
client=client,
130+
),
131+
)
132+
133+
yield extract_200_response(
134+
"userInfoById",
135+
user_info_by_id.sync_detailed(
136+
id=user_id,
137+
client=client,
138+
),
139+
)
140+
141+
yield extract_200_response(
142+
"projectInfoById",
143+
project_info_by_id.sync_detailed(
144+
id=project_id,
145+
client=client,
146+
),
147+
)
148+
149+
yield extract_200_response(
150+
"projectInfoByName",
151+
project_info_by_name.sync_detailed(name=project_name, client=client),
152+
)
153+
154+
yield extract_200_response(
155+
"taskInfosByProjectId",
156+
task_infos_by_project_id.sync_detailed(
157+
id=project_id,
158+
client=client,
159+
),
160+
)
161+
162+
yield extract_200_response(
163+
"annotationInfosByTaskId",
164+
annotation_infos_by_task_id.sync_detailed(id=task_id, client=client),
165+
)
166+
167+
yield extract_200_response(
168+
"userLoggedTime",
169+
user_logged_time.sync_detailed(
170+
id=user_id,
171+
client=client,
172+
),
95173
)
96-
assert dataset_info_response.status_code == 200
97-
yield "datasetInfo", dataset_info_response.content
98174

99175
for api_endpoint in [
100176
datastore_list,
@@ -106,28 +182,30 @@ def iterate_request_ids_with_responses() -> Iterable[Tuple[str, bytes]]:
106182
api_endpoint_name = api_endpoint.__name__.split(".")[-1]
107183
api_endpoint_name = snake_to_camel_case(api_endpoint_name)
108184

109-
api_endpoint_response = api_endpoint.sync_detailed(client=client)
110-
assert api_endpoint_response.status_code == 200
111-
yield api_endpoint_name, api_endpoint_response.content
112-
113-
if api_endpoint == current_user_info:
114-
user_id = json.loads(api_endpoint_response.content)["id"]
115-
116-
user_logged_time_response = user_logged_time.sync_detailed(
117-
id=user_id,
118-
client=client,
119-
)
120-
assert user_logged_time_response.status_code == 200
121-
yield "userLoggedTime", user_logged_time_response.content
185+
yield extract_200_response(
186+
api_endpoint_name, api_endpoint.sync_detailed(client=client)
187+
)
122188

123189

124190
FIELDS_WITH_VARYING_CONTENT = [
191+
"experiences",
192+
"adminViewConfiguration",
193+
"novelUserExperienceInfos",
194+
"viewConfiguration",
195+
]
196+
197+
OPTIONAL_FIELDS = [
125198
"adminViewConfiguration",
126199
"novelUserExperienceInfos",
127200
"viewConfiguration",
128201
]
129202

130203

204+
def extract_200_response(name: str, response: Any) -> Tuple[str, bytes]:
205+
assert response.status_code == 200, response.content
206+
return name, response.content
207+
208+
131209
def make_properties_required(x: Any) -> None:
132210
if isinstance(x, dict):
133211
for key, value in x.items():
@@ -146,7 +224,7 @@ def make_properties_required(x: Any) -> None:
146224
x["required"] = list(
147225
property
148226
for property in properties.keys()
149-
if property not in FIELDS_WITH_VARYING_CONTENT
227+
if property not in OPTIONAL_FIELDS
150228
)
151229

152230

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from webknossos import AnnotationState, Project, Task
2+
3+
4+
def main() -> None:
5+
# Assume running annotation project with previously created tasks.
6+
# Compare webKnossos documentation at https://docs.webknossos.org/webknossos/tasks.html
7+
8+
# Fetch statistics about how many annotation tasks have been completed yet.
9+
sample_project = Project.get_by_name("sampleProject")
10+
tasks = sample_project.get_tasks()
11+
total_active_instances = sum([task.status.active_instance_count for task in tasks])
12+
total_open_instances = sum([task.status.open_instance_count for task in tasks])
13+
print(
14+
f"There are {total_active_instances} active and {total_open_instances} open task instances."
15+
)
16+
17+
# Find and download all of the project’s annotations that are already finished by annotators
18+
finished_annotations = []
19+
for task in tasks:
20+
for annotation_info in task.get_annotation_infos():
21+
if annotation_info.state == AnnotationState.FINISHED:
22+
finished_annotation = annotation_info.download_annotation()
23+
finished_annotations.append(finished_annotation)
24+
25+
assert len(finished_annotations) > 0, "No annotations are finished yet!"
26+
27+
# Assume a second task type is in place that instructs annotators to take the previously created
28+
# annotations and perform a secondary annotation step on them
29+
# (e.g. first task is to seed positions, second is to fully label around those)
30+
31+
task_type_id = "61f90e4efe0100b102553009" # from webKnossos web interface
32+
tasks = Task.create_from_annotations(
33+
task_type_id=task_type_id, # New task type instructs annotators what to do in the task
34+
project_name="sampleProject2",
35+
base_annotations=finished_annotations,
36+
needed_experience_domain="sampleExp", # Only annotators with the experience "sampleExp" of at least value 2 will get this task
37+
needed_experience_value=2,
38+
)
39+
print(f"New tasks: {tasks}")
40+
41+
42+
if __name__ == "__main__":
43+
main()

webknossos/tests/client/test_user.py

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

55

66
def assert_valid_user(user: User) -> None:
7-
assert user.id
7+
assert user.user_id
88
assert user.first_name
99
assert user.last_name
1010
assert user.email

webknossos/webknossos/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
# The table above contains zero-width spaces in the code examples after each dot to enforce correct line-breaks.
2424

25+
from webknossos.administration import *
2526
from webknossos.annotation import *
2627
from webknossos.client import *
2728
from webknossos.dataset import *
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from webknossos.administration.project import Project
2+
from webknossos.administration.task import Task
3+
from webknossos.administration.user import User

0 commit comments

Comments
 (0)