diff --git a/app/Dockerfile.jetson b/app/Dockerfile.jetson new file mode 100644 index 0000000..455296b --- /dev/null +++ b/app/Dockerfile.jetson @@ -0,0 +1,26 @@ +# OSNICK=stretch|bionic|buster +ARG OSNICK=buster + +#---------------------------------------------------------------------------------------------- +FROM redisfab/redisedgevision-${OSNICK}:0.2.0 + +# This is due on the following error on ARMv8: +# /usr/lib/aarch64-linux-gnu/libgomp.so.1: cannot allocate memory in static TLS block +# Something is exausting TLS, causing libgomp to fail. Preloading it as a workaround helps. + +ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libgomp.so.1 +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get -qq update && apt-get install -qqy wget python3-distutils patch +RUN wget -q https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py && python3 /tmp/get-pip.py + +WORKDIR /app +ADD . /app + +# patch init script for aarch64 +RUN patch init.py init.patch +RUN patch gear.py gear.patch + +RUN pip3 install -r requirements.txt + +ENTRYPOINT [ "python3" ] diff --git a/app/gear.patch b/app/gear.patch new file mode 100644 index 0000000..1a17c1c --- /dev/null +++ b/app/gear.patch @@ -0,0 +1,53 @@ +--- gear.py 2021-03-29 17:15:04.726216533 -0500 ++++ gear.py.updated 2021-03-29 17:14:34.195958815 -0500 +@@ -27,6 +27,7 @@ + return res + + def addToGraphRunner(x): ++ x = x['value'] + try: + xlog('addToGraphRunner:', 'count=', x['count']) + +@@ -54,7 +55,7 @@ + animal = index[str(res.index(res1[0]) - 1)][1] + xlog('addToGraphRunner:', 'animal=', animal) + +- return (animal, x['img']) ++ return animal, x['img'] + except: + xlog('addToGraphRunner: error:', sys.exc_info()[0]) + +@@ -62,21 +63,24 @@ + # save animal name into a new stream + try: + redisgears.executeCommand('xadd', 'cats', 'MAXLEN', '~', str(MAX_IMAGES), '*', 'image', 'data:image/jpeg;base64,' + base64.b64encode(x[1]).decode('utf8')) ++ xlog('addToStream: ', x[0]) + except: + xlog('addToStream: error:', sys.exc_info()[0]) + + def shouldTakeFrame(x): ++ v = x['value'] + try: + global framesToDrop + framesToDrop += 1 +- xlog('shouldTakeFrame', x['count'], (framesToDrop % 10 == 0)) ++ xlog('shouldTakeFrame', v['count'], (framesToDrop % 10 == 0)) + return framesToDrop % 10 == 0 + except: + xlog('shouldTakeFrame: error:', sys.exc_info()[0]) + + def passAll(x): ++ v = x['value'] + try: +- redisgears.executeCommand('xadd', 'all', 'MAXLEN', '~', str(MAX_IMAGES), '*', 'image', 'data:image/jpeg;base64,' + base64.b64encode(x['img']).decode('utf8')) ++ redisgears.executeCommand('xadd', 'all', 'MAXLEN', '~', str(MAX_IMAGES), '*', 'image', 'data:image/jpeg;base64,' + base64.b64encode(v['img']).decode('utf8')) + except: + xlog('passAll: error:', sys.exc_info()[0]) + +@@ -87,4 +91,5 @@ + map(addToGraphRunner).\ + filter(lambda x: 'cat' in x[0]).\ + foreach(addToStream).\ +- register('camera:0') ++ register(prefix='camera:0') ++ diff --git a/app/init.patch b/app/init.patch new file mode 100644 index 0000000..ff95d08 --- /dev/null +++ b/app/init.patch @@ -0,0 +1,11 @@ +--- init.py 2021-03-29 17:14:57.525688056 -0500 ++++ init.py.updated 2021-03-29 17:14:27.563462225 -0500 +@@ -27,7 +27,7 @@ + print('Loading model - ', end='') + with open('models/mobilenet_v2_1.4_224_frozen.pb', 'rb') as f: + model = f.read() +- res = conn.execute_command('AI.MODELSET', 'mobilenet:model', 'TF', 'CPU', 'INPUTS', 'input', 'OUTPUTS', 'MobilenetV2/Predictions/Reshape_1', model) ++ res = conn.execute_command('AI.MODELSET', 'mobilenet:model', 'TF', 'CPU', 'INPUTS', 'input', 'OUTPUTS', 'MobilenetV2/Predictions/Reshape_1', 'BLOB', model) + print(res) + + # Load the gear diff --git a/camera/Dockerfile.jetson b/camera/Dockerfile.jetson new file mode 100644 index 0000000..6f4f9b8 --- /dev/null +++ b/camera/Dockerfile.jetson @@ -0,0 +1,30 @@ +FROM nvcr.io/nvidia/l4t-base:r32.5.0 + +# This is due on the following error on ARMv8: +# /usr/lib/aarch64-linux-gnu/libgomp.so.1: cannot allocate memory in static TLS block +# Something is exausting TLS, causing libgomp to fail. Preloading it as a workaround helps. +# ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libgomp.so.1 + +ENV DEBIAN_FRONTEND=noninteractive +ENV LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu:$LD_LIBRARY_PATH +ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libgomp.so.1 + +RUN apt-get -qq update && apt-get upgrade -y +RUN apt-get install -qqy curl patch +RUN curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py && python3 /tmp/get-pip.py + +RUN pip install redis==3.2.1 + +WORKDIR /usr/src/app + +ADD read_camera_jetson.py ./ +ADD *.jpg ./ +ADD build_opencv.patch ./ + +# build opencv from the source for Jetson Nano aarch64 +RUN wget https://raw.githubusercontent.com/mdegans/nano_build_opencv/301e95dd6c361ed29dc523a46483f05bafd7f70b/build_opencv.sh +RUN patch build_opencv.sh build_opencv.patch +RUN chmod +x build_opencv.sh +RUN ./build_opencv.sh + +CMD [ "python3", "./read_camera_jetson.py", "-u", "redis://redis:6379"] diff --git a/camera/build_opencv.patch b/camera/build_opencv.patch new file mode 100644 index 0000000..ca5447b --- /dev/null +++ b/camera/build_opencv.patch @@ -0,0 +1,69 @@ +diff --git a/build_opencv.sh b/build_opencv.sh +index 486bca9..e3c22c1 100755 +--- a/build_opencv.sh ++++ b/build_opencv.sh +@@ -2,6 +2,7 @@ + # 2019 Michael de Gans + + set -e ++set -x + + # change default constants here: + readonly PREFIX=/usr/local # install prefix, (can be ~/.local for a user install) +@@ -24,7 +25,7 @@ cleanup () { + if ! [[ "$1" -eq "--test-warning" ]] ; then + echo "(Doing so may make running tests on the build later impossible)" + fi +- read -p "Y/N " yn ++ yn="Y" + case ${yn} in + [Yy]* ) rm -rf /tmp/build_opencv ; break;; + [Nn]* ) exit ;; +@@ -53,9 +54,9 @@ install_dependencies () { + # open-cv has a lot of dependencies, but most can be found in the default + # package repository or should already be installed (eg. CUDA). + echo "Installing build dependencies." +- sudo apt-get update +- sudo apt-get dist-upgrade -y --autoremove +- sudo apt-get install -y \ ++ apt-get update ++ apt-get dist-upgrade -y --autoremove ++ apt-get install -y \ + build-essential \ + cmake \ + git \ +@@ -104,7 +105,7 @@ install_dependencies () { + configure () { + local CMAKEFLAGS=" + -D BUILD_EXAMPLES=OFF +- -D BUILD_opencv_python2=ON ++ -D BUILD_opencv_python2=OFF + -D BUILD_opencv_python3=ON + -D CMAKE_BUILD_TYPE=RELEASE + -D CMAKE_INSTALL_PREFIX=${PREFIX} +@@ -114,13 +115,13 @@ configure () { + -D CUDNN_VERSION='8.0' + -D EIGEN_INCLUDE_PATH=/usr/include/eigen3 + -D ENABLE_NEON=ON +- -D OPENCV_DNN_CUDA=ON ++ -D OPENCV_DNN_CUDA=OFF + -D OPENCV_ENABLE_NONFREE=ON + -D OPENCV_EXTRA_MODULES_PATH=/tmp/build_opencv/opencv_contrib/modules + -D OPENCV_GENERATE_PKGCONFIG=ON + -D WITH_CUBLAS=ON +- -D WITH_CUDA=ON +- -D WITH_CUDNN=ON ++ -D WITH_CUDA=OFF ++ -D WITH_CUDNN=OFF + -D WITH_GSTREAMER=ON + -D WITH_LIBV4L=ON + -D WITH_OPENGL=ON" +@@ -175,7 +176,7 @@ main () { + if [[ -w ${PREFIX} ]] ; then + make install 2>&1 | tee -a install.log + else +- sudo make install 2>&1 | tee -a install.log ++ make install 2>&1 | tee -a install.log + fi + + cleanup --test-warning diff --git a/camera/read_camera_jetson.py b/camera/read_camera_jetson.py new file mode 100644 index 0000000..a12e0e8 --- /dev/null +++ b/camera/read_camera_jetson.py @@ -0,0 +1,133 @@ +import argparse +import cv2 +import redis +import time +import sys +import os +import atexit + +try: + import urllib.parse +except ImportError: + import urllib.parse as urlparse + +IMAGE_WIDTH = 640 +IMAGE_HEIGHT = 480 + +MAX_IMAGES = 1000 # 5 + +def gstreamer_pipeline( + capture_width=IMAGE_WIDTH, + capture_height=IMAGE_HEIGHT, + display_width=IMAGE_WIDTH, + display_height=IMAGE_HEIGHT, + framerate=15, + flip_method=0, +): + return ( + "nvarguscamerasrc ! " + "video/x-raw(memory:NVMM), " + "width=(int)%d, height=(int)%d, " + "format=(string)NV12, framerate=(fraction)%d/1 ! " + "nvvidconv flip-method=%d ! " + "video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! " + "videoconvert ! " + "video/x-raw, format=(string)BGR ! appsink" + % ( + capture_width, + capture_height, + framerate, + flip_method, + display_width, + display_height, + ) + ) + + +class Webcam: + def __init__(self, infile=0, fps=15.0): + if infile: + self.cam = cv2.VideoCapture(infile) + self.cam.set(cv2.CAP_PROP_FPS, fps) + self.cam.set(cv2.CAP_PROP_FRAME_WIDTH, IMAGE_WIDTH) + self.cam.set(cv2.CAP_PROP_FRAME_HEIGHT, IMAGE_HEIGHT) + else: + self.cam = cv2.VideoCapture(gstreamer_pipeline(flip_method=0), cv2.CAP_GSTREAMER) + atexit.register(self.cam.release) + + + def __iter__(self): + self.count = -1 + return self + + def __next__(self): # Python 2.7 + self.count += 1 + + # Read image + ret_val, img0 = self.cam.read() + assert ret_val, 'Webcam Error' + + # Preprocess + # img = cv2.flip(img0, 1) + img = img0 + + return self.count, img + + def __len__(self): + return 0 + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('infile', help='Input file (leave empty to use webcam)', nargs='?', type=str, default=None) + parser.add_argument('-o', '--output', help='Output stream key name', type=str, default='camera:0') + parser.add_argument('-u', '--url', help='Redis URL', type=str, default='redis://localhost:6379') + parser.add_argument('--fmt', help='Frame storage format', type=str, default='.jpg') + parser.add_argument('--fps', help='Frames per second (webcam)', type=float, default=15.0) + parser.add_argument('--maxlen', help='Maximum length of output stream', type=int, default=1000) + parser.add_argument('--test', help='transmit image instead of reading webcam', action="store_true") + args = parser.parse_args() + + # Set up Redis connection + url = urllib.parse.urlparse(args.url) + conn = redis.Redis(host=url.hostname, port=url.port) + if not conn.ping(): + raise Exception('Redis unavailable') + print('Connected to Redis') + sys.stdout.flush() + + if args.test is False: + print('Operating in camera mode') + sys.stdout.flush() + if args.infile is None: + loader = Webcam(infile=0, fps=args.fps) + else: + loader = Webcam(infile=int(args.infile), fps=args.fps) + + for (count, img) in loader: + _, data = cv2.imencode(args.fmt, img) + msg = { + 'count': count, + 'image': data.tobytes() + } + _id = conn.execute_command('xadd', args.output, 'MAXLEN', '~', str(MAX_IMAGES), '*', 'count', msg['count'], 'img', msg['image']) + print('count: {} id: {}'.format(count, _id)) + sys.stdout.flush() + else: + image_file = os.environ['ANIMAL'] + '.jpg' + print('Operating in test mode with image ' + image_file) + sys.stdout.flush() + img0 = cv2.imread(image_file) + img = cv2.resize(img0, (IMAGE_WIDTH, IMAGE_HEIGHT)) + _, data = cv2.imencode(args.fmt, img) + count = 1 + while True: + msg = { + 'count': count, + 'image': data.tobytes() + } + _id = conn.execute_command('xadd', args.output, 'MAXLEN', '~', str(MAX_IMAGES), '*', 'count', msg['count'], 'img', msg['image']) + # print('count: {} rc: {} id: {}'.format(count, rc, _id)) + # sys.stdout.flush() + count += 1 + time.sleep(0.1) diff --git a/redis/Dockerfile.jetson b/redis/Dockerfile.jetson new file mode 100644 index 0000000..03ad4a6 --- /dev/null +++ b/redis/Dockerfile.jetson @@ -0,0 +1,100 @@ +ARG REDIS_VER=6.0.9 +ARG GEARS_VER=1.0.6 +ARG AI_VER=1.0.2 + +ARG OS=L4T + +ARG OSNICK=bionic + +# ARCH=x64|arm64v8|arm32v7 +ARG ARCH=arm64v8 + +ARG PACK=0 +ARG TEST=0 + +#---------------------------------------------------------------------------------------------- +FROM redisfab/redis:${REDIS_VER}-${ARCH}-${OSNICK} AS redis +FROM redisfab/jetpack:4.4.1-${ARCH}-l4t AS builder + +ARG OS +ARG ARCH +ARG REDIS_VER +ARG GEARS_VER +ARG AI_VER + +RUN echo "Building for $${OS} for ${ARCH} [with Redis ${REDIS_VER}]" + +WORKDIR /build + +RUN apt-get update +RUN apt-get install -y locales python3-dev patch +ENV LANG en_US.UTF-8 + +COPY --from=redis /usr/local/ /usr/local/ + +# build RedisAI +RUN git clone --recursive --depth 1 --branch v${AI_VER} https://github.com/RedisAI/RedisAI.git + +WORKDIR /build/RedisAI + +# patch system setup script for aarch64 +RUN git lfs install +ADD jetson.patch ./ +RUN git apply jetson.patch + +RUN PIP=1 FORCE=1 ./opt/readies/bin/getpy3 +RUN ./opt/system-setup.py + +ARG DEPS_ARGS="GPU=1 WITH_TFLITE=0 WITH_PT=0 WITH_ORT=0 WITH_UNIT_TESTS=0" +RUN if [ "$DEPS_ARGS" = "" ]; then ./get_deps.sh; else env $DEPS_ARGS ./get_deps.sh; fi + +ARG BUILD_ARGS="GPU=1 SHOW=1 WITH_TFLITE=0 WITH_PT=0 WITH_ORT=0 WITH_UNIT_TESTS=0" + +#RUN apt-get install -y libegl1-mesa-dev freeglut3-dev + +RUN bash -c "set -e &&\ + . ./opt/readies/bin/sourced ./profile.d &&\ + make -C opt build $BUILD_ARGS" + +# build RedisGears +WORKDIR /build + +RUN git clone --recursive https://github.com/RedisGears/RedisGears.git +RUN cd RedisGears && git checkout tags/v${GEARS_VER} && ./deps/readies/bin/getpy2 && make setup && make fetch && make all + +#---------------------------------------------------------------------------------------------- +FROM nvcr.io/nvidia/l4t-base:r32.4.4 + +ARG ARCH +ARG GEARS_VER + +ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_DRIVER_CAPABILITIES compute,utility + +RUN apt-get -qq update && apt-get -q install -y libgomp1 build-essential libatlas-base-dev cmake + +ENV REDIS_MODULES /usr/lib/redis/modules +RUN mkdir -p $REDIS_MODULES/ +RUN mkdir /artifacts + +COPY --from=redis /usr/local/ /usr/local/ +COPY --from=builder /build/RedisAI/install-gpu/ $REDIS_MODULES/ +COPY --from=builder /build/RedisGears/bin/linux-${ARCH}-release/ $REDIS_MODULES/ +COPY --from=builder /build/RedisGears/artifacts/release/ /artifacts/ + +RUN $REDIS_MODULES/python3_${GEARS_VER}/bin/python3 -m pip install --upgrade pip +RUN $REDIS_MODULES/python3_${GEARS_VER}/bin/python3 -m pip install setuptools==49.2.0 + +# build numpy from source to use ATLAS library +RUN env LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu:$LD_LIBRARY_PATH $REDIS_MODULES/python3_${GEARS_VER}/bin/python3 -m pip install --no-binary :all: numpy +RUN $REDIS_MODULES/python3_${GEARS_VER}/bin/python3 -m pip install opencv-python imageio + +EXPOSE 6379 +ENTRYPOINT ["redis-server"] + +ENV GEARS_VER ${GEARS_VER} + +CMD ["--loadmodule", "/usr/lib/redis/modules/redisai.so", \ + "--loadmodule", "/usr/lib/redis/modules/redisgears.so", \ + "PythonHomeDir", "/usr/lib/redis/modules/python3_$GEARS_VER/", \ + "PythonInstallationDir", "/usr/lib/redis/modules/"] diff --git a/redis/jetson.patch b/redis/jetson.patch new file mode 100644 index 0000000..7c9d29c --- /dev/null +++ b/redis/jetson.patch @@ -0,0 +1,46 @@ +diff --git a/get_deps.sh b/get_deps.sh +index 89ea863..263dd2d 100755 +--- a/get_deps.sh ++++ b/get_deps.sh +@@ -75,7 +75,7 @@ if [[ $WITH_DLPACK != 0 ]]; then + + if [[ ! -d $DLPACK ]]; then + echo "Cloning dlpack ..." +- git clone --depth 1 https://github.com/dmlc/dlpack.git $DLPACK ++ git clone --depth 1 --branch v0.3 https://github.com/dmlc/dlpack.git $DLPACK + echo "Done." + else + echo "dlpack is in place." +@@ -86,7 +86,7 @@ fi + + ################################################################################# LIBTENSORFLOW + +-TF_VERSION="1.15.0" ++TF_VERSION="2.3.2" + + if [[ $WITH_TF != 0 ]]; then + [[ $FORCE == 1 ]] && rm -rf $LIBTENSORFLOW +@@ -112,9 +112,8 @@ if [[ $WITH_TF != 0 ]]; then + LIBTF_URL_BASE=https://storage.googleapis.com/tensorflow/libtensorflow + fi + elif [[ $ARCH == arm64v8 ]]; then +- TF_VERSION=1.15.0 + TF_ARCH=arm64 +- LIBTF_URL_BASE=https://s3.amazonaws.com/redismodules/tensorflow ++ LIBTF_URL_BASE=https://s3.amazonaws.com/redismodules/redisai/redisai + elif [[ $ARCH == arm32v7 ]]; then + TF_VERSION=1.15.0 + TF_ARCH=arm +diff --git a/opt/system-setup.py b/opt/system-setup.py +index 873d422..91deb61 100755 +--- a/opt/system-setup.py ++++ b/opt/system-setup.py +@@ -33,7 +33,7 @@ class RedisAISetup(paella.Setup): + self.install("build-essential cmake") + self.install("python3-regex") + self.install("python3-psutil python3-networkx python3-numpy") # python3-skimage +- self.install_git_lfs_on_linux() ++ # self.install_git_lfs_on_linux() + + def redhat_compat(self): + self.install("redhat-lsb-core")