From f0e98c1e359f985d1337cdfa4b78ed3bb7404a2b Mon Sep 17 00:00:00 2001 From: Hennzau Date: Fri, 2 Aug 2024 18:32:32 +0200 Subject: [PATCH 01/11] add arrow.rs module to manipulate UnionArrays + add ImageDataType --- Cargo.lock | 852 +++++++++++++++++++++++++- Cargo.toml | 5 +- README.md | 28 + examples/dummy-opencv-capture/main.rs | 68 ++ src/arrow.rs | 46 ++ src/image.rs | 259 ++++++++ src/lib.rs | 29 + 7 files changed, 1285 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 examples/dummy-opencv-capture/main.rs create mode 100644 src/arrow.rs create mode 100644 src/image.rs diff --git a/Cargo.lock b/Cargo.lock index a3cde12..0b42c06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,49 +2,592 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "const-random", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrow" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05048a8932648b63f21c37d88b552ccc8a65afb6dfe9fc9f30ce79174c2e7a85" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a57966e43bfe9a3277984a14c24ec617ad874e4c0e1d2a1b083a39cfbf22c" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "num", +] + +[[package]] +name = "arrow-array" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f4a9468c882dc66862cef4e1fd8423d47e67972377d85d80e022786427768c" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "hashbrown", + "num", +] + +[[package]] +name = "arrow-buffer" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c975484888fc95ec4a632cdc98be39c085b1bb518531b0c80c5d462063e5daa1" +dependencies = [ + "bytes", + "half", + "num", +] + +[[package]] +name = "arrow-cast" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da26719e76b81d8bc3faad1d4dbdc1bcc10d14704e63dc17fc9f3e7e1e567c8e" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "half", + "lexical-core", + "num", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13c36dc5ddf8c128df19bab27898eea64bf9da2b555ec1cd17a8ff57fba9ec2" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "lazy_static", + "lexical-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd9d6f18c65ef7a2573ab498c374d8ae364b4a4edf67105357491c031f716ca5" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num", +] + +[[package]] +name = "arrow-ipc" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e786e1cdd952205d9a8afc69397b317cfbb6e0095e445c69cda7e8da5c1eeb0f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "flatbuffers", +] + +[[package]] +name = "arrow-json" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb22284c5a2a01d73cebfd88a33511a3234ab45d66086b2ca2d1228c3498e445" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap", + "lexical-core", + "num", + "serde", + "serde_json", +] + +[[package]] +name = "arrow-ord" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42745f86b1ab99ef96d1c0bcf49180848a64fe2c7a7a0d945bc64fa2b21ba9bc" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "half", + "num", +] + +[[package]] +name = "arrow-row" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd09a518c602a55bd406bcc291a967b284cfa7a63edfbf8b897ea4748aad23c" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e972cd1ff4a4ccd22f86d3e53e835c2ed92e0eea6a3e8eadb72b4f1ac802cf8" + +[[package]] +name = "arrow-select" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600bae05d43483d216fb3494f8c32fdbefd8aa4e1de237e790dbb3d9f44690a3" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", +] + +[[package]] +name = "arrow-string" +version = "52.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc1985b67cb45f6606a248ac2b4a288849f196bab8c657ea5589f47cdd55e6" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastformat" version = "0.1.0" dependencies = [ + "arrow", + "eyre", + "ndarray", "pyo3", ] +[[package]] +name = "flatbuffers" +version = "24.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "lock_api" version = "0.4.12" @@ -55,6 +598,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "memoffset" version = "0.9.1" @@ -64,6 +629,93 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -180,27 +832,121 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "2.0.72" @@ -218,6 +964,15 @@ version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4873307b7c257eddcb50c9bedf158eb669578359fb28428bef438fec8e6ba7c2" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -230,6 +985,81 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -293,3 +1123,23 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index f374e82..2e22451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,10 @@ description = "Fast world data converter" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "fastformat" -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] [dependencies] pyo3 = "0.21.1" +ndarray = "0.15.4" +arrow = "52.1.0" +eyre = "0.6.12" diff --git a/README.md b/README.md new file mode 100644 index 0000000..87b1b11 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +## Project Objective + +The goal is to create an efficient way to reconstruct and process data in real-time, in specific formats like NDarray, +Numpy, or Arrow, without unnecessary copies. + +- **Independent Library**: We are creating an independent library for our formats, which everyone could use even without + DORA. The current name is fastformat: + +- **Agnosticism**: We want our solution to be agnostic to Arrow, meaning it can work with Numpy and other data types + easily by implementing to_[format] functions. + +- **Rust and Python Code**: The main implementation will be in Rust for performance and portability reasons, with a + Python interface using PyO3. This will allow for fast vector manipulation and potentially GPU support in the future. + +- **Portability and Dependencies**: By using Rust, we aim to minimize dependencies and maximize portability. This will + allow us to achieve optimal performance across different platforms. + +- **Simplicity**: The goal is not to create yet another library for creating formats to store/process data. The aim here + is to create a simple interface to wrap data from one type and convert it to another. + +## DataTypes + +- **Image** as a **UnionArray**, + - Field "pixels': UintXArray (e.g [0, 255, 0, 255, 0, 255, ...]) + - Field "width": Uint32Array (e.g [1280]) + - Field "height": Uint32Array (e.g [720]) + - Field "encoding": StringArray (e.g ["RGB8"]) + - Field "name" (Optional): StringArray (e.g,["image.front_camera"] or [None]) diff --git a/examples/dummy-opencv-capture/main.rs b/examples/dummy-opencv-capture/main.rs new file mode 100644 index 0000000..d767f14 --- /dev/null +++ b/examples/dummy-opencv-capture/main.rs @@ -0,0 +1,68 @@ +extern crate fastformat; + +use fastformat::image::Image; + +fn camera_read() -> ndarray::Array { + // Dummy camera read + + let flat_image = (1..28).collect::>(); + println!( + "Generate a camera image at address: {:?}", + flat_image.as_ptr() + ); + + let image = Image::new_bgr8(flat_image, 3, 3, None); + + return image.to_nd_array().unwrap(); +} + +fn image_show(_frame: ndarray::ArrayView) { + // Dummy image show + + println!("Showing an image."); +} + +fn send_output(arrow_array: arrow::array::UnionArray) { + // Dummy send output + + let image = Image::from_arrow(arrow_array).unwrap(); + + println!( + "Sending an image to dataflow. Image address is: {:?}", + image.as_ptr() + ); +} + +fn main() { + // Read OpenCV Camera, default is nd_array BGR8 + let frame = camera_read(); + + let image = Image::from_bgr8_nd_array(frame, Some("camera.left")); + + // Convert to RGB8, apply some filter (Black and White). + let mut frame = image.to_rgb().to_nd_array().unwrap(); + + for i in 0..frame.shape()[0] { + for j in 0..frame.shape()[1] { + let mean = + (frame[[i, j, 0]] as f32 + frame[[i, j, 1]] as f32 + frame[[i, j, 2]] as f32) / 3.0; + + if mean > 128.0 { + frame[[i, j, 0]] = 255; + frame[[i, j, 1]] = 255; + frame[[i, j, 2]] = 255; + } else { + frame[[i, j, 0]] = 0; + frame[[i, j, 1]] = 0; + frame[[i, j, 2]] = 0; + } + } + } + + let image = Image::from_rgb8_nd_array(frame, Some("camera.left.baw")); + + // Plot the image, you may only need a nd array view + image_show(image.nd_array_view().unwrap()); + + send_output(image.to_arrow().unwrap()); +} diff --git a/src/arrow.rs b/src/arrow.rs new file mode 100644 index 0000000..9144850 --- /dev/null +++ b/src/arrow.rs @@ -0,0 +1,46 @@ +use arrow::datatypes::DataType; + +use std::{collections::HashMap, sync::Arc}; + +use eyre::{ContextCompat, Result}; + +pub fn union_look_up_table(fields: &arrow::datatypes::UnionFields) -> HashMap { + let mut result = HashMap::new(); + + for field in fields.iter() { + let (a, b) = field; + + result.insert(b.name().to_string(), a); + } + + return result; +} + +pub fn column_by_name<'a, T: 'static>( + array: &'a arrow::array::UnionArray, + field: &'a str, + look_up_table: &'a HashMap, +) -> Result<&'a T> { + let index = look_up_table + .get(field) + .cloned() + .wrap_err(format!("Couldn't get field {} from look_up table", field))?; + + return array + .child(index) + .as_any() + .downcast_ref::() + .wrap_err(format!("Couldn't downcast field {} to type T", field)); +} + +pub fn union_field( + index: i8, + name: &str, + data_type: DataType, + nullable: bool, +) -> (i8, Arc) { + ( + index, + Arc::new(arrow::datatypes::Field::new(name, data_type, nullable)), + ) +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..eb590dc --- /dev/null +++ b/src/image.rs @@ -0,0 +1,259 @@ +use crate::arrow::{column_by_name, union_field, union_look_up_table}; + +use std::{mem, sync::Arc}; + +use eyre::{Context, Report, Result}; + +#[derive(PartialEq, Debug)] +enum Encoding { + BGR8, + RGB8, +} + +impl Encoding { + fn to_string(&self) -> String { + match self { + Self::BGR8 => "BGR8".to_string(), + Self::RGB8 => "RGB8".to_string(), + } + } + + fn from_string(encoding: &str) -> Result { + match encoding { + "BGR8" => Ok(Self::BGR8), + "RGB8" => Ok(Self::RGB8), + _ => Err(Report::msg(format!( + "Encoding {} is not supported.", + encoding + ))), + } + } +} + +#[derive(Debug)] +pub struct Image { + pixels: Vec, + + width: u32, + height: u32, + encoding: Encoding, + + name: Option, +} + +impl Image { + pub fn as_ptr(&self) -> *const u8 { + self.pixels.as_ptr() as *const u8 + } + + pub fn to_nd_array(self) -> Result> { + match self.encoding { + Encoding::BGR8 => ndarray::Array::from_shape_vec( + (self.height as usize, self.width as usize, 3), + self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + Encoding::RGB8 => ndarray::Array::from_shape_vec( + (self.height as usize, self.width as usize, 3), + self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + } + } + + pub fn nd_array_view(&self) -> Result> { + match self.encoding { + Encoding::BGR8 => ndarray::ArrayView::from_shape( + (self.height as usize, self.width as usize, 3), + &self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + Encoding::RGB8 => ndarray::ArrayView::from_shape( + (self.height as usize, self.width as usize, 3), + &self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + } + } + + pub fn nd_array_view_mut(&mut self) -> Result> { + match self.encoding { + Encoding::BGR8 => ndarray::ArrayViewMut::from_shape( + (self.height as usize, self.width as usize, 3), + &mut self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + Encoding::RGB8 => ndarray::ArrayViewMut::from_shape( + (self.height as usize, self.width as usize, 3), + &mut self.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + } + } +} + +impl Image { + pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { + Self { + pixels, + width, + height, + encoding: Encoding::RGB8, + name: name.map(|s| s.to_string()), + } + } + + pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { + Self { + pixels, + width, + height, + encoding: Encoding::BGR8, + name: name.map(|s| s.to_string()), + } + } + + pub fn to_bgr(self) -> Self { + if self.encoding == Encoding::BGR8 { + return self; + } + + let mut pixels = self.pixels; + + for i in (0..pixels.len()).step_by(3) { + pixels.swap(i, i + 2); + } + + Self { + pixels, + width: self.width, + height: self.height, + encoding: Encoding::BGR8, + name: self.name, + } + } + + pub fn to_rgb(self) -> Self { + if self.encoding == Encoding::RGB8 { + return self; + } + + let mut pixels = self.pixels; + + for i in (0..pixels.len()).step_by(3) { + pixels.swap(i, i + 2); + } + + Self { + pixels, + width: self.width, + height: self.height, + encoding: Encoding::RGB8, + name: self.name, + } + } +} + +impl Image { + pub fn from_rgb8_nd_array(array: ndarray::Array, name: Option<&str>) -> Self { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_rgb8(pixels, width, height, name) + } + + pub fn from_bgr8_nd_array(array: ndarray::Array, name: Option<&str>) -> Self { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_bgr8(pixels, width, height, name) + } +} + +impl Image { + pub fn from_arrow(array: arrow::array::UnionArray) -> Result { + use arrow::array::Array; + + let union_fields = match array.data_type() { + arrow::datatypes::DataType::Union(fields, ..) => fields, + _ => { + return Err(Report::msg("UnionArray has invalid data type.")); + } + }; + + let look_up_table = union_look_up_table(&union_fields); + + let width = + column_by_name::(&array, "width", &look_up_table)?.value(0); + let height = + column_by_name::(&array, "height", &look_up_table)?.value(0); + let encoding = Encoding::from_string( + &column_by_name::(&array, "encoding", &look_up_table)? + .value(0), + )?; + + let name = column_by_name::(&array, "name", &look_up_table)?; + + let name = if name.is_null(0) { + None + } else { + Some(name.value(0).to_string()) + }; + + let name = name.as_ref().map(|s| s.as_str()); + + unsafe { + let array = mem::ManuallyDrop::new(array); + let pixels = + column_by_name::(&array, "pixels", &look_up_table)?; + + let ptr = pixels.values().as_ptr(); + let len = pixels.len(); + + let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); + + return match encoding { + Encoding::RGB8 => Ok(Self::new_rgb8(pixels, width, height, name)), + Encoding::BGR8 => Ok(Self::new_bgr8(pixels, width, height, name)), + }; + } + } + + pub fn to_arrow(self) -> Result { + let pixels = arrow::array::UInt8Array::from(self.pixels); + + let width = arrow::array::UInt32Array::from(vec![self.width; 1]); + let height = arrow::array::UInt32Array::from(vec![self.height; 1]); + let encoding = arrow::array::StringArray::from(vec![self.encoding.to_string(); 1]); + + let name = arrow::array::StringArray::from(vec![self.name; 1]); + + let type_ids = [].into_iter().collect::>(); + let offsets = [].into_iter().collect::>(); + + let union_fields = [ + union_field(0, "pixels", arrow::datatypes::DataType::UInt8, false), + union_field(1, "width", arrow::datatypes::DataType::UInt32, false), + union_field(2, "height", arrow::datatypes::DataType::UInt32, false), + union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), + union_field(4, "name", arrow::datatypes::DataType::Utf8, true), + ] + .into_iter() + .collect::(); + + let children: Vec> = vec![ + Arc::new(pixels), + Arc::new(width), + Arc::new(height), + Arc::new(encoding), + Arc::new(name), + ]; + + arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) + .wrap_err("Failed to create UnionArray") + } +} diff --git a/src/lib.rs b/src/lib.rs index c343935..fd00f2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ use pyo3::prelude::*; +pub mod arrow; + +pub mod image; + /// Formats the sum of two numbers as string. #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult { @@ -12,3 +16,28 @@ fn fastformat(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) } + +mod tests { + #[test] + fn test_zero_copy_image_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let image = Image::new_bgr8(flat_image, 3, 3, None); + + let frame = image.to_nd_array().unwrap(); + let image = Image::from_bgr8_nd_array(frame, Some("camera.left")); + + let frame = image.to_rgb().to_nd_array().unwrap(); + let image = Image::from_rgb8_nd_array(frame, Some("camera.left")); + + let frame = image.to_bgr().to_arrow().unwrap(); + let image = Image::from_arrow(frame).unwrap(); + + let final_buffer_address = image.as_ptr(); + + assert_eq!(original_buffer_address, final_buffer_address); + } +} From ba4c767607949f13fec3bbc2748f3561ac27c590 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Sat, 3 Aug 2024 00:21:48 +0200 Subject: [PATCH 02/11] Change previous Image struct for an Enum to make it easier to convert with Arrow (dynamic types and sizes) --- examples/dummy-opencv-capture/main.rs | 10 +- src/image.rs | 364 ++++++++++++++++---------- src/lib.rs | 37 ++- 3 files changed, 259 insertions(+), 152 deletions(-) diff --git a/examples/dummy-opencv-capture/main.rs b/examples/dummy-opencv-capture/main.rs index d767f14..954eab9 100644 --- a/examples/dummy-opencv-capture/main.rs +++ b/examples/dummy-opencv-capture/main.rs @@ -13,7 +13,7 @@ fn camera_read() -> ndarray::Array { let image = Image::new_bgr8(flat_image, 3, 3, None); - return image.to_nd_array().unwrap(); + return image.to_bgr8_ndarray().unwrap(); } fn image_show(_frame: ndarray::ArrayView) { @@ -37,10 +37,10 @@ fn main() { // Read OpenCV Camera, default is nd_array BGR8 let frame = camera_read(); - let image = Image::from_bgr8_nd_array(frame, Some("camera.left")); + let image = Image::from_bgr8_ndarray(frame, Some("camera.left")); // Convert to RGB8, apply some filter (Black and White). - let mut frame = image.to_rgb().to_nd_array().unwrap(); + let mut frame = image.to_rgb().unwrap().to_rgb8_ndarray().unwrap(); for i in 0..frame.shape()[0] { for j in 0..frame.shape()[1] { @@ -59,10 +59,10 @@ fn main() { } } - let image = Image::from_rgb8_nd_array(frame, Some("camera.left.baw")); + let image = Image::from_rgb8_ndarray(frame, Some("camera.left.baw")); // Plot the image, you may only need a nd array view - image_show(image.nd_array_view().unwrap()); + image_show(image.to_rgb8_ndarray_view().unwrap()); send_output(image.to_arrow().unwrap()); } diff --git a/src/image.rs b/src/image.rs index eb590dc..bbd4934 100644 --- a/src/image.rs +++ b/src/image.rs @@ -4,177 +4,226 @@ use std::{mem, sync::Arc}; use eyre::{Context, Report, Result}; -#[derive(PartialEq, Debug)] -enum Encoding { - BGR8, - RGB8, -} - -impl Encoding { - fn to_string(&self) -> String { - match self { - Self::BGR8 => "BGR8".to_string(), - Self::RGB8 => "RGB8".to_string(), - } - } - - fn from_string(encoding: &str) -> Result { - match encoding { - "BGR8" => Ok(Self::BGR8), - "RGB8" => Ok(Self::RGB8), - _ => Err(Report::msg(format!( - "Encoding {} is not supported.", - encoding - ))), - } - } -} - #[derive(Debug)] -pub struct Image { +pub struct ImageData { pixels: Vec, width: u32, height: u32, - encoding: Encoding, name: Option, } -impl Image { - pub fn as_ptr(&self) -> *const u8 { - self.pixels.as_ptr() as *const u8 - } - - pub fn to_nd_array(self) -> Result> { - match self.encoding { - Encoding::BGR8 => ndarray::Array::from_shape_vec( - (self.height as usize, self.width as usize, 3), - self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - Encoding::RGB8 => ndarray::Array::from_shape_vec( - (self.height as usize, self.width as usize, 3), - self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - } - } +pub enum Image { + ImageRGB8(ImageData), + ImageBGR8(ImageData), + ImageGray8(ImageData), +} - pub fn nd_array_view(&self) -> Result> { - match self.encoding { - Encoding::BGR8 => ndarray::ArrayView::from_shape( - (self.height as usize, self.width as usize, 3), - &self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - Encoding::RGB8 => ndarray::ArrayView::from_shape( - (self.height as usize, self.width as usize, 3), - &self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), +impl Image { + pub fn as_ptr(&self) -> *const u8 { + match self { + Self::ImageRGB8(image) => image.pixels.as_ptr(), + Self::ImageBGR8(image) => image.pixels.as_ptr(), + Self::ImageGray8(image) => image.pixels.as_ptr(), } } - pub fn nd_array_view_mut(&mut self) -> Result> { - match self.encoding { - Encoding::BGR8 => ndarray::ArrayViewMut::from_shape( - (self.height as usize, self.width as usize, 3), - &mut self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - Encoding::RGB8 => ndarray::ArrayViewMut::from_shape( - (self.height as usize, self.width as usize, 3), - &mut self.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - } + pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { + Self::ImageBGR8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + }) } -} -impl Image { pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self { + Self::ImageRGB8(ImageData { pixels, width, height, - encoding: Encoding::RGB8, name: name.map(|s| s.to_string()), - } + }) } - pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self { + pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { + Self::ImageGray8(ImageData { pixels, width, height, - encoding: Encoding::BGR8, name: name.map(|s| s.to_string()), - } + }) } - pub fn to_bgr(self) -> Self { - if self.encoding == Encoding::BGR8 { - return self; - } - - let mut pixels = self.pixels; - - for i in (0..pixels.len()).step_by(3) { - pixels.swap(i, i + 2); - } - - Self { - pixels, - width: self.width, - height: self.height, - encoding: Encoding::BGR8, - name: self.name, + pub fn to_rgb(self) -> Result { + match self { + Self::ImageBGR8(image) => { + let mut pixels = image.pixels; + + for i in (0..pixels.len()).step_by(3) { + pixels.swap(i, i + 2); + } + + Ok(Self::ImageRGB8(ImageData { + pixels, + width: image.width, + height: image.height, + name: image.name.clone(), + })) + } + Self::ImageRGB8(_) => Ok(self), + Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to RGB")), } } - pub fn to_rgb(self) -> Self { - if self.encoding == Encoding::RGB8 { - return self; + pub fn to_bgr(self) -> Result { + match self { + Self::ImageRGB8(image) => { + let mut pixels = image.pixels; + + for i in (0..pixels.len()).step_by(3) { + pixels.swap(i, i + 2); + } + + Ok(Self::ImageBGR8(ImageData { + pixels, + width: image.width, + height: image.height, + name: image.name.clone(), + })) + } + Self::ImageBGR8(_) => Ok(self), + Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to BGR")), } + } - let mut pixels = self.pixels; + pub fn from_rgb8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; - for i in (0..pixels.len()).step_by(3) { - pixels.swap(i, i + 2); - } + let pixels = array.into_raw_vec(); - Self { - pixels, - width: self.width, - height: self.height, - encoding: Encoding::RGB8, - name: self.name, - } + Self::new_rgb8(pixels, width, height, name) } -} -impl Image { - pub fn from_rgb8_nd_array(array: ndarray::Array, name: Option<&str>) -> Self { + pub fn from_bgr8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { let width = array.shape()[1] as u32; let height = array.shape()[0] as u32; let pixels = array.into_raw_vec(); - Self::new_rgb8(pixels, width, height, name) + Self::new_bgr8(pixels, width, height, name) } - pub fn from_bgr8_nd_array(array: ndarray::Array, name: Option<&str>) -> Self { + pub fn from_gray8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { let width = array.shape()[1] as u32; let height = array.shape()[0] as u32; let pixels = array.into_raw_vec(); - Self::new_bgr8(pixels, width, height, name) + Self::new_gray8(pixels, width, height, name) + } + + pub fn to_rgb8_ndarray(self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn to_bgr8_ndarray(self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn to_gray8_ndarray(self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn to_rgb8_ndarray_view(&self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn to_bgr8_ndarray_view(&self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn to_gray8_ndarray_view(&self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn to_rgb8_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn to_bgr8_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn to_gray8_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } } -} -impl Image { pub fn from_arrow(array: arrow::array::UnionArray) -> Result { use arrow::array::Array; @@ -191,10 +240,10 @@ impl Image { column_by_name::(&array, "width", &look_up_table)?.value(0); let height = column_by_name::(&array, "height", &look_up_table)?.value(0); - let encoding = Encoding::from_string( - &column_by_name::(&array, "encoding", &look_up_table)? - .value(0), - )?; + let encoding = + column_by_name::(&array, "encoding", &look_up_table)? + .value(0) + .to_string(); let name = column_by_name::(&array, "name", &look_up_table)?; @@ -208,35 +257,76 @@ impl Image { unsafe { let array = mem::ManuallyDrop::new(array); - let pixels = - column_by_name::(&array, "pixels", &look_up_table)?; + let pixels = match encoding.as_str() { + "RGB8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "BGR8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "GRAY8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + _ => { + return Err(Report::msg(format!("Invalid encoding: {}", encoding))); + } + }; let ptr = pixels.values().as_ptr(); let len = pixels.len(); let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); - return match encoding { - Encoding::RGB8 => Ok(Self::new_rgb8(pixels, width, height, name)), - Encoding::BGR8 => Ok(Self::new_bgr8(pixels, width, height, name)), + return match encoding.as_str() { + "RGB8" => Ok(Self::new_rgb8(pixels, width, height, name)), + "BGR8" => Ok(Self::new_bgr8(pixels, width, height, name)), + "GRAY8" => Ok(Self::new_gray8(pixels, width, height, name)), + _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), }; } } - pub fn to_arrow(self) -> Result { - let pixels = arrow::array::UInt8Array::from(self.pixels); - - let width = arrow::array::UInt32Array::from(vec![self.width; 1]); - let height = arrow::array::UInt32Array::from(vec![self.height; 1]); - let encoding = arrow::array::StringArray::from(vec![self.encoding.to_string(); 1]); + fn get_image_details( + image: &ImageData, + ) -> ( + arrow::array::UInt32Array, + arrow::array::UInt32Array, + arrow::array::StringArray, + arrow::array::StringArray, + ) { + let width = arrow::array::UInt32Array::from(vec![image.width; 1]); + let height = arrow::array::UInt32Array::from(vec![image.height; 1]); + let encoding = arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]); + + let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); + + (width, height, encoding, name) + } - let name = arrow::array::StringArray::from(vec![self.name; 1]); + pub fn to_arrow(self) -> Result { + let ((width, height, encoding, name), pixels, datatype) = match self { + Image::ImageBGR8(image) => ( + Self::get_image_details(&image), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageRGB8(image) => ( + Self::get_image_details(&image), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageGray8(image) => ( + Self::get_image_details(&image), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + }; let type_ids = [].into_iter().collect::>(); let offsets = [].into_iter().collect::>(); let union_fields = [ - union_field(0, "pixels", arrow::datatypes::DataType::UInt8, false), + union_field(0, "pixels", datatype, false), union_field(1, "width", arrow::datatypes::DataType::UInt32, false), union_field(2, "height", arrow::datatypes::DataType::UInt32, false), union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), diff --git a/src/lib.rs b/src/lib.rs index fd00f2e..ac5ba7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,25 +19,42 @@ fn fastformat(m: &Bound<'_, PyModule>) -> PyResult<()> { mod tests { #[test] - fn test_zero_copy_image_conversion() { + fn test_ndarray_conversion() { use crate::image::Image; let flat_image = (1..28).collect::>(); let original_buffer_address = flat_image.as_ptr(); - let image = Image::new_bgr8(flat_image, 3, 3, None); + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); + let image_buffer_address = bgr8_image.as_ptr(); - let frame = image.to_nd_array().unwrap(); - let image = Image::from_bgr8_nd_array(frame, Some("camera.left")); + let bgr8_ndarray = bgr8_image.to_bgr8_ndarray().unwrap(); + let ndarray_buffer_address = bgr8_ndarray.as_ptr(); - let frame = image.to_rgb().to_nd_array().unwrap(); - let image = Image::from_rgb8_nd_array(frame, Some("camera.left")); + let final_image = Image::from_bgr8_ndarray(bgr8_ndarray, None); + let final_image_buffer_address = final_image.as_ptr(); - let frame = image.to_bgr().to_arrow().unwrap(); - let image = Image::from_arrow(frame).unwrap(); + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } + + #[test] + fn test_arrow_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); + let image_buffer_address = bgr8_image.as_ptr(); + + let arrow_image = bgr8_image.to_arrow().unwrap(); - let final_buffer_address = image.as_ptr(); + let new_image = Image::from_arrow(arrow_image).unwrap(); + let final_image_buffer = new_image.as_ptr(); - assert_eq!(original_buffer_address, final_buffer_address); + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, final_image_buffer); } } From cbbf34337d4632f62b18dc485f590d36c8b804d3 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Sat, 3 Aug 2024 00:24:21 +0200 Subject: [PATCH 03/11] Fix image encoding with arrow error --- src/image.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/image.rs b/src/image.rs index bbd4934..619919b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -292,31 +292,32 @@ impl Image { arrow::array::UInt32Array, arrow::array::UInt32Array, arrow::array::StringArray, - arrow::array::StringArray, ) { let width = arrow::array::UInt32Array::from(vec![image.width; 1]); let height = arrow::array::UInt32Array::from(vec![image.height; 1]); - let encoding = arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]); let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); - (width, height, encoding, name) + (width, height, name) } pub fn to_arrow(self) -> Result { - let ((width, height, encoding, name), pixels, datatype) = match self { + let ((width, height, name), encoding, pixels, datatype) = match self { Image::ImageBGR8(image) => ( Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), arrow::array::UInt8Array::from(image.pixels), arrow::datatypes::DataType::UInt8, ), Image::ImageRGB8(image) => ( Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), arrow::array::UInt8Array::from(image.pixels), arrow::datatypes::DataType::UInt8, ), Image::ImageGray8(image) => ( Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), arrow::array::UInt8Array::from(image.pixels), arrow::datatypes::DataType::UInt8, ), From a7b532afca270ea186156cb0f8ee16222d24f36c Mon Sep 17 00:00:00 2001 From: Hennzau Date: Sat, 3 Aug 2024 17:15:47 +0200 Subject: [PATCH 04/11] separate image.rs module into multiple modules + add tests inside each submodule --- examples/dummy-opencv-capture/main.rs | 12 +- src/image.rs | 299 ++------------------------ src/image/arrow.rs | 153 +++++++++++++ src/image/bgr8.rs | 141 ++++++++++++ src/image/gray8.rs | 139 ++++++++++++ src/image/rgb8.rs | 139 ++++++++++++ src/lib.rs | 42 ---- 7 files changed, 601 insertions(+), 324 deletions(-) create mode 100644 src/image/arrow.rs create mode 100644 src/image/bgr8.rs create mode 100644 src/image/gray8.rs create mode 100644 src/image/rgb8.rs diff --git a/examples/dummy-opencv-capture/main.rs b/examples/dummy-opencv-capture/main.rs index 954eab9..7a0e504 100644 --- a/examples/dummy-opencv-capture/main.rs +++ b/examples/dummy-opencv-capture/main.rs @@ -11,9 +11,9 @@ fn camera_read() -> ndarray::Array { flat_image.as_ptr() ); - let image = Image::new_bgr8(flat_image, 3, 3, None); + let image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); - return image.to_bgr8_ndarray().unwrap(); + return image.bgr8_to_ndarray().unwrap(); } fn image_show(_frame: ndarray::ArrayView) { @@ -37,10 +37,10 @@ fn main() { // Read OpenCV Camera, default is nd_array BGR8 let frame = camera_read(); - let image = Image::from_bgr8_ndarray(frame, Some("camera.left")); + let image = Image::bgr8_from_ndarray(frame, Some("camera.left")).unwrap(); // Convert to RGB8, apply some filter (Black and White). - let mut frame = image.to_rgb().unwrap().to_rgb8_ndarray().unwrap(); + let mut frame = image.to_rgb8().unwrap().rgb8_to_ndarray().unwrap(); for i in 0..frame.shape()[0] { for j in 0..frame.shape()[1] { @@ -59,10 +59,10 @@ fn main() { } } - let image = Image::from_rgb8_ndarray(frame, Some("camera.left.baw")); + let image = Image::rgb8_from_ndarray(frame, Some("camera.left.baw")).unwrap(); // Plot the image, you may only need a nd array view - image_show(image.to_rgb8_ndarray_view().unwrap()); + image_show(image.rgb8_to_ndarray_view().unwrap()); send_output(image.to_arrow().unwrap()); } diff --git a/src/image.rs b/src/image.rs index 619919b..6489d79 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,8 +1,10 @@ -use crate::arrow::{column_by_name, union_field, union_look_up_table}; +use eyre::{Report, Result}; -use std::{mem, sync::Arc}; +mod bgr8; +mod gray8; +mod rgb8; -use eyre::{Context, Report, Result}; +mod arrow; #[derive(Debug)] pub struct ImageData { @@ -29,34 +31,7 @@ impl Image { } } - pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageBGR8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageRGB8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Self { - Self::ImageGray8(ImageData { - pixels, - width, - height, - name: name.map(|s| s.to_string()), - }) - } - - pub fn to_rgb(self) -> Result { + pub fn to_rgb8(self) -> Result { match self { Self::ImageBGR8(image) => { let mut pixels = image.pixels; @@ -73,11 +48,11 @@ impl Image { })) } Self::ImageRGB8(_) => Ok(self), - Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to RGB")), + _ => Err(Report::msg("Can't convert image to RGB8")), } } - pub fn to_bgr(self) -> Result { + pub fn to_bgr8(self) -> Result { match self { Self::ImageRGB8(image) => { let mut pixels = image.pixels; @@ -94,257 +69,29 @@ impl Image { })) } Self::ImageBGR8(_) => Ok(self), - Self::ImageGray8(_) => Err(Report::msg("Can't convert grayscale image to BGR")), - } - } - - pub fn from_rgb8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_rgb8(pixels, width, height, name) - } - - pub fn from_bgr8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_bgr8(pixels, width, height, name) - } - - pub fn from_gray8_ndarray(array: ndarray::Array, name: Option<&str>) -> Self { - let width = array.shape()[1] as u32; - let height = array.shape()[0] as u32; - - let pixels = array.into_raw_vec(); - - Self::new_gray8(pixels, width, height, name) - } - - pub fn to_rgb8_ndarray(self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray(self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray(self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize), - image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn to_rgb8_ndarray_view(&self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray_view(&self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray_view(&self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize), - &image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn to_rgb8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in RGB8 format")), - } - } - - pub fn to_bgr8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in BGR8 format")), - } - } - - pub fn to_gray8_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize), - &mut image.pixels, - ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), - _ => Err(Report::msg("Image is not in Gray8 format")), - } - } - - pub fn from_arrow(array: arrow::array::UnionArray) -> Result { - use arrow::array::Array; - - let union_fields = match array.data_type() { - arrow::datatypes::DataType::Union(fields, ..) => fields, - _ => { - return Err(Report::msg("UnionArray has invalid data type.")); - } - }; - - let look_up_table = union_look_up_table(&union_fields); - - let width = - column_by_name::(&array, "width", &look_up_table)?.value(0); - let height = - column_by_name::(&array, "height", &look_up_table)?.value(0); - let encoding = - column_by_name::(&array, "encoding", &look_up_table)? - .value(0) - .to_string(); - - let name = column_by_name::(&array, "name", &look_up_table)?; - - let name = if name.is_null(0) { - None - } else { - Some(name.value(0).to_string()) - }; - - let name = name.as_ref().map(|s| s.as_str()); - - unsafe { - let array = mem::ManuallyDrop::new(array); - let pixels = match encoding.as_str() { - "RGB8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - "BGR8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - "GRAY8" => { - column_by_name::(&array, "pixels", &look_up_table)? - } - _ => { - return Err(Report::msg(format!("Invalid encoding: {}", encoding))); - } - }; - - let ptr = pixels.values().as_ptr(); - let len = pixels.len(); - - let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); - - return match encoding.as_str() { - "RGB8" => Ok(Self::new_rgb8(pixels, width, height, name)), - "BGR8" => Ok(Self::new_bgr8(pixels, width, height, name)), - "GRAY8" => Ok(Self::new_gray8(pixels, width, height, name)), - _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), - }; + _ => Err(Report::msg("Can't convert image to BGR8")), } } +} - fn get_image_details( - image: &ImageData, - ) -> ( - arrow::array::UInt32Array, - arrow::array::UInt32Array, - arrow::array::StringArray, - ) { - let width = arrow::array::UInt32Array::from(vec![image.width; 1]); - let height = arrow::array::UInt32Array::from(vec![image.height; 1]); +mod tests { + #[test] + fn test_rgb8_to_bgr8() { + use crate::image::Image; - let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); + let flat_image = (1..28).collect::>(); - (width, height, name) + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + image.to_bgr8().unwrap(); } - pub fn to_arrow(self) -> Result { - let ((width, height, name), encoding, pixels, datatype) = match self { - Image::ImageBGR8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageRGB8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageGray8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), - arrow::datatypes::DataType::UInt8, - ), - }; - - let type_ids = [].into_iter().collect::>(); - let offsets = [].into_iter().collect::>(); - - let union_fields = [ - union_field(0, "pixels", datatype, false), - union_field(1, "width", arrow::datatypes::DataType::UInt32, false), - union_field(2, "height", arrow::datatypes::DataType::UInt32, false), - union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), - union_field(4, "name", arrow::datatypes::DataType::Utf8, true), - ] - .into_iter() - .collect::(); + #[test] + fn test_bgr8_to_rgb8() { + use crate::image::Image; - let children: Vec> = vec![ - Arc::new(pixels), - Arc::new(width), - Arc::new(height), - Arc::new(encoding), - Arc::new(name), - ]; + let flat_image = (1..28).collect::>(); - arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) - .wrap_err("Failed to create UnionArray") + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + image.to_rgb8().unwrap(); } } diff --git a/src/image/arrow.rs b/src/image/arrow.rs new file mode 100644 index 0000000..135abf2 --- /dev/null +++ b/src/image/arrow.rs @@ -0,0 +1,153 @@ +use crate::arrow::{column_by_name, union_field, union_look_up_table}; + +use super::{Image, ImageData}; +use eyre::{Context, Report, Result}; + +use std::{mem, sync::Arc}; + +impl Image { + pub fn from_arrow(array: arrow::array::UnionArray) -> Result { + use arrow::array::Array; + + let union_fields = match array.data_type() { + arrow::datatypes::DataType::Union(fields, ..) => fields, + _ => { + return Err(Report::msg("UnionArray has invalid data type.")); + } + }; + + let look_up_table = union_look_up_table(&union_fields); + + let width = + column_by_name::(&array, "width", &look_up_table)?.value(0); + let height = + column_by_name::(&array, "height", &look_up_table)?.value(0); + let encoding = + column_by_name::(&array, "encoding", &look_up_table)? + .value(0) + .to_string(); + + let name = column_by_name::(&array, "name", &look_up_table)?; + + let name = if name.is_null(0) { + None + } else { + Some(name.value(0).to_string()) + }; + + let name = name.as_ref().map(|s| s.as_str()); + + unsafe { + let array = mem::ManuallyDrop::new(array); + let pixels = match encoding.as_str() { + "RGB8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "BGR8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + "GRAY8" => { + column_by_name::(&array, "pixels", &look_up_table)? + } + _ => { + return Err(Report::msg(format!("Invalid encoding: {}", encoding))); + } + }; + + let ptr = pixels.values().as_ptr(); + let len = pixels.len(); + + let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); + + return match encoding.as_str() { + "RGB8" => Self::new_rgb8(pixels, width, height, name), + "BGR8" => Self::new_bgr8(pixels, width, height, name), + "GRAY8" => Self::new_gray8(pixels, width, height, name), + _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), + }; + } + } + + fn get_image_details( + image: &ImageData, + ) -> ( + arrow::array::UInt32Array, + arrow::array::UInt32Array, + arrow::array::StringArray, + ) { + let width = arrow::array::UInt32Array::from(vec![image.width; 1]); + let height = arrow::array::UInt32Array::from(vec![image.height; 1]); + + let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); + + (width, height, name) + } + + pub fn to_arrow(self) -> Result { + let ((width, height, name), encoding, pixels, datatype) = match self { + Image::ImageBGR8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageRGB8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + Image::ImageGray8(image) => ( + Self::get_image_details(&image), + arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), + arrow::array::UInt8Array::from(image.pixels), + arrow::datatypes::DataType::UInt8, + ), + }; + + let type_ids = [].into_iter().collect::>(); + let offsets = [].into_iter().collect::>(); + + let union_fields = [ + union_field(0, "pixels", datatype, false), + union_field(1, "width", arrow::datatypes::DataType::UInt32, false), + union_field(2, "height", arrow::datatypes::DataType::UInt32, false), + union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), + union_field(4, "name", arrow::datatypes::DataType::Utf8, true), + ] + .into_iter() + .collect::(); + + let children: Vec> = vec![ + Arc::new(pixels), + Arc::new(width), + Arc::new(height), + Arc::new(encoding), + Arc::new(name), + ]; + + arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) + .wrap_err("Failed to create UnionArray width Image data.") + } +} + +mod tests { + #[test] + fn test_arrow_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = bgr8_image.as_ptr(); + + let arrow_image = bgr8_image.to_arrow().unwrap(); + + let new_image = Image::from_arrow(arrow_image).unwrap(); + let final_image_buffer = new_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, final_image_buffer); + } +} diff --git a/src/image/bgr8.rs b/src/image/bgr8.rs new file mode 100644 index 0000000..184e59f --- /dev/null +++ b/src/image/bgr8.rs @@ -0,0 +1,141 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if width * height * 3 != pixels.len() as u32 { + return Err(Report::msg( + "Width, height and BGR8 encoding doesn't match pixels data length.", + )); + } + + Ok(Self::ImageBGR8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn bgr8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_bgr8(pixels, width, height, name) + } + + pub fn bgr8_to_ndarray(self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn bgr8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } + + pub fn bgr8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in BGR8 format")), + } + } +} + +mod tests { + #[test] + fn test_bgr8_creation() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_bgr8_from_ndarray() { + use ndarray::Array3; + + use crate::image::Image; + + let array = Array3::::zeros((3, 3, 3)); + + Image::bgr8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray().unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_bgr8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let mut image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.bgr8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_bgr8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = bgr8_image.as_ptr(); + + let bgr8_ndarray = bgr8_image.bgr8_to_ndarray().unwrap(); + let ndarray_buffer_address = bgr8_ndarray.as_ptr(); + + let final_image = Image::bgr8_from_ndarray(bgr8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/image/gray8.rs b/src/image/gray8.rs new file mode 100644 index 0000000..1bb38d3 --- /dev/null +++ b/src/image/gray8.rs @@ -0,0 +1,139 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if pixels.len() != (width * height) as usize { + return Err(Report::msg("Invalid pixels data length.")); + } + + Ok(Self::ImageGray8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn gray8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_gray8(pixels, width, height, name) + } + + pub fn gray8_to_ndarray(self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn gray8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } + + pub fn gray8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in Gray8 format")), + } + } +} + +mod test { + #[test] + fn test_gray8_creation() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_gray8_from_ndarray() { + use ndarray::Array2; + + use crate::image::Image; + + let array = Array2::::zeros((3, 3)); + + Image::gray8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_gray8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray().unwrap(); + } + + #[test] + fn test_gray8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_gray8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + + let mut image = Image::new_gray8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.gray8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_gray8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..10).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let gray8_image = Image::new_gray8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = gray8_image.as_ptr(); + + let gray8_ndarray = gray8_image.gray8_to_ndarray().unwrap(); + let ndarray_buffer_address = gray8_ndarray.as_ptr(); + + let final_image = Image::gray8_from_ndarray(gray8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/image/rgb8.rs b/src/image/rgb8.rs new file mode 100644 index 0000000..a69505c --- /dev/null +++ b/src/image/rgb8.rs @@ -0,0 +1,139 @@ +use super::{Image, ImageData}; + +use eyre::{Context, Report, Result}; + +impl Image { + pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if pixels.len() != (width * height * 3) as usize { + return Err(Report::msg("Invalid pixel data length.")); + } + + Ok(Self::ImageRGB8(ImageData { + pixels, + width, + height, + name: name.map(|s| s.to_string()), + })) + } + + pub fn rgb8_from_ndarray( + array: ndarray::Array, + name: Option<&str>, + ) -> Result { + let width = array.shape()[1] as u32; + let height = array.shape()[0] as u32; + + let pixels = array.into_raw_vec(); + + Self::new_rgb8(pixels, width, height, name) + } + + pub fn rgb8_to_ndarray(self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( + (image.height as usize, image.width as usize, 3), + image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn rgb8_to_ndarray_view(&self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( + (image.height as usize, image.width as usize, 3), + &image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } + + pub fn rgb8_to_ndarray_view_mut(&mut self) -> Result> { + match self { + Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( + (image.height as usize, image.width as usize, 3), + &mut image.pixels, + ) + .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + _ => Err(Report::msg("Image is not in RGB8 format")), + } + } +} + +mod tests { + #[test] + fn test_rgb8_creation() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + } + + #[test] + fn test_rgb8_from_ndarray() { + use ndarray::Array3; + + use crate::image::Image; + + let array = Array3::::zeros((3, 3, 3)); + + Image::rgb8_from_ndarray(array, Some("camera.test")).unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray().unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray_view() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray_view().unwrap(); + } + + #[test] + fn test_rgb8_to_ndarray_view_mut() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + + let mut image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); + + image.rgb8_to_ndarray_view_mut().unwrap(); + } + + #[test] + fn test_rgb8_ndarray_zero_copy_conversion() { + use crate::image::Image; + + let flat_image = (1..28).collect::>(); + let original_buffer_address = flat_image.as_ptr(); + + let rgb8_image = Image::new_rgb8(flat_image, 3, 3, None).unwrap(); + let image_buffer_address = rgb8_image.as_ptr(); + + let rgb8_ndarray = rgb8_image.rgb8_to_ndarray().unwrap(); + let ndarray_buffer_address = rgb8_ndarray.as_ptr(); + + let final_image = Image::rgb8_from_ndarray(rgb8_ndarray, None).unwrap(); + let final_image_buffer_address = final_image.as_ptr(); + + assert_eq!(original_buffer_address, image_buffer_address); + assert_eq!(image_buffer_address, ndarray_buffer_address); + assert_eq!(ndarray_buffer_address, final_image_buffer_address); + } +} diff --git a/src/lib.rs b/src/lib.rs index ac5ba7c..7419bd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,45 +16,3 @@ fn fastformat(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; Ok(()) } - -mod tests { - #[test] - fn test_ndarray_conversion() { - use crate::image::Image; - - let flat_image = (1..28).collect::>(); - let original_buffer_address = flat_image.as_ptr(); - - let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); - let image_buffer_address = bgr8_image.as_ptr(); - - let bgr8_ndarray = bgr8_image.to_bgr8_ndarray().unwrap(); - let ndarray_buffer_address = bgr8_ndarray.as_ptr(); - - let final_image = Image::from_bgr8_ndarray(bgr8_ndarray, None); - let final_image_buffer_address = final_image.as_ptr(); - - assert_eq!(original_buffer_address, image_buffer_address); - assert_eq!(image_buffer_address, ndarray_buffer_address); - assert_eq!(ndarray_buffer_address, final_image_buffer_address); - } - - #[test] - fn test_arrow_conversion() { - use crate::image::Image; - - let flat_image = (1..28).collect::>(); - let original_buffer_address = flat_image.as_ptr(); - - let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None); - let image_buffer_address = bgr8_image.as_ptr(); - - let arrow_image = bgr8_image.to_arrow().unwrap(); - - let new_image = Image::from_arrow(arrow_image).unwrap(); - let final_image_buffer = new_image.as_ptr(); - - assert_eq!(original_buffer_address, image_buffer_address); - assert_eq!(image_buffer_address, final_image_buffer); - } -} From 3e55852544d155acd8d13298cf71975fcd3cdb63 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Sat, 3 Aug 2024 18:59:05 +0200 Subject: [PATCH 05/11] add comments and comments tests/examples --- examples/dummy-opencv-capture/main.rs | 6 +- src/arrow.rs | 122 ++++++++++++++++++++- src/image.rs | 4 +- src/image/arrow.rs | 95 ++++++++++++++-- src/image/bgr8.rs | 151 +++++++++++++++++++++++++- src/image/gray8.rs | 141 ++++++++++++++++++++++++ src/image/rgb8.rs | 151 +++++++++++++++++++++++++- 7 files changed, 642 insertions(+), 28 deletions(-) diff --git a/examples/dummy-opencv-capture/main.rs b/examples/dummy-opencv-capture/main.rs index 7a0e504..97b6359 100644 --- a/examples/dummy-opencv-capture/main.rs +++ b/examples/dummy-opencv-capture/main.rs @@ -5,7 +5,7 @@ use fastformat::image::Image; fn camera_read() -> ndarray::Array { // Dummy camera read - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; println!( "Generate a camera image at address: {:?}", flat_image.as_ptr() @@ -34,7 +34,7 @@ fn send_output(arrow_array: arrow::array::UnionArray) { } fn main() { - // Read OpenCV Camera, default is nd_array BGR8 + // Read OpenCV Camera, default is ndarray BGR8 let frame = camera_read(); let image = Image::bgr8_from_ndarray(frame, Some("camera.left")).unwrap(); @@ -61,7 +61,7 @@ fn main() { let image = Image::rgb8_from_ndarray(frame, Some("camera.left.baw")).unwrap(); - // Plot the image, you may only need a nd array view + // Plot the image, you may only need a ndarray_view image_show(image.rgb8_to_ndarray_view().unwrap()); send_output(image.to_arrow().unwrap()); diff --git a/src/arrow.rs b/src/arrow.rs index 9144850..ddd9adc 100644 --- a/src/arrow.rs +++ b/src/arrow.rs @@ -4,7 +4,42 @@ use std::{collections::HashMap, sync::Arc}; use eyre::{ContextCompat, Result}; -pub fn union_look_up_table(fields: &arrow::datatypes::UnionFields) -> HashMap { +/// Creates a lookup table (`HashMap`) from the fields of a union. +/// +/// This function takes a reference to `arrow::datatypes::UnionFields` and +/// creates a `HashMap` where the field names are the keys (as `String`) and +/// the associated values are the field identifiers (`i8`). +/// +/// # Arguments +/// +/// * `fields` - A reference to the fields of the Arrow union data structure (`arrow::datatypes::UnionFields`). +/// +/// # Returns +/// +/// A `HashMap` with the field names as keys and their identifiers (`i8`) as values. +/// +/// # Example +/// +/// ``` +/// use arrow::datatypes::{Field, DataType, UnionFields}; +/// use std::collections::HashMap; +/// +/// use fastformat::arrow::union_lookup_table; +/// +/// let fields = UnionFields::new( +/// vec![1, 2], +/// vec![ +/// Field::new("field1", DataType::Int32, false), +/// Field::new("field2", DataType::Float64, false), +/// ], +/// ); +/// +/// let lookup_table = union_lookup_table(&fields); +/// +/// assert_eq!(lookup_table.get("field1"), Some(&1)); +/// assert_eq!(lookup_table.get("field2"), Some(&2)); +/// ``` +pub fn union_lookup_table(fields: &arrow::datatypes::UnionFields) -> HashMap { let mut result = HashMap::new(); for field in fields.iter() { @@ -16,12 +51,56 @@ pub fn union_look_up_table(fields: &arrow::datatypes::UnionFields) -> HashMap fields, +/// _ => panic!("Unexpected data type for image array") +/// }; +/// +/// let lookup_table = union_lookup_table(&union_fields); +/// +/// let int_column = column_by_name::(&array, "field1", &lookup_table); +/// ``` pub fn column_by_name<'a, T: 'static>( array: &'a arrow::array::UnionArray, field: &'a str, - look_up_table: &'a HashMap, + lookup_table: &'a HashMap, ) -> Result<&'a T> { - let index = look_up_table + let index = lookup_table .get(field) .cloned() .wrap_err(format!("Couldn't get field {} from look_up table", field))?; @@ -33,6 +112,43 @@ pub fn column_by_name<'a, T: 'static>( .wrap_err(format!("Couldn't downcast field {} to type T", field)); } +/// Creates a tuple representing a union field with an index and an `Arc`-wrapped `Field`. +/// +/// This function constructs a tuple where the first element is the given index and the second element +/// is an `Arc`-wrapped `Field` constructed using the provided name, data type, and nullability. +/// +/// # Arguments +/// +/// * `index` - An identifier (`i8`) for the union field. +/// * `name` - A string slice representing the name of the field. +/// * `data_type` - The data type of the field (`arrow::datatypes::DataType`). +/// * `nullable` - A boolean indicating whether the field is nullable. +/// +/// # Returns +/// +/// A tuple where the first element is the given index (`i8`) and the second element is an `Arc`-wrapped +/// `Field` constructed from the provided name, data type, and nullability. +/// +/// # Example +/// +/// ``` +/// use arrow::datatypes::{DataType, Field}; +/// use std::sync::Arc; +/// use fastformat::arrow::union_field; +/// +/// let index = 1; +/// let name = "field1"; +/// let data_type = DataType::Int32; +/// let nullable = false; +/// +/// let union_field_tuple = union_field(index, name, data_type, nullable); +/// +/// assert_eq!(union_field_tuple.0, 1); +/// assert_eq!(union_field_tuple.1.name(), "field1"); +/// assert_eq!(union_field_tuple.1.data_type(), &DataType::Int32); +/// assert_eq!(union_field_tuple.1.is_nullable(), false); +/// ``` +/// pub fn union_field( index: i8, name: &str, diff --git a/src/image.rs b/src/image.rs index 6489d79..7cd3f49 100644 --- a/src/image.rs +++ b/src/image.rs @@ -79,7 +79,7 @@ mod tests { fn test_rgb8_to_bgr8() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); image.to_bgr8().unwrap(); @@ -89,7 +89,7 @@ mod tests { fn test_bgr8_to_rgb8() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); image.to_rgb8().unwrap(); diff --git a/src/image/arrow.rs b/src/image/arrow.rs index 135abf2..da655fc 100644 --- a/src/image/arrow.rs +++ b/src/image/arrow.rs @@ -1,4 +1,4 @@ -use crate::arrow::{column_by_name, union_field, union_look_up_table}; +use crate::arrow::{column_by_name, union_field, union_lookup_table}; use super::{Image, ImageData}; use eyre::{Context, Report, Result}; @@ -6,6 +6,38 @@ use eyre::{Context, Report, Result}; use std::{mem, sync::Arc}; impl Image { + /// Constructs an `Image` from an Arrow `UnionArray`. + /// + /// This function takes an Arrow `UnionArray` and extracts the necessary fields to construct + /// an `Image` object. It validates the data type of the `UnionArray`, builds a lookup table for + /// the fields, retrieves the image properties (width, height, encoding, name), and decodes the + /// pixel data based on the encoding. + /// + /// # Arguments + /// + /// * `array` - A reference to an `arrow::array::UnionArray` that contains the image data. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `UnionArray` has an invalid data type, if required fields are missing, + /// or if the pixel data cannot be downcasted to the expected type based on the encoding. + /// + /// # Example + /// + /// ``` + /// use arrow::array::UnionArray; + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(pixels, 3, 3, None).unwrap(); + /// let array = image.to_arrow().unwrap(); + /// + /// let image = Image::from_arrow(array).unwrap(); + /// ``` pub fn from_arrow(array: arrow::array::UnionArray) -> Result { use arrow::array::Array; @@ -16,18 +48,18 @@ impl Image { } }; - let look_up_table = union_look_up_table(&union_fields); + let lookup_table = union_lookup_table(&union_fields); let width = - column_by_name::(&array, "width", &look_up_table)?.value(0); + column_by_name::(&array, "width", &lookup_table)?.value(0); let height = - column_by_name::(&array, "height", &look_up_table)?.value(0); + column_by_name::(&array, "height", &lookup_table)?.value(0); let encoding = - column_by_name::(&array, "encoding", &look_up_table)? + column_by_name::(&array, "encoding", &lookup_table)? .value(0) .to_string(); - let name = column_by_name::(&array, "name", &look_up_table)?; + let name = column_by_name::(&array, "name", &lookup_table)?; let name = if name.is_null(0) { None @@ -41,13 +73,13 @@ impl Image { let array = mem::ManuallyDrop::new(array); let pixels = match encoding.as_str() { "RGB8" => { - column_by_name::(&array, "pixels", &look_up_table)? + column_by_name::(&array, "pixels", &lookup_table)? } "BGR8" => { - column_by_name::(&array, "pixels", &look_up_table)? + column_by_name::(&array, "pixels", &lookup_table)? } "GRAY8" => { - column_by_name::(&array, "pixels", &look_up_table)? + column_by_name::(&array, "pixels", &lookup_table)? } _ => { return Err(Report::msg(format!("Invalid encoding: {}", encoding))); @@ -68,6 +100,19 @@ impl Image { } } + /// Extracts image details (width, height, name) from an `ImageData` object. + /// + /// This function takes a reference to an `ImageData` object and creates Arrow arrays for the width, + /// height, and name of the image. + /// + /// # Arguments + /// + /// * `image` - A reference to an `ImageData` object that contains the image properties. + /// + /// # Returns + /// + /// A tuple containing an `arrow::array::UInt32Array` for the width, an `arrow::array::UInt32Array` for the height, + /// and an `arrow::array::StringArray` for the name of the image. fn get_image_details( image: &ImageData, ) -> ( @@ -83,6 +128,36 @@ impl Image { (width, height, name) } + /// Converts an `Image` into an Arrow `UnionArray`. + /// + /// This function takes an `Image` object and converts it into an Arrow `UnionArray` + /// that contains the image properties and pixel data. The conversion handles different + /// image encodings (BGR8, RGB8, GRAY8) and ensures that the resulting `UnionArray` + /// contains all necessary fields. + /// + /// # Arguments + /// + /// * `self` - The `Image` object to be converted. + /// + /// # Returns + /// + /// A `Result` containing the constructed `arrow::array::UnionArray` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `UnionArray` cannot be created due to issues with the provided data. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// use fastformat::image::ImageData; + /// + /// let pixels = vec![0; 640 * 480 * 3]; + /// let image = Image::new_bgr8(pixels, 640, 480, None).unwrap(); + /// + /// let arrow_array = image.to_arrow().unwrap(); + /// ``` pub fn to_arrow(self) -> Result { let ((width, height, name), encoding, pixels, datatype) = match self { Image::ImageBGR8(image) => ( @@ -136,7 +211,7 @@ mod tests { fn test_arrow_conversion() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let original_buffer_address = flat_image.as_ptr(); let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); diff --git a/src/image/bgr8.rs b/src/image/bgr8.rs index 184e59f..e4b1371 100644 --- a/src/image/bgr8.rs +++ b/src/image/bgr8.rs @@ -3,6 +3,36 @@ use super::{Image, ImageData}; use eyre::{Context, Report, Result}; impl Image { + /// Creates a new `Image` in BGR8 format. + /// + /// This function constructs a new `Image` object with the given pixel data, width, height, + /// and an optional name. It ensures that the pixel data length matches the expected size + /// for the given width, height, and BGR8 encoding (3 bytes per pixel). + /// + /// # Arguments + /// + /// * `pixels` - A `Vec` containing the pixel data in BGR8 format. + /// * `width` - The width of the image. + /// * `height` - The height of the image. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the length of the pixel data does not match the expected size + /// based on the width, height, and BGR8 encoding. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// ``` pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { if width * height * 3 != pixels.len() as u32 { return Err(Report::msg( @@ -18,6 +48,33 @@ impl Image { })) } + /// Creates a new `Image` in BGR8 format from an ndarray. + /// + /// This function constructs a new `Image` object from an `ndarray::Array` with shape (height, width, 3). + /// It converts the ndarray into a raw vector and uses it to create the `Image`. + /// + /// # Arguments + /// + /// * `array` - An `ndarray::Array` containing the pixel data in BGR8 format. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the ndarray cannot be converted into a valid `Image`. + /// + /// # Example + /// + /// ``` + /// use ndarray::Array3; + /// use fastformat::image::Image; + /// + /// let array = Array3::::zeros((3, 3, 3)); // 3x3 image with 3 channels + /// let image = Image::bgr8_from_ndarray(array, Some("example")).unwrap(); + /// ``` pub fn bgr8_from_ndarray( array: ndarray::Array, name: Option<&str>, @@ -30,6 +87,34 @@ impl Image { Self::new_bgr8(pixels, width, height, name) } + /// Converts a BGR8 `Image` into an ndarray. + /// + /// This function takes a BGR8 `Image` and converts it into an `ndarray::Array`. + /// The resulting ndarray has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `self` - The `Image` object to be converted. + /// + /// # Returns + /// + /// A `Result` containing the constructed ndarray if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in BGR8 format or if the pixel data cannot be reshaped + /// into the expected ndarray format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray = image.bgr8_to_ndarray().unwrap(); + /// ``` pub fn bgr8_to_ndarray(self) -> Result> { match self { Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( @@ -41,6 +126,34 @@ impl Image { } } + /// Converts a BGR8 `Image` into an ndarray view. + /// + /// This function takes a reference to a BGR8 `Image` and creates an `ndarray::ArrayView` + /// over the pixel data. The resulting view has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `&self` - A reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in BGR8 format or if the pixel data cannot be reshaped + /// into the expected ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view = image.bgr8_to_ndarray_view().unwrap(); + /// ``` pub fn bgr8_to_ndarray_view(&self) -> Result> { match self { Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( @@ -52,6 +165,34 @@ impl Image { } } + /// Converts a mutable BGR8 `Image` into a mutable ndarray view. + /// + /// This function takes a mutable reference to a BGR8 `Image` and creates an `ndarray::ArrayViewMut` + /// over the pixel data. The resulting view has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `&mut self` - A mutable reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the mutable ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in BGR8 format or if the pixel data cannot be reshaped + /// into the expected mutable ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let mut image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view_mut = image.bgr8_to_ndarray_view_mut().unwrap(); + /// ``` pub fn bgr8_to_ndarray_view_mut(&mut self) -> Result> { match self { Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( @@ -69,7 +210,7 @@ mod tests { fn test_bgr8_creation() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); } @@ -89,7 +230,7 @@ mod tests { fn test_bgr8_to_ndarray() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -100,7 +241,7 @@ mod tests { fn test_bgr8_to_ndarray_view() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -111,7 +252,7 @@ mod tests { fn test_bgr8_to_ndarray_view_mut() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let mut image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -122,7 +263,7 @@ mod tests { fn test_bgr8_ndarray_zero_copy_conversion() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let original_buffer_address = flat_image.as_ptr(); let bgr8_image = Image::new_bgr8(flat_image, 3, 3, None).unwrap(); diff --git a/src/image/gray8.rs b/src/image/gray8.rs index 1bb38d3..efb7d8c 100644 --- a/src/image/gray8.rs +++ b/src/image/gray8.rs @@ -3,6 +3,36 @@ use super::{Image, ImageData}; use eyre::{Context, Report, Result}; impl Image { + /// Creates a new `Image` in Gray8 format. + /// + /// This function constructs a new `Image` object with the given pixel data, width, height, + /// and an optional name. It ensures that the pixel data length matches the expected size + /// for the given width and height. + /// + /// # Arguments + /// + /// * `pixels` - A `Vec` containing the pixel data in Gray8 format. + /// * `width` - The width of the image. + /// * `height` - The height of the image. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the length of the pixel data does not match the expected size + /// based on the width and height. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// ``` pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { if pixels.len() != (width * height) as usize { return Err(Report::msg("Invalid pixels data length.")); @@ -16,6 +46,33 @@ impl Image { })) } + /// Creates a new `Image` in Gray8 format from an ndarray. + /// + /// This function constructs a new `Image` object from an `ndarray::Array` with shape (height, width). + /// It converts the ndarray into a raw vector and uses it to create the `Image`. + /// + /// # Arguments + /// + /// * `array` - An `ndarray::Array` containing the pixel data in Gray8 format. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the ndarray cannot be converted into a valid `Image`. + /// + /// # Example + /// + /// ``` + /// use ndarray::Array2; + /// use fastformat::image::Image; + /// + /// let array = Array2::::zeros((3, 3)); // 3x3 image + /// let image = Image::gray8_from_ndarray(array, Some("example")).unwrap(); + /// ``` pub fn gray8_from_ndarray( array: ndarray::Array, name: Option<&str>, @@ -28,6 +85,34 @@ impl Image { Self::new_gray8(pixels, width, height, name) } + /// Converts a Gray8 `Image` into an ndarray. + /// + /// This function takes a Gray8 `Image` and converts it into an `ndarray::Array`. + /// The resulting ndarray has shape (height, width). + /// + /// # Arguments + /// + /// * `self` - The `Image` object to be converted. + /// + /// # Returns + /// + /// A `Result` containing the constructed ndarray if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in Gray8 format or if the pixel data cannot be reshaped + /// into the expected ndarray format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray = image.gray8_to_ndarray().unwrap(); + /// ``` pub fn gray8_to_ndarray(self) -> Result> { match self { Self::ImageGray8(image) => ndarray::Array::from_shape_vec( @@ -39,6 +124,34 @@ impl Image { } } + /// Converts a Gray8 `Image` into an ndarray view. + /// + /// This function takes a reference to a Gray8 `Image` and creates an `ndarray::ArrayView` + /// over the pixel data. The resulting view has shape (height, width). + /// + /// # Arguments + /// + /// * `&self` - A reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in Gray8 format or if the pixel data cannot be reshaped + /// into the expected ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view = image.gray8_to_ndarray_view().unwrap(); + /// ``` pub fn gray8_to_ndarray_view(&self) -> Result> { match self { Self::ImageGray8(image) => ndarray::ArrayView::from_shape( @@ -50,6 +163,34 @@ impl Image { } } + /// Converts a mutable Gray8 `Image` into a mutable ndarray view. + /// + /// This function takes a mutable reference to a Gray8 `Image` and creates an `ndarray::ArrayViewMut` + /// over the pixel data. The resulting view has shape (height, width). + /// + /// # Arguments + /// + /// * `&mut self` - A mutable reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the mutable ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in Gray8 format or if the pixel data cannot be reshaped + /// into the expected mutable ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let mut image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view_mut = image.gray8_to_ndarray_view_mut().unwrap(); + /// ``` pub fn gray8_to_ndarray_view_mut(&mut self) -> Result> { match self { Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( diff --git a/src/image/rgb8.rs b/src/image/rgb8.rs index a69505c..ffb10fd 100644 --- a/src/image/rgb8.rs +++ b/src/image/rgb8.rs @@ -3,6 +3,36 @@ use super::{Image, ImageData}; use eyre::{Context, Report, Result}; impl Image { + /// Creates a new `Image` in RGB8 format. + /// + /// This function constructs a new `Image` object with the given pixel data, width, height, + /// and an optional name. It ensures that the pixel data length matches the expected size + /// for the given width and height. + /// + /// # Arguments + /// + /// * `pixels` - A `Vec` containing the pixel data in RGB8 format. + /// * `width` - The width of the image. + /// * `height` - The height of the image. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the length of the pixel data does not match the expected size + /// based on the width and height. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// ``` pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { if pixels.len() != (width * height * 3) as usize { return Err(Report::msg("Invalid pixel data length.")); @@ -16,6 +46,33 @@ impl Image { })) } + /// Creates a new `Image` in RGB8 format from an ndarray. + /// + /// This function constructs a new `Image` object from an `ndarray::Array` with shape (height, width, 3). + /// It converts the ndarray into a raw vector and uses it to create the `Image`. + /// + /// # Arguments + /// + /// * `array` - An `ndarray::Array` containing the pixel data in RGB8 format. + /// * `name` - An optional string slice representing the name of the image. + /// + /// # Returns + /// + /// A `Result` containing the constructed `Image` if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the ndarray cannot be converted into a valid `Image`. + /// + /// # Example + /// + /// ``` + /// use ndarray::Array3; + /// use fastformat::image::Image; + /// + /// let array = Array3::::zeros((3, 3, 3)); // 3x3 image with 3 bytes per pixel + /// let image = Image::rgb8_from_ndarray(array, Some("example")).unwrap(); + /// ``` pub fn rgb8_from_ndarray( array: ndarray::Array, name: Option<&str>, @@ -28,6 +85,34 @@ impl Image { Self::new_rgb8(pixels, width, height, name) } + /// Converts an RGB8 `Image` into an ndarray. + /// + /// This function takes an RGB8 `Image` and converts it into an `ndarray::Array`. + /// The resulting ndarray has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `self` - The `Image` object to be converted. + /// + /// # Returns + /// + /// A `Result` containing the constructed ndarray if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in RGB8 format or if the pixel data cannot be reshaped + /// into the expected ndarray format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray = image.rgb8_to_ndarray().unwrap(); + /// ``` pub fn rgb8_to_ndarray(self) -> Result> { match self { Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( @@ -39,6 +124,34 @@ impl Image { } } + /// Converts an RGB8 `Image` into an ndarray view. + /// + /// This function takes a reference to an RGB8 `Image` and creates an `ndarray::ArrayView` + /// over the pixel data. The resulting view has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `&self` - A reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in RGB8 format or if the pixel data cannot be reshaped + /// into the expected ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view = image.rgb8_to_ndarray_view().unwrap(); + /// ``` pub fn rgb8_to_ndarray_view(&self) -> Result> { match self { Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( @@ -50,6 +163,34 @@ impl Image { } } + /// Converts a mutable RGB8 `Image` into a mutable ndarray view. + /// + /// This function takes a mutable reference to an RGB8 `Image` and creates an `ndarray::ArrayViewMut` + /// over the pixel data. The resulting view has shape (height, width, 3). + /// + /// # Arguments + /// + /// * `&mut self` - A mutable reference to the `Image` object to be viewed. + /// + /// # Returns + /// + /// A `Result` containing the mutable ndarray view if successful, or an error otherwise. + /// + /// # Errors + /// + /// Returns an error if the `Image` is not in RGB8 format or if the pixel data cannot be reshaped + /// into the expected mutable ndarray view format. + /// + /// # Example + /// + /// ``` + /// use fastformat::image::Image; + /// + /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let mut image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// + /// let ndarray_view_mut = image.rgb8_to_ndarray_view_mut().unwrap(); + /// ``` pub fn rgb8_to_ndarray_view_mut(&mut self) -> Result> { match self { Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( @@ -67,7 +208,7 @@ mod tests { fn test_rgb8_creation() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); } @@ -87,7 +228,7 @@ mod tests { fn test_rgb8_to_ndarray() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -98,7 +239,7 @@ mod tests { fn test_rgb8_to_ndarray_view() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -109,7 +250,7 @@ mod tests { fn test_rgb8_to_ndarray_view_mut() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let mut image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); @@ -120,7 +261,7 @@ mod tests { fn test_rgb8_ndarray_zero_copy_conversion() { use crate::image::Image; - let flat_image = (1..28).collect::>(); + let flat_image = vec![0; 27]; let original_buffer_address = flat_image.as_ptr(); let rgb8_image = Image::new_rgb8(flat_image, 3, 3, None).unwrap(); From 4848146c944869398a0968dd6cc7523d9bf97d5d Mon Sep 17 00:00:00 2001 From: Hennzau Date: Mon, 5 Aug 2024 00:34:43 +0200 Subject: [PATCH 06/11] Cargo clippy corrections --- src/arrow.rs | 2 +- src/image/arrow.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/arrow.rs b/src/arrow.rs index ddd9adc..e688af7 100644 --- a/src/arrow.rs +++ b/src/arrow.rs @@ -48,7 +48,7 @@ pub fn union_lookup_table(fields: &arrow::datatypes::UnionFields) -> HashMap(&array, "width", &lookup_table)?.value(0); @@ -67,7 +67,7 @@ impl Image { Some(name.value(0).to_string()) }; - let name = name.as_ref().map(|s| s.as_str()); + let name = name.as_deref(); unsafe { let array = mem::ManuallyDrop::new(array); From d8a7eaa0108c790d01f88189154520f0046fc9e1 Mon Sep 17 00:00:00 2001 From: Enzo Le Van Date: Mon, 5 Aug 2024 16:51:41 +0200 Subject: [PATCH 07/11] Update LICENSE --- LICENSE | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 201 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 2e27333..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2024 dora-rs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 4ddadda4a5338b06ec0e7dbd75cb94f85afdd481 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Mon, 5 Aug 2024 16:52:47 +0200 Subject: [PATCH 08/11] Update license to Apache 2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2e22451..fbf00ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "fastformat" version = "0.1.0" edition = "2021" -license = "MIT" +license = "Apache-2.0" description = "Fast world data converter" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 996585ea76dc6ee85d4e60d977320c716e8cfeee Mon Sep 17 00:00:00 2001 From: Hennzau Date: Mon, 5 Aug 2024 16:57:58 +0200 Subject: [PATCH 09/11] Change pixels field to data --- README.md | 4 ++-- src/arrow.rs | 4 ++-- src/image.rs | 24 ++++++++++++------------ src/image/arrow.rs | 40 ++++++++++++++++++++-------------------- src/image/bgr8.rs | 42 +++++++++++++++++++++--------------------- src/image/gray8.rs | 42 +++++++++++++++++++++--------------------- src/image/rgb8.rs | 40 ++++++++++++++++++++-------------------- 7 files changed, 98 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 87b1b11..e951c04 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Numpy, or Arrow, without unnecessary copies. ## DataTypes -- **Image** as a **UnionArray**, - - Field "pixels': UintXArray (e.g [0, 255, 0, 255, 0, 255, ...]) +- **Image**: (Arrow representation is a **UnionArray**), + - Field "data': UintXArray (e.g [0, 255, 0, 255, 0, 255, ...]) - Field "width": Uint32Array (e.g [1280]) - Field "height": Uint32Array (e.g [720]) - Field "encoding": StringArray (e.g ["RGB8"]) diff --git a/src/arrow.rs b/src/arrow.rs index e688af7..9f84456 100644 --- a/src/arrow.rs +++ b/src/arrow.rs @@ -82,8 +82,8 @@ pub fn union_lookup_table(fields: &arrow::datatypes::UnionFields) -> HashMap { - pixels: Vec, + data: Vec, width: u32, height: u32, @@ -25,23 +25,23 @@ pub enum Image { impl Image { pub fn as_ptr(&self) -> *const u8 { match self { - Self::ImageRGB8(image) => image.pixels.as_ptr(), - Self::ImageBGR8(image) => image.pixels.as_ptr(), - Self::ImageGray8(image) => image.pixels.as_ptr(), + Self::ImageRGB8(image) => image.data.as_ptr(), + Self::ImageBGR8(image) => image.data.as_ptr(), + Self::ImageGray8(image) => image.data.as_ptr(), } } pub fn to_rgb8(self) -> Result { match self { Self::ImageBGR8(image) => { - let mut pixels = image.pixels; + let mut data = image.data; - for i in (0..pixels.len()).step_by(3) { - pixels.swap(i, i + 2); + for i in (0..data.len()).step_by(3) { + data.swap(i, i + 2); } Ok(Self::ImageRGB8(ImageData { - pixels, + data, width: image.width, height: image.height, name: image.name.clone(), @@ -55,14 +55,14 @@ impl Image { pub fn to_bgr8(self) -> Result { match self { Self::ImageRGB8(image) => { - let mut pixels = image.pixels; + let mut data = image.data; - for i in (0..pixels.len()).step_by(3) { - pixels.swap(i, i + 2); + for i in (0..data.len()).step_by(3) { + data.swap(i, i + 2); } Ok(Self::ImageBGR8(ImageData { - pixels, + data, width: image.width, height: image.height, name: image.name.clone(), diff --git a/src/image/arrow.rs b/src/image/arrow.rs index 8051d01..bb96766 100644 --- a/src/image/arrow.rs +++ b/src/image/arrow.rs @@ -32,8 +32,8 @@ impl Image { /// use arrow::array::UnionArray; /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_bgr8(pixels, 3, 3, None).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(data, 3, 3, None).unwrap(); /// let array = image.to_arrow().unwrap(); /// /// let image = Image::from_arrow(array).unwrap(); @@ -71,30 +71,30 @@ impl Image { unsafe { let array = mem::ManuallyDrop::new(array); - let pixels = match encoding.as_str() { + let data = match encoding.as_str() { "RGB8" => { - column_by_name::(&array, "pixels", &lookup_table)? + column_by_name::(&array, "data", &lookup_table)? } "BGR8" => { - column_by_name::(&array, "pixels", &lookup_table)? + column_by_name::(&array, "data", &lookup_table)? } "GRAY8" => { - column_by_name::(&array, "pixels", &lookup_table)? + column_by_name::(&array, "data", &lookup_table)? } _ => { return Err(Report::msg(format!("Invalid encoding: {}", encoding))); } }; - let ptr = pixels.values().as_ptr(); - let len = pixels.len(); + let ptr = data.values().as_ptr(); + let len = data.len(); - let pixels = Vec::from_raw_parts(ptr as *mut u8, len, len); + let data = Vec::from_raw_parts(ptr as *mut u8, len, len); return match encoding.as_str() { - "RGB8" => Self::new_rgb8(pixels, width, height, name), - "BGR8" => Self::new_bgr8(pixels, width, height, name), - "GRAY8" => Self::new_gray8(pixels, width, height, name), + "RGB8" => Self::new_rgb8(data, width, height, name), + "BGR8" => Self::new_bgr8(data, width, height, name), + "GRAY8" => Self::new_gray8(data, width, height, name), _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), }; } @@ -153,29 +153,29 @@ impl Image { /// use fastformat::image::Image; /// use fastformat::image::ImageData; /// - /// let pixels = vec![0; 640 * 480 * 3]; - /// let image = Image::new_bgr8(pixels, 640, 480, None).unwrap(); + /// let data = vec![0; 640 * 480 * 3]; + /// let image = Image::new_bgr8(data, 640, 480, None).unwrap(); /// /// let arrow_array = image.to_arrow().unwrap(); /// ``` pub fn to_arrow(self) -> Result { - let ((width, height, name), encoding, pixels, datatype) = match self { + let ((width, height, name), encoding, data, datatype) = match self { Image::ImageBGR8(image) => ( Self::get_image_details(&image), arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), + arrow::array::UInt8Array::from(image.data), arrow::datatypes::DataType::UInt8, ), Image::ImageRGB8(image) => ( Self::get_image_details(&image), arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), + arrow::array::UInt8Array::from(image.data), arrow::datatypes::DataType::UInt8, ), Image::ImageGray8(image) => ( Self::get_image_details(&image), arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), - arrow::array::UInt8Array::from(image.pixels), + arrow::array::UInt8Array::from(image.data), arrow::datatypes::DataType::UInt8, ), }; @@ -184,7 +184,7 @@ impl Image { let offsets = [].into_iter().collect::>(); let union_fields = [ - union_field(0, "pixels", datatype, false), + union_field(0, "data", datatype, false), union_field(1, "width", arrow::datatypes::DataType::UInt32, false), union_field(2, "height", arrow::datatypes::DataType::UInt32, false), union_field(3, "encoding", arrow::datatypes::DataType::Utf8, false), @@ -194,7 +194,7 @@ impl Image { .collect::(); let children: Vec> = vec![ - Arc::new(pixels), + Arc::new(data), Arc::new(width), Arc::new(height), Arc::new(encoding), diff --git a/src/image/bgr8.rs b/src/image/bgr8.rs index e4b1371..e41b3b7 100644 --- a/src/image/bgr8.rs +++ b/src/image/bgr8.rs @@ -11,7 +11,7 @@ impl Image { /// /// # Arguments /// - /// * `pixels` - A `Vec` containing the pixel data in BGR8 format. + /// * `data` - A `Vec` containing the pixel data in BGR8 format. /// * `width` - The width of the image. /// * `height` - The height of the image. /// * `name` - An optional string slice representing the name of the image. @@ -30,18 +30,18 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(data, 3, 3, Some("example")).unwrap(); /// ``` - pub fn new_bgr8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { - if width * height * 3 != pixels.len() as u32 { + pub fn new_bgr8(data: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if width * height * 3 != data.len() as u32 { return Err(Report::msg( - "Width, height and BGR8 encoding doesn't match pixels data length.", + "Width, height and BGR8 encoding doesn't match data data length.", )); } Ok(Self::ImageBGR8(ImageData { - pixels, + data, width, height, name: name.map(|s| s.to_string()), @@ -82,9 +82,9 @@ impl Image { let width = array.shape()[1] as u32; let height = array.shape()[0] as u32; - let pixels = array.into_raw_vec(); + let data = array.into_raw_vec(); - Self::new_bgr8(pixels, width, height, name) + Self::new_bgr8(data, width, height, name) } /// Converts a BGR8 `Image` into an ndarray. @@ -110,8 +110,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray = image.bgr8_to_ndarray().unwrap(); /// ``` @@ -119,9 +119,9 @@ impl Image { match self { Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( (image.height as usize, image.width as usize, 3), - image.pixels, + image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), } } @@ -149,8 +149,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_bgr8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view = image.bgr8_to_ndarray_view().unwrap(); /// ``` @@ -158,9 +158,9 @@ impl Image { match self { Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( (image.height as usize, image.width as usize, 3), - &image.pixels, + &image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), } } @@ -188,8 +188,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let mut image = Image::new_bgr8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let mut image = Image::new_bgr8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view_mut = image.bgr8_to_ndarray_view_mut().unwrap(); /// ``` @@ -197,9 +197,9 @@ impl Image { match self { Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( (image.height as usize, image.width as usize, 3), - &mut image.pixels, + &mut image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and BGR8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), } } diff --git a/src/image/gray8.rs b/src/image/gray8.rs index efb7d8c..dbd3b2c 100644 --- a/src/image/gray8.rs +++ b/src/image/gray8.rs @@ -11,7 +11,7 @@ impl Image { /// /// # Arguments /// - /// * `pixels` - A `Vec` containing the pixel data in Gray8 format. + /// * `data` - A `Vec` containing the pixel data in Gray8 format. /// * `width` - The width of the image. /// * `height` - The height of the image. /// * `name` - An optional string slice representing the name of the image. @@ -30,16 +30,16 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel - /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(data, 3, 3, Some("example")).unwrap(); /// ``` - pub fn new_gray8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { - if pixels.len() != (width * height) as usize { - return Err(Report::msg("Invalid pixels data length.")); + pub fn new_gray8(data: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if data.len() != (width * height) as usize { + return Err(Report::msg("Invalid data data length.")); } Ok(Self::ImageGray8(ImageData { - pixels, + data, width, height, name: name.map(|s| s.to_string()), @@ -80,9 +80,9 @@ impl Image { let width = array.shape()[1] as u32; let height = array.shape()[0] as u32; - let pixels = array.into_raw_vec(); + let data = array.into_raw_vec(); - Self::new_gray8(pixels, width, height, name) + Self::new_gray8(data, width, height, name) } /// Converts a Gray8 `Image` into an ndarray. @@ -108,8 +108,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel - /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray = image.gray8_to_ndarray().unwrap(); /// ``` @@ -117,9 +117,9 @@ impl Image { match self { Self::ImageGray8(image) => ndarray::Array::from_shape_vec( (image.height as usize, image.width as usize), - image.pixels, + image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), } } @@ -147,8 +147,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel - /// let image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let image = Image::new_gray8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view = image.gray8_to_ndarray_view().unwrap(); /// ``` @@ -156,9 +156,9 @@ impl Image { match self { Self::ImageGray8(image) => ndarray::ArrayView::from_shape( (image.height as usize, image.width as usize), - &image.pixels, + &image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), } } @@ -186,8 +186,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 9]; // 3x3 image with 1 byte per pixel - /// let mut image = Image::new_gray8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 9]; // 3x3 image with 1 byte per pixel + /// let mut image = Image::new_gray8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view_mut = image.gray8_to_ndarray_view_mut().unwrap(); /// ``` @@ -195,9 +195,9 @@ impl Image { match self { Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( (image.height as usize, image.width as usize), - &mut image.pixels, + &mut image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and Gray8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), } } diff --git a/src/image/rgb8.rs b/src/image/rgb8.rs index ffb10fd..3ed6354 100644 --- a/src/image/rgb8.rs +++ b/src/image/rgb8.rs @@ -11,7 +11,7 @@ impl Image { /// /// # Arguments /// - /// * `pixels` - A `Vec` containing the pixel data in RGB8 format. + /// * `data` - A `Vec` containing the pixel data in RGB8 format. /// * `width` - The width of the image. /// * `height` - The height of the image. /// * `name` - An optional string slice representing the name of the image. @@ -30,16 +30,16 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(data, 3, 3, Some("example")).unwrap(); /// ``` - pub fn new_rgb8(pixels: Vec, width: u32, height: u32, name: Option<&str>) -> Result { - if pixels.len() != (width * height * 3) as usize { + pub fn new_rgb8(data: Vec, width: u32, height: u32, name: Option<&str>) -> Result { + if data.len() != (width * height * 3) as usize { return Err(Report::msg("Invalid pixel data length.")); } Ok(Self::ImageRGB8(ImageData { - pixels, + data, width, height, name: name.map(|s| s.to_string()), @@ -80,9 +80,9 @@ impl Image { let width = array.shape()[1] as u32; let height = array.shape()[0] as u32; - let pixels = array.into_raw_vec(); + let data = array.into_raw_vec(); - Self::new_rgb8(pixels, width, height, name) + Self::new_rgb8(data, width, height, name) } /// Converts an RGB8 `Image` into an ndarray. @@ -108,8 +108,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray = image.rgb8_to_ndarray().unwrap(); /// ``` @@ -117,9 +117,9 @@ impl Image { match self { Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( (image.height as usize, image.width as usize, 3), - image.pixels, + image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), } } @@ -147,8 +147,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let image = Image::new_rgb8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view = image.rgb8_to_ndarray_view().unwrap(); /// ``` @@ -156,9 +156,9 @@ impl Image { match self { Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( (image.height as usize, image.width as usize, 3), - &image.pixels, + &image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), } } @@ -186,8 +186,8 @@ impl Image { /// ``` /// use fastformat::image::Image; /// - /// let pixels = vec![0; 27]; // 3x3 image with 3 bytes per pixel - /// let mut image = Image::new_rgb8(pixels, 3, 3, Some("example")).unwrap(); + /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel + /// let mut image = Image::new_rgb8(data, 3, 3, Some("example")).unwrap(); /// /// let ndarray_view_mut = image.rgb8_to_ndarray_view_mut().unwrap(); /// ``` @@ -195,9 +195,9 @@ impl Image { match self { Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( (image.height as usize, image.width as usize, 3), - &mut image.pixels, + &mut image.data, ) - .wrap_err("Failed to reshape pixels into ndarray: width, height and RGB8 encoding doesn't match pixels data length."), + .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), } } From e1fd557ae1027f10fef53ec7423a618c64ef78f5 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Mon, 5 Aug 2024 23:45:01 +0200 Subject: [PATCH 10/11] Reformat Image structure to be able to parse and convert different datatype(u8, u16 etc...) + parser for encoding --- src/image.rs | 65 +++++++++--------- src/image/arrow.rs | 149 ++++++++++++++++++----------------------- src/image/bgr8.rs | 34 +++++----- src/image/container.rs | 36 ++++++++++ src/image/encoding.rs | 31 +++++++++ src/image/gray8.rs | 34 +++++----- src/image/rgb8.rs | 34 +++++----- 7 files changed, 214 insertions(+), 169 deletions(-) create mode 100644 src/image/container.rs create mode 100644 src/image/encoding.rs diff --git a/src/image.rs b/src/image.rs index 00eef78..bb0af3b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,74 +1,73 @@ use eyre::{Report, Result}; +use container::DataContainer; +use encoding::Encoding; + mod bgr8; mod gray8; mod rgb8; mod arrow; +mod container; +mod encoding; #[derive(Debug)] -pub struct ImageData { - data: Vec, +pub struct Image { + data: DataContainer, width: u32, height: u32, - name: Option, -} + encoding: Encoding, -pub enum Image { - ImageRGB8(ImageData), - ImageBGR8(ImageData), - ImageGray8(ImageData), + name: Option, } impl Image { pub fn as_ptr(&self) -> *const u8 { - match self { - Self::ImageRGB8(image) => image.data.as_ptr(), - Self::ImageBGR8(image) => image.data.as_ptr(), - Self::ImageGray8(image) => image.data.as_ptr(), - } + self.data.as_ptr() } pub fn to_rgb8(self) -> Result { - match self { - Self::ImageBGR8(image) => { - let mut data = image.data; + match self.encoding { + Encoding::BGR8 => { + let mut data = self.data.into_u8()?; for i in (0..data.len()).step_by(3) { data.swap(i, i + 2); } - Ok(Self::ImageRGB8(ImageData { - data, - width: image.width, - height: image.height, - name: image.name.clone(), - })) + Ok(Image { + data: DataContainer::from_u8(data), + width: self.width, + height: self.height, + encoding: Encoding::RGB8, + name: self.name.clone(), + }) } - Self::ImageRGB8(_) => Ok(self), + Encoding::RGB8 => Ok(self), _ => Err(Report::msg("Can't convert image to RGB8")), } } pub fn to_bgr8(self) -> Result { - match self { - Self::ImageRGB8(image) => { - let mut data = image.data; + match self.encoding { + Encoding::RGB8 => { + let mut data = self.data.into_u8()?; for i in (0..data.len()).step_by(3) { data.swap(i, i + 2); } - Ok(Self::ImageBGR8(ImageData { - data, - width: image.width, - height: image.height, - name: image.name.clone(), - })) + Ok(Image { + data: DataContainer::from_u8(data), + width: self.width, + height: self.height, + encoding: Encoding::BGR8, + name: self.name.clone(), + }) } - Self::ImageBGR8(_) => Ok(self), + Encoding::BGR8 => Ok(self), _ => Err(Report::msg("Can't convert image to BGR8")), } } diff --git a/src/image/arrow.rs b/src/image/arrow.rs index bb96766..d518765 100644 --- a/src/image/arrow.rs +++ b/src/image/arrow.rs @@ -1,11 +1,22 @@ use crate::arrow::{column_by_name, union_field, union_lookup_table}; -use super::{Image, ImageData}; +use super::{container::DataContainer, encoding::Encoding, Image}; use eyre::{Context, Report, Result}; -use std::{mem, sync::Arc}; +use std::{collections::HashMap, mem, sync::Arc}; impl Image { + unsafe fn arrow_data_to_vec( + array: &arrow::array::UnionArray, + lookup_table: &HashMap, + ) -> Result> { + let arrow = column_by_name::>(array, "data", lookup_table)?; + let ptr = arrow.values().as_ptr(); + let len = arrow.len(); + + Ok(Vec::from_raw_parts(ptr as *mut G, len, len)) + } + /// Constructs an `Image` from an Arrow `UnionArray`. /// /// This function takes an Arrow `UnionArray` and extracts the necessary fields to construct @@ -54,10 +65,11 @@ impl Image { column_by_name::(&array, "width", &lookup_table)?.value(0); let height = column_by_name::(&array, "height", &lookup_table)?.value(0); - let encoding = + let encoding = Encoding::from_string( column_by_name::(&array, "encoding", &lookup_table)? .value(0) - .to_string(); + .to_string(), + )?; let name = column_by_name::(&array, "name", &lookup_table)?; @@ -67,65 +79,54 @@ impl Image { Some(name.value(0).to_string()) }; - let name = name.as_deref(); - unsafe { let array = mem::ManuallyDrop::new(array); - let data = match encoding.as_str() { - "RGB8" => { - column_by_name::(&array, "data", &lookup_table)? - } - "BGR8" => { - column_by_name::(&array, "data", &lookup_table)? - } - "GRAY8" => { - column_by_name::(&array, "data", &lookup_table)? - } - _ => { - return Err(Report::msg(format!("Invalid encoding: {}", encoding))); - } - }; - - let ptr = data.values().as_ptr(); - let len = data.len(); - - let data = Vec::from_raw_parts(ptr as *mut u8, len, len); - return match encoding.as_str() { - "RGB8" => Self::new_rgb8(data, width, height, name), - "BGR8" => Self::new_bgr8(data, width, height, name), - "GRAY8" => Self::new_gray8(data, width, height, name), - _ => Err(Report::msg(format!("Invalid encoding: {}", encoding))), + let data = match encoding { + Encoding::RGB8 => DataContainer::from_u8(Self::arrow_data_to_vec::< + arrow::datatypes::UInt8Type, + u8, + >(&array, &lookup_table)?), + Encoding::BGR8 => DataContainer::from_u8(Self::arrow_data_to_vec::< + arrow::datatypes::UInt8Type, + u8, + >(&array, &lookup_table)?), + Encoding::GRAY8 => DataContainer::from_u8(Self::arrow_data_to_vec::< + arrow::datatypes::UInt8Type, + u8, + >(&array, &lookup_table)?), }; + + Ok(Image { + data, + width, + height, + encoding, + name, + }) } } - /// Extracts image details (width, height, name) from an `ImageData` object. - /// - /// This function takes a reference to an `ImageData` object and creates Arrow arrays for the width, - /// height, and name of the image. - /// - /// # Arguments - /// - /// * `image` - A reference to an `ImageData` object that contains the image properties. - /// - /// # Returns - /// - /// A tuple containing an `arrow::array::UInt32Array` for the width, an `arrow::array::UInt32Array` for the height, - /// and an `arrow::array::StringArray` for the name of the image. - fn get_image_details( - image: &ImageData, - ) -> ( - arrow::array::UInt32Array, - arrow::array::UInt32Array, - arrow::array::StringArray, - ) { - let width = arrow::array::UInt32Array::from(vec![image.width; 1]); - let height = arrow::array::UInt32Array::from(vec![image.height; 1]); - - let name = arrow::array::StringArray::from(vec![image.name.clone(); 1]); - - (width, height, name) + fn convert_image_details_to_arrow(image: Image) -> Result>> { + let width = Arc::new(arrow::array::UInt32Array::from(vec![image.width; 1])); + let height = Arc::new(arrow::array::UInt32Array::from(vec![image.height; 1])); + + let encoding = Arc::new(arrow::array::StringArray::from(vec![ + image + .encoding + .to_string(); + 1 + ])); + + let name = Arc::new(arrow::array::StringArray::from(vec![image.name.clone(); 1])); + + let data: Arc = match image.encoding { + Encoding::RGB8 => Arc::new(arrow::array::UInt8Array::from(image.data.into_u8()?)), + Encoding::BGR8 => Arc::new(arrow::array::UInt8Array::from(image.data.into_u8()?)), + Encoding::GRAY8 => Arc::new(arrow::array::UInt8Array::from(image.data.into_u8()?)), + }; + + Ok(vec![data, width, height, encoding, name]) } /// Converts an `Image` into an Arrow `UnionArray`. @@ -151,7 +152,6 @@ impl Image { /// /// ``` /// use fastformat::image::Image; - /// use fastformat::image::ImageData; /// /// let data = vec![0; 640 * 480 * 3]; /// let image = Image::new_bgr8(data, 640, 480, None).unwrap(); @@ -159,30 +159,15 @@ impl Image { /// let arrow_array = image.to_arrow().unwrap(); /// ``` pub fn to_arrow(self) -> Result { - let ((width, height, name), encoding, data, datatype) = match self { - Image::ImageBGR8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["BGR8".to_string(); 1]), - arrow::array::UInt8Array::from(image.data), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageRGB8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["RGB8".to_string(); 1]), - arrow::array::UInt8Array::from(image.data), - arrow::datatypes::DataType::UInt8, - ), - Image::ImageGray8(image) => ( - Self::get_image_details(&image), - arrow::array::StringArray::from(vec!["GRAY8".to_string(); 1]), - arrow::array::UInt8Array::from(image.data), - arrow::datatypes::DataType::UInt8, - ), - }; - let type_ids = [].into_iter().collect::>(); let offsets = [].into_iter().collect::>(); + let datatype = match self.encoding { + Encoding::RGB8 => arrow::datatypes::DataType::UInt8, + Encoding::BGR8 => arrow::datatypes::DataType::UInt8, + Encoding::GRAY8 => arrow::datatypes::DataType::UInt8, + }; + let union_fields = [ union_field(0, "data", datatype, false), union_field(1, "width", arrow::datatypes::DataType::UInt32, false), @@ -193,13 +178,7 @@ impl Image { .into_iter() .collect::(); - let children: Vec> = vec![ - Arc::new(data), - Arc::new(width), - Arc::new(height), - Arc::new(encoding), - Arc::new(name), - ]; + let children = Self::convert_image_details_to_arrow(self)?; arrow::array::UnionArray::try_new(union_fields, type_ids, Some(offsets), children) .wrap_err("Failed to create UnionArray width Image data.") diff --git a/src/image/bgr8.rs b/src/image/bgr8.rs index e41b3b7..f9c1f08 100644 --- a/src/image/bgr8.rs +++ b/src/image/bgr8.rs @@ -1,5 +1,4 @@ -use super::{Image, ImageData}; - +use super::{container::DataContainer, encoding::Encoding, Image}; use eyre::{Context, Report, Result}; impl Image { @@ -40,12 +39,13 @@ impl Image { )); } - Ok(Self::ImageBGR8(ImageData { - data, + Ok(Image { + data: DataContainer::from_u8(data), width, height, + encoding: Encoding::BGR8, name: name.map(|s| s.to_string()), - })) + }) } /// Creates a new `Image` in BGR8 format from an ndarray. @@ -116,10 +116,10 @@ impl Image { /// let ndarray = image.bgr8_to_ndarray().unwrap(); /// ``` pub fn bgr8_to_ndarray(self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.data, + match self.encoding { + Encoding::BGR8 => ndarray::Array::from_shape_vec( + (self.height as usize, self.width as usize, 3), + self.data.into_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), @@ -155,10 +155,10 @@ impl Image { /// let ndarray_view = image.bgr8_to_ndarray_view().unwrap(); /// ``` pub fn bgr8_to_ndarray_view(&self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.data, + match self.encoding { + Encoding::BGR8 => ndarray::ArrayView::from_shape( + (self.height as usize, self.width as usize, 3), + self.data.as_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), @@ -194,10 +194,10 @@ impl Image { /// let ndarray_view_mut = image.bgr8_to_ndarray_view_mut().unwrap(); /// ``` pub fn bgr8_to_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageBGR8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.data, + match self.encoding { + Encoding::BGR8 => ndarray::ArrayViewMut::from_shape( + (self.height as usize, self.width as usize, 3), + self.data.as_mut_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and BGR8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in BGR8 format")), diff --git a/src/image/container.rs b/src/image/container.rs new file mode 100644 index 0000000..d05dd91 --- /dev/null +++ b/src/image/container.rs @@ -0,0 +1,36 @@ +use eyre::Result; + +#[derive(Debug)] +pub enum DataContainer { + U8Data(Vec), +} + +impl DataContainer { + pub fn as_ptr(&self) -> *const u8 { + match self { + Self::U8Data(data) => data.as_ptr(), + } + } + + pub fn into_u8(self) -> Result> { + match self { + Self::U8Data(data) => Ok(data), + } + } + + pub fn as_u8(&self) -> Result<&Vec> { + match self { + Self::U8Data(data) => Ok(data), + } + } + + pub fn as_mut_u8(&mut self) -> Result<&mut Vec> { + match self { + Self::U8Data(data) => Ok(data), + } + } + + pub fn from_u8(data: Vec) -> Self { + Self::U8Data(data) + } +} diff --git a/src/image/encoding.rs b/src/image/encoding.rs new file mode 100644 index 0000000..ff1c22c --- /dev/null +++ b/src/image/encoding.rs @@ -0,0 +1,31 @@ +use eyre::{Report, Result}; + +use std::fmt::Display; + +#[derive(Debug, Clone, Copy)] +pub enum Encoding { + RGB8, + BGR8, + GRAY8, +} + +impl Encoding { + pub fn from_string(encoding: String) -> Result { + match encoding.as_str() { + "RGB8" => Ok(Self::RGB8), + "BGR8" => Ok(Self::BGR8), + "GRAY8" => Ok(Self::GRAY8), + _ => Err(Report::msg(format!("Invalid String Encoding {}", encoding))), + } + } +} + +impl Display for Encoding { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match self { + Self::RGB8 => write!(fmt, "RGB8"), + Self::BGR8 => write!(fmt, "BGR8"), + Self::GRAY8 => write!(fmt, "GRAY8"), + } + } +} diff --git a/src/image/gray8.rs b/src/image/gray8.rs index dbd3b2c..9a02230 100644 --- a/src/image/gray8.rs +++ b/src/image/gray8.rs @@ -1,5 +1,4 @@ -use super::{Image, ImageData}; - +use super::{container::DataContainer, encoding::Encoding, Image}; use eyre::{Context, Report, Result}; impl Image { @@ -38,12 +37,13 @@ impl Image { return Err(Report::msg("Invalid data data length.")); } - Ok(Self::ImageGray8(ImageData { - data, + Ok(Image { + data: DataContainer::from_u8(data), width, height, + encoding: Encoding::GRAY8, name: name.map(|s| s.to_string()), - })) + }) } /// Creates a new `Image` in Gray8 format from an ndarray. @@ -114,10 +114,10 @@ impl Image { /// let ndarray = image.gray8_to_ndarray().unwrap(); /// ``` pub fn gray8_to_ndarray(self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize), - image.data, + match self.encoding { + Encoding::GRAY8 => ndarray::Array::from_shape_vec( + (self.height as usize, self.width as usize), + self.data.into_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), @@ -153,10 +153,10 @@ impl Image { /// let ndarray_view = image.gray8_to_ndarray_view().unwrap(); /// ``` pub fn gray8_to_ndarray_view(&self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize), - &image.data, + match self.encoding { + Encoding::GRAY8 => ndarray::ArrayView::from_shape( + (self.height as usize, self.width as usize), + self.data.as_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), @@ -192,10 +192,10 @@ impl Image { /// let ndarray_view_mut = image.gray8_to_ndarray_view_mut().unwrap(); /// ``` pub fn gray8_to_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageGray8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize), - &mut image.data, + match self.encoding { + Encoding::GRAY8 => ndarray::ArrayViewMut::from_shape( + (self.height as usize, self.width as usize), + self.data.as_mut_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and Gray8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in Gray8 format")), diff --git a/src/image/rgb8.rs b/src/image/rgb8.rs index 3ed6354..8ebfda2 100644 --- a/src/image/rgb8.rs +++ b/src/image/rgb8.rs @@ -1,5 +1,4 @@ -use super::{Image, ImageData}; - +use super::{container::DataContainer, encoding::Encoding, Image}; use eyre::{Context, Report, Result}; impl Image { @@ -38,12 +37,13 @@ impl Image { return Err(Report::msg("Invalid pixel data length.")); } - Ok(Self::ImageRGB8(ImageData { - data, + Ok(Image { + data: DataContainer::from_u8(data), width, height, + encoding: Encoding::RGB8, name: name.map(|s| s.to_string()), - })) + }) } /// Creates a new `Image` in RGB8 format from an ndarray. @@ -114,10 +114,10 @@ impl Image { /// let ndarray = image.rgb8_to_ndarray().unwrap(); /// ``` pub fn rgb8_to_ndarray(self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::Array::from_shape_vec( - (image.height as usize, image.width as usize, 3), - image.data, + match self.encoding { + Encoding::RGB8 => ndarray::Array::from_shape_vec( + (self.height as usize, self.width as usize, 3), + self.data.into_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), @@ -153,10 +153,10 @@ impl Image { /// let ndarray_view = image.rgb8_to_ndarray_view().unwrap(); /// ``` pub fn rgb8_to_ndarray_view(&self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayView::from_shape( - (image.height as usize, image.width as usize, 3), - &image.data, + match self.encoding { + Encoding::RGB8 => ndarray::ArrayView::from_shape( + (self.height as usize, self.width as usize, 3), + self.data.as_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), @@ -192,10 +192,10 @@ impl Image { /// let ndarray_view_mut = image.rgb8_to_ndarray_view_mut().unwrap(); /// ``` pub fn rgb8_to_ndarray_view_mut(&mut self) -> Result> { - match self { - Self::ImageRGB8(image) => ndarray::ArrayViewMut::from_shape( - (image.height as usize, image.width as usize, 3), - &mut image.data, + match self.encoding { + Encoding::RGB8 => ndarray::ArrayViewMut::from_shape( + (self.height as usize, self.width as usize, 3), + self.data.as_mut_u8()?, ) .wrap_err("Failed to reshape data into ndarray: width, height and RGB8 encoding doesn't match data data length."), _ => Err(Report::msg("Image is not in RGB8 format")), From 8e216f218a470e6ef850569d81a5861ef6fe6c97 Mon Sep 17 00:00:00 2001 From: Hennzau Date: Tue, 6 Aug 2024 12:19:41 +0200 Subject: [PATCH 11/11] Add rgb<->bgr conversion test --- src/image.rs | 32 ++++++++++++++++++++++++++------ src/image/arrow.rs | 3 +-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/image.rs b/src/image.rs index bb0af3b..9af6e3d 100644 --- a/src/image.rs +++ b/src/image.rs @@ -28,6 +28,10 @@ impl Image { self.data.as_ptr() } + pub fn data(&self) -> &DataContainer { + &self.data + } + pub fn to_rgb8(self) -> Result { match self.encoding { Encoding::BGR8 => { @@ -78,19 +82,35 @@ mod tests { fn test_rgb8_to_bgr8() { use crate::image::Image; - let flat_image = vec![0; 27]; - + let flat_image = (0..27).collect::>(); let image = Image::new_rgb8(flat_image, 3, 3, Some("camera.test")).unwrap(); - image.to_bgr8().unwrap(); + + let final_image = image.to_bgr8().unwrap(); + let final_image_data = final_image.data().as_u8().unwrap(); + + let expected_image = vec![ + 2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9, 14, 13, 12, 17, 16, 15, 20, 19, 18, 23, 22, 21, + 26, 25, 24, + ]; + + assert_eq!(&expected_image, final_image_data); } #[test] fn test_bgr8_to_rgb8() { use crate::image::Image; - let flat_image = vec![0; 27]; - + let flat_image = (0..27).collect::>(); let image = Image::new_bgr8(flat_image, 3, 3, Some("camera.test")).unwrap(); - image.to_rgb8().unwrap(); + + let final_image = image.to_rgb8().unwrap(); + let final_image_data = final_image.data().as_u8().unwrap(); + + let expected_image = vec![ + 2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9, 14, 13, 12, 17, 16, 15, 20, 19, 18, 23, 22, 21, + 26, 25, 24, + ]; + + assert_eq!(&expected_image, final_image_data); } } diff --git a/src/image/arrow.rs b/src/image/arrow.rs index d518765..3661588 100644 --- a/src/image/arrow.rs +++ b/src/image/arrow.rs @@ -40,7 +40,6 @@ impl Image { /// # Example /// /// ``` - /// use arrow::array::UnionArray; /// use fastformat::image::Image; /// /// let data = vec![0; 27]; // 3x3 image with 3 bytes per pixel @@ -187,7 +186,7 @@ impl Image { mod tests { #[test] - fn test_arrow_conversion() { + fn test_arrow_zero_copy_conversion() { use crate::image::Image; let flat_image = vec![0; 27];