Compare commits

...

10 Commits

Author SHA1 Message Date
c8fa5db7d3 Add pytest suite with 40 unit and integration tests
Coverage:
- test_model: SimpleNet forward (parametrized over batch sizes and both
  unsqueezed and flat input shapes), layer dimensions, differentiability,
  and ONNX round-trip
- test_inference: load_model resolution order (bundled, cwd override,
  explicit path, missing path), and predict shape/dtype/determinism plus
  endpoint sanity across 8 cardinal/diagonal targets
- test_train: _load_csv parsing, TrajectoryDataset indexing, full train()
  pipeline producing a single-file ONNX, plus a smoke test against the
  real data shipped under data/
- test_cli: --help for the three console scripts and a real run of
  mouse-visualize via both the entry point and python -m

Wire up pytest via dependency-groups and tool.pytest.ini_options.
2026-05-12 00:33:15 +08:00
de602365e9 Refactor into standard Python package with uv
- Move all code into src/mouse_control/ following the src-layout convention,
  with model/collect/train/visualize/inference as separate modules and
  SimpleNet extracted into model.py
- Add hatchling-based pyproject.toml with three CLI entry points
  (mouse-collect, mouse-train, mouse-visualize) and bundle the trained
  ONNX model as a package resource under assets/
- Move training data to data/, delete superseded show.py, remove dead
  imports (numpy, onnxruntime, onnx, onnxsim) and the unused
  visualize_path() helper
- Fix crashes in the collector: guard against destroyed-widget access
  after the n==100 destroy(), and skip save_to_csv when recording is
  off or the path is too short to derive 10 keypoints
- Switch ONNX export to external_data=False so the model ships as a
  single self-contained 19KB file
- Bytes-load the bundled model via importlib.resources so packaging
  remains correct under zip-safe distributions
- Rewrite README around the new layout, commands, and public API
2026-05-12 00:30:08 +08:00
suixin1424
2c482150d4 Update README.md 2025-01-21 14:17:27 +08:00
suixin1424
d62373e810 Update README.md 2025-01-19 18:49:23 +08:00
suixin1424
1c8dbe1ccd Update README.md 2025-01-19 18:28:14 +08:00
suixin1424
5043f8c674 Update README.md 2024-02-10 15:31:18 +08:00
suixin1424
7ddb7969cc Update README.md 2024-02-02 17:43:44 +08:00
suixin1424
a1a6c36502 fix bug 2024-02-02 17:43:09 +08:00
suixin1424
902b2a9386 Update README.md 2024-01-28 20:44:17 +08:00
suixin1424
e0bc413150 Update README.md 2024-01-28 20:43:44 +08:00
24 changed files with 2601 additions and 278 deletions

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# Byte-compiled / build artifacts
__pycache__/
*.py[cod]
*.egg-info/
build/
dist/
# Virtual envs / tooling
.venv/
.uv/
# Generated outputs (only at repo root; bundled assets must stay)
/trajectories.png
/mouse.onnx
/mouse.onnx.data
# IDE
.idea/
.vscode/
# pytest
.pytest_cache/

164
README.md
View File

