def api_delete_speechparts_alignments(api_version, doc_id, user_id): from app.api.transcriptions.routes import get_reference_transcription forbid = forbid_if_nor_teacher_nor_admin_and_wants_user_data( current_app, user_id) if forbid: return forbid transcription = get_reference_transcription(doc_id) if transcription is None: return make_404(details="Transcription not found") try: # TRUNCATE for old_al in AlignmentDiscours.query.filter( AlignmentDiscours.transcription_id == transcription.id, AlignmentDiscours.user_id == user_id).all(): db.session.delete(old_al) db.session.commit() except Exception as e: db.session.rollback() print(str(e)) return make_400(str(e)) return make_200(data=[])
def api_get_alignment_translation_from_user(api_version, doc_id, user_id): from app.api.transcriptions.routes import get_reference_transcription from app.api.translations.routes import get_translation forbid = forbid_if_nor_teacher_nor_admin_and_wants_user_data( current_app, user_id) if forbid: return forbid transcription = get_reference_transcription(doc_id) if transcription is None: return make_404(details="No transcription available") translation = get_translation(doc_id=doc_id, user_id=user_id) if translation is None: return make_404(details="No translation available") alignments = AlignmentTranslation.query.filter( AlignmentTranslation.transcription_id == transcription.id, AlignmentTranslation.translation_id == translation.id).all() ptrs = [(a.ptr_transcription_start, a.ptr_transcription_end, a.ptr_translation_start, a.ptr_translation_end) for a in alignments] return make_200(data=ptrs)
def api_documents_transcriptions_alignments_discours(api_version, doc_id, user_id=None): """ If user_id is None: get the reference translation (if any) to find the alignment :param api_version: :param doc_id: :param user_id: :return: """ transcription = get_reference_transcription(doc_id) if transcription is None: return make_404() if user_id is None: user_id = transcription.user_id else: forbid = forbid_if_nor_teacher_nor_admin_and_wants_user_data( current_app, user_id) if forbid: return forbid alignments = AlignmentDiscours.query.filter( AlignmentDiscours.transcription_id == transcription.id, AlignmentDiscours.user_id == user_id).all() if len(alignments) == 0: return make_404() return make_200(data=[al.serialize() for al in alignments])
def api_delete_documents_transcriptions_alignments_images( api_version, doc_id, anno_id): """ :param api_version: :param doc_id: :param user_id: :return: """ transcription = get_reference_transcription(doc_id) if transcription is None: return make_404() manifest_url = Image.query.filter( Image.doc_id == doc_id).first().manifest_url alignments = AlignmentImage.query.filter( AlignmentImage.transcription_id == transcription.id, AlignmentImage.user_id == transcription.user_id, AlignmentImage.manifest_url == manifest_url, AlignmentImage.zone_id == anno_id).all() if len(alignments) > 0: try: for al in alignments: db.session.delete(al) db.session.commit() except Exception as e: db.session.rollback() print(str(e)) return make_400(str(e)) return make_200()
def api_post_translation_alignments(api_version, doc_id, user_id): """ NB: Posting alignment has a 'TRUNCATE AND REPLACE' effect """ from app.api.transcriptions.routes import get_reference_transcription from app.api.translations.routes import get_translation forbid = forbid_if_nor_teacher_nor_admin_and_wants_user_data( current_app, user_id) if forbid: return forbid transcription = get_reference_transcription(doc_id) if transcription is None: return make_404(details="Transcription not found") translation = get_translation(doc_id=doc_id, user_id=user_id) if translation is None: return make_404(details="Translation not found") data = request.get_json() data = data.get("data", []) ptrs = [] try: # TRUNCATE for old_al in AlignmentTranslation.query.filter( AlignmentTranslation.transcription_id == transcription.id, AlignmentTranslation.translation_id == translation.id).all(): db.session.delete(old_al) # INSERT for (ptr_transcription_start, ptr_transcription_end, ptr_translation_start, ptr_translation_end) in data: new_al = AlignmentTranslation( transcription_id=transcription.id, translation_id=translation.id, ptr_transcription_start=ptr_transcription_start, ptr_transcription_end=ptr_transcription_end, ptr_translation_start=ptr_translation_start, ptr_translation_end=ptr_translation_end) db.session.add(new_al) ptrs.append( (new_al.ptr_transcription_start, new_al.ptr_transcription_end, new_al.ptr_translation_start, new_al.ptr_translation_end)) db.session.commit() except Exception as e: db.session.rollback() print(str(e)) return make_400(str(e)) return make_200(data=ptrs)
def api_all_commentary(api_version, doc_id): if not get_doc(doc_id).is_transcription_validated: return make_403() tr = get_reference_transcription(doc_id) if tr is None: return make_404() commentaries = Commentary.query.filter( Commentary.doc_id == doc_id, Commentary.user_id == tr.user_id, ).all() return make_200(data=[c.serialize() for c in commentaries])
def get_reference_commentaries(doc_id, type_id=None): """ :param type_id: :param doc_id: :return: """ from app.api.transcriptions.routes import get_reference_transcription tr_ref = get_reference_transcription(doc_id) if tr_ref is None: return [] if type_id is None: type_id = Commentary.type_id return Commentary.query.filter(doc_id == Commentary.doc_id, tr_ref.user_id == Commentary.user_id, type_id == Commentary.type_id).all()
def api_get_alignment_translation(api_version, doc_id): from app.api.transcriptions.routes import get_reference_transcription from app.api.translations.routes import get_reference_translation transcription = get_reference_transcription(doc_id) if transcription is None: return make_404(details="No transcription available") translation = get_reference_translation(doc_id) if translation is None: return make_404(details="No translation available") alignments = AlignmentTranslation.query.filter( AlignmentTranslation.transcription_id == transcription.id, AlignmentTranslation.translation_id == translation.id).all() ptrs = [(a.ptr_transcription_start, a.ptr_transcription_end, a.ptr_translation_start, a.ptr_translation_end) for a in alignments] return make_200(data=ptrs)
def view_document_translation_alignment(api_version, doc_id): translation = get_reference_translation(doc_id) transcription = get_reference_transcription(doc_id) if not transcription or not translation: return make_404() tr_w_notes, tl_w_notes, notes, num_al = add_notes_refs( transcription.serialize_for_user(transcription.user_id), translation.serialize_for_user(translation.user_id)) if num_al <= 0: return make_404(details="Aucun alignement") return make_200({ "doc_id": doc_id, #"transcription": Markup("".join(tr_w_notes)), #"translation": Markup("".join(tl_w_notes)), "alignments": [z for z in zip(tr_w_notes, tl_w_notes)], "notes": notes })
def view_document_speech_parts_alignment(api_version, doc_id, user_id=None): tr = get_reference_transcription(doc_id) if tr is None: return make_404() if user_id is None: user_id = tr.user_id alignments = AlignmentDiscours.query.filter( AlignmentDiscours.transcription_id == tr.id, AlignmentDiscours.user_id == user_id).all() if len(alignments) <= 0: return make_404(details="Aucun alignement") _content = add_speechparts_refs_to_text(tr.content, alignments) return make_200({ "content": Markup(_content) if tr.content is not None else "", "notes": {"{:010d}".format(al.id): al.note for al in alignments} })
def api_post_documents_transcriptions_alignments_discours( api_version, doc_id, user_id): """ { "data": [ { "speech_part_type_id" : 1, "ptr_start": 1, "ptr_end": 20, "note": "aaa" }, { "speech_part_type_id" : 2, "ptr_start": 21, "ptr_end": 450, "note": "bb" } ] } :param user_id: :param api_version: :param doc_id: :return: """ """ NB: Posting alignment has a 'TRUNCATE AND REPLACE' effect """ forbid = forbid_if_nor_teacher_nor_admin_and_wants_user_data( current_app, user_id) if forbid: return forbid transcription = get_reference_transcription(doc_id) if transcription is None: return make_404(details="Transcription not found") data = request.get_json() data = data.get("data", []) ptrs = [] try: # TRUNCATE for old_al in AlignmentDiscours.query.filter( AlignmentDiscours.transcription_id == transcription.id, AlignmentDiscours.user_id == user_id).all(): db.session.delete(old_al) # INSERT for d in data: new_al = AlignmentDiscours( transcription_id=transcription.id, speech_part_type_id=d["speech_part_type_id"], ptr_start=d["ptr_start"], ptr_end=d["ptr_end"], user_id=user_id, note=d.get('note', None)) db.session.add(new_al) ptrs.append({ 'id': new_al.id, 'ptr_start': new_al.ptr_start, 'ptr_end': new_al.ptr_end, 'note': new_al.note, 'speech_part_type_id': new_al.speech_part_type_id }) db.session.commit() except Exception as e: db.session.rollback() print(str(e)) return make_400(str(e)) return make_200(data=ptrs)
def api_documents_put_annotation(api_version, doc_id, zone_id): """ expected format: { "manifest_url": "https://../manifest20.json", "canvas_idx": 0, // In case there are multiple images on a canvas, optionnal, default is 0 "img_idx": 0, "zone_type_id": 2, "fragment': "620,128,788,159", "svg': "620,128,788,159, ...", // in case of a COMMENTING motivation, the text content is embedded within the annotation, // optionnal, default is none "note": "Ceci est une majuscule" // in case of a DESCRIBING motivation, the text content is a segment of the transcription "ptr_start" : 3 "ptr_end" : 131 } :param api_version: :param doc_id: :return: """ data = request.get_json() try: doc = Document.query.filter(Document.id == doc_id).first() url = data['manifest_url'] canvas_idx = data['canvas_idx'] doc_id = doc.id img_idx = data.get('img_idx', 0) note = data.get('note', None) ptr_start = data.get('ptr_start', None) ptr_end = data.get('ptr_end', None) if note is not None and (ptr_start is not None or ptr_end is not None): raise Exception('ambiguous annotation type') img_zone = ImageZone.query.with_for_update(nowait=True).filter( ImageZone.zone_id == zone_id, ImageZone.manifest_url == url, ImageZone.canvas_idx == canvas_idx, ImageZone.img_idx == img_idx, ImageZone.user_id == doc.user_id ).one() img_zone.zone_id = int(zone_id) img_zone.note = note img_zone.zone_type_id = data.get('zone_type_id', 2) img_zone.fragment = data.get('fragment', None) img_zone.svg = data.get('svg', None) db.session.flush() #db.session.add(img_zone) tr = get_reference_transcription(doc_id) if tr is None: raise Exception('There is no reference transcription to use in this annotation') print('finding existing al') al = AlignmentImage.query.filter( AlignmentImage.transcription_id == tr.id, AlignmentImage.manifest_url == url, AlignmentImage.canvas_idx == canvas_idx, AlignmentImage.img_idx == img_idx, AlignmentImage.zone_id == zone_id ).first() if ptr_start is not None and ptr_end is not None: if al is None: print('new al') note = None new_al = AlignmentImage( transcription_id=tr.id, user_id=doc.user_id, zone_id=zone_id, manifest_url=url, canvas_idx=canvas_idx, img_idx=img_idx, ptr_transcription_start=ptr_start, ptr_transcription_end=ptr_end ) db.session.add(new_al) else: print('update al') al.ptr_transcription_start = ptr_start al.ptr_transcription_end = ptr_end db.session.add(al) else: # let's check if there is an old al to delete if al is not None: print("delete old al") db.session.delete(al) #db.session.add(img_zone) db.session.commit() except Exception as e: print(data, str(e)) db.session.rollback() return make_400(details="Cannot update this annotation: %s" % str(e)) return make_200(data=img_zone.serialize())
def api_documents_post_annotation(api_version, doc_id): """ expected format: { "manifest_url": "https://../manifest20.json", "canvas_idx": 0, // In case there are multiple images on a canvas, optionnal, default is 0 "img_idx": 0, "zone_type_id": 2, "fragment": "620,128,788,159", // FragmentSelector "svg": "<svg ...>" // SvgSelector // in case of a COMMENTING motivation, the text content is embedded within the annotation, // optionnal, default is none "note": "Ceci est une majuscule" // in case of a DESCRIBING motivation, the text content is a segment of the transcription "ptr_start" : 3 "ptr_end" : 131 } :param api_version: :param doc_id: :return: """ data = request.get_json() try: doc = Document.query.filter(Document.id == doc_id).first() url = data['manifest_url'] canvas_idx = data['canvas_idx'] doc_id = doc.id img_idx = data.get('img_idx', 0) note = data.get('note', None) ptr_start = data.get('ptr_start', None) ptr_end = data.get('ptr_end', None) if note is not None and (ptr_start is not None or ptr_end is not None): raise Exception('ambiguous annotation type') # test if the image is in db first if Image.query.filter( Image.manifest_url == url, Image.canvas_idx == canvas_idx, Image.doc_id == doc_id, Image.img_idx == img_idx ).first() is None: raise Exception('image unknown: %s', [url, canvas_idx, doc_id, img_idx]) # compute relative zone id last_zone = ImageZone.query.filter( ImageZone.manifest_url == url, ImageZone.canvas_idx == canvas_idx, ImageZone.img_idx == img_idx ).order_by(ImageZone.zone_id.desc()).first() if last_zone is None: new_zone_id = 1 else: new_zone_id = int(last_zone.zone_id) + 1 new_anno = ImageZone( zone_id=new_zone_id, manifest_url=url, canvas_idx=canvas_idx, img_idx=img_idx, user_id=doc.user_id, zone_type_id=data['zone_type_id'], svg=data.get('svg', None), fragment=data.get('fragment', None), note=note ) db.session.add(new_anno) if ptr_start is not None and ptr_end is not None: tr = get_reference_transcription(doc_id) if tr is None: raise Exception('There is no reference transcription use in this annotation') al = AlignmentImage.query.filter( AlignmentImage.transcription_id == tr.id, AlignmentImage.manifest_url == url, AlignmentImage.canvas_idx == canvas_idx, AlignmentImage.img_idx == img_idx, AlignmentImage.ptr_transcription_start == ptr_start, AlignmentImage.ptr_transcription_end == ptr_end ).first() if al is not None: raise Exception('this alignment already exists: %s', [tr.id, ptr_start, ptr_end]) new_al = AlignmentImage( transcription_id=tr.id, user_id=doc.user_id, zone_id=new_zone_id, manifest_url=url, canvas_idx=canvas_idx, img_idx=img_idx, ptr_transcription_start=ptr_start, ptr_transcription_end=ptr_end ) db.session.add(new_al) db.session.commit() except Exception as e: print(data, str(e)) db.session.rollback() return make_400(details="Cannot build this new annotation: %s" % str(e)) return make_201(data=new_anno.serialize())
def api_documents_annotations(api_version, doc_id, zone_id): """ :param canvas_name: :param api_version: :param doc_id: :param zone_id: :return: """ from app.api.transcriptions.routes import get_reference_transcription tr = get_reference_transcription(doc_id) if tr is None: return make_404() try: manifest = make_manifest(api_version, doc_id) sequence = manifest["sequences"][0] img = Image.query.filter(Image.doc_id == doc_id).first() # select annotations zones img_zone = ImageZone.query.filter( ImageZone.zone_id == zone_id, ImageZone.manifest_url == img.manifest_url ).one() res_uri = current_app.with_url_prefix(request.path) # if the note content is empty, then you need to fetch a transcription segment img_al = None if img_zone.note is None: try: img_al = AlignmentImage.query.filter( AlignmentImage.transcription_id == tr.id, AlignmentImage.zone_id == img_zone.zone_id, AlignmentImage.user_id == tr.user_id, AlignmentImage.manifest_url == img.manifest_url ).one() note_content = tr.content[img_al.ptr_transcription_start:img_al.ptr_transcription_end] except NoResultFound: return make_404(details="This transcription zone has no text fragment attached to it".format(doc_id)) # else it is a mere image note else: note_content = img_zone.note # TODO: gerer erreur si pas d'image dans le canvas canvas = sequence["canvases"][img_zone.canvas_idx] img_json = canvas["images"][img_zone.img_idx] url = current_app.with_url_prefix(url_for("api_bp.api_documents_manifest", api_version=1.0, doc_id=doc_id)) new_annotation = make_annotation( url, canvas["@id"], img_json.get('fragment', None), img_json.get('svg', None), res_uri, note_content, format="text/html" ) return new_annotation except Exception as e: print(str(e)) return make_400(str(e))
def api_documents_annotations_list_by_canvas(api_version, doc_id, motivation, canvas_idx): """ """ user = current_app.get_current_user() doc = Document.query.filter(Document.id == doc_id).first() if user.is_anonymous and doc.is_published is False: annotation_list = make_annotation_list(request.base_url, []) return annotation_list try: manifest = make_manifest(api_version, doc_id) sequence = manifest["sequences"][0] canvas = sequence["canvases"][int(canvas_idx)] annotations = [] img = Image.query.filter(Image.doc_id == doc_id).first() img = Image.query.filter(Image.manifest_url == img.manifest_url, Image.doc_id == doc_id, Image.canvas_idx == canvas_idx).first() # TODO s'il y a plusieurs images dans un seul et même canvas ? #img_json = canvas["images"][0] kwargs = { "doc_id": doc_id, "api_version": api_version } manifest_url = current_app.with_url_prefix( url_for("api_bp.api_documents_manifest", api_version=1.0, doc_id=doc_id)) for img_zone in [zone for zone in img.zones]: kwargs["zone_id"] = img_zone.zone_id res_uri = current_app.with_url_prefix(url_for("api_bp.api_documents_annotations", **kwargs)) print(img_zone, img_zone.zone_type.label) if img_zone.zone_type.label == "describing": from app.api.transcriptions.routes import get_reference_transcription tr = get_reference_transcription(doc_id) if tr is None: annotation_list = make_annotation_list(request.base_url, []) return annotation_list img_al = AlignmentImage.query.filter( AlignmentImage.transcription_id == tr.id, AlignmentImage.manifest_url == img_zone.manifest_url, AlignmentImage.canvas_idx == img_zone.canvas_idx, AlignmentImage.img_idx == img_zone.img_idx, AlignmentImage.zone_id == img_zone.zone_id ).first() # is there a text segment bound to this image zone? if img_al is not None: text_content = tr.content[img_al.ptr_transcription_start:img_al.ptr_transcription_end] else: text_content = "" else: text_content = img_zone.note new_annotation = make_annotation( manifest_url, canvas["@id"], img_zone.fragment, img_zone.svg, res_uri, content=text_content, format="text/html" ) annotations.append(new_annotation) annotation_list = make_annotation_list(request.base_url, annotations) return annotation_list except Exception as e: print(str(e)) return make_400(str(e))