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_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_user_upload_to_display_set_without_interface(client, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user=user) ci = ComponentInterface.objects.filter(slug="generic-overlay").get() civ = ComponentInterfaceValueFactory(interface=ci) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) assert ds.values.count() == 1 upload = create_upload_from_file( file_path=Path(__file__).parent / "resources" / "image10x10x10.mha", creator=user, ) with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "display_set": ds.pk }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 400 assert ("An interface needs to be defined to upload to a display set." in response.json()["non_field_errors"])
def test_assign_score(settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory(use_display_sets=False) im = ImageFactory() q1 = QuestionFactory(reader_study=rs) q2 = QuestionFactory(reader_study=rs, answer_type=Question.AnswerType.MULTIPLE_CHOICE) e, r1, r2 = UserFactory(), UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(e) rs.add_reader(r1) rs.add_reader(r2) with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q1, creator=r1, answer="foo") a1.images.add(im) assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q1, creator=e, answer="foo", is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q1, creator=r2, answer="foo") a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0 with capture_on_commit_callbacks(execute=True): a1 = AnswerFactory(question=q2, creator=r1, answer=[]) a1.images.add(im) a1.refresh_from_db() assert a1.score is None with capture_on_commit_callbacks(execute=True): gt = AnswerFactory(question=q2, creator=e, answer=[], is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 with capture_on_commit_callbacks(execute=True): a2 = AnswerFactory(question=q2, creator=r2, answer=[]) a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0
def test_question_delete_disabled_for_questions_with_answers(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) AnswerFactory(creator=r1, question=q, answer=True) assert Answer.objects.count() == 1 assert Question.objects.count() == 1 assert not q.is_fully_editable response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 403 assert Question.objects.count() == 1 # if answer is deleted, deletion of the question is possible again get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=editor, ) assert Answer.objects.count() == 0 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0
def test_remove_image_api_view(client): rs = ReaderStudyFactory() reader, editor = UserFactory(), UserFactory() rs.add_reader(reader) rs.add_editor(editor) response = get_view_for_user( viewname="api:reader-study-remove-image", reverse_kwargs={"pk": rs.pk}, user=reader, client=client, method=client.patch, data={"image": 1}, content_type="application/json", follow=True, ) assert response.status_code == 403 response = get_view_for_user( viewname="api:reader-study-remove-image", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.patch, data={"image": 1}, content_type="application/json", follow=True, ) assert response.status_code == 200 assert "Image could not be removed from reader study." in str( response.content ) im = ImageFactory() rs.images.add(im) assert im in rs.images.all() response = get_view_for_user( viewname="api:reader-study-remove-image", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.patch, data={"image": im.pk}, content_type="application/json", follow=True, ) assert response.status_code == 200 assert "Image removed from reader study." in str(response.content) assert im not in rs.images.all()
def test_session_with_user_upload_to_readerstudy(client, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user=user) upload = create_upload_from_file( file_path=Path(__file__).parent / "resources" / "image10x10x10.mha", creator=user, ) # try upload with interface with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "reader_study": rs.slug, "interface": "generic-overlay", }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 400 assert ( "An interface can only be defined for archive, archive item or display set uploads." in response.json()["non_field_errors"]) # try without interface with capture_on_commit_callbacks(execute=True): response = get_view_for_user( viewname="api:upload-session-list", user=user, client=client, method=client.post, content_type="application/json", data={ "uploads": [upload.api_url], "reader_study": rs.slug }, HTTP_X_FORWARDED_PROTO="https", ) assert response.status_code == 201 upload_session = response.json() assert upload_session["uploads"] == [upload.api_url]
def test_upload_some_images(client: Client, challenge_set, settings): # Override the celery settings settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) user = UserFactory() # Use reader studies as this uses UploadRawImagesForm rs = ReaderStudyFactory(use_display_sets=False) rs.add_editor(user) response = get_view_for_user( client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, ) assert response.status_code == 200 assert rs.images.count() == 0 assert RawImageUploadSession.objects.count() == 0 user_upload = create_upload_from_file(file_path=RESOURCE_PATH / "image10x10x10.mha", creator=user) with capture_on_commit_callbacks(execute=True): response = get_view_for_user( data={"user_uploads": [user_upload.pk]}, client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, method=client.post, ) assert response.status_code == 302 assert rs.images.count() == 1 sessions = RawImageUploadSession.objects.all() assert len(sessions) == 1 response = get_view_for_user(url=sessions[0].get_absolute_url(), client=client, user=user) assert response.status_code == 200 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=UserFactory(is_staff=True), ) assert response.status_code == 403
def test_assign_answer_image(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory( reader_study=rs, answer_type=Question.ANSWER_TYPE_POLYGON_IMAGE ) us = RawImageUploadSessionFactory(creator=reader) answer = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us.pk)}, ) f = StagedFileFactory( file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha" ) RawImageFileFactory(upload_session=us, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us.pk}, user=reader, client=client, method=client.patch, data={"answer": str(answer.pk)}, content_type="application/json", ) assert response.status_code == 200 answer.refresh_from_db() image = us.image_set.first() assert answer.answer_image == image assert reader.has_perm("view_image", image) assert editor.has_perm("view_image", image)
class TwoReaderStudies: def __init__(self): self.creator = get_rs_creator() self.rs1, self.rs2 = ReaderStudyFactory( use_display_sets=False), ReaderStudyFactory(use_display_sets=False) self.editor1, self.reader1, self.editor2, self.reader2 = ( UserFactory(), UserFactory(), UserFactory(), UserFactory(), ) self.rs1.add_editor(user=self.editor1) self.rs2.add_editor(user=self.editor2) self.rs1.add_reader(user=self.reader1) self.rs2.add_reader(user=self.reader2) self.u = UserFactory()
def test_upload_some_images(client: Client, challenge_set, settings): # Override the celery settings settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) user = UserFactory() # Use reader studies as this uses UploadRawImagesForm rs = ReaderStudyFactory() rs.add_editor(user) response = get_view_for_user( client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, ) assert response.status_code == 200 assert rs.images.count() == 0 assert RawImageUploadSession.objects.count() == 0 file1 = create_file_from_filepath(RESOURCE_PATH / "image10x10x10.mha") response = get_view_for_user( data={"files": f"{file1.uuid}"}, client=client, viewname="reader-studies:add-images", user=user, reverse_kwargs={"slug": rs.slug}, method=client.post, ) assert response.status_code == 302 assert rs.images.count() == 1 sessions = RawImageUploadSession.objects.all() assert len(sessions) == 1 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=user ) assert response.status_code == 200 response = get_view_for_user( url=sessions[0].get_absolute_url(), client=client, user=UserFactory(is_staff=True), ) assert response.status_code == 403
def test_assign_score(settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) rs = ReaderStudyFactory() im = ImageFactory() q1 = QuestionFactory(reader_study=rs) q2 = QuestionFactory( reader_study=rs, answer_type=Question.ANSWER_TYPE_MULTIPLE_CHOICE ) e, r1, r2 = UserFactory(), UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(e) rs.add_reader(r1) rs.add_reader(r2) a1 = AnswerFactory(question=q1, creator=r1, answer="foo") a1.images.add(im) assert a1.score is None gt = AnswerFactory( question=q1, creator=e, answer="foo", is_ground_truth=True ) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 a2 = AnswerFactory(question=q1, creator=r2, answer="foo") a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0 a1 = AnswerFactory(question=q2, creator=r1, answer=[]) a1.images.add(im) assert a1.score is None gt = AnswerFactory(question=q2, creator=e, answer=[], is_ground_truth=True) gt.images.add(im) a1.refresh_from_db() assert a1.score == 1.0 a2 = AnswerFactory(question=q2, creator=r2, answer=[]) a2.images.add(im) a2.refresh_from_db() assert a2.score == 1.0
def test_generate_hanging_list_api_view(generate_hanging_list, client): rs = ReaderStudyFactory() editor = UserFactory() rs.add_editor(editor) response = get_view_for_user( viewname="api:reader-study-generate-hanging-list", reverse_kwargs={"pk": rs.pk}, user=editor, client=client, method=client.patch, follow=True, ) assert response.status_code == 200 assert "Hanging list generated." in str(response.content) generate_hanging_list.assert_called_once()
def test_reader_study_delete(client): rs = ReaderStudyFactory(use_display_sets=False) editor = UserFactory() reader = UserFactory() rs.add_editor(editor) rs.add_reader(reader) assert ReaderStudy.objects.count() == 1 assert is_following(user=editor, obj=rs) response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=reader, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert "Confirm Deletion" in response.rendered_content response = get_view_for_user( viewname="reader-studies:delete", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 0 assert not is_following(user=editor, obj=rs)
def test_api_list_is_filtered(client): rs1, rs2 = ReaderStudyFactory(), ReaderStudyFactory() rs1_editor = UserFactory() rs1.add_editor(rs1_editor) q1, q2 = ( QuestionFactory(reader_study=rs1), QuestionFactory(reader_study=rs2), ) a1, _ = ( AnswerFactory(question=q1, answer=True), AnswerFactory(question=q2, answer=False), ) response = get_view_for_user(viewname="api:reader-study-list", user=rs1_editor, client=client) assert response.status_code == 200 assert response.json()["count"] == 1 response = get_view_for_user( viewname="api:reader-study-detail", reverse_kwargs={"pk": rs1.pk}, user=rs1_editor, client=client, ) assert response.status_code == 200 assert len(response.json()["questions"]) == 1 response = get_view_for_user( viewname="api:reader-studies-question-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(q1.pk) response = get_view_for_user( viewname="api:reader-studies-answer-list", user=rs1_editor, client=client, ) assert response.status_code == 200 assert response.json()["count"] == 1 assert response.json()["results"][0]["pk"] == str(a1.pk)
def test_answer_remove(client): rs = ReaderStudyFactory() r1, r2, editor = UserFactory(), UserFactory(), UserFactory() rs.add_reader(r1) rs.add_reader(r2) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.ANSWER_TYPE_BOOL, ) im = ImageFactory() a1 = AnswerFactory(creator=r1, question=q, answer=True) a1.images.set([im]) a2 = AnswerFactory(creator=r2, question=q, answer=True) a2.images.set([im]) assert Answer.objects.count() == 2 response = get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=r1, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:answers-remove", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"user": r1.id}, follow=True, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == 1 assert Answer.objects.filter(creator=r1).count() == 0 assert Answer.objects.filter(creator=r2).count() == 1
def test_upload_session_owned_by_answer_creator(client, settings, answer_type): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im = ImageFactory() editor, reader = UserFactory(), UserFactory() rs.images.add(im) rs.add_editor(editor) rs.add_reader(reader) question = QuestionFactory(reader_study=rs, answer_type=answer_type) us1 = RawImageUploadSessionFactory(creator=reader) us2 = RawImageUploadSessionFactory(creator=editor) answer1 = AnswerFactory( creator=reader, question=question, answer={"upload_session_pk": str(us1.pk)}, ) f = StagedFileFactory(file__from_path=Path(__file__).parent.parent / "cases_tests" / "resources" / "image10x10x10.mha") RawImageFileFactory(upload_session=us1, staged_file_id=f.file_id) response = get_view_for_user( viewname="api:upload-session-process-images", reverse_kwargs={"pk": us2.pk}, user=editor, client=client, method=client.patch, data={"answer": str(answer1.pk)}, content_type="application/json", ) assert response.status_code == 400 assert (b"User does not have permission to add an image to this answer" in response.rendered_content)
def test_question_delete(client): rs = ReaderStudyFactory(use_display_sets=False) r1, editor = UserFactory(), UserFactory() rs.add_reader(r1) rs.add_editor(editor) q = QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.AnswerType.BOOL, ) assert Question.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, follow=True, user=r1, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:question-delete", client=client, method=client.post, reverse_kwargs={ "slug": rs.slug, "pk": q.pk }, user=editor, ) assert response.status_code == 302 assert Question.objects.count() == 0 assert str(rs) in response.url
def reader_study_with_gt(): rs = ReaderStudyFactory() im1, im2 = ImageFactory(name="im1"), ImageFactory(name="im2") q1, q2, q3 = [ QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q2", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q3", ), ] r1, r2, editor = UserFactory(), UserFactory(), UserFactory() rs.add_reader(r1) rs.add_reader(r2) rs.add_editor(editor) rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.save() for question in [q1, q2, q3]: for im in [im1, im2]: ans = AnswerFactory( question=question, creator=editor, answer=True, is_ground_truth=True, ) ans.images.add(im) return rs
def test_reader_study_display_set_list(client): user = UserFactory() rs = ReaderStudyFactory(use_display_sets=True) rs.add_editor(user) civ = ComponentInterfaceValueFactory(image=ImageFactory()) ds = DisplaySetFactory(reader_study=rs) ds.values.add(civ) response = get_view_for_user( viewname="reader-studies:display_sets", reverse_kwargs={"slug": rs.slug}, client=client, user=user, ) assert response.status_code == 200 response = get_view_for_user( viewname="reader-studies:display_sets", reverse_kwargs={"slug": rs.slug}, client=client, user=user, method=client.get, follow=True, data={ "length": 10, "draw": 1, "order[0][dir]": "desc", "order[0][column]": 0, }, **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}, ) resp = response.json() assert str(ds.pk) in resp["data"][0][0]
def test_csv_export(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-studies-answer-list", params={"question__reader_study": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers if isinstance(answer, dict): for key in answer: assert key in content else: assert re.sub(r"\n", r"\\n", str(a.answer)) in content assert a.creator.username in content response = get_view_for_user( viewname="api:reader-studies-question-list", params={"reader_study": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/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 response = get_view_for_user( viewname="api:image-list", params={"readerstudies": str(rs.pk)}, user=editor, client=client, method=client.get, HTTP_ACCEPT="text/csv", ) headers = str(response.serialize_headers()) content = str(response.content) assert response.status_code == 200 assert "Content-Type: text/csv" in headers assert im.name in content
def test_progress_for_user(settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory() im1, im2 = ImageFactory(name="im1"), ImageFactory(name="im2") q1, q2, q3 = [ QuestionFactory(reader_study=rs), QuestionFactory(reader_study=rs), QuestionFactory(reader_study=rs), ] reader = UserFactory() rs.add_reader(reader) question_perc = 100 / 6 assert rs.get_progress_for_user(reader) == { "diff": 0.0, "hangings": 0.0, "questions": 0.0, } rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.save() progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == 0 a11 = AnswerFactory(question=q1, answer="foo", creator=reader) a11.images.add(im1) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == pytest.approx(question_perc) a21 = AnswerFactory(question=q1, answer="foo", creator=reader) a21.images.add(im2) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == pytest.approx(question_perc * 2) a12 = AnswerFactory(question=q2, answer="foo", creator=reader) a12.images.add(im1) a13 = AnswerFactory(question=q3, answer="foo", creator=reader) a13.images.add(im1) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 50 assert progress["questions"] == pytest.approx(question_perc * 4) editor = UserFactory() rs.add_reader(editor) rs.add_editor(editor) for q in [q1, q2, q3]: for im in [im1, im2]: a = AnswerFactory(question=q, answer="foo", creator=editor, is_ground_truth=True) a.images.add(im) progress = rs.get_progress_for_user(editor) assert progress["hangings"] == 0 assert progress["questions"] == 0 for q in [q1, q2, q3]: for im in [im1, im2]: a = AnswerFactory(question=q, answer="foo", creator=editor, is_ground_truth=False) a.images.add(im) progress = rs.get_progress_for_user(editor) assert progress["hangings"] == 100.0 assert progress["questions"] == 100.0
def test_answer_update(client): im1, im2 = ImageFactory(), ImageFactory() rs = ReaderStudyFactory() rs.images.add(im1, im2) rs.save() reader = UserFactory() rs.add_reader(reader) editor = UserFactory() rs.add_editor(editor) 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": [im1.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.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={ "answer": False, "images": [im2.api_url] }, content_type="application/json", ) assert response.status_code == 400 answer.refresh_from_db() assert response.json() == { "non_field_errors": ["This reader study does not allow answer modification."] } assert answer.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 rs.allow_answer_modification = True rs.save() response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={ "answer": False, "images": [im2.api_url] }, content_type="application/json", ) assert response.status_code == 400 answer.refresh_from_db() assert response.json() == { "non_field_errors": ["Only the answer field can be modified."] } assert answer.answer is True assert answer.images.first() == im1 assert answer.history.count() == 1 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=reader, client=client, method=client.patch, data={"answer": False}, content_type="application/json", ) assert response.status_code == 200 answer.refresh_from_db() assert answer.answer is False assert answer.images.first() == im1 assert answer.history.count() == 2 response = get_view_for_user( viewname="api:reader-studies-answer-detail", reverse_kwargs={"pk": answer.pk}, user=editor, client=client, method=client.patch, data={"answer": False}, content_type="application/json", ) assert response.status_code == 403 answer.refresh_from_db() assert answer.answer is False assert answer.history.count() == 2
def test_progress_for_user(settings, use_display_sets): # noqa: C901 settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) rs = ReaderStudyFactory(use_display_sets=use_display_sets) im1, im2 = ImageFactory(name="im1"), ImageFactory(name="im2") q1, q2, q3 = [ QuestionFactory(reader_study=rs), QuestionFactory(reader_study=rs), QuestionFactory(reader_study=rs), ] reader = UserFactory() rs.add_reader(reader) question_perc = 100 / 6 assert rs.get_progress_for_user(reader) == { "diff": 0.0, "hangings": 0.0, "questions": 0.0, } if use_display_sets: ci = ComponentInterface.objects.get(slug="generic-medical-image") civ1 = ComponentInterfaceValueFactory(image=im1, interface=ci) civ2 = ComponentInterfaceValueFactory(image=im2, interface=ci) ds1, ds2 = DisplaySetFactory(reader_study=rs), DisplaySetFactory( reader_study=rs) ds1.values.add(civ1) ds2.values.add(civ2) else: rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.save() progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == 0 a11 = AnswerFactory(question=q1, answer="foo", creator=reader) if use_display_sets: a11.display_set = ds1 a11.save() else: a11.images.add(im1) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == pytest.approx(question_perc) a21 = AnswerFactory(question=q1, answer="foo", creator=reader) if use_display_sets: a21.display_set = ds2 a21.save() else: a21.images.add(im2) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 0 assert progress["questions"] == pytest.approx(question_perc * 2) a12 = AnswerFactory(question=q2, answer="foo", creator=reader) a13 = AnswerFactory(question=q3, answer="foo", creator=reader) if use_display_sets: a12.display_set = ds1 a12.save() a13.display_set = ds1 a13.save() else: a12.images.add(im1) a13.images.add(im1) progress = rs.get_progress_for_user(reader) assert progress["hangings"] == 50 assert progress["questions"] == pytest.approx(question_perc * 4) editor = UserFactory() rs.add_reader(editor) rs.add_editor(editor) for q in [q1, q2, q3]: if use_display_sets: for ds in [ds1, ds2]: a = AnswerFactory( question=q, answer="foo", creator=editor, is_ground_truth=True, display_set=ds, ) else: for im in [im1, im2]: a = AnswerFactory( question=q, answer="foo", creator=editor, is_ground_truth=True, ) a.images.add(im) progress = rs.get_progress_for_user(editor) assert progress["hangings"] == 0 assert progress["questions"] == 0 for q in [q1, q2, q3]: if use_display_sets: for ds in [ds1, ds2]: a = AnswerFactory( question=q, answer="foo", creator=editor, is_ground_truth=False, display_set=ds, ) else: for im in [im1, im2]: a = AnswerFactory( question=q, answer="foo", creator=editor, is_ground_truth=False, ) a.images.add(im) progress = rs.get_progress_for_user(editor) assert progress["hangings"] == 100.0 assert progress["questions"] == 100.0
def test_archive_items_to_reader_study_update(client, settings): settings.task_eager_propagates = (True, ) settings.task_always_eager = (True, ) archive = ArchiveFactory() rs1 = ReaderStudyFactory(use_display_sets=True) rs2 = ReaderStudyFactory(use_display_sets=False) editor, user = UserFactory(), UserFactory() archive.add_user(user) archive.add_editor(editor) rs1.add_editor(editor) rs2.add_editor(editor) im1, im2, im3, im4 = ImageFactory.create_batch(4) overlay = ComponentInterface.objects.get(slug="generic-overlay") image = ComponentInterface.objects.get(slug="generic-medical-image") civ1, civ2, civ3, civ4 = ( ComponentInterfaceValueFactory(interface=image, image=im1), ComponentInterfaceValueFactory(interface=image, image=im2), ComponentInterfaceValueFactory(interface=overlay, image=im3), ComponentInterfaceValueFactory(interface=overlay, image=im4), ) ai1 = ArchiveItemFactory(archive=archive) ai2 = ArchiveItemFactory(archive=archive) ai1.values.add(civ1) ai2.values.add(civ2) response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, reverse_kwargs={"slug": archive.slug}, user=user, ) assert response.status_code == 200 assert str(rs1.pk) not in response.rendered_content assert str(rs2.pk) not in response.rendered_content response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, reverse_kwargs={"slug": archive.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert str(rs1.pk) in response.rendered_content assert str(rs2.pk) not in response.rendered_content assert im1.name in response.rendered_content assert im2.name in response.rendered_content assert im3.name not in response.rendered_content assert im4.name not in response.rendered_content ai1.values.add(civ3) ai2.values.add(civ4) response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, reverse_kwargs={"slug": archive.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert f"{im1.name}, {im3.name}" in response.rendered_content assert f"{im2.name}, {im4.name}" in response.rendered_content
def test_archive_items_to_reader_study_update_form(client, settings): settings.task_eager_propagates = (True,) settings.task_always_eager = (True,) archive = ArchiveFactory() rs1, rs2 = ReaderStudyFactory(use_display_sets=True), ReaderStudyFactory( use_display_sets=False ) editor, reader = UserFactory(), UserFactory() archive.editors_group.user_set.add(editor) rs1.add_editor(editor) rs1.add_reader(reader) rs2.add_editor(editor) im1, im2, im3, im4 = ImageFactory.create_batch(4) overlay = ComponentInterface.objects.get(slug="generic-overlay") image = ComponentInterface.objects.get(slug="generic-medical-image") civ1, civ2, civ3, civ4 = ( ComponentInterfaceValueFactory(interface=image, image=im1), ComponentInterfaceValueFactory(interface=image, image=im2), ComponentInterfaceValueFactory(interface=overlay, image=im3), ComponentInterfaceValueFactory(interface=overlay, image=im4), ) ai1 = ArchiveItemFactory(archive=archive) ai2 = ArchiveItemFactory(archive=archive) ai1.values.add(civ1) ai2.values.add(civ2) assert rs1.display_sets.count() == 0 response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, method=client.post, data={"items": [ai1.pk, ai2.pk], "reader_study": rs1.pk}, reverse_kwargs={"slug": archive.slug}, follow=True, user=reader, ) assert response.status_code == 403 assert rs1.display_sets.count() == 0 response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, method=client.post, data={"items": [ai1.pk, ai2.pk], "reader_study": rs1.pk}, reverse_kwargs={"slug": archive.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert rs1.display_sets.count() == 2 assert sorted( list(rs1.display_sets.values_list("values", flat=True)) ) == sorted([civ1.pk, civ2.pk]) assert rs2.display_sets.count() == 0 response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, method=client.post, data={"items": [ai1.pk, ai2.pk], "reader_study": rs2.pk}, reverse_kwargs={"slug": archive.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert rs2.display_sets.count() == 0 ai1.values.add(civ3) ai2.values.add(civ4) response = get_view_for_user( viewname="archives:items-reader-study-update", client=client, method=client.post, data={"items": [ai1.pk, ai2.pk], "reader_study": rs1.pk}, reverse_kwargs={"slug": archive.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert rs1.display_sets.count() == 4 assert sorted( sorted(list(ds.values.values_list("pk", flat=True))) for ds in rs1.display_sets.all() ) == sorted([[civ1.pk], [civ2.pk], [civ1.pk, civ3.pk], [civ2.pk, civ4.pk]])
def test_example_ground_truth(client, tmpdir): rs = ReaderStudyFactory() reader, editor = UserFactory(), UserFactory() q1, q2, q3 = ( QuestionFactory( reader_study=rs, question_text="q1", answer_type=Question.ANSWER_TYPE_BOOL, ), QuestionFactory( reader_study=rs, question_text="q2", answer_type=Question.ANSWER_TYPE_CHOICE, ), QuestionFactory( reader_study=rs, question_text="q3", answer_type=Question.ANSWER_TYPE_SINGLE_LINE_TEXT, ), ) CategoricalOptionFactory(question=q2, title="option") im1, im2, im3 = (ImageFactory(), ImageFactory(), ImageFactory()) rs.images.set([im1, im2, im3]) rs.add_reader(reader) rs.add_editor(editor) rs.generate_hanging_list() response = get_view_for_user( viewname="reader-studies:example-ground-truth", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=reader, ) assert response.status_code == 403 response = get_view_for_user( viewname="reader-studies:example-ground-truth", client=client, method=client.get, reverse_kwargs={"slug": rs.slug}, follow=True, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == 0 gt = io.BytesIO() gt.write(response.content) gt.seek(0) response = get_view_for_user( viewname="reader-studies:add-ground-truth", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, follow=True, data={"ground_truth": gt}, user=editor, ) assert response.status_code == 200 assert Answer.objects.count() == rs.images.count() * rs.questions.count() for image in [im1, im2, im3]: for question in [q1, q2, q3]: assert Answer.objects.filter(images=image, question=question, is_ground_truth=True).exists()
def test_reader_study_copy(client): rs = ReaderStudyFactory(title="copied") editor = UserFactory() editor2 = UserFactory() reader = UserFactory() rs.add_reader(reader) rs.add_editor(editor) rs.add_editor(editor2) QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q1", ), QuestionFactory( reader_study=rs, answer_type=Question.AnswerType.BOOL, question_text="q2", ) im1, im2 = ImageFactory(), ImageFactory() rs.images.set([im1, im2]) rs.hanging_list = [{"main": im1.name}, {"main": im2.name}] rs.case_text = {im1.name: "test", im2.name: "test2"} rs.save() assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=reader, follow=True, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=editor, follow=True, ) assert response.status_code == 403 assert ReaderStudy.objects.count() == 1 add_perm = Permission.objects.get( codename=f"add_{ReaderStudy._meta.model_name}" ) editor.user_permissions.add(add_perm) response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "1"}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 2 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "1" assert _rs.images.count() == 0 assert _rs.questions.count() == 0 assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 assert _rs.hanging_list == [] assert _rs.case_text == {} response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "2", "copy_questions": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 3 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "2" assert _rs.questions.count() == 2 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "3", "copy_images": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 4 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "3" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "4", "copy_hanging_list": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ( "Hanging list and case text can only be copied if the images are copied as well" in response.rendered_content ) assert ReaderStudy.objects.count() == 4 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "4", "copy_images": True, "copy_hanging_list": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 5 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "4" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == rs.hanging_list assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "5", "copy_images": True, "copy_case_text": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 6 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "5" assert _rs.questions.count() == 0 assert _rs.images.count() == 2 assert _rs.hanging_list == [] assert _rs.case_text == rs.case_text assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "6", "copy_readers": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 7 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "6" assert _rs.questions.count() == 0 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 1 assert _rs.editors_group.user_set.count() == 1 response = get_view_for_user( viewname="reader-studies:copy", client=client, method=client.post, reverse_kwargs={"slug": rs.slug}, data={"title": "7", "copy_editors": True}, user=editor, follow=True, ) assert response.status_code == 200 assert ReaderStudy.objects.count() == 8 _rs = ReaderStudy.objects.order_by("created").last() assert _rs.title == "7" assert _rs.questions.count() == 0 assert _rs.images.count() == 0 assert _rs.hanging_list == [] assert _rs.case_text == {} assert _rs.readers_group.user_set.count() == 0 assert _rs.editors_group.user_set.count() == 2