@@ -1,26 +1,148 @@
# mouse_control # mouse_control
一种基于神经网络来模拟人手移动鼠标的方法
一种基于神经网络来模拟人手移动鼠标的方法。
## 简介 ## 简介
本项目源于该死的轨迹检测。经过初步验证,本项目是有效的。
本项目源于该死的轨迹检测。我们用一个简单的三层全连接神经网络去拟合真人鼠标移动轨迹。
<img src="./imgs/Figure_1.png"> <img src="./imgs/Figure_1.png">
上图为真人轨迹移动得到的散点图。这里我们用10个点来拟合轨迹。
可以看到,人手移动其实和pid以及其他的曲线是有一定区别的。 上图为真人轨迹移动得到的散点图——用 10 个关键点拟合一条轨迹。可以看到,人手移动和 PID、贝塞尔等曲线有明显区别因此用神经网络来拟合更合适。
因此,我们选用神经网络来拟合真人鼠标移动轨迹。
该神经网络其实很简单,只有三个全连接层。 **网络结构**
输入为目标距离当前位置的dxdy。
输出为10个点用来模拟真人的轨迹。 - 输入:`(dx, dy)` —— 目标点相对当前位置的偏移
## 收集鼠标轨迹 - 三层全连接:`Linear(2 → 64) → ReLU → Linear(64 → 32) → ReLU → Linear(32 → 20)`
首先运行collect_data.py。运行后我们会看到这样一个界面。 - 输出:`(10, 2)` —— 10 个轨迹点
仓库内含 `data/` 下的 400 条采集数据和 `src/mouse_control/assets/mouse.onnx` 预训练模型clone 之后可以直接 `mouse-visualize` 看效果。
## 安装
使用 [uv](https://docs.astral.sh/uv/) 管理:
```bash
uv sync
```
或从构建产物安装:
```bash
uv pip install dist/mouse_control-0.1.0-py3-none-any.whl
```
要求 Python ≥ 3.12。
## 项目结构
```
mouse_control/
├── pyproject.toml
├── README.md
├── data/ # 训练 / 测试数据 (380 + 20 条)
│ ├── mouse_data.csv
│ └── mouse_data_test.csv
├── imgs/ # README 图片
└── src/mouse_control/ # 包源码 (src layout)
├── __init__.py # 公共 API
├── model.py # SimpleNet 网络定义
├── collect.py # 数据采集 GUI
├── train.py # 训练 + ONNX 导出
├── visualize.py # 轨迹可视化
├── inference.py # 模型加载 / 推理辅助
└── assets/
└── mouse.onnx # 随包发布的预训练模型
```
## 命令行用法
安装后可以直接调用三个命令(也可以用 `uv run <cmd>`
### 1. 采集真人鼠标轨迹
```bash
uv run mouse-collect -o data/mouse_data.csv -n 100
```
<img src="./imgs/collect.png"> <img src="./imgs/collect.png">
我们需要点击红球,就会开始记录鼠标轨迹,点击蓝球,结束记录。这样我们就成功收集到一条鼠标轨迹的数据。
重复这样每收集100次程序会退出。我们一共要收集约300条数据。 全屏界面:点**红球**开始记录、移动到**蓝球**点击结束记录,每条轨迹保存 10 个关键点到 CSV。
这样,我们就收集好了数据。
## 划分数据集(可选) - 默认采集 100 条后自动退出,按 `Esc` 提前退出
我们将mouse_data.csv用vscode打开选择一些数据剪切到mouse_data_test.csv中。 - 推荐采集 ≥ 300 条以获得较好的训练效果
## 训练模型
运行train.py程序就会开始训练模型。最终我们能看到控制台打印出的一条test数据。是一个dxdy拟合出的十个点。 ### 2. 划分训练 / 测试集
## 验证
`mouse_data.csv` 里**剪切**若干行10-20 条)到 `mouse_data_test.csv` 即可。
### 3. 训练模型
```bash
uv run mouse-train \
--train-csv data/mouse_data.csv \
--test-csv data/mouse_data_test.csv \
--output mouse.onnx \
--epochs 1000
```
训练结束后在当前目录生成 `mouse.onnx`。其它参数 `--batch-size` `--lr` 可调。
### 4. 可视化效果
```bash
uv run mouse-visualize --n-trajectories 10 --seed 42
```
<img src="./imgs/Figure_2.png"> <img src="./imgs/Figure_2.png">
我们将刚刚得到的十个点放到show.py中观察散点图发现其轨迹类似于本人鼠标移动轨迹。
todo 随机生成若干目标点,每条轨迹包含 10 个模型输出点 + 80 个三次样条插值点。`--no-show` 跳过 GUI 窗口、只写 PNG`--model` 指定其它 ONNX 文件。
使用onnxruntime在c++上进行推理
## Python API
```python
import mouse_control as mc
# 加载模型:优先 ./mouse.onnx否则回退到包内打包的预训练模型
session = mc.load_model()
# 推理:输入 (dx, dy),输出 (10, 2) 轨迹点
pts = mc.predict(session, 120, -80)
# -> ndarray, shape (10, 2), dtype float32
# 直接使用模型类(用于自训练 / 微调)
from mouse_control import SimpleNet
```
## ONNX 推理(其它语言)
### Python (onnxruntime)
```python
import numpy as np
import onnxruntime as ort
session = ort.InferenceSession("mouse.onnx")
inp = np.array([[[100.0, 200.0]]], dtype=np.float32) # dx=100, dy=200
output = session.run(None, {"input": inp})[0] # shape (1, 10, 2)
```
### C++ (OpenCV DNN)
```cpp
cv::dnn::Net net = cv::dnn::readNetFromONNX("mouse.onnx");
cv::Mat blob(1, 2, CV_32F, cv::Scalar(100, 100)); // 输入 dx=100, dy=100
net.setInput(blob, "input");
cv::Mat output = net.forward("output"); // 输出 1*10*2
std::cout << output.at<float>(0, 8, 0) << std::endl;
```
## 打包发布
```bash
uv build
# -> dist/mouse_control-0.1.0-py3-none-any.whl
# -> dist/mouse_control-0.1.0.tar.gz
```
wheel 内含 `assets/mouse.onnx` 预训练模型,安装后直接 `mc.load_model()` 即可使用。

View File

@@ -1,130 +0,0 @@
import math
import random
import tkinter as tk
import matplotlib.pyplot as plt
import csv
from tkinter import Label
# 创建窗口
root = tk.Tk()
root.attributes('-fullscreen', True) # 全屏显示
label_n = Label(root, text="n: 0", font=("Helvetica", 16))
label_n.pack()
csv_file_path = "mouse_data.csv"
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# 设置小球的初始位置
ball1_pos = (screen_width/2, screen_height/2)
ball2_pos = (ball1_pos[0] + random.randint(-200, 200),ball1_pos[1] + random.randint(-200, 200))
# 设置小球的半径
ball_radius = 20
# 设置鼠标记录状态
recording = False
mouse_path = []
n=0
# 鼠标移动事件处理函数
def motion(event):
global recording, mouse_path, n
if recording:
mouse_path.append((event.x, event.y))
# 鼠标点击事件处理函数
def mouse_click(event):
global recording, mouse_path, ball2_pos, n
if event.x >= ball1_pos[0] - ball_radius and event.x <= ball1_pos[0] + ball_radius and event.y >= ball1_pos[1] - ball_radius and event.y <= ball1_pos[1] + ball_radius:
recording = True
mouse_path = [(event.x, event.y)]
elif event.x >= ball2_pos[0] - ball_radius and event.x <= ball2_pos[0] + ball_radius and event.y >= ball2_pos[1] - ball_radius and event.y <= ball2_pos[1] + ball_radius:
recording = False
canvas.delete("ball2")
#visualize_path(mouse_path) # 可视化鼠标轨迹
save_to_csv(mouse_path)
n = n+1
if n == 100:
root.destroy()
label_n.config(text=f"n: {n}")
mouse_path = []
# 重新生成第二个小球的位置
ball2_pos = (ball1_pos[0] + random.randint(-200, 200), ball1_pos[1] + random.randint(-200, 200))
# 绘制新的第二个小球
canvas.create_oval(ball2_pos[0]-ball_radius, ball2_pos[1]-ball_radius, ball2_pos[0]+ball_radius, ball2_pos[1]+ball_radius, fill="blue", tags="ball2")
# 键盘事件处理函数
def key(event):
if event.keysym == "Escape":
root.destroy()
def save_to_csv(path):
# 将路径坐标转换为相对于起点的坐标
x_rel = [px - path[0][0] for px, py in path]
y_rel = [-(py - path[0][1]) for px, py in path]
# 计算每个点相对于起点的距离用于z轴表示
distances = [math.sqrt((x_rel[0] - px)**2 + (y_rel[0] - py)**2) for px, py in zip(x_rel, y_rel)]
# 选择10个关键点
key_points_indices = [int(i) for i in range(0, len(path), max(1, len(path)//10))]
key_points_x = [x_rel[i] for i in key_points_indices]
key_points_y = [y_rel[i] for i in key_points_indices]
key_points_distances = [distances[i] for i in key_points_indices]
# 打开 CSV 文件进行写操作
with open(csv_file_path, mode='a', newline='') as csv_file:
csv_writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
# 写入一行数据
csv_writer.writerow([f"{key_points_x[-1]},{key_points_y[-1]}"] + [f"{key_points_x[i]},{key_points_y[i]}" for i in range(0,10)])
def visualize_path(path):
# 将路径坐标转换为相对于起点的坐标
x_rel = [px - path[0][0] for px, py in path]
y_rel = [-(py - path[0][1]) for px, py in path]
# 计算每个点相对于起点的距离用于z轴表示
distances = [math.sqrt((x_rel[0] - px)**2 + (y_rel[0] - py)**2) for px, py in zip(x_rel, y_rel)]
# 选择10个关键点
key_points_indices = [int(i) for i in range(0, len(path), max(1, len(path)//10))]
key_points_x = [x_rel[i] for i in key_points_indices]
key_points_y = [y_rel[i] for i in key_points_indices]
key_points_distances = [distances[i] for i in key_points_indices]
# 使用z轴信息通过颜色表示距离的远近
plt.scatter(key_points_x, key_points_y, c=key_points_distances, cmap='viridis', marker='o', s=50)
# 在关键点位置添加文本标签,显示终点到起点的距离
plt.text(key_points_x[-1], key_points_y[-1], f'Distance to Origin: {key_points_distances[-1]:.2f}', ha='right', va='bottom', bbox=dict(facecolor='white', alpha=0.5))
# 添加颜色条表示z轴信息
plt.colorbar(label='Distance to Endpoint')
plt.show()
# 绘制小球
canvas = tk.Canvas(root, width=root.winfo_screenwidth(), height=root.winfo_screenheight())
canvas.pack()
canvas.create_oval(ball1_pos[0]-ball_radius, ball1_pos[1]-ball_radius, ball1_pos[0]+ball_radius, ball1_pos[1]+ball_radius, fill="red")
canvas.create_oval(ball2_pos[0]-ball_radius, ball2_pos[1]-ball_radius, ball2_pos[0]+ball_radius, ball2_pos[1]+ball_radius, fill="blue", tags="ball2")
# 绑定鼠标事件
canvas.bind('<Motion>', motion)
canvas.bind('<Button-1>', mouse_click)
# 绑定键盘事件
root.bind('<Key>', key)
# 运行窗口
root.mainloop()

380
data/mouse_data.csv Normal file
View File

@@ -0,0 +1,380 @@
"189,-42","0,0","7,-1","58,-25","136,-49","188,-57","219,-60","216,-54","211,-51","205,-47","196,-43"
"117,102","0,0","12,7","36,24","72,45","96,60","108,71","110,77","112,83","114,89","116,96"
"-83,87","0,0","-4,2","-11,10","-26,28","-40,44","-56,60","-63,69","-70,75","-76,79","-80,83"
"-55,-84","0,0","-6,-4","-12,-15","-22,-32","-33,-46","-41,-57","-45,-64","-46,-69","-49,-73","-51,-78"
"-18,-150","0,0","-1,-5","-5,-19","-12,-49","-16,-97","-23,-117","-24,-129","-23,-138","-21,-146","-18,-154"
"-172,-121","0,0","-9,-10","-40,-34","-80,-61","-112,-82","-132,-93","-138,-98","-146,-108","-159,-119","-165,-120"
"69,-37","0,0","2,-2","6,-6","12,-11","19,-16","31,-22","46,-29","60,-32","64,-34","66,-36"
"-166,-62","0,0","-8,-4","-29,-16","-75,-42","-118,-61","-128,-63","-134,-62","-144,-61","-154,-61","-161,-62"
"99,-15","0,0","6,0","20,-3","57,-6","83,-7","113,-12","134,-14","130,-14","117,-14","105,-15"
"65,106","0,0","-4,-1","2,5","11,18","28,34","47,54","55,70","59,83","63,96","65,106"
"48,-83","0,0","4,-10","9,-21","12,-31","17,-44","24,-59","29,-70","37,-79","42,-85","46,-86"
"-146,38","0,0","-11,0","-39,3","-80,11","-125,18","-149,19","-159,21","-164,23","-168,26","-157,32"
"-149,-9","0,0","-13,-5","-40,-12","-76,-19","-104,-24","-128,-24","-143,-23","-144,-18","-145,-13","-149,-9"
"-9,5","0,0","6,-5","15,-8","20,-9","25,-9","28,-7","25,-5","18,-2","11,0","6,1"
"200,-109","0,0","2,-4","8,-11","30,-36","59,-60","85,-73","120,-87","152,-98","171,-104","184,-106"
"-15,184","0,0","2,19","2,50","-3,83","-17,112","-20,132","-16,151","-16,164","-17,170","-18,176"
"109,-132","0,0","-7,0","-2,-10","23,-38","63,-81","90,-109","107,-124","121,-136","126,-138","118,-136"
"-127,-91","0,0","-8,-19","-34,-44","-66,-58","-83,-64","-90,-68","-103,-74","-114,-77","-120,-82","-124,-86"
"-186,185","0,0","-10,13","-58,72","-106,118","-118,141","-133,158","-143,166","-152,171","-163,179","-181,190"
"38,92","0,0","4,3","11,13","21,25","31,37","38,51","39,62","39,70","38,75","38,79"
"52,154","0,0","1,7","15,37","35,80","57,108","64,121","64,129","65,137","64,145","59,152"
"149,-106","0,0","4,-6","18,-21","44,-43","68,-61","89,-73","110,-87","120,-94","125,-96","132,-100"
"-45,-81","0,0","-1,-16","-4,-45","-11,-60","-16,-73","-20,-82","-26,-87","-33,-89","-39,-91","-43,-88"
"-18,44","0,0","0,2","-1,7","-4,20","-6,24","-7,29","-9,33","-11,36","-12,38","-13,39"
"68,-144","0,0","11,-26","22,-70","41,-116","51,-137","83,-165","93,-172","86,-166","77,-159","73,-154"
"-124,-125","0,0","-8,-5","-26,-20","-54,-41","-79,-68","-100,-95","-115,-114","-124,-122","-128,-125","-126,-128"
"-109,62","0,0","-10,8","-22,21","-34,33","-56,48","-79,60","-86,63","-92,64","-98,64","-104,63"
"-171,-36","0,0","-13,-1","-51,-14","-100,-33","-127,-45","-139,-47","-145,-44","-154,-42","-163,-42","-167,-40"
"-129,-163","0,0","-5,-11","-22,-44","-47,-86","-79,-129","-87,-135","-93,-140","-100,-143","-106,-146","-117,-155"
"92,21","0,0","10,-1","37,-3","77,4","114,14","128,20","120,21","112,22","106,22","99,22"
"-182,-85","0,0","-13,-6","-38,-23","-72,-44","-106,-62","-139,-72","-153,-77","-158,-79","-165,-80","-171,-82"
"-17,-72","0,0","0,-8","0,-14","0,-22","-3,-32","-7,-42","-11,-51","-14,-58","-17,-64","-18,-67"
"-114,72","0,0","3,-3","4,-7","6,-9","1,-6","-11,5","-32,21","-56,39","-75,53","-96,64"
"64,-108","0,0","1,-4","2,-12","6,-24","11,-44","24,-68","40,-89","44,-91","47,-94","51,-96"
"-75,179","0,0","-3,12","-16,52","-39,98","-57,127","-65,143","-69,151","-71,158","-72,165","-74,172"
"-133,-26","0,0","-9,-7","-28,-15","-51,-23","-78,-26","-98,-27","-106,-26","-113,-26","-119,-26","-127,-26"
"143,126","0,0","3,3","25,20","59,46","102,75","140,95","142,102","142,108","142,115","143,120"
"-71,-90","0,0","-3,-9","-16,-28","-34,-54","-45,-73","-49,-77","-52,-82","-54,-86","-56,-90","-62,-90"
"2,-135","0,0","0,-5","-1,-15","-3,-30","-5,-50","-5,-71","-4,-93","-2,-112","-2,-125","-1,-131"
"-102,152","0,0","-5,17","-38,85","-54,104","-64,113","-73,124","-79,130","-84,137","-90,144","-96,149"
"174,-92","0,0","10,-8","25,-19","53,-37","88,-55","112,-65","130,-72","135,-74","140,-76","155,-84"
"-76,155","0,0","-2,17","-9,48","-23,78","-38,99","-48,115","-53,125","-55,131","-59,135","-65,144"
"116,81","0,0","10,7","24,17","61,41","74,59","79,63","83,69","94,74","101,77","109,79"
"19,169","0,0","7,12","15,43","23,78","26,105","22,126","20,141","19,149","18,155","18,161"
"74,-92","0,0","3,-8","14,-27","34,-51","42,-58","45,-62","53,-71","59,-79","67,-88","74,-92"
"-114,198","0,0","-8,32","-28,80","-34,100","-47,121","-57,136","-71,157","-82,171","-92,176","-102,186"
"116,110","0,0","16,12","44,35","78,65","90,77","95,91","105,108","119,121","127,127","122,119"
"-155,-189","0,0","-1,-4","-12,-29","-34,-69","-69,-107","-103,-149","-123,-173","-132,-180","-137,-186","-144,-191"
"60,76","0,0","-5,-2","1,2","17,16","39,44","64,65","71,72","75,78","79,86","75,84"
"155,65","0,0","10,1","40,6","88,13","135,21","151,27","153,34","154,40","156,45","156,51"
"74,-107","0,0","2,-8","20,-32","40,-60","49,-76","60,-93","68,-108","69,-114","72,-119","74,-113"
"191,65","0,0","8,1","51,4","124,22","161,37","166,44","170,52","175,58","181,62","191,65"
"41,110","0,0","-6,-1","-2,2","11,13","20,28","32,55","38,77","39,84","41,95","42,103"
"114,56","0,0","7,1","30,3","63,11","86,20","95,25","98,30","106,35","112,41","116,48"
"-138,105","0,0","-18,12","-48,36","-65,56","-85,70","-102,79","-110,83","-118,87","-125,92","-130,99"
"-149,-188","0,0","-7,-18","-32,-50","-76,-103","-104,-148","-121,-167","-124,-182","-129,-186","-141,-187","-149,-188"
"6,104","0,0","-1,4","1,10","4,23","8,42","11,60","11,76","7,86","7,92","6,98"
"-27,-33","0,0","0,-1","-2,-7","-6,-13","-13,-20","-18,-23","-22,-27","-24,-30","-25,-32","-27,-33"
"-130,127","0,0","-4,9","-23,36","-56,72","-84,89","-93,97","-101,107","-113,115","-121,119","-126,121"
"97,-195","0,0","6,-13","29,-52","55,-108","71,-150","73,-166","75,-180","84,-193","88,-199","94,-199"
"86,-169","0,0","17,-25","36,-58","50,-88","58,-108","61,-116","70,-133","72,-141","74,-149","78,-154"
"-28,72","0,0","-1,4","-3,15","-7,31","-11,44","-12,49","-13,54","-16,61","-20,65","-25,69"
"65,112","0,0","-4,7","-8,15","0,22","12,31","25,41","37,56","48,73","57,89","61,100"
"-129,51","0,0","-6,4","-25,8","-78,14","-112,17","-117,22","-116,29","-120,35","-125,38","-128,45"
"54,-8","0,0","3,-3","7,-6","19,-10","29,-11","34,-10","38,-9","42,-9","45,-8","49,-8"
"-109,40","0,0","-12,1","-32,4","-57,10","-81,16","-90,19","-91,23","-95,26","-99,29","-104,33"
"61,152","0,0","5,5","16,21","36,47","53,78","60,112","60,127","61,133","61,139","62,146"
"-28,-124","0,0","-4,-15","-12,-35","-25,-54","-33,-73","-32,-88","-27,-97","-27,-102","-27,-107","-27,-112"
"46,-133","0,0","3,-8","13,-25","23,-43","29,-59","34,-78","40,-102","45,-121","46,-125","46,-129"
"-163,-133","0,0","-5,-1","-28,-17","-70,-51","-104,-79","-123,-92","-134,-102","-147,-112","-157,-125","-163,-133"
"21,-18","0,0","2,-3","4,-7","7,-10","10,-12","11,-13","13,-14","15,-15","17,-16","19,-18"
"-130,49","0,0","-5,2","-22,9","-48,14","-79,16","-98,18","-103,21","-109,28","-117,36","-123,42"
"1,-135","0,0","3,-14","6,-40","5,-62","1,-81","-1,-95","-1,-108","-1,-119","3,-128","4,-133"
"195,122","0,0","7,7","34,27","79,55","123,76","149,88","154,93","156,98","173,106","194,113"
"-14,-86","0,0","-6,-2","-11,-3","-15,-5","-15,-13","-17,-28","-18,-47","-20,-58","-20,-64","-17,-75"
"40,132","0,0","-2,7","-3,12","2,23","13,47","22,75","28,102","33,116","36,120","38,126"
"-98,-108","0,0","-4,-9","-16,-26","-42,-50","-63,-71","-79,-85","-85,-91","-87,-94","-89,-98","-91,-101"
"-111,29","0,0","-8,-2","-30,-2","-55,0","-72,6","-81,11","-86,17","-93,22","-100,24","-106,26"
"-90,-68","0,0","-8,-4","-25,-12","-44,-24","-60,-36","-72,-49","-77,-56","-80,-59","-82,-61","-85,-64"
"109,65","0,0","3,3","14,10","32,21","53,32","70,42","85,50","95,56","99,59","103,62"
"-119,-158","0,0","-9,-10","-30,-37","-62,-80","-89,-111","-96,-123","-99,-131","-103,-138","-109,-147","-115,-154"
"-178,6","0,0","-8,-1","-29,-5","-64,-10","-104,-10","-137,-7","-151,-4","-156,-3","-160,-1","-166,3"
"94,156","0,0","0,10","20,34","39,74","54,100","65,111","91,129","101,141","102,153","94,156"
"-24,91","0,0","0,6","-4,18","-8,34","-13,46","-16,53","-17,61","-17,72","-17,80","-18,85"
"-2,-170","0,0","3,-9","7,-32","3,-58","0,-92","1,-124","-4,-134","-8,-139","-9,-144","-9,-152"
"-36,-116","0,0","0,-11","-3,-33","-11,-52","-19,-72","-24,-92","-25,-102","-27,-105","-29,-108","-32,-112"
"-16,-129","0,0","-1,-6","-3,-17","-9,-36","-17,-57","-25,-76","-28,-90","-29,-99","-30,-107","-26,-117"
"-35,8","0,0","-3,0","-7,0","-12,0","-17,0","-21,1","-24,1","-26,2","-29,3","-31,5"
"52,-83","0,0","3,-4","10,-18","17,-34","21,-48","25,-61","30,-71","37,-78","42,-83","44,-86"
"94,8","0,0","3,1","8,1","15,1","25,2","36,3","46,4","57,4","68,4","78,4"
"-97,61","0,0","-5,4","-12,12","-22,23","-32,34","-44,42","-57,49","-70,52","-81,53","-89,54"
"18,59","0,0","2,2","5,11","7,17","12,25","15,31","17,37","18,43","18,49","18,53"
"-79,-38","0,0","-4,-2","-11,-6","-20,-11","-30,-15","-38,-19","-45,-23","-52,-26","-59,-29","-65,-32"
"-158,24","0,0","-11,-5","-30,-10","-46,-11","-63,-7","-85,2","-110,11","-119,15","-130,20","-150,23"
"176,-89","0,0","13,-8","52,-27","95,-49","126,-61","137,-65","145,-72","155,-81","166,-89","176,-89"
"51,-110","0,0","1,-11","6,-30","17,-51","25,-64","29,-75","32,-85","39,-97","43,-102","46,-105"
"-9,-148","0,0","0,-6","-2,-19","-8,-39","-13,-64","-14,-91","-14,-114","-15,-127","-17,-133","-18,-138"
"-51,-41","0,0","-3,-2","-7,-5","-13,-10","-23,-17","-32,-23","-41,-27","-47,-30","-50,-33","-52,-36"
"160,-6","0,0","8,0","31,-5","66,-5","90,-4","105,-8","121,-12","140,-12","162,-12","164,-9"
"7,-84","0,0","-1,-1","1,-8","2,-16","1,-27","-1,-41","-1,-57","1,-70","3,-76","4,-80"
"-155,-175","0,0","-5,-19","-30,-64","-78,-113","-111,-140","-122,-151","-128,-156","-136,-164","-143,-171","-150,-173"
"-45,-187","0,0","-1,-10","-7,-41","-18,-68","-29,-100","-38,-133","-44,-151","-48,-161","-49,-171","-47,-180"
"159,-69","0,0","7,-3","24,-12","59,-26","107,-46","167,-66","197,-69","194,-69","185,-69","173,-69"
"-76,74","0,0","-10,16","-46,52","-82,82","-92,113","-105,136","-90,144","-99,156","-93,136","-83,107"
"-103,144","0,0","-7,16","-20,54","-37,93","-60,121","-66,133","-72,134","-79,134","-88,137","-103,144"
"5,108","0,0","4,-1","7,4","9,14","12,31","16,46","18,57","17,68","13,82","9,93"
"69,-138","0,0","3,-11","8,-39","15,-75","22,-95","29,-106","45,-126","58,-142","63,-146","65,-141"
"-160,30","0,0","-2,6","-15,10","-37,15","-65,23","-110,33","-138,36","-147,38","-152,39","-157,36"
"160,13","0,0","3,0","8,0","20,-2","44,-3","70,-3","93,0","114,4","134,7","150,10"
"-64,77","0,0","-2,0","-4,6","-13,16","-28,32","-42,46","-51,56","-56,61","-60,66","-62,69"
"-143,184","0,0","-3,3","-6,8","-16,23","-32,45","-51,73","-74,103","-103,135","-124,156","-131,167"
"26,-152","0,0","3,-14","11,-46","19,-81","25,-106","27,-116","28,-121","29,-126","30,-134","32,-143"
"0,72","0,0","1,4","2,10","3,14","3,20","3,30","3,42","3,52","3,60","1,68"
"-11,151","0,0","0,5","0,12","-1,55","-1,82","-2,105","-7,125","-9,138","-10,145","-11,151"
"-6,-95","0,0","0,-10","0,-22","-1,-38","-3,-55","-6,-70","-7,-77","-7,-81","-6,-86","-6,-89"
"-9,40","0,0","0,3","0,6","-1,9","-1,12","-2,15","-4,21","-5,27","-6,32","-7,36"
"-24,-62","0,0","0,-5","-1,-12","-5,-22","-9,-31","-14,-39","-18,-44","-19,-48","-20,-51","-21,-55"
"25,10","0,0","2,1","4,1","6,2","8,3","11,4","13,6","16,6","18,8","20,9"
"89,147","0,0","4,3","13,9","25,23","40,48","64,90","74,113","80,131","84,141","85,145"
"195,-73","0,0","17,-8","49,-25","86,-42","115,-52","139,-59","160,-66","178,-72","187,-74","192,-75"
"-144,26","0,0","-1,2","-23,6","-78,15","-125,25","-164,30","-184,32","-183,32","-168,27","-155,26"
"154,-148","0,0","2,-10","17,-35","43,-60","76,-80","115,-105","142,-121","146,-125","150,-131","151,-138"
"-145,94","0,0","-8,4","-23,11","-46,24","-72,37","-99,51","-122,64","-133,72","-136,77","-138,82"
"-119,-52","0,0","-10,-2","-44,-15","-75,-26","-91,-29","-97,-32","-105,-37","-112,-40","-115,-45","-119,-52"
"-139,208","0,0","-8,13","-43,52","-82,104","-100,132","-105,145","-113,163","-121,175","-131,193","-134,200"
"95,-61","0,0","3,-4","8,-9","16,-18","26,-27","38,-36","52,-44","68,-52","81,-56","89,-59"
"0,37","0,0","0,1","0,2","0,3","0,4","0,5","0,6","0,7","0,9","0,11"
"-55,73","0,0","-5,7","-16,19","-29,34","-43,48","-49,53","-50,57","-52,60","-54,63","-55,68"
"122,-114","0,0","-3,-4","-2,-10","8,-22","21,-36","38,-50","63,-69","88,-88","110,-104","116,-109"
"52,-183","0,0","5,-14","13,-45","23,-88","31,-127","52,-169","71,-197","67,-197","62,-193","57,-186"
"179,-29","0,0","12,-6","41,-12","81,-15","113,-17","134,-19","141,-20","148,-21","162,-24","173,-27"
"-6,127","0,0","0,6","-2,17","-3,32","-3,51","-4,72","-5,92","-5,103","-4,114","-4,122"
"-157,13","0,0","-19,-3","-68,-4","-124,-8","-175,-10","-182,-7","-185,-1","-184,3","-172,8","-163,12"
"-65,-119","0,0","-10,-19","-23,-40","-39,-60","-51,-74","-53,-79","-55,-86","-58,-96","-59,-102","-60,-108"
"138,154","0,0","10,4","31,19","59,41","87,69","105,96","112,112","120,125","127,139","134,148"
"166,-100","0,0","4,-6","16,-17","31,-28","55,-45","91,-68","129,-90","143,-96","149,-97","155,-100"
"-81,-53","0,0","-10,-5","-24,-14","-38,-24","-53,-31","-58,-33","-62,-36","-67,-40","-72,-44","-76,-49"
"163,-4","0,0","10,-3","40,-5","78,-5","113,-5","131,-7","139,-7","152,-6","157,-5","163,-4"
"85,-17","0,0","4,-2","35,-10","48,-11","61,-12","71,-14","77,-15","80,-15","83,-16","85,-17"
"-11,-34","0,0","0,-3","0,-8","-2,-13","-5,-19","-8,-24","-10,-28","-11,-29","-11,-32","-11,-34"
"34,32","0,0","2,0","4,0","12,4","17,8","22,13","26,17","30,22","32,27","34,30"
"66,72","0,0","6,3","14,10","27,22","35,31","43,40","58,57","75,74","81,82","73,76"
"127,-32","0,0","6,-1","20,-6","44,-13","76,-18","101,-20","112,-22","116,-24","118,-26","121,-28"
"5,177","0,0","0,10","0,39","-1,83","0,111","0,126","1,144","2,152","2,160","4,167"
"-16,126","0,0","0,9","-1,26","-5,52","-10,76","-14,94","-15,102","-16,106","-16,111","-16,116"
"113,-60","0,0","26,-24","47,-39","68,-50","84,-59","96,-65","101,-68","104,-69","108,-69","111,-67"
"-85,-53","0,0","-5,-3","-19,-13","-47,-24","-70,-32","-84,-38","-89,-42","-91,-44","-90,-48","-85,-53"
"-92,-139","0,0","-5,-4","-15,-18","-25,-40","-37,-65","-48,-82","-57,-94","-62,-99","-65,-105","-72,-119"
"165,6","0,0","3,2","12,2","29,2","55,3","81,5","101,7","116,7","136,6","153,5"
"-34,-167","0,0","4,-25","4,-57","-6,-86","-14,-117","-16,-138","-25,-150","-32,-153","-36,-160","-38,-170"
"159,125","0,0","3,4","18,10","48,23","95,45","118,64","125,75","130,86","140,102","151,115"
"30,66","0,0","-1,-3","0,-2","4,1","7,7","12,19","18,35","23,48","28,57","29,62"
"158,-43","0,0","4,-1","9,-5","23,-13","52,-25","90,-36","114,-40","124,-42","128,-43","137,-43"
"-144,-54","0,0","-13,1","-29,-5","-51,-15","-76,-25","-100,-35","-125,-46","-141,-50","-146,-52","-144,-54"
"-125,8","0,0","-13,-2","-34,-4","-46,-4","-58,-2","-72,2","-88,6","-97,8","-104,8","-111,8"
"95,-154","0,0","3,-18","19,-58","42,-104","64,-137","65,-144","70,-149","75,-153","81,-155","89,-155"
"124,33","0,0","8,0","21,-1","43,-1","73,1","103,7","125,12","129,15","129,20","130,24"
"65,-65","0,0","3,-3","8,-11","17,-23","30,-36","43,-48","52,-54","55,-56","57,-58","60,-62"
"140,161","0,0","9,1","27,11","54,38","94,70","103,82","108,101","117,123","129,139","136,152"
"-54,-99","0,0","-2,-10","-12,-34","-32,-62","-49,-84","-57,-95","-59,-101","-59,-106","-59,-108","-57,-104"
"4,26","0,0","0,2","0,4","1,6","1,9","2,12","3,15","3,18","4,20","4,22"
"-131,-98","0,0","-12,-9","-30,-24","-58,-49","-80,-65","-92,-72","-103,-79","-108,-82","-114,-85","-120,-90"
"-4,-21","0,0","-1,-3","-2,-6","-3,-9","-5,-12","-5,-14","-5,-16","-5,-18","-5,-20","-4,-21"
"64,-18","0,0","5,-2","12,-6","24,-12","42,-17","64,-22","78,-25","78,-25","75,-23","71,-21"
"-33,141","0,0","2,9","-2,27","-13,57","-20,80","-23,100","-24,121","-28,138","-32,147","-33,141"
"-25,56","0,0","-1,4","-2,7","-2,10","-3,14","-4,20","-7,26","-12,36","-17,44","-20,50"
"-5,157","0,0","-1,17","0,53","4,86","6,111","7,119","4,127","3,136","1,142","-2,151"
"51,-82","0,0","6,-12","19,-31","30,-50","38,-66","41,-77","43,-84","47,-95","51,-98","51,-88"
"-133,-57","0,0","-7,-4","-22,-13","-45,-23","-77,-31","-103,-37","-109,-40","-114,-42","-119,-44","-123,-47"
"-111,-14","0,0","-6,0","-17,-4","-32,-10","-52,-15","-68,-16","-92,-17","-96,-16","-99,-15","-103,-14"
"138,32","0,0","10,0","41,1","86,5","128,13","146,16","150,19","153,23","151,27","147,29"
"95,-156","0,0","4,-8","14,-24","31,-49","48,-74","59,-94","65,-112","69,-123","78,-138","88,-149"
"88,48","0,0","13,1","35,6","59,15","71,23","72,27","75,34","80,40","83,45","85,48"
"-87,0","0,0","-5,-2","-14,-4","-27,-6","-41,-6","-59,-5","-75,-3","-87,-3","-94,-1","-95,0"
"89,187","0,0","9,15","23,45","42,89","53,121","55,139","61,153","72,165","78,171","83,177"
"123,-170","0,0","5,-8","13,-24","29,-52","55,-87","85,-121","106,-143","117,-153","118,-157","118,-162"
"78,-63","0,0","0,2","4,-3","9,-7","13,-11","18,-15","25,-20","36,-28","51,-40","65,-52"
"36,87","0,0","3,5","6,18","10,36","18,56","25,69","27,75","27,80","29,84","33,85"
"148,83","0,0","22,10","60,33","72,44","82,53","103,65","116,72","127,79","132,83","140,83"
"-63,-12","0,0","-3,-2","-6,-5","-12,-8","-23,-12","-37,-15","-49,-17","-57,-18","-59,-16","-61,-14"
"118,27","0,0","7,5","30,14","47,18","69,19","88,20","97,21","101,22","108,24","115,25"
"-113,-49","0,0","-4,-4","-16,-12","-32,-21","-50,-27","-63,-30","-70,-31","-74,-32","-80,-34","-90,-38"
"-38,189","0,0","0,28","-1,59","-4,89","-8,115","-11,139","-14,156","-22,178","-28,182","-33,185"
"-93,-99","0,0","-4,-6","-15,-15","-38,-32","-56,-50","-63,-61","-67,-66","-72,-72","-83,-83","-90,-95"
"114,33","0,0","2,2","8,2","27,2","58,6","93,12","117,19","130,25","127,27","123,29"
"140,110","0,0","2,4","24,13","57,27","86,42","102,56","111,67","115,73","124,81","136,94"
"-3,-140","0,0","0,-18","-12,-52","-20,-93","-12,-136","-8,-154","-8,-160","-5,-163","-5,-157","-4,-149"
"-119,-97","0,0","-14,-14","-61,-51","-80,-60","-89,-65","-94,-68","-105,-76","-116,-85","-120,-89","-121,-94"
"36,101","0,0","2,2","4,5","9,13","16,28","26,48","33,67","37,83","38,90","38,94"
"151,96","0,0","8,5","20,10","53,26","87,44","97,51","105,60","123,72","141,78","144,85"
"148,190","0,0","21,17","46,39","82,76","120,114","128,136","131,154","137,168","141,177","143,185"
"25,120","0,0","7,10","19,22","26,34","31,54","31,71","31,82","30,92","30,99","28,104"
"161,161","0,0","5,2","33,19","75,48","110,75","133,96","145,116","153,135","157,150","161,158"
"-95,46","0,0","-7,0","-22,2","-47,8","-65,15","-74,21","-86,28","-88,31","-90,37","-93,42"
"-190,38","0,0","-4,0","-17,0","-45,5","-84,14","-121,22","-152,29","-174,36","-186,38","-190,38"
"-120,61","0,0","-10,6","-24,17","-35,28","-49,37","-65,43","-77,44","-90,46","-101,50","-107,54"
"102,33","0,0","11,-1","38,-3","63,-3","84,3","98,9","102,12","102,16","102,21","102,28"
"-26,-38","0,0","-2,-5","-6,-11","-10,-16","-12,-19","-14,-21","-16,-25","-20,-28","-23,-31","-24,-35"
"-59,102","0,0","-8,17","-16,39","-19,56","-26,72","-31,79","-38,87","-44,92","-50,96","-59,102"
"84,84","0,0","-2,2","6,6","24,17","45,28","69,45","79,58","83,67","84,71","84,75"
"23,126","0,0","2,2","5,5","10,11","16,20","22,30","27,52","27,74","26,96","25,108"
"-17,-63","0,0","0,-10","-1,-20","-4,-30","-9,-38","-13,-45","-15,-51","-15,-55","-15,-58","-16,-60"
"55,90","0,0","0,5","3,9","7,15","13,22","21,30","29,41","37,53","44,64","50,74"
"-8,-89","0,0","2,-5","3,-13","4,-25","2,-39","0,-53","-3,-65","-4,-73","-5,-77","-6,-80"
"-73,-74","0,0","-6,-8","-18,-22","-36,-33","-50,-42","-58,-48","-62,-52","-63,-58","-66,-63","-69,-67"
"93,-93","0,0","7,-8","19,-22","34,-41","47,-57","58,-69","67,-79","78,-86","89,-92","93,-93"
"53,191","0,0","-2,7","3,25","14,64","32,105","46,134","51,152","51,167","52,176","53,184"
"-42,10","0,0","-4,1","-7,3","-11,5","-16,6","-20,7","-25,8","-30,8","-34,9","-38,9"
"117,-79","0,0","7,-3","26,-22","51,-46","80,-67","102,-75","112,-77","123,-81","125,-82","120,-81"
"40,43","0,0","3,6","10,16","15,22","20,28","23,31","26,33","28,36","30,40","33,41"
"93,69","0,0","9,0","25,2","41,12","53,30","61,39","65,42","71,48","79,57","85,64"
"80,117","0,0","-3,1","2,4","13,19","25,42","36,62","51,76","61,84","67,93","72,103"
"-201,45","0,0","-8,2","-33,1","-72,6","-108,9","-115,12","-127,20","-141,27","-150,31","-169,38"
"142,135","0,0","7,2","31,18","57,45","88,68","103,84","119,96","129,103","136,112","139,118"
"-115,-8","0,0","-3,2","-8,0","-20,-2","-38,-4","-58,-7","-75,-10","-89,-12","-103,-12","-108,-11"
"154,-100","0,0","4,0","15,-9","34,-25","61,-44","84,-62","112,-84","140,-100","146,-100","150,-100"
"108,130","0,0","5,2","18,13","37,35","54,62","68,86","81,103","89,115","96,122","102,127"
"-156,56","0,0","-7,2","-29,6","-70,14","-98,21","-121,29","-137,38","-142,42","-146,45","-149,48"
"144,114","0,0","8,5","31,16","83,50","117,66","135,76","139,81","141,85","142,101","144,114"
"120,22","0,0","2,2","5,2","8,2","20,3","32,5","45,8","59,12","76,15","94,18"
"165,171","0,0","10,9","40,24","76,55","118,94","155,126","172,140","174,146","177,156","178,164"
"-174,173","0,0","-9,16","-35,49","-58,79","-86,106","-120,132","-141,148","-154,158","-160,163","-165,168"
"-99,-45","0,0","-10,-9","-28,-21","-48,-29","-68,-33","-78,-35","-83,-37","-86,-39","-90,-42","-95,-43"
"69,-22","0,0","1,1","2,0","6,-2","11,-4","15,-7","21,-9","27,-12","34,-14","42,-16"
"-60,-33","0,0","-2,-1","-5,-2","-8,-4","-12,-7","-17,-11","-23,-14","-29,-19","-36,-23","-42,-26"
"-46,21","0,0","-3,1","-8,1","-12,2","-20,4","-26,7","-30,9","-35,13","-40,17","-46,21"
"48,-29","0,0","1,-1","3,-3","7,-6","14,-12","22,-18","30,-23","37,-26","42,-28","46,-29"
"-88,-111","0,0","-14,-22","-42,-51","-57,-71","-59,-78","-60,-87","-63,-91","-65,-100","-75,-104","-82,-108"
"-145,73","0,0","-12,5","-28,13","-54,27","-78,51","-95,67","-108,74","-116,76","-128,76","-140,76"
"-57,-95","0,0","0,-9","-8,-26","-20,-49","-27,-68","-33,-75","-38,-79","-38,-85","-43,-88","-48,-92"
"-134,-6","0,0","-5,-2","-20,-7","-49,-10","-75,-10","-88,-9","-102,-4","-110,-1","-117,1","-125,2"
"-22,53","0,0","0,5","-2,11","-3,16","-4,20","-5,25","-7,29","-9,33","-11,37","-13,43"
"-100,-133","0,0","-4,-20","-19,-45","-42,-74","-54,-96","-54,-115","-54,-123","-55,-130","-62,-132","-76,-133"
"173,82","0,0","15,4","47,13","83,29","124,45","141,58","156,66","166,71","170,76","173,82"
"6,77","0,0","-1,4","-1,7","0,9","1,16","1,28","1,43","2,56","4,65","5,71"
"-97,205","0,0","-4,23","-32,60","-64,98","-78,121","-83,136","-84,150","-84,168","-90,189","-94,198"
"-88,163","0,0","-8,3","-18,15","-24,21","-36,36","-55,72","-70,99","-78,128","-82,147","-86,157"
"-143,171","0,0","-13,12","-53,41","-103,76","-124,96","-134,115","-138,127","-143,137","-144,146","-144,159"
"-12,177","0,0","0,6","0,16","0,43","-7,86","-9,124","-10,146","-13,158","-13,165","-13,172"
"40,-83","0,0","7,-10","11,-18","17,-32","22,-43","26,-53","30,-62","34,-69","37,-74","38,-76"
"-188,-37","0,0","-10,-4","-25,-12","-43,-22","-66,-27","-95,-27","-122,-27","-145,-28","-168,-30","-183,-34"
"-118,32","0,0","-4,0","-12,0","-24,2","-42,4","-62,9","-82,15","-96,21","-107,26","-112,29"
"201,28","0,0","25,-6","84,-9","126,-3","141,-1","152,-1","163,2","181,6","197,14","201,21"
"84,21","0,0","6,0","16,0","30,1","44,2","58,4","70,7","79,11","81,14","82,16"
"-5,68","0,0","-1,3","-1,8","-3,16","-5,24","-5,30","-5,36","-5,42","-5,49","-5,57"
"-89,43","0,0","-7,1","-21,6","-34,11","-50,16","-63,19","-69,22","-75,28","-81,34","-84,37"
"94,50","0,0","5,0","13,0","23,2","35,8","49,18","67,30","81,39","90,45","93,47"
"-171,-163","0,0","-6,-29","-33,-68","-78,-116","-107,-140","-114,-146","-121,-148","-136,-151","-156,-157","-164,-160"
"163,-116","0,0","7,-6","20,-20","38,-38","62,-58","93,-81","118,-99","128,-102","142,-106","157,-112"
"-56,-44","0,0","-2,-3","-6,-8","-13,-14","-27,-23","-37,-30","-45,-36","-50,-40","-53,-41","-55,-42"
"-156,183","0,0","-16,28","-56,81","-104,127","-124,149","-132,160","-139,169","-144,175","-151,180","-156,183"
"179,93","0,0","5,2","19,7","42,16","72,29","108,45","144,61","165,74","171,79","174,84"
"42,-11","0,0","-1,-1","1,-2","3,-4","6,-7","9,-10","12,-12","15,-14","32,-15","39,-13"
"-101,24","0,0","-4,0","-12,1","-31,4","-63,12","-89,18","-103,21","-110,23","-112,25","-108,25"
"-64,-53","0,0","-2,-7","-11,-19","-21,-27","-29,-32","-38,-39","-46,-45","-51,-48","-57,-50","-60,-52"
"-124,137","0,0","-3,9","-7,20","-12,32","-34,63","-73,97","-94,109","-102,114","-112,123","-119,128"
"78,-183","0,0","4,-7","14,-28","31,-60","40,-87","44,-106","50,-125","56,-141","67,-159","76,-172"
"-132,23","0,0","-9,2","-32,6","-59,11","-81,13","-99,16","-115,18","-121,20","-124,21","-128,23"
"180,27","0,0","19,-2","62,-3","110,3","144,5","154,6","162,9","170,13","175,17","178,22"
"81,-6","0,0","10,-7","26,-11","43,-12","55,-11","60,-9","64,-8","68,-8","74,-8","78,-8"
"-130,-125","0,0","-4,-7","-13,-22","-34,-42","-53,-60","-63,-69","-86,-87","-114,-107","-124,-114","-127,-118"
"-190,179","0,0","-43,27","-113,73","-148,103","-152,115","-159,128","-172,151","-179,161","-184,165","-187,173"
"27,96","0,0","2,6","6,14","12,26","20,44","25,61","27,70","27,77","27,85","27,91"
"58,-10","0,0","3,-1","6,-3","10,-5","14,-6","21,-8","29,-9","38,-10","47,-10","54,-10"
"155,-25","0,0","1,3","7,3","23,-3","50,-12","92,-21","120,-24","140,-26","148,-26","152,-26"
"-67,-82","0,0","-6,-6","-15,-18","-24,-27","-30,-35","-37,-44","-45,-51","-54,-56","-61,-66","-65,-76"
"101,162","0,0","-3,7","7,22","35,40","68,68","92,103","102,122","105,133","107,141","105,152"
"95,99","0,0","2,4","6,8","13,14","22,24","34,38","48,54","61,69","75,82","86,90"
"-9,-155","0,0","0,-10","-3,-31","-9,-60","-13,-85","-13,-104","-10,-123","-10,-139","-9,-150","-9,-155"
"10,158","0,0","-1,6","-1,18","-1,37","0,63","3,91","7,114","10,130","11,139","10,150"
"-10,-207","0,0","-1,-11","-2,-41","-1,-82","-1,-122","-3,-152","-7,-171","-10,-181","-9,-191","-8,-200"
"59,218","0,0","9,31","29,79","63,144","85,175","88,185","86,195","84,206","80,217","72,219"
"35,-93","0,0","5,-7","13,-22","20,-37","24,-47","28,-59","31,-74","31,-84","32,-87","34,-89"
"-28,43","0,0","-2,-1","-4,0","-6,5","-9,12","-14,21","-18,29","-22,35","-25,39","-28,43"
"-173,43","0,0","-10,0","-54,7","-111,16","-143,23","-144,29","-147,35","-153,37","-159,39","-166,40"
"17,-149","0,0","-1,0","1,-10","5,-25","10,-44","14,-60","17,-76","20,-95","22,-116","24,-136"
"41,92","0,0","2,6","7,14","14,26","21,36","26,50","31,64","34,75","36,82","38,85"
"-81,72","0,0","-10,6","-25,18","-36,29","-48,41","-62,52","-70,59","-74,64","-78,68","-81,72"
"-49,-121","0,0","-2,-16","-9,-42","-14,-80","-19,-112","-21,-123","-25,-126","-31,-127","-37,-125","-43,-122"
"153,-39","0,0","8,-5","19,-9","39,-14","67,-20","95,-25","117,-27","136,-28","147,-30","150,-32"
"-169,-118","0,0","-15,-8","-64,-36","-129,-68","-152,-77","-159,-82","-175,-95","-183,-106","-184,-115","-177,-118"
"118,139","0,0","7,9","30,31","50,60","68,85","80,102","90,119","105,135","116,145","118,148"
"50,-113","0,0","7,-15","15,-33","20,-53","25,-76","33,-99","39,-116","40,-119","43,-121","47,-118"
"-114,157","0,0","-2,9","-17,36","-55,84","-81,109","-93,119","-101,131","-103,143","-105,150","-109,155"
"-160,-25","0,0","-6,2","-13,4","-21,3","-41,-5","-75,-17","-103,-24","-117,-25","-127,-24","-144,-24"
"-19,-121","0,0","1,-21","3,-53","-1,-72","-3,-84","-5,-90","-8,-99","-11,-108","-15,-112","-17,-117"
"-121,-73","0,0","-6,-3","-18,-10","-36,-21","-59,-32","-80,-45","-98,-57","-110,-64","-118,-68","-120,-70"
"-61,105","0,0","-6,9","-16,27","-28,47","-42,69","-48,83","-50,88","-53,92","-55,98","-58,102"
"42,48","0,0","4,4","7,10","12,16","18,23","24,28","31,33","37,38","40,42","42,45"
"-182,69","0,0","-6,8","-44,16","-99,21","-144,30","-154,36","-157,43","-160,48","-168,55","-178,63"
"-15,103","0,0","-2,11","-5,32","-6,49","-7,63","-8,70","-8,78","-9,87","-11,95","-13,99"
"41,-102","0,0","2,-7","9,-21","18,-38","27,-55","33,-71","37,-82","39,-88","40,-93","40,-96"
"82,-168","0,0","-1,-5","4,-19","12,-39","19,-63","30,-93","51,-130","70,-154","77,-160","79,-164"
"135,-169","0,0","5,1","18,-9","41,-36","66,-69","85,-94","101,-116","119,-140","130,-154","131,-160"
"-157,64","0,0","-3,3","-11,8","-23,15","-44,23","-68,32","-92,40","-112,47","-128,51","-141,56"
"171,58","0,0","10,0","57,5","104,20","133,34","141,41","148,46","160,52","165,56","171,58"
"-148,208","0,0","-8,8","-17,16","-37,53","-69,120","-98,176","-119,209","-131,221","-140,225","-145,218"
"-160,9","0,0","-10,-1","-27,-2","-50,-4","-72,-5","-92,-3","-114,1","-129,5","-139,6","-149,8"
"24,31","0,0","3,1","6,3","9,7","12,11","14,15","16,18","17,20","18,23","20,26"
"197,-170","0,0","23,-17","92,-65","167,-123","192,-145","226,-179","224,-183","215,-178","203,-173","197,-170"
"-44,50","0,0","-1,-2","8,-6","12,-10","0,-10","-17,-3","-23,13","-32,30","-40,37","-43,43"
"31,173","0,0","26,14","36,50","36,85","36,103","36,120","41,133","41,143","37,156","35,165"
"-29,-41","0,0","-2,-4","-3,-6","-5,-10","-9,-17","-13,-22","-17,-28","-21,-32","-25,-36","-27,-39"
"-89,-115","0,0","-3,-12","-12,-30","-27,-54","-41,-70","-50,-78","-61,-95","-65,-102","-69,-105","-74,-109"
"126,-157","0,0","5,-12","18,-30","37,-54","55,-78","70,-97","81,-109","91,-120","109,-138","121,-148"
"-39,-192","0,0","0,-15","-7,-62","-16,-92","-17,-111","-17,-124","-19,-134","-24,-145","-29,-161","-33,-176"
"-200,58","0,0","-14,3","-61,20","-120,35","-135,41","-145,43","-159,47","-172,50","-181,53","-190,55"
"29,20","0,0","1,0","4,1","7,3","10,5","13,8","16,10","19,13","22,15","25,18"
"18,130","0,0","6,13","11,34","21,63","26,89","26,103","26,111","24,118","21,124","18,130"
"100,-78","0,0","4,-4","14,-15","27,-27","44,-40","57,-49","65,-54","70,-59","80,-67","93,-75"
"-97,-142","0,0","-19,-38","-50,-65","-59,-75","-64,-82","-71,-91","-74,-101","-78,-110","-81,-118","-90,-131"
"155,171","0,0","10,17","38,38","68,58","87,83","106,106","119,121","136,138","146,154","152,163"
"-136,78","0,0","-8,9","-28,26","-74,50","-119,67","-147,70","-167,74","-172,77","-152,77","-136,78"
"10,-165","0,0","-3,-8","-8,-29","-9,-60","-2,-90","5,-115","7,-127","8,-138","7,-147","8,-157"
"67,-140","0,0","4,-11","9,-27","21,-51","34,-72","41,-86","45,-96","51,-109","58,-124","65,-135"
"-156,-120","0,0","-7,-7","-21,-31","-86,-89","-109,-113","-114,-116","-122,-114","-133,-113","-140,-114","-148,-117"
"61,76","0,0","7,4","16,10","26,26","39,47","51,62","55,69","58,78","64,88","66,88"
"116,148","0,0","7,7","21,25","40,60","61,94","78,116","89,128","96,136","104,140","109,143"
"86,-195","0,0","3,-11","14,-43","32,-82","39,-112","54,-160","60,-179","66,-181","74,-181","79,-185"
"-57,-164","0,0","5,-35","1,-92","-13,-123","-23,-132","-28,-151","-32,-176","-37,-181","-44,-182","-50,-180"
"-76,190","0,0","-1,14","-7,46","-29,106","-51,144","-55,156","-55,168","-56,178","-59,184","-67,188"
"-50,162","0,0","-2,16","-7,36","-17,65","-24,95","-30,112","-34,119","-36,127","-42,142","-49,154"
"151,65","0,0","3,4","7,9","18,9","44,9","77,19","113,33","134,43","136,48","145,59"
"181,-139","0,0","10,-17","48,-64","92,-111","127,-134","151,-148","172,-158","181,-160","183,-154","182,-146"
"130,-120","0,0","8,-9","24,-25","52,-52","77,-78","92,-93","101,-100","117,-112","124,-117","130,-120"
"-16,114","0,0","-3,4","-7,23","-10,44","-10,62","-11,76","-14,90","-16,98","-16,104","-16,109"
"-195,91","0,0","-9,3","-33,13","-97,29","-169,43","-188,50","-187,57","-189,65","-192,72","-195,82"
"97,31","0,0","4,1","9,1","15,2","23,4","36,9","53,15","70,21","86,27","95,29"
"-126,31","0,0","-3,0","-7,0","-13,1","-24,3","-35,6","-48,11","-62,15","-76,18","-90,22"
"-144,-117","0,0","-10,-6","-29,-18","-56,-36","-80,-59","-104,-83","-122,-99","-128,-107","-130,-110","-134,-113"
"2,-191","0,0","1,-23","1,-71","-1,-117","-1,-138","-4,-149","-8,-160","-8,-167","-7,-175","-6,-181"
"129,-74","0,0","6,-3","18,-13","38,-27","58,-41","80,-55","112,-71","134,-81","138,-84","134,-77"
"-63,90","0,0","-2,4","-7,12","-18,28","-30,44","-44,59","-55,71","-56,76","-57,81","-60,86"
"149,20","0,0","13,-4","48,-6","83,-5","111,-5","117,-2","123,2","126,7","130,10","139,16"
"-72,141","0,0","-3,11","-18,41","-40,74","-56,91","-61,97","-65,109","-65,116","-67,124","-70,135"
"19,-133","0,0","5,-13","18,-40","27,-68","28,-85","28,-94","25,-102","23,-109","21,-118","19,-127"
"184,-66","0,0","14,-7","38,-21","76,-34","104,-41","126,-47","139,-51","145,-53","150,-55","156,-57"
"128,-202","0,0","17,-11","49,-34","75,-61","87,-94","90,-124","106,-159","112,-168","118,-180","126,-195"
"-79,-143","0,0","-1,-2","-1,-30","-4,-51","-11,-68","-20,-89","-30,-106","-40,-115","-51,-121","-60,-128"
"-30,-132","0,0","2,0","2,-16","-3,-41","-12,-67","-20,-87","-24,-102","-27,-110","-29,-115","-30,-124"
"-60,13","0,0","-1,0","-3,-2","-6,-4","-10,-5","-14,-5","-17,-4","-22,-1","-31,3","-42,7"
"160,153","0,0","9,7","52,35","119,80","144,114","140,121","143,128","148,137","151,144","155,148"
"-109,-47","0,0","-8,-6","-28,-21","-57,-37","-76,-47","-85,-52","-91,-52","-96,-51","-101,-51","-106,-51"
"59,113","0,0","0,3","3,11","15,37","23,51","33,64","42,79","51,92","55,101","57,106"
"-128,23","0,0","-13,0","-47,-2","-70,-3","-80,-2","-84,1","-90,5","-102,11","-110,16","-116,18"
"50,108","0,0","5,11","14,33","25,53","34,68","39,79","40,86","42,91","45,94","48,99"
"123,-61","0,0","16,-4","43,-22","73,-39","94,-48","108,-51","113,-52","116,-54","119,-57","123,-61"
"23,63","0,0","2,5","6,13","11,19","16,25","20,32","22,39","23,47","23,55","23,60"
"-59,-167","0,0","-6,-14","-27,-56","-45,-98","-50,-121","-46,-135","-43,-156","-47,-164","-52,-171","-56,-172"
"-28,-53","0,0","-3,-1","-6,-6","-11,-14","-16,-22","-19,-31","-22,-39","-24,-44","-26,-47","-27,-50"
"197,-85","0,0","8,-9","26,-23","58,-41","91,-55","120,-62","147,-67","156,-67","165,-73","180,-79"
"-148,-23","0,0","-6,-1","-29,-6","-66,-13","-96,-20","-115,-22","-123,-22","-129,-22","-136,-22","-144,-22"
"-102,-162","0,0","-10,-10","-30,-44","-47,-81","-54,-106","-67,-125","-79,-141","-86,-146","-91,-151","-97,-156"
"198,157","0,0","13,10","63,39","133,80","162,104","163,119","177,135","186,142","191,148","198,152"
"-91,177","0,0","2,6","3,19","-9,50","-23,89","-41,116","-60,134","-73,146","-82,159","-88,170"
"40,-163","0,0","0,-11","3,-35","8,-63","13,-96","20,-122","28,-142","34,-153","35,-158","37,-162"
"25,-149","0,0","-2,-3","0,-16","8,-49","12,-80","11,-102","10,-116","14,-127","16,-136","21,-144"
"62,22","0,0","3,0","7,0","11,1","18,3","30,8","42,13","51,18","56,23","58,26"
"-158,-111","0,0","-4,-3","-10,-12","-29,-31","-60,-57","-99,-84","-130,-104","-145,-112","-150,-113","-154,-111"
"-22,53","0,0","0,4","-1,9","-2,13","-5,18","-7,26","-10,35","-12,40","-15,45","-19,50"
"-119,188","0,0","-3,3","-12,13","-35,34","-68,66","-87,97","-100,130","-109,155","-112,169","-115,177"
"105,102","0,0","-1,6","2,9","15,15","41,29","68,49","82,70","91,88","96,96","99,98"
"174,-106","0,0","22,-19","62,-46","102,-66","140,-96","179,-129","194,-137","193,-128","187,-117","181,-112"
"-64,124","0,0","-1,3","-3,7","-7,15","-15,33","-28,59","-39,81","-48,97","-55,106","-60,111"
"-27,63","0,0","0,3","-3,8","-6,14","-11,22","-15,32","-19,45","-22,51","-23,54","-24,56"
"-67,-158","0,0","-4,-14","-14,-46","-19,-82","-22,-101","-29,-120","-31,-146","-36,-149","-42,-150","-49,-151"
"126,-113","0,0","24,-32","57,-67","78,-87","90,-100","104,-116","120,-127","127,-129","130,-124","127,-119"
"32,-34","0,0","0,0","3,-2","7,-7","12,-12","17,-16","22,-21","25,-24","28,-26","28,-28"
"111,9","0,0","4,0","11,-2","19,-2","28,-3","43,-3","59,-2","74,-1","85,2","96,4"
"166,183","0,0","15,12","49,39","73,67","89,108","114,143","123,160","130,171","137,173","145,175"
"149,7","0,0","2,-1","10,-1","23,-1","43,2","66,6","82,8","100,8","125,7","142,7"
"-49,-28","0,0","-3,-1","-10,-6","-21,-15","-27,-20","-30,-22","-33,-23","-37,-23","-41,-24","-44,-25"
"173,-21","0,0","3,-1","10,-4","47,-10","70,-10","91,-10","111,-11","124,-11","130,-12","137,-16"
"-75,130","0,0","1,5","1,11","-1,23","-6,39","-17,68","-39,99","-62,120","-70,125","-73,127"
"-153,46","0,0","-4,0","-11,0","-24,3","-44,9","-69,17","-92,26","-115,34","-135,42","-148,46"
"-119,6","0,0","-4,0","-10,1","-19,1","-30,2","-47,3","-65,4","-83,4","-101,5","-119,6"
"131,-9","0,0","8,-1","21,-5","46,-10","75,-13","106,-14","129,-13","139,-9","142,-7","136,-8"
"-82,-134","0,0","-3,-32","-21,-74","-40,-101","-49,-109","-54,-120","-57,-134","-60,-141","-65,-144","-75,-141"
"-141,51","0,0","-6,6","-24,15","-57,29","-84,37","-99,43","-106,48","-114,52","-125,53","-131,53"
"189,-189","0,0","9,-15","27,-41","55,-74","96,-115","132,-144","161,-167","176,-179","179,-181","183,-184"
1 189,-42 0,0 7,-1 58,-25 136,-49 188,-57 219,-60 216,-54 211,-51 205,-47 196,-43
2 117,102 0,0 12,7 36,24 72,45 96,60 108,71 110,77 112,83 114,89 116,96
3 -83,87 0,0 -4,2 -11,10 -26,28 -40,44 -56,60 -63,69 -70,75 -76,79 -80,83
4 -55,-84 0,0 -6,-4 -12,-15 -22,-32 -33,-46 -41,-57 -45,-64 -46,-69 -49,-73 -51,-78
5 -18,-150 0,0 -1,-5 -5,-19 -12,-49 -16,-97 -23,-117 -24,-129 -23,-138 -21,-146 -18,-154
6 -172,-121 0,0 -9,-10 -40,-34 -80,-61 -112,-82 -132,-93 -138,-98 -146,-108 -159,-119 -165,-120
7 69,-37 0,0 2,-2 6,-6 12,-11 19,-16 31,-22 46,-29 60,-32 64,-34 66,-36
8 -166,-62 0,0 -8,-4 -29,-16 -75,-42 -118,-61 -128,-63 -134,-62 -144,-61 -154,-61 -161,-62
9 99,-15 0,0 6,0 20,-3 57,-6 83,-7 113,-12 134,-14 130,-14 117,-14 105,-15
10 65,106 0,0 -4,-1 2,5 11,18 28,34 47,54 55,70 59,83 63,96 65,106
11 48,-83 0,0 4,-10 9,-21 12,-31 17,-44 24,-59 29,-70 37,-79 42,-85 46,-86
12 -146,38 0,0 -11,0 -39,3 -80,11 -125,18 -149,19 -159,21 -164,23 -168,26 -157,32
13 -149,-9 0,0 -13,-5 -40,-12 -76,-19 -104,-24 -128,-24 -143,-23 -144,-18 -145,-13 -149,-9
14 -9,5 0,0 6,-5 15,-8 20,-9 25,-9 28,-7 25,-5 18,-2 11,0 6,1
15 200,-109 0,0 2,-4 8,-11 30,-36 59,-60 85,-73 120,-87 152,-98 171,-104 184,-106
16 -15,184 0,0 2,19 2,50 -3,83 -17,112 -20,132 -16,151 -16,164 -17,170 -18,176
17 109,-132 0,0 -7,0 -2,-10 23,-38 63,-81 90,-109 107,-124 121,-136 126,-138 118,-136
18 -127,-91 0,0 -8,-19 -34,-44 -66,-58 -83,-64 -90,-68 -103,-74 -114,-77 -120,-82 -124,-86
19 -186,185 0,0 -10,13 -58,72 -106,118 -118,141 -133,158 -143,166 -152,171 -163,179 -181,190
20 38,92 0,0 4,3 11,13 21,25 31,37 38,51 39,62 39,70 38,75 38,79
21 52,154 0,0 1,7 15,37 35,80 57,108 64,121 64,129 65,137 64,145 59,152
22 149,-106 0,0 4,-6 18,-21 44,-43 68,-61 89,-73 110,-87 120,-94 125,-96 132,-100
23 -45,-81 0,0 -1,-16 -4,-45 -11,-60 -16,-73 -20,-82 -26,-87 -33,-89 -39,-91 -43,-88
24 -18,44 0,0 0,2 -1,7 -4,20 -6,24 -7,29 -9,33 -11,36 -12,38 -13,39
25 68,-144 0,0 11,-26 22,-70 41,-116 51,-137 83,-165 93,-172 86,-166 77,-159 73,-154
26 -124,-125 0,0 -8,-5 -26,-20 -54,-41 -79,-68 -100,-95 -115,-114 -124,-122 -128,-125 -126,-128
27 -109,62 0,0 -10,8 -22,21 -34,33 -56,48 -79,60 -86,63 -92,64 -98,64 -104,63
28 -171,-36 0,0 -13,-1 -51,-14 -100,-33 -127,-45 -139,-47 -145,-44 -154,-42 -163,-42 -167,-40
29 -129,-163 0,0 -5,-11 -22,-44 -47,-86 -79,-129 -87,-135 -93,-140 -100,-143 -106,-146 -117,-155
30 92,21 0,0 10,-1 37,-3 77,4 114,14 128,20 120,21 112,22 106,22 99,22
31 -182,-85 0,0 -13,-6 -38,-23 -72,-44 -106,-62 -139,-72 -153,-77 -158,-79 -165,-80 -171,-82
32 -17,-72 0,0 0,-8 0,-14 0,-22 -3,-32 -7,-42 -11,-51 -14,-58 -17,-64 -18,-67
33 -114,72 0,0 3,-3 4,-7 6,-9 1,-6 -11,5 -32,21 -56,39 -75,53 -96,64
34 64,-108 0,0 1,-4 2,-12 6,-24 11,-44 24,-68 40,-89 44,-91 47,-94 51,-96
35 -75,179 0,0 -3,12 -16,52 -39,98 -57,127 -65,143 -69,151 -71,158 -72,165 -74,172
36 -133,-26 0,0 -9,-7 -28,-15 -51,-23 -78,-26 -98,-27 -106,-26 -113,-26 -119,-26 -127,-26
37 143,126 0,0 3,3 25,20 59,46 102,75 140,95 142,102 142,108 142,115 143,120
38 -71,-90 0,0 -3,-9 -16,-28 -34,-54 -45,-73 -49,-77 -52,-82 -54,-86 -56,-90 -62,-90
39 2,-135 0,0 0,-5 -1,-15 -3,-30 -5,-50 -5,-71 -4,-93 -2,-112 -2,-125 -1,-131
40 -102,152 0,0 -5,17 -38,85 -54,104 -64,113 -73,124 -79,130 -84,137 -90,144 -96,149
41 174,-92 0,0 10,-8 25,-19 53,-37 88,-55 112,-65 130,-72 135,-74 140,-76 155,-84
42 -76,155 0,0 -2,17 -9,48 -23,78 -38,99 -48,115 -53,125 -55,131 -59,135 -65,144
43 116,81 0,0 10,7 24,17 61,41 74,59 79,63 83,69 94,74 101,77 109,79
44 19,169 0,0 7,12 15,43 23,78 26,105 22,126 20,141 19,149 18,155 18,161
45 74,-92 0,0 3,-8 14,-27 34,-51 42,-58 45,-62 53,-71 59,-79 67,-88 74,-92
46 -114,198 0,0 -8,32 -28,80 -34,100 -47,121 -57,136 -71,157 -82,171 -92,176 -102,186
47 116,110 0,0 16,12 44,35 78,65 90,77 95,91 105,108 119,121 127,127 122,119
48 -155,-189 0,0 -1,-4 -12,-29 -34,-69 -69,-107 -103,-149 -123,-173 -132,-180 -137,-186 -144,-191
49 60,76 0,0 -5,-2 1,2 17,16 39,44 64,65 71,72 75,78 79,86 75,84
50 155,65 0,0 10,1 40,6 88,13 135,21 151,27 153,34 154,40 156,45 156,51
51 74,-107 0,0 2,-8 20,-32 40,-60 49,-76 60,-93 68,-108 69,-114 72,-119 74,-113
52 191,65 0,0 8,1 51,4 124,22 161,37 166,44 170,52 175,58 181,62 191,65
53 41,110 0,0 -6,-1 -2,2 11,13 20,28 32,55 38,77 39,84 41,95 42,103
54 114,56 0,0 7,1 30,3 63,11 86,20 95,25 98,30 106,35 112,41 116,48
55 -138,105 0,0 -18,12 -48,36 -65,56 -85,70 -102,79 -110,83 -118,87 -125,92 -130,99
56 -149,-188 0,0 -7,-18 -32,-50 -76,-103 -104,-148 -121,-167 -124,-182 -129,-186 -141,-187 -149,-188
57 6,104 0,0 -1,4 1,10 4,23 8,42 11,60 11,76 7,86 7,92 6,98
58 -27,-33 0,0 0,-1 -2,-7 -6,-13 -13,-20 -18,-23 -22,-27 -24,-30 -25,-32 -27,-33
59 -130,127 0,0 -4,9 -23,36 -56,72 -84,89 -93,97 -101,107 -113,115 -121,119 -126,121
60 97,-195 0,0 6,-13 29,-52 55,-108 71,-150 73,-166 75,-180 84,-193 88,-199 94,-199
61 86,-169 0,0 17,-25 36,-58 50,-88 58,-108 61,-116 70,-133 72,-141 74,-149 78,-154
62 -28,72 0,0 -1,4 -3,15 -7,31 -11,44 -12,49 -13,54 -16,61 -20,65 -25,69
63 65,112 0,0 -4,7 -8,15 0,22 12,31 25,41 37,56 48,73 57,89 61,100
64 -129,51 0,0 -6,4 -25,8 -78,14 -112,17 -117,22 -116,29 -120,35 -125,38 -128,45
65 54,-8 0,0 3,-3 7,-6 19,-10 29,-11 34,-10 38,-9 42,-9 45,-8 49,-8
66 -109,40 0,0 -12,1 -32,4 -57,10 -81,16 -90,19 -91,23 -95,26 -99,29 -104,33
67 61,152 0,0 5,5 16,21 36,47 53,78 60,112 60,127 61,133 61,139 62,146
68 -28,-124 0,0 -4,-15 -12,-35 -25,-54 -33,-73 -32,-88 -27,-97 -27,-102 -27,-107 -27,-112
69 46,-133 0,0 3,-8 13,-25 23,-43 29,-59 34,-78 40,-102 45,-121 46,-125 46,-129
70 -163,-133 0,0 -5,-1 -28,-17 -70,-51 -104,-79 -123,-92 -134,-102 -147,-112 -157,-125 -163,-133
71 21,-18 0,0 2,-3 4,-7 7,-10 10,-12 11,-13 13,-14 15,-15 17,-16 19,-18
72 -130,49 0,0 -5,2 -22,9 -48,14 -79,16 -98,18 -103,21 -109,28 -117,36 -123,42
73 1,-135 0,0 3,-14 6,-40 5,-62 1,-81 -1,-95 -1,-108 -1,-119 3,-128 4,-133
74 195,122 0,0 7,7 34,27 79,55 123,76 149,88 154,93 156,98 173,106 194,113
75 -14,-86 0,0 -6,-2 -11,-3 -15,-5 -15,-13 -17,-28 -18,-47 -20,-58 -20,-64 -17,-75
76 40,132 0,0 -2,7 -3,12 2,23 13,47 22,75 28,102 33,116 36,120 38,126
77 -98,-108 0,0 -4,-9 -16,-26 -42,-50 -63,-71 -79,-85 -85,-91 -87,-94 -89,-98 -91,-101
78 -111,29 0,0 -8,-2 -30,-2 -55,0 -72,6 -81,11 -86,17 -93,22 -100,24 -106,26
79 -90,-68 0,0 -8,-4 -25,-12 -44,-24 -60,-36 -72,-49 -77,-56 -80,-59 -82,-61 -85,-64
80 109,65 0,0 3,3 14,10 32,21 53,32 70,42 85,50 95,56 99,59 103,62
81 -119,-158 0,0 -9,-10 -30,-37 -62,-80 -89,-111 -96,-123 -99,-131 -103,-138 -109,-147 -115,-154
82 -178,6 0,0 -8,-1 -29,-5 -64,-10 -104,-10 -137,-7 -151,-4 -156,-3 -160,-1 -166,3
83 94,156 0,0 0,10 20,34 39,74 54,100 65,111 91,129 101,141 102,153 94,156
84 -24,91 0,0 0,6 -4,18 -8,34 -13,46 -16,53 -17,61 -17,72 -17,80 -18,85
85 -2,-170 0,0 3,-9 7,-32 3,-58 0,-92 1,-124 -4,-134 -8,-139 -9,-144 -9,-152
86 -36,-116 0,0 0,-11 -3,-33 -11,-52 -19,-72 -24,-92 -25,-102 -27,-105 -29,-108 -32,-112
87 -16,-129 0,0 -1,-6 -3,-17 -9,-36 -17,-57 -25,-76 -28,-90 -29,-99 -30,-107 -26,-117
88 -35,8 0,0 -3,0 -7,0 -12,0 -17,0 -21,1 -24,1 -26,2 -29,3 -31,5
89 52,-83 0,0 3,-4 10,-18 17,-34 21,-48 25,-61 30,-71 37,-78 42,-83 44,-86
90 94,8 0,0 3,1 8,1 15,1 25,2 36,3 46,4 57,4 68,4 78,4
91 -97,61 0,0 -5,4 -12,12 -22,23 -32,34 -44,42 -57,49 -70,52 -81,53 -89,54
92 18,59 0,0 2,2 5,11 7,17 12,25 15,31 17,37 18,43 18,49 18,53
93 -79,-38 0,0 -4,-2 -11,-6 -20,-11 -30,-15 -38,-19 -45,-23 -52,-26 -59,-29 -65,-32
94 -158,24 0,0 -11,-5 -30,-10 -46,-11 -63,-7 -85,2 -110,11 -119,15 -130,20 -150,23
95 176,-89 0,0 13,-8 52,-27 95,-49 126,-61 137,-65 145,-72 155,-81 166,-89 176,-89
96 51,-110 0,0 1,-11 6,-30 17,-51 25,-64 29,-75 32,-85 39,-97 43,-102 46,-105
97 -9,-148 0,0 0,-6 -2,-19 -8,-39 -13,-64 -14,-91 -14,-114 -15,-127 -17,-133 -18,-138
98 -51,-41 0,0 -3,-2 -7,-5 -13,-10 -23,-17 -32,-23 -41,-27 -47,-30 -50,-33 -52,-36
99 160,-6 0,0 8,0 31,-5 66,-5 90,-4 105,-8 121,-12 140,-12 162,-12 164,-9
100 7,-84 0,0 -1,-1 1,-8 2,-16 1,-27 -1,-41 -1,-57 1,-70 3,-76 4,-80
101 -155,-175 0,0 -5,-19 -30,-64 -78,-113 -111,-140 -122,-151 -128,-156 -136,-164 -143,-171 -150,-173
102 -45,-187 0,0 -1,-10 -7,-41 -18,-68 -29,-100 -38,-133 -44,-151 -48,-161 -49,-171 -47,-180
103 159,-69 0,0 7,-3 24,-12 59,-26 107,-46 167,-66 197,-69 194,-69 185,-69 173,-69
104 -76,74 0,0 -10,16 -46,52 -82,82 -92,113 -105,136 -90,144 -99,156 -93,136 -83,107
105 -103,144 0,0 -7,16 -20,54 -37,93 -60,121 -66,133 -72,134 -79,134 -88,137 -103,144
106 5,108 0,0 4,-1 7,4 9,14 12,31 16,46 18,57 17,68 13,82 9,93
107 69,-138 0,0 3,-11 8,-39 15,-75 22,-95 29,-106 45,-126 58,-142 63,-146 65,-141
108 -160,30 0,0 -2,6 -15,10 -37,15 -65,23 -110,33 -138,36 -147,38 -152,39 -157,36
109 160,13 0,0 3,0 8,0 20,-2 44,-3 70,-3 93,0 114,4 134,7 150,10
110 -64,77 0,0 -2,0 -4,6 -13,16 -28,32 -42,46 -51,56 -56,61 -60,66 -62,69
111 -143,184 0,0 -3,3 -6,8 -16,23 -32,45 -51,73 -74,103 -103,135 -124,156 -131,167
112 26,-152 0,0 3,-14 11,-46 19,-81 25,-106 27,-116 28,-121 29,-126 30,-134 32,-143
113 0,72 0,0 1,4 2,10 3,14 3,20 3,30 3,42 3,52 3,60 1,68
114 -11,151 0,0 0,5 0,12 -1,55 -1,82 -2,105 -7,125 -9,138 -10,145 -11,151
115 -6,-95 0,0 0,-10 0,-22 -1,-38 -3,-55 -6,-70 -7,-77 -7,-81 -6,-86 -6,-89
116 -9,40 0,0 0,3 0,6 -1,9 -1,12 -2,15 -4,21 -5,27 -6,32 -7,36
117 -24,-62 0,0 0,-5 -1,-12 -5,-22 -9,-31 -14,-39 -18,-44 -19,-48 -20,-51 -21,-55
118 25,10 0,0 2,1 4,1 6,2 8,3 11,4 13,6 16,6 18,8 20,9
119 89,147 0,0 4,3 13,9 25,23 40,48 64,90 74,113 80,131 84,141 85,145
120 195,-73 0,0 17,-8 49,-25 86,-42 115,-52 139,-59 160,-66 178,-72 187,-74 192,-75
121 -144,26 0,0 -1,2 -23,6 -78,15 -125,25 -164,30 -184,32 -183,32 -168,27 -155,26
122 154,-148 0,0 2,-10 17,-35 43,-60 76,-80 115,-105 142,-121 146,-125 150,-131 151,-138
123 -145,94 0,0 -8,4 -23,11 -46,24 -72,37 -99,51 -122,64 -133,72 -136,77 -138,82
124 -119,-52 0,0 -10,-2 -44,-15 -75,-26 -91,-29 -97,-32 -105,-37 -112,-40 -115,-45 -119,-52
125 -139,208 0,0 -8,13 -43,52 -82,104 -100,132 -105,145 -113,163 -121,175 -131,193 -134,200
126 95,-61 0,0 3,-4 8,-9 16,-18 26,-27 38,-36 52,-44 68,-52 81,-56 89,-59
127 0,37 0,0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 0,9 0,11
128 -55,73 0,0 -5,7 -16,19 -29,34 -43,48 -49,53 -50,57 -52,60 -54,63 -55,68
129 122,-114 0,0 -3,-4 -2,-10 8,-22 21,-36 38,-50 63,-69 88,-88 110,-104 116,-109
130 52,-183 0,0 5,-14 13,-45 23,-88 31,-127 52,-169 71,-197 67,-197 62,-193 57,-186
131 179,-29 0,0 12,-6 41,-12 81,-15 113,-17 134,-19 141,-20 148,-21 162,-24 173,-27
132 -6,127 0,0 0,6 -2,17 -3,32 -3,51 -4,72 -5,92 -5,103 -4,114 -4,122
133 -157,13 0,0 -19,-3 -68,-4 -124,-8 -175,-10 -182,-7 -185,-1 -184,3 -172,8 -163,12
134 -65,-119 0,0 -10,-19 -23,-40 -39,-60 -51,-74 -53,-79 -55,-86 -58,-96 -59,-102 -60,-108
135 138,154 0,0 10,4 31,19 59,41 87,69 105,96 112,112 120,125 127,139 134,148
136 166,-100 0,0 4,-6 16,-17 31,-28 55,-45 91,-68 129,-90 143,-96 149,-97 155,-100
137 -81,-53 0,0 -10,-5 -24,-14 -38,-24 -53,-31 -58,-33 -62,-36 -67,-40 -72,-44 -76,-49
138 163,-4 0,0 10,-3 40,-5 78,-5 113,-5 131,-7 139,-7 152,-6 157,-5 163,-4
139 85,-17 0,0 4,-2 35,-10 48,-11 61,-12 71,-14 77,-15 80,-15 83,-16 85,-17
140 -11,-34 0,0 0,-3 0,-8 -2,-13 -5,-19 -8,-24 -10,-28 -11,-29 -11,-32 -11,-34
141 34,32 0,0 2,0 4,0 12,4 17,8 22,13 26,17 30,22 32,27 34,30
142 66,72 0,0 6,3 14,10 27,22 35,31 43,40 58,57 75,74 81,82 73,76
143 127,-32 0,0 6,-1 20,-6 44,-13 76,-18 101,-20 112,-22 116,-24 118,-26 121,-28
144 5,177 0,0 0,10 0,39 -1,83 0,111 0,126 1,144 2,152 2,160 4,167
145 -16,126 0,0 0,9 -1,26 -5,52 -10,76 -14,94 -15,102 -16,106 -16,111 -16,116
146 113,-60 0,0 26,-24 47,-39 68,-50 84,-59 96,-65 101,-68 104,-69 108,-69 111,-67
147 -85,-53 0,0 -5,-3 -19,-13 -47,-24 -70,-32 -84,-38 -89,-42 -91,-44 -90,-48 -85,-53
148 -92,-139 0,0 -5,-4 -15,-18 -25,-40 -37,-65 -48,-82 -57,-94 -62,-99 -65,-105 -72,-119
149 165,6 0,0 3,2 12,2 29,2 55,3 81,5 101,7 116,7 136,6 153,5
150 -34,-167 0,0 4,-25 4,-57 -6,-86 -14,-117 -16,-138 -25,-150 -32,-153 -36,-160 -38,-170
151 159,125 0,0 3,4 18,10 48,23 95,45 118,64 125,75 130,86 140,102 151,115
152 30,66 0,0 -1,-3 0,-2 4,1 7,7 12,19 18,35 23,48 28,57 29,62
153 158,-43 0,0 4,-1 9,-5 23,-13 52,-25 90,-36 114,-40 124,-42 128,-43 137,-43
154 -144,-54 0,0 -13,1 -29,-5 -51,-15 -76,-25 -100,-35 -125,-46 -141,-50 -146,-52 -144,-54
155 -125,8 0,0 -13,-2 -34,-4 -46,-4 -58,-2 -72,2 -88,6 -97,8 -104,8 -111,8
156 95,-154 0,0 3,-18 19,-58 42,-104 64,-137 65,-144 70,-149 75,-153 81,-155 89,-155
157 124,33 0,0 8,0 21,-1 43,-1 73,1 103,7 125,12 129,15 129,20 130,24
158 65,-65 0,0 3,-3 8,-11 17,-23 30,-36 43,-48 52,-54 55,-56 57,-58 60,-62
159 140,161 0,0 9,1 27,11 54,38 94,70 103,82 108,101 117,123 129,139 136,152
160 -54,-99 0,0 -2,-10 -12,-34 -32,-62 -49,-84 -57,-95 -59,-101 -59,-106 -59,-108 -57,-104
161 4,26 0,0 0,2 0,4 1,6 1,9 2,12 3,15 3,18 4,20 4,22
162 -131,-98 0,0 -12,-9 -30,-24 -58,-49 -80,-65 -92,-72 -103,-79 -108,-82 -114,-85 -120,-90
163 -4,-21 0,0 -1,-3 -2,-6 -3,-9 -5,-12 -5,-14 -5,-16 -5,-18 -5,-20 -4,-21
164 64,-18 0,0 5,-2 12,-6 24,-12 42,-17 64,-22 78,-25 78,-25 75,-23 71,-21
165 -33,141 0,0 2,9 -2,27 -13,57 -20,80 -23,100 -24,121 -28,138 -32,147 -33,141
166 -25,56 0,0 -1,4 -2,7 -2,10 -3,14 -4,20 -7,26 -12,36 -17,44 -20,50
167 -5,157 0,0 -1,17 0,53 4,86 6,111 7,119 4,127 3,136 1,142 -2,151
168 51,-82 0,0 6,-12 19,-31 30,-50 38,-66 41,-77 43,-84 47,-95 51,-98 51,-88
169 -133,-57 0,0 -7,-4 -22,-13 -45,-23 -77,-31 -103,-37 -109,-40 -114,-42 -119,-44 -123,-47
170 -111,-14 0,0 -6,0 -17,-4 -32,-10 -52,-15 -68,-16 -92,-17 -96,-16 -99,-15 -103,-14
171 138,32 0,0 10,0 41,1 86,5 128,13 146,16 150,19 153,23 151,27 147,29
172 95,-156 0,0 4,-8 14,-24 31,-49 48,-74 59,-94 65,-112 69,-123 78,-138 88,-149
173 88,48 0,0 13,1 35,6 59,15 71,23 72,27 75,34 80,40 83,45 85,48
174 -87,0 0,0 -5,-2 -14,-4 -27,-6 -41,-6 -59,-5 -75,-3 -87,-3 -94,-1 -95,0
175 89,187 0,0 9,15 23,45 42,89 53,121 55,139 61,153 72,165 78,171 83,177
176 123,-170 0,0 5,-8 13,-24 29,-52 55,-87 85,-121 106,-143 117,-153 118,-157 118,-162
177 78,-63 0,0 0,2 4,-3 9,-7 13,-11 18,-15 25,-20 36,-28 51,-40 65,-52
178 36,87 0,0 3,5 6,18 10,36 18,56 25,69 27,75 27,80 29,84 33,85
179 148,83 0,0 22,10 60,33 72,44 82,53 103,65 116,72 127,79 132,83 140,83
180 -63,-12 0,0 -3,-2 -6,-5 -12,-8 -23,-12 -37,-15 -49,-17 -57,-18 -59,-16 -61,-14
181 118,27 0,0 7,5 30,14 47,18 69,19 88,20 97,21 101,22 108,24 115,25
182 -113,-49 0,0 -4,-4 -16,-12 -32,-21 -50,-27 -63,-30 -70,-31 -74,-32 -80,-34 -90,-38
183 -38,189 0,0 0,28 -1,59 -4,89 -8,115 -11,139 -14,156 -22,178 -28,182 -33,185
184 -93,-99 0,0 -4,-6 -15,-15 -38,-32 -56,-50 -63,-61 -67,-66 -72,-72 -83,-83 -90,-95
185 114,33 0,0 2,2 8,2 27,2 58,6 93,12 117,19 130,25 127,27 123,29
186 140,110 0,0 2,4 24,13 57,27 86,42 102,56 111,67 115,73 124,81 136,94
187 -3,-140 0,0 0,-18 -12,-52 -20,-93 -12,-136 -8,-154 -8,-160 -5,-163 -5,-157 -4,-149
188 -119,-97 0,0 -14,-14 -61,-51 -80,-60 -89,-65 -94,-68 -105,-76 -116,-85 -120,-89 -121,-94
189 36,101 0,0 2,2 4,5 9,13 16,28 26,48 33,67 37,83 38,90 38,94
190 151,96 0,0 8,5 20,10 53,26 87,44 97,51 105,60 123,72 141,78 144,85
191 148,190 0,0 21,17 46,39 82,76 120,114 128,136 131,154 137,168 141,177 143,185
192 25,120 0,0 7,10 19,22 26,34 31,54 31,71 31,82 30,92 30,99 28,104
193 161,161 0,0 5,2 33,19 75,48 110,75 133,96 145,116 153,135 157,150 161,158
194 -95,46 0,0 -7,0 -22,2 -47,8 -65,15 -74,21 -86,28 -88,31 -90,37 -93,42
195 -190,38 0,0 -4,0 -17,0 -45,5 -84,14 -121,22 -152,29 -174,36 -186,38 -190,38
196 -120,61 0,0 -10,6 -24,17 -35,28 -49,37 -65,43 -77,44 -90,46 -101,50 -107,54
197 102,33 0,0 11,-1 38,-3 63,-3 84,3 98,9 102,12 102,16 102,21 102,28
198 -26,-38 0,0 -2,-5 -6,-11 -10,-16 -12,-19 -14,-21 -16,-25 -20,-28 -23,-31 -24,-35
199 -59,102 0,0 -8,17 -16,39 -19,56 -26,72 -31,79 -38,87 -44,92 -50,96 -59,102
200 84,84 0,0 -2,2 6,6 24,17 45,28 69,45 79,58 83,67 84,71 84,75
201 23,126 0,0 2,2 5,5 10,11 16,20 22,30 27,52 27,74 26,96 25,108
202 -17,-63 0,0 0,-10 -1,-20 -4,-30 -9,-38 -13,-45 -15,-51 -15,-55 -15,-58 -16,-60
203 55,90 0,0 0,5 3,9 7,15 13,22 21,30 29,41 37,53 44,64 50,74
204 -8,-89 0,0 2,-5 3,-13 4,-25 2,-39 0,-53 -3,-65 -4,-73 -5,-77 -6,-80
205 -73,-74 0,0 -6,-8 -18,-22 -36,-33 -50,-42 -58,-48 -62,-52 -63,-58 -66,-63 -69,-67
206 93,-93 0,0 7,-8 19,-22 34,-41 47,-57 58,-69 67,-79 78,-86 89,-92 93,-93
207 53,191 0,0 -2,7 3,25 14,64 32,105 46,134 51,152 51,167 52,176 53,184
208 -42,10 0,0 -4,1 -7,3 -11,5 -16,6 -20,7 -25,8 -30,8 -34,9 -38,9
209 117,-79 0,0 7,-3 26,-22 51,-46 80,-67 102,-75 112,-77 123,-81 125,-82 120,-81
210 40,43 0,0 3,6 10,16 15,22 20,28 23,31 26,33 28,36 30,40 33,41
211 93,69 0,0 9,0 25,2 41,12 53,30 61,39 65,42 71,48 79,57 85,64
212 80,117 0,0 -3,1 2,4 13,19 25,42 36,62 51,76 61,84 67,93 72,103
213 -201,45 0,0 -8,2 -33,1 -72,6 -108,9 -115,12 -127,20 -141,27 -150,31 -169,38
214 142,135 0,0 7,2 31,18 57,45 88,68 103,84 119,96 129,103 136,112 139,118
215 -115,-8 0,0 -3,2 -8,0 -20,-2 -38,-4 -58,-7 -75,-10 -89,-12 -103,-12 -108,-11
216 154,-100 0,0 4,0 15,-9 34,-25 61,-44 84,-62 112,-84 140,-100 146,-100 150,-100
217 108,130 0,0 5,2 18,13 37,35 54,62 68,86 81,103 89,115 96,122 102,127
218 -156,56 0,0 -7,2 -29,6 -70,14 -98,21 -121,29 -137,38 -142,42 -146,45 -149,48
219 144,114 0,0 8,5 31,16 83,50 117,66 135,76 139,81 141,85 142,101 144,114
220 120,22 0,0 2,2 5,2 8,2 20,3 32,5 45,8 59,12 76,15 94,18
221 165,171 0,0 10,9 40,24 76,55 118,94 155,126 172,140 174,146 177,156 178,164
222 -174,173 0,0 -9,16 -35,49 -58,79 -86,106 -120,132 -141,148 -154,158 -160,163 -165,168
223 -99,-45 0,0 -10,-9 -28,-21 -48,-29 -68,-33 -78,-35 -83,-37 -86,-39 -90,-42 -95,-43
224 69,-22 0,0 1,1 2,0 6,-2 11,-4 15,-7 21,-9 27,-12 34,-14 42,-16
225 -60,-33 0,0 -2,-1 -5,-2 -8,-4 -12,-7 -17,-11 -23,-14 -29,-19 -36,-23 -42,-26
226 -46,21 0,0 -3,1 -8,1 -12,2 -20,4 -26,7 -30,9 -35,13 -40,17 -46,21
227 48,-29 0,0 1,-1 3,-3 7,-6 14,-12 22,-18 30,-23 37,-26 42,-28 46,-29
228 -88,-111 0,0 -14,-22 -42,-51 -57,-71 -59,-78 -60,-87 -63,-91 -65,-100 -75,-104 -82,-108
229 -145,73 0,0 -12,5 -28,13 -54,27 -78,51 -95,67 -108,74 -116,76 -128,76 -140,76
230 -57,-95 0,0 0,-9 -8,-26 -20,-49 -27,-68 -33,-75 -38,-79 -38,-85 -43,-88 -48,-92
231 -134,-6 0,0 -5,-2 -20,-7 -49,-10 -75,-10 -88,-9 -102,-4 -110,-1 -117,1 -125,2
232 -22,53 0,0 0,5 -2,11 -3,16 -4,20 -5,25 -7,29 -9,33 -11,37 -13,43
233 -100,-133 0,0 -4,-20 -19,-45 -42,-74 -54,-96 -54,-115 -54,-123 -55,-130 -62,-132 -76,-133
234 173,82 0,0 15,4 47,13 83,29 124,45 141,58 156,66 166,71 170,76 173,82
235 6,77 0,0 -1,4 -1,7 0,9 1,16 1,28 1,43 2,56 4,65 5,71
236 -97,205 0,0 -4,23 -32,60 -64,98 -78,121 -83,136 -84,150 -84,168 -90,189 -94,198
237 -88,163 0,0 -8,3 -18,15 -24,21 -36,36 -55,72 -70,99 -78,128 -82,147 -86,157
238 -143,171 0,0 -13,12 -53,41 -103,76 -124,96 -134,115 -138,127 -143,137 -144,146 -144,159
239 -12,177 0,0 0,6 0,16 0,43 -7,86 -9,124 -10,146 -13,158 -13,165 -13,172
240 40,-83 0,0 7,-10 11,-18 17,-32 22,-43 26,-53 30,-62 34,-69 37,-74 38,-76
241 -188,-37 0,0 -10,-4 -25,-12 -43,-22 -66,-27 -95,-27 -122,-27 -145,-28 -168,-30 -183,-34
242 -118,32 0,0 -4,0 -12,0 -24,2 -42,4 -62,9 -82,15 -96,21 -107,26 -112,29
243 201,28 0,0 25,-6 84,-9 126,-3 141,-1 152,-1 163,2 181,6 197,14 201,21
244 84,21 0,0 6,0 16,0 30,1 44,2 58,4 70,7 79,11 81,14 82,16
245 -5,68 0,0 -1,3 -1,8 -3,16 -5,24 -5,30 -5,36 -5,42 -5,49 -5,57
246 -89,43 0,0 -7,1 -21,6 -34,11 -50,16 -63,19 -69,22 -75,28 -81,34 -84,37
247 94,50 0,0 5,0 13,0 23,2 35,8 49,18 67,30 81,39 90,45 93,47
248 -171,-163 0,0 -6,-29 -33,-68 -78,-116 -107,-140 -114,-146 -121,-148 -136,-151 -156,-157 -164,-160
249 163,-116 0,0 7,-6 20,-20 38,-38 62,-58 93,-81 118,-99 128,-102 142,-106 157,-112
250 -56,-44 0,0 -2,-3 -6,-8 -13,-14 -27,-23 -37,-30 -45,-36 -50,-40 -53,-41 -55,-42
251 -156,183 0,0 -16,28 -56,81 -104,127 -124,149 -132,160 -139,169 -144,175 -151,180 -156,183
252 179,93 0,0 5,2 19,7 42,16 72,29 108,45 144,61 165,74 171,79 174,84
253 42,-11 0,0 -1,-1 1,-2 3,-4 6,-7 9,-10 12,-12 15,-14 32,-15 39,-13
254 -101,24 0,0 -4,0 -12,1 -31,4 -63,12 -89,18 -103,21 -110,23 -112,25 -108,25
255 -64,-53 0,0 -2,-7 -11,-19 -21,-27 -29,-32 -38,-39 -46,-45 -51,-48 -57,-50 -60,-52
256 -124,137 0,0 -3,9 -7,20 -12,32 -34,63 -73,97 -94,109 -102,114 -112,123 -119,128
257 78,-183 0,0 4,-7 14,-28 31,-60 40,-87 44,-106 50,-125 56,-141 67,-159 76,-172
258 -132,23 0,0 -9,2 -32,6 -59,11 -81,13 -99,16 -115,18 -121,20 -124,21 -128,23
259 180,27 0,0 19,-2 62,-3 110,3 144,5 154,6 162,9 170,13 175,17 178,22
260 81,-6 0,0 10,-7 26,-11 43,-12 55,-11 60,-9 64,-8 68,-8 74,-8 78,-8
261 -130,-125 0,0 -4,-7 -13,-22 -34,-42 -53,-60 -63,-69 -86,-87 -114,-107 -124,-114 -127,-118
262 -190,179 0,0 -43,27 -113,73 -148,103 -152,115 -159,128 -172,151 -179,161 -184,165 -187,173
263 27,96 0,0 2,6 6,14 12,26 20,44 25,61 27,70 27,77 27,85 27,91
264 58,-10 0,0 3,-1 6,-3 10,-5 14,-6 21,-8 29,-9 38,-10 47,-10 54,-10
265 155,-25 0,0 1,3 7,3 23,-3 50,-12 92,-21 120,-24 140,-26 148,-26 152,-26
266 -67,-82 0,0 -6,-6 -15,-18 -24,-27 -30,-35 -37,-44 -45,-51 -54,-56 -61,-66 -65,-76
267 101,162 0,0 -3,7 7,22 35,40 68,68 92,103 102,122 105,133 107,141 105,152
268 95,99 0,0 2,4 6,8 13,14 22,24 34,38 48,54 61,69 75,82 86,90
269 -9,-155 0,0 0,-10 -3,-31 -9,-60 -13,-85 -13,-104 -10,-123 -10,-139 -9,-150 -9,-155
270 10,158 0,0 -1,6 -1,18 -1,37 0,63 3,91 7,114 10,130 11,139 10,150
271 -10,-207 0,0 -1,-11 -2,-41 -1,-82 -1,-122 -3,-152 -7,-171 -10,-181 -9,-191 -8,-200
272 59,218 0,0 9,31 29,79 63,144 85,175 88,185 86,195 84,206 80,217 72,219
273 35,-93 0,0 5,-7 13,-22 20,-37 24,-47 28,-59 31,-74 31,-84 32,-87 34,-89
274 -28,43 0,0 -2,-1 -4,0 -6,5 -9,12 -14,21 -18,29 -22,35 -25,39 -28,43
275 -173,43 0,0 -10,0 -54,7 -111,16 -143,23 -144,29 -147,35 -153,37 -159,39 -166,40
276 17,-149 0,0 -1,0 1,-10 5,-25 10,-44 14,-60 17,-76 20,-95 22,-116 24,-136
277 41,92 0,0 2,6 7,14 14,26 21,36 26,50 31,64 34,75 36,82 38,85
278 -81,72 0,0 -10,6 -25,18 -36,29 -48,41 -62,52 -70,59 -74,64 -78,68 -81,72
279 -49,-121 0,0 -2,-16 -9,-42 -14,-80 -19,-112 -21,-123 -25,-126 -31,-127 -37,-125 -43,-122
280 153,-39 0,0 8,-5 19,-9 39,-14 67,-20 95,-25 117,-27 136,-28 147,-30 150,-32
281 -169,-118 0,0 -15,-8 -64,-36 -129,-68 -152,-77 -159,-82 -175,-95 -183,-106 -184,-115 -177,-118
282 118,139 0,0 7,9 30,31 50,60 68,85 80,102 90,119 105,135 116,145 118,148
283 50,-113 0,0 7,-15 15,-33 20,-53 25,-76 33,-99 39,-116 40,-119 43,-121 47,-118
284 -114,157 0,0 -2,9 -17,36 -55,84 -81,109 -93,119 -101,131 -103,143 -105,150 -109,155
285 -160,-25 0,0 -6,2 -13,4 -21,3 -41,-5 -75,-17 -103,-24 -117,-25 -127,-24 -144,-24
286 -19,-121 0,0 1,-21 3,-53 -1,-72 -3,-84 -5,-90 -8,-99 -11,-108 -15,-112 -17,-117
287 -121,-73 0,0 -6,-3 -18,-10 -36,-21 -59,-32 -80,-45 -98,-57 -110,-64 -118,-68 -120,-70
288 -61,105 0,0 -6,9 -16,27 -28,47 -42,69 -48,83 -50,88 -53,92 -55,98 -58,102
289 42,48 0,0 4,4 7,10 12,16 18,23 24,28 31,33 37,38 40,42 42,45
290 -182,69 0,0 -6,8 -44,16 -99,21 -144,30 -154,36 -157,43 -160,48 -168,55 -178,63
291 -15,103 0,0 -2,11 -5,32 -6,49 -7,63 -8,70 -8,78 -9,87 -11,95 -13,99
292 41,-102 0,0 2,-7 9,-21 18,-38 27,-55 33,-71 37,-82 39,-88 40,-93 40,-96
293 82,-168 0,0 -1,-5 4,-19 12,-39 19,-63 30,-93 51,-130 70,-154 77,-160 79,-164
294 135,-169 0,0 5,1 18,-9 41,-36 66,-69 85,-94 101,-116 119,-140 130,-154 131,-160
295 -157,64 0,0 -3,3 -11,8 -23,15 -44,23 -68,32 -92,40 -112,47 -128,51 -141,56
296 171,58 0,0 10,0 57,5 104,20 133,34 141,41 148,46 160,52 165,56 171,58
297 -148,208 0,0 -8,8 -17,16 -37,53 -69,120 -98,176 -119,209 -131,221 -140,225 -145,218
298 -160,9 0,0 -10,-1 -27,-2 -50,-4 -72,-5 -92,-3 -114,1 -129,5 -139,6 -149,8
299 24,31 0,0 3,1 6,3 9,7 12,11 14,15 16,18 17,20 18,23 20,26
300 197,-170 0,0 23,-17 92,-65 167,-123 192,-145 226,-179 224,-183 215,-178 203,-173 197,-170
301 -44,50 0,0 -1,-2 8,-6 12,-10 0,-10 -17,-3 -23,13 -32,30 -40,37 -43,43
302 31,173 0,0 26,14 36,50 36,85 36,103 36,120 41,133 41,143 37,156 35,165
303 -29,-41 0,0 -2,-4 -3,-6 -5,-10 -9,-17 -13,-22 -17,-28 -21,-32 -25,-36 -27,-39
304 -89,-115 0,0 -3,-12 -12,-30 -27,-54 -41,-70 -50,-78 -61,-95 -65,-102 -69,-105 -74,-109
305 126,-157 0,0 5,-12 18,-30 37,-54 55,-78 70,-97 81,-109 91,-120 109,-138 121,-148
306 -39,-192 0,0 0,-15 -7,-62 -16,-92 -17,-111 -17,-124 -19,-134 -24,-145 -29,-161 -33,-176
307 -200,58 0,0 -14,3 -61,20 -120,35 -135,41 -145,43 -159,47 -172,50 -181,53 -190,55
308 29,20 0,0 1,0 4,1 7,3 10,5 13,8 16,10 19,13 22,15 25,18
309 18,130 0,0 6,13 11,34 21,63 26,89 26,103 26,111 24,118 21,124 18,130
310 100,-78 0,0 4,-4 14,-15 27,-27 44,-40 57,-49 65,-54 70,-59 80,-67 93,-75
311 -97,-142 0,0 -19,-38 -50,-65 -59,-75 -64,-82 -71,-91 -74,-101 -78,-110 -81,-118 -90,-131
312 155,171 0,0 10,17 38,38 68,58 87,83 106,106 119,121 136,138 146,154 152,163
313 -136,78 0,0 -8,9 -28,26 -74,50 -119,67 -147,70 -167,74 -172,77 -152,77 -136,78
314 10,-165 0,0 -3,-8 -8,-29 -9,-60 -2,-90 5,-115 7,-127 8,-138 7,-147 8,-157
315 67,-140 0,0 4,-11 9,-27 21,-51 34,-72 41,-86 45,-96 51,-109 58,-124 65,-135
316 -156,-120 0,0 -7,-7 -21,-31 -86,-89 -109,-113 -114,-116 -122,-114 -133,-113 -140,-114 -148,-117
317 61,76 0,0 7,4 16,10 26,26 39,47 51,62 55,69 58,78 64,88 66,88
318 116,148 0,0 7,7 21,25 40,60 61,94 78,116 89,128 96,136 104,140 109,143
319 86,-195 0,0 3,-11 14,-43 32,-82 39,-112 54,-160 60,-179 66,-181 74,-181 79,-185
320 -57,-164 0,0 5,-35 1,-92 -13,-123 -23,-132 -28,-151 -32,-176 -37,-181 -44,-182 -50,-180
321 -76,190 0,0 -1,14 -7,46 -29,106 -51,144 -55,156 -55,168 -56,178 -59,184 -67,188
322 -50,162 0,0 -2,16 -7,36 -17,65 -24,95 -30,112 -34,119 -36,127 -42,142 -49,154
323 151,65 0,0 3,4 7,9 18,9 44,9 77,19 113,33 134,43 136,48 145,59
324 181,-139 0,0 10,-17 48,-64 92,-111 127,-134 151,-148 172,-158 181,-160 183,-154 182,-146
325 130,-120 0,0 8,-9 24,-25 52,-52 77,-78 92,-93 101,-100 117,-112 124,-117 130,-120
326 -16,114 0,0 -3,4 -7,23 -10,44 -10,62 -11,76 -14,90 -16,98 -16,104 -16,109
327 -195,91 0,0 -9,3 -33,13 -97,29 -169,43 -188,50 -187,57 -189,65 -192,72 -195,82
328 97,31 0,0 4,1 9,1 15,2 23,4 36,9 53,15 70,21 86,27 95,29
329 -126,31 0,0 -3,0 -7,0 -13,1 -24,3 -35,6 -48,11 -62,15 -76,18 -90,22
330 -144,-117 0,0 -10,-6 -29,-18 -56,-36 -80,-59 -104,-83 -122,-99 -128,-107 -130,-110 -134,-113
331 2,-191 0,0 1,-23 1,-71 -1,-117 -1,-138 -4,-149 -8,-160 -8,-167 -7,-175 -6,-181
332 129,-74 0,0 6,-3 18,-13 38,-27 58,-41 80,-55 112,-71 134,-81 138,-84 134,-77
333 -63,90 0,0 -2,4 -7,12 -18,28 -30,44 -44,59 -55,71 -56,76 -57,81 -60,86
334 149,20 0,0 13,-4 48,-6 83,-5 111,-5 117,-2 123,2 126,7 130,10 139,16
335 -72,141 0,0 -3,11 -18,41 -40,74 -56,91 -61,97 -65,109 -65,116 -67,124 -70,135
336 19,-133 0,0 5,-13 18,-40 27,-68 28,-85 28,-94 25,-102 23,-109 21,-118 19,-127
337 184,-66 0,0 14,-7 38,-21 76,-34 104,-41 126,-47 139,-51 145,-53 150,-55 156,-57
338 128,-202 0,0 17,-11 49,-34 75,-61 87,-94 90,-124 106,-159 112,-168 118,-180 126,-195
339 -79,-143 0,0 -1,-2 -1,-30 -4,-51 -11,-68 -20,-89 -30,-106 -40,-115 -51,-121 -60,-128
340 -30,-132 0,0 2,0 2,-16 -3,-41 -12,-67 -20,-87 -24,-102 -27,-110 -29,-115 -30,-124
341 -60,13 0,0 -1,0 -3,-2 -6,-4 -10,-5 -14,-5 -17,-4 -22,-1 -31,3 -42,7
342 160,153 0,0 9,7 52,35 119,80 144,114 140,121 143,128 148,137 151,144 155,148
343 -109,-47 0,0 -8,-6 -28,-21 -57,-37 -76,-47 -85,-52 -91,-52 -96,-51 -101,-51 -106,-51
344 59,113 0,0 0,3 3,11 15,37 23,51 33,64 42,79 51,92 55,101 57,106
345 -128,23 0,0 -13,0 -47,-2 -70,-3 -80,-2 -84,1 -90,5 -102,11 -110,16 -116,18
346 50,108 0,0 5,11 14,33 25,53 34,68 39,79 40,86 42,91 45,94 48,99
347 123,-61 0,0 16,-4 43,-22 73,-39 94,-48 108,-51 113,-52 116,-54 119,-57 123,-61
348 23,63 0,0 2,5 6,13 11,19 16,25 20,32 22,39 23,47 23,55 23,60
349 -59,-167 0,0 -6,-14 -27,-56 -45,-98 -50,-121 -46,-135 -43,-156 -47,-164 -52,-171 -56,-172
350 -28,-53 0,0 -3,-1 -6,-6 -11,-14 -16,-22 -19,-31 -22,-39 -24,-44 -26,-47 -27,-50
351 197,-85 0,0 8,-9 26,-23 58,-41 91,-55 120,-62 147,-67 156,-67 165,-73 180,-79
352 -148,-23 0,0 -6,-1 -29,-6 -66,-13 -96,-20 -115,-22 -123,-22 -129,-22 -136,-22 -144,-22
353 -102,-162 0,0 -10,-10 -30,-44 -47,-81 -54,-106 -67,-125 -79,-141 -86,-146 -91,-151 -97,-156
354 198,157 0,0 13,10 63,39 133,80 162,104 163,119 177,135 186,142 191,148 198,152
355 -91,177 0,0 2,6 3,19 -9,50 -23,89 -41,116 -60,134 -73,146 -82,159 -88,170
356 40,-163 0,0 0,-11 3,-35 8,-63 13,-96 20,-122 28,-142 34,-153 35,-158 37,-162
357 25,-149 0,0 -2,-3 0,-16 8,-49 12,-80 11,-102 10,-116 14,-127 16,-136 21,-144
358 62,22 0,0 3,0 7,0 11,1 18,3 30,8 42,13 51,18 56,23 58,26
359 -158,-111 0,0 -4,-3 -10,-12 -29,-31 -60,-57 -99,-84 -130,-104 -145,-112 -150,-113 -154,-111
360 -22,53 0,0 0,4 -1,9 -2,13 -5,18 -7,26 -10,35 -12,40 -15,45 -19,50
361 -119,188 0,0 -3,3 -12,13 -35,34 -68,66 -87,97 -100,130 -109,155 -112,169 -115,177
362 105,102 0,0 -1,6 2,9 15,15 41,29 68,49 82,70 91,88 96,96 99,98
363 174,-106 0,0 22,-19 62,-46 102,-66 140,-96 179,-129 194,-137 193,-128 187,-117 181,-112
364 -64,124 0,0 -1,3 -3,7 -7,15 -15,33 -28,59 -39,81 -48,97 -55,106 -60,111
365 -27,63 0,0 0,3 -3,8 -6,14 -11,22 -15,32 -19,45 -22,51 -23,54 -24,56
366 -67,-158 0,0 -4,-14 -14,-46 -19,-82 -22,-101 -29,-120 -31,-146 -36,-149 -42,-150 -49,-151
367 126,-113 0,0 24,-32 57,-67 78,-87 90,-100 104,-116 120,-127 127,-129 130,-124 127,-119
368 32,-34 0,0 0,0 3,-2 7,-7 12,-12 17,-16 22,-21 25,-24 28,-26 28,-28
369 111,9 0,0 4,0 11,-2 19,-2 28,-3 43,-3 59,-2 74,-1 85,2 96,4
370 166,183 0,0 15,12 49,39 73,67 89,108 114,143 123,160 130,171 137,173 145,175
371 149,7 0,0 2,-1 10,-1 23,-1 43,2 66,6 82,8 100,8 125,7 142,7
372 -49,-28 0,0 -3,-1 -10,-6 -21,-15 -27,-20 -30,-22 -33,-23 -37,-23 -41,-24 -44,-25
373 173,-21 0,0 3,-1 10,-4 47,-10 70,-10 91,-10 111,-11 124,-11 130,-12 137,-16
374 -75,130 0,0 1,5 1,11 -1,23 -6,39 -17,68 -39,99 -62,120 -70,125 -73,127
375 -153,46 0,0 -4,0 -11,0 -24,3 -44,9 -69,17 -92,26 -115,34 -135,42 -148,46
376 -119,6 0,0 -4,0 -10,1 -19,1 -30,2 -47,3 -65,4 -83,4 -101,5 -119,6
377 131,-9 0,0 8,-1 21,-5 46,-10 75,-13 106,-14 129,-13 139,-9 142,-7 136,-8
378 -82,-134 0,0 -3,-32 -21,-74 -40,-101 -49,-109 -54,-120 -57,-134 -60,-141 -65,-144 -75,-141
379 -141,51 0,0 -6,6 -24,15 -57,29 -84,37 -99,43 -106,48 -114,52 -125,53 -131,53
380 189,-189 0,0 9,-15 27,-41 55,-74 96,-115 132,-144 161,-167 176,-179 179,-181 183,-184

20
data/mouse_data_test.csv Normal file
View File

@@ -0,0 +1,20 @@
"-122,-17","0,0","-3,-3","-9,-7","-19,-11","-32,-16","-51,-19","-68,-20","-84,-20","-102,-19","-114,-17"
"-115,-153","0,0","-10,-13","-34,-62","-68,-105","-74,-128","-78,-139","-82,-147","-89,-152","-98,-155","-107,-156"
"-77,193","0,0","-2,9","-12,31","-26,68","-41,106","-54,136","-63,156","-67,170","-71,178","-75,183"
"-85,-86","0,0","-5,-5","-20,-19","-47,-36","-69,-50","-80,-57","-85,-61","-85,-66","-85,-71","-85,-77"
"29,-175","0,0","9,-10","25,-51","32,-90","30,-119","30,-138","30,-146","29,-152","33,-165","36,-175"
"-201,197","0,0","-12,20","-64,77","-113,129","-130,160","-139,175","-152,178","-171,181","-184,183","-191,187"
"-124,-16","0,0","-6,-1","-20,-4","-42,-8","-67,-13","-90,-16","-109,-19","-116,-21","-119,-20","-122,-18"
"-123,53","0,0","-16,7","-47,17","-73,22","-92,24","-106,29","-109,32","-112,38","-115,45","-118,49"
"-146,-125","0,0","0,-12","-12,-40","-33,-58","-64,-84","-95,-115","-116,-123","-126,-125","-134,-122","-141,-122"
"132,49","0,0","9,5","32,11","56,18","81,30","95,36","102,39","108,41","114,44","123,48"
"157,164","0,0","9,10","36,35","72,68","107,97","132,121","146,137","149,145","151,152","154,158"
"-52,48","0,0","-5,5","-14,11","-20,18","-26,24","-29,30","-30,36","-31,41","-32,47","-40,48"
"-135,-106","0,0","-13,-15","-34,-37","-56,-61","-75,-78","-86,-90","-93,-98","-99,-104","-104,-105","-116,-107"
"197,99","0,0","3,6","23,21","62,33","111,48","157,63","177,74","181,82","184,86","192,91"
"129,-194","0,0","7,-7","19,-26","45,-55","77,-85","104,-117","117,-139","118,-149","119,-162","123,-180"
"167,-56","0,0","14,-6","42,-15","79,-23","115,-31","138,-39","149,-45","152,-48","155,-50","159,-51"
"132,143","0,0","28,4","84,24","113,57","116,77","118,88","120,98","120,107","122,124","126,135"
"-113,89","0,0","-11,8","-38,23","-71,41","-81,48","-86,56","-89,62","-92,67","-97,73","-103,79"
"-46,-8","0,0","-2,1","-4,1","-7,1","-11,0","-16,-2","-24,-4","-30,-5","-35,-7","-39,-8"
"-40,181","0,0","0,37","-11,92","-12,120","-12,133","-12,148","-14,168","-21,178","-25,184","-32,184"
1 -122,-17 0,0 -3,-3 -9,-7 -19,-11 -32,-16 -51,-19 -68,-20 -84,-20 -102,-19 -114,-17
2 -115,-153 0,0 -10,-13 -34,-62 -68,-105 -74,-128 -78,-139 -82,-147 -89,-152 -98,-155 -107,-156
3 -77,193 0,0 -2,9 -12,31 -26,68 -41,106 -54,136 -63,156 -67,170 -71,178 -75,183
4 -85,-86 0,0 -5,-5 -20,-19 -47,-36 -69,-50 -80,-57 -85,-61 -85,-66 -85,-71 -85,-77
5 29,-175 0,0 9,-10 25,-51 32,-90 30,-119 30,-138 30,-146 29,-152 33,-165 36,-175
6 -201,197 0,0 -12,20 -64,77 -113,129 -130,160 -139,175 -152,178 -171,181 -184,183 -191,187
7 -124,-16 0,0 -6,-1 -20,-4 -42,-8 -67,-13 -90,-16 -109,-19 -116,-21 -119,-20 -122,-18
8 -123,53 0,0 -16,7 -47,17 -73,22 -92,24 -106,29 -109,32 -112,38 -115,45 -118,49
9 -146,-125 0,0 0,-12 -12,-40 -33,-58 -64,-84 -95,-115 -116,-123 -126,-125 -134,-122 -141,-122
10 132,49 0,0 9,5 32,11 56,18 81,30 95,36 102,39 108,41 114,44 123,48
11 157,164 0,0 9,10 36,35 72,68 107,97 132,121 146,137 149,145 151,152 154,158
12 -52,48 0,0 -5,5 -14,11 -20,18 -26,24 -29,30 -30,36 -31,41 -32,47 -40,48
13 -135,-106 0,0 -13,-15 -34,-37 -56,-61 -75,-78 -86,-90 -93,-98 -99,-104 -104,-105 -116,-107
14 197,99 0,0 3,6 23,21 62,33 111,48 157,63 177,74 181,82 184,86 192,91
15 129,-194 0,0 7,-7 19,-26 45,-55 77,-85 104,-117 117,-139 118,-149 119,-162 123,-180
16 167,-56 0,0 14,-6 42,-15 79,-23 115,-31 138,-39 149,-45 152,-48 155,-50 159,-51
17 132,143 0,0 28,4 84,24 113,57 116,77 118,88 120,98 120,107 122,124 126,135
18 -113,89 0,0 -11,8 -38,23 -71,41 -81,48 -86,56 -89,62 -92,67 -97,73 -103,79
19 -46,-8 0,0 -2,1 -4,1 -7,1 -11,0 -16,-2 -24,-4 -30,-5 -35,-7 -39,-8
20 -40,181 0,0 0,37 -11,92 -12,120 -12,133 -12,148 -14,168 -21,178 -25,184 -32,184

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@

51
pyproject.toml Normal file
View File

@@ -0,0 +1,51 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mouse-control"
version = "0.1.0"
description = "Neural-network mouse trajectory generation that mimics human motion."
readme = "README.md"
requires-python = ">=3.12"
keywords = ["mouse", "trajectory", "neural-network", "onnx", "anti-detection"]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Operating System :: Microsoft :: Windows",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
"matplotlib>=3.10",
"numpy>=2.0",
"onnxruntime>=1.20",
"onnxscript>=0.7",
"pandas>=2.0",
"scipy>=1.14",
"torch>=2.5",
]
[project.scripts]
mouse-collect = "mouse_control.collect:main"
mouse-train = "mouse_control.train:main"
mouse-visualize = "mouse_control.visualize:main"
[dependency-groups]
dev = ["pytest>=8.0"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra"
[tool.hatch.build.targets.wheel]
packages = ["src/mouse_control"]
artifacts = ["src/mouse_control/assets/*.onnx"]
[tool.hatch.build.targets.sdist]
include = [
"src/",
"data/",
"imgs/",
"README.md",
"pyproject.toml",
]

25
show.py
View File

@@ -1,25 +0,0 @@
import matplotlib.pyplot as plt
import torch
# 您的tensor数据
points = torch.tensor([
[ 0.4762, 0.9082],
[ -31.5831, -39.9315],
[ -52.1844, -71.0071],
[ -60.4211, -83.4873],
[ -69.3365, -100.6035],
[ -80.3101, -110.0435],
[ -86.7765, -120.0472],
[ -93.2574, -127.8565],
[ -98.5992, -135.3300]
])
# 提取x和y坐标
x = points[:, 0].tolist()
y = points[:, 1].tolist()
# 绘制散点图
plt.scatter(x, y)
# 显示图形
plt.show()

View File

@@ -0,0 +1,7 @@
"""Neural-network mouse trajectory generation that mimics human motion."""
from mouse_control.inference import load_model, predict
from mouse_control.model import SimpleNet
__version__ = "0.1.0"
__all__ = ["SimpleNet", "load_model", "predict", "__version__"]

View File

Binary file not shown.

View File

@@ -0,0 +1,133 @@
import argparse
import csv
import random
import tkinter as tk
from pathlib import Path
BG_COLOR = "#2b2b2b"
BALL_RADIUS = 20
TARGET_OFFSET_RANGE = 200
N_KEYPOINTS = 10
class TrajectoryCollector:
def __init__(self, csv_path: Path, n_trajectories: int = 100) -> None:
self.csv_path = Path(csv_path)
self.n_target = n_trajectories
self.root = tk.Tk()
self.root.attributes("-fullscreen", True)
self.root.configure(bg=BG_COLOR)
screen_w = self.root.winfo_screenwidth()
screen_h = self.root.winfo_screenheight()
self.ball1_pos = (screen_w / 2, screen_h / 2)
self.ball2_pos = self._new_target()
self.label = tk.Label(
self.root, text="n: 0", font=("Helvetica", 16),
bg=BG_COLOR, fg="white",
)
self.label.pack()
self.canvas = tk.Canvas(
self.root, width=screen_w, height=screen_h,
bg=BG_COLOR, highlightthickness=0,
)
self.canvas.pack()
self._draw_ball(self.ball1_pos, "red")
self._draw_ball(self.ball2_pos, "blue", tags="ball2")
self.recording = False
self.mouse_path: list[tuple[int, int]] = []
self.n = 0
self.canvas.bind("<Motion>", self._on_motion)
self.canvas.bind("<Button-1>", self._on_click)
self.root.bind("<Key>", self._on_key)
def _new_target(self) -> tuple[float, float]:
cx, cy = self.ball1_pos
return (
cx + random.randint(-TARGET_OFFSET_RANGE, TARGET_OFFSET_RANGE),
cy + random.randint(-TARGET_OFFSET_RANGE, TARGET_OFFSET_RANGE),
)
def _draw_ball(self, pos: tuple[float, float], color: str, tags: str = "") -> None:
x, y = pos
self.canvas.create_oval(
x - BALL_RADIUS, y - BALL_RADIUS,
x + BALL_RADIUS, y + BALL_RADIUS,
fill=color, tags=tags,
)
@staticmethod
def _hit(event: tk.Event, pos: tuple[float, float]) -> bool:
return (
abs(event.x - pos[0]) <= BALL_RADIUS
and abs(event.y - pos[1]) <= BALL_RADIUS
)
def _on_motion(self, event: tk.Event) -> None:
if self.recording:
self.mouse_path.append((event.x, event.y))
def _on_click(self, event: tk.Event) -> None:
if self._hit(event, self.ball1_pos):
self.recording = True
self.mouse_path = [(event.x, event.y)]
elif self._hit(event, self.ball2_pos):
if not self.recording or len(self.mouse_path) < N_KEYPOINTS:
return
self.recording = False
self.canvas.delete("ball2")
self._save_path(self.mouse_path)
self.n += 1
if self.n >= self.n_target:
self.root.destroy()
return
self.label.config(text=f"n: {self.n}")
self.mouse_path = []
self.ball2_pos = self._new_target()
self._draw_ball(self.ball2_pos, "blue", tags="ball2")
def _on_key(self, event: tk.Event) -> None:
if event.keysym == "Escape":
self.root.destroy()
def _save_path(self, path: list[tuple[int, int]]) -> None:
origin_x, origin_y = path[0]
x_rel = [px - origin_x for px, _ in path]
y_rel = [-(py - origin_y) for _, py in path]
step = max(1, len(path) // N_KEYPOINTS)
idx = list(range(0, len(path), step))
kx = [x_rel[i] for i in idx]
ky = [y_rel[i] for i in idx]
with open(self.csv_path, "a", newline="") as f:
writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)
row = [f"{kx[-1]},{ky[-1]}"] + [
f"{kx[i]},{ky[i]}" for i in range(N_KEYPOINTS)
]
writer.writerow(row)
def run(self) -> None:
self.root.mainloop()
def main() -> None:
parser = argparse.ArgumentParser(
description="Collect human mouse trajectories via a fullscreen Tk GUI.",
)
parser.add_argument(
"--output", "-o", type=Path, default=Path("mouse_data.csv"),
help="CSV file to append trajectories to (default: mouse_data.csv).",
)
parser.add_argument(
"--count", "-n", type=int, default=100,
help="Number of trajectories to collect before exiting (default: 100).",
)
args = parser.parse_args()
TrajectoryCollector(args.output, args.count).run()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,28 @@
from importlib.resources import files
from pathlib import Path
import numpy as np
import onnxruntime as ort
def load_model(model_path: str | Path | None = None) -> ort.InferenceSession:
"""Load the ONNX mouse-trajectory model.
Resolution order when ``model_path`` is None:
1. ``./mouse.onnx`` in the current working directory (freshly trained).
2. The model bundled inside the installed package.
"""
if model_path is not None:
return ort.InferenceSession(str(model_path))
cwd_model = Path("mouse.onnx")
if cwd_model.is_file():
return ort.InferenceSession(str(cwd_model))
data = files("mouse_control.assets").joinpath("mouse.onnx").read_bytes()
return ort.InferenceSession(data)
def predict(session: ort.InferenceSession, dx: float, dy: float) -> np.ndarray:
"""Return a (10, 2) array of trajectory points for the given displacement."""
inp = np.array([[[float(dx), float(dy)]]], dtype=np.float32)
input_name = session.get_inputs()[0].name
return session.run(None, {input_name: inp})[0][0]

View File

@@ -0,0 +1,17 @@
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self) -> None:
super().__init__()
self.fc1 = nn.Linear(2, 64)
self.fc2 = nn.Linear(64, 32)
self.fc3 = nn.Linear(32, 20)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = torch.flatten(x, start_dim=1)
x = nn.functional.relu(self.fc1(x))
x = nn.functional.relu(self.fc2(x))
x = self.fc3(x)
return x.view(-1, 10, 2)

113
src/mouse_control/train.py Normal file
View File

@@ -0,0 +1,113 @@
import argparse
from pathlib import Path
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from mouse_control.model import SimpleNet
class TrajectoryDataset(Dataset):
def __init__(self, dx_dy: torch.Tensor, labels: torch.Tensor) -> None:
self.dx_dy = dx_dy
self.labels = labels
def __len__(self) -> int:
return len(self.dx_dy)
def __getitem__(self, idx: int) -> tuple[torch.Tensor, torch.Tensor]:
return self.dx_dy[idx], self.labels[idx]
def _load_csv(csv_path: Path) -> tuple[torch.Tensor, torch.Tensor]:
df = pd.read_csv(csv_path, header=None)
inputs = df.iloc[:, 0].apply(lambda x: list(map(int, x.split(","))))
labels = df.apply(
lambda row: [list(map(int, row[i].split(","))) for i in range(1, 11)],
axis=1,
)
inputs_t = torch.Tensor(inputs.tolist()).unsqueeze(1)
labels_t = torch.Tensor(labels.tolist())
return inputs_t, labels_t
def train(
train_csv: Path,
test_csv: Path,
output: Path,
epochs: int = 1000,
batch_size: int = 64,
lr: float = 1e-3,
) -> None:
train_x, train_y = _load_csv(train_csv)
test_x, test_y = _load_csv(test_csv)
train_loader = DataLoader(
TrajectoryDataset(train_x, train_y), batch_size=batch_size, shuffle=True,
)
test_loader = DataLoader(
TrajectoryDataset(test_x, test_y), batch_size=batch_size, shuffle=False,
)
model = SimpleNet()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.9)
model.train()
for epoch in range(epochs):
epoch_loss = 0.0
for batch_x, batch_y in train_loader:
optimizer.zero_grad()
output_t = model(batch_x)
loss = criterion(output_t, batch_y)
loss.backward()
optimizer.step()
scheduler.step()
epoch_loss += loss.item()
if (epoch + 1) % 50 == 0 or epoch == 0:
print(f"epoch {epoch + 1:>4}/{epochs} train_loss={epoch_loss / len(train_loader):.4f}")
model.eval()
with torch.no_grad():
total = 0.0
last_batch = None
for batch_x, batch_y in test_loader:
pred = model(batch_x)
total += criterion(pred, batch_y).item()
last_batch = (batch_x, pred)
print(f"test_loss={total / len(test_loader):.4f}")
if last_batch is not None:
sample_x, sample_pred = last_batch
print("sample input :", sample_x[0].tolist())
print("sample output:", sample_pred[0].tolist())
dummy = torch.randn(1, 1, 2)
torch.onnx.export(
model, dummy, str(output),
input_names=["input"], output_names=["output"],
external_data=False,
)
print(f"exported -> {output}")
def main() -> None:
parser = argparse.ArgumentParser(description="Train the mouse trajectory model.")
parser.add_argument("--train-csv", type=Path, default=Path("data/mouse_data.csv"))
parser.add_argument("--test-csv", type=Path, default=Path("data/mouse_data_test.csv"))
parser.add_argument("--output", "-o", type=Path, default=Path("mouse.onnx"))
parser.add_argument("--epochs", type=int, default=1000)
parser.add_argument("--batch-size", type=int, default=64)
parser.add_argument("--lr", type=float, default=1e-3)
args = parser.parse_args()
train(
args.train_csv, args.test_csv, args.output,
epochs=args.epochs, batch_size=args.batch_size, lr=args.lr,
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,104 @@
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import CubicSpline
from mouse_control.inference import load_model, predict
def _interpolate(xs: np.ndarray, ys: np.ndarray, n_points: int) -> tuple[np.ndarray, np.ndarray]:
chord = np.cumsum(np.sqrt(np.diff(xs) ** 2 + np.diff(ys) ** 2))
t = np.concatenate([[0.0], chord])
if t[-1] == 0:
return xs, ys
cs_x = CubicSpline(t, xs)
cs_y = CubicSpline(t, ys)
t_dense = np.linspace(0, t[-1], n_points)
return cs_x(t_dense), cs_y(t_dense)
def visualize(
model_path: Path | None = None,
n_trajectories: int = 10,
n_interp: int = 80,
seed: int | None = None,
save_path: Path | None = Path("trajectories.png"),
show: bool = True,
) -> None:
rng = np.random.default_rng(seed)
targets = rng.integers(low=-200, high=201, size=(n_trajectories, 2))
session = load_model(model_path)
cmap = plt.get_cmap("tab10")
fig, ax = plt.subplots(figsize=(10, 8))
for i, (dx, dy) in enumerate(targets):
pred = predict(session, dx, dy) # (10, 2)
xs = np.concatenate([[0.0], pred[:, 0]])
ys = np.concatenate([[0.0], pred[:, 1]])
xs_dense, ys_dense = _interpolate(xs, ys, n_interp)
color = cmap(i % cmap.N)
ax.plot(xs_dense, ys_dense, "-", color=color, alpha=0.85, linewidth=1.6)
ax.scatter(xs_dense, ys_dense, color=color, s=6, alpha=0.5, zorder=2)
ax.scatter(
pred[:, 0], pred[:, 1], color=color, s=35, zorder=3,
edgecolors="white", linewidths=0.6,
)
ax.scatter(
[dx], [dy], color=color, marker="x", s=140, linewidths=2.5,
zorder=4, label=f"target ({dx}, {dy})",
)
ax.scatter([0], [0], color="black", s=90, zorder=5, label="start (0, 0)")
ax.axhline(0, color="gray", linewidth=0.5, alpha=0.5)
ax.axvline(0, color="gray", linewidth=0.5, alpha=0.5)
ax.set_aspect("equal")
ax.set_title(
f"Mouse trajectories — 10 model points + cubic-spline interpolation ({n_interp} pts)",
)
ax.set_xlabel("dx")
ax.set_ylabel("dy")
ax.legend(loc="best", fontsize=8, ncol=2)
ax.grid(True, alpha=0.3)
plt.tight_layout()
if save_path is not None:
plt.savefig(save_path, dpi=120)
print(f"saved -> {save_path}")
if show:
plt.show()
plt.close(fig)
def main() -> None:
parser = argparse.ArgumentParser(
description="Render predicted mouse trajectories for random targets.",
)
parser.add_argument(
"--model", type=Path, default=None,
help="Path to ONNX model (defaults to ./mouse.onnx or bundled model).",
)
parser.add_argument("--n-trajectories", "-n", type=int, default=10)
parser.add_argument("--n-interp", type=int, default=80)
parser.add_argument("--seed", type=int, default=None)
parser.add_argument("--save", type=Path, default=Path("trajectories.png"))
parser.add_argument(
"--no-show", action="store_true",
help="Skip opening the matplotlib window (still writes --save).",
)
args = parser.parse_args()
visualize(
model_path=args.model,
n_trajectories=args.n_trajectories,
n_interp=args.n_interp,
seed=args.seed,
save_path=args.save,
show=not args.no_show,
)
if __name__ == "__main__":
main()

32
tests/conftest.py Normal file
View File

@@ -0,0 +1,32 @@
from __future__ import annotations
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
DATA_DIR = REPO_ROOT / "data"
@pytest.fixture(scope="session")
def train_csv() -> Path:
path = DATA_DIR / "mouse_data.csv"
if not path.is_file():
pytest.skip(f"missing training csv: {path}")
return path
@pytest.fixture(scope="session")
def test_csv() -> Path:
path = DATA_DIR / "mouse_data_test.csv"
if not path.is_file():
pytest.skip(f"missing test csv: {path}")
return path
@pytest.fixture(scope="session")
def bundled_session():
"""Shared inference session for the bundled model (loading is slow)."""
from mouse_control import load_model
return load_model()

49
tests/test_cli.py Normal file
View File

@@ -0,0 +1,49 @@
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
import pytest
CLI_COMMANDS = ["mouse-collect", "mouse-train", "mouse-visualize"]
@pytest.mark.parametrize("command", CLI_COMMANDS)
def test_cli_help(command: str) -> None:
"""Every CLI must respond to --help with exit 0 and an argparse usage line."""
result = subprocess.run(
[command, "--help"], capture_output=True, text=True, check=False,
)
assert result.returncode == 0, result.stderr
assert "usage:" in result.stdout
def test_visualize_no_show_produces_png(tmp_path: Path) -> None:
save_path = tmp_path / "out.png"
result = subprocess.run(
[
"mouse-visualize", "--no-show", "--seed", "1",
"--n-trajectories", "3", "--n-interp", "20",
"--save", str(save_path),
],
capture_output=True, text=True, check=False,
)
assert result.returncode == 0, result.stderr
assert save_path.exists()
assert save_path.stat().st_size > 1000
def test_visualize_module_invocation(tmp_path: Path) -> None:
"""The module is also runnable via `python -m mouse_control.visualize`."""
save_path = tmp_path / "out.png"
result = subprocess.run(
[
sys.executable, "-m", "mouse_control.visualize",
"--no-show", "--seed", "2", "--n-trajectories", "2",
"--save", str(save_path),
],
capture_output=True, text=True, check=False,
)
assert result.returncode == 0, result.stderr
assert save_path.exists()

95
tests/test_inference.py Normal file
View File

@@ -0,0 +1,95 @@
from __future__ import annotations
import shutil
from importlib.resources import as_file, files
from pathlib import Path
import numpy as np
import onnxruntime as ort
import pytest
from mouse_control import load_model, predict
def _bundled_path() -> Path:
"""Resolve the bundled mouse.onnx to a stable filesystem path for tests."""
with as_file(files("mouse_control.assets").joinpath("mouse.onnx")) as p:
return Path(p)
def test_load_model_returns_inference_session(bundled_session) -> None:
assert isinstance(bundled_session, ort.InferenceSession)
def test_bundled_input_output_names(bundled_session) -> None:
assert bundled_session.get_inputs()[0].name == "input"
assert bundled_session.get_outputs()[0].name == "output"
def test_load_model_explicit_path() -> None:
session = load_model(_bundled_path())
assert isinstance(session, ort.InferenceSession)
def test_load_model_missing_path_raises() -> None:
with pytest.raises(Exception):
load_model("/no/such/path.onnx")
def test_load_model_cwd_override(tmp_path, monkeypatch) -> None:
"""When ./mouse.onnx exists, it should take precedence over the bundle."""
monkeypatch.chdir(tmp_path)
shutil.copy(_bundled_path(), tmp_path / "mouse.onnx")
session = load_model() # implicit
assert isinstance(session, ort.InferenceSession)
# Must produce the same output as the bundle since the file is identical.
pkg_session = load_model(_bundled_path())
a = predict(session, 75, -40)
b = predict(pkg_session, 75, -40)
np.testing.assert_array_equal(a, b)
def test_load_model_falls_back_to_bundled(tmp_path, monkeypatch) -> None:
"""With no ./mouse.onnx, load_model() must succeed via the bundle."""
monkeypatch.chdir(tmp_path)
assert not (tmp_path / "mouse.onnx").exists()
session = load_model()
assert isinstance(session, ort.InferenceSession)
def test_predict_shape_and_dtype(bundled_session) -> None:
out = predict(bundled_session, 100.0, 200.0)
assert out.shape == (10, 2)
assert out.dtype == np.float32
def test_predict_is_deterministic(bundled_session) -> None:
a = predict(bundled_session, 100, 100)
b = predict(bundled_session, 100, 100)
np.testing.assert_array_equal(a, b)
def test_predict_int_and_float_args_equivalent(bundled_session) -> None:
a = predict(bundled_session, 50, 50)
b = predict(bundled_session, 50.0, 50.0)
np.testing.assert_array_equal(a, b)
@pytest.mark.parametrize(
"dx,dy",
[
(150, 0), (-150, 0), (0, 150), (0, -150),
(100, 100), (-100, 100), (100, -100), (-100, -100),
],
)
def test_predict_endpoint_near_target(bundled_session, dx: int, dy: int) -> None:
"""Trained model's endpoint should fall within ~50px of the target."""
end = predict(bundled_session, dx, dy)[-1]
err = float(np.linalg.norm(end - np.array([dx, dy])))
assert err < 50, f"target ({dx},{dy}) endpoint err={err:.1f}"
def test_predict_zero_displacement_stays_near_origin(bundled_session) -> None:
end = predict(bundled_session, 0, 0)[-1]
assert abs(end[0]) < 20
assert abs(end[1]) < 20

74
tests/test_model.py Normal file
View File

@@ -0,0 +1,74 @@
from __future__ import annotations
import pytest
import torch
from mouse_control import SimpleNet
@pytest.fixture
def model() -> SimpleNet:
return SimpleNet()
@pytest.mark.parametrize("batch_size", [1, 4, 16, 64])
def test_forward_with_unsqueezed_input(model: SimpleNet, batch_size: int) -> None:
out = model(torch.randn(batch_size, 1, 2))
assert out.shape == (batch_size, 10, 2)
@pytest.mark.parametrize("batch_size", [1, 8, 32])
def test_forward_with_flat_input(model: SimpleNet, batch_size: int) -> None:
"""SimpleNet's first layer flattens, so (N, 2) is also valid."""
out = model(torch.randn(batch_size, 2))
assert out.shape == (batch_size, 10, 2)
def test_layers_have_expected_dimensions(model: SimpleNet) -> None:
assert model.fc1.in_features == 2
assert model.fc1.out_features == 64
assert model.fc2.in_features == 64
assert model.fc2.out_features == 32
assert model.fc3.in_features == 32
assert model.fc3.out_features == 20
def test_model_has_trainable_parameters(model: SimpleNet) -> None:
params = list(model.parameters())
assert params, "SimpleNet should expose parameters"
assert all(p.requires_grad for p in params)
def test_forward_is_differentiable(model: SimpleNet) -> None:
inp = torch.randn(2, 1, 2, requires_grad=True)
out = model(inp)
out.sum().backward()
assert inp.grad is not None
assert inp.grad.shape == inp.shape
def test_onnx_export_round_trips(tmp_path, model: SimpleNet) -> None:
"""Trained or not, the model architecture must round-trip via ONNX."""
import numpy as np
import onnxruntime as ort
onnx_path = tmp_path / "round_trip.onnx"
dummy = torch.randn(1, 1, 2)
model.eval()
torch.onnx.export(
model, dummy, str(onnx_path),
input_names=["input"], output_names=["output"],
external_data=False,
)
assert onnx_path.exists()
assert not onnx_path.with_suffix(onnx_path.suffix + ".data").exists(), \
"external_data=False must produce a single-file ONNX"
session = ort.InferenceSession(str(onnx_path))
inp = np.array([[[42.0, -17.0]]], dtype=np.float32)
out = session.run(None, {"input": inp})[0]
assert out.shape == (1, 10, 2)
with torch.no_grad():
torch_out = model(torch.from_numpy(inp)).numpy()
np.testing.assert_allclose(out, torch_out, atol=1e-4)

103
tests/test_train.py Normal file
View File

@@ -0,0 +1,103 @@
from __future__ import annotations
import csv
from pathlib import Path
import numpy as np
import onnxruntime as ort
import pytest
import torch
from mouse_control.train import TrajectoryDataset, _load_csv, train
def _write_csv(path: Path, rows: list[list[tuple[int, int]]]) -> None:
"""Write rows in the project's quirky single-quoted-pair format."""
with open(path, "w", newline="") as f:
writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)
for row in rows:
# First column is the target (last keypoint), then 10 keypoints.
target_x, target_y = row[-1]
cells = [f"{target_x},{target_y}"] + [f"{x},{y}" for x, y in row]
writer.writerow(cells)
@pytest.fixture
def synthetic_csv(tmp_path: Path) -> Path:
"""Tiny linear trajectories from origin -> target."""
path = tmp_path / "synth.csv"
rows = []
rng = np.random.default_rng(0)
for _ in range(20):
tx, ty = int(rng.integers(-150, 151)), int(rng.integers(-150, 151))
keypoints = [
(int(round(tx * i / 9)), int(round(ty * i / 9))) for i in range(10)
]
rows.append(keypoints)
_write_csv(path, rows)
return path
def test_load_csv_shapes(synthetic_csv: Path) -> None:
inputs, labels = _load_csv(synthetic_csv)
assert inputs.shape == (20, 1, 2)
assert labels.shape == (20, 10, 2)
assert inputs.dtype == torch.float32
assert labels.dtype == torch.float32
def test_load_csv_round_trip(synthetic_csv: Path) -> None:
"""The target column (input) must equal the last keypoint of the labels."""
inputs, labels = _load_csv(synthetic_csv)
last_keypoint = labels[:, -1, :]
np.testing.assert_array_equal(inputs.squeeze(1).numpy(), last_keypoint.numpy())
def test_dataset_indexing(synthetic_csv: Path) -> None:
inputs, labels = _load_csv(synthetic_csv)
ds = TrajectoryDataset(inputs, labels)
assert len(ds) == 20
x, y = ds[0]
assert x.shape == (1, 2)
assert y.shape == (10, 2)
def test_load_csv_missing_file_raises(tmp_path: Path) -> None:
with pytest.raises(FileNotFoundError):
_load_csv(tmp_path / "missing.csv")
def test_train_end_to_end_produces_single_file_onnx(
synthetic_csv: Path, tmp_path: Path,
) -> None:
"""A quick training run must export a valid, self-contained ONNX model."""
output = tmp_path / "trained.onnx"
train(
train_csv=synthetic_csv,
test_csv=synthetic_csv,
output=output,
epochs=3,
batch_size=8,
)
assert output.exists()
assert output.stat().st_size > 1000
# external_data=False -> no .data sidecar
assert not output.with_suffix(output.suffix + ".data").exists()
session = ort.InferenceSession(str(output))
inp = np.array([[[100.0, 50.0]]], dtype=np.float32)
out = session.run(None, {"input": inp})[0]
assert out.shape == (1, 10, 2)
def test_train_with_real_data(train_csv: Path, test_csv: Path, tmp_path: Path) -> None:
"""Smoke test against the project's actual data."""
output = tmp_path / "real.onnx"
train(
train_csv=train_csv,
test_csv=test_csv,
output=output,
epochs=2,
batch_size=32,
)
assert output.exists()

View File

@@ -1,97 +0,0 @@
import numpy as np
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
import onnxruntime
import onnx
from onnxsim import simplify
# 读取CSV文件
train_csv_path = 'mouse_data.csv' # 替换为你的CSV文件路径
train_csv = pd.read_csv(train_csv_path, header=None)
test_csv_path = 'mouse_data_test.csv' # 替换为你的CSV文件路径
test_csv = pd.read_csv(test_csv_path, header=None)
# 提取dx, dy和标签
dx_dy_train = train_csv.iloc[:, 0].apply(lambda x: list(map(int, x.split(','))))
dx_dy_labels_train = train_csv.apply(lambda row: [list(map(int, row[i].split(','))) for i in range(1,10)], axis=1)
dx_dy_test = test_csv.iloc[:, 0].apply(lambda x: list(map(int, x.split(','))))
dx_dy_labels_test = test_csv.apply(lambda row: [list(map(int, row[i].split(','))) for i in range(1,10)], axis=1)
# 转换为PyTorch Tensor
dx_dy_train_tensor = torch.Tensor(dx_dy_train.tolist())
dx_dy_train_tensor = dx_dy_train_tensor.unsqueeze(1)
labels_train_tensor = torch.Tensor(dx_dy_labels_train.tolist())
dx_dy_test_tensor = torch.Tensor(dx_dy_test.tolist())
dx_dy_test_tensor = dx_dy_test_tensor.unsqueeze(1)
labels_test_tensor = torch.Tensor(dx_dy_labels_test.tolist())
# 创建自定义Dataset
class CustomDataset(Dataset):
def __init__(self, dx_dy, labels):
self.dx_dy = dx_dy
self.labels = labels
def __len__(self):
return len(self.dx_dy)
def __getitem__(self, idx):
return self.dx_dy[idx], self.labels[idx]
# 创建Dataset和DataLoader
train_dataset = CustomDataset(dx_dy_train_tensor, labels_train_tensor)
test_dataset = CustomDataset(dx_dy_test_tensor, labels_test_tensor)
batch_size = 64
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
# 定义神经网络模型
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(2, 64)
self.fc2 = nn.Linear(64, 32)
self.fc3 = nn.Linear(32, 18)
def forward(self, input_data):
x = torch.flatten(input_data, start_dim=1)
x = self.fc1(x)
x = nn.ReLU()(x)
x = self.fc2(x)
x = nn.ReLU()(x)
x = self.fc3(x)
return x.view(-1, 9, 2)
# 初始化模型、损失函数和优化器
model = SimpleNet()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.9)
# 训练模型
epochs = 1000
for epoch in range(epochs):
for batch_dx_dy, batch_labels in train_dataloader:
optimizer.zero_grad()
output = model(batch_dx_dy)
loss = criterion(output, batch_labels)
print(loss)
loss.backward()
optimizer.step()
scheduler.step()
print('test')
for idx, data in enumerate(test_dataloader):
batch_dx_dy, batch_labels = data
output = model(batch_dx_dy)
loss = criterion(output, batch_labels)
print(loss)
if idx == len(test_dataloader)-1:
print(batch_dx_dy[0])
print(output[0])
model.eval()
onnx_name = 'mouse.onnx'
dummy = torch.randn(1, 1, 2)
torch.onnx.export(model, dummy, onnx_name,verbose=True, input_names=['input'], output_names=['output'])

1227
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff