Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/write pecha annotation #2

Merged
merged 34 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
517b741
modify/set file path type as Path
tenzin3 Jul 1, 2024
3836ad4
set project_name as "openpecha"
tenzin3 Jul 1, 2024
54df02a
create/class Pecha
tenzin3 Jul 1, 2024
1adcc16
modify/move alignment pecha codes to its __init__.py
tenzin3 Jul 1, 2024
fce29b2
create/class Annotation
tenzin3 Jul 1, 2024
ac29227
rename function/to set_annotations
tenzin3 Jul 2, 2024
8169348
Pecha function write annotations
tenzin3 Jul 2, 2024
5ea7922
Pecha function create pecha folder
tenzin3 Jul 2, 2024
cac48fc
fix/set annotation data id as unique
tenzin3 Jul 2, 2024
0cd444a
write metadata as json
tenzin3 Jul 2, 2024
4157fef
include base path to func write annotations
tenzin3 Jul 2, 2024
6420cd2
include base path to PlainText parse
tenzin3 Jul 2, 2024
b6b4c7a
refactor code
tenzin3 Jul 2, 2024
053717c
fix/check if attribute exits
tenzin3 Jul 2, 2024
c3251b2
test for plaintext
tenzin3 Jul 2, 2024
6f773af
refactor test plaintext
tenzin3 Jul 2, 2024
216b4fb
fix/check if annotations exits before setting
tenzin3 Jul 2, 2024
761ac78
test for Pecha
tenzin3 Jul 2, 2024
047b3ac
test for ids
tenzin3 Jul 2, 2024
4bd3982
modify/convert relative path in json string
tenzin3 Jul 2, 2024
b46fdd0
test case for Pecha write annotations
tenzin3 Jul 2, 2024
8872295
refactor/Pecha create pecha folder
tenzin3 Jul 2, 2024
1c31178
refactor
tenzin3 Jul 2, 2024
edf86ca
delete/ unneccessary line
tenzin3 Jul 2, 2024
ba87b8f
set ANNOTATION_STORE_ID and ANNOTATION_DATASET_ID in config
tenzin3 Jul 3, 2024
a886053
set base file name as uuid
tenzin3 Jul 3, 2024
a939d5d
rename PlainText to PlainTextLineAlignedParser
tenzin3 Jul 3, 2024
7741ab3
Layer abstraction class
tenzin3 Jul 3, 2024
6fd99d1
join class Annotation in Layer field
tenzin3 Jul 3, 2024
2ea5a28
modify/Pecha fields
tenzin3 Jul 3, 2024
3b5f6a2
modify/PlainTextLineAlignedParser parser func
tenzin3 Jul 3, 2024
e9ccaef
fix/set basefile name and layer file name same
tenzin3 Jul 4, 2024
c6b4fa0
Layer write layer
tenzin3 Jul 4, 2024
12a379f
Pecha write function
tenzin3 Jul 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pechas/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ repos:
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml

- repo: https://github.com/asottile/pyupgrade
Expand Down
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "project_name"
name = "openpecha"
version = "0.0.1"
authors = [
{ name="OpenPecha", email="dev@openpecha.org" },
]
description = "A small example package"
description = "OpenPecha toolkit version 2"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
Expand All @@ -17,6 +17,12 @@ classifiers = [
"Operating System :: OS Independent",
]

dependencies = [
"pydantic >= 2.7.4",
"stam == 0.8.2",

]

[project.optional-dependencies]
dev = [
"pytest",
Expand Down
36 changes: 36 additions & 0 deletions src/openpecha/alignment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import List, Tuple


class AlignmentMetadata:
pass


class Alignment:
def __init__(
self,
metadata: AlignmentMetadata,
parser_segment_pairs=None,
alignment_segment_pairs=None,
):
self.metadata = metadata
self.parser_segment_pairs = parser_segment_pairs
self.alignment_segment_pairs = alignment_segment_pairs

@classmethod
def from_path(cls, path: str):
pass

@classmethod
def from_id(cls, alignment_id: str):
pass

@classmethod
def from_segment_pairs(
cls,
segment_pairs: List[Tuple[Tuple[str, str], Tuple[str, str]]],
metadata: AlignmentMetadata,
):
return cls(metadata=metadata, parser_segment_pairs=segment_pairs)

def save(self, path: str):
pass
36 changes: 0 additions & 36 deletions src/openpecha/alignment/alignment.py

This file was deleted.

79 changes: 68 additions & 11 deletions src/openpecha/alignment/parsers/plaintext.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,77 @@
class PlainText:
def __init__(self, source_text: str, target_text: str):
from pathlib import Path
from typing import Dict

from openpecha.ids import get_initial_pecha_id, get_uuid
from openpecha.pecha import Pecha
from openpecha.pecha.annotation import Annotation
from openpecha.pecha.layer import Layer, LayerEnum


class PlainTextLineAlignedParser:
def __init__(self, source_text: str, target_text: str, metadata: dict):
self.source_text = source_text
self.traget_text = target_text
self.target_text = target_text
self.metadata = metadata

@classmethod
def from_files(cls, source_path: str, target_path: str):
source_text = open(source_path).read()
target_text = open(target_path).read()
return cls(source_text, target_text)
def from_files(cls, source_path: Path, target_path: Path, metadata: dict):
source_text = source_path.read_text(encoding="utf-8")
target_text = target_path.read_text(encoding="utf-8")
return cls(source_text, target_text, metadata)

def create_pecha_layer(self, base_text: str, annotation: LayerEnum):
""" """
layer_annotations: Dict[str, Annotation] = {}
char_count = 0
for segment in base_text.split("\n"):
layer_annotations[get_uuid()] = Annotation(
id_=get_uuid(),
segment=segment,
start=char_count,
end=char_count + len(segment),
)
char_count += len(segment)

return Layer(annotation_label=annotation, annotations=layer_annotations)

def parse(self, metadata: dict):
# source_segments = self.source_text.split("\n")
# target_segments = self.target_text.split("\n")
def parse(self):
source_pecha_id, target_pecha_id = (
get_initial_pecha_id(),
get_initial_pecha_id(),
)

source_base_fname, target_base_fname = get_uuid(), get_uuid()
source_base_files = {source_base_fname: self.source_text}
target_base_files = {target_base_fname: self.target_text}

source_annotation = LayerEnum(self.metadata["source"]["annotation_label"])
target_annotation = LayerEnum(self.metadata["target"]["annotation_label"])

source_layers = {
source_base_fname: {
source_annotation: self.create_pecha_layer(
self.source_text, source_annotation
)
}
}
target_layers = {
target_base_fname: {
target_annotation: self.create_pecha_layer(
self.target_text, target_annotation
),
}
}

source_pecha = Pecha( # noqa
source_pecha_id, source_base_files, source_layers, self.metadata["source"]
)
target_pecha = Pecha( # noqa
target_pecha_id, target_base_files, target_layers, self.metadata["target"]
)
return source_pecha, target_pecha

# TODO:
# 1. Create pecha with segment layers for source and target text

# 2. create a segment pairs [((source_pecha_id,source_segment_id), (target_pecha_id, target_segment_id)), ...]
# 3. Create AlignmentMetadata

Expand Down
2 changes: 0 additions & 2 deletions src/openpecha/alignment/pecha/pecha.py

This file was deleted.

16 changes: 16 additions & 0 deletions src/openpecha/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pathlib import Path
from shutil import rmtree


def _mkdir(path):
if path.exists():
rmtree(path)
path.mkdir(exist_ok=True, parents=True)
return path


BASE_PATH = _mkdir(Path.home() / ".pechadata")
PECHAS_PATH = _mkdir(BASE_PATH / "pechas")

PECHA_ANNOTATION_STORE_ID = "PechaAnnotationStore"
PECHA_DATASET_ID = "PechaDataSet"
64 changes: 64 additions & 0 deletions src/openpecha/pecha/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import json
from pathlib import Path
from shutil import rmtree
from typing import Dict

from stam import AnnotationStore, Offset, Selector

from openpecha.config import (
PECHA_ANNOTATION_STORE_ID,
PECHA_DATASET_ID,
PECHAS_PATH,
_mkdir,
)
from openpecha.ids import get_uuid
from openpecha.pecha.annotation import Annotation
from openpecha.pecha.layer import Layer, LayerEnum


class Pecha:
def __init__(
self,
pecha_id: str,
bases: Dict[str, str],
layers: Dict[str, Dict[LayerEnum, Layer]],
metadata: Dict[str, str],
) -> None:
self.pecha_id = pecha_id
self.bases = bases
self.layers = layers
self.metadata = metadata

@classmethod
def from_path(cls, path: str):
pass

@classmethod
def from_id(cls, pecha_id: str):
pass

def write(self, export_path: Path = PECHAS_PATH):

pecha_dir = _mkdir(export_path / self.pecha_id)
self.base_path = _mkdir(pecha_dir / f"{self.pecha_id}.opf")
""" write metadata """
self.metadata_fn = self.base_path / "metadata.json"
self.metadata_fn.write_text(
json.dumps(self.metadata, indent=4, ensure_ascii=False), encoding="utf-8"
)

""" write base file"""
base_dir = _mkdir(self.base_path / "base")
for base_fname, base_text in self.bases.items():
base_fn = base_dir / f"{base_fname}.txt"
base_fn.write_text(base_text, encoding="utf-8")

layer_dir = _mkdir(self.base_path / "layers")
""" write annotation layers"""
for layer_fname, layer_data in self.layers.items():
for _, layer in layer_data.items():
_mkdir(layer_dir / layer_fname)
layer.write(
base_file_path=base_dir / layer_fname,
export_path=layer_dir / layer_fname,
)
15 changes: 15 additions & 0 deletions src/openpecha/pecha/annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pydantic import BaseModel, Field, ValidationInfo, field_validator


class Annotation(BaseModel):
segment: str
start: int = Field(ge=0)
end: int = Field(ge=0)
metadata: dict = Field(default_factory=dict)

@field_validator("end")
@classmethod
def end_must_not_be_less_than_start(cls, v: int, values: ValidationInfo) -> int:
if "start" in values.data and v < values.data["start"]:
raise ValueError("Span end must not be less than start")
return v
76 changes: 76 additions & 0 deletions src/openpecha/pecha/layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import json
from enum import Enum
from pathlib import Path
from typing import Dict

from stam import AnnotationStore, Offset, Selector

from openpecha.config import PECHA_ANNOTATION_STORE_ID, PECHA_DATASET_ID
from openpecha.ids import get_uuid
from openpecha.pecha.annotation import Annotation


class LayerEnum(Enum):
segment = "Segment"
commentaries = "Commentaries"


def get_annotation_category():
# TODO
# Return annotation category based on the annotation label
return "Structure Type"


class Layer:
def __init__(self, annotation_label: LayerEnum, annotations: Dict[str, Annotation]):
self.annotation_label = annotation_label
self.annotations = annotations

def covert_to_relative_path(self, json_string: str, export_path: Path):
"""convert the absolute path to relative path for base file path in json string"""
json_object = json.loads(json_string)
for resource in json_object["resources"]:
original_path = Path(resource["@include"])
resource["@include"] = str(original_path.relative_to(export_path))
return json_object

def write(self, base_file_path: Path, export_path: Path):
"""write annotations in stam data model"""
self.annotation_store = AnnotationStore(id=PECHA_ANNOTATION_STORE_ID)
self.resource = self.annotation_store.add_resource(
id=base_file_path.name, filename=base_file_path.as_posix()
)
self.dataset = self.annotation_store.add_dataset(id=PECHA_DATASET_ID)

annotation_category = get_annotation_category()
self.dataset.add_key(annotation_category)

unique_annotation_data_id = get_uuid()
for annotation_id, annotation in self.annotations.items():
target = Selector.textselector(
self.resource,
Offset.simple(annotation.start, annotation.end),
)
data = [
{
"id": unique_annotation_data_id,
"key": annotation_category,
"value": self.annotation_label.value,
"set": self.dataset.id(),
}
]
self.annotation_store.annotate(
id=annotation_id,
target=target,
data=data,
)
""" save annotations in json"""
json_string = self.annotation_store.to_json_string()
json_object = self.covert_to_relative_path(json_string, export_path)
""" add four uuid digits to the layer file name for uniqueness"""
layer_fname = f"{self.annotation_label.value}-{get_uuid()[:4]}.json"
with open(
export_path / layer_fname,
"w",
) as f:
f.write(json.dumps(json_object, indent=4, ensure_ascii=False))
File renamed without changes.
Empty file removed tests/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions tests/alignment/parsers/plaintext/data/comments.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{D3874}༄༅༅། །རྒྱ་གར་སྐད་དུ། བོ་དྷི་སཏྭ་ཙཱརྱ་ཨ་བ་ཏཱ་ར་སང་ཀཱ་ར།
བོད་སྐད་དུ། བྱང་ཆུབ་སེམས་དཔའི་སྤྱོད་པ་ལ་འཇུག་པའི་ལེགས་པར་སྦྱར་བ།
བཅོམ་ལྡན་འདས་གསུང་གི་མངའ་བདག་འཇམ་དཔལ་གཞོན་ནུར་གྱུར་པ་ལ་ཕྱག་འཚལ་ལོ། །ངོ་བོ་ཉིད་ནི་བྱང་ཆུབ་སེམས་པའི། །རྒྱ་མཚོ་དེ་ལ་ཕྱག་འཚལ་ཏེ། །བདག་འདྲའི་སྤྱོད་པའི་ཡན་ལག་ལ། །འཇུག་ཕྱིར་ལེགས་སྦྱར་བཤད་ཙམ་བྱ། །དམ་པ་རྣམས་ཀྱིས་ནི་ཐོག་མ་དང་བར་དང་ཐ་མར་དགེ་བ་མངོན་པར་འཕེལ་བར་བྱ་བ་ཡིན་པས། བདེ་གཤེགས་ཞེས་བྱ་བ་ལ་སོགས་པ་སྨོས་པ་ཡིན་ཏེ། འདིར་ཕྱག་འཚལ་བ་ནི་དང་པོར་དགེ་བའོ། །ཆོས་བསྟན་པ་ནི་བར་དུ་དགེ་བའོ། །དགེ་བའི་རྩ་བ་ཡོངས་སུ་བསྔོ་བ་ནི་དགེ་བའི་རྩ་བ་མངོན་པར་འཕེལ་བ་ཡིན་པས་ཐ་མར་དགེ་བ་ཡིན་ནོ། །དེ་ལ་བདེ་བར་གཤེགས་པ་ནི་རྟོགས་པར་བྱ་བའི་ལྷག་མ་མི་མངའ་བས་ན་ཡོངས་སུ་རྫོགས་པར་ཐུགས་སུ་ཆུད་པའི་ཕྱིར་བདེ་བར་གཤེགས་པའོ། །ཆོས་ཀྱི་སྐུ་མངའ་བ་ནི་ལུང་དང་རྟོགས་པའི་བདག་ཉིད་ཅན་གྱི་དམ་པའི་ཆོས་ཀྱི་ཚོགས་ནི་ཆོས་ཀྱི་སྐུ་སྟེ་དེ་དང་བཅས་པའོ། །སྲས་བཅས་ནི་ཉིད་ལས་འཁྲུངས་པའི་སྲས་ཏེ། བྱང་ཆུབ་སེམས་དཔའ་དང་བཅས་པའོ། །ལ་ལ་ལས་ནི་བདེ་གཤེགས་དམ་པའི་ཆོས་དང་དགེ་འདུན་བཅས་ཞེས་ཟེར་རོ། །བཙུན་པ་ནི་ཉན་ཐོས་ཆེན་པོ་བརྒྱད་ལ་སོགས་པ་ལ་བྱ་སྟེ། དེ་དག་མ་ལུས་པ་ཀུན་ལ་ཕྱག་འཚལ་བའོ། །དཀོན་མཆོག་གསུམ་པོ་གཙོ་བོར་གྱུར་པས་སོ་སོར་སྨོས་པ་ཡིན་ལ། དེ་དག་ཀྱང་ཕྱག་བྱ་བར་འོས་པ་ཡིན་པས་གུས་པས་ཕྱག་འཚལ་ཏེ། ཞེས་བྱ་བ་སྨོས་ཏེ། འདིར་ཡོན་ཏན་དམ་པའི་བསྟོད་པ་རྒྱ་ཆེ་བ་དང་། མཆོད་པ་ཁྱད་པར་དུ་འཕགས་པའི་དམིགས་པ་ཡིད་ལ་བྱེད་པ་ལས་བྱུང་བའི་མོས་པའི་བསམ་པ་ཤིན་ཏུ་ཕུལ་དུ་བྱུང་བའི་དགའ་བ་རྒྱ་ཆེ་བའི་མཆོད་པ་དང་བཅས་པས་ལུས་ཞིང་ཐམས་ཅད་ཀྱི་རྡུལ་སྙེད་ཀྱིས་བཏུད་ཅིང་ཕྱག་འཚལ་ལོ། །དེ་ལྟར་ཕྱག་བཙལ་ནས་ཅི་ཞིག་བྱེད་ཅེ་ན། བདེ་གཤེགས་སྲས་ཀྱི་ཞེས་བྱ་བ་ལ་སོགས་པ་སྨོས་ཏེ། བདེ་བར་གཤེགས་པའི་བདག་ཉིད་ནི་ཆོས་ཀྱི་སྐུ་སྟེ། དེའི་དབང་དུ་བྱས་པ་ལས་སྐྱེས་པ་ས་ཆེན་པོ་ཐོབ་པ་དང་། རྒྱུ་ལ་གནས་པ་རྣམས་སོ། །དེ་རྣམས་ཀྱི་སྡོམ་པ་ནི་མི་དགེ་བ་སྤོང་བ་དང་། དགེ་བ་ལ་འཇུག་པ་དང་། སེམས་ཅན་གྱི་དོན་བྱ་བའོ། །དེ་ཡང་བཅོམ་ལྡན་འདས་ཀྱིས་ཤིན་ཏུ་ཟབ་ཅིང་རྒྱ་ཆེ་བའི་བདག་ཉིད་ཅན་དུ་གསུངས་ལ། དེར་བྱང་ཆུབ་ཏུ་སེམས་བསྐྱེད་པའི་ཕན་ཡོན་ལ་སོགས་པའི་དོན་རྣམ་པ་བཅུ་པོ་གང་ཡིན་པ་དེས་འཇུག་པའི་བདེ་བར་གཤེགས་པའི་སྲས་ཀྱི་སྡོམ་པ་ལ་འཇུག་པ་བསྟན་པར་བྱའོ། །དེ་ཡང་ལུང་བཞིན་ཞེས་བྱ་བ་སྟེ། ལུང་གི་དོན་དང་མི་འགལ་བར་རོ། །ལུང་ལས་ནི་བཅོམ་ལྡན་འདས་ཀྱིས་རྒྱ་ཆེར་གསུངས་སོ་ཞེ་ན། མདོར་བསྡུས་ནས་ནི་ཞེས་བྱ་བ་སྨོས་སོ། །དེ་ལྟ་ཡིན་དུ་ཆུག་ན། ཅི་འདིར་སྔོན་ཆད་མ་བྱུང་བ་གཞན་འགའ་ཞིག་སྨས་སམ།

ལུང་ཇི་ལྟ་བ་ཡིན་ཞེ་ན། སྔོན་ཆད་ཅེས་བྱ་བ་ལ་སོགས་པ་སྨོས་སོ། །སྡེབ་སྦྱོར་མཁས་པས་སྔོན་མ་ཡིན་ནམ་ཞེ་ན། སྡེབ་སྦྱོར་ཞེས་བྱ་བ་ལ་སོགས་པ་སྨོས་སོ། །གང་གི་ཕྱིར་འདིར་སྡེབ་སྦྱོར་ལ་མཁས་པ་མེད་པ་ཉིད་ཀྱི་ཕྱིར་གཞན་གྱི་དོན་དུ་བདག་གིས་འདི་གཞུང་དུ་ཉེ་བར་སྦྱར་བ་མ་བྱས་སོ་ཞེས་བྱ་བར་དགོངས་སོ། །དེ་ལྟར་གལ་ཏེ་གཞན་གྱི་དོན་དུ་མ་བྱས་ན་ཅིའི་ཕྱིར་བྱེད་ཅེ་ན། དེའི་ཕྱིར་རང་གི་ཞེས་བྱ་བ་ལ་སོགས་པ་སྨོས་ཏེ། ཡིད་ལ་འདིར་བྱང་ཆུབ་ཀྱི་སེམས་ཏེ། དེ་བསྒོམ་པའི་ཕྱིར་ཞེས་བྱ་བ་ནི་བསླབ་པའི་ཕྱིར་རོ། །བཅོམ་ལྡན་འདས་ཀྱིས་ལུང་ལས་ཚིག་གི་དོན་རྒྱ་ཆེར་གསུངས་པ་དེ་ལས་མདོར་བསྡུས་ཏེ་རང་གི་ཡིད་ལ་བསྒོམ་པར་བྱ་བའི་ཕྱིར་བདག་གིས་འདི་བྱས་སོ་ཞེས་པའོ། །
Loading
Loading