J2i.Net

Nothing at all and Everything in general.

Using PowerShell to Setup Spectator View

I have some down time and spent some of the time to clear the drive on a computer, reinstall Windows, and rebuild my development environment. While I was doing this I decided to try out SpectatorView for the HoloLens. For those unfamiliar SpectatorView is a solution for creating recordings of what one sees through a HoloLens. The HoloLens does have a recording feature built in, but that feature is low resolution. Using SpectatorView one can produce a high resolution recording. Using a high resolution camera mounted to a HoloLens and a video capture a computer takes the motion data stream from the HoloLens to overlay objects from a HoloLens program onto a video stream. I tried it out last week with a 1K camera (Canon 5D Mark III) and it works great!

One of the personal goals that I have is that when possible to automate setup steps for a development environment, especially if I may need to do them again. I expect to see a fast return on investment for doing this either through time saved when I seed to setup an environment for myself again or when coworkers are able to save time by being able to use the scripts that I've made. Up until now my use of PowerShell has been light, but it looked to be the perfect scripting language for this task. For the most part PowerShell gives access to the COM, WMI, and .Net objects in a scripted environment.

Software and Files Needed for Spectator View

To setup SpectatorView there are a number of software components that are needed.

    • Visual Studio
    • Unity
    • BlackMagic DeckLink SDK
    • Hololens Companion Toolkit
    • Hololens Toolkit
    • OpenCV 3.2
    • Canon SDK (Optional)

Unity and Visual Studio are frequently used within my team, so I'm starting off with the assumption that these two components are already present. Getting the other components is easy enough, so I don't expect an attempt at scripting the acquisition and installations for it to save much time. But I also feel that initial attempts at scripting are better applied to something simple to allow for problems to be found before being applied to more complex scenarios. The Canon SDK can only be downloaded if someone registers with Canon, requests access to the SDK, and then downloads the SDK after receiving approval to download the SDK. Since there are manual steps involved in getting access to the Canon SDK I did not script the acquisition of the file. Similarly the BlackMagic Decklink SDK also requires registration to download. While I could not script the acquisition of these two files I was still able to handle them post download in my script. Each version of these SDKs have a slightly different name since the version number is a part of the file name. To keep the script easy to use it will figure out the actual name of the file when run. If a new version of one of the SDKs were to be used it would only be necessary to replace the ZIP file being used.

Cloning the HoloLend Companion Kit with github

The Hololens Companion Kit is the easiest of the components to acquire through a script. It can be downloaded using GIT. So I won't spend much time talking about how to download it and only mention it at all since it is a necessary component.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";
$companionKitFilePath = $PSScriptRoot + "\HoloLensCompanionKit";
$kitIsDownloaded = Test-Path $companionKitFilePath;
if(!$kitIsDownloaded) {
	git clone $hololensCompanionKitURL;
}

If you are not familiar with what the Test-Path command means don't worry about it just yet. I'll explain it's use when it is used on another component.

File Paths

Leaving nothing to be ambiguous many of the file locations referenced are relative to the location of the power script file. In PowerScript There is a variable named $PSScriptRoot whose value is the full path to the folder from which the script is run. While not absolutely necessary I build paths to various files and folders using this variable.

Downloading OpenCV

For downloading the OpenCV source I've placed the URLs for OpenCV from one of the mirrors in a variable at the top of my script. If I ever wanted to change the version of OpenCV used I would need to change the value in this variable. There are several ways to download a file in PowerScript. I decided to use .Net's WebClient because of speed and predictability. I considered using BITS but when using BITS to download you can't know when the service will get around to downloading the file; it will do so on it's own schedule. Downloading the file with the WebClient is easy. But it provides no feedback while it works. Just so that someone using the script doesn't think that something is wrong I decided to print a message letting them know to hold on for a moment.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";			
$webClient = New-Object System.Net.WebClient;
$openCVArchivePath = $openCVFolder + "\archive.exe"
Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
$webClient.DownloadFile($openCVUrl,$openCVArchivePath );

If you were to take the above and put it in a file with a "ps1" extension and run it you'll find that a file downloads named archive.exe. OpenCV for Windows is distributed in a self extracting archive (Which is why it uses an EXE extension instead of ZIP). Once the files is downloaded if you were to run it you would be greeted with a prompt asking where you want the file to be extracted. For automating the setup I don't want the archive to give these prompts. I'll invoke it passing to it on the command line the location that it should use. Adding those arguments to the above script we end up with the following.

