diff --git a/.github/workflows/javaTests.yml b/.github/workflows/javaTests.yml index c31b41df3a3..19da09ee687 100644 --- a/.github/workflows/javaTests.yml +++ b/.github/workflows/javaTests.yml @@ -69,6 +69,7 @@ jobs: "**.functions.builtin.part2.**", "**.functions.frame.**,**.functions.indexing.**,**.functions.io.**,**.functions.iogen.**", "**.functions.dnn.**", + "**.functions.nativ.**", "**.functions.paramserv.**", "**.functions.recompile.**,**.functions.misc.**,**.functions.mlcontext.**", "**.functions.nary.**,**.functions.quaternary.**", @@ -81,39 +82,39 @@ jobs: ] name: ${{ matrix.tests }} steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - - name: ${{ matrix.tests }} - uses: ./.github/action/ - id: test - with: - test-to-run: ${{ matrix.tests }} - - - name: Clean Github Artifact Name of Asterisks - run: | - ARTIFACT_NAME="transient_jacoco" - ARTIFACT_NAME+="-${{ matrix.os }}" - ARTIFACT_NAME+="-java-${{ matrix.java }}" - ARTIFACT_NAME+="-${{ matrix.javadist }}" - ARTIFACT_NAME+="-${{ matrix.tests }}" - ARTIFACT_NAME=${ARTIFACT_NAME//\*/x} # replace * with x - echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV - - - name: Save Java Test Coverage as Artifact - uses: actions/upload-artifact@v3 - with: - name: ${{ env.ARTIFACT_NAME }} - path: target/jacoco.exec - retention-days: 1 + - name: Checkout Repository + uses: actions/checkout@v4 + + + - name: ${{ matrix.tests }} + uses: ./.github/action/ + id: test + with: + test-to-run: ${{ matrix.tests }} + + - name: Clean Github Artifact Name of Asterisks + run: | + ARTIFACT_NAME="transient_jacoco" + ARTIFACT_NAME+="-${{ matrix.os }}" + ARTIFACT_NAME+="-java-${{ matrix.java }}" + ARTIFACT_NAME+="-${{ matrix.javadist }}" + ARTIFACT_NAME+="-${{ matrix.tests }}" + ARTIFACT_NAME=${ARTIFACT_NAME//\*/x} # replace * with x + echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV + + - name: Save Java Test Coverage as Artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: target/jacoco.exec + retention-days: 1 determine_test_coverage: name: Determine Test Coverage runs-on: ubuntu-latest needs: [ java_tests - ] + ] steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt index b3de917bf54..97672cfb11b 100644 --- a/src/main/cpp/CMakeLists.txt +++ b/src/main/cpp/CMakeLists.txt @@ -31,8 +31,8 @@ option(USE_OPEN_BLAS "Whether to use OpenBLAS (Defaults to compiling with Intel option(USE_INTEL_MKL "Whether to use Intel MKL (Defaults to compiling with Intel MKL)" OFF) # Build a shared libraray -set(HEADER_FILES libmatrixdnn.h libmatrixmult.h systemds.h common.h) -set(SOURCE_FILES libmatrixdnn.cpp libmatrixmult.cpp systemds.cpp) +set(HEADER_FILES imgUtils.h libmatrixdnn.h libmatrixmult.h systemds.h common.h) +set(SOURCE_FILES imgUtils.cpp libmatrixdnn.cpp libmatrixmult.cpp systemds_img.cpp systemds.cpp) # Build a shared libraray add_library(systemds SHARED ${SOURCE_FILES} ${HEADER_FILES}) @@ -95,5 +95,4 @@ if (USE_OPEN_BLAS) set_target_properties(systemds PROPERTIES LINK_FLAGS "${OpenMP_CXX_FLAGS} ${MATH_LIBRARIES}") elseif(USE_INTEL_MKL) set_target_properties(systemds PROPERTIES LINK_FLAGS "${MATH_LIBRARIES}") -endif() - +endif() \ No newline at end of file diff --git a/src/main/cpp/build.sh b/src/main/cpp/build.sh index e40ec895a3c..54869b14fbc 100755 --- a/src/main/cpp/build.sh +++ b/src/main/cpp/build.sh @@ -35,6 +35,7 @@ intel_mkl="libmkl_rt.so" # GCC __float128 shared support library: libquadmath.so.0 openblas="libopenblas.so\|libgfortran.so\|libquadmath.so" +export DEBIAN_FRONTEND=noninteractive if ! [ -x "$(command -v cmake)" ]; then echo 'Error: cmake is not installed.' >&2 @@ -46,6 +47,14 @@ if ! [ -x "$(command -v patchelf)" ]; then exit 1 fi +# Check if OpenBLAS is installed +if ! ldconfig -p | grep -q libopenblas; then + echo "OpenBLAS not found. Installing OpenBLAS..." + + apt-get update + apt-get install libopenblas-dev -y +fi + # configure and compile INTEL MKL cmake . -B INTEL -DUSE_INTEL_MKL=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS="-DUSE_GNU_THREADING -m64" cmake --build INTEL --target install --config Release diff --git a/src/main/cpp/imgUtils.cpp b/src/main/cpp/imgUtils.cpp new file mode 100644 index 00000000000..23f2d71adba --- /dev/null +++ b/src/main/cpp/imgUtils.cpp @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#include +#include +#include +#include "common.h" + +using namespace std; + +void printImage(const double* image, int rows, int cols) { + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + cout << image[i * cols + j] << " "; + } + cout << endl; + } + cout << "\n"<< endl; +} + +void img_transform(const double* img_in, int orig_w, int orig_h, int out_w, int out_h, double a, double b, double c, double d, + double e, double f, double fill_value, double* img_out) { + double divisor = a * e - b * d; + if (divisor == 0) { + std::cout << "Inverse matrix does not exist! Returning input." << std::endl; + for (int i = 0; i < orig_h; i++) { + for (int j = 0; j < orig_w; j++) { + img_out[i * orig_w + j] = img_in[i * orig_w + j]; + } + } + } else { + // Create the inverted transformation matrix + double T_inv[9]; + T_inv[0] = e / divisor; + T_inv[1] = -b / divisor; + T_inv[2] = (b * f - c * e) / divisor; + T_inv[3] = -d / divisor; + T_inv[4] = a / divisor; + T_inv[5] = (c * d - a * f) / divisor; + T_inv[6] = 0.0; + T_inv[7] = 0.0; + T_inv[8] = 1.0; + + // Create the coordinates of output pixel centers linearized in row-major order + double* coords = new double[2 * out_w * out_h]; + for (int i = 0; i < out_h; i++) { + for (int j = 0; j < out_w; j++) { + coords[2 * (i * out_w + j)] = j + 0.5; + coords[2 * (i * out_w + j) + 1] = i + 0.5; + } + } + + // Perform matrix multiplication to compute sampling pixel indices + double* transformed_coords = new double[2 * out_w * out_h]; + for (int i = 0; i < out_w * out_h; i++) { + double x = coords[2 * i]; + double y = coords[2 * i + 1]; + transformed_coords[2 * i] = std::floor(T_inv[0] * x + T_inv[1] * y + T_inv[2]) + 1; + transformed_coords[2 * i + 1] = std::floor(T_inv[3] * x + T_inv[4] * y + T_inv[5]) + 1; + } + + // Fill output image + for (int i = 0; i < out_h; i++) { + for (int j = 0; j < out_w; j++) { + int inx = static_cast(transformed_coords[2 * (i * out_w + j)]) - 1; + int iny = static_cast(transformed_coords[2 * (i * out_w + j) + 1]) - 1; + if (inx >= 0 && inx < orig_w && iny >= 0 && iny < orig_h) { + img_out[i * out_w + j] = img_in[iny * orig_w + inx]; + } else { + img_out[i * out_w + j] = fill_value; + } + } + } + + delete[] coords; + delete[] transformed_coords; + } +} + +void imageRotate(double* img_in, int rows, int cols, double radians, double fill_value, double* img_out) { + + // Translation matrix for moving the origin to the center of the image + double t1_data[] = { + 1, 0, static_cast(-cols)/2, + 0, 1, static_cast(-rows)/2, + 0, 0, 1 + }; + double* t1 = t1_data; + // Translation matrix for moving the origin back to the top left corner + double t2_data[] = { + 1, 0, static_cast(cols)/2, + 0, 1, static_cast(rows)/2, + 0, 0, 1 + }; + double* t2 = t2_data; + // The rotation matrix around the origin + double rot_data[] = { + cos(radians), sin(radians), 0, + -sin(radians), cos(radians), 0, + 0, 0, 1 + }; + double* rot = rot_data; + + // Combined transformation matrix + double m_data1[3*3]; + cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 3, 3, 3, 1.0, t2, 3, rot, 3, 0.0, m_data1, 3); + double m_data2[3*3]; + cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 3, 3, 3, 1.0, m_data1, 3, t1, 3, 0.0, m_data2, 3); + double* m = m_data2; + // Transform image + img_transform(img_in,rows,cols,rows,cols,m[0], m[1], m[2], m[3], m[4], m[5], fill_value, img_out); +} + +double* imageCutout(double* img_in, int rows, int cols, int x, int y, int width, int height, double fill_value) { + // Allocate memory for the output image using MKL + double* img_out = new double[rows * cols]; + + if (width < 1 || height < 1) { + // Invalid width or height, return the input image as it is + cblas_dcopy(rows * cols, img_in, 1, img_out, 1); + } else { + int end_x = x + width - 1; + int end_y = y + height - 1; + + int start_x = std::max(1, x); + int start_y = std::max(1, y); + end_x = std::min(cols, end_x); + end_y = std::min(rows, end_y); + + // Copy the input image to the output image using MKL + cblas_dcopy(rows * cols, img_in, 1, img_out, 1); + + // Fill the cutout region with the fill_value + for (int i = start_y - 1; i < end_y; ++i) { + for (int j = start_x - 1; j < end_x; ++j) { + img_out[i * cols + j] = fill_value; + } + } + } + + return img_out; +} + +double* imageCrop(double* img_in, int orig_w, int orig_h, int w, int h, int x_offset, int y_offset) { + // Allocate memory for the output image + double* img_out = new double[w * h]; + + int start_h = (std::ceil((orig_h - h) / 2)) + y_offset - 1 ; + int end_h = (start_h + h - 1); + int start_w = (std::ceil((orig_w - w) / 2)) + x_offset - 1; + int end_w = (start_w + w - 1); + + // Create a mask to identify the cropped region + double* mask = new double[orig_w * orig_h]; + double* temp_mask = new double[w * h]; + + // Set mask elements to 0 outside the cropped region and 1 inside + memset(mask, 0, orig_w * orig_h * sizeof(double)); + for(int i = 0; i < h * w; i++) { + temp_mask[i] = 1; + } + + for (int i = start_h; i <= end_h; ++i) { + for (int j = start_w; j <= end_w; ++j) { + mask[i * orig_w + j] = temp_mask[(i - start_h) * w + (j - start_w)]; + } + } + + // Apply the mask to crop the image + for (int i = 0; i < h; ++i) { + for (int j = 0; j < w; ++j) { + img_out[i * w + j] = img_in[(start_h + i) * orig_w + (start_w + j)] * mask[(start_h + i) * orig_w + (start_w + j)]; + } + } + + // Free memory for the mask + delete[] mask; + delete[] temp_mask; + + return img_out; +} + +void img_translate(double* img_in, double offset_x, double offset_y, + int in_w, int in_h, int out_w, int out_h, double fill_value, double* img_out) { + int w = out_w; + int h = out_h; + + offset_x = round(offset_x); + offset_y = round(offset_y); + + + int start_x = 0 - static_cast(offset_x); + int start_y = 0 - static_cast(offset_y); + int end_x = std::max(w, out_w) - static_cast(offset_x); + int end_y = std::max(h, out_h) - static_cast(offset_y); + + if (start_x < 0) + start_x = 0; + if (start_y < 0) + start_y = 0; + + if (w < end_x) + end_x = w; + if (h < end_y) + end_y = h; + + if (out_w < end_x + static_cast(offset_x)) + end_x = out_w - static_cast(offset_x); + if (out_h < end_y + static_cast(offset_y)) + end_y = out_h - static_cast(offset_y); + + for (int y = 0; y < out_h; ++y) { + for (int x = 0; x < out_w; ++x) { + img_out[y * out_w + x] = fill_value; + } + } + + if (start_x < end_x && start_y < end_y) { + for (int y = start_y + static_cast(offset_y); y < end_y + static_cast(offset_y); ++y) { + int x_in = (start_x > 0) ? start_x + static_cast(offset_x): start_x; + int y_in = (start_y > 0 ) ? y : y - static_cast(offset_y); + cblas_dcopy(end_x - start_x, &img_in[(x_in) + (y_in) * in_w], + 1, &img_out[y * out_w + start_x + static_cast(offset_x)], 1); + } + } +} + diff --git a/src/main/cpp/imgUtils.h b/src/main/cpp/imgUtils.h new file mode 100644 index 00000000000..001d5ad282c --- /dev/null +++ b/src/main/cpp/imgUtils.h @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#ifndef IMAGE_UTILS_H +#define IMAGE_UTILS_H +#include + +void printImage(const double* image, int rows, int cols); + +void m_img_transform(const double* img_in, int orig_w, int orig_h, int out_w, int out_h, double a, double b, double c, double d, + double e, double f, double fill_value, double* img_out); + +void imageRotate(double* img_in, int rows, int cols, double radians, double fill_value, double* img_out); + +double* imageCutout(double* img_in, int rows, int cols, int x, int y, int width, int height, double fill_value); + +double* imageCrop(double* img_in, int orig_w, int orig_h, int w, int h, int x_offset, int y_offset); + +void img_translate(double* img_in, double offset_x, double offset_y, + int in_w, int in_h, int out_w, int out_h, double fill_value, double* img_out); + +#endif /* IMAGE_UTILS_H */ diff --git a/src/main/cpp/lib/libsystemds_mkl-Linux-x86_64.so b/src/main/cpp/lib/libsystemds_mkl-Linux-x86_64.so index a677b940138..69f0c5efc58 100644 Binary files a/src/main/cpp/lib/libsystemds_mkl-Linux-x86_64.so and b/src/main/cpp/lib/libsystemds_mkl-Linux-x86_64.so differ diff --git a/src/main/cpp/lib/libsystemds_openblas-Linux-x86_64.so b/src/main/cpp/lib/libsystemds_openblas-Linux-x86_64.so index 227443e3248..680f6e89ee5 100644 Binary files a/src/main/cpp/lib/libsystemds_openblas-Linux-x86_64.so and b/src/main/cpp/lib/libsystemds_openblas-Linux-x86_64.so differ diff --git a/src/main/cpp/systemds.cpp b/src/main/cpp/systemds.cpp index 86ac053ad10..43d7c5e5ff4 100644 --- a/src/main/cpp/systemds.cpp +++ b/src/main/cpp/systemds.cpp @@ -27,7 +27,7 @@ #include "libmatrixdnn.h" #include "libmatrixmult.h" #include "systemds.h" - +#include "imgUtils.h" // Results from Matrix-vector/vector-matrix 1M x 1K, dense show that GetDoubleArrayElements creates a copy on OpenJDK. // Logic: @@ -38,8 +38,8 @@ #define GET_DOUBLE_ARRAY(env, input, numThreads) \ ((double*)env->GetPrimitiveArrayCritical(input, NULL)) // ( maxThreads != -1 && ((int)numThreads) == maxThreads ? ((double*)env->GetPrimitiveArrayCritical(input, NULL)) : env->GetDoubleArrayElements(input,NULL) ) - -// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- // From: https://developer.android.com/training/articles/perf-jni.html // 0 // Actual: the array object is un-pinned. @@ -54,7 +54,7 @@ #define RELEASE_ARRAY(env, input, inputPtr, numThreads) \ env->ReleasePrimitiveArrayCritical(input, inputPtr, 0) // ( maxThreads != -1 && ((int)numThreads) == maxThreads ? env->ReleasePrimitiveArrayCritical(input, inputPtr, 0) : env->ReleaseDoubleArrayElements(input, inputPtr, 0) ) - + // ------------------------------------------------------------------- JNIEXPORT void JNICALL Java_org_apache_sysds_utils_NativeHelper_setMaxNumThreads (JNIEnv *, jclass, jint jmaxThreads) { @@ -76,7 +76,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_dmmdd( RELEASE_INPUT_ARRAY(env, m1, m1Ptr, numThreads); RELEASE_INPUT_ARRAY(env, m2, m2Ptr, numThreads); - RELEASE_ARRAY(env, ret, retPtr, numThreads); + RELEASE_ARRAY(env, ret, retPtr, numThreads); return static_cast(nnz); } @@ -122,33 +122,33 @@ JNIEXPORT jboolean JNICALL Java_org_apache_sysds_utils_NativeHelper_conv2dSparse double* avalsPtr = GET_DOUBLE_ARRAY(env, avals, numThreads); double* filterPtr = GET_DOUBLE_ARRAY(env, filter, numThreads); double* retPtr = GET_DOUBLE_ARRAY(env, ret, numThreads); - - conv2dSparse((int)apos, (int)alen, aixPtr, avalsPtr, filterPtr, retPtr, (int)N, (int)C, (int)H, (int)W, + + conv2dSparse((int)apos, (int)alen, aixPtr, avalsPtr, filterPtr, retPtr, (int)N, (int)C, (int)H, (int)W, (int)K, (int)R, (int)S, (int)stride_h, (int)stride_w, (int)pad_h, (int)pad_w, (int)P, (int)Q, (int)numThreads); - + RELEASE_INPUT_ARRAY(env, avals, avalsPtr, numThreads); RELEASE_INPUT_ARRAY(env, filter, filterPtr, numThreads); env->ReleasePrimitiveArrayCritical(aix, aixPtr, JNI_ABORT); - RELEASE_ARRAY(env, ret, retPtr, numThreads); + RELEASE_ARRAY(env, ret, retPtr, numThreads); return (jboolean) true; } JNIEXPORT jboolean JNICALL Java_org_apache_sysds_utils_NativeHelper_conv2dBackwardFilterSparseDense - (JNIEnv * env, jclass, jint apos, jint alen, jintArray aix, jdoubleArray avals, jdoubleArray dout, + (JNIEnv * env, jclass, jint apos, jint alen, jintArray aix, jdoubleArray avals, jdoubleArray dout, jdoubleArray ret, jint N, jint C, jint H, jint W, jint K, jint R, jint S, jint stride_h, jint stride_w, jint pad_h, jint pad_w, jint P, jint Q, jint numThreads) { int* aixPtr = ((int*)env->GetPrimitiveArrayCritical(aix, NULL)); double* avalsPtr = GET_DOUBLE_ARRAY(env, avals, numThreads); double* doutPtr = GET_DOUBLE_ARRAY(env, dout, numThreads); double* retPtr = GET_DOUBLE_ARRAY(env, ret, numThreads); - - conv2dBackwardFilterSparseDense((int)apos, (int)alen, aixPtr, avalsPtr, doutPtr, retPtr, (int)N, (int)C, (int)H, (int)W, + + conv2dBackwardFilterSparseDense((int)apos, (int)alen, aixPtr, avalsPtr, doutPtr, retPtr, (int)N, (int)C, (int)H, (int)W, (int)K, (int)R, (int)S, (int)stride_h, (int)stride_w, (int)pad_h, (int)pad_w, (int)P, (int)Q, (int)numThreads); - + RELEASE_INPUT_ARRAY(env, avals, avalsPtr, numThreads); RELEASE_INPUT_ARRAY(env, dout, doutPtr, numThreads); env->ReleasePrimitiveArrayCritical(aix, aixPtr, JNI_ABORT); - RELEASE_ARRAY(env, ret, retPtr, numThreads); + RELEASE_ARRAY(env, ret, retPtr, numThreads); return (jboolean) true; } @@ -162,11 +162,11 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_conv2dDense( double* retPtr = GET_DOUBLE_ARRAY(env, ret, numThreads); if(inputPtr == NULL || filterPtr == NULL || retPtr == NULL) return -1; - + size_t nnz = dconv2dBiasAddDense(inputPtr, 0, filterPtr, retPtr, (int) N, (int) C, (int) H, (int) W, (int) K, (int) R, (int) S, (int) stride_h, (int) stride_w, (int) pad_h, (int) pad_w, (int) P, (int) Q, false, (int) numThreads); - + RELEASE_INPUT_ARRAY(env, input, inputPtr, numThreads); RELEASE_INPUT_ARRAY(env, filter, filterPtr, numThreads); RELEASE_ARRAY(env, ret, retPtr, numThreads); @@ -184,11 +184,11 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_dconv2dBiasAddD double* retPtr = GET_DOUBLE_ARRAY(env, ret, numThreads); if(inputPtr == NULL || biasPtr == NULL || filterPtr == NULL || retPtr == NULL) return -1; - + size_t nnz = dconv2dBiasAddDense(inputPtr, biasPtr, filterPtr, retPtr, (int) N, (int) C, (int) H, (int) W, (int) K, (int) R, (int) S, (int) stride_h, (int) stride_w, (int) pad_h, (int) pad_w, (int) P, (int) Q, true, (int) numThreads); - + RELEASE_INPUT_ARRAY(env, input, inputPtr, numThreads); RELEASE_INPUT_ARRAY(env, bias, biasPtr, numThreads); RELEASE_INPUT_ARRAY(env, filter, filterPtr, numThreads); @@ -199,7 +199,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_dconv2dBiasAddD JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_sconv2dBiasAddDense( JNIEnv* env, jclass, jobject input, jobject bias, jobject filter, jobject ret, jint N, jint C, jint H, jint W, jint K, jint R, jint S, - jint stride_h, jint stride_w, jint pad_h, jint pad_w, jint P, jint Q, jint numThreads) + jint stride_h, jint stride_w, jint pad_h, jint pad_w, jint P, jint Q, jint numThreads) { float* inputPtr = (float*) env->GetDirectBufferAddress(input); float* biasPtr = (float*) env->GetDirectBufferAddress(bias); @@ -207,7 +207,7 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_sconv2dBiasAddD float* retPtr = (float*) env->GetDirectBufferAddress(ret); if(inputPtr == NULL || biasPtr == NULL || filterPtr == NULL || retPtr == NULL) return -1; - + size_t nnz = sconv2dBiasAddDense(inputPtr, biasPtr, filterPtr, retPtr, (int) N, (int) C, (int) H, (int) W, (int) K, (int) R, (int) S, (int) stride_h, (int) stride_w, (int) pad_h, (int) pad_w, (int) P, (int) Q, true, (int) numThreads); @@ -254,4 +254,5 @@ JNIEXPORT jlong JNICALL Java_org_apache_sysds_utils_NativeHelper_conv2dBackwardF RELEASE_INPUT_ARRAY(env, dout, doutPtr, numThreads); RELEASE_ARRAY(env, ret, retPtr, numThreads); return static_cast(nnz); -} \ No newline at end of file +} + diff --git a/src/main/cpp/systemds.h b/src/main/cpp/systemds.h index b4f741f2904..686ae8dc3d5 100644 --- a/src/main/cpp/systemds.h +++ b/src/main/cpp/systemds.h @@ -114,6 +114,47 @@ JNIEXPORT jboolean JNICALL Java_org_apache_sysds_utils_NativeHelper_conv2dSparse JNIEXPORT void JNICALL Java_org_apache_sysds_utils_NativeHelper_setMaxNumThreads (JNIEnv *, jclass, jint); +/* + * Class: org_apache_sysds_utils_NativeHelper + * Method: imageRotate + * Signature: ([DIIIDD[D)V + */ +JNIEXPORT void JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imageRotate + (JNIEnv *, jclass, jdoubleArray, jint, jint, jdouble, jdouble, jdoubleArray); + +/* + * Class: org_apache_sysds_utils_NativeHelper + * Method: imageCutout + * Signature: ([DIIIIIDD)[D + */ +JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imageCutout + (JNIEnv *, jclass, jdoubleArray, jint, jint, jint, jint, jint, jint, jdouble); + +/* + * Class: org_apache_sysds_utils_NativeHelper + * Method: cropImage + * Signature: ([DIIIIII)[D + */ +JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_cropImage(JNIEnv *, jclass, + jdoubleArray, jint, jint, jint, jint, jint, jint); + +/* + * Class: org_apache_sysds_utils_NativeHelper + * Method: shearImage + * Signature: ([DIIIIII)[D + */ +JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_shearImage(JNIEnv *env, jclass, + jdoubleArray img_in, jint width, jint height, jdouble shear_x, jdouble shear_y, jdouble fill_value); + +/* + * Class: org_apache_sysds_utils_NativeHelper + * Method: imgTranslate + * Signature: ([DDBIIIIID[D)V + */ +JNIEXPORT void JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imgTranslate(JNIEnv *env, jclass cls, + jdoubleArray img_in, jdouble offset_x, jdouble offset_y, + jint in_w, jint in_h, jint out_w, jint out_h, + jdouble fill_value, jdoubleArray img_out); #ifdef __cplusplus } #endif diff --git a/src/main/cpp/systemds_img.cpp b/src/main/cpp/systemds_img.cpp new file mode 100644 index 00000000000..be1b9d47402 --- /dev/null +++ b/src/main/cpp/systemds_img.cpp @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "common.h" +#include "libmatrixdnn.h" +#include "libmatrixmult.h" +#include "systemds.h" +#include "imgUtils.h" + +JNIEXPORT void JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imageRotate + (JNIEnv* env, jclass clazz, jdoubleArray img_in, jint rows, jint cols, jdouble radians, jdouble fill_value, jdoubleArray img_out) { + + if (rows != cols) { + jclass exceptionClass = env->FindClass("java/lang/IllegalArgumentException"); + if (exceptionClass != NULL) { + env->ThrowNew(exceptionClass, "Image dimensions must be equal"); + return; + } + } + + jsize num_pixels = env->GetArrayLength(img_in); + jdouble* img_in_data = env->GetDoubleArrayElements(img_in, NULL); + jdouble* img_out_data = new jdouble[num_pixels]; + imageRotate(img_in_data, rows, cols, radians, fill_value, img_out_data); + env->SetDoubleArrayRegion(img_out, 0, num_pixels, img_out_data); + delete[] img_out_data; + env->ReleaseDoubleArrayElements(img_in, img_in_data, JNI_ABORT); +} + +JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imageCutout + (JNIEnv* env, jclass cls, jdoubleArray img_in, jint rows, jint cols, jint x, jint y, jint width, jint height, jdouble fill_value) { + + jdouble* img_in_arr = env->GetDoubleArrayElements(img_in, nullptr); + jdouble* img_out_arr = imageCutout(img_in_arr, rows, cols, x, y, width, height, fill_value); + jdoubleArray img_out = env->NewDoubleArray(rows * cols); + + env->SetDoubleArrayRegion(img_out, 0, rows * cols, img_out_arr); + env->ReleaseDoubleArrayElements(img_in, img_in_arr, JNI_ABORT); + delete[] img_out_arr; + + return img_out; +} + +JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_cropImage(JNIEnv *env, jclass, + jdoubleArray img_in, jint orig_w, jint orig_h, jint w, jint h, jint x_offset, jint y_offset) { + jsize length = env->GetArrayLength(img_in); + double *img_in_array = env->GetDoubleArrayElements(img_in, 0); + + int start_h = (ceil((orig_h - h) / 2)) + y_offset - 1; + int end_h = (start_h + h - 1); + int start_w = (ceil((orig_w - w) / 2)) + x_offset - 1; + int end_w = (start_w + w - 1); + + jdoubleArray img_out_java; + if (start_h < 0 || end_h >= orig_h || start_w < 0 || end_w >= orig_w) { + img_out_java = env->NewDoubleArray(orig_w * orig_h); + env->SetDoubleArrayRegion(img_out_java, 0, orig_w * orig_h, img_in_array); + + }else { + double *img_out = imageCrop(img_in_array, orig_w, orig_h, w, h, x_offset, y_offset); + + if (img_out == nullptr) { + return nullptr; + } + + img_out_java = env->NewDoubleArray(w * h); + env->SetDoubleArrayRegion(img_out_java, 0, w * h, img_out); + delete[] img_out; + } + + env->ReleaseDoubleArrayElements(img_in, img_in_array, 0); + + return img_out_java; +} + +/*JNIEXPORT jdoubleArray JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_shearImage(JNIEnv *env, jclass, + jdoubleArray img_in, jint width, jint height, jdouble shear_x, jdouble shear_y, jdouble fill_value) { + + + jsize length = env->GetArrayLength(img_in); + double *img_in_array = env->GetDoubleArrayElements(img_in, 0); + + + double* img_out = img_transform(img_in_array, width, height, shear_x, shear_y, fill_value); + + + env->ReleaseDoubleArrayElements(img_in, img_in_array, 0); + + if (img_out == nullptr) { + return nullptr; + } + + + jdoubleArray img_out_java = env->NewDoubleArray(width * height); + env->SetDoubleArrayRegion(img_out_java, 0, width * height, img_out); + + + delete[] img_out; + + return img_out_java; +}*/ + +JNIEXPORT void JNICALL Java_org_apache_sysds_utils_ImgNativeHelper_imgTranslate(JNIEnv *env, jclass cls, + jdoubleArray img_in, jdouble offset_x, jdouble offset_y, + jint in_w, jint in_h, jint out_w, jint out_h, + jdouble fill_value, jdoubleArray img_out) { + + jdouble* j_img_in = env->GetDoubleArrayElements(img_in, nullptr); + jdouble* j_img_out = env->GetDoubleArrayElements(img_out, nullptr); + img_translate(j_img_in, offset_x, offset_y, in_w, in_h, out_w, out_h, fill_value, j_img_out); + env->ReleaseDoubleArrayElements(img_in, j_img_in, 0); + env->ReleaseDoubleArrayElements(img_out, j_img_out, 0); +} \ No newline at end of file diff --git a/src/main/java/org/apache/sysds/utils/ImgNativeHelper.java b/src/main/java/org/apache/sysds/utils/ImgNativeHelper.java new file mode 100644 index 00000000000..efd97fdd571 --- /dev/null +++ b/src/main/java/org/apache/sysds/utils/ImgNativeHelper.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.sysds.utils; + +import org.apache.commons.lang.SystemUtils; + +import java.io.File; + +public class ImgNativeHelper extends NativeHelper { + private final String blasType; + + public ImgNativeHelper(String blasType) { + if(blasType != null) this.blasType = blasType; + else this.blasType = "openblas"; + loadNativeLibrary(); + } + + // Method to load the native library based on the specified BLAS type + private void loadNativeLibrary() { + try { + if (SystemUtils.IS_OS_LINUX) { + String libname = blasType + "-Linux-x86_64.so"; + System.load(System.getProperty("user.dir")+"/src/main/cpp/lib/libsystemds_" + libname); + //System.load("/home/runner/work/systemds/systemds/src/main/cpp/lib/libsystemds_openblas-Linux-x86_64.so"); + } else if (SystemUtils.IS_OS_WINDOWS) { + String libname = blasType + "-Windows-x86_64.dll"; + System.load("/src/main/cpp/lib/".replace("/", File.separator) + "libsystemds_" + libname); + } else if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX) { + // String libname = "systemds_" + blasType + "-Darwin-x86_64"; + System.load(System.getProperty("user.dir")+"/src/main/cpp/lib/libsystemds_" + blasType + "-Darwin-x86_64.dylib"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Rotate an image by the specified number of radians. + * + * @param img_in Input image represented as a 1D double array. + * @param rows Number of rows in the image. + * @param cols Number of columns in the image. + * @param radians Angle in radians by which to rotate the image. + * @param fill_value Value to fill empty pixels after rotation. + * @param img_out Output image represented as a 1D double array. + */ + public native void imageRotate(double[] img_in, int rows, int cols, double radians, double fill_value, double[] img_out); + + /** + * Extract a cutout (subregion) from an image. + * + * @param img_in Input image represented as a 1D double array. + * @param rows Number of rows in the image. + * @param cols Number of columns in the image. + * @param x X-coordinate of the top-left corner of the cutout. + * @param y Y-coordinate of the top-left corner of the cutout. + * @param width Width of the cutout. + * @param height Height of the cutout. + * @param fill_value Value to fill empty pixels in the cutout. + * @return A double array representing the cutout. + */ + public native double[] imageCutout(double[] img_in, int rows, int cols, int x, int y, int width, int height, double fill_value); + + /** + * Crop an image to a specified width and height, starting from a specified offset. + * + * @param img_in Input image represented as a 1D double array. + * @param orig_w Original width of the image. + * @param orig_h Original height of the image. + * @param w Width of the cropped region. + * @param h Height of the cropped region. + * @param x_offset X-coordinate offset for cropping. + * @param y_offset Y-coordinate offset for cropping. + * @return A double array representing the cropped image. + */ + public native double[] cropImage(double[] img_in, int orig_w, int orig_h, int w, int h, int x_offset, int y_offset); + + /** + * Translate (shift) an image by the specified offset in both X and Y directions. + * + * @param img_in Input image represented as a 1D double array. + * @param offset_x X-coordinate offset for translation. + * @param offset_y Y-coordinate offset for translation. + * @param in_w Input image width. + * @param in_h Input image height. + * @param out_w Output image width. + * @param out_h Output image height. + * @param fill_value Value to fill empty pixels after translation. + * @param img_out Output image represented as a 1D double array. + */ + public native void imgTranslate(double[] img_in, double offset_x, double offset_y, + int in_w, int in_h, int out_w, int out_h, + double fill_value, double[] img_out); + +} diff --git a/src/main/java/org/apache/sysds/utils/NativeHelper.java b/src/main/java/org/apache/sysds/utils/NativeHelper.java index e1b3de2f3db..53b8f496fc7 100644 --- a/src/main/java/org/apache/sysds/utils/NativeHelper.java +++ b/src/main/java/org/apache/sysds/utils/NativeHelper.java @@ -416,4 +416,5 @@ public static native boolean conv2dSparse(int apos, int alen, int[] aix, double[ // different tradeoffs. In current implementation, we always use GetPrimitiveArrayCritical as it has proven to be // fastest. We can revisit this decision later and hence I would not recommend removing this method. private static native void setMaxNumThreads(int numThreads); + } diff --git a/src/test/java/org/apache/sysds/test/functions/nativ/ImgUtilsTest.java b/src/test/java/org/apache/sysds/test/functions/nativ/ImgUtilsTest.java new file mode 100644 index 00000000000..af65d79d4bd --- /dev/null +++ b/src/test/java/org/apache/sysds/test/functions/nativ/ImgUtilsTest.java @@ -0,0 +1,521 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.sysds.test.functions.nativ; + +import org.apache.sysds.utils.ImgNativeHelper; +import org.junit.Test; +import static org.junit.Assert.assertArrayEquals; + +public class ImgUtilsTest { + + @Test + public void runTestsWithOpenBlas() { + runTests("openblas"); + } + + //TODO configure intel mkl in test docker container to run this test + public void runTestsWithMKL() { + runTests("mkl"); + } + + + + public static void testImageRotation90And45(ImgNativeHelper imgNativeHelper) { + + // Input image dimensions + int rows = 3; + int cols = 3; + // Input image + double[] img_in = { + 1,2,3, + 4,5,6, + 7,8,9 + }; + // Rotation angle in radians + double radians = Math.PI / 2; + // Fill value for the output image + double fill_value = 0.0; + // Expected output image + double[] expected_img_out_90 = { + 3,6,9, + 2,5,8, + 1,4,7 + }; + + double[] expected_img_out_45 = { + 2,3,6, + 1,5,9, + 4,7,8 + }; + + // Create the output image array + double[] img_out = new double[rows * cols]; + imgNativeHelper.imageRotate(img_in, rows, cols, radians, fill_value, img_out); + assertArrayEquals(expected_img_out_90, img_out, 0.0001); + //rotate by 45 + imgNativeHelper.imageRotate(img_in, rows, cols, radians/2, fill_value, img_out); + assertArrayEquals(expected_img_out_45, img_out, 0.0001); + } + + + + public static void testImageRotation90And45_4x4(ImgNativeHelper imgNativeHelper) { + + // Input image dimensions + int rows = 4; + int cols = 4; + // Input image + double[] img_in = { + 1,2,3,5, + 4,5,6,5, + 7,8,9,5, + 5,5,5,5, + }; + // Rotation angle in radians + double radians = Math.PI / 2; + // Fill value for the output image + double fill_value = 0.0; + // Expected output image + double[] expected_img_out_90 = { + 5,5,5,5, + 3,6,9,5, + 2,5,8,5, + 1,4,7,5 + }; + + + double[] expected_img_out_45 = { + 0,3,5,0, + 2,5,6,5, + 4,5,8,5, + 0,7,5,0 + }; + + // Create the output image array + double[] img_out = new double[rows * cols]; + imgNativeHelper.imageRotate(img_in, rows, cols, radians, fill_value, img_out); + assertArrayEquals(expected_img_out_90, img_out, 0.0001); + //rotate by 45 + imgNativeHelper.imageRotate(img_in, rows, cols, radians/2, fill_value, img_out); + assertArrayEquals(expected_img_out_45, img_out, 0.0001); + } + + + public static void testImageRotation45(ImgNativeHelper imgNativeHelper) { + // Input image dimensions + int rows = 6; + int cols = 6; + + // Input image + double[] img_in = { + 1.0, 2.0, 3.0, 4.0,5.0,6.0, + 5.0, 6.0, 7.0, 8.0,9.0,10.0, + 9.0, 10.0, 11.0, 12.0,13.0,14.0, + 13.0, 14.0, 15.0, 16.0,17.0,18.0, + 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, + 21.0,22.0,23.0,24.0,25.0,26.0 + }; + + // Rotation angle in radians + double radians = Math.PI / 4; + + // Fill value for the output image + double fill_value = 0.0; + + // Expected output image + double[] expected_img_out = { + 0, 4, 5, 10 ,14, 0, + 3, 4, 8 ,13 ,18 ,18, + 2, 7, 12 ,16 ,17 ,22, + 5, 10, 15 ,16, 20 ,25, + 9, 13, 14, 19, 24 ,24, + 0, 13, 17 ,22, 23 ,0 + }; + + // Create the output image array + double[] img_out = new double[rows * cols]; + + // Rotate the image + imgNativeHelper.imageRotate(img_in, rows, cols, radians, fill_value, img_out); + + // Compare the output image with the expected image + assertArrayEquals(expected_img_out, img_out, 0.0001); + } + + + public static void testImageRotation180(ImgNativeHelper imgNativeHelper) { + // Input image dimensions + int rows = 4; + int cols = 4; + + // Input image + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + // Rotation angle in radians + double radians = Math.PI; + + // Fill value for the output image + double fill_value = 0.0; + + // Expected output image + double[] expected_img_out = { + 16.0, 15.0, 14.0, 13.0, + 12.0, 11.0, 10.0, 9.0, + 8.0, 7.0, 6.0, 5.0, + 4.0, 3.0, 2.0, 1.0 + }; + + // Create the output image array + double[] img_out = new double[rows * cols]; + + // Rotate the image + imgNativeHelper.imageRotate(img_in, rows, cols, radians, fill_value, img_out); + + // Compare the output image with the expected image + assertArrayEquals(expected_img_out, img_out, 0.0001); + } + + + public static void testCutoutImage(ImgNativeHelper imgNativeHelper) { + // Example input 2D matrix + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + int rows = 4; + int cols = 4; + int x = 2; + int y = 2; + int width = 3; + int height = 3; + double fill_value = 0.0; + + // Perform image cutout using JNI + double[] img_out = imgNativeHelper.imageCutout(img_in, rows, cols, x, y, width, height, fill_value); + + // Expected output image after cutout + double[] expectedOutput = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 0.0, 0.0, 0.0, + 9.0, 0.0, 0.0, 0.0, + 13.0, 0.0, 0.0, 0.0 + }; + + // Check if the output image matches the expected output + assertArrayEquals(expectedOutput, img_out,0.0001); + } + + + public static void testCutoutImageNonSquare(ImgNativeHelper imgNativeHelper) { + // Example input 2D matrix + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0 + }; + + int rows = 3; + int cols = 4; + int x = 1; + int y = 1; + int width = 3; + int height = 2; + double fill_value = 0.0; + + // Perform image cutout using JNI + double[] img_out = imgNativeHelper.imageCutout(img_in, rows, cols, x, y, width, height, fill_value); + + // Expected output image after cutout + double[] expectedOutput = { + 0.0, 0.0, 0.0, 4.0, + 0.0, 0.0, 0.0, 8.0, + 9.0, 10.0, 11.0, 12.0 + }; + + // Check if the output image matches the expected output + assertArrayEquals(expectedOutput, img_out, 0.0001); + } + + + public static void testImageCutoutInvalidCutout(ImgNativeHelper imgNativeHelper) { + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + int rows = 4; + int cols = 4; + int x = 3; + int y = 3; + int width = -2; + int height = 0; + double fill_value = 0.0; + + double[] expectedOutput = img_in; // Expect no change since the cutout is invalid + + double[] img_out = imgNativeHelper.imageCutout(img_in, rows, cols, x, y, width, height, fill_value); + assertArrayEquals(expectedOutput, img_out,0.0001); + } + + + public static void testImageCutoutNoCutout(ImgNativeHelper imgNativeHelper) { + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + int rows = 4; + int cols = 4; + int x = 3; + int y = 3; + int width = 1; + int height = 1; + double fill_value = 0.0; + + double[] expectedOutput = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 0.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + double[] img_out = imgNativeHelper.imageCutout(img_in, rows, cols, x, y, width, height, fill_value); + assertArrayEquals(expectedOutput, img_out,0.0001); + } + + + public static void testImageCropValidCrop(ImgNativeHelper imgNativeHelper) { + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + int orig_w = 4; + int orig_h = 4; + int w = 2; + int h = 2; + int x_offset = 1; + int y_offset = 1; + + double[] expectedOutput = { + 6.0, 7.0, + 10.0, 11.0 + }; + + double[] img_out = imgNativeHelper.cropImage(img_in, orig_w, orig_h, w, h, x_offset, y_offset); + + assertArrayEquals(expectedOutput, img_out,0.0001); } + + + public static void testImageCropInvalidCrop(ImgNativeHelper imgNativeHelper) { + double[] img_in = { + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + }; + + int orig_w = 4; + int orig_h = 4; + int w = 5; + int h = 5; + int x_offset = 1; + int y_offset = 1; + + double[] expectedOutput = img_in; // Expect no change since the crop is invalid + + double[] img_out = imgNativeHelper.cropImage(img_in, orig_w, orig_h, w, h, x_offset, y_offset); + assertArrayEquals(expectedOutput, img_out,0.0001); + } + + + public void testImgTranslate(ImgNativeHelper imgNativeHelper) { + int in_w = 5; + int in_h = 5; + int out_w = 7; + int out_h = 7; + double fill_value = 0.0; + + double[] img_in = new double[in_w * in_h]; + for (int i = 0; i < in_w * in_h; ++i) { + img_in[i] = i + 1; // Filling input image with sequential values + } + + double[] img_out = new double[out_w * out_h]; + + double offset_x = 1.5; + double offset_y = 1.5; + + imgNativeHelper.imgTranslate(img_in, offset_x, offset_y, in_w, in_h, out_w, out_h, fill_value, img_out); + + // Expected output based on the given offsets and fill value + double[] expectedOutput = { + 0,0,0,0,0,0,0, + 0,0,0,0,0,0,0, + 0,0,1,2,3,4,5, + 0,0,6,7,8,9,10, + 0,0,11,12,13,14,15, + 0,0,16,17,18,19,20, + 0,0,21,22,23,24,25 + }; + + assertArrayEquals(expectedOutput, img_out, 1e-9); // Compare arrays with a small epsilon + } + + + public static void testImgTranslateNegativeOffsets(ImgNativeHelper imgNativeHelper) { + int in_w = 5; + int in_h = 5; + int out_w = 6; + int out_h = 6; + double fill_value = 0.0; + + double[] img_in = new double[in_w * in_h]; + for (int i = 0; i < in_w * in_h; ++i) { + img_in[i] = i + 1; // Filling input image with sequential values + } + + double[] img_out = new double[out_w * out_h]; + + double offset_x = -0.5; // Negative offset in X direction + double offset_y = -0.5; // Negative offset in Y direction + + imgNativeHelper.imgTranslate(img_in, offset_x, offset_y, in_w, in_h, out_w, out_h, fill_value, img_out); + + // Expected output based on the given offsets and fill value + double[] expectedOutput = { + 1,2,3,4,5,0, + 6,7,8,9,10,0, + 11,12,13,14,15,0, + 16,17,18,19,20,0, + 21,22,23,24,25,0, + 0,0,0,0,0,0, + }; + + assertArrayEquals(expectedOutput, img_out, 1e-9); // Compare arrays with a small epsilon + } + + + public static void testImgTranslatePositiveAndNegativeOffsets(ImgNativeHelper imgNativeHelper) { + int in_w = 5; + int in_h = 5; + int out_w = 6; + int out_h = 6; + double fill_value = 0.0; + + double[] img_in = new double[in_w * in_h]; + for (int i = 0; i < in_w * in_h; ++i) { + img_in[i] = i + 1; // Filling input image with sequential values + } + + double[] img_out = new double[out_w * out_h]; + + double offset_x = -0.5; // Negative offset in X direction + double offset_y = 0.5; // Negative offset in Y direction + + imgNativeHelper.imgTranslate(img_in, offset_x, offset_y, in_w, in_h, out_w, out_h, fill_value, img_out); + + // Expected output based on the given offsets and fill value + double[] expectedOutput = { + 0,0,0,0,0,0, + 1,2,3,4,5,0, + 6,7,8,9,10,0, + 11,12,13,14,15,0, + 16,17,18,19,20,0, + 21,22,23,24,25,0, + }; + + assertArrayEquals(expectedOutput, img_out, 1e-9); // Compare arrays with a small epsilon + } + + + public static void testImgTranslatePositiveAndNegativeOffsets2(ImgNativeHelper imgNativeHelper) { + int in_w = 5; + int in_h = 5; + int out_w = 6; + int out_h = 6; + double fill_value = 0.0; + + double[] img_in = new double[in_w * in_h]; + for (int i = 0; i < in_w * in_h; ++i) { + img_in[i] = i + 1; // Filling input image with sequential values + } + + double[] img_out = new double[out_w * out_h]; + + double offset_x = 0.5; // Negative offset in X direction + double offset_y = -0.5; // Negative offset in Y direction + + imgNativeHelper.imgTranslate(img_in, offset_x, offset_y, in_w, in_h, out_w, out_h, fill_value, img_out); + + // Expected output based on the given offsets and fill value + double[] expectedOutput = { + 0,1,2,3,4,5, + 0,6,7,8,9,10, + 0,11,12,13,14,15, + 0,16,17,18,19,20, + 0,21,22,23,24,25, + 0,0,0,0,0,0, + }; + + assertArrayEquals(expectedOutput, img_out, 1e-9); // Compare arrays with a small epsilon + } + + public void runTests(String blasType) { + + ImgNativeHelper imgNativeHelper = new ImgNativeHelper(blasType); + //double startTime =System.currentTimeMillis(); + testImageRotation45(imgNativeHelper); + testImageRotation90And45(imgNativeHelper); + testImageRotation90And45_4x4(imgNativeHelper); + testImageRotation180(imgNativeHelper); + + testCutoutImage(imgNativeHelper); + testImageCutoutInvalidCutout(imgNativeHelper); + testImageCutoutNoCutout(imgNativeHelper); + testCutoutImageNonSquare(imgNativeHelper); + + testImageCropValidCrop(imgNativeHelper); + testImageCropInvalidCrop(imgNativeHelper); + + testImgTranslate(imgNativeHelper); + testImgTranslateNegativeOffsets(imgNativeHelper); + testImgTranslatePositiveAndNegativeOffsets(imgNativeHelper); + testImgTranslatePositiveAndNegativeOffsets2(imgNativeHelper); + //double endTime =System.currentTimeMillis(); + + //System.out.println(blasType+" total execution time:"+(endTime -startTime)); + + } +} diff --git a/src/test/java/org/apache/sysds/test/functions/nativ/PerformanceComparison.java b/src/test/java/org/apache/sysds/test/functions/nativ/PerformanceComparison.java new file mode 100644 index 00000000000..c23465c449e --- /dev/null +++ b/src/test/java/org/apache/sysds/test/functions/nativ/PerformanceComparison.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.sysds.test.functions.nativ; + +import org.apache.sysds.test.AutomatedTestBase; +import org.apache.sysds.test.TestConfiguration; +import org.apache.sysds.test.TestUtils; +import org.apache.sysds.utils.ImgNativeHelper; +import org.junit.Test; + +import java.util.Random; + +import static org.apache.sysds.utils.ImgNativeHelper.*; + +public class PerformanceComparison extends AutomatedTestBase { + + private final static String TEST_DIR = "functions/builtin/"; + private final static String TEST_CLASS_DIR = TEST_DIR + PerformanceComparison.class.getSimpleName() + "/"; + + private final static double eps = 1e-10; + private final static int rows = 512; + private final static int cols = 512; + private final static double spSparse = 0.1; + private final static double spDense = 0.9; + private final static int x_offset = 12; + private final static int y_offset = 24; + private final static float size = 0.8f; + + private final ImgNativeHelper imgNativeHelper = new ImgNativeHelper("openblas"); + + @Override + public void setUp() { + // addTestConfiguration(TEST_NAME,new TestConfiguration(TEST_CLASS_DIR, TEST_NAME,new String[]{"B"})); + } + + private static double randomDouble(double min, double max) { + return min + (max - min) * new Random().nextDouble(); + } + + // Function to generate a random square matrix of size n x n + public static double[] generateRandomMatrix(int rows, int cols, double min, double max, double sparsity, long seed) { + double[] matrix = new double[rows * cols]; + Random random = (seed == -1) ? TestUtils.random : new Random(seed); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + int index = i * cols + j; // Calculate the index for the 1D array + if (random.nextDouble() > sparsity) { + continue; + } + matrix[index] = (random.nextDouble() * (max - min) + min); + } + } + + return matrix; + } + + // Function to print a square matrix + private static void printMatrix(double[] matrix, int n) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + System.out.printf("%.4f\t", matrix[i * n + j]); + } + System.out.println(); + } + } + + public void runBlasTests(boolean sparse,int n, int seed) { + double spSparse = 0.1; + double spDense = 0.9; + double sparsity = sparse ? spSparse: spDense; + + double[] img_in = PerformanceComparison.generateRandomMatrix(n, n, 0, 255, sparsity, seed); + double radians = Math.PI / 4.0; + + // Benchmark imageRotate + double[] img_rotated = new double[n * n]; + long startTime = System.currentTimeMillis(); + + imgNativeHelper.imageRotate(img_in, n, n, radians, 0.0,img_rotated); + + long endTime = System.currentTimeMillis(); + System.out.println("image_rotate\nTotal execution time:"+(endTime -startTime)); + + // Benchmark imageCutout + int x = 100; + int y = 100; + int width = 200; + int height = 200; + startTime =System.currentTimeMillis(); + double[] img_cutout = imgNativeHelper.imageCutout(img_in, n, n, x, y, width, height, 0.0); + endTime =System.currentTimeMillis(); + System.out.println("image_cutout\nTotal execution time:"+(endTime -startTime)); + + // Benchmark cropImage + int orig_w = 512; + int orig_h = 512; + int crop_w = 256; + int crop_h = 256; + int x_offset = 128; + int y_offset = 128; + startTime =System.currentTimeMillis(); + double[] cropped_img = imgNativeHelper.cropImage(img_in, orig_w, orig_h, crop_w, crop_h, x_offset, y_offset); + endTime =System.currentTimeMillis(); + System.out.println("image_crop\nTotal execution time:"+(endTime -startTime)); + + // Benchmark imgTranslate + int in_w = 512; + int in_h = 512; + int out_w = 512; + int out_h = 512; + double[] img_translated = new double[out_w * out_h]; + startTime =System.currentTimeMillis(); + + imgNativeHelper.imgTranslate(img_in, 50.0,50.0,in_w, in_h, out_w, out_h, 0.0,img_translated); + + endTime =System.currentTimeMillis(); + System.out.println("image_translate\nTotal execution time:"+(endTime -startTime)); + } + + + public void runDMLTests(boolean sparse,int seed) + { + String[] testNames = {"image_rotate","image_cutout","image_crop","image_translate"}; + for(String TEST_NAME : testNames) { + System.out.println(TEST_NAME+"\n"); + addTestConfiguration(TEST_NAME,new TestConfiguration(TEST_CLASS_DIR, TEST_NAME,new String[]{"B"})); + + try + { + loadTestConfiguration(getTestConfiguration(TEST_NAME)); + double sparsity = sparse ? spSparse : spDense; + String HOME = SCRIPT_DIR + TEST_DIR; + fullDMLScriptName = HOME + TEST_NAME + ".dml"; + programArgs = new String[]{"-stats","-nvargs", + "in_file=" + input("A"), "out_file=" + output("B"), + "size=" + size, "x_offset=" + x_offset, "y_offset=" + y_offset, "width=" + cols, "height=" + rows + }; + + //generate actual dataset + double[][] A = getRandomMatrix(rows, cols, 0, 255, sparsity, seed); + writeInputMatrixWithMTD("A", A, true); + + runTest(true, false, null, -1); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + } + + + public void benchmarkDMLImgImplementations() { + for(int i = 0; i < 100; i ++) { + runDMLTests(true,i); + } + + } + + + public void benchmarkBlasImgImplementations() { + for(int i = 0; i < 100; i ++) { + runBlasTests(true,512,7); + } + + } + +}