Instructions to use SamsungSAILMontreal/chart2table-qwen3.5-4b-v1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use SamsungSAILMontreal/chart2table-qwen3.5-4b-v1 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="SamsungSAILMontreal/chart2table-qwen3.5-4b-v1") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("SamsungSAILMontreal/chart2table-qwen3.5-4b-v1", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use SamsungSAILMontreal/chart2table-qwen3.5-4b-v1 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/SamsungSAILMontreal/chart2table-qwen3.5-4b-v1
- SGLang
How to use SamsungSAILMontreal/chart2table-qwen3.5-4b-v1 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use SamsungSAILMontreal/chart2table-qwen3.5-4b-v1 with Docker Model Runner:
docker model run hf.co/SamsungSAILMontreal/chart2table-qwen3.5-4b-v1
chart2table — Qwen3.5-4B Fine-tuned
A Qwen3.5-4B model fine-tuned for extracting structured numerical data from line and scatter charts into CSV tables.
Model Description
This model is fine-tuned from Qwen/Qwen3.5-4B using LoRA on our dataset of plots.
Given a chart image, the model outputs a CSV table containing:
- Chart title, x-axis label, and y-axis label as a comment header
- One column per data series, with x-values in the first column
Supported chart types: line charts, scatter charts, dot-line charts.
Intended Use
- Digitising scientific figures from papers and reports
- Automated data extraction from charts for downstream analysis
- Benchmarking and evaluation on chart-understanding tasks
Training Details
| Setting | Value |
|---|---|
| Base model | Qwen/Qwen3.5-4B |
| Fine-tuning method | LoRA (rank 16) via TRL SFTTrainer, PEFT 0.19.1 |
| Training data | mix of synthetic and other sources |
| Max sequence length | 4096 tokens |
| Hardware | 1× NVIDIA A100 80 GB |
Usage
import torch
from PIL import Image
from transformers import AutoProcessor, Qwen3_5ForConditionalGeneration
from qwen_vl_utils import process_vision_info
model_id = "SamsungSAILMontreal/chart2table-qwen3.5-4b-v1"
processor = AutoProcessor.from_pretrained(
model_id,
min_pixels=256 * 28 * 28,
max_pixels=1280 * 28 * 28,
)
model = Qwen3_5ForConditionalGeneration.from_pretrained(
model_id,
torch_dtype=torch.bfloat16,
device_map="auto",
)
model.eval()
# Load your chart image
image = Image.open("chart.png").convert("RGB")
prompt = """You are analysing a line or scatter chart.
Your task is to extract the chart title, x-axis label, y-axis label, and raw numerical data from the chart and output it as a CSV table wrapped in a markdown code block.
Instructions:
1. Identify all data series (lines / point sets) in the chart.
2. Extract chart metadata: title, x-axis label, and y-axis label.
3. Read the x-axis values carefully — they may be years, categories, or numeric labels.
4. For each series, extract approximately 5-50 data points, depending on the chart,
that faithfully represent the curve, including start and end points.
5. Column names:
- If a legend is visible, or each curve is directly labelled with text beside it, use those labels as the column names.
- If there is no legend or in-plot labels and there is a single series, use the y-axis label (or "unlabeled" if absent).
- If there is no legend or in-plot labels and there are multiple series, the individual names are not recoverable, so
order the columns from the topmost curve to the bottommost (by average vertical position) and name them
"unlabeled 1", "unlabeled 2", … (or, if a y-axis label is present, "<y-axis label> 1", "<y-axis label> 2", …).
6. Pay close attention to the scale and gridlines when reading y-axis values;
if numerical values on the y-axis are missing, assume the values are scaled between 0 and 1.
7. Use a single shared x column listing every x value across all series (sorted). If a series has no
data point at a given x, leave its cell empty (so a row may look like "x1,,y2").
8. If a series is drawn as data points (markers) with a smooth fitted or guide line through them,
report the DATA POINTS (markers), not the fitted line. Marker fill (solid vs hollow) is only a style;
ignore it. Ignore non-data overlays such as reference lines, trend/annotation arrows, region labels,
and shaded guides.
Output format — markdown CSV code block only, no other text:
```csv
# Title: <chart title>, X: <x-axis label>, Y: <y-axis label>
x-axis label,Series A name,Series B name,...
x1,y1_A,y1_B,...
x2,y2_A,y2_B,...
```
Example:
```csv
# Title: Company growth report, X: Year, Y: US$
Year,Revenue,Profit
2018,100.5,12.3
2019,115.2,18.7
2020,130.0,22.1
```
Example with series sampled at different x (note the empty cell):
```csv
# Title: Raman spectra, X: Wavenumber (cm⁻¹), Y: Intensity (a.u.)
Wavenumber (cm⁻¹),Sample 1,Sample 2
100,0.20,
150,0.80,0.30
200,,0.90
```
"""
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": prompt},
],
}
]
text = processor.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(
text=[text],
images=image_inputs,
videos=video_inputs,
padding=True,
return_tensors="pt",
).to(model.device)
with torch.inference_mode():
generated_ids = model.generate(
**inputs,
max_new_tokens=2048,
do_sample=False,
repetition_penalty=1.2,
)
output = processor.batch_decode(
[ids[len(inp):] for ids, inp in zip(generated_ids, inputs.input_ids)],
skip_special_tokens=True,
clean_up_tokenization_spaces=False,
)[0]
print(output)
Parsing the output
The model returns a fenced CSV block. A simple way to extract it:
import re, io
import pandas as pd
match = re.search(r"```(?:csv)?\s*(.*?)```", output, re.DOTALL)
if match:
csv_text = match.group(1).strip()
# Strip the optional comment header line
lines = [l for l in csv_text.splitlines() if not l.startswith("#")]
df = pd.read_csv(io.StringIO("\n".join(lines)))
print(df)
Extracting meta data (title, axis labels) and visualizing the plot can be done as following:
def extract_metadata_from_text(text: str):
"""Extract title/x/y metadata from comment headers in model/chart text."""
title = ""
x_label = ""
y_label = ""
for raw_line in str(text).splitlines():
line = raw_line.strip()
if not line.startswith("#"):
continue
body = line[1:].strip()
if not body:
continue
fields = list(re.finditer(r"(?:^|,\s*)(title|x|y)\s*:\s*", body, flags=re.IGNORECASE))
if not fields:
continue
for i, match in enumerate(fields):
key = match.group(1).lower()
start = match.end()
end = fields[i + 1].start() if i + 1 < len(fields) else len(body)
value = body[start:end].strip().strip(",").strip()
if not value:
continue
if key == "title" and not title:
title = value
elif key == "x" and not x_label:
x_label = value
elif key == "y" and not y_label:
y_label = value
if title and x_label and y_label:
break
return {"title": title, "xlabel": x_label, "ylabel": y_label}
meta_data = extract_metadata_from_text(output)
df.plot(x=df.columns[0], kind="line", marker='o', figsize=(4, 3), **meta_data)