def test_add_image_to_public_result(): g_reg_anon = Group.objects.get( name=settings.REGISTERED_AND_ANON_USERS_GROUP_NAME ) g_reg = Group.objects.get(name=settings.REGISTERED_USERS_GROUP_NAME) job = AlgorithmJobFactory(public=True) civ_images = ( ComponentInterfaceValueFactory(image=ImageFactory()), ComponentInterfaceValueFactory(image=ImageFactory()), ) for im in civ_images: assert "view_image" not in get_perms(g_reg, im.image) assert "view_image" not in get_perms(g_reg_anon, im.image) job.outputs.add(*civ_images) for im in civ_images: assert "view_image" not in get_perms(g_reg, im.image) assert "view_image" in get_perms(g_reg_anon, im.image) job.outputs.remove(civ_images[0].pk) assert "view_image" not in get_perms(g_reg, civ_images[0].image) assert "view_image" not in get_perms(g_reg_anon, civ_images[0].image) assert "view_image" not in get_perms(g_reg, civ_images[1].image) assert "view_image" in get_perms(g_reg_anon, civ_images[1].image) job.outputs.clear() for im in civ_images: assert "view_image" not in get_perms(g_reg, im.image) assert "view_image" not in get_perms(g_reg_anon, im.image)
def test_algorithm_input_image_multiple_files(build_images, client, algorithm_io_image, settings, component_interfaces): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) creator = UserFactory() assert Job.objects.count() == 0 # Create the algorithm image algorithm_container, sha256 = algorithm_io_image alg = AlgorithmImageFactory(image__from_path=algorithm_container, image_sha256=sha256, ready=True) alg.algorithm.add_editor(creator) alg.algorithm.inputs.set(ComponentInterface.objects.all()) # create the job job = Job.objects.create(creator=creator, algorithm_image=alg) us = RawImageUploadSessionFactory() ImageFactory(origin=us), ImageFactory(origin=us) ci = ComponentInterface.objects.get(slug=DEFAULT_INPUT_INTERFACE_SLUG) civ = ComponentInterfaceValue.objects.create(interface=ci) job.inputs.add(civ) with pytest.raises(ValueError): with capture_on_commit_callbacks(execute=True): run_algorithm_job_for_inputs(job_pk=job.pk, upload_pks={civ.pk: us.pk})
def test_group_clearing(self, reverse): job = AlgorithmJobFactory() civ_in, civ_out = ( ComponentInterfaceValueFactory(image=ImageFactory()), ComponentInterfaceValueFactory(image=ImageFactory()), ) job.inputs.add(civ_in) job.outputs.add(civ_out) groups = job.viewer_groups.all() assert len(groups) > 0 for group in groups: assert "view_job" in get_perms(group, job) assert "view_image" in get_perms(group, civ_in.image) assert "view_image" in get_perms(group, civ_out.image) if reverse: for group in groups: group.job_set.clear() else: job.viewer_groups.clear() for group in groups: assert "view_job" not in get_perms(group, job) assert "view_image" not in get_perms(group, civ_in.image) assert "view_image" not in get_perms(group, civ_out.image)
def test_mine(client): im1, im2 = ImageFactory(), ImageFactory() rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1.images.add(im1) rs2.images.add(im2) reader = UserFactory() rs1.add_reader(reader) rs2.add_reader(reader) q1 = QuestionFactory( reader_study=rs1, answer_type=Question.ANSWER_TYPE_BOOL ) q2 = QuestionFactory( reader_study=rs2, answer_type=Question.ANSWER_TYPE_BOOL ) a1 = AnswerFactory(question=q1, creator=reader, answer=True) a1.images.add(im1) a2 = AnswerFactory(question=q2, creator=reader, answer=True) a2.images.add(im2) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, content_type="application/json", ) response = response.json() assert response["count"] == 2 response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs1.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=reader, client=client, method=client.get, data={"question__reader_study": rs2.pk}, content_type="application/json", ) response = response.json() assert response["count"] == 1 assert response["results"][0]["pk"] == str(a2.pk)
def test_gets_creator_from_session(self): riu = RawImageUploadSessionFactory() riu.image_set.add(ImageFactory(), ImageFactory()) create_algorithm_jobs( algorithm_image=AlgorithmImageFactory(), images=riu.image_set.all(), creator=riu.creator, ) j = Job.objects.first() assert j.creator == riu.creator
def test_landmarks_single(self): u = UserFactory() i1 = ImageFactory() i2 = ImageFactory() las = LandmarkAnnotationSetFactory(grader=u) SingleLandmarkAnnotationFactory(annotation_set=las, image=i1) SingleLandmarkAnnotationFactory(annotation_set=las, image=i2) landmarks = RetinaImageSerializer(i1).data["landmark_annotations"] assert len(landmarks) == 1 assert landmarks == [i2.pk]
def test_jobs_workflow(self): ai = AlgorithmImageFactory() images = [ImageFactory(), ImageFactory()] civ_sets = [{ ComponentInterfaceValueFactory(image=im, interface=ai.algorithm.inputs.get()) } for im in images] with capture_on_commit_callbacks() as callbacks: create_algorithm_jobs(algorithm_image=ai, civ_sets=civ_sets) assert len(callbacks) == 2
def images(self): return [ ImageFactory.build( filename='regular/image/url.jpg', caption='Regular image caption' ), ImageFactory.build( filename='another/image/url.jpg', caption='Another image caption' ) ]
def test_provider_describe(self): image = ImageFactory() image.file = MockedUrlImageFile(image_url=test_image) provider = get_provider( 'wagtailaltgenerator.providers.rekognition.Rekognition') data = provider().describe(image) self.assertIsNone(data.description) self.assertTrue(len(data.tags) > 0)
def test_landmarks_multiple(self): u = UserFactory() i1 = ImageFactory() img_pks = set() for _ in range(4): img = ImageFactory() las = LandmarkAnnotationSetFactory(grader=u) SingleLandmarkAnnotationFactory(annotation_set=las, image=img) SingleLandmarkAnnotationFactory(annotation_set=las, image=i1) img_pks.add(img.pk) landmarks = RetinaImageSerializer(i1).data["landmark_annotations"] assert len(landmarks) == 4 assert set(landmarks) == img_pks
def test_gets_creator_from_session(self): ai = AlgorithmImageFactory() riu = RawImageUploadSessionFactory() riu.image_set.add(ImageFactory(), ImageFactory()) civ_sets = [{ ComponentInterfaceValueFactory(image=image, interface=ai.algorithm.inputs.get()) } for image in riu.image_set.all()] create_algorithm_jobs(algorithm_image=ai, civ_sets=civ_sets, creator=riu.creator) j = Job.objects.first() assert j.creator == riu.creator
def test_workstation_query(settings): image, overlay = ImageFactory(), ImageFactory() qs = workstation_query(image=image) assert "&" not in qs assert f"{settings.WORKSTATIONS_BASE_IMAGE_QUERY_PARAM}={image.pk}" in qs assert (f"{settings.WORKSTATIONS_OVERLAY_QUERY_PARAM}={overlay.pk}" not in qs) qs = workstation_query(image=image, overlay=overlay) assert "&" in qs assert f"{settings.WORKSTATIONS_BASE_IMAGE_QUERY_PARAM}={image.pk}" in qs assert f"{settings.WORKSTATIONS_OVERLAY_QUERY_PARAM}={overlay.pk}" in qs
def test_provider_describe_by_url(self): app_settings.ALT_GENERATOR_PREFER_UPLOAD = False image = ImageFactory() image.file = MockedUrlImageFile(image_url=test_image) provider = get_provider( 'wagtailaltgenerator.providers.cognitive.Cognitive') data = provider().describe(image) self.assertIsNotNone(data.description) self.assertTrue(len(data.tags) > 0) app_settings.ALT_GENERATOR_PREFER_UPLOAD = True
def test_update_method_new_slas_get_added_existing_get_updated(self): annotation_set_dict, annotation_set_obj = self.save_annotation_set() old_slas = list(annotation_set_obj.singlelandmarkannotation_set.all()) image3 = ImageFactory() new_slas = [ { "image": image3.id, "landmarks": [[2, 2], [3, 3], [4, 4]] }, { "image": old_slas[0].image.id, "landmarks": [[4, 4], [5, 5], [6, 6]], }, ] updated_set = { **annotation_set_dict, "singlelandmarkannotation_set": new_slas, } serializer = LandmarkAnnotationSetSerializer(annotation_set_obj, data=updated_set) serializer.is_valid() saved_set = None try: saved_set = serializer.save() except Exception as e: pytest.fail(f"Saving serializer failed with error: {str(e)}") assert saved_set.singlelandmarkannotation_set.count() == 3 assert saved_set.singlelandmarkannotation_set.filter( image=image3).exists() assert saved_set.singlelandmarkannotation_set.get( image__id=old_slas[0].image.id).landmarks == [[4.0, 4.0], [5.0, 5.0], [6.0, 6.0]]
def test_csv_export(now, client, answer_type, answer): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() editor = UserFactory() rs.add_editor(editor) reader = UserFactory() rs.add_reader(reader) q = QuestionFactory( question_text="foo", reader_study=rs, answer_type=answer_type ) a = AnswerFactory(question=q, answer=answer) a.images.add(im) a.save() response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.get, content_type="application/json", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert ( f'filename="{rs.slug}-answers-2020-01-01T00:00:00+00:00.csv"' in headers ) assert a.question.question_text in content assert a.question.get_answer_type_display() in content assert str(a.question.required) in content assert a.question.get_image_port_display() in content if isinstance(answer, dict): for key in answer: assert key in content else: assert re.sub(r"[\n\r\t]", " ", str(a.answer)) in content assert im.name in content assert a.creator.username in content response = get_view_for_user( viewname="api:reader-study-export-answers", reverse_kwargs={"pk": rs.pk}, user=reader, client=client, method=client.get, content_type="application/json", ) assert response.status_code == 404
def test_deleting_archive_item_removes_permissions(): ai1, ai2 = ArchiveItemFactory.create_batch(2) im = ImageFactory() civ = ComponentInterfaceValueFactory(image=im) with capture_on_commit_callbacks(execute=True): ai1.values.set([civ]) ai2.values.set([civ]) assert get_groups_with_set_perms(im) == { ai1.archive.editors_group: {"view_image"}, ai1.archive.uploaders_group: {"view_image"}, ai1.archive.users_group: {"view_image"}, ai2.archive.editors_group: {"view_image"}, ai2.archive.uploaders_group: {"view_image"}, ai2.archive.users_group: {"view_image"}, } with capture_on_commit_callbacks(execute=True): ai1.delete() assert get_groups_with_set_perms(im) == { ai2.archive.editors_group: {"view_image"}, ai2.archive.uploaders_group: {"view_image"}, ai2.archive.users_group: {"view_image"}, }
def test_changing_archive_updates_permissions(): ai = ArchiveItemFactory() im = ImageFactory() civ = ComponentInterfaceValueFactory(image=im) with capture_on_commit_callbacks(execute=True): ai.values.set([civ]) assert get_groups_with_set_perms(im) == { ai.archive.editors_group: {"view_image"}, ai.archive.uploaders_group: {"view_image"}, ai.archive.users_group: {"view_image"}, } a2 = ArchiveFactory() ai.archive = a2 with capture_on_commit_callbacks(execute=True): ai.save() assert get_groups_with_set_perms(im) == { a2.editors_group: {"view_image"}, a2.uploaders_group: {"view_image"}, a2.users_group: {"view_image"}, }
def test_image_permission_with_public_job(): g_reg_anon = Group.objects.get( name=settings.REGISTERED_AND_ANON_USERS_GROUP_NAME ) g_reg = Group.objects.get(name=settings.REGISTERED_USERS_GROUP_NAME) job = AlgorithmJobFactory() output_image = ImageFactory() civ = ComponentInterfaceValueFactory(image=output_image) job.outputs.add(civ) assert "view_image" not in get_perms(g_reg, output_image) assert "view_image" not in get_perms(g_reg_anon, output_image) assert "view_image" not in get_perms(g_reg, job.inputs.first().image) assert "view_image" not in get_perms(g_reg_anon, job.inputs.first().image) job.public = True job.save() assert "view_image" not in get_perms(g_reg, output_image) assert "view_image" in get_perms(g_reg_anon, output_image) assert "view_image" not in get_perms(g_reg, job.inputs.first().image) assert "view_image" in get_perms(g_reg_anon, job.inputs.first().image) job.public = False job.save() assert "view_image" not in get_perms(g_reg, output_image) assert "view_image" not in get_perms(g_reg_anon, output_image) assert "view_image" not in get_perms(g_reg, job.inputs.first().image) assert "view_image" not in get_perms(g_reg_anon, job.inputs.first().image)
def generate_annotation_set(retina_grader=False): grader = UserFactory() if retina_grader: add_to_graders_group([grader]) measurement = MeasurementAnnotationFactory(grader=grader) boolean = BooleanClassificationAnnotationFactory(grader=grader) integer = IntegerClassificationAnnotationFactory(grader=grader) polygon = PolygonAnnotationSetFactory(grader=grader) coordinatelist = CoordinateListAnnotationFactory(grader=grader) landmark = LandmarkAnnotationSetFactory(grader=grader) etdrs = ETDRSGridAnnotationFactory(grader=grader) # Create child models for polygon annotation set SinglePolygonAnnotationFactory.create_batch(10, annotation_set=polygon) # Create child models for landmark annotation set (3 per image) for i in range(5): image = ImageFactory() SingleLandmarkAnnotationFactory(annotation_set=landmark, image=image) return AnnotationSet( grader=grader, measurement=measurement, boolean=boolean, polygon=polygon, coordinatelist=coordinatelist, landmark=landmark, etdrs=etdrs, integer=integer, )
def test_no_algorithm_image_does_nothing(self): image = ImageFactory() create_algorithm_jobs( algorithm_image=None, images=[image], ) assert Job.objects.count() == 0
def test_job_permissions_for_archive(self): ai = AlgorithmImageFactory(ready=True) archive = ArchiveFactory() # Fake an image upload via a session u = UserFactory() s = UploadSessionFactory(creator=u) im = ImageFactory() s.image_set.set([im]) archive.images.set([im]) archive.algorithms.set([ai.algorithm]) create_algorithm_jobs_for_archive(archive_pks=[archive.pk]) job = Job.objects.get() # The archive editors, users and uploaders, algorithm editors and job # viewers should be able to view the job assert get_groups_with_set_perms(job) == { archive.editors_group: {"view_job"}, archive.users_group: {"view_job"}, archive.uploaders_group: {"view_job"}, ai.algorithm.editors_group: {"view_job"}, job.viewers: {"view_job"}, } # No-one should be able to change the job assert (get_users_with_perms(job, attach_perms=True, with_group_users=False) == {}) # No-one should be in the viewers group assert {*job.viewers.user_set.all()} == set()
def test_ground_truth_is_excluded(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) editor = UserFactory() rs.add_editor(editor) rs.add_reader(editor) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) a1 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=True) a1.images.add(im) a2 = AnswerFactory(question=q, creator=editor, answer=True, is_ground_truth=False) a2.images.add(im) response = get_view_for_user( viewname="api:reader-studies-answer-mine", user=editor, client=client, method=client.get, content_type="application/json", ) results = response.json()["results"] assert len(results) == 1 assert results[0]["pk"] == str(a2.pk)
def test_answer_create(client): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.BOOL) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": True, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == 201 answer = Answer.objects.get(pk=response.data.get("pk")) assert answer.creator == reader assert answer.images.count() == 1 assert answer.images.all()[0] == im assert answer.question == q assert answer.answer is True
def test_hanging_list_shuffle_per_user(client): hanging_list = [{"main": f"image_{n}"} for n in range(10)] rs = ReaderStudyFactory(hanging_list=hanging_list) images = [ImageFactory(name=f"image_{n}") for n in range(10)] rs.images.set(images) rs.save() # The shuffling is seeded with the users pk, so needs to stay constant u1, u2 = UserFactory(pk=1_000_000), UserFactory(pk=1_000_001) rs.add_reader(user=u1) rs.add_reader(user=u2) assert rs.get_hanging_list_images_for_user( user=u1) == rs.get_hanging_list_images_for_user(user=u2) rs.shuffle_hanging_list = True rs.save() u1_list = rs.get_hanging_list_images_for_user(user=u1) u2_list = rs.get_hanging_list_images_for_user(user=u2) # Check that the list is different per user and contains all of the images assert u1_list != u2_list assert ({u["main"] for u in u1_list} == {u["main"] for u in u2_list} == {im.api_url for im in images}) # Check that repeat requests return the same list assert rs.get_hanging_list_images_for_user( user=u1) == rs.get_hanging_list_images_for_user(user=u1) # Check that the list is consistent over time, if not, maybe numpy has # changed their implementation api_to_image = {im.api_url: im.name for im in images} assert [api_to_image[h["main"]] for h in u1_list] == [ "image_8", "image_3", "image_7", "image_1", "image_4", "image_9", "image_0", "image_5", "image_6", "image_2", ] # Check that the api is hooked up response = get_view_for_user( client=client, viewname="api:reader-study-detail", reverse_kwargs={"pk": rs.pk}, user=u1, ) assert response.status_code == 200 assert response.json()["hanging_list_images"] == u1_list
def test_answer_creator_is_reader(client): rs_set = TwoReaderStudies() im = ImageFactory() rs_set.rs1.images.add(im) q = QuestionFactory(reader_study=rs_set.rs1, answer_type=Question.AnswerType.BOOL) tests = ( (rs_set.editor1, 201), (rs_set.reader1, 201), (rs_set.editor2, 400), (rs_set.reader2, 400), (rs_set.u, 400), ) for test in tests: response = get_view_for_user( viewname="api:reader-studies-answer-list", user=test[0], client=client, method=client.post, data={ "answer": True, "images": [im.api_url], "question": q.api_url, }, content_type="application/json", ) assert response.status_code == test[1]
def test_reader_study_add_ground_truth_ds(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory(use_display_sets=True) QuestionFactory( reader_study=rs, question_text="bar", answer_type=Question.AnswerType.SINGLE_LINE_TEXT, ) civ = ComponentInterfaceValueFactory(image=ImageFactory()) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) editor = UserFactory() rs.editors_group.user_set.add(editor) gt = io.StringIO() fake_writer = csv.writer(gt) fake_writer.writerows([["images", "foo"], [str(ds.pk), "bar"]]) gt.seek(0) response = get_view_for_user( viewname="reader-studies:add-ground-truth", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"ground_truth": gt}, follow=True, user=editor, ) assert response.status_code == 200
def test_answer_is_correct_type(client, answer_type, answer, expected): im = ImageFactory() rs = ReaderStudyFactory() rs.images.add(im) rs.save() reader = UserFactory() rs.add_reader(reader) q = QuestionFactory(reader_study=rs, answer_type=answer_type) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=reader, client=client, method=client.post, data={ "answer": answer, "images": [im.api_url], "question": q.api_url }, content_type="application/json", ) assert response.status_code == expected
def test_used_by_other_public_result_permissions(): g_reg_anon = Group.objects.get( name=settings.REGISTERED_AND_ANON_USERS_GROUP_NAME) g_reg = Group.objects.get(name=settings.REGISTERED_USERS_GROUP_NAME) j1 = AlgorithmJobFactory(public=True) j2 = AlgorithmJobFactory(public=True) shared_image = ImageFactory() civ1 = ComponentInterfaceValueFactory(image=shared_image) j1.outputs.add(civ1) civ2 = ComponentInterfaceValueFactory(image=shared_image) j2.outputs.add(civ2) assert "view_image" not in get_perms(g_reg, shared_image) assert "view_image" in get_perms(g_reg_anon, shared_image) j2.outputs.clear() assert "view_image" not in get_perms(g_reg, shared_image) assert "view_image" in get_perms(g_reg_anon, shared_image) j2.outputs.add(civ2) j2.public = False j2.save() assert "view_image" not in get_perms(g_reg, shared_image) assert "view_image" in get_perms(g_reg_anon, shared_image) j1.public = False j1.save() assert "view_image" not in get_perms(g_reg, shared_image) assert "view_image" not in get_perms(g_reg_anon, shared_image)
def test_job_permissions_for_session(self): ai = AlgorithmImageFactory(ready=True) u = UserFactory() s = UploadSessionFactory(creator=u) im = ImageFactory() s.image_set.set([im]) create_algorithm_jobs_for_session(upload_session_pk=s.pk, algorithm_image_pk=ai.pk) job = Job.objects.get() # Editors and viewers should be able to view the job assert get_groups_with_set_perms(job) == { ai.algorithm.editors_group: {"view_job"}, job.viewers: {"view_job"}, } # The Session Creator should be able to change the job assert get_users_with_perms(job, attach_perms=True, with_group_users=False) == { u: ["change_job"] } # The only member of the viewers group should be the creator assert {*job.viewers.user_set.all()} == {u}
def test_filter_images_api_view(client): alg = AlgorithmFactory() user = UserFactory() alg.add_editor(user=user) alg_job = AlgorithmJobFactory(algorithm_image__algorithm=alg, creator=user) im = ImageFactory() civ = ComponentInterfaceValueFactory(image=im) alg_job.outputs.add(civ) response = get_view_for_user( viewname="api:image-list", client=client, user=user, content_type="application/json", ) assert response.status_code == 200 assert {r["pk"] for r in response.json()["results"]} == { str(i.pk) for i in [*[inpt.image for inpt in alg_job.inputs.all()], im] } response = get_view_for_user( client=client, user=user, viewname="api:image-list", data={"origin": str(im.origin.pk)}, content_type="application/json", ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(im.pk)
def test_job_permissions_for_challenge(self): ai = AlgorithmImageFactory(ready=True) archive = ArchiveFactory() evaluation = EvaluationFactory(submission__phase__archive=archive, submission__algorithm_image=ai) # Fake an image upload via a session u = UserFactory() s = UploadSessionFactory(creator=u) im = ImageFactory() s.image_set.set([im]) archive.images.set([im]) create_algorithm_jobs_for_evaluation(evaluation_pk=evaluation.pk) job = Job.objects.get() # Only the challenge admins and job viewers should be able to view the # job. NOTE: NOT THE ALGORITHM EDITORS, they are the participants # to the challenge and should not be able to see the test data assert get_groups_with_set_perms(job) == { evaluation.submission.phase.challenge.admins_group: {"view_job"}, job.viewers: {"view_job"}, } # No-one should be able to change the job assert (get_users_with_perms(job, attach_perms=True, with_group_users=False) == {}) # No-one should be in the viewers group assert {*job.viewers.user_set.all()} == set()
def main_image(self): return ImageFactory.build( filename='main/image/url.jpg', caption='Main image caption' )