diff --git a/upload/actions/test.php b/upload/actions/test.php new file mode 100644 index 000000000..0c0cf3855 --- /dev/null +++ b/upload/actions/test.php @@ -0,0 +1,32 @@ + [ 0 => "Naked", 1 => "Safe"] + ,'rescale_factor' => 0.00392156862745098 + ,'format' => 'rgb' + ,'height' => 224 + ,'width' => 224 + ,'shape' => 'bhwc' /* batch channel height width */ + ,'modelNameOrPath' => __DIR__.'/../ai/models/model.onnx'// https://huggingface.co/suko/nsfw +]); + +/** Load models */ +$ia->loadModel(); + +/** analyse one image */ +$tags = $ia->getTags(__DIR__.'/../ai/images/test_image.jpg'); + +var_dump($tags); \ No newline at end of file diff --git a/upload/admin_area/styles/cb_2014/layout/system_info.html b/upload/admin_area/styles/cb_2014/layout/system_info.html index b289badce..e62bda13d 100644 --- a/upload/admin_area/styles/cb_2014/layout/system_info.html +++ b/upload/admin_area/styles/cb_2014/layout/system_info.html @@ -84,13 +84,13 @@

PHP CLI

{foreach $php_extensions_list as $key => $extension}
- +
@@ -119,13 +119,13 @@

PHP CLI

{foreach $php_extensions_list as $key => $extension}
- +
diff --git a/upload/ai/images/test_image.jpg b/upload/ai/images/test_image.jpg new file mode 100644 index 000000000..138802617 Binary files /dev/null and b/upload/ai/images/test_image.jpg differ diff --git a/upload/ai/models/readme b/upload/ai/models/readme new file mode 100644 index 000000000..a954786ae --- /dev/null +++ b/upload/ai/models/readme @@ -0,0 +1 @@ + telecharger le fichier model.onnx depuis le site : https://huggingface.co/suko/nsfw et le placer dans ce dossier \ No newline at end of file diff --git a/upload/cb_install/modes/precheck.php b/upload/cb_install/modes/precheck.php index 8a450b66b..64f892199 100644 --- a/upload/cb_install/modes/precheck.php +++ b/upload/cb_install/modes/precheck.php @@ -61,11 +61,16 @@ } $line++; - if( empty($php_extensions[$key]) ) { - $everything_good = false; - $msg = ['err' => $extension['display'] . ' extension is not enabled']; + if( !in_array($key, $php_extensions, true) ) { + $txt = ' extension is not enabled'; + if( $key == 'ffi' ){ + $msg = ['war' => $extension['display'] . $txt]; + } else { + $everything_good = false; + $msg = ['err' => $extension['display'] . $txt]; + } } else { - $msg = ['msg' => $extension['display'] . ' extension '. $php_extensions[$key]]; + $msg = ['msg' => $extension['display'] . ' extension enabled']; } echo '' . $extension['display'] . ''; diff --git a/upload/composer.json b/upload/composer.json index 7f53de5a3..3f6bdd104 100644 --- a/upload/composer.json +++ b/upload/composer.json @@ -8,7 +8,9 @@ "oxygenzsas/composer_lib_discord": "dev-php7", "psr/http-server-middleware": "1.0.2", "ext-json": "*", - "ext-mysqli": "*" + "ext-mysqli": "*", + "ext-ffi": "*", + "veka-server/onnx-php": "dev-php7" }, "config": { "platform": { diff --git a/upload/includes/classes/AIVision.class.php b/upload/includes/classes/AIVision.class.php new file mode 100644 index 000000000..f0a26408e --- /dev/null +++ b/upload/includes/classes/AIVision.class.php @@ -0,0 +1,207 @@ + "Naked", 1 => "Safe"]; + protected $rescale_factor = 0.00392156862745098 ; + protected $format = 'rgb'; + protected $height = 224; + protected $width = 224; + protected $shape = 'bhwc'; /* batch channel height width */ + protected $modelNameOrPath ; + + protected $provider = 'CPUExecutionProvider'; + protected $model; + + public function __construct(array $config = [], $lib = null) { + + if(!empty($config)) { + $this->tags = $config['tags'] ?? [] ; + $this->rescale_factor = $config['rescale_factor'] ?? 1 ; + $this->modelNameOrPath = $config['modelNameOrPath'] ?? null ; + $this->format = $config['format'] ?? 'rgb' ; + $this->height = $config['height'] ?? 2 ; + $this->width = $config['width'] ?? 256 ; + $this->shape = $config['shape'] ?? 'bchw' ; + } + + if(!empty($lib)) { + + if(!is_file($lib)) { + throw new \Exception( 'Unable to find the lib file : ' . dirname($lib) ); + } + + \Onnx\FFI::$lib = $lib; + + if (\FFI\WorkDirectory\WorkDirectory::set(dirname($lib)) === false) { + throw new \Exception( 'FAILED to CWD has been updated to: ' . dirname($lib) ); + } + + } + } + + public function loadModel($model = null) { + if(!empty($model)) { + $this->modelNameOrPath = $model; + } + + if(empty($this->modelNameOrPath)) { + throw new \Exception('model missing'); + } + + /** chargement du model */ + $this->model = new \Onnx\Model($this->modelNameOrPath); + } + + /** + * @throws \Exception + */ + public static function createImageGDFromPath($path) { + + $pathParts = pathinfo( $path); + switch ( strtolower($pathParts["extension"])) { + case 'png': + $image = imagecreatefrompng($path); + break; + case 'jpg': + case 'jpeg': + $image = imagecreatefromjpeg($path); + break; + case 'gif': + $image = imagecreatefromgif($path); + break; + default: + throw new \Exception('Format de fichier non reconnu'); + } + + return $image; + } + + public static function getGDImageFromImg($image, $width, $height) + { + $img = self::createImageGDFromPath($image); + + if(imagesx($img) > imagesy($img)) { + $reduction = imagesx($img) / $width; + } else { + $reduction = imagesy($img) / $height; + } + + /** converti l'image en $this->dimenssion_imagex$this->dimenssion_image */ + $image2 = imagecreatetruecolor($width, $height); /* dimmension fixe */ + imagefill($image2,0,0,0x7fff0000); /* remplir avec de la transparence */ + imagecopyresampled($image2, $img, 0, 0, 0, 0, floor(imagesx($img)/$reduction), floor(imagesy($img)/$reduction), imagesx($img), imagesy($img)); /* copier l'image */ + + return $image2; + } + + public static function getPixels($img, $format = 'rgb', $rescale_factor = 1) + { + $pixels = []; + $width = imagesx($img); + $height = imagesy($img); + + // Mapping for different formats + $formats = [ + 'bgr' => ['blue', 'green', 'red'], + 'rgb' => ['red', 'green', 'blue'], + ]; + + // Ensure the format exists, otherwise default to 'rgb' + $format = isset($formats[strtolower($format)]) ? $formats[strtolower($format)] : $formats['rgb']; + + for ($y = 0; $y < $height; $y++) { + $row = []; + for ($x = 0; $x < $width; $x++) { + $rgb = imagecolorat($img, $x, $y); + $color = imagecolorsforindex($img, $rgb); + $row[] = [ + $color[$format[0]] * $rescale_factor, + $color[$format[1]] * $rescale_factor, + $color[$format[2]] * $rescale_factor + ]; + } + $pixels[] = $row; + } + return $pixels; + } + + public function getTags($image){ + + /** resize de l'image pour qu'elle soit dans le bon format */ + $img = self::getGDImageFromImg( $image, $this->width, $this->height ); + + /** Extraction des pixels */ + $pixels = self::getPixels($img, $this->format, $this->rescale_factor); + + /** converti le shape */ + $pixels = $this->transposeImage([$pixels], $this->shape); + + /** Récuperation du nom de l'input depuis le model */ + $input_name = $this->model->inputs()[0]['name']; + + $tensor = Tensor::fromArray($pixels,DType::FLOAT32); + + /** prediction IA */ + $result = $this->model->predict([$input_name => $tensor]); + + $clean_result = $this->postprocess($result); + + return $clean_result; + } + + function transposeImage($pixels, $format = 'bhwc') { + + if(strtolower($format) == 'bhwc') { + return $pixels; + } + + $transposedImage = []; + + if($format == 'bchw') { + foreach ($pixels as $b => $batch) { + foreach ($batch as $h => $row) { + foreach ($row as $w => $pixel) { + foreach ($pixel as $c => $value) { + $transposedImage[$b][$c][$h][$w] = $value; + } + } + } + } + } + + return $transposedImage; + } + + protected function postprocess($result) { + + $t = []; + $output_name = $this->model->outputs()[0]['name']; + + foreach ($result[$output_name][0] as $idx => $v) { + + if(empty($this->tags[$idx])) { + continue; + } + + $t[$this->tags[$idx]] = $v; + + } + + return $t; + } + + public function setProvider(string $provider = null) + { + if(empty($provider)) { + $this->provider = 'CPUExecutionProvider'; + return ; + } + + $this->provider = $provider; + } + +} \ No newline at end of file diff --git a/upload/includes/classes/system.class.php b/upload/includes/classes/system.class.php index 8a5173b7d..9051983fa 100644 --- a/upload/includes/classes/system.class.php +++ b/upload/includes/classes/system.class.php @@ -9,48 +9,7 @@ private static function init_php_extensions($type, $custom_filepath = null) { switch($type){ case 'php_web': - ob_start(); - phpinfo(INFO_MODULES); - $s = ob_get_contents(); - ob_end_clean(); - - $s = strip_tags($s, '

