-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsave_sample.php
More file actions
110 lines (96 loc) · 4.49 KB
/
Copy pathsave_sample.php
File metadata and controls
110 lines (96 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php
/* ============================================================
save_sample.php — receives an analyzed X-ray + its 5 marker points and
stores it as labelled training data in a PROTECTED store outside the web root.
Token-gated, validates image type/size, generates its own filenames. Writes:
<STORE>/images/<id>.png
<STORE>/annotations.csv (training format: image_id,component_name,x,y)
<STORE>/records/<id>.json (full record incl. auto vs corrected points)
SET YOUR OWN TOKEN below and mirror it in dataset.js.
============================================================ */
header('Content-Type: application/json');
// ---- config ----
// Token lives in secret.php (gitignored, NOT in the public repo). Fail closed if
// it's missing or empty so a fresh deploy can't be spammed with an empty token.
@include __DIR__ . '/secret.php';
// Store INSIDE the web root (PHP can always write within its own tree, regardless
// of open_basedir / which user the web server runs as) but block web access with a
// deny-all .htaccess below. Avoids the move_uploaded_file 'save failed' that an
// outside-web-root path hits when open_basedir restricts PHP to public_html.
const STORE = __DIR__ . '/dataset_store';
const MAX_BYTES = 15 * 1024 * 1024;
$COMPONENTS = ['proximal_connector', 'distal_connector', 'right_side_marker',
'indicator_bar', 'indicator_t_bar'];
function fail($code, $msg) {
http_response_code($code);
echo json_encode(['ok' => false, 'error' => $msg]);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') fail(405, 'POST only');
if (!defined('SAVE_TOKEN') || SAVE_TOKEN === '') fail(503, 'endpoint not configured');
if (($_POST['token'] ?? '') !== SAVE_TOKEN) fail(403, 'bad token');
if (!isset($_FILES['image'])) fail(400, 'no image');
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) fail(400, 'upload error');
if ($_FILES['image']['size'] > MAX_BYTES) fail(413, 'image too large');
// validate it's really an image (png/jpeg)
$info = @getimagesize($_FILES['image']['tmp_name']);
if ($info === false || !in_array($info[2], [IMAGETYPE_PNG, IMAGETYPE_JPEG], true)) {
fail(415, 'not a png/jpeg');
}
$meta = json_decode($_POST['meta'] ?? '{}', true);
if (!is_array($meta)) fail(400, 'bad meta');
$points = $meta['final_points'] ?? [];
foreach ($COMPONENTS as $c) {
if (!isset($points[$c]) || count($points[$c]) !== 2) fail(400, "missing point: $c");
}
// generated id + extension matching the actual image type — no user input in the path
$id = date('Ymd_His') . '_' . bin2hex(random_bytes(4));
$ext = ($info[2] === IMAGETYPE_PNG) ? 'png' : 'jpg';
$imgName = $id . '.' . $ext;
@mkdir(STORE . '/images', 0775, true);
@mkdir(STORE . '/records', 0775, true);
// block web access to the stored images (defense in depth — it's inside the web root)
$ht = STORE . '/.htaccess';
if (!file_exists($ht)) {
@file_put_contents($ht, "Require all denied\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n");
}
$imgPath = STORE . '/images/' . $imgName;
if (!move_uploaded_file($_FILES['image']['tmp_name'], $imgPath)) {
$why = !is_dir(STORE . '/images') ? 'no store dir'
: (!is_writable(STORE . '/images') ? 'store not writable' : 'move failed');
fail(500, 'save failed: ' . $why);
}
@chmod($imgPath, 0660);
// append the 5 annotation rows (training-compatible)
$csv = STORE . '/annotations.csv';
// ftell() is unreliable right after fopen('a'), so decide on the header up front.
$needHeader = !file_exists($csv) || filesize($csv) === 0;
$fh = fopen($csv, 'a');
if ($fh) {
if (flock($fh, LOCK_EX)) {
if ($needHeader) fwrite($fh, "image_id,component_name,x,y\n");
foreach ($COMPONENTS as $c) {
$p = $points[$c];
fwrite($fh, sprintf("%s,%s,%.2f,%.2f\n", $imgName, $c, $p[0], $p[1]));
}
flock($fh, LOCK_UN);
}
fclose($fh);
}
// full record (the rich signal: model guess vs correction, provenance)
$record = [
'id' => $id,
'ts' => date('c'),
'image' => $imgName,
'width' => $meta['width'] ?? $info[0],
'height' => $meta['height'] ?? $info[1],
'setting' => $meta['setting'] ?? null,
'angle' => $meta['angle'] ?? null,
'source' => $meta['source'] ?? 'manual',
'corrected' => $meta['corrected'] ?? false,
'final_points' => $points,
'auto_points' => $meta['auto_points'] ?? null,
'app_version' => $meta['app_version'] ?? null,
];
file_put_contents(STORE . '/records/' . $id . '.json', json_encode($record, JSON_PRETTY_PRINT));
echo json_encode(['ok' => true, 'id' => $id]);