HumanSignal / HumanSignal/label-studio

Offsets in HTML annotation

Open
#6,446 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
28.3k
Forks
3.7k
Avg merge
14h
Merged PRs (30d)
15

Description

**Describe the bug**
Hi!
I don't understand how I should use `HyperText` and `HyperTextLabels` for span annotation in `.html` files.
It seems only `text` field work correctly.

1. length of the `text` is not the same:
```
# for globalOffsets
text length: 83
globaloffsets: 27 - 106
globaloffset 'length': 79

# for endOffset and startOffset
text length: 83
globaloffset 'length': 79
globaloffsets: 27 - 106
```
But according to this, it should be the same?
https://github.com/HumanSignal/label-studio/blob/develop/web/libs/editor/src/tags/object/RichText/domManager.md#content-field

2. In my case, annotations can overlap. Therefore, without reliable offsets, it's impossible to merge them accurately.
3. According to #4843 , it's impossible to "map" `globalOffsets` to text.
4. There are several issues https://github.com/HumanSignal/label-studio/issues?q=globalOffsets
5. I also tried to use xpath `start` and `end` -- same result i can't get same text. Do you have any example how reuse it?

**To Reproduce**
```python
from __future__ import annotations

import json
import logging
import pathlib

def debug_annotation(json_path: str, debug_level: int = logging.DEBUG) -> None:
# create logger
logger = logging.getLogger(__name__)
logger.setLevel(debug_level)
logger.addHandler(logging.StreamHandler())

# read json file
with pathlib.Path(json_path).open("r", encoding="utf-8") as json_file:
json_data = json.load(json_file)

for task in json_data:
logger.info("Task ID: %d", task["id"])
for annotation in task["annotations"]:
logger.info("Annotation ID: %d", annotation["id"])
for item in annotation["result"]:
if len(item["value"]["text"]) != item["value"]["endOffset"] - item["value"]["startOffset"]:
logger.critical(
"LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: %d, Annotation ID: %d, Result ID: %s",
task["id"],
annotation["id"],
item["id"],
)
logger.critical("Text: %s", item["value"]["text"])
logger.critical("text length: %d", len(item["value"]["text"]))
logger.critical("offset 'length': %d", item["value"]["endOffset"] - item["value"]["startOffset"])
logger.critical("offsets: %d - %d", item["value"]["startOffset"], item["value"]["endOffset"])
logger.critical("")

if (
len(item["value"]["text"])
!= item["value"]["globalOffsets"]["end"] - item["value"]["globalOffsets"]["start"]
):
logger.critical(
"LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: %d, Annotation ID: %d, Result ID: %s",
task["id"],
annotation["id"],
item["id"],
)
logger.critical("Text: %s", item["value"]["text"])
logger.critical("text length: %d", len(item["value"]["text"]))
logger.critical(
"globaloffset 'length': %d",
item["value"]["globalOffsets"]["end"] - item["value"]["globalOffsets"]["start"],
)
logger.critical(
"globaloffsets: %d - %d",
item["value"]["globalOffsets"]["start"],
item["value"]["globalOffsets"]["end"],
)
logger.critical("")

logger.info("Result ID: %s", item["id"])
logger.info("Result text: %s", item["value"]["text"])
logger.info("startOffset: %d", item["value"]["startOffset"])
logger.info("endOffset: %d", item["value"]["endOffset"])
logger.info("globalOffsets (start): %d", item["value"]["globalOffsets"]["start"])
logger.info("globalOffsets (end): %d", item["value"]["globalOffsets"]["end"])
logger.info("")

return None

if __name__ == "__main__":
# see Additional context
path_to_json = "path/to/small.json"

debug_annotation(path_to_json, debug_level=logging.CRITICAL)
```

OUTPUT
```
LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: 132570841, Annotation ID: 45365694, Result ID: oicRy3z29K
Text: こんにちは!\nこれはサンプルです。
text length: 18
offset 'length': 10
offsets: 0 - 10

LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: 132570841, Annotation ID: 45365694, Result ID: oicRy3z29K
Text: こんにちは!\nこれはサンプルです。
text length: 18
globaloffset 'length': 21
globaloffsets: 6 - 27

LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: 132570841, Annotation ID: 45365694, Result ID: 5Fsy0sv91L
Text: こんにちは!\nこれはサンプルです。
text length: 18
offset 'length': 10
offsets: 0 - 10

LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: 132570841, Annotation ID: 45365694, Result ID: 5Fsy0sv91L
Text: こんにちは!\nこれはサンプルです。
text length: 18
globaloffset 'length': 21
globaloffsets: 6 - 27

LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: iokeAq7ZtS
Text: This is an example paragraph using the Fira Code font which supports ligatures.\n\n
text length: 83
offset 'length': 79
offsets: 0 - 79

LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: iokeAq7ZtS
Text: This is an example paragraph using the Fira Code font which supports ligatures.\n\n
text length: 83
globaloffset 'length': 79
globaloffsets: 27 - 106

LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: XRLK45yuq-
Text: Ligature Example\nThis is an example paragraph using the Fira Code font which supports ligatures.\n\nCommon ligatures include symbols like: ==, !=, ===, <=,>=, -->, and others.
text length: 176
offset 'length': 75
offsets: 0 - 75

LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: XRLK45yuq-
Text: Ligature Example\nThis is an example paragraph using the Fira Code font which supports ligatures.\n\nCommon ligatures include symbols like: ==, !=, ===, <=,>=, -->, and others.
text length: 176
globaloffset 'length': 181
globaloffsets: 5 - 186

LEN OF TEXT NOT MATCHED (`end-start` offsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: RBB6g6SYJw
Text: Ligature Example\nThis is an example paragraph using the Fira Code font which supports ligatures.\n\nCommon ligatures include symbols like: ==, !=, ===, <=,>=, -->, and others.
text length: 176
offset 'length': 75
offsets: 0 - 75

LEN OF TEXT NOT MATCHED (`end-start` globalOffsets): Task ID: 132570842, Annotation ID: 45365741, Result ID: RBB6g6SYJw
Text: Ligature Example\nThis is an example paragraph using the Fira Code font which supports ligatures.\n\nCommon ligatures include symbols like: ==, !=, ===, <=,>=, -->, and others.
text length: 176
globaloffset 'length': 181
globaloffsets: 5 - 186
```

**Expected behavior**
I expect the offsets and global offsets to match the length of the extracted text.

**Screenshots**
None

**Environment (please complete the following information):**
- OS: macOS Sonoma
- Label Studio Version: Academic License

**Additional context**
**_HTML Files were uploaded via GUI in Google Chrome._**
1) Output json file [small.json](https://github.com/user-attachments/files/17167608/small.json)
2) UI config:
```xml











```
3) HTML samples

№1
```html


サンプル

こんにちは!


これはサンプルです。

```
№2
```html



HTML Page with Ligatures

/* Using Google Fonts to import a ligature-friendly font */
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400&display=swap');

body {
font-family: 'Fira Code', monospace;
margin: 20px;
}

p {
font-size: 16px;
}

Ligature Example

This is an example paragraph using the Fira Code font which supports ligatures.


Common ligatures include symbols like: ==, !=, ===, <=,>=, -->, and others.

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.