'); - $s = preg_replace('/]*>([^<]+)<\/th>/', "\\1", $s); - $s = preg_replace('/]*>([^<]+)<\/td>/', "\\1", $s); - $vTmp = preg_split('/(

[^<]+<\/h2>)/', $s, -1, PREG_SPLIT_DELIM_CAPTURE); - $vModules = []; - for ($i = 1; $i < count($vTmp); $i++) { - if (preg_match('/

([^<]+)<\/h2>/', $vTmp[$i], $vMat)) { - $vName = trim($vMat[1]); - $vTmp2 = explode("\n", $vTmp[$i + 1]); - foreach ($vTmp2 as $vOne) { - $vPat = '([^<]+)<\/info>'; - $vPat3 = "/$vPat\s*$vPat\s*$vPat/"; - $vPat2 = "/$vPat\s*$vPat/"; - if (preg_match($vPat3, $vOne, $vMat)) { // 3cols - $vModules[$vName][trim($vMat[1])] = [ - trim($vMat[2]), - trim($vMat[3]) - ]; - } elseif (preg_match($vPat2, $vOne, $vMat)) { // 2cols - $vModules[$vName][trim($vMat[1])] = trim($vMat[2]); - } - } - } - } - - $regex_version = '(\d+\.\d+\.\d+)'; - $php_extensions = self::get_php_extensions_list(); - foreach($php_extensions as $key => $extension){ - foreach($extension['version_tags'] as $tag){ - if (!empty($vModules[$key][$tag]) && empty(self::$extensionsWeb[$key])) { - $matches = []; - preg_match($regex_version, $vModules[$key][$tag], $matches); - self::$extensionsWeb[$key] = $matches[0]??$vModules[$key][$tag]; - } - } - } - + self::$extensionsWeb = array_map('strtolower', get_loaded_extensions()); break; case 'php_cli': @@ -83,17 +42,7 @@ private static function init_php_extensions($type, $custom_filepath = null) } foreach($php_extensions as $key => $extension){ - foreach($extension['version_tags'] as $tag){ - if (strpos($line, $tag) !== false) { - $line = explode('=>', $line); - $tmp_version = trim(end($line)); - - preg_match($regex_version, $tmp_version, $match_version); - self::$extensionsCli[$key] = $match_version[0] ?? $tmp_version; - - continue 3; - } - } + self::$extensionsCli[] = $key; } } @@ -136,6 +85,10 @@ public static function get_php_extensions_list(): array 'fileinfo' => [ 'display' => 'Fileinfo' ,'version_tags' => ['fileinfo support'] + ], + 'ffi' => [ + 'display' => 'FFI' + ,'version_tags' => ['FFI support'] ] ]; } diff --git a/upload/vendor/composer/autoload_psr4.php b/upload/vendor/composer/autoload_psr4.php index e8fbff4fd..6501a146b 100644 --- a/upload/vendor/composer/autoload_psr4.php +++ b/upload/vendor/composer/autoload_psr4.php @@ -13,4 +13,7 @@ 'Predis\\' => array($vendorDir . '/predis/predis/src'), 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), 'OxygenzSAS\\Discord\\' => array($vendorDir . '/oxygenzsas/composer_lib_discord/src'), + 'Onnx\\' => array($vendorDir . '/veka-server/onnx-php/src'), + 'FFI\\WorkDirectory\\' => array($vendorDir . '/veka-server/onnx-php/work-directory/src'), + 'FFI\\Env\\' => array($vendorDir . '/veka-server/onnx-php/env/src'), ); diff --git a/upload/vendor/composer/autoload_real.php b/upload/vendor/composer/autoload_real.php index 5ea3a6501..434d936de 100644 --- a/upload/vendor/composer/autoload_real.php +++ b/upload/vendor/composer/autoload_real.php @@ -22,6 +22,8 @@ public static function getLoader() return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInitc5bfc8e5670876b11014ce9f23d445ff', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInitc5bfc8e5670876b11014ce9f23d445ff', 'loadClassLoader')); diff --git a/upload/vendor/composer/autoload_static.php b/upload/vendor/composer/autoload_static.php index 5da16cd40..02406126f 100644 --- a/upload/vendor/composer/autoload_static.php +++ b/upload/vendor/composer/autoload_static.php @@ -22,6 +22,12 @@ class ComposerStaticInitc5bfc8e5670876b11014ce9f23d445ff 'O' => array ( 'OxygenzSAS\\Discord\\' => 19, + 'Onnx\\' => 5, + ), + 'F' => + array ( + 'FFI\\WorkDirectory\\' => 18, + 'FFI\\Env\\' => 8, ), ); @@ -55,6 +61,18 @@ class ComposerStaticInitc5bfc8e5670876b11014ce9f23d445ff array ( 0 => __DIR__ . '/..' . '/oxygenzsas/composer_lib_discord/src', ), + 'Onnx\\' => + array ( + 0 => __DIR__ . '/..' . '/veka-server/onnx-php/src', + ), + 'FFI\\WorkDirectory\\' => + array ( + 0 => __DIR__ . '/..' . '/veka-server/onnx-php/work-directory/src', + ), + 'FFI\\Env\\' => + array ( + 0 => __DIR__ . '/..' . '/veka-server/onnx-php/env/src', + ), ); public static $classMap = array ( diff --git a/upload/vendor/composer/installed.json b/upload/vendor/composer/installed.json index 03826a3e0..4e7e066f5 100644 --- a/upload/vendor/composer/installed.json +++ b/upload/vendor/composer/installed.json @@ -282,27 +282,27 @@ }, { "name": "psr/http-message", - "version": "1.1", - "version_normalized": "1.1.0.0", + "version": "1.0.1", + "version_normalized": "1.0.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": ">=5.3.0" }, - "time": "2023-04-04T09:50:52+00:00", + "time": "2016-08-06T14:39:51+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.0.x-dev" } }, "installation-source": "dist", @@ -332,7 +332,7 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/master" }, "install-path": "../psr/http-message" }, @@ -573,6 +573,61 @@ "source": "https://github.com/smarty-php/smarty/tree/v3.1.48" }, "install-path": "../smarty/smarty" + }, + { + "name": "veka-server/onnx-php", + "version": "dev-php7", + "version_normalized": "dev-php7", + "source": { + "type": "git", + "url": "https://github.com/veka-server/onnx-php.git", + "reference": "69f54a5cdde794cddd5a6871fc62a6603cdeba99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/veka-server/onnx-php/zipball/69f54a5cdde794cddd5a6871fc62a6603cdeba99", + "reference": "69f54a5cdde794cddd5a6871fc62a6603cdeba99", + "shasum": "" + }, + "require": { + "ext-ffi": "*", + "ext-mbstring": "*", + "php": ">= 7" + }, + "time": "2024-08-09T15:46:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Onnx\\": "src/", + "FFI\\Env\\": "env/src", + "FFI\\WorkDirectory\\": "work-directory/src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "veka-server", + "email": "veka610@gmail.com" + } + ], + "description": "lib for use onnxruntime with php", + "keywords": [ + "ai", + "llm", + "models", + "onnx", + "onnxruntime", + "php" + ], + "support": { + "issues": "https://github.com/veka-server/onnx-php/issues", + "source": "https://github.com/veka-server/onnx-php/tree/php7" + }, + "install-path": "../veka-server/onnx-php" } ], "dev": true, diff --git a/upload/vendor/composer/installed.php b/upload/vendor/composer/installed.php index 19a3f0291..a1a8e869a 100644 --- a/upload/vendor/composer/installed.php +++ b/upload/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '4281d3da6a34fb4bd18850d7cfe3d1cc501767c7', + 'reference' => 'f60e5b26a59e9906ecb51115e8b5aa5fc11a80a9', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '4281d3da6a34fb4bd18850d7cfe3d1cc501767c7', + 'reference' => 'f60e5b26a59e9906ecb51115e8b5aa5fc11a80a9', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -56,9 +56,9 @@ 'dev_requirement' => false, ), 'psr/http-message' => array( - 'pretty_version' => '1.1', - 'version' => '1.1.0.0', - 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba', + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), @@ -100,5 +100,14 @@ 'aliases' => array(), 'dev_requirement' => false, ), + 'veka-server/onnx-php' => array( + 'pretty_version' => 'dev-php7', + 'version' => 'dev-php7', + 'reference' => '69f54a5cdde794cddd5a6871fc62a6603cdeba99', + 'type' => 'library', + 'install_path' => __DIR__ . '/../veka-server/onnx-php', + 'aliases' => array(), + 'dev_requirement' => false, + ), ), ); diff --git a/upload/vendor/composer/platform_check.php b/upload/vendor/composer/platform_check.php new file mode 100644 index 000000000..f79e574be --- /dev/null +++ b/upload/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/upload/vendor/psr/http-message/README.md b/upload/vendor/psr/http-message/README.md index 2668be6c3..28185338f 100644 --- a/upload/vendor/psr/http-message/README.md +++ b/upload/vendor/psr/http-message/README.md @@ -10,7 +10,4 @@ interface that describes a HTTP message. See the specification for more details. Usage ----- -Before reading the usage guide we recommend reading the PSR-7 interfaces method list: - -* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md) -* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md) \ No newline at end of file +We'll certainly need some stuff in here. \ No newline at end of file diff --git a/upload/vendor/psr/http-message/composer.json b/upload/vendor/psr/http-message/composer.json index 56e8c0a6d..b0d2937a0 100644 --- a/upload/vendor/psr/http-message/composer.json +++ b/upload/vendor/psr/http-message/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": "^7.2 || ^8.0" + "php": ">=5.3.0" }, "autoload": { "psr-4": { @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.0.x-dev" } } } diff --git a/upload/vendor/psr/http-message/docs/PSR7-Interfaces.md b/upload/vendor/psr/http-message/docs/PSR7-Interfaces.md deleted file mode 100644 index 3a7e7dda6..000000000 --- a/upload/vendor/psr/http-message/docs/PSR7-Interfaces.md +++ /dev/null @@ -1,130 +0,0 @@ -# Interfaces - -The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces. - -The interfaces defined in PSR-7 are the following: - -| Class Name | Description | -|---|---| -| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message | -| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. | -| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. | -| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. | -| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream | -| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. | -| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. | - -## `Psr\Http\Message\MessageInterface` Methods - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getProtocolVersion()` | Retrieve HTTP protocol version | 1.0 or 1.1 | -| `withProtocolVersion($version)` | Returns new message instance with given HTTP protocol version | | -| `getHeaders()` | Retrieve all HTTP Headers | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) | -| `hasHeader($name)` | Checks if HTTP Header with given name exists | | -| `getHeader($name)` | Retrieves a array with the values for a single header | | -| `getHeaderLine($name)` | Retrieves a comma-separated string of the values for a single header | | -| `withHeader($name, $value)` | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. | -| `withAddedHeader($name, $value)` | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created | -| `withoutHeader($name)` | Removes HTTP Header with given name| | -| `getBody()` | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`| -| `withBody(StreamInterface $body)` | Returns new message instance with given HTTP Message Body | | - - -## `Psr\Http\Message\RequestInterface` Methods - -Same methods as `Psr\Http\Message\MessageInterface` + the following methods: - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getRequestTarget()` | Retrieves the message's request target | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) | -| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target | | -| `getMethod()` | Retrieves the HTTP method of the request. | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) | -| `withMethod($method)` | Returns a new message instance with the provided HTTP method | | -| `getUri()` | Retrieves the URI instance | | -| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI | | - - -## `Psr\Http\Message\ServerRequestInterface` Methods - -Same methods as `Psr\Http\Message\RequestInterface` + the following methods: - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getServerParams() ` | Retrieve server parameters | Typically derived from `$_SERVER` | -| `getCookieParams()` | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` | -| `withCookieParams(array $cookies)` | Returns a new request instance with the specified cookies | | -| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments | | -| `getUploadedFiles()` | Retrieve normalized file upload data | | -| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files | | -| `getParsedBody()` | Retrieve any parameters provided in the request body | | -| `withParsedBody($data)` | Returns a new request instance with the specified body parameters | | -| `getAttributes()` | Retrieve attributes derived from the request | | -| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute | | -| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute | | -| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute | | - -## `Psr\Http\Message\ResponseInterface` Methods: - -Same methods as `Psr\Http\Message\MessageInterface` + the following methods: - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getStatusCode()` | Gets the response status code. | | -| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | | -| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | | - -## `Psr\Http\Message\StreamInterface` Methods - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | | -| `close()` | Closes the stream and any underlying resources. | | -| `detach()` | Separates any underlying resources from the stream. | | -| `getSize()` | Get the size of the stream if known. | | -| `eof()` | Returns true if the stream is at the end of the stream.| | -| `isSeekable()` | Returns whether or not the stream is seekable. | | -| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | | -| `rewind()` | Seek to the beginning of the stream. | | -| `isWritable()` | Returns whether or not the stream is writable. | | -| `write($string)` | Write data to the stream. | | -| `isReadable()` | Returns whether or not the stream is readable. | | -| `read($length)` | Read data from the stream. | | -| `getContents()` | Returns the remaining contents in a string | | -| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | | - -## `Psr\Http\Message\UriInterface` Methods - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getScheme()` | Retrieve the scheme component of the URI. | | -| `getAuthority()` | Retrieve the authority component of the URI. | | -| `getUserInfo()` | Retrieve the user information component of the URI. | | -| `getHost()` | Retrieve the host component of the URI. | | -| `getPort()` | Retrieve the port component of the URI. | | -| `getPath()` | Retrieve the path component of the URI. | | -| `getQuery()` | Retrieve the query string of the URI. | | -| `getFragment()` | Retrieve the fragment component of the URI. | | -| `withScheme($scheme)` | Return an instance with the specified scheme. | | -| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | | -| `withHost($host)` | Return an instance with the specified host. | | -| `withPort($port)` | Return an instance with the specified port. | | -| `withPath($path)` | Return an instance with the specified path. | | -| `withQuery($query)` | Return an instance with the specified query string. | | -| `withFragment($fragment)` | Return an instance with the specified URI fragment. | | -| `__toString()` | Return the string representation as a URI reference. | | - -## `Psr\Http\Message\UploadedFileInterface` Methods - -| Method Name | Description | Notes | -|------------------------------------| ----------- | ----- | -| `getStream()` | Retrieve a stream representing the uploaded file. | | -| `moveTo($targetPath)` | Move the uploaded file to a new location. | | -| `getSize()` | Retrieve the file size. | | -| `getError()` | Retrieve the error associated with the uploaded file. | | -| `getClientFilename()` | Retrieve the filename sent by the client. | | -| `getClientMediaType()` | Retrieve the media type sent by the client. | | - -> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. -> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. - diff --git a/upload/vendor/psr/http-message/docs/PSR7-Usage.md b/upload/vendor/psr/http-message/docs/PSR7-Usage.md deleted file mode 100644 index b6d048a34..000000000 --- a/upload/vendor/psr/http-message/docs/PSR7-Usage.md +++ /dev/null @@ -1,159 +0,0 @@ -### PSR-7 Usage - -All PSR-7 applications comply with these interfaces -They were created to establish a standard between middleware implementations. - -> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. -> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. - - -The following examples will illustrate how basic operations are done in PSR-7. - -##### Examples - - -For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc) -All PSR-7 implementations should have the same behaviour. - -The following will be assumed: -`$request` is an object of `Psr\Http\Message\RequestInterface` and - -`$response` is an object implementing `Psr\Http\Message\RequestInterface` - - -### Working with HTTP Headers - -#### Adding headers to response: - -```php -$response->withHeader('My-Custom-Header', 'My Custom Message'); -``` - -#### Appending values to headers - -```php -$response->withAddedHeader('My-Custom-Header', 'The second message'); -``` - -#### Checking if header exists: - -```php -$request->hasHeader('My-Custom-Header'); // will return false -$response->hasHeader('My-Custom-Header'); // will return true -``` - -> Note: My-Custom-Header was only added in the Response - -#### Getting comma-separated values from a header (also applies to request) - -```php -// getting value from request headers -$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8" -// getting value from response headers -$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message" -``` - -#### Getting array of value from a header (also applies to request) -```php -// getting value from request headers -$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"] -// getting value from response headers -$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"] -``` - -#### Removing headers from HTTP Messages -```php -// removing a header from Request, removing deprecated "Content-MD5" header -$request->withoutHeader('Content-MD5'); - -// removing a header from Response -// effect: the browser won't know the size of the stream -// the browser will download the stream till it ends -$response->withoutHeader('Content-Length'); -``` - -### Working with HTTP Message Body - -When working with the PSR-7 there are two methods of implementation: -#### 1. Getting the body separately - -> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented. - -```php -$body = $response->getBody(); -// operations on body, eg. read, write, seek -// ... -// replacing the old body -$response->withBody($body); -// this last statement is optional as we working with objects -// in this case the "new" body is same with the "old" one -// the $body variable has the same value as the one in $request, only the reference is passed -``` - -#### 2. Working directly on response - -> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required - -```php -$response->getBody()->write('hello'); -``` - -### Getting the body contents - -The following snippet gets the contents of a stream contents. -> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream. -```php -$body = $response->getBody(); -$body->rewind(); // or $body->seek(0); -$bodyText = $body->getContents(); -``` -> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended. - -### Append to body - -```php -$response->getBody()->write('Hello'); // writing directly -$body = $request->getBody(); // which is a `StreamInterface` -$body->write('xxxxx'); -``` - -### Prepend to body -Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended. -The following example will explain the behaviour of streams. - -```php -// assuming our response is initially empty -$body = $repsonse->getBody(); -// writing the string "abcd" -$body->write('abcd'); - -// seeking to start of stream -$body->seek(0); -// writing 'ef' -$body->write('ef'); // at this point the stream contains "efcd" -``` - -#### Prepending by rewriting separately - -```php -// assuming our response body stream only contains: "abcd" -$body = $response->getBody(); -$body->rewind(); -$contents = $body->getContents(); // abcd -// seeking the stream to beginning -$body->rewind(); -$body->write('ef'); // stream contains "efcd" -$body->write($contents); // stream contains "efabcd" -``` - -> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`. - -#### Prepending by using contents as a string -```php -$body = $response->getBody(); -$body->rewind(); -$contents = $body->getContents(); // efabcd -$contents = 'ef'.$contents; -$body->rewind(); -$body->write($contents); -``` diff --git a/upload/vendor/psr/http-message/src/MessageInterface.php b/upload/vendor/psr/http-message/src/MessageInterface.php index 8cdb4ed63..dd46e5ec8 100644 --- a/upload/vendor/psr/http-message/src/MessageInterface.php +++ b/upload/vendor/psr/http-message/src/MessageInterface.php @@ -1,7 +1,5 @@ = 7", + "ext-mbstring": "*", + "ext-ffi": "*" + } +} diff --git a/upload/vendor/veka-server/onnx-php/env/src/Exception/EnvironmentException.php b/upload/vendor/veka-server/onnx-php/env/src/Exception/EnvironmentException.php new file mode 100644 index 000000000..a5f8c324b --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/env/src/Exception/EnvironmentException.php @@ -0,0 +1,76 @@ +OrtGetApiBase()[0]->GetVersionString)(); + } + + private static $libc; + + // for Windows + public static function libc() + { + if (!isset(self::$libc)) { + self::$libc = \FFI::cdef( + 'size_t mbstowcs(void *wcstr, const char *mbstr, size_t count);', + 'msvcrt.dll' + ); + } + + return self::$libc; + } +} diff --git a/upload/vendor/veka-server/onnx-php/src/GraphOptimizationLevel.php b/upload/vendor/veka-server/onnx-php/src/GraphOptimizationLevel.php new file mode 100644 index 000000000..16db9165b --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/src/GraphOptimizationLevel.php @@ -0,0 +1,12 @@ +ffi = FFI::instance(); + $this->api = self::api(); + + // session options + $sessionOptions = $this->ffi->new('OrtSessionOptions*'); + $this->checkStatus(($this->api->CreateSessionOptions)(\FFI::addr($sessionOptions))); + if ($enableCpuMemArena) { + $this->checkStatus(($this->api->EnableCpuMemArena)($sessionOptions)); + } else { + $this->checkStatus(($this->api->DisableCpuMemArena)($sessionOptions)); + } + if ($enableMemPattern) { + $this->checkStatus(($this->api->EnableMemPattern)($sessionOptions)); + } else { + $this->checkStatus(($this->api->DisableMemPattern)($sessionOptions)); + } + if ($enableProfiling) { + $this->checkStatus(($this->api->EnableProfiling)($sessionOptions, $this->ortString($profileFilePrefix ?? 'onnxruntime_profile_'))); + } else { + $this->checkStatus(($this->api->DisableProfiling)($sessionOptions)); + } + if (!is_null($executionMode)) { + $this->checkStatus(($this->api->SetSessionExecutionMode)($sessionOptions, $executionMode->value)); + } + if (!is_null($freeDimensionOverridesByDenotation)) { + foreach ($freeDimensionOverridesByDenotation as $k => $v) { + $this->checkStatus(($this->api->AddFreeDimensionOverride)($sessionOptions, $k, $v)); + } + } + if (!is_null($freeDimensionOverridesByName)) { + foreach ($freeDimensionOverridesByName as $k => $v) { + $this->checkStatus(($this->api->AddFreeDimensionOverrideByName)($sessionOptions, $k, $v)); + } + } + if (!is_null($graphOptimizationLevel)) { + $this->checkStatus(($this->api->SetSessionGraphOptimizationLevel)($sessionOptions, $graphOptimizationLevel->value)); + } + if (!is_null($interOpNumThreads)) { + $this->checkStatus(($this->api->SetInterOpNumThreads)($sessionOptions, $interOpNumThreads)); + } + if (!is_null($intraOpNumThreads)) { + $this->checkStatus(($this->api->SetIntraOpNumThreads)($sessionOptions, $intraOpNumThreads)); + } + if (!is_null($logSeverityLevel)) { + $this->checkStatus(($this->api->SetSessionLogSeverityLevel)($sessionOptions, $logSeverityLevel)); + } + if (!is_null($logVerbosityLevel)) { + $this->checkStatus(($this->api->SetSessionLogVerbosityLevel)($sessionOptions, $logVerbosityLevel)); + } + if (!is_null($logid)) { + $this->checkStatus(($this->api->SetSessionLogId)($sessionOptions, $logid)); + } + if (!is_null($optimizedModelFilepath)) { + $this->checkStatus(($this->api->SetOptimizedModelFilePath)($sessionOptions, $this->ortString($optimizedModelFilepath))); + } + if (!is_null($sessionConfigEntries)) { + foreach ($sessionConfigEntries as $k => $v) { + $this->checkStatus(($this->api->AddSessionConfigEntry)($sessionOptions, $k, $v)); + } + } + foreach ($providers as $provider) { + if (!in_array($provider, $this->providers())) { + trigger_error('Provider not available: ' . $provider, E_USER_WARNING); + continue; + } + + if ($provider == 'CUDAExecutionProvider') { + $cudaOptions = $this->ffi->new('OrtCUDAProviderOptionsV2*'); + $this->checkStatus(($this->api->CreateCUDAProviderOptions)(\FFI::addr($cudaOptions))); + $this->checkStatus(($this->api->SessionOptionsAppendExecutionProvider_CUDA_V2)($sessionOptions, $cudaOptions)); + ($this->api->ReleaseCUDAProviderOptions)($cudaOptions); + } elseif ($provider == 'CPUExecutionProvider') { + break; + } else { + throw new \InvalidArgumentException('Provider not supported: ' . $provider); + } + } + + $this->session = $this->loadSession($path, $sessionOptions); + $this->allocator = $this->loadAllocator(); + $this->inputs = $this->loadInputs(); + $this->outputs = $this->loadOutputs(); + + ($this->api->ReleaseSessionOptions)($sessionOptions); + } + + public function __destruct() + { + ($this->api->ReleaseSession)($this->session); + } + + public function run($outputNames, $inputFeed, $logSeverityLevel = null, $logVerbosityLevel = null, $logid = null, $terminate = null) + { + // pointer references + $refs = []; + + $inputTensor = $this->createInputTensor($inputFeed, $refs); + + if (!isset($outputNames)) { + $outputNames = array_map(function($v) { + return $v['name']; + }, $this->outputs); + } + + $outputsSize = count($outputNames); + $outputTensor = $this->ffi->new("OrtValue*[$outputsSize]"); + $inputNodeNames = $this->createNodeNames(array_keys($inputFeed), $refs); + $outputNodeNames = $this->createNodeNames($outputNames, $refs); + + // run options + $runOptions = $this->ffi->new('OrtRunOptions*'); + $this->checkStatus(($this->api->CreateRunOptions)(\FFI::addr($runOptions))); + if (!is_null($logVerbosityLevel)) { + $this->checkStatus(($this->api->RunOptionsSetRunLogSeverityLevel)($runOptions, $logSeverityLevel)); + } + if (!is_null($logVerbosityLevel)) { + $this->checkStatus(($this->api->RunOptionsSetRunLogVerbosityLevel)($runOptions, $logVerbosityLevel)); + } + if (!is_null($logid)) { + $this->checkStatus(($this->api->RunOptionsSetRunTag)($runOptions, $logid)); + } + if (!is_null($terminate)) { + if ($terminate) { + $this->checkStatus(($this->api->RunOptionsSetTerminate)($runOptions)); + } else { + $this->checkStatus(($this->api->RunOptionsUnsetTerminate)($runOptions)); + } + } + + $rrr = ($this->api->Run)($this->session, $runOptions, $inputNodeNames, $inputTensor, count($inputFeed), $outputNodeNames, count($outputNames), $outputTensor); + + $this->checkStatus($rrr); + + $output = []; + foreach ($outputTensor as $t) { + $output[] = $this->createFromOnnxValue($t); + } + + // TODO use finally + ($this->api->ReleaseRunOptions)($runOptions); + if ($inputTensor) { + for ($i = 0; $i < count($inputFeed); $i++) { + ($this->api->ReleaseValue)($inputTensor[$i]); + } + } + // output values released in createFromOnnxValue + + return $output; + } + + public function inputs() + { + return $this->inputs; + } + + public function outputs() + { + return $this->outputs; + } + + public function modelmeta() + { + $keys = $this->ffi->new('char**'); + $numKeys = $this->ffi->new('int64_t'); + $description = $this->ffi->new('char*'); + $domain = $this->ffi->new('char*'); + $graphName = $this->ffi->new('char*'); + $graphDescription = $this->ffi->new('char*'); + $producerName = $this->ffi->new('char*'); + $version = $this->ffi->new('int64_t'); + + $metadata = $this->ffi->new('OrtModelMetadata*'); + $this->checkStatus(($this->api->SessionGetModelMetadata)($this->session, \FFI::addr($metadata))); + + $customMetadataMap = []; + $this->checkStatus(($this->api->ModelMetadataGetCustomMetadataMapKeys)($metadata, $this->allocator, \FFI::addr($keys), \FFI::addr($numKeys))); + for ($i = 0; $i < $numKeys->cdata; $i++) { + $keyPtr = $keys[$i]; + $key = \FFI::string($keyPtr); + $value = $this->ffi->new('char*'); + $this->checkStatus(($this->api->ModelMetadataLookupCustomMetadataMap)($metadata, $this->allocator, $key, \FFI::addr($value))); + $customMetadataMap[$key] = \FFI::string($value); + + $this->allocatorFree($keyPtr); + $this->allocatorFree($value); + } + $this->allocatorFree($keys); + + $this->checkStatus(($this->api->ModelMetadataGetDescription)($metadata, $this->allocator, \FFI::addr($description))); + $this->checkStatus(($this->api->ModelMetadataGetDomain)($metadata, $this->allocator, \FFI::addr($domain))); + $this->checkStatus(($this->api->ModelMetadataGetGraphName)($metadata, $this->allocator, \FFI::addr($graphName))); + $this->checkStatus(($this->api->ModelMetadataGetGraphDescription)($metadata, $this->allocator, \FFI::addr($graphDescription))); + $this->checkStatus(($this->api->ModelMetadataGetProducerName)($metadata, $this->allocator, \FFI::addr($producerName))); + $this->checkStatus(($this->api->ModelMetadataGetVersion)($metadata, \FFI::addr($version))); + + $ret = [ + 'custom_metadata_map' => $customMetadataMap, + 'description' => \FFI::string($description), + 'domain' => \FFI::string($domain), + 'graph_name' => \FFI::string($graphName), + 'graph_description' => \FFI::string($graphDescription), + 'producer_name' => \FFI::string($producerName), + 'version' => $version->cdata + ]; + + // TODO use finally + ($this->api->ReleaseModelMetadata)($metadata); + $this->allocatorFree($description); + $this->allocatorFree($domain); + $this->allocatorFree($graphName); + $this->allocatorFree($graphDescription); + $this->allocatorFree($producerName); + + return $ret; + } + + // return value has double underscore like Python + public function endProfiling() + { + $out = $this->ffi->new('char*'); + $this->checkStatus(($this->api->SessionEndProfiling)($this->session, $this->allocator, \FFI::addr($out))); + return \FFI::string($out); + } + + // no way to set providers with C API yet + // so we can return all available providers + public function providers() + { + $outPtr = $this->ffi->new('char**'); + $lengthPtr = $this->ffi->new('int'); + $this->checkStatus(($this->api->GetAvailableProviders)(\FFI::addr($outPtr), \FFI::addr($lengthPtr))); + $length = $lengthPtr->cdata; + $providers = []; + for ($i = 0; $i < $length; $i++) { + $providers[] = \FFI::string($outPtr[$i]); + } + ($this->api->ReleaseAvailableProviders)($outPtr, $length); + return $providers; + } + + private function loadSession($path, $sessionOptions) + { + $session = $this->ffi->new('OrtSession*'); + if (is_resource($path) && get_resource_type($path) == 'stream') { + $contents = stream_get_contents($path); + $this->checkStatus(($this->api->CreateSessionFromArray)(self::env(), $contents, strlen($contents), $sessionOptions, \FFI::addr($session))); + } else { + $this->checkStatus(($this->api->CreateSession)(self::env(), $this->ortString($path), $sessionOptions, \FFI::addr($session))); + } + return $session; + } + + private function loadAllocator() + { + $allocator = $this->ffi->new('OrtAllocator*'); + $this->checkStatus(($this->api->GetAllocatorWithDefaultOptions)(\FFI::addr($allocator))); + return $allocator; + } + + private function loadInputs() + { + $inputs = []; + $numInputNodes = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->SessionGetInputCount)($this->session, \FFI::addr($numInputNodes))); + for ($i = 0; $i < $numInputNodes->cdata; $i++) { + $namePtr = $this->ffi->new('char*'); + $this->checkStatus(($this->api->SessionGetInputName)($this->session, $i, $this->allocator, \FFI::addr($namePtr))); + // freed in nodeInfo + $typeinfo = $this->ffi->new('OrtTypeInfo*'); + $this->checkStatus(($this->api->SessionGetInputTypeInfo)($this->session, $i, \FFI::addr($typeinfo))); + $inputs[] = array_merge(['name' => \FFI::string($namePtr)], $this->nodeInfo($typeinfo)); + $this->allocatorFree($namePtr); + } + return $inputs; + } + + private function loadOutputs() + { + $outputs = []; + $numOutputNodes = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->SessionGetOutputCount)($this->session, \FFI::addr($numOutputNodes))); + for ($i = 0; $i < $numOutputNodes->cdata; $i++) { + $namePtr = $this->ffi->new('char*'); + $this->checkStatus(($this->api->SessionGetOutputName)($this->session, $i, $this->allocator, \FFI::addr($namePtr))); + // freed in nodeInfo + $typeinfo = $this->ffi->new('OrtTypeInfo*'); + $this->checkStatus(($this->api->SessionGetOutputTypeInfo)($this->session, $i, \FFI::addr($typeinfo))); + $outputs[] = array_merge(['name' => \FFI::string($namePtr)], $this->nodeInfo($typeinfo)); + $this->allocatorFree($namePtr); + } + return $outputs; + } + + private function createInputTensor($inputFeed, &$refs) + { + $allocatorInfo = $this->ffi->new('OrtMemoryInfo*'); + $this->checkStatus(($this->api->CreateCpuMemoryInfo)(1, 0, \FFI::addr($allocatorInfo))); + $inputFeedSize = count($inputFeed); + if ($inputFeedSize == 0) { + throw new \Exception('No input'); + } + $inputTensor = $this->ffi->new("OrtValue*[$inputFeedSize]"); + + $idx = 0; + /** @var TensorInterface $input */ + foreach ($inputFeed as $inputName => $input) { + // TODO support more types + $inp = null; + foreach ($this->inputs as $i) { + if ($i['name'] == $inputName) { + $inp = $i; + break; + } + } + if (is_null($inp)) { + throw new \Exception("Unknown input: $inputName"); + } + + $shape = $input->shape(); + $ndim = $input->ndim(); + $size = $input->size(); + + $inputNodeShape = $this->ffi->new("int64_t[$ndim]"); + for ($i = 0; $i < $ndim; $i++) { + $inputNodeShape[$i] = $shape[$i]; + } + + if ($inp['type'] == 'tensor(string)') { + $inputTensorValues = $this->ffi->new("char*[$size]"); + $i = 0; + $this->fillStringTensorValues($input, $inputTensorValues, $refs); + + $typeEnum = $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING; + $this->checkStatus(($this->api->CreateTensorAsOrtValue)($this->allocator, $inputNodeShape, $ndim, $typeEnum, \FFI::addr($inputTensor[$idx]))); + $this->checkStatus(($this->api->FillStringTensor)($inputTensor[$idx], $inputTensorValues, count($inputTensorValues))); + } else { + + $inputTypes = array_flip(array_map(function($v) { + return "tensor($v)"; + }, $this->elementDataTypes())); + + if (isset($inputTypes[$inp['type']])) { + $typeEnum = $inputTypes[$inp['type']]; + $castType = $this->castTypes()[$typeEnum]; + } else { + $this->unsupportedType('input', $inp['type']); + } + + if ($size === 0) { + $inputTensorValues = $this->ffi->new("void *"); + } else { + $inputTensorValues = $this->ffi->new("{$castType}[$size]"); + } + + $inputString = $input->toString(); + + $refs[] = $inputTensorValues; + \FFI::memcpy($inputTensorValues, $inputString, strlen($inputString)); + + $this->checkStatus( + ($this->api->CreateTensorWithDataAsOrtValue)( + $allocatorInfo, + $inputTensorValues, + \FFI::sizeof($inputTensorValues), + $inputNodeShape, + $ndim, + $typeEnum, + \FFI::addr($inputTensor[$idx]) + ) + ); + + $refs[] = $inputNodeShape; + } + $idx++; + } + + // TODO use finally + ($this->api->ReleaseMemoryInfo)($allocatorInfo); + + return $inputTensor; + } + + private function fillStringTensorValues(TensorInterface $input, $ptr, &$refs) + { + foreach ($input->buffer() as $i => $v) { + $strPtr = $this->cstring($v); + $ptr[$i] = $strPtr; + $refs[] = $strPtr; + } + } + + private function createNodeNames($names, &$refs) + { + $namesSize = count($names); + $ptr = $this->ffi->new("char*[$namesSize]"); + foreach ($names as $i => $name) { + $strPtr = $this->cstring($name); + $ptr[$i] = $strPtr; + $refs[] = $strPtr; + } + return $ptr; + } + + private function cstring($str) + { + $bytes = strlen($str) + 1; + // TODO fix? + $ptr = $this->ffi->new("char[$bytes]", false); + \FFI::memcpy($ptr, $str, $bytes - 1); + $ptr[$bytes - 1] = "\0"; + return $ptr; + } + + private function createFromOnnxValue($outPtr) + { + try { + $outType = $this->ffi->new('ONNXType'); + $this->checkStatus(($this->api->GetValueType)($outPtr, \FFI::addr($outType))); + + if ($outType->cdata == $this->ffi->ONNX_TYPE_TENSOR) { + $typeinfo = $this->ffi->new('OrtTensorTypeAndShapeInfo*'); + $this->checkStatus(($this->api->GetTensorTypeAndShape)($outPtr, \FFI::addr($typeinfo))); + + list($type, $shape) = $this->tensorTypeAndShape($typeinfo); + + // TODO skip if string + $tensorData = $this->ffi->new('void*'); + $this->checkStatus(($this->api->GetTensorMutableData)($outPtr, \FFI::addr($tensorData))); + + $outSize = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->GetTensorShapeElementCount)($typeinfo, \FFI::addr($outSize))); + $outputTensorSize = $outSize->cdata; + + ($this->api->ReleaseTensorTypeAndShapeInfo)($typeinfo); + + $castTypes = $this->castTypes(); + if (isset($castTypes[$type])) { + $arr = $this->ffi->cast($castTypes[$type] . "[$outputTensorSize]", $tensorData); + + $dtype = $this->tensorTypes()[$type]; + $stringPtr = \FFI::string($arr, \FFI::sizeof($arr)); + return Tensor::fromString($stringPtr, $dtype, $shape); + } elseif ($type == $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING) { + $arr = $this->createStringsFromOnnxValue($outPtr, $outputTensorSize); + + return Tensor::fromArray($arr, DType::STRING, $shape); + } else { + $this->unsupportedType('element', $type); + } + } elseif ($outType->cdata == $this->ffi->ONNX_TYPE_SEQUENCE) { + $out = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->GetValueCount)($outPtr, \FFI::addr($out))); + + $ret = []; + for ($i = 0; $i < $out->cdata; $i++) { + $seq = $this->ffi->new('OrtValue*'); + $this->checkStatus(($this->api->GetValue)($outPtr, $i, $this->allocator, \FFI::addr($seq))); + $ret[] = $this->createFromOnnxValue($seq); + } + return $ret; + } elseif ($outType->cdata == $this->ffi->ONNX_TYPE_MAP) { + $typeShape = $this->ffi->new('OrtTensorTypeAndShapeInfo*'); + $mapKeys = $this->ffi->new('OrtValue*'); + $mapValues = $this->ffi->new('OrtValue*'); + $elemType = $this->ffi->new('ONNXTensorElementDataType'); + + $this->checkStatus(($this->api->GetValue)($outPtr, 0, $this->allocator, \FFI::addr($mapKeys))); + $this->checkStatus(($this->api->GetValue)($outPtr, 1, $this->allocator, \FFI::addr($mapValues))); + $this->checkStatus(($this->api->GetTensorTypeAndShape)($mapKeys, \FFI::addr($typeShape))); + $this->checkStatus(($this->api->GetTensorElementType)($typeShape, \FFI::addr($elemType))); + + ($this->api->ReleaseTensorTypeAndShapeInfo)($typeShape); + + // TODO support more types + if ($elemType->cdata == $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64) { + $ret = []; + $keys = $this->createFromOnnxValue($mapKeys); + $values = $this->createFromOnnxValue($mapValues); + foreach ($keys as $i => $k) { + $ret[$k] = $values[$i]; + } + return $ret; + } else { + $this->unsupportedType('element', $elemType); + } + } else { + $this->unsupportedType('ONNX', $outType->cdata); + } + } finally { + if (!\FFI::isNull($outPtr)) { + ($this->api->ReleaseValue)($outPtr); + } + } + } + + private function tensorTypes() + { + return [ + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED => 'undefined', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT => DType::FLOAT32, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 => DType::UINT8, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8 => DType::INT8, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16 => DType::UINT16, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16 => DType::INT16, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 => DType::INT32, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 => DType::INT64, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING => DType::STRING, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL => DType::BOOL, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 => DType::FLOAT16, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE => DType::FLOAT64, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32 => DType::UINT32, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 => DType::UINT64, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 => DType::COMPLEX64, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 => DType::COMPLEX128, + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 => DType::BFLOAT16 + ]; + } + + private function createStringsFromOnnxValue($outPtr, $outputTensorSize) + { + $len = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->GetStringTensorDataLength)($outPtr, \FFI::addr($len))); + + $sLen = $len->cdata; + $s = $this->ffi->new("char[$sLen]"); + $offsets = $this->ffi->new("size_t[$outputTensorSize]"); + $this->checkStatus(($this->api->GetStringTensorContent)($outPtr, $s, $sLen, $offsets, $outputTensorSize)); + + $result = []; + foreach ($offsets as $i => $v) { + $start = $v; + $end = $i < $outputTensorSize - 1 ? $offsets[$i + 1] : $sLen; + $size = $end - $start; + $result[] = \FFI::string($s + $start, $size); + } + return $result; + } + + private static function checkStatus($status) + { + if (!is_null($status)) { + $message = (self::api()->GetErrorMessage)($status); + (self::api()->ReleaseStatus)($status); + throw new \Exception($message); + } + } + + private function nodeInfo($typeinfo) + { + $onnxType = $this->ffi->new('ONNXType'); + $this->checkStatus(($this->api->GetOnnxTypeFromTypeInfo)($typeinfo, \FFI::addr($onnxType))); + + if ($onnxType->cdata == $this->ffi->ONNX_TYPE_TENSOR) { + $tensorInfo = $this->ffi->new('OrtTensorTypeAndShapeInfo*'); + // don't free tensor_info + $this->checkStatus(($this->api->CastTypeInfoToTensorInfo)($typeinfo, \FFI::addr($tensorInfo))); + + list($type, $shape) = $this->tensorTypeAndShape($tensorInfo); + $elementDataType = $this->elementDataTypes()[$type]; + return ['type' => "tensor($elementDataType)", 'shape' => $shape]; + } elseif ($onnxType->cdata == $this->ffi->ONNX_TYPE_SEQUENCE) { + $sequenceTypeInfo = $this->ffi->new('OrtSequenceTypeInfo*'); + $this->checkStatus(($this->api->CastTypeInfoToSequenceTypeInfo)($typeinfo, \FFI::addr($sequenceTypeInfo))); + $nestedTypeInfo = $this->ffi->new('OrtTypeInfo*'); + $this->checkStatus(($this->api->GetSequenceElementType)($sequenceTypeInfo, \FFI::addr($nestedTypeInfo))); + $v = $this->nodeInfo($nestedTypeInfo)['type']; + + return ['type' => "seq($v)", 'shape' => []]; + } elseif ($onnxType->cdata == $this->ffi->ONNX_TYPE_MAP) { + $mapTypeInfo = $this->ffi->new('OrtMapTypeInfo*'); + $this->checkStatus(($this->api->CastTypeInfoToMapTypeInfo)($typeinfo, \FFI::addr($mapTypeInfo))); + + // key + $keyType = $this->ffi->new('ONNXTensorElementDataType'); + $this->checkStatus(($this->api->GetMapKeyType)($mapTypeInfo, \FFI::addr($keyType))); + $k = $this->elementDataTypes()[$keyType->cdata]; + + // value + $valueTypeInfo = $this->ffi->new('OrtTypeInfo*'); + $this->checkStatus(($this->api->GetMapValueType)($mapTypeInfo, \FFI::addr($valueTypeInfo))); + $v = $this->nodeInfo($valueTypeInfo)['type']; + + return ['type' => "map($k,$v)", 'shape' => []]; + } else { + $this->unsupportedType('ONNX', $onnxType->cdata); + } + } + + private function castTypes() + { + return [ + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT => 'float', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 => 'uint8_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8 => 'int8_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16 => 'uint16_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16 => 'int16_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 => 'int32_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 => 'int64_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL => 'bool', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE => 'double', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32 => 'uint32_t', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 => 'uint64_t' + ]; + } + + private function elementDataTypes() + { + return [ + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED => 'undefined', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT => 'float', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 => 'uint8', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8 => 'int8', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16 => 'uint16', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16 => 'int16', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 => 'uint32', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 => 'int64', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING => 'string', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL => 'bool', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 => 'float16', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE => 'double', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32 => 'uint32', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 => 'uint64', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 => 'complex64', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 => 'complex128', + $this->ffi->ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 => 'bfloat16' + ]; + } + + private function tensorTypeAndShape($tensorInfo) + { + $type = $this->ffi->new('ONNXTensorElementDataType'); + $this->checkStatus(($this->api->GetTensorElementType)($tensorInfo, \FFI::addr($type))); + + $numDimsPtr = $this->ffi->new('size_t'); + $this->checkStatus(($this->api->GetDimensionsCount)($tensorInfo, \FFI::addr($numDimsPtr))); + $numDims = $numDimsPtr->cdata; + + if ($numDims > 0) { + $nodeDims = $this->ffi->new("int64_t[$numDims]"); + $this->checkStatus(($this->api->GetDimensions)($tensorInfo, $nodeDims, $numDims)); + $dims = $this->readArray($nodeDims); + + $symbolicDims = $this->ffi->new("char*[$numDims]"); + $this->checkStatus(($this->api->GetSymbolicDimensions)($tensorInfo, $symbolicDims, $numDims)); + for ($i = 0; $i < $numDims; $i++) { + $namedDim = \FFI::string($symbolicDims[$i]); + if ($namedDim != '') { + $dims[$i] = $namedDim; + } + } + } else { + $dims = []; + } + + return [$type->cdata, $dims]; + } + + private function unsupportedType($name, $type) + { + throw new \Exception("Unsupported $name type: $type"); + } + + private function readArray($cdata) + { + $arr = []; + $n = count($cdata); + for ($i = 0; $i < $n; $i++) { + $arr[] = $cdata[$i]; + } + return $arr; + } + + private function allocatorFree($ptr) + { + ($this->api->AllocatorFree)($this->allocator, $ptr); + } + + private static function api() + { + return (FFI::instance()->OrtGetApiBase()[0]->GetApi)(11)[0]; + } + + // wide string on Windows + // char string on Linux + // see ORTCHAR_T in onnxruntime_c_api.h + private function ortString($str) + { + if (PHP_OS == 'WINNT' || PHP_OS == 'WIN32' || PHP_OS == 'Windows') { + $libc = FFI::libc(); + $max = strlen($str) + 1; // for null byte + // wchar_t not supported + // use char instead of casting later + // since FFI::cast only references data + $dest = $libc->new('char[' . ($max * 2) . ']'); + $ret = $libc->mbstowcs($dest, $str, $max); + if ($ret != strlen($str)) { + throw new \Exception('Expected mbstowcs to return ' . strlen($str) . ", got $ret"); + } + return $dest; + } else { + return $str; + } + } + + private static function env() + { + // TODO use mutex for thread-safety + // TODO memoize + + $env = FFI::instance()->new('OrtEnv*'); + (self::api()->CreateEnv)(3, 'Default', \FFI::addr($env)); + // disable telemetry + // https://github.com/microsoft/onnxruntime/blob/master/docs/Privacy.md + self::checkStatus((self::api()->DisableTelemetryEvents)($env)); + return $env; + } +} diff --git a/upload/vendor/veka-server/onnx-php/src/Library.php b/upload/vendor/veka-server/onnx-php/src/Library.php new file mode 100644 index 000000000..7d831edd3 --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/src/Library.php @@ -0,0 +1,141 @@ + [ + 'file' => 'onnxruntime-linux-x64-{{version}}', + 'checksum' => 'fa4d11b3fa1b2bf1c3b2efa8f958634bc34edc95e351ac2a0408c6ad5c5504f0', + 'lib' => 'libonnxruntime.so.{{version}}', + 'ext' => 'tgz' + ], + 'aarch64-linux' => [ + 'file' => 'onnxruntime-linux-aarch64-{{version}}', + 'checksum' => 'c278ca7ce725d2b26cf1ec62c93affaec4145a9a3d7721fb5d1af5497136ca76', + 'lib' => 'libonnxruntime.so.{{version}}', + 'ext' => 'tgz' + ], + 'x86_64-darwin' => [ + 'file' => 'onnxruntime-osx-x86_64-{{version}}', + 'checksum' => '3af96893675b295e5e0eb886f470de585089f92f9950158d042fbc02b44ed101', + 'lib' => 'libonnxruntime.{{version}}.dylib', + 'ext' => 'tgz' + ], + 'arm64-darwin' => [ + 'file' => 'onnxruntime-osx-arm64-{{version}}', + 'checksum' => 'c5ff520d2913e3360670979ca4fe43717fc3aa0c0c367a75fbb6f2f15c0cb48d', + 'lib' => 'libonnxruntime.{{version}}.dylib', + 'ext' => 'tgz' + ], + 'x64-windows' => [ + 'file' => 'onnxruntime-win-x64-{{version}}', + 'checksum' => 'a91af21ca8f9bdfa5a1aac3fdd0591384b4e2866d41612925f1758d5522829e7', + 'lib' => 'onnxruntime.dll', + 'ext' => 'zip' + ] + ]; + + public static function install($event = null) + { + $dest = self::defaultLib(); + if (file_exists($dest)) { + return; + } + + $dir = self::libDir(); + if (!file_exists($dir)) { + mkdir($dir); + } + + $file = self::platform('file'); + $ext = self::platform('ext'); + $url = self::withVersion("https://github.com/microsoft/onnxruntime/releases/download/v{{version}}/$file.$ext"); + $contents = file_get_contents($url); + + $checksum = hash('sha256', $contents); + if ($checksum != self::platform('checksum')) { + throw new \Exception("Bad checksum: $checksum"); + } + + $tempDest = tempnam(sys_get_temp_dir(), 'onnxruntime') . '.' . $ext; + file_put_contents($tempDest, $contents); + + $archive = new \PharData($tempDest); + if ($ext != 'zip') { + $archive = $archive->decompress(); + } + $archive->extractTo(self::libDir()); + } + + /** + * @throws OnnxException + */ + public static function ThrowExceptionIfLibNotFound() + { + $dest = self::defaultLib(); + if (!file_exists($dest)) { + throw new OnnxException('OnnxRuntime not found'); + } + } + + public static function defaultLib() + { + return self::libDir() . DIRECTORY_SEPARATOR . self::libFile(); + } + + private static function libDir() + { + return ( self::$folder ?? dirname(__DIR__)).DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR ; + } + + public static function setFolder(String $path){ + + if(!is_dir($path)){ + mkdir($path); + } + + $path = realpath($path); + + self::$folder = $path; + FFI::$lib = self::defaultLib(); + } + + private static function libFile() + { + return self::withVersion(self::platform('file') . DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR . self::platform('lib')); + } + + private static function platform($key) + { + return self::PLATFORMS[self::platformKey()][$key]; + } + + private static function platformKey() + { + if (PHP_OS == 'WINNT' || PHP_OS == 'WIN32' || PHP_OS == 'Windows') { + return 'x64-windows'; + } elseif (PHP_OS == 'Darwin') { + if (php_uname('m') == 'x86_64') { + return 'x86_64-darwin'; + } else { + return 'arm64-darwin'; + } + } else { + if (php_uname('m') == 'x86_64') { + return 'x86_64-linux'; + } else { + return 'aarch64-linux'; + } + } + } + + private static function withVersion($str) + { + return str_replace('{{version}}', self::VERSION, $str); + } +} diff --git a/upload/vendor/veka-server/onnx-php/src/Model.php b/upload/vendor/veka-server/onnx-php/src/Model.php new file mode 100644 index 000000000..6242af2fd --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/src/Model.php @@ -0,0 +1,41 @@ +session = new InferenceSession($path, ...$sessionOptions); + } + + public function predict($inputFeed, $outputNames = null, ...$runOptions) + { + // Check if $outputNames is not set or null + if (!isset($outputNames)) { + $outputNames = array_map(function($o) { + return $o['name']; + }, $this->outputs()); + } + $predictions = $this->session->run($outputNames, $inputFeed, ...$runOptions); + return array_combine($outputNames, $predictions); + } + + public function inputs() + { + return $this->session->inputs(); + } + + public function outputs() + { + return $this->session->outputs(); + } + + public function metadata() + { + return $this->session->modelmeta(); + } +} diff --git a/upload/vendor/veka-server/onnx-php/src/OnnxException.php b/upload/vendor/veka-server/onnx-php/src/OnnxException.php new file mode 100644 index 000000000..80dce54c1 --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/src/OnnxException.php @@ -0,0 +1,8 @@ +dtype = $dtype; + $this->shape = $shape; + $this->buffer = $buffer; + } + + /** + * Create a Tensor instance from a PHP array. + * + * @param array $array The input array. + * @param mixed $dtype The data type of the tensor (optional). + * @param array|null $shape The shape of the tensor (optional). + * @return static The created Tensor instance. + * @throws InvalidArgumentException If the shape isn't provided when the array is empty. + */ + public static function fromArray(array $array, $dtype = null, $shape = null) + { + if (empty($array) && $shape === null) { + throw new InvalidArgumentException('Shape must be provided when the array is empty'); + } + + if (!isset($shape)) { + $shape = self::generateShape($array); + } + $buffer = []; + $index = 0; + + self::flattenArray($array, $buffer, $index, $dtype); + + return new static($buffer, $shape, $dtype); + } + + /** + * Create a Tensor instance from a packed binary string. + * + * @param string $string The packed binary string containing the tensor data (flat) + * @param DType $dtype The data type of the tensor. + * @param array $shape The shape of the tensor. + * @return static The created Tensor instance. + * @throws RuntimeException If an error occurs during string unpacking. + * @throws InvalidArgumentException If the number of elements in the string does not match the shape. + * @throws \Exception + */ + public static function fromString(string $string, $dtype, array $shape) + { + $data = unpack(DType::packFormat($dtype), $string); + + if ($data === false) { + throw new RuntimeException('Error unpacking string data'); + } + + if (count($data) != array_product($shape)) { + throw new InvalidArgumentException('The number of elements in the string does not match the shape'); + } + + $buffer = []; + foreach ($data as $i => $value) { + $buffer[$i - 1] = DType::castValue($dtype, $value); + } + + return new static($buffer, $shape, $dtype); + } + + public function shape() :array + { + return $this->shape; + } + + public function ndim() :int + { + return count($this->shape); + } + + public function dtype() + { + return $this->dtype; + } + + public function buffer() :array + { + return $this->buffer; + } + + public function size() :int + { + return array_product($this->shape); + } + + public function reshape(array $shape) + { + if (array_product($shape) != array_product($this->shape)) { + throw new InvalidArgumentException('New shape must have the same number of elements'); + } + + return new static($this->buffer, $shape, $this->dtype); + } + + public function toArray() :array + { + $i = 0; + return self::unflattenArray($this->buffer, $this->shape, $i); + } + + public function toString() :string + { + // Récupération du format de pack avant la boucle pour éviter de l'appeler à chaque itération + $packFormat = DType::packFormat($this->dtype); + + $packedValues = ''; + + foreach ($this->buffer as $value) { + // Stockage des valeurs packées dans le tableau + $packedValues .= pack($packFormat, $value); + } + + return $packedValues; + } + + public function count() :int + { + return $this->shape[0]; + } + + public static function generateShape(array $array) :array + { + $shape = []; + + while (is_array($array)) { + $shape[] = count($array); + $array = reset($array); + } + + return $shape; + } + + public static function flattenArray(array $nestedArray, array &$buffer, int &$index, $dtype) + { + foreach ($nestedArray as $value) { + if (is_array($value)) { + self::flattenArray($value, $buffer, $index, $dtype); + } else { + $buffer[$index++] = $value; + } + } + } + + public static function unflattenArray(array &$buffer, array $shape, int &$index) :array + { + if (array_product($shape) === 0) { + return []; + } + + $nestedArray = []; + $size = array_shift($shape); + + for ($i = 0; $i < $size; $i++) { + $nestedArray[] = empty($shape) ? $buffer[$index++] : self::unflattenArray($buffer, $shape, $index); + } + + return $nestedArray; + } + + public function getIterator() :Traversable + { + if (empty($this->shape)) { + return new EmptyIterator(); + } + + for ($i = 0; $i < $this->count(); $i++) { + yield $i => $this->offsetGet($i); + } + } + + public function offsetExists($offset) :bool + { + return $offset >= 0 && $offset < $this->shape[0]; + } + + public function offsetGet($offset) + { + if (!$this->offsetExists($offset)) { + throw new OutOfBoundsException('Index out of bounds'); + } + + if (count($this->shape) === 1) { + return $this->buffer[$offset]; + } + + $newShape = array_slice($this->shape, 1); + $newSize = array_product($newShape); + $buffer = []; + + for ($i = 0; $i < $newSize; $i++) { + $buffer[$i] = $this->buffer[$offset * $newSize + $i]; + } + + return new self($buffer, $newShape, $this->dtype); + } + + public function offsetSet($offset, $value) + { + if (!$this->offsetExists($offset)) { + throw new OutOfBoundsException('Index out of bounds'); + } + + if (count($this->shape) === 0) { + if (!is_scalar($value)) { + throw new InvalidArgumentException('Value must be scalar'); + } + $this->buffer[$offset] = $value; + return; + } + + if (!($value instanceof self) || $value->shape() !== array_slice($this->shape, 1)) { + throw new InvalidArgumentException('Value must be a tensor with the same sub-shape'); + } + + $buffer = $value->buffer(); + $newSize = array_product(array_slice($this->shape, 1)); + + for ($i = 0; $i < $newSize; $i++) { + $this->buffer[$offset * $newSize + $i] = $buffer[$i]; + } + } + + public function offsetUnset( $offset) + { + throw new RuntimeException('Cannot unset tensor elements'); + } + + + /** + * Transpose tensor data from BHWC to BCHW. + * + * @return static The transposed Tensor instance. + * @throws InvalidArgumentException If the tensor does not have exactly 4 dimensions. + */ + public function transposeBhwcToBchw() + { + if (count($this->shape) !== 4) { + throw new InvalidArgumentException('Tensor must have exactly 4 dimensions to transpose from BHWC to BCHW'); + } + + list($batch, $height, $width, $channels) = $this->shape; + $newShape = [$batch, $channels, $height, $width]; + $newBuffer = array_fill(0, array_product($newShape), 0); + + for ($b = 0; $b < $batch; $b++) { + for ($h = 0; $h < $height; $h++) { + for ($w = 0; $w < $width; $w++) { + for ($c = 0; $c < $channels; $c++) { + $oldIndex = (($b * $height + $h) * $width + $w) * $channels + $c; + $newIndex = (($b * $channels + $c) * $height + $h) * $width + $w; + $newBuffer[$newIndex] = $this->buffer[$oldIndex]; + } + } + } + } + + return new static($newBuffer, $newShape, $this->dtype); + } + +} diff --git a/upload/vendor/veka-server/onnx-php/src/TensorInterface.php b/upload/vendor/veka-server/onnx-php/src/TensorInterface.php new file mode 100644 index 000000000..da2203874 --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/src/TensorInterface.php @@ -0,0 +1,30 @@ +fallback = $this->getInitialCurrentWorkingDirectory(); + } + + /** + * @return non-empty-string|null + */ + private function getInitialCurrentWorkingDirectory() + { + if ($directory = \getcwd()) { + return $directory; + } + + if (isset($_SERVER['SCRIPT_NAME']) && $directory = \dirname($_SERVER['SCRIPT_NAME'])) { + return $directory; + } + + if (isset($_SERVER['SCRIPT_FILENAME']) && $directory = \dirname($_SERVER['SCRIPT_FILENAME'])) { + return $directory; + } + + if (isset($_SERVER['PHP_SELF']) && $directory = \dirname($_SERVER['PHP_SELF'])) { + return $directory; + } + + return null; + } + + /** + * @return array{fallback:non-empty-string|null} + */ + public function __serialize(): array + { + return ['fallback' => $this->fallback]; + } + + /** + * @param array{fallback:non-empty-string|null} $data + */ + public function __unserialize(array $data) + { + $this->fallback = $data['fallback'] ?? $this->getInitialCurrentWorkingDirectory(); + } +} diff --git a/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/UnixAwareThreadSafeDriver.php b/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/UnixAwareThreadSafeDriver.php new file mode 100644 index 000000000..d335407da --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/UnixAwareThreadSafeDriver.php @@ -0,0 +1,73 @@ +boot(); + } + + private function boot() + { + $this->ffi = \FFI::cdef(self::STDLIB); + } + + /** + * @return string + */ + abstract protected static function getEnvVariableName(); + + public function get() + { + /** + * Note: The getenv function returns a pointer to a string associated + * with the matched list member. The string pointed to shall not be + * modified by the program, but may be overwritten by a subsequent + * call to the getenv function. + * + * @var CData $directory + */ + $directory = $this->ffi->getenv(static::getEnvVariableName()); + + if ($directory === null) { + return $this->fallback; + } + + return \FFI::string($directory) ?: $this->fallback; + } + + public function set($directory):bool + { + return $this->ffi->setenv(static::getEnvVariableName(), $directory, 1) === 0; + } + + public function __unserialize(array $data) + { + parent::__unserialize($data); + $this->boot(); + } +} diff --git a/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/WindowsThreadSafeDriver.php b/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/WindowsThreadSafeDriver.php new file mode 100644 index 000000000..b4446b394 --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/work-directory/src/Driver/WindowsThreadSafeDriver.php @@ -0,0 +1,143 @@ +internal = $this->getDefaultInternalEncoding(); + $this->external = $this->getDefaultExternalEncoding(); + + $this->boot(); + } + + private function getDefaultInternalEncoding() + { + return \mb_internal_encoding() ?: self::DEFAULT_INTERNAL_ENCODING; + } + + private function getDefaultExternalEncoding() + { + return 'UTF-16' . (\unpack('S', "\x01\x00")[1] === 1 ? 'LE' : 'BE'); + } + + private function boot() + { + $this->ffi = \FFI::cdef(self::KERNEL32, 'kernel32.dll'); + } + + public function get() + { + $bufferSizeDiv2 = self::DEFAULT_EXPECTED_BUFFER_SIZE; + $uint16Array = $this->ffi->new("uint16_t[$bufferSizeDiv2]", false); + $uint16ArrayPointer = \FFI::addr($uint16Array[0]); + + $length = $this->ffi->GetDllDirectoryW(self::DEFAULT_EXPECTED_BUFFER_SIZE, $uint16Array); + $result = null; + + if ($length !== 0) { + $char8Array = $this->ffi->cast("char*", $uint16ArrayPointer); + $char8ArrayPointer = \FFI::addr($char8Array[0]); + + $result = \FFI::string($char8ArrayPointer, $length * 2); + $result = \mb_convert_encoding($result, $this->internal, $this->external); + } + + try { + return $result ?: $this->fallback; + } finally { + \FFI::free($uint16Array); + } + } + + public function set($directory) :bool + { + if (\mb_detect_encoding($directory, 'ASCII', true)) { + return $this->ffi->SetDllDirectoryA($directory) !== 0; + } + + $directory = \mb_convert_encoding($directory, $this->external, $this->internal) . "\0\0"; + + $bytes = \strlen($directory); + $charArray = $this->ffi->new("char[$bytes]", false); + $charArrayPointer = \FFI::addr($charArray[0]); + + \FFI::memcpy($charArrayPointer, $directory, $bytes); + + $bytesDiv2 = (int)\ceil($bytes / 2); + $uint16Array = \FFI::cast("uint16_t[$bytesDiv2]", $charArray); + $uint16ArrayPointer = \FFI::addr($uint16Array[0]); + + try { + return $this->ffi->SetDllDirectoryW(\FFI::addr($uint16Array[0])) !== 0; + } finally { + \FFI::free($uint16ArrayPointer); + } + } + + public function __serialize() :array + { + return \array_merge(parent::__serialize(), [ + 'internal' => $this->internal, + 'external' => $this->external, + ]); + } + + public function __unserialize(array $data) + { + parent::__unserialize($data); + + $this->internal = isset($data['internal']) ? $data['internal'] : $this->getDefaultInternalEncoding(); + $this->external = isset($data['external']) ? $data['external'] : $this->getDefaultExternalEncoding(); + + $this->boot(); + } +} diff --git a/upload/vendor/veka-server/onnx-php/work-directory/src/WorkDirectory.php b/upload/vendor/veka-server/onnx-php/work-directory/src/WorkDirectory.php new file mode 100644 index 000000000..e80d49e5d --- /dev/null +++ b/upload/vendor/veka-server/onnx-php/work-directory/src/WorkDirectory.php @@ -0,0 +1,74 @@ +get(); + } + + /** + * Change directory. + * + * @psalm-taint-sink file $directory + * @param non-empty-string $directory The new current directory. + * + * @return bool Returns {@see true} on success or {@see false} on failure. + */ + public static function set(string $directory): bool + { + $driver = self::driver(); + + return $driver->set($directory); + } +}