$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";			
$webClient = New-Object System.Net.WebClient;
$openCVFolder = "${PSScriptRoot}\openCV\openCV3.2"
$openCVArchivePath = $openCVFolder + "\archive.exe"
Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
$webClient.DownloadFile($openCVUrl,$openCVArchivePath );
Write-Host "Download complete";
& "${openCVArchivePath}" -o "${openCVFolder}" -y

This works. But i wanted it to be possible to run this script more than once if it failed for some reason. To prevent the script from reinstalling OpenCV again if it had been installed before I check for the existence of the OpenCV folder. This is a less than thorough test as it would not detect conditions such as an archive being partially unzipped before failing. But this is satisficing for my purposes. The Test-Path< command can be used to determine whether or not a file object exists at some path. I wrapped the above code in a block that checks for the existence of the OpenCV folder first.

$openCVIsDownloaded = Test-Path $openCVFolder
if(!$openCVIsDownloaded) {
	## Download code goes here
}
		

Unpacking the Canon and BlackMagic SDKS

The BlackMagic and Canon SDKs are both in ZIP files. Unpacking them will be about the same. I'll only talk about the Canon SDK for a moment. But the there's equal applicability to what I am about to say to both. Like the OpenCV SDK I'll check to see if the folder for the Canon SDK exists before trying to unpack it. The script requires that the Canon SDK zip file be in the same folder as the script. The Canon SDK versions all start with the same prefix. They all start with EDSDK. To find the file I use the Get-ChildItem command to get a list of files. If there is more than one version fo teh SDK in the folder sorting the results and taking the last should result in the most recent one being selected. PowerShell allows the use of negative index numbers to address an item from the end of a list. Index -1 will be the last item in the list. Taking the last item and getting its FullName value will give the path to the zip file to be unzipped. The Expand-Archive command will unzip the file to a specified path.

################################################
# Unpacking the Canon SDK
################################################
$CanonSDKIsPresent = Test-Path "${PSScriptRoot}\CanonSDK";
if(!$CanonSDKIsPresent)
{
	#Find the Canon SDK Zip(s) present
	$canonSdkArchiveList = Get-ChildItem "${PSScriptRoot}\EDSDK*.zip" | Sort;
	$canonSDKZip = $canonSdkArchiveList[-1].FullName
	Expand-Archive  -path $canonSDKZip -DestinationPath "${PSScriptRoot}\CanonSDK";
} 
################################################
# Unpacking the Black Magic SDK
################################################
$BlackMagicSDKIsPresent = Test-Path "${PSScriptRoot}\BlackMagicSDK";
if(!$BlackMagicSDKIsPresent) 
{
	#Find the Black Magic SDKs present
	$blackMagicSDKList = Get-ChildItem "${PSScriptRoot}\Blackmagic_Decklink_SDK*.zip";
	$blackMagicZip = $blackMagicSDKList[-1].FullName;
	Expand-Archive  -path $blackMagicZip -DestinationPath "${PSScriptRoot}\BlackMagicSDK";
}
	

Modifying the Visual Studio Dependencies File

The Visual Studio project that is part of the SpectatorView software requires some updates so that it knows where the various SDKs are located. The path to the BlackMagic SDK, Canon SDK, and OpenCV software must be added to it. The dependencies file dependencies.props is an XML file. The Common Language Runtime has classes for manipulating XML files. I use one of these to update this file. The exact path of each component could differ depending on which SDK version is used. Rather than hard code the path I use the Get-ChildItem command again to query for the name of folders. For the CanonSDK the line of script code to get the path looks like the following.

$canonPath = (Get-ChildItem "${PSScriptRoot}\CanonSDK")[-1].FullName+"\Windows";

There is a little more nesting that occurs with the other two SDKs. But the lines for getting their paths is similar.

$openCVPath = ((Get-ChildItem (Get-ChildItem "${PSScriptRoot}\opencv" | ?{$_.PsIsContainer} )[-1].FullName)|?{$_.PsIsContainer})[-1].FullName+"\sources\include";
$blackMagicPath = (Get-ChildItem "${PSScriptRoot}\BlackMagicSDK" | ?{$_.PsIsContainer})[-1].FullName +"\Windows";

With those paths populated I now need to load the XML file, update the values, and write them back. The Common Language Runtime's XML class is available to PowerShell. Given a string that contains XML if that string is cast to [xml] it will be parsed and all the nodes available through its properties. To get the XML string from the contents of the dependencies files the Get-Content command will be used. Given a file path it returns the contents as a string.

