diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b8415d..56dd33a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,16 @@ find_package( LibCarna "3.4.0" REQUIRED COMPONENTS release ) include_directories( ${LibCarna_INCLUDE_DIR} ) set( LIBCARNA_VERSION ${FOUND_VERSION} ) +############################################ +# Macro that sets variable to default value +# only when the variable isn't defined yet +############################################ + +macro( copy_python_aux_files mod_path ) + file( GLOB PYTHON_AUX_FILES "${CMAKE_CURRENT_SOURCE_DIR}/misc/${mod_path}/*.py" ) + file( COPY ${PYTHON_AUX_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${mod_path}" ) +endmacro() + ############################################ configure_file( @@ -95,13 +105,17 @@ configure_file( @ONLY ) -file( GLOB PYTHON_AUX_FILES "${CMAKE_CURRENT_SOURCE_DIR}/misc/libcarna/*.py" ) -file( COPY ${PYTHON_AUX_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${PYTHON_MODULE_NAME}" ) +copy_python_aux_files( "${PYTHON_MODULE_NAME}" ) +copy_python_aux_files( "${PYTHON_MODULE_NAME}/libs" ) +copy_python_aux_files( "${PYTHON_MODULE_NAME}/libs/skvideo" ) +copy_python_aux_files( "${PYTHON_MODULE_NAME}/libs/skvideo/io" ) +copy_python_aux_files( "${PYTHON_MODULE_NAME}/libs/skvideo/utils" ) file( GLOB LICENSES "${LibCarna_LICENSE_DIR}/LICENSE*" ) file( COPY ${LICENSES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) file( COPY ${CMAKE_CURRENT_SOURCE_DIR}/README.md DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) file( COPY ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) +file( COPY ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE-skvideo DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" ) ############################################ # Project diff --git a/LICENSE-skvideo b/LICENSE-skvideo new file mode 100644 index 0000000..00c747e --- /dev/null +++ b/LICENSE-skvideo @@ -0,0 +1,32 @@ +New BSD License + +Copyright (c) 2015 The scikit-video developers. +All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of the scikit-video developers nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + diff --git a/environment.yml b/environment.yml index 78929e0..7998f6e 100644 --- a/environment.yml +++ b/environment.yml @@ -28,7 +28,6 @@ dependencies: - libcarna ==3.4.0 - matplotlib-base # for `_colormap_helper` - numpngw ==0.1.4 # writes APNG - - scikit-video ==1.1.11 # API for ffmpeg - ffmpeg # writes h264 - scipy # for `libcarna.data` and `libcarna.normalize_hounsfield_units` - scikit-image # for `libcarna.data` diff --git a/examples/cells.ipynb b/examples/cells.ipynb index 43da89c..dc734dc 100644 --- a/examples/cells.ipynb +++ b/examples/cells.ipynb @@ -16,10 +16,10 @@ "id": "59e8a2c3", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:25.120269Z", - "iopub.status.busy": "2025-05-13T11:30:25.120166Z", - "iopub.status.idle": "2025-05-13T11:30:25.533939Z", - "shell.execute_reply": "2025-05-13T11:30:25.533481Z" + "iopub.execute_input": "2025-05-15T21:27:19.188480Z", + "iopub.status.busy": "2025-05-15T21:27:19.188383Z", + "iopub.status.idle": "2025-05-15T21:27:19.451209Z", + "shell.execute_reply": "2025-05-15T21:27:19.450771Z" }, "vscode": { "languageId": "plaintext" @@ -46,10 +46,10 @@ "id": "97c2c4ed", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:25.535447Z", - "iopub.status.busy": "2025-05-13T11:30:25.535270Z", - "iopub.status.idle": "2025-05-13T11:30:25.596255Z", - "shell.execute_reply": "2025-05-13T11:30:25.595909Z" + "iopub.execute_input": "2025-05-15T21:27:19.452634Z", + "iopub.status.busy": "2025-05-15T21:27:19.452366Z", + "iopub.status.idle": "2025-05-15T21:27:19.511614Z", + "shell.execute_reply": "2025-05-15T21:27:19.511231Z" }, "vscode": { "languageId": "plaintext" @@ -91,10 +91,10 @@ "id": "ed16156a", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:25.597390Z", - "iopub.status.busy": "2025-05-13T11:30:25.597224Z", - "iopub.status.idle": "2025-05-13T11:30:26.469406Z", - "shell.execute_reply": "2025-05-13T11:30:26.469030Z" + "iopub.execute_input": "2025-05-15T21:27:19.512874Z", + "iopub.status.busy": "2025-05-15T21:27:19.512591Z", + "iopub.status.idle": "2025-05-15T21:27:20.356472Z", + "shell.execute_reply": "2025-05-15T21:27:20.355943Z" }, "vscode": { "languageId": "plaintext" @@ -107,7 +107,7 @@ "\n", "
\n", "
\n", - " \n", + " \n", "
\n", " \n", "
\n", @@ -209,10 +209,10 @@ "id": "d35452de", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:26.472115Z", - "iopub.status.busy": "2025-05-13T11:30:26.471993Z", - "iopub.status.idle": "2025-05-13T11:30:26.889987Z", - "shell.execute_reply": "2025-05-13T11:30:26.889554Z" + "iopub.execute_input": "2025-05-15T21:27:20.360150Z", + "iopub.status.busy": "2025-05-15T21:27:20.360030Z", + "iopub.status.idle": "2025-05-15T21:27:20.791692Z", + "shell.execute_reply": "2025-05-15T21:27:20.791163Z" }, "vscode": { "languageId": "plaintext" @@ -322,10 +322,10 @@ "id": "c33d38bc", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:26.894851Z", - "iopub.status.busy": "2025-05-13T11:30:26.894647Z", - "iopub.status.idle": "2025-05-13T11:30:26.899321Z", - "shell.execute_reply": "2025-05-13T11:30:26.898913Z" + "iopub.execute_input": "2025-05-15T21:27:20.796659Z", + "iopub.status.busy": "2025-05-15T21:27:20.796532Z", + "iopub.status.idle": "2025-05-15T21:27:20.801742Z", + "shell.execute_reply": "2025-05-15T21:27:20.801247Z" }, "vscode": { "languageId": "plaintext" @@ -352,10 +352,10 @@ "id": "ea6ed2dd", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:26.901128Z", - "iopub.status.busy": "2025-05-13T11:30:26.900781Z", - "iopub.status.idle": "2025-05-13T11:30:27.319418Z", - "shell.execute_reply": "2025-05-13T11:30:27.318973Z" + "iopub.execute_input": "2025-05-15T21:27:20.803824Z", + "iopub.status.busy": "2025-05-15T21:27:20.803322Z", + "iopub.status.idle": "2025-05-15T21:27:21.237795Z", + "shell.execute_reply": "2025-05-15T21:27:21.237411Z" }, "vscode": { "languageId": "plaintext" @@ -461,10 +461,10 @@ "id": "b00a9d85", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:27.322333Z", - "iopub.status.busy": "2025-05-13T11:30:27.322209Z", - "iopub.status.idle": "2025-05-13T11:30:27.855893Z", - "shell.execute_reply": "2025-05-13T11:30:27.855442Z" + "iopub.execute_input": "2025-05-15T21:27:21.241334Z", + "iopub.status.busy": "2025-05-15T21:27:21.241206Z", + "iopub.status.idle": "2025-05-15T21:27:21.753263Z", + "shell.execute_reply": "2025-05-15T21:27:21.752812Z" }, "vscode": { "languageId": "plaintext" @@ -625,10 +625,10 @@ "id": "9931ba22", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:27.859332Z", - "iopub.status.busy": "2025-05-13T11:30:27.858992Z", - "iopub.status.idle": "2025-05-13T11:30:28.082316Z", - "shell.execute_reply": "2025-05-13T11:30:28.081718Z" + "iopub.execute_input": "2025-05-15T21:27:21.756417Z", + "iopub.status.busy": "2025-05-15T21:27:21.756309Z", + "iopub.status.idle": "2025-05-15T21:27:22.330925Z", + "shell.execute_reply": "2025-05-15T21:27:22.330337Z" }, "vscode": { "languageId": "plaintext" @@ -663,10 +663,10 @@ "id": "537cfdd2", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:28.084053Z", - "iopub.status.busy": "2025-05-13T11:30:28.083694Z", - "iopub.status.idle": "2025-05-13T11:30:28.472777Z", - "shell.execute_reply": "2025-05-13T11:30:28.472296Z" + "iopub.execute_input": "2025-05-15T21:27:22.332649Z", + "iopub.status.busy": "2025-05-15T21:27:22.332540Z", + "iopub.status.idle": "2025-05-15T21:27:22.715198Z", + "shell.execute_reply": "2025-05-15T21:27:22.714738Z" }, "vscode": { "languageId": "plaintext" @@ -679,7 +679,7 @@ "\n", "
\n", "
\n", - " \n", + " \n", "
\n", " \n", "
\n", @@ -742,10 +742,10 @@ "id": "417a2589", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:28.475480Z", - "iopub.status.busy": "2025-05-13T11:30:28.475278Z", - "iopub.status.idle": "2025-05-13T11:30:28.500487Z", - "shell.execute_reply": "2025-05-13T11:30:28.499892Z" + "iopub.execute_input": "2025-05-15T21:27:22.718182Z", + "iopub.status.busy": "2025-05-15T21:27:22.718021Z", + "iopub.status.idle": "2025-05-15T21:27:22.742904Z", + "shell.execute_reply": "2025-05-15T21:27:22.742418Z" }, "vscode": { "languageId": "plaintext" @@ -773,10 +773,10 @@ "id": "b8af663f", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:28.501942Z", - "iopub.status.busy": "2025-05-13T11:30:28.501831Z", - "iopub.status.idle": "2025-05-13T11:30:29.434996Z", - "shell.execute_reply": "2025-05-13T11:30:29.434505Z" + "iopub.execute_input": "2025-05-15T21:27:22.744404Z", + "iopub.status.busy": "2025-05-15T21:27:22.744295Z", + "iopub.status.idle": "2025-05-15T21:27:23.611474Z", + "shell.execute_reply": "2025-05-15T21:27:23.611046Z" }, "vscode": { "languageId": "plaintext" @@ -847,10 +847,10 @@ "id": "1e2cbf14", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:29.442424Z", - "iopub.status.busy": "2025-05-13T11:30:29.442310Z", - "iopub.status.idle": "2025-05-13T11:30:29.444747Z", - "shell.execute_reply": "2025-05-13T11:30:29.444365Z" + "iopub.execute_input": "2025-05-15T21:27:23.618664Z", + "iopub.status.busy": "2025-05-15T21:27:23.618350Z", + "iopub.status.idle": "2025-05-15T21:27:23.620849Z", + "shell.execute_reply": "2025-05-15T21:27:23.620356Z" }, "vscode": { "languageId": "plaintext" @@ -876,10 +876,10 @@ "id": "dab0513a", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:29.446255Z", - "iopub.status.busy": "2025-05-13T11:30:29.446056Z", - "iopub.status.idle": "2025-05-13T11:30:30.235444Z", - "shell.execute_reply": "2025-05-13T11:30:30.234995Z" + "iopub.execute_input": "2025-05-15T21:27:23.622549Z", + "iopub.status.busy": "2025-05-15T21:27:23.622358Z", + "iopub.status.idle": "2025-05-15T21:27:24.408958Z", + "shell.execute_reply": "2025-05-15T21:27:24.408486Z" }, "vscode": { "languageId": "plaintext" @@ -948,7 +948,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.10" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/examples/cthead.ipynb b/examples/cthead.ipynb index 34b6461..abc3bf6 100644 --- a/examples/cthead.ipynb +++ b/examples/cthead.ipynb @@ -16,10 +16,10 @@ "id": "94b5ef80", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:31.941828Z", - "iopub.status.busy": "2025-05-13T11:30:31.941728Z", - "iopub.status.idle": "2025-05-13T11:30:32.355719Z", - "shell.execute_reply": "2025-05-13T11:30:32.355253Z" + "iopub.execute_input": "2025-05-15T21:27:26.313901Z", + "iopub.status.busy": "2025-05-15T21:27:26.313708Z", + "iopub.status.idle": "2025-05-15T21:27:26.574048Z", + "shell.execute_reply": "2025-05-15T21:27:26.573730Z" }, "vscode": { "languageId": "plaintext" @@ -44,10 +44,10 @@ "id": "1ee080d3", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:32.357299Z", - "iopub.status.busy": "2025-05-13T11:30:32.357117Z", - "iopub.status.idle": "2025-05-13T11:30:32.484031Z", - "shell.execute_reply": "2025-05-13T11:30:32.483644Z" + "iopub.execute_input": "2025-05-15T21:27:26.575502Z", + "iopub.status.busy": "2025-05-15T21:27:26.575328Z", + "iopub.status.idle": "2025-05-15T21:27:26.699765Z", + "shell.execute_reply": "2025-05-15T21:27:26.699392Z" }, "vscode": { "languageId": "plaintext" @@ -88,10 +88,10 @@ "id": "7af5a922", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:32.485668Z", - "iopub.status.busy": "2025-05-13T11:30:32.485559Z", - "iopub.status.idle": "2025-05-13T11:30:32.940363Z", - "shell.execute_reply": "2025-05-13T11:30:32.940013Z" + "iopub.execute_input": "2025-05-15T21:27:26.701258Z", + "iopub.status.busy": "2025-05-15T21:27:26.701154Z", + "iopub.status.idle": "2025-05-15T21:27:27.153944Z", + "shell.execute_reply": "2025-05-15T21:27:27.153581Z" }, "vscode": { "languageId": "plaintext" @@ -104,7 +104,7 @@ "\n", "
\n", "
\n", - " \n", + " \n", "
\n", " \n", "
\n", @@ -206,10 +206,10 @@ "id": "493f1153", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:32.942033Z", - "iopub.status.busy": "2025-05-13T11:30:32.941917Z", - "iopub.status.idle": "2025-05-13T11:30:33.347677Z", - "shell.execute_reply": "2025-05-13T11:30:33.347213Z" + "iopub.execute_input": "2025-05-15T21:27:27.155486Z", + "iopub.status.busy": "2025-05-15T21:27:27.155382Z", + "iopub.status.idle": "2025-05-15T21:27:27.564193Z", + "shell.execute_reply": "2025-05-15T21:27:27.563790Z" }, "vscode": { "languageId": "plaintext" @@ -318,10 +318,10 @@ "id": "696c040a", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:33.350375Z", - "iopub.status.busy": "2025-05-13T11:30:33.350168Z", - "iopub.status.idle": "2025-05-13T11:30:33.441500Z", - "shell.execute_reply": "2025-05-13T11:30:33.440988Z" + "iopub.execute_input": "2025-05-15T21:27:27.567051Z", + "iopub.status.busy": "2025-05-15T21:27:27.566946Z", + "iopub.status.idle": "2025-05-15T21:27:27.659501Z", + "shell.execute_reply": "2025-05-15T21:27:27.659011Z" }, "vscode": { "languageId": "plaintext" @@ -346,10 +346,10 @@ "id": "fa026447", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:33.443055Z", - "iopub.status.busy": "2025-05-13T11:30:33.442941Z", - "iopub.status.idle": "2025-05-13T11:30:34.253172Z", - "shell.execute_reply": "2025-05-13T11:30:34.252573Z" + "iopub.execute_input": "2025-05-15T21:27:27.660963Z", + "iopub.status.busy": "2025-05-15T21:27:27.660860Z", + "iopub.status.idle": "2025-05-15T21:27:28.447397Z", + "shell.execute_reply": "2025-05-15T21:27:28.446812Z" }, "vscode": { "languageId": "plaintext" @@ -394,10 +394,10 @@ "id": "6e7f236c", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:34.254808Z", - "iopub.status.busy": "2025-05-13T11:30:34.254696Z", - "iopub.status.idle": "2025-05-13T11:30:35.014910Z", - "shell.execute_reply": "2025-05-13T11:30:35.014452Z" + "iopub.execute_input": "2025-05-15T21:27:28.449085Z", + "iopub.status.busy": "2025-05-15T21:27:28.448976Z", + "iopub.status.idle": "2025-05-15T21:27:29.204135Z", + "shell.execute_reply": "2025-05-15T21:27:29.203694Z" }, "vscode": { "languageId": "plaintext" @@ -505,10 +505,10 @@ "id": "41f16aea", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:35.016862Z", - "iopub.status.busy": "2025-05-13T11:30:35.016752Z", - "iopub.status.idle": "2025-05-13T11:30:35.522063Z", - "shell.execute_reply": "2025-05-13T11:30:35.521595Z" + "iopub.execute_input": "2025-05-15T21:27:29.205733Z", + "iopub.status.busy": "2025-05-15T21:27:29.205624Z", + "iopub.status.idle": "2025-05-15T21:27:29.697097Z", + "shell.execute_reply": "2025-05-15T21:27:29.696630Z" }, "vscode": { "languageId": "plaintext" @@ -567,7 +567,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.10" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 5b82088..6eb9bc6 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -14,10 +14,10 @@ "id": "3bf7058d", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.159368Z", - "iopub.status.busy": "2025-05-13T11:30:37.159210Z", - "iopub.status.idle": "2025-05-13T11:30:37.733839Z", - "shell.execute_reply": "2025-05-13T11:30:37.733418Z" + "iopub.execute_input": "2025-05-15T21:27:31.292087Z", + "iopub.status.busy": "2025-05-15T21:27:31.291928Z", + "iopub.status.idle": "2025-05-15T21:27:31.692969Z", + "shell.execute_reply": "2025-05-15T21:27:31.692413Z" }, "vscode": { "languageId": "plaintext" @@ -52,10 +52,10 @@ "id": "cd38b675", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.735613Z", - "iopub.status.busy": "2025-05-13T11:30:37.735453Z", - "iopub.status.idle": "2025-05-13T11:30:37.737396Z", - "shell.execute_reply": "2025-05-13T11:30:37.737148Z" + "iopub.execute_input": "2025-05-15T21:27:31.694352Z", + "iopub.status.busy": "2025-05-15T21:27:31.694173Z", + "iopub.status.idle": "2025-05-15T21:27:31.696537Z", + "shell.execute_reply": "2025-05-15T21:27:31.696148Z" }, "vscode": { "languageId": "plaintext" @@ -84,10 +84,10 @@ "id": "8518d1b2", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.738879Z", - "iopub.status.busy": "2025-05-13T11:30:37.738781Z", - "iopub.status.idle": "2025-05-13T11:30:37.740641Z", - "shell.execute_reply": "2025-05-13T11:30:37.740403Z" + "iopub.execute_input": "2025-05-15T21:27:31.697706Z", + "iopub.status.busy": "2025-05-15T21:27:31.697602Z", + "iopub.status.idle": "2025-05-15T21:27:31.699966Z", + "shell.execute_reply": "2025-05-15T21:27:31.699586Z" }, "vscode": { "languageId": "plaintext" @@ -120,10 +120,10 @@ "id": "df9403bf", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.742124Z", - "iopub.status.busy": "2025-05-13T11:30:37.742021Z", - "iopub.status.idle": "2025-05-13T11:30:37.746048Z", - "shell.execute_reply": "2025-05-13T11:30:37.745798Z" + "iopub.execute_input": "2025-05-15T21:27:31.701092Z", + "iopub.status.busy": "2025-05-15T21:27:31.700980Z", + "iopub.status.idle": "2025-05-15T21:27:31.705588Z", + "shell.execute_reply": "2025-05-15T21:27:31.705204Z" }, "vscode": { "languageId": "plaintext" @@ -133,7 +133,7 @@ { "data": { "text/plain": [ - ".Geometry at 0x7c02b5141eb0>" + ".Geometry at 0x79040f1b1df0>" ] }, "execution_count": 4, @@ -182,10 +182,10 @@ "id": "4a1a1c31", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.747577Z", - "iopub.status.busy": "2025-05-13T11:30:37.747480Z", - "iopub.status.idle": "2025-05-13T11:30:37.749306Z", - "shell.execute_reply": "2025-05-13T11:30:37.749077Z" + "iopub.execute_input": "2025-05-15T21:27:31.706696Z", + "iopub.status.busy": "2025-05-15T21:27:31.706593Z", + "iopub.status.idle": "2025-05-15T21:27:31.708834Z", + "shell.execute_reply": "2025-05-15T21:27:31.708452Z" }, "vscode": { "languageId": "plaintext" @@ -221,10 +221,10 @@ "id": "0ca491a8", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.750878Z", - "iopub.status.busy": "2025-05-13T11:30:37.750675Z", - "iopub.status.idle": "2025-05-13T11:30:37.833847Z", - "shell.execute_reply": "2025-05-13T11:30:37.833433Z" + "iopub.execute_input": "2025-05-15T21:27:31.709922Z", + "iopub.status.busy": "2025-05-15T21:27:31.709820Z", + "iopub.status.idle": "2025-05-15T21:27:31.782437Z", + "shell.execute_reply": "2025-05-15T21:27:31.782093Z" }, "vscode": { "languageId": "plaintext" @@ -253,10 +253,10 @@ "id": "37ff9c22", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.835247Z", - "iopub.status.busy": "2025-05-13T11:30:37.835139Z", - "iopub.status.idle": "2025-05-13T11:30:37.931324Z", - "shell.execute_reply": "2025-05-13T11:30:37.930910Z" + "iopub.execute_input": "2025-05-15T21:27:31.783596Z", + "iopub.status.busy": "2025-05-15T21:27:31.783489Z", + "iopub.status.idle": "2025-05-15T21:27:31.876948Z", + "shell.execute_reply": "2025-05-15T21:27:31.876528Z" }, "vscode": { "languageId": "plaintext" @@ -266,7 +266,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -307,10 +307,10 @@ "id": "10ab7a0a", "metadata": { "execution": { - "iopub.execute_input": "2025-05-13T11:30:37.932674Z", - "iopub.status.busy": "2025-05-13T11:30:37.932571Z", - "iopub.status.idle": "2025-05-13T11:30:38.079719Z", - "shell.execute_reply": "2025-05-13T11:30:38.079276Z" + "iopub.execute_input": "2025-05-15T21:27:31.878180Z", + "iopub.status.busy": "2025-05-15T21:27:31.878061Z", + "iopub.status.idle": "2025-05-15T21:27:32.032392Z", + "shell.execute_reply": "2025-05-15T21:27:32.031942Z" }, "vscode": { "languageId": "plaintext" @@ -373,7 +373,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.10" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/misc/libcarna/_imshow.py b/misc/libcarna/_imshow.py index 8601ab3..84ef9d0 100644 --- a/misc/libcarna/_imshow.py +++ b/misc/libcarna/_imshow.py @@ -8,7 +8,7 @@ import numpngw import numpy as np -import skvideo.io +from .libs.skvideo import io as skvideo_io try: from IPython.core.display import HTML as IPythonHTML @@ -47,7 +47,7 @@ def _render_html_h264(array: np.ndarray | Iterable[np.ndarray], fps: float = 25) # Encode video with tempfile.NamedTemporaryFile(suffix='.mp4') as mp4_file: - with skvideo.io.FFmpegWriter( + with skvideo_io.FFmpegWriter( mp4_file.name, outputdict={ '-vcodec': 'h264', diff --git a/misc/libcarna/libs/__init__.py b/misc/libcarna/libs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/misc/libcarna/libs/skvideo/__init__.py b/misc/libcarna/libs/skvideo/__init__.py new file mode 100644 index 0000000..b678efb --- /dev/null +++ b/misc/libcarna/libs/skvideo/__init__.py @@ -0,0 +1,200 @@ +__version__ = "1.1.11" + +from .utils import check_output, where +import os +import warnings + +# Run a program-based check to see if all install +# requirements have been met. +# Sets environment variables based on programs +# found. + +def which(command): + candidates = where(command) + if len(candidates) > 0: + return os.path.split(candidates[0])[0] + else: + return "" + + +# only ffprobe exists with ffmpeg +_FFMPEG_PATH = which("ffprobe") + +_MEDIAINFO_PATH = which("mediainfo") + +_HAS_FFMPEG = 0 +_HAS_MEDIAINFO = 0 + +_FFMPEG_MAJOR_VERSION = "0" +_FFMPEG_MINOR_VERSION = "0" +_FFMPEG_PATCH_VERSION = "0" + +_FFMPEG_SUPPORTED_DECODERS = [] +_FFMPEG_SUPPORTED_ENCODERS = [] + +_FFPROBE_APPLICATION = "ffprobe" +_FFMPEG_APPLICATION = "ffmpeg" +_MEDIAINFO_APPLICATION = "mediainfo" + +# Windows compat +if os.name == "nt": + _FFPROBE_APPLICATION += ".exe" + _FFMPEG_APPLICATION += ".exe" + _MEDIAINFO_APPLICATION += ".exe" + +def scan_ffmpeg(): + global _FFMPEG_MAJOR_VERSION + global _FFMPEG_MINOR_VERSION + global _FFMPEG_PATCH_VERSION + global _FFMPEG_SUPPORTED_DECODERS + global _FFMPEG_SUPPORTED_ENCODERS + _FFMPEG_MAJOR_VERSION = "0" + _FFMPEG_MINOR_VERSION = "0" + _FFMPEG_PATCH_VERSION = "0" + _FFMPEG_SUPPORTED_DECODERS = [] + _FFMPEG_SUPPORTED_ENCODERS = [] + try: + # grab program version string + version = check_output([os.path.join(_FFMPEG_PATH, _FFMPEG_APPLICATION), "-version"]) + # only parse the first line returned + firstline = version.split(b'\n')[0] + + # the 3rd element in this line is the version number + version = firstline.split(b' ')[2].strip() + versionparts = version.split(b'.') + if version[0] == b'N': + # this is the 'git' version of FFmpeg + _FFMPEG_MAJOR_VERSION = version + else: + _FFMPEG_MAJOR_VERSION = versionparts[0] + _FFMPEG_MINOR_VERSION = versionparts[1] + if len(versionparts) > 2: + _FFMPEG_PATCH_VERSION = versionparts[2] + except: + pass + + # by running the above code block, the bottom arrays are populated + # output staticly provided for speed concerns + _FFMPEG_SUPPORTED_DECODERS = [ + b'.264', b'.265', b'.302', b'.3g2', b'.3gp', b'.722', b'.aa', b'.aa3', b'.aac', b'.ac3', + b'.acm', b'.adf', b'.adp', b'.ads', b'.adx', b'.aea', b'.afc', b'.aif', b'.aifc', b'.aiff', + b'.al', b'.amr', b'.ans', b'.ape', b'.apl', b'.apng', b'.aqt', b'.art', b'.asc', b'.asf', + b'.ass', b'.ast', b'.au', b'.avc', b'.avi', b'.avr', b'.bcstm', b'.bfstm', b'.bin', b'.bit', + b'.bmp', b'.bmv', b'.brstm', b'.caf', b'.cavs', b'.cdata', b'.cdg', b'.cdxl', b'.cgi', + b'.cif', b'.daud', b'.dav', b'.dif', b'.diz', b'.dnxhd', b'.dpx', b'.drc', b'.dss', b'.dtk', b'.dts', + b'.dtshd', b'.dv', b'.eac3', b'.fap', b'.ffm', b'.ffmeta', b'.flac', b'.flm', b'.flv', + b'.fsb', b'.g722', b'.g723_1', b'.g729', b'.genh', b'.gif', b'.gsm', b'.gxf', b'.h261', + b'.h263', b'.h264', b'.h265', b'.h26l', b'.hevc', b'.ice', b'.ico', b'.idf', b'.idx', b'.im1', + b'.im24', b'.im8', b'.ircam', b'.ivf', b'.ivr', b'.j2c', b'.j2k', b'.jls', b'.jp2', b'.jpeg', + b'.jpg', b'.js', b'.jss', b'.lbc', b'.ljpg', b'.lrc', b'.lvf', b'.m2a', b'.m2t', b'.m2ts', + b'.m3u8', b'.m4a', b'.m4v', b'.mac', b'.mj2', b'.mjpeg', b'.mjpg', b'.mk3d', b'.mka', b'.mks', + b'.mkv', b'.mlp', b'.mmf', b'.mov', b'.mp2', b'.mp3', b'.mp4', b'.mpa', b'.mpc', b'.mpeg', + b'.mpg', b'.mpl2', b'.mpo', b'.msf', b'.mts', b'.mvi', b'.mxf', b'.mxg', b'.nfo', b'.nist', + b'.nut', b'.ogg', b'.ogv', b'.oma', b'.omg', b'.paf', b'.pam', b'.pbm', b'.pcx', b'.pgm', + b'.pgmyuv', b'.pix', b'.pjs', b'.png', b'.ppm', b'.pvf', b'.qcif', b'.ra', b'.ras', b'.rco', + b'.rcv', b'.rgb', b'.rm', b'.roq', b'.rs', b'.rsd', b'.rso', b'.rt', b'.sami', b'.sb', b'.sbg', + b'.sdr2', b'.sf', b'.sgi', b'.shn', b'.sln', b'.smi', b'.son', b'.sox', b'.spdif', b'.sph', + b'.srt', b'.ss2', b'.ssa', b'.stl', b'.str', b'.sub', b'.sun', b'.sunras', b'.sup', b'.svag', + b'.sw', b'.swf', b'.tak', b'.tco', b'.tga', b'.thd', b'.tif', b'.tiff', b'.ts', b'.tta', + b'.txt', b'.ub', b'.ul', b'.uw', b'.v', b'.v210', b'.vag', b'.vb', b'.vc1', b'.viv', b'.voc', + b'.vpk', b'.vqe', b'.vqf', b'.vql', b'.vt', b'.vtt', b'.w64', b'.wav', b'.webm', b'.wma', + b'.wmv', b'.wtv', b'.wv', b'.xbm', b'.xface', b'.xl', b'.xml', b'.xvag', b'.xwd', b'.y', + b'.y4m', b'.yop', b'.yuv', b'.yuv10', + + # extra extensions that are known container formats + b'.raw', + b'.iso' + ] + + _FFMPEG_SUPPORTED_ENCODERS = [ + b'., A64', b'.264', b'.265', b'.302', b'.3g2', b'.3gp', b'.722', b'.a64', b'.aa3', b'.aac', + b'.ac3', b'.adts', b'.adx', b'.afc', b'.aif', b'.aifc', b'.aiff', b'.al', b'.amr', b'.apng', + b'.asf', b'.ass', b'.ast', b'.au', b'.avc', b'.avi', b'.bit', b'.bmp', b'.caf', b'.cavs', + b'.chk', b'.cif', b'.daud', b'.dav', b'.dif', b'.dnxhd', b'.dpx', b'.drc', b'.dts', b'.dv', b'.dvd', + b'.eac3', b'.f4v', b'.ffm', b'.ffmeta', b'.flac', b'.flm', b'.flv', b'.g722', b'.g723_1', + b'.gif', b'.gxf', b'.h261', b'.h263', b'.h264', b'.h265', b'.h26l', b'.hevc', b'.ico', + b'.im1', b'.im24', b'.im8', b'.ircam', b'.isma', b'.ismv', b'.ivf', b'.j2c', b'.j2k', b'.jls', + b'.jp2', b'.jpeg', b'.jpg', b'.js', b'.jss', b'.latm', b'.lbc', b'.ljpg', b'.loas', b'.lrc', + b'.m1v', b'.m2a', b'.m2t', b'.m2ts', b'.m2v', b'.m3u8', b'.m4a', b'.m4v', b'.mj2', b'.mjpeg', + b'.mjpg', b'.mk3d', b'.mka', b'.mks', b'.mkv', b'.mlp', b'.mmf', b'.mov', b'.mp2', b'.mp3', + b'.mp4', b'.mpa', b'.mpeg', b'.mpg', b'.mpo', b'.mts', b'.mxf', b'.nut', b'.oga', b'.ogg', + b'.ogv', b'.oma', b'.omg', b'.opus', b'.pam', b'.pbm', b'.pcx', b'.pgm', b'.pgmyuv', b'.pix', + b'.png', b'.ppm', b'.psp', b'.qcif', b'.ra', b'.ras', b'.rco', b'.rcv', b'.rgb', b'.rm', + b'.roq', b'.rs', b'.rso', b'.sb', b'.sf', b'.sgi', b'.sox', b'.spdif', b'.spx', b'.srt', + b'.ssa', b'.sub', b'.sun', b'.sunras', b'.sw', b'.swf', b'.tco', b'.tga', b'.thd', b'.tif', + b'.tiff', b'.ts', b'.ub', b'.ul', b'.uw', b'.vc1', b'.vob', b'.voc', b'.vtt', b'.w64', b'.wav', + b'.webm', b'.webp', b'.wma', b'.wmv', b'.wtv', b'.wv', b'.xbm', b'.xface', b'.xml', b'.xwd', + b'.y', b'.y4m', b'.yuv', + + # extra extensions that are known container formats + b'.raw' + ] + + +if _MEDIAINFO_PATH is not None: + _HAS_MEDIAINFO = 1 + + +# allow library configuration checking +def getFFmpegPath(): + """ Returns the path to the directory containing both ffmpeg and ffprobe + """ + return _FFMPEG_PATH + + +def getFFmpegVersion(): + """ Returns the version of FFmpeg that is currently being used + """ + if _FFMPEG_MAJOR_VERSION[0] == 'N': + return "%s" % (_FFMPEG_MAJOR_VERSION, ) + else: + return "%s.%s.%s" % (_FFMPEG_MAJOR_VERSION, _FFMPEG_MINOR_VERSION, _FFMPEG_PATCH_VERSION) + + +def setFFmpegPath(path): + """ Sets up the path to the directory containing both ffmpeg and ffprobe + + Use this function for to specify specific system installs of FFmpeg. All + calls to ffmpeg and ffprobe will use this path as a prefix. + + Parameters + ---------- + path : string + Path to directory containing ffmpeg and ffprobe + + Returns + ------- + none + + """ + global _FFMPEG_PATH + global _HAS_FFMPEG + _FFMPEG_PATH = path + + # check to see if the executables actually exist on these paths + if os.path.isfile(os.path.join(_FFMPEG_PATH, _FFMPEG_APPLICATION)) and os.path.isfile(os.path.join(_FFMPEG_PATH, _FFPROBE_APPLICATION)): + _HAS_FFMPEG = 1 + else: + warnings.warn("ffmpeg/ffprobe not found in path: " + str(path), UserWarning) + _HAS_FFMPEG = 0 + global _FFMPEG_MAJOR_VERSION + global _FFMPEG_MINOR_VERSION + global _FFMPEG_PATCH_VERSION + _FFMPEG_MAJOR_VERSION = "0" + _FFMPEG_MINOR_VERSION = "0" + _FFMPEG_PATCH_VERSION = "0" + return + + # reload version from new path + scan_ffmpeg() + + +if (len(_FFMPEG_PATH) > 0): + setFFmpegPath(_FFMPEG_PATH) + + +__all__ = [ + getFFmpegPath, + getFFmpegVersion, + setFFmpegPath, +] diff --git a/misc/libcarna/libs/skvideo/io/__init__.py b/misc/libcarna/libs/skvideo/io/__init__.py new file mode 100644 index 0000000..b38cc81 --- /dev/null +++ b/misc/libcarna/libs/skvideo/io/__init__.py @@ -0,0 +1,10 @@ +"""Utilities to read/write image/video data. + +""" + + +from .ffmpeg import * + +__all__ = [ + 'FFmpegWriter', +] diff --git a/misc/libcarna/libs/skvideo/io/abstract.py b/misc/libcarna/libs/skvideo/io/abstract.py new file mode 100644 index 0000000..d72e6d2 --- /dev/null +++ b/misc/libcarna/libs/skvideo/io/abstract.py @@ -0,0 +1,217 @@ +import os +import warnings + +import numpy as np + +from ..utils import * + + +class VideoWriterAbstract(object): + """Writes frames + + this class provides sane initializations for the default case. + """ + NEED_RGB2GRAY_HACK = False + DEFAULT_OUTPUT_PIX_FMT = "yuvj444p" + + def __init__(self, filename, inputdict=None, outputdict=None, verbosity=0): + """Prepares parameters + + Does not instantiate the an FFmpeg subprocess, but simply + prepares the required parameters. + + Parameters + ---------- + filename : string + Video file path for writing + + inputdict : dict + Input dictionary parameters, i.e. how to interpret the data coming from python. + + outputdict : dict + Output dictionary parameters, i.e. how to encode the data + when writing to file. + + Returns + ------- + none + + """ + self.DEVNULL = open(os.devnull, 'wb') + + filename = os.path.abspath(filename) + _, self.extension = os.path.splitext(filename) + + # check that the extension makes sense + encoders = self._getSupportedEncoders() + if encoders != NotImplemented: + assert str.encode( + self.extension).lower() in encoders, "Unknown encoder extension: " + self.extension.lower() + + self._filename = filename + basepath, _ = os.path.split(filename) + + # check to see if filename is a valid file location + assert os.access(basepath, os.W_OK), "Cannot write to directory: " + basepath + + if not inputdict: + inputdict = {} + + if not outputdict: + outputdict = {} + + self.inputdict = inputdict + self.outputdict = outputdict + self.verbosity = verbosity + + if "-f" not in self.inputdict: + self.inputdict["-f"] = "rawvideo" + self.warmStarted = False + + def _warmStart(self, M, N, C, dtype): + self.warmStarted = True + + if "-pix_fmt" not in self.inputdict: + # check the number channels to guess + if dtype.kind == 'u' and dtype.itemsize == 2: + suffix = 'le' if dtype.byteorder else 'be' + if C == 1: + if self.NEED_RGB2GRAY_HACK: + self.inputdict["-pix_fmt"] = "rgb48" + suffix + self.rgb2grayhack = True + C = 3 + else: + self.inputdict["-pix_fmt"] = "gray16" + suffix + elif C == 2: + self.inputdict["-pix_fmt"] = "ya16" + suffix + elif C == 3: + self.inputdict["-pix_fmt"] = "rgb48" + suffix + elif C == 4: + self.inputdict["-pix_fmt"] = "rgba64" + suffix + else: + raise NotImplemented + else: + if C == 1: + if self.NEED_RGB2GRAY_HACK: + self.inputdict["-pix_fmt"] = "rgb24" + self.rgb2grayhack = True + C = 3 + else: + self.inputdict["-pix_fmt"] = "gray" + elif C == 2: + self.inputdict["-pix_fmt"] = "ya8" + elif C == 3: + self.inputdict["-pix_fmt"] = "rgb24" + elif C == 4: + self.inputdict["-pix_fmt"] = "rgba" + else: + raise NotImplemented + + self.bpp = bpplut[self.inputdict["-pix_fmt"]][1] + self.inputNumChannels = bpplut[self.inputdict["-pix_fmt"]][0] + bitpercomponent = self.bpp // self.inputNumChannels + if bitpercomponent == 8: + self.dtype = np.dtype('u1') # np.uint8 + elif bitpercomponent == 16: + suffix = self.inputdict['-pix_fmt'][-2:] + if suffix == 'le': + self.dtype = np.dtype('u2') + else: + raise ValueError(self.inputdict['-pix_fmt'] + 'is not a valid pix_fmt for numpy conversion') + + assert self.inputNumChannels == C, "Failed to pass the correct number of channels %d for the pixel format %s." % ( + self.inputNumChannels, self.inputdict["-pix_fmt"]) + + if ("-s" in self.inputdict): + widthheight = self.inputdict["-s"].split('x') + self.inputwidth = int(widthheight[0]) + self.inputheight = int(widthheight[1]) + else: + self.inputdict["-s"] = str(N) + "x" + str(M) + self.inputwidth = N + self.inputheight = M + + # prepare output parameters, if raw + if self.extension == ".yuv": + if "-pix_fmt" not in self.outputdict: + self.outputdict["-pix_fmt"] = self.DEFAULT_OUTPUT_PIX_FMT + if self.verbosity > 0: + warnings.warn("No output color space provided. Assuming {}.".format(self.DEFAULT_OUTPUT_PIX_FMT), + UserWarning) + + self._createProcess(self.inputdict, self.outputdict, self.verbosity) + + def _createProcess(self, inputdict, outputdict, verbosity): + pass + + def _prepareData(self, data): + return data # general case : do nothing + + def close(self): + """Closes the video and terminates FFmpeg process + + """ + if self._proc is None: # pragma: no cover + return # no process + if self._proc.poll() is not None: + return # process already dead + if self._proc.stdin: + self._proc.stdin.close() + self._proc.wait() + self._proc = None + self.DEVNULL.close() + + def writeFrame(self, im): + """Sends ndarray frames to FFmpeg + + """ + vid = vshape(im) + T, M, N, C = vid.shape + if not self.warmStarted: + self._warmStart(M, N, C, im.dtype) + + vid = vid.clip(0, (1 << (self.dtype.itemsize << 3)) - 1).astype(self.dtype) + vid = self._prepareData(vid) + T, M, N, C = vid.shape # in case of hack ine prepareData to change the image shape (gray2RGB in libAV for exemple) + + # check if we need to do some bit-plane swapping + # for the raw data format + if self.inputdict["-pix_fmt"].startswith('yuv444p') or self.inputdict["-pix_fmt"].startswith('yuvj444p') or \ + self.inputdict["-pix_fmt"].startswith('yuva444p'): + vid = vid.transpose((0, 3, 1, 2)) + + # Check size of image + if M != self.inputheight or N != self.inputwidth: + raise ValueError('All images in a movie should have same size') + if C != self.inputNumChannels: + raise ValueError('All images in a movie should have same ' + 'number of channels') + + assert self._proc is not None # Check status + + # Write + try: + self._proc.stdin.write(vid.tostring()) + except IOError as e: + # Show the command and stderr from pipe + msg = '{0:}\n\nFFMPEG COMMAND:\n{1:}\n\nFFMPEG STDERR ' \ + 'OUTPUT:\n'.format(e, self._cmd) + raise IOError(msg) + + def _getSupportedEncoders(self): + return NotImplemented + + def _dict2Args(self, dict): + args = [] + for key in dict.keys(): + args.append(key) + args.append(dict[key]) + return args + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff --git a/misc/libcarna/libs/skvideo/io/ffmpeg.py b/misc/libcarna/libs/skvideo/io/ffmpeg.py new file mode 100755 index 0000000..ffd9bd1 --- /dev/null +++ b/misc/libcarna/libs/skvideo/io/ffmpeg.py @@ -0,0 +1,49 @@ +""" Plugin that uses ffmpeg to read and write series of images to +a wide range of video formats. + +""" + +# Heavily inspired from Almar Klein's imageio code +# Copyright (c) 2015, imageio contributors +# distributed under the terms of the BSD License (included in release). + +import subprocess as sp + +from .abstract import VideoWriterAbstract +from .. import _FFMPEG_APPLICATION +from .. import _FFMPEG_PATH +from .. import _FFMPEG_SUPPORTED_ENCODERS +from .. import _HAS_FFMPEG +from ..utils import * + +class FFmpegWriter(VideoWriterAbstract): + """Writes frames using FFmpeg + + Using FFmpeg as a backend, this class + provides sane initializations for the default case. + """ + + def __init__(self, *args, **kwargs): + assert _HAS_FFMPEG, "Cannot find installation of real FFmpeg (which comes with ffprobe)." + super(FFmpegWriter,self).__init__(*args, **kwargs) + + def _getSupportedEncoders(self): + return _FFMPEG_SUPPORTED_ENCODERS + + def _createProcess(self, inputdict, outputdict, verbosity): + iargs = self._dict2Args(inputdict) + oargs = self._dict2Args(outputdict) + + cmd = [_FFMPEG_PATH + "/" + _FFMPEG_APPLICATION, "-y"] + iargs + ["-i", "-"] + oargs + [self._filename] + + self._cmd = " ".join(cmd) + + # Launch process + if self.verbosity > 0: + print(self._cmd) + self._proc = sp.Popen(cmd, stdin=sp.PIPE, + stdout=sp.PIPE, stderr=None) + else: + self._proc = sp.Popen(cmd, stdin=sp.PIPE, + stdout=self.DEVNULL, stderr=sp.STDOUT) + diff --git a/misc/libcarna/libs/skvideo/utils/__init__.py b/misc/libcarna/libs/skvideo/utils/__init__.py new file mode 100644 index 0000000..30f7ec0 --- /dev/null +++ b/misc/libcarna/libs/skvideo/utils/__init__.py @@ -0,0 +1,293 @@ +""" Various utility functions for manipulating video data + +""" +import os +import platform +import itertools +import subprocess as sp + + +# dictionary based on pix_fmt keys +# first element is number of components +# second element is number of bits per pixel +bpplut = {} +bpplut["yuv420p"] = [3, 12] +bpplut["yuyv422"] = [3, 16] +bpplut["rgb24"] = [3, 24] +bpplut["bgr24"] = [3, 24] +bpplut["yuv422p"] = [3, 16] +bpplut["yuv444p"] = [3, 24] +bpplut["yuv410p"] = [3, 9] +bpplut["yuv411p"] = [3, 12] +bpplut["gray"] = [1, 8] +bpplut["monow"] = [1, 1] +bpplut["monob"] = [1, 1] +bpplut["pal8"] = [1, 8] +bpplut["yuvj420p"] = [3, 12] +bpplut["yuvj422p"] = [3, 16] +bpplut["yuvj444p"] = [3, 24] +bpplut["xvmcmc"] = [0, 0] +bpplut["xvmcidct"] = [0, 0] +bpplut["uyvy422"] = [3, 16] +bpplut["uyyvyy411"] = [3, 12] +bpplut["bgr8"] = [3, 8] +bpplut["bgr4"] = [3, 4] +bpplut["bgr4_byte"] = [3, 4] +bpplut["rgb8"] = [3, 8] +bpplut["rgb4"] = [3, 4] +bpplut["rgb4_byte"] = [3, 4] +bpplut["nv12"] = [3, 12] +bpplut["nv21"] = [3, 12] +bpplut["argb"] = [4, 32] +bpplut["rgba"] = [4, 32] +bpplut["abgr"] = [4, 32] +bpplut["bgra"] = [4, 32] +bpplut["gray16be"] = [1, 16] +bpplut["gray16le"] = [1, 16] +bpplut["yuv440p"] = [3, 16] +bpplut["yuvj440p"] = [3, 16] +bpplut["yuva420p"] = [4, 20] +bpplut["vdpau_h264"] = [0, 0] +bpplut["vdpau_mpeg1"] = [0, 0] +bpplut["vdpau_mpeg2"] = [0, 0] +bpplut["vdpau_wmv3"] = [0, 0] +bpplut["vdpau_vc1"] = [0, 0] +bpplut["rgb48be"] = [3, 48] +bpplut["rgb48le"] = [3, 48] +bpplut["rgb565be"] = [3, 16] +bpplut["rgb565le"] = [3, 16] +bpplut["rgb555be"] = [3, 15] +bpplut["rgb555le"] = [3, 15] +bpplut["bgr565be"] = [3, 16] +bpplut["bgr565le"] = [3, 16] +bpplut["bgr555be"] = [3, 15] +bpplut["bgr555le"] = [3, 15] +bpplut["vaapi_moco"] = [0, 0] +bpplut["vaapi_idct"] = [0, 0] +bpplut["vaapi_vld"] = [0, 0] +bpplut["yuv420p16le"] = [3, 24] +bpplut["yuv420p16be"] = [3, 24] +bpplut["yuv422p16le"] = [3, 32] +bpplut["yuv422p16be"] = [3, 32] +bpplut["yuv444p16le"] = [3, 48] +bpplut["yuv444p16be"] = [3, 48] +bpplut["vdpau_mpeg4"] = [0, 0] +bpplut["dxva2_vld"] = [0, 0] +bpplut["rgb444le"] = [3, 12] +bpplut["rgb444be"] = [3, 12] +bpplut["bgr444le"] = [3, 12] +bpplut["bgr444be"] = [3, 12] +bpplut["ya8"] = [2, 16] +bpplut["bgr48be"] = [3, 48] +bpplut["bgr48le"] = [3, 48] +bpplut["yuv420p9be"] = [3, 13] +bpplut["yuv420p9le"] = [3, 13] +bpplut["yuv420p10be"] = [3, 15] +bpplut["yuv420p10le"] = [3, 15] +bpplut["yuv422p10be"] = [3, 20] +bpplut["yuv422p10le"] = [3, 20] +bpplut["yuv444p9be"] = [3, 27] +bpplut["yuv444p9le"] = [3, 27] +bpplut["yuv444p10be"] = [3, 30] +bpplut["yuv444p10le"] = [3, 30] +bpplut["yuv422p9be"] = [3, 18] +bpplut["yuv422p9le"] = [3, 18] +bpplut["vda_vld"] = [0, 0] +bpplut["gbrp"] = [3, 24] +bpplut["gbrp9be"] = [3, 27] +bpplut["gbrp9le"] = [3, 27] +bpplut["gbrp10be"] = [3, 30] +bpplut["gbrp10le"] = [3, 30] +bpplut["gbrp16be"] = [3, 48] +bpplut["gbrp16le"] = [3, 48] +bpplut["yuva420p9be"] = [4, 22] +bpplut["yuva420p9le"] = [4, 22] +bpplut["yuva422p9be"] = [4, 27] +bpplut["yuva422p9le"] = [4, 27] +bpplut["yuva444p9be"] = [4, 36] +bpplut["yuva444p9le"] = [4, 36] +bpplut["yuva420p10be"] = [4, 25] +bpplut["yuva420p10le"] = [4, 25] +bpplut["yuva422p10be"] = [4, 30] +bpplut["yuva422p10le"] = [4, 30] +bpplut["yuva444p10be"] = [4, 40] +bpplut["yuva444p10le"] = [4, 40] +bpplut["yuva420p16be"] = [4, 40] +bpplut["yuva420p16le"] = [4, 40] +bpplut["yuva422p16be"] = [4, 48] +bpplut["yuva422p16le"] = [4, 48] +bpplut["yuva444p16be"] = [4, 64] +bpplut["yuva444p16le"] = [4, 64] +bpplut["vdpau"] = [0, 0] +bpplut["xyz12le"] = [3, 36] +bpplut["xyz12be"] = [3, 36] +bpplut["nv16"] = [3, 16] +bpplut["nv20le"] = [3, 20] +bpplut["nv20be"] = [3, 20] +bpplut["yvyu422"] = [3, 16] +bpplut["vda"] = [0, 0] +bpplut["ya16be"] = [2, 32] +bpplut["ya16le"] = [2, 32] +bpplut["qsv"] = [0, 0] +bpplut["mmal"] = [0, 0] +bpplut["d3d11va_vld"] = [0, 0] +bpplut["rgba64be"] = [4, 64] +bpplut["rgba64le"] = [4, 64] +bpplut["bgra64be"] = [4, 64] +bpplut["bgra64le"] = [4, 64] +bpplut["0rgb"] = [3, 24] +bpplut["rgb0"] = [3, 24] +bpplut["0bgr"] = [3, 24] +bpplut["bgr0"] = [3, 24] +bpplut["yuva444p"] = [4, 32] +bpplut["yuva422p"] = [4, 24] +bpplut["yuv420p12be"] = [3, 18] +bpplut["yuv420p12le"] = [3, 18] +bpplut["yuv420p14be"] = [3, 21] +bpplut["yuv420p14le"] = [3, 21] +bpplut["yuv422p12be"] = [3, 24] +bpplut["yuv422p12le"] = [3, 24] +bpplut["yuv422p14be"] = [3, 28] +bpplut["yuv422p14le"] = [3, 28] +bpplut["yuv444p12be"] = [3, 36] +bpplut["yuv444p12le"] = [3, 36] +bpplut["yuv444p14be"] = [3, 42] +bpplut["yuv444p14le"] = [3, 42] +bpplut["gbrp12be"] = [3, 36] +bpplut["gbrp12le"] = [3, 36] +bpplut["gbrp14be"] = [3, 42] +bpplut["gbrp14le"] = [3, 42] +bpplut["gbrap"] = [4, 32] +bpplut["gbrap16be"] = [4, 64] +bpplut["gbrap16le"] = [4, 64] +bpplut["yuvj411p"] = [3, 12] +bpplut["bayer_bggr8"] = [3, 8] +bpplut["bayer_rggb8"] = [3, 8] +bpplut["bayer_gbrg8"] = [3, 8] +bpplut["bayer_grbg8"] = [3, 8] +bpplut["bayer_bggr16le"] = [3, 16] +bpplut["bayer_bggr16be"] = [3, 16] +bpplut["bayer_rggb16le"] = [3, 16] +bpplut["bayer_rggb16be"] = [3, 16] +bpplut["bayer_gbrg16le"] = [3, 16] +bpplut["bayer_gbrg16be"] = [3, 16] +bpplut["bayer_grbg16le"] = [3, 16] +bpplut["bayer_grbg16be"] = [3, 16] +bpplut["yuv440p10le"] = [3, 20] +bpplut["yuv440p10be"] = [3, 20] +bpplut["yuv440p12le"] = [3, 24] +bpplut["yuv440p12be"] = [3, 24] +bpplut["ayuv64le"] = [4, 64] +bpplut["ayuv64be"] = [4, 64] +bpplut["videotoolbox_vld"] = [0, 0] + +# patch for python 2.6 +def check_output(*popenargs, **kwargs): + closeNULL = 0 + try: + from subprocess import DEVNULL + closeNULL = 0 + except ImportError: + import os + DEVNULL = open(os.devnull, 'wb') + closeNULL = 1 + + process = sp.Popen(stdout=sp.PIPE, stderr=DEVNULL, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + + if closeNULL: + DEVNULL.close() + + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + error = sp.CalledProcessError(retcode, cmd) + error.output = output + raise error + return output + +def where( filename ): + """ Returns all matching file paths. """ + return list(iwhere(filename)) + +def iwhere( filename ): + possible_paths = _gen_possible_matches(filename) + existing_file_paths = filter(os.path.isfile, possible_paths) + return existing_file_paths + +def iter_unique(iterable): + yielded = set() + for i in iterable: + if i in yielded: + continue + yield i + yielded.add(i) + +def imapchain(*a, **kwa): + """ Like map but also chains the results. """ + + imap_results = map(*a, **kwa) + return itertools.chain(*imap_results) + +def _gen_possible_matches(filename): + path_parts = os.environ.get("PATH", "").split(os.pathsep) + path_parts = itertools.chain((os.curdir,), path_parts) + possible_paths = map(lambda path_part: os.path.join(path_part, filename), path_parts) + + if platform.system() == "Windows": + possible_paths = imapchain(lambda path: (path, path+".bat", path+".com", path+".exe"), possible_paths) + + possible_paths = map(os.path.abspath, possible_paths) + + result = iter_unique(possible_paths) + + return result + +def vshape(videodata): + """Standardizes the input data shape. + + Transforms video data into the standardized shape (T, M, N, C), where + T is number of frames, M is height, N is width, and C is number of + channels. + + Parameters + ---------- + videodata : ndarray + Input data of shape (T, M, N, C), (T, M, N), (M, N, C), or (M, N), where + T is number of frames, M is height, N is width, and C is number of + channels. + + Returns + ------- + videodataout : ndarray + Standardized version of videodata, shape (T, M, N, C) + + """ + import numpy as np + if not isinstance(videodata, np.ndarray): + videodata = np.array(videodata) + + if len(videodata.shape) == 2: + a, b = videodata.shape + return videodata.reshape(1, a, b, 1) + elif len(videodata.shape) == 3: + a, b, c = videodata.shape + # check the last dimension small + # interpret as color channel + if c in [1, 2, 3, 4]: + return videodata.reshape(1, a, b, c) + else: + return videodata.reshape(a, b, c, 1) + elif len(videodata.shape) == 4: + return videodata + else: + raise ValueError("Improper data input") + +__all__ = [ + 'bpplut', + 'where', + 'check_output', + 'vshape', +] diff --git a/setup.py.in b/setup.py.in index fd8d5d3..5a33bdc 100644 --- a/setup.py.in +++ b/setup.py.in @@ -22,11 +22,22 @@ if __name__ == '__main__': 'LICENSE-LibCarna', 'LICENSE-Eigen', 'LICENSE-GLEW', + 'LICENSE-skvideo', ], package_dir = { 'libcarna': 'libcarna', + 'libcarna.libs': 'libcarna/libs', + 'libcarna.libs.skvideo': 'libcarna/libs/skvideo', + 'libcarna.libs.skvideo.io': 'libcarna/libs/skvideo/io', + 'libcarna.libs.skvideo.utils': 'libcarna/libs/skvideo/utils', }, - packages = ['libcarna'], + packages = [ + 'libcarna', + 'libcarna.libs', + 'libcarna.libs.skvideo', + 'libcarna.libs.skvideo.io', + 'libcarna.libs.skvideo.utils', + ], package_data = { 'libcarna': ['*.so'], }, @@ -44,7 +55,6 @@ if __name__ == '__main__': install_requires = [ 'numpy', 'numpngw >=0.1.4, <0.2', - 'scikit-video >=1.1.11, <1.2', 'scipy', 'scikit-image', 'tifffile',