diff --git a/.gitignore b/.gitignore index 081c460..7f58e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ src/cpp_grouping/*.pyd src/cpp_grouping/cpp_grouping.cpp cuda_fatbin + +installer/pyi_build +installer/pyi_temp +installer/*.exe \ No newline at end of file diff --git a/installer/3d_bz.spec b/installer/3d_bz.spec new file mode 100644 index 0000000..055f4c4 --- /dev/null +++ b/installer/3d_bz.spec @@ -0,0 +1,46 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + +SITE_PACKAGES = 'C:/Users/Carson/miniconda3/envs/env_3d_beats/Lib/site-packages/' +REPO_ROOT = 'C:/Users/Carson/code/hand_decision_trees' + +a = Analysis([REPO_ROOT + '/src/3d_bz.py'], + pathex=[REPO_ROOT], + binaries=[ (SITE_PACKAGES + 'glfw/glfw3.dll', '.') ], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) + +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='3d_bz', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True, + disable_windowed_traceback=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='3d_bz') diff --git a/installer/build_all.ps1 b/installer/build_all.ps1 new file mode 100644 index 0000000..0e07520 --- /dev/null +++ b/installer/build_all.ps1 @@ -0,0 +1,30 @@ +# run this to create an installer. +# you must already have precompiled fatbins, and declare the model / config / fatbins you want to include +# run this from the root of the repo. i.e. you should be running the command: ./installer/build_all.ps1 + +$pyi_build = './installer/pyi_build' +$pyi_temp = './installer/pyi_temp' + +if (Test-Path $pyi_build) { Remove-Item -Recurse -Force $pyi_build } +if (Test-Path $pyi_temp) { Remove-Item -Recurse -Force $pyi_temp } + +# use pyinstaller to assemble standalone binary (and many many DLLs and other libs) +pyinstaller --workpath $pyi_temp --distpath $pyi_build ./installer/3d_bz.spec + +if (Test-Path $pyi_temp) { Remove-Item -Recurse -Force $pyi_temp } + +# you need Inno Setup (6) installed to make the installer! + +$model_dir = 'model' +$model_cfg = 'model/model_cfg.json' +$fatbin_dir = 'cuda_fatbin' + +&'C:\Program Files (x86)\Inno Setup 6\ISCC.exe' ` + make_windows_installer.iss ` + /Dbuild_dir=$pyi_build/3d_bz ` + /Dmodel_dir=$model_dir ` + /Dmodel_cfg=$model_cfg ` + /Dfatbin_dir=$fatbin_dir ` + /DAPP_NAME=3d-beats ` + /DAPP_VERSION=2.0 ` + /Oinstaller diff --git a/make_windows_installer.iss b/make_windows_installer.iss new file mode 100644 index 0000000..97176d3 --- /dev/null +++ b/make_windows_installer.iss @@ -0,0 +1,54 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define PUBLISHER "Carson Swope" +#define APP_EXE "3d_bz.exe" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{E8E83B0A-4C84-458C-BA7B-CAEF3B56768B} +AppName={#APP_NAME} +AppVersion={#APP_VERSION} +AppVerName={#APP_NAME} {#APP_VERSION} +AppCopyright=Copyright (C) 2021 Carson Swope +AppPublisher=Carson Swope +AppPublisherURL=https://www.3d-beats.com/ +DefaultDirName={autopf}\{#APP_NAME} +DisableProgramGroupPage=yes +; Install for current user, not system-wide +PrivilegesRequired=lowest +OutputBaseFilename={#APP_NAME}-setup-{#APP_VERSION} +Compression=lzma +SolidCompression=yes +WizardStyle=modern +VersionInfoVersion={#APP_VERSION} + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "{#build_dir}/*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs +Source: "{#model_dir}/*"; DestDir: "{app}/{#model_dir}"; Flags: ignoreversion recursesubdirs +Source: "{#fatbin_dir}/*"; DestDir: "{app}/{#fatbin_dir}"; Flags: ignoreversion recursesubdirs +Source: "hand_config.json"; DestDir: "{app}"; Flags: ignoreversion + +[Dirs] +Name: "{userappdata}\3d-beats" + +[UninstallDelete] +Type: filesandordirs; Name: "{userappdata}\3d-beats" +Type: filesandordirs; Name: "{app}" + +[Icons] +; Name: "{app}\{#APP_NAME}"; Filename: "{app}\{#APP_NAME}" +Name: "{app}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"; \ + Parameters: "-cfg {app}\{#model_cfg} --fatbin_in {app}\{#fatbin_dir}" +Name: "{autoprograms}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"; \ + Parameters: "-cfg {app}\{#model_cfg} --fatbin_in {app}\{#fatbin_dir}" +Name: "{autodesktop}\{#APP_NAME}"; Filename: "{app}\{#APP_EXE}"; \ + Parameters: "-cfg {#model_cfg} --fatbin_in {#fatbin_dir}"; Tasks: desktopicon + diff --git a/readme.md b/readme.md index 41bffd9..19e19f5 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -# 3d Beats v2! +# 3d-beats ![Hand Classifier](rdf.gif) @@ -8,9 +8,13 @@ Labeled training data is obtained by using skin paint to color various sections To generate fingertip positions, the mean shift algorithm is used to find the center of each classification region. The original depth image is then sampled at the determined pixel, to find the height of each fingertip above the plane. -## Instructions for installation / development. +## Installation -Unfortunately, it's not very well packaged for easy distribution at the moment. To start, you will need: +Download the latest installer from the releases page. + +## Instructions for local development + +To set up your system for local development, you will need: - [microsoft visual studio 2019](https://visualstudio.microsoft.com/downloads/) (2019). Unfortunately you have to download the whole thing just to get the compiler toolkit - [cuda toolkit](https://developer.nvidia.com/cuda-11.3.0-download-archive?target_os=Windows&target_arch=x86_64) (11.3 ? Maybe other versions would work) @@ -91,10 +95,6 @@ Okay, so now the real thing! The idea is that each fingertip and thumb is assign - make new model. simpler 2-stage RDF architecture: - 1. fingertip OR {rest of hand} OR {thumb tip?} (2-3 classes) - 2. identify which fingertip (4-5 classes) -- make midi selection more robust, / UI -- distribution - - python w/ environment. pyinstaller? - - precompiled fatbins - WORKING - - custom package: cpp_grouping - - model files -- cuda/opengl cleanup errors in console \ No newline at end of file +- make midi selection more robust / provide UI for it +- cuda/opengl cleanup errors in console +- clean up gui layout - sensible defaults so imgui.ini not necessary diff --git a/requirements.txt b/requirements.txt index 0ddbcc2..b30b022 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ PyOpenGL==3.1.5 pyrealsense2==2.44.0.3073 scipy==1.6.2 python-rtmidi==1.4.9 +pyinstaller==4.5.1 diff --git a/src/calibrated_plane.py b/src/calibrated_plane.py index d3b1b59..644dd80 100644 --- a/src/calibrated_plane.py +++ b/src/calibrated_plane.py @@ -37,8 +37,6 @@ def get_mat(self): def make(self, pts_gpu, img_dims, start_mat = None): self.rand_generator.fill_uniform(self.rand_cu) - # self.rand_cpu = np.random.uniform((self.num_random_guesses, 32), dtype=np.float32) - # self.rand_cu.set(self.rand_cpu) self.candidate_planes_cu.fill(np.float(0)) DIM_X = img_dims[0] diff --git a/src/decision_tree.py b/src/decision_tree.py index 8227ba0..26aa005 100644 --- a/src/decision_tree.py +++ b/src/decision_tree.py @@ -2,6 +2,9 @@ import json import numpy as np +from pathlib import Path +import os.path + import pycuda.gpuarray as cu_array import pycuda.driver as cu @@ -177,6 +180,8 @@ class LayeredDecisionForest(): @staticmethod def load(config_filename, eval_dims): cfg = json.loads(open(config_filename).read()) + # models are loaded 1-by-1 from paths with parent directory as a root + cfg['root'] = os.path.join(*Path(config_filename).parts[0:-1]) return LayeredDecisionForest(cfg, eval_dims) def __init__(self, cfg, eval_dims): @@ -185,7 +190,8 @@ def __init__(self, cfg, eval_dims): self.eval_dims = eval_dims # y,x !! self.m = [] for l in cfg['layers']: - m = DecisionForest.load(l['model']) + # model path is relative to config file itself + m = DecisionForest.load(os.path.join(cfg['root'], l['model'])) if 'filter_model' in l and 'filter_model_class in l': filter_model = l['filter_model'] filter_model_class = l['filter_model_class'] @@ -194,7 +200,7 @@ def __init__(self, cfg, eval_dims): filter_model_class = None self.m.append((m, filter_model, filter_model_class)) - # self.m = [DecisionForest.load(m) for m in cfg['layers']] + self.num_models = len(self.m) self.label_images = [GpuBuffer(eval_dims, dtype=np.uint16) for _ in range(self.num_models)] diff --git a/src/rs_util.py b/src/rs_util.py index c5d2ffb..d22778b 100644 --- a/src/rs_util.py +++ b/src/rs_util.py @@ -21,11 +21,12 @@ def start_stream(args): pipeline_wrapper = rs.pipeline_wrapper(pipeline) pipeline_profile = config.resolve(pipeline_wrapper) device = pipeline_profile.get_device() + # in the place where the original command was run, this file must be available device_config_json = open('hand_config.json', 'r').read() rs.rs400_advanced_mode(device).load_json(device_config_json) device.first_depth_sensor().set_option(rs.option.depth_units, 0.0001) config.enable_stream(rs.stream.depth, 848, 480, rs.format.z16, 90) - + profile = pipeline.start(config) if rs_bag: profile.get_device().as_playback().set_real_time(False)