$dependencies = [xml] (Get-Content -Path "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props");
$dependencies.Project.PropertyGroup[0]."OpenCV_vc14" = $openCVPath ;
$dependencies.Project.PropertyGroup[0]."DeckLink_inc" = $blackMagicPAth;
$dependencies.Project.PropertyGroup[0]."Canon_SDK" = $canonPath;
$dependencies.Save( "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props" );

Next Steps

The script is at the bottom of this post. Running this script doesn't result in SpectatorView being 100% setup. It is still necessary to go through calibration (a step that requires you to physically do some things) in front of the camera) and copying the libraries from the sample project into your own project. There are opportunities for further automating the setup. But I felt this was a good time to write about what is working at this moment (if I wait until it is perfect ot may never get posted).

clear;
################################################
# A few download URLs
################################################
$openCVUrl = "https://downloads.sourceforge.net/project/opencvlibrary/opencv-win/3.2.0/opencv-3.2.0-vc14.exe?r=http%3A%2F%2Fopencv.org%2Freleases.html&ts=1501614414&use_mirror=iweb";
$holoLensCompanionKitURL = "https://github.com/Microsoft/HoloLensCompanionKit";
$webClient = New-Object System.Net.WebClient;
Write-Host "Running from ${PSScriptRoot}";


$companionKitFilePath = $PSScriptRoot + "\HoloLensCompanionKit";
$openCVFolder = "${PSScriptRoot}\openCV\openCV3.2"
$openCVArchivePath = $openCVFolder + "\archive.exe"

$kitIsDownloaded = Test-Path $companionKitFilePath;
if(!$kitIsDownloaded) {
	git clone $hololensCompanionKitURL;
}

$openCVIsDownloaded = Test-Path $openCVFolder
if(!$openCVIsDownloaded) {
	New-Item "$openCVFolder"  -type directory
	Write-Host "Downloading OpenCV. This is going to take a while..." -foreground "Green"
	$webClient.DownloadFile($openCVUrl,$openCVArchivePath );	
	Write-Host "Download complete";
	& "${openCVArchivePath}" -o "${openCVFolder}" -y
}
################################################
# Unpacking the Black Magic SDK
################################################
$BlackMagicSDKIsPresent = Test-Path "${PSScriptRoot}\BlackMagicSDK";
if(!$BlackMagicSDKIsPresent) 
{
	#Find the Black Magic SDKs present
	$blackMagicSDKList = Get-ChildItem "${PSScriptRoot}\Blackmagic_Decklink_SDK*.zip";
	$blackMagicZip = $blackMagicSDKList[-1].FullName;
	Expand-Archive  -path $blackMagicZip -DestinationPath "${PSScriptRoot}\BlackMagicSDK";
}
################################################
# Unpacking the Canon SDK
################################################
$CanonSDKIsPresent = Test-Path "${PSScriptRoot}\CanonSDK";
if(!$CanonSDKIsPresent)
{
	#Find the Canon SDK Zip(s) present
	$canonSdkArchiveList = Get-ChildItem "${PSScriptRoot}\EDSDK*.zip" | Sort;
	$canonSDKZip = $canonSdkArchiveList[-1].FullName;
	Expand-Archive  -path $canonSDKZip -DestinationPath "${PSScriptRoot}\CanonSDK";
} 
else 
{
	Write-Host "Canon SDK already present";
}
################################################
# Modifying the Dependencies File
################################################
$canonPath = (Get-ChildItem "${PSScriptRoot}\CanonSDK")[-1].FullName+"\Windows";
$openCVPath = ((Get-ChildItem (Get-ChildItem "${PSScriptRoot}\opencv" | ?{$_.PsIsContainer} )[-1].FullName)|?{$_.PsIsContainer})[-1].FullName+"\sources\include";
$blackMagicPath = (Get-ChildItem "${PSScriptRoot}\BlackMagicSDK" | ?{$_.PsIsContainer})[-1].FullName +"\Windows";

$dependencies = [xml] (Get-Content -Path "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props");
$dependencies.Project.PropertyGroup[0]."OpenCV_vc14" = $openCVPath ;
$dependencies.Project.PropertyGroup[0]."DeckLink_inc" = $blackMagicPAth;
$dependencies.Project.PropertyGroup[0].= $canonPath;
$dependencies.Save( "${PSScriptRoot}\${HoloLensCompanionKit}\HoloLensCompanionKit\SpectatorView\dependencies.props" );
Comments are closed