diff --git a/src/c/CudaImageProccessing.sln b/src/c/CudaImageProccessing.sln
index 8a878364a8d303e858110e0b8fa163b5db647711..d59eda2b9a44d8f3cd0208dfb22aa6d5c0b84de6 100644
--- a/src/c/CudaImageProccessing.sln
+++ b/src/c/CudaImageProccessing.sln
@@ -10,20 +10,39 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CudaMex", "CudaMex.vcxproj"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CudaImageProcessor", "CudaImageProcessor.vcxproj", "{3E663AF2-4E6F-487B-9072-CCA90C66A822}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CudaPy3DLL", "CudaPy3DLL.vcxproj", "{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}"
+	ProjectSection(ProjectDependencies) = postProject
+		{3E663AF2-4E6F-487B-9072-CCA90C66A822} = {3E663AF2-4E6F-487B-9072-CCA90C66A822}
+	EndProjectSection
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
 		Release|x64 = Release|x64
+		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Debug|x64.ActiveCfg = Debug|x64
 		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Debug|x64.Build.0 = Debug|x64
+		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Debug|x86.ActiveCfg = Debug|x64
 		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Release|x64.ActiveCfg = Release|x64
 		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Release|x64.Build.0 = Release|x64
+		{6698E8EC-49D9-421E-AA87-5BCC6B466347}.Release|x86.ActiveCfg = Release|x64
 		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Debug|x64.ActiveCfg = Debug|x64
 		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Debug|x64.Build.0 = Debug|x64
+		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Debug|x86.ActiveCfg = Debug|x64
 		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Release|x64.ActiveCfg = Release|x64
 		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Release|x64.Build.0 = Release|x64
+		{3E663AF2-4E6F-487B-9072-CCA90C66A822}.Release|x86.ActiveCfg = Release|x64
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Debug|x64.ActiveCfg = Debug|x64
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Debug|x64.Build.0 = Debug|x64
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Debug|x86.ActiveCfg = Debug|Win32
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Debug|x86.Build.0 = Debug|Win32
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Release|x64.ActiveCfg = Release|x64
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Release|x64.Build.0 = Release|x64
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Release|x86.ActiveCfg = Release|Win32
+		{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}.Release|x86.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/src/c/CudaMex.vcxproj b/src/c/CudaMex.vcxproj
index 537a32fd2ad2e02b22fd25cef10ab3fd91f94491..e9a5be280b26935501866eaad242b9f89d688b8b 100644
--- a/src/c/CudaMex.vcxproj
+++ b/src/c/CudaMex.vcxproj
@@ -92,10 +92,11 @@ copy $(OutDir)CudaMex.dll "$(ProjectDir)Mex.mexw64"</Command>
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClInclude Include="Cuda\Vec.h" />
-    <ClInclude Include="Mex\CommandList.h" />
     <ClInclude Include="Mex\MexCommand.h" />
+    <ClInclude Include="Mex\MexWrapDef.h" />
     <ClInclude Include="Mex\MexKernel.h" />
     <ClInclude Include="Mex\ScopedProcessMutex.h" />
+    <ClInclude Include="WrapCmds\CommandList.h" />
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Mex\CudaMex.cpp" />
diff --git a/src/c/CudaMex.vcxproj.filters b/src/c/CudaMex.vcxproj.filters
index 366a02e6bb192615973d7970c789a23f5e93123a..8bdc58978afa18c40168387ade148315dcade9da 100644
--- a/src/c/CudaMex.vcxproj.filters
+++ b/src/c/CudaMex.vcxproj.filters
@@ -21,15 +21,18 @@
     <ClInclude Include="Mex\MexCommand.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="Mex\CommandList.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="Mex\ScopedProcessMutex.h">
       <Filter>Header Files</Filter>
     </ClInclude>
     <ClInclude Include="Mex\MexKernel.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="WrapCmds\CommandList.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Mex\MexWrapDef.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Mex\CudaMex.cpp">
diff --git a/src/c/CudaPy3DLL.vcxproj b/src/c/CudaPy3DLL.vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..5d19ee69cae8a3fcbeae9bf44ab6e7d7972c5075
--- /dev/null
+++ b/src/c/CudaPy3DLL.vcxproj
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="Python\hip_module.cpp" />
+    <ClCompile Include="Python\PyKernel.cpp" />
+    <ClCompile Include="Python\PyWrapClosure.cpp" />
+    <ClCompile Include="Python\PyWrapCommand.cpp" />
+    <ClCompile Include="Python\PyWrapDeviceCount.cpp" />
+    <ClCompile Include="Python\PyWrapDeviceStats.cpp" />
+    <ClCompile Include="Python\PyWrapElementWiseDifference.cpp" />
+    <ClCompile Include="Python\PyWrapEntropyFilter.cpp" />
+    <ClCompile Include="Python\PyWrapGaussian.cpp" />
+    <ClCompile Include="Python\PyWrapGetMinMax.cpp" />
+    <ClCompile Include="Python\PyWrapHighPassFilter.cpp" />
+    <ClCompile Include="Python\PyWrapLoG.cpp" />
+    <ClCompile Include="Python\PyWrapMaxFilter.cpp" />
+    <ClCompile Include="Python\PyWrapMeanFilter.cpp" />
+    <ClCompile Include="Python\PyWrapMedianFilter.cpp" />
+    <ClCompile Include="Python\PyWrapMinFilter.cpp" />
+    <ClCompile Include="Python\PyWrapMinMax.cpp" />
+    <ClCompile Include="Python\PyWrapMultiplySum.cpp" />
+    <ClCompile Include="Python\PyWrapOpener.cpp" />
+    <ClCompile Include="Python\PyWrapStdFilter.cpp" />
+    <ClCompile Include="Python\PyWrapSum.cpp" />
+    <ClCompile Include="Python\PyWrapVarFilter.cpp" />
+    <ClCompile Include="Python\PyWrapWienerFilter.cpp" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="Python\PyIncludes.h" />
+    <ClInclude Include="Python\PyKernel.h" />
+    <ClInclude Include="Python\PyWrapCommand.h" />
+    <ClInclude Include="Python\PyWrapDef.h" />
+    <ClInclude Include="WrapCmds\CommandList.h" />
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{0957901A-E67A-40C2-9EF5-76DF8EFBC2D5}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>CudaPy3DLL</RootNamespace>
+    <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)..\Python\</OutDir>
+    <IntDir>$(SolutionDir)Intermediate\$(ProjectName)\$(Configuration)_$(PlatformName)\</IntDir>
+    <TargetName>HIP</TargetName>
+    <TargetExt>.pyd</TargetExt>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)..\Python\</OutDir>
+    <IntDir>$(SolutionDir)Intermediate\$(ProjectName)\$(Configuration)_$(PlatformName)\</IntDir>
+    <TargetName>HIP</TargetName>
+    <TargetExt>.pyd</TargetExt>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)..\Python\</OutDir>
+    <IntDir>$(SolutionDir)Intermediate\$(ProjectName)\$(Configuration)_$(PlatformName)\</IntDir>
+    <TargetName>HIP</TargetName>
+    <TargetExt>.pyd</TargetExt>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)..\Python\</OutDir>
+    <IntDir>$(SolutionDir)Intermediate\$(ProjectName)\$(Configuration)_$(PlatformName)\</IntDir>
+    <TargetName>HIP</TargetName>
+    <TargetExt>.pyd</TargetExt>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;CUDAPY3DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>Python;$(PYTHON3_DIR)/include;$(NUMPY3_DIR)/core/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessToFile>false</PreprocessToFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(PYTHON3_DIR)\libs;$(SolutionDir)Output\CudaImageProcessor\$(Configuration)_$(PlatformName)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>CudaImageProcessor_d.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;CUDAPY3DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>Python;$(PYTHON3_DIR)/include;$(NUMPY3_DIR)/core/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessToFile>false</PreprocessToFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(PYTHON3_DIR)\libs;$(SolutionDir)Output\CudaImageProcessor\$(Configuration)_$(PlatformName)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>CudaImageProcessor_d.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;CUDAPY3DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>Python;$(PYTHON3_DIR)/include;$(NUMPY3_DIR)/core/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessToFile>false</PreprocessToFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(PYTHON3_DIR)\libs;$(SolutionDir)Output\CudaImageProcessor\$(Configuration)_$(PlatformName)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>CudaImageProcessor.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;CUDAPY3DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>Python;$(PYTHON3_DIR)/include;$(NUMPY3_DIR)/core/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessToFile>false</PreprocessToFile>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(PYTHON3_DIR)\libs;$(SolutionDir)Output\CudaImageProcessor\$(Configuration)_$(PlatformName)</AdditionalLibraryDirectories>
+      <AdditionalDependencies>CudaImageProcessor.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/src/c/CudaPy3DLL.vcxproj.filters b/src/c/CudaPy3DLL.vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..f49bf9599ed325280fbc8f6945097b82a23d4f87
--- /dev/null
+++ b/src/c/CudaPy3DLL.vcxproj.filters
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="Python\hip_module.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapCommand.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapDeviceCount.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapDeviceStats.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapClosure.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyKernel.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapElementWiseDifference.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapEntropyFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapGaussian.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapGetMinMax.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapHighPassFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapLoG.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMaxFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMeanFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMedianFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMinFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMinMax.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapOpener.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapStdFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapSum.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapVarFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapWienerFilter.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Python\PyWrapMultiplySum.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="WrapCmds\CommandList.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Python\PyWrapDef.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Python\PyWrapCommand.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Python\PyKernel.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Python\PyIncludes.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/c/Mex/CommandList.h b/src/c/Mex/CommandList.h
deleted file mode 100644
index 77b35196b45a92a2e41f4831b77b396a512d6c36..0000000000000000000000000000000000000000
--- a/src/c/Mex/CommandList.h
+++ /dev/null
@@ -1,61 +0,0 @@
-#undef DEF_MEX_COMMAND
-#undef BEGIN_MEX_COMMANDS
-#undef END_MEX_COMMANDS
-
-#if defined(INSTANCE_COMMANDS)
-#define BEGIN_MEX_COMMANDS
-#define END_MEX_COMMANDS
-#define DEF_MEX_COMMAND(name) Mex##name _ginstMex##name;
-#elif defined(BUILD_COMMANDS)
-#define BEGIN_MEX_COMMANDS																								\
-	MexCommand* const MexCommand::m_commands[] =												\
-							{
-
-#define END_MEX_COMMANDS																								\
-							};																							\
-							const size_t MexCommand::m_numCommands = sizeof(MexCommand::m_commands) / sizeof(MexCommand*);
-
-#define DEF_MEX_COMMAND(name) &_ginstMex##name,
-#else
-#define BEGIN_MEX_COMMANDS
-#define END_MEX_COMMANDS
-#define DEF_MEX_COMMAND(name)																	\
-class Mex##name : public MexCommand															\
-	{																									\
-	public:																								\
-	virtual std::string check(int nlhs,mxArray* plhs[],int nrhs,const mxArray* prhs[]) const;	\
-	virtual void execute(int nlhs,mxArray* plhs[],int nrhs,const mxArray* prhs[])  const;		\
-	\
-	virtual void usage(std::vector<std::string>& outArgs,std::vector<std::string>& inArgs)  const;	\
-	virtual void help(std::vector<std::string>& helpLines) const;									\
-	\
-	Mex##name() :MexCommand(#name) {};															\
-	};
-#endif
-
-BEGIN_MEX_COMMANDS
-// These are default commands defined for all MEX routines.
-DEF_MEX_COMMAND(Info)
-DEF_MEX_COMMAND(Help)
-// Additional specific mex commands should be added here.
-DEF_MEX_COMMAND(DeviceCount)
-DEF_MEX_COMMAND(DeviceStats)
-DEF_MEX_COMMAND(Closure)
-DEF_MEX_COMMAND(ElementWiseDifference)
-DEF_MEX_COMMAND(EntropyFilter)
-DEF_MEX_COMMAND(Gaussian)
-DEF_MEX_COMMAND(GetMinMax)
-DEF_MEX_COMMAND(HighPassFilter)
-DEF_MEX_COMMAND(LoG)
-DEF_MEX_COMMAND(MaxFilter)
-DEF_MEX_COMMAND(MeanFilter)
-DEF_MEX_COMMAND(MedianFilter)
-DEF_MEX_COMMAND(MinFilter)
-DEF_MEX_COMMAND(MinMax)
-DEF_MEX_COMMAND(MultiplySum)
-DEF_MEX_COMMAND(Opener)
-DEF_MEX_COMMAND(StdFilter)
-DEF_MEX_COMMAND(Sum)
-DEF_MEX_COMMAND(VarFilter)
-DEF_MEX_COMMAND(WienerFilter)
-END_MEX_COMMANDS
diff --git a/src/c/Mex/MexCommand.cpp b/src/c/Mex/MexCommand.cpp
index d64b055b88eea4ee0ac5c8ddccd05f9622037ff0..97e4cb3c73a91d4ad0f86d3a50c835afa3150910 100644
--- a/src/c/Mex/MexCommand.cpp
+++ b/src/c/Mex/MexCommand.cpp
@@ -1,11 +1,13 @@
 #include "MexCommand.h"
 
 #define INSTANCE_COMMANDS
-#include "CommandList.h"
+#include "MexWrapDef.h"
+#include "../WrapCmds/CommandList.h"
 #undef INSTANCE_COMMANDS
 
 #define BUILD_COMMANDS
-#include "CommandList.h"
+#include "MexWrapDef.h"
+#include "../WrapCmds/CommandList.h"
 #undef BUILD_COMMANDS
 
 // Module name info
diff --git a/src/c/Mex/MexCommand.h b/src/c/Mex/MexCommand.h
index ac83b7ec8c6709a02e9245d73fab42c6e1849336..59807ff1f48bac509a240c59536b7e17607ae02b 100644
--- a/src/c/Mex/MexCommand.h
+++ b/src/c/Mex/MexCommand.h
@@ -286,4 +286,5 @@ private:
 };
 
 
-#include "CommandList.h"
+#include "MexWrapDef.h"
+#include "../WrapCmds/CommandList.h"
diff --git a/src/c/Mex/MexGetMinMax.cpp b/src/c/Mex/MexGetMinMax.cpp
index 96ff9871be2643f8bfb9dfcd780198c2a6f95ba8..3e587e0ae29089d4a8a771d65abf32f21d55a5aa 100644
--- a/src/c/Mex/MexGetMinMax.cpp
+++ b/src/c/Mex/MexGetMinMax.cpp
@@ -7,6 +7,7 @@
 
 void MexGetMinMax::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) const
 {
+	// TODO: Why is this the only device with a 0 default?
 	int device = 0;
 
 	if (!mxIsEmpty(prhs[1]))
diff --git a/src/c/Mex/MexLoG.cpp b/src/c/Mex/MexLoG.cpp
index c211b455cdbf86f1ce3f1133fffddef6e2418ba3..cbf67295e8e12a1ad815be18768ca5c06855413d 100644
--- a/src/c/Mex/MexLoG.cpp
+++ b/src/c/Mex/MexLoG.cpp
@@ -109,6 +109,7 @@ void MexLoG::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
 		setupInputPointers(prhs[0], imageDims, &imageInPtr);
 		setupOutputPointers(&plhs[0], imageDims, &imageOutPtr);
 
+		// TODO: Do we really want to use float outputs here?
 		ImageContainer<double> imageIn(imageInPtr, imageDims);
 		ImageContainer<float> imageOut(imageOutPtr, imageDims);
 
diff --git a/src/c/Mex/MexVarFilter.cpp b/src/c/Mex/MexVarFilter.cpp
index b56c371f313c37be2e7d49783de8ec66b1960033..86caeda630963e0328b117f4c758c506cd3da4bd 100644
--- a/src/c/Mex/MexVarFilter.cpp
+++ b/src/c/Mex/MexVarFilter.cpp
@@ -33,7 +33,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<bool> imageIn(imageInPtr, imageDims);
 		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 
 	}
 	else if (mxIsUint8(prhs[0]))
@@ -44,7 +44,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
 		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsUint16(prhs[0]))
 	{
@@ -54,7 +54,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
 		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsInt16(prhs[0]))
 	{
@@ -64,7 +64,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<short> imageIn(imageInPtr, imageDims);
 		ImageContainer<short> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsUint32(prhs[0]))
 	{
@@ -74,7 +74,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
 		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsInt32(prhs[0]))
 	{
@@ -84,7 +84,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<int> imageIn(imageInPtr, imageDims);
 		ImageContainer<int> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsSingle(prhs[0]))
 	{
@@ -94,7 +94,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<float> imageIn(imageInPtr, imageDims);
 		ImageContainer<float> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else if (mxIsDouble(prhs[0]))
 	{
@@ -104,7 +104,7 @@ void MexVarFilter::execute(int nlhs, mxArray* plhs[], int nrhs, const mxArray* p
 		ImageContainer<double> imageIn(imageInPtr, imageDims);
 		ImageContainer<double> imageOut(imageOutPtr, imageDims);
 
-		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
 	}
 	else
 	{
diff --git a/src/c/Mex/MexWrapDef.h b/src/c/Mex/MexWrapDef.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e2cd73a9063b76eac61f31ed61052e2bb323c04
--- /dev/null
+++ b/src/c/Mex/MexWrapDef.h
@@ -0,0 +1,34 @@
+#undef BEGIN_WRAP_COMMANDS
+#undef END_WRAP_COMMANDS
+#undef DEF_WRAP_COMMAND
+
+#if defined(INSTANCE_COMMANDS)
+#define BEGIN_WRAP_COMMANDS
+#define END_WRAP_COMMANDS
+#define DEF_WRAP_COMMAND(name) Mex##name _ginstMex##name;
+#elif defined(BUILD_COMMANDS)
+#define BEGIN_WRAP_COMMANDS																								\
+	MexCommand* const MexCommand::m_commands[] =																		\
+							{
+
+#define END_WRAP_COMMANDS																								\
+							};																							\
+							const size_t MexCommand::m_numCommands = sizeof(MexCommand::m_commands) / sizeof(MexCommand*);
+
+#define DEF_WRAP_COMMAND(name) &_ginstMex##name,
+#else
+#define BEGIN_WRAP_COMMANDS
+#define END_WRAP_COMMANDS
+#define DEF_WRAP_COMMAND(name)																\
+class Mex##name : public MexCommand															\
+{																							\
+	public:																					\
+	virtual std::string check(int nlhs,mxArray* plhs[],int nrhs,const mxArray* prhs[]) const;	\
+	virtual void execute(int nlhs,mxArray* plhs[],int nrhs,const mxArray* prhs[])  const;		\
+	\
+	virtual void usage(std::vector<std::string>& outArgs,std::vector<std::string>& inArgs)  const;	\
+	virtual void help(std::vector<std::string>& helpLines) const;									\
+	\
+	Mex##name() :MexCommand(#name) {};														\
+};
+#endif
diff --git a/src/c/Python/PyIncludes.h b/src/c/Python/PyIncludes.h
new file mode 100644
index 0000000000000000000000000000000000000000..4924f545b951a1aab3a943b0f92d418c2eeceff2
--- /dev/null
+++ b/src/c/Python/PyIncludes.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <Python.h>
+
+// Make sure that Numpy symbols don't get re-imported in multiple compilation units
+#ifndef NUMPY_IMPORT_MODULE
+	#define NO_IMPORT_ARRAY
+#endif
+
+#define PY_ARRAY_UNIQUE_SYMBOL HIP_ARRAY_API
+#include <numpy/arrayobject.h>
+
+#include <py3c.h>
diff --git a/src/c/Python/PyKernel.cpp b/src/c/Python/PyKernel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9ecdfec7123077277abdfc73dca150e3bae3401e
--- /dev/null
+++ b/src/c/Python/PyKernel.cpp
@@ -0,0 +1,123 @@
+#include "PyKernel.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/ImageDimensions.cuh"
+
+#include <string.h>
+
+
+ImageContainer<float> getKernel(PyArrayObject* kernel)
+{
+	int numDims = PyArray_NDIM(kernel);
+	const npy_intp* DIMS = PyArray_DIMS(kernel);
+
+	Vec<size_t> kernDims(1);
+
+	if ( numDims > 2 )
+		kernDims.z = (size_t)DIMS[2];
+	else
+		kernDims.z = 1;
+
+	if ( numDims > 1 )
+		kernDims.y = (size_t)DIMS[1];
+	else
+		kernDims.y = 1;
+
+	if ( numDims > 0 )
+		kernDims.x = (size_t)DIMS[0];
+	else
+		return ImageContainer<float>();
+
+	ImageContainer<float> kernelOut;
+	kernelOut.resize(ImageDimensions(kernDims, 1, 1));
+
+	float* kernPtr = kernelOut.getPtr();
+
+	
+	if ( PyArray_TYPE(kernel) == NPY_BOOL )
+	{
+		bool* mexKernelData;
+		mexKernelData = (bool*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			if ( mexKernelData[i] )
+				kernPtr[i] = 1.0f;
+			else
+				kernPtr[i] = 0.0f;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_UINT8 )
+	{
+		unsigned char* mexKernelData;
+		mexKernelData = (unsigned char*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			double val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_INT16 )
+	{
+		short* mexKernelData;
+		mexKernelData = (short*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			short val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_UINT16 )
+	{
+		unsigned short* mexKernelData;
+		mexKernelData = (unsigned short*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			unsigned short val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_INT32 )
+	{
+		int* mexKernelData;
+		mexKernelData = (int*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			int val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_UINT32 )
+	{
+		unsigned int* mexKernelData;
+		mexKernelData = (unsigned int*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			unsigned int val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_FLOAT )
+	{
+		float* mexKernelData = (float*)PyArray_DATA(kernel);
+		memcpy(kernPtr, mexKernelData, sizeof(float)*kernDims.product());
+	}
+	else if ( PyArray_TYPE(kernel) == NPY_DOUBLE )
+	{
+		double* mexKernelData;
+		mexKernelData = (double*)PyArray_DATA(kernel);
+
+		for ( int i = 0; i < kernDims.product(); ++i )
+		{
+			double val = mexKernelData[i];
+			kernPtr[i] = (float)val;
+		}
+	}
+
+	return kernelOut;
+}
diff --git a/src/c/Python/PyKernel.h b/src/c/Python/PyKernel.h
new file mode 100644
index 0000000000000000000000000000000000000000..38e3b223f6e455a97a673e9fd2f77ec1d09352f1
--- /dev/null
+++ b/src/c/Python/PyKernel.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "../Cuda/ImageContainer.h"
+#include "../Cuda/Vec.h"
+
+#include "PyIncludes.h"
+
+ImageContainer<float> getKernel(PyArrayObject* kernel);
diff --git a/src/c/Python/PyWrapClosure.cpp b/src/c/Python/PyWrapClosure.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b342aad9d4041d3ea0390b3293702bcb432440f
--- /dev/null
+++ b/src/c/Python/PyWrapClosure.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapClosure::docString[] = "imageOut = HIP.Closure(imageIn,kernel,[numIterations],[device])\n\n"\
+"This kernel will dilate followed by an erosion.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapClosure::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+						&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*) PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*) PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		closure(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapCommand.cpp b/src/c/Python/PyWrapCommand.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9c393e2302cf99bdd80528046c51b04eb876f5c
--- /dev/null
+++ b/src/c/Python/PyWrapCommand.cpp
@@ -0,0 +1,118 @@
+// Setup for defining wrapped commands
+#include "PyWrapCommand.h"
+
+#define INSTANCE_COMMANDS
+#include "PyWrapDef.h"
+#include "../WrapCmds/CommandList.h"
+#undef INSTANCE_COMMANDS
+
+#define BUILD_COMMANDS
+#include "PyWrapDef.h"
+#include "../WrapCmds/CommandList.h"
+#undef BUILD_COMMANDS
+
+
+void setupDims(PyArrayObject* im, ImageDimensions& dimsOut)
+{
+	dimsOut.dims = Vec<size_t>(1);
+	dimsOut.chan = 1;
+	dimsOut.frame = 1;
+
+	int numDims = PyArray_NDIM(im);
+	const npy_intp* DIMS = PyArray_DIMS(im);
+
+	for ( int i=0; i < std::min(numDims, 3); ++i )
+		dimsOut.dims.e[i] = (size_t)DIMS[i];
+
+	if ( numDims > 3 )
+		dimsOut.chan = (unsigned int)DIMS[3];
+
+	if ( numDims > 4 )
+		dimsOut.frame = (unsigned int)DIMS[4];
+}
+
+
+template <typename T, typename U>
+void converter(void* in, void* out, size_t len)
+{
+	for ( int i=0; i < len; ++i )
+		((U*)out)[i] = static_cast<U>(((T*)in)[i]);
+}
+
+bool pyarrayToVec(PyObject* ar, Vec<double>& outVec)
+{
+	int ndim = PyArray_NDIM(ar);
+	if ( ndim > 1 )
+		return false;
+
+	int array_size = PyArray_DIM(ar, 0);
+	if ( array_size != 3 )
+		return false;
+
+	if ( PyArray_TYPE(ar) == NPY_UINT8 )
+		converter<uint8_t,double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_UINT16 )
+		converter<uint16_t, double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_INT16 )
+		converter<int16_t, double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_UINT32 )
+		converter<uint32_t, double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_INT32 )
+		converter<int32_t, double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_FLOAT )
+		converter<float, double>(PyArray_DATA(ar), outVec.e, array_size);
+	else if ( PyArray_TYPE(ar) == NPY_DOUBLE )
+		converter<double, double>(PyArray_DATA(ar), outVec.e, array_size);
+
+	return true;
+}
+
+
+bool pylistToVec(PyObject* list, Vec<double>& outVec)
+{
+	Py_ssize_t list_size = PyList_Size(list);
+	if ( list_size != 3 )
+		return false;
+
+	for ( int i=0; i < list_size; ++i )
+	{
+		PyObject* item = PyList_GetItem(list, i);
+		if ( PyLong_Check(item) )
+			outVec.e[i] = PyLong_AsDouble(item);
+		else if ( PyFloat_Check(item) )
+			outVec.e[i] = PyFloat_AsDouble(item);
+		else
+			return false;
+	}
+
+	return true;
+}
+
+
+bool pyobjToVec(PyObject* list_array, Vec<double>& outVec)
+{
+	if ( PyList_Check(list_array) )
+		return pylistToVec(list_array, outVec);
+
+	else if ( PyArray_Check(list_array) )
+		return pyarrayToVec(list_array, outVec);
+
+	return false;
+}
+
+
+// Info Command (unimplemented)
+const char PyWrapInfo::docString[] = "Not implemented for Python bindings.";
+PyObject* PyWrapInfo::execute(PyObject* self, PyObject* args)
+{
+	PyErr_SetString(PyExc_RuntimeWarning, "Info() not implemented for Python!");
+	return nullptr;
+}
+
+// Help Command (unimplemented)
+const char PyWrapHelp::docString[] = "Not implemented for Python bindings.";
+PyObject* PyWrapHelp::execute(PyObject* self, PyObject* args)
+{
+	PyErr_SetString(PyExc_RuntimeWarning, "Help() not implemented for Python!");
+	return nullptr;
+}
\ No newline at end of file
diff --git a/src/c/Python/PyWrapCommand.h b/src/c/Python/PyWrapCommand.h
new file mode 100644
index 0000000000000000000000000000000000000000..9f2ffe328e95d8f9aa297a6c29e142d70fe3401c
--- /dev/null
+++ b/src/c/Python/PyWrapCommand.h
@@ -0,0 +1,67 @@
+#pragma once
+#include <string>
+#include <algorithm>
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/ImageDimensions.cuh"
+
+#include "PyIncludes.h"
+
+
+// Simple template-specialization map for C++ to mex types
+template <typename T> struct TypeMap { static const NPY_TYPES npyType; };
+template <> struct TypeMap<bool> { static const NPY_TYPES npyType = NPY_BOOL; };
+template <> struct TypeMap<char> { static const NPY_TYPES npyType = NPY_INT8; };
+template <> struct TypeMap<short> { static const NPY_TYPES npyType = NPY_INT16; };
+template <> struct TypeMap<int> { static const NPY_TYPES npyType = NPY_INT32; };
+template <> struct TypeMap<unsigned char> { static const NPY_TYPES npyType = NPY_UINT8; };
+template <> struct TypeMap<unsigned short> { static const NPY_TYPES npyType = NPY_UINT16; };
+template <> struct TypeMap<unsigned int> { static const NPY_TYPES npyType = NPY_UINT32; };
+template <> struct TypeMap<float> { static const NPY_TYPES npyType = NPY_FLOAT; };
+template <> struct TypeMap<double> { static const NPY_TYPES npyType = NPY_DOUBLE; };
+
+
+void setupDims(PyArrayObject* im, ImageDimensions& dimsOut);
+bool pyobjToVec(PyObject* list_array, Vec<double>& outVec);
+
+// General array creation method
+template <typename T>
+PyArrayObject* createArray(int ndim, npy_intp* dims)
+{
+	return ((PyArrayObject*)PyArray_SimpleNew(ndim, dims, TypeMap<T>::npyType));
+}
+
+template <typename T>
+void setupImagePointers(PyArrayObject* imageIn, T** image, ImageDimensions& dims, PyArrayObject** argOut = nullptr, T** imageOut = nullptr)
+{
+	setupInputPointers(imageIn, dims, image);
+	if ( argOut != nullptr && imageOut != nullptr )
+		setupOutputPointers(argOut, dims, imageOut);
+}
+
+template <typename T>
+void setupInputPointers(PyArrayObject* imageIn, ImageDimensions& dims, T** image)
+{
+	setupDims(imageIn, dims);
+	*image = (T*)PyArray_DATA(imageIn);
+}
+
+template <typename T>
+void setupOutputPointers(PyArrayObject** imageOut, ImageDimensions& dims, T** image)
+{
+	npy_intp pyDims[5];
+	for ( int i = 0; i < 3; ++i )
+		pyDims[i] = dims.dims.e[i];
+
+	pyDims[3] = dims.chan;
+	pyDims[4] = dims.frame;
+
+	*imageOut = createArray<T>(5, pyDims);
+	*image = (T*)PyArray_DATA(*imageOut);
+
+	memset(*image, 0, sizeof(T)*dims.getNumElements());
+}
+
+
+#include "PyWrapDef.h"
+#include "../WrapCmds/CommandList.h"
diff --git a/src/c/Python/PyWrapDef.h b/src/c/Python/PyWrapDef.h
new file mode 100644
index 0000000000000000000000000000000000000000..8be52ccaa7bf96197630698c8267dda6c63165a6
--- /dev/null
+++ b/src/c/Python/PyWrapDef.h
@@ -0,0 +1,29 @@
+#undef BEGIN_WRAP_COMMANDS
+#undef END_WRAP_COMMANDS
+#undef DEF_WRAP_COMMAND
+
+#if defined(INSTANCE_COMMANDS)
+#define BEGIN_WRAP_COMMANDS
+#define END_WRAP_COMMANDS
+#define DEF_WRAP_COMMAND(name)
+#elif defined(BUILD_COMMANDS)
+#define BEGIN_WRAP_COMMANDS						\
+	struct PyMethodDef hip_methods[] =			\
+	{
+
+#define END_WRAP_COMMANDS						\
+		{nullptr, nullptr, 0, nullptr}			\
+	};
+
+#define DEF_WRAP_COMMAND(name) {#name, PyWrap##name::execute, METH_VARARGS, PyWrap##name::docString},
+#else
+#define BEGIN_WRAP_COMMANDS
+#define END_WRAP_COMMANDS
+#define DEF_WRAP_COMMAND(name)									\
+class PyWrap##name												\
+{																\
+public:															\
+	static PyObject* execute(PyObject* self, PyObject* args);	\
+	static const char docString[];								\
+};
+#endif
diff --git a/src/c/Python/PyWrapDeviceCount.cpp b/src/c/Python/PyWrapDeviceCount.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..da36eb9259555ac45ef75726345f9a0bfbeafcc7
--- /dev/null
+++ b/src/c/Python/PyWrapDeviceCount.cpp
@@ -0,0 +1,27 @@
+#include "PyWrapCommand.h"
+#include "../Cuda/CWrappers.h"
+
+
+const char PyWrapDeviceCount::docString[] = "NumCudaDevices, MemoryStats = HIP.DeviceCount()\n\n"\
+	"This will return the number of Cuda devices available, and their memory.\n"\
+	"\tNumCudaDevices -- this is the number of Cuda devices available.\n"\
+	"\tMemoryStats -- this is an array of structures where each entry corresponds to a Cuda device.\n"\
+	"The memory structure contains the total memory on the device and the memory available for a Cuda call.\n";
+
+PyObject* PyWrapDeviceCount::execute(PyObject* self, PyObject* args)
+{
+	if ( !PyArg_ParseTuple(args, "") )
+		return nullptr;
+
+	size_t* memStats;
+	int numDevices = memoryStats(&memStats);
+
+	PyObject* py_mem = PyList_New(numDevices);
+	for ( int i=0; i < numDevices; ++i )
+	{
+		PyObject* py_struct = Py_BuildValue("{sksk}", "total", memStats[i*2], "available", memStats[i*2+1]);
+		PyList_SetItem(py_mem, i, py_struct);
+	}
+
+	return Py_BuildValue("(kN)", numDevices, py_mem);
+}
diff --git a/src/c/Python/PyWrapDeviceStats.cpp b/src/c/Python/PyWrapDeviceStats.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c8822031e31b6ab18d3a1402b08b9247086ddff
--- /dev/null
+++ b/src/c/Python/PyWrapDeviceStats.cpp
@@ -0,0 +1,45 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/CudaDeviceStats.h"
+
+
+const char PyWrapDeviceStats::docString[] = "DeviceStatsArray = HIP.DeviceStats()\n\n"\
+	"This will return the statistics of each Cuda capable device installed.\n"\
+	"\tDeviceStatsArray -- this is an array of structs, one struct per device.\n"\
+	"The struct has these fields: name, major, minor, constMem, sharedMem, totalMem, tccDriver, mpCount, threadsPerMP, warpSize, maxThreads.\n";
+
+
+PyObject* PyWrapDeviceStats::execute(PyObject* self, PyObject* args)
+{
+	if ( !PyArg_ParseTuple(args, "") )
+		return nullptr;
+
+	DevStats* devStats;
+	int numDevices = deviceStats(&devStats);
+
+	PyObject* py_stats = PyList_New(numDevices);
+	for ( int device = 0; device<numDevices; ++device )
+	{
+		DevStats& dev = devStats[device];
+
+		PyObject* dict = PyDict_New();
+		PyDict_SetItemString(dict, "name", PyStr_FromString(dev.name.c_str()));
+		PyDict_SetItemString(dict, "major", PyLong_FromLong(dev.major));
+		PyDict_SetItemString(dict, "minor", PyLong_FromLong(dev.minor));
+		PyDict_SetItemString(dict, "constMem", PyLong_FromUnsignedLongLong(dev.constMem));
+		PyDict_SetItemString(dict, "sharedMem", PyLong_FromUnsignedLongLong(dev.sharedMem));
+		PyDict_SetItemString(dict, "totalMem", PyLong_FromUnsignedLongLong(dev.totalMem));
+		PyDict_SetItemString(dict, "tccDriver", PyBool_FromLong(dev.tccDriver));
+		PyDict_SetItemString(dict, "mpCount", PyLong_FromLong(dev.mpCount));
+		PyDict_SetItemString(dict, "threadsPerMP", PyLong_FromLong(dev.threadsPerMP));
+		PyDict_SetItemString(dict, "warpSize", PyLong_FromLong(dev.warpSize));
+		PyDict_SetItemString(dict, "maxThreads", PyLong_FromLong(dev.maxThreads));
+
+		PyList_SetItem(py_stats, device, dict);
+	}
+
+	delete[] devStats;
+
+	return py_stats;
+}
diff --git a/src/c/Python/PyWrapElementWiseDifference.cpp b/src/c/Python/PyWrapElementWiseDifference.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..732e3675c2270107f35dcccbfff8c4e18c30804c
--- /dev/null
+++ b/src/c/Python/PyWrapElementWiseDifference.cpp
@@ -0,0 +1,208 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+// TODO: Fixup documentation to clarify subtraction!!!
+const char PyWrapElementWiseDifference::docString[] = "imageOut = HIP.ElementWiseDifference(imageIn1,imageIn2,[device])"\
+"This subtracts the second array from the first, element by element (A-B).\n"\
+"\timage1In = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\timage2In = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.";
+
+
+PyObject* PyWrapElementWiseDifference::execute(PyObject*self, PyObject* args)
+{
+	int device = -1;
+
+	PyObject* imIn1;
+	PyObject* imIn2;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|i", &PyArray_Type, &imIn1, &PyArray_Type, &imIn2,
+							&device) )
+		return nullptr;
+
+	if ( imIn1 == nullptr ) return nullptr;
+	if ( imIn2 == nullptr ) return nullptr;
+
+	PyArrayObject* im1Contig = (PyArrayObject*)PyArray_FROM_OTF(imIn1, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* im2Contig = (PyArrayObject*)PyArray_FROM_OTF(imIn2, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	PyArrayObject* imOut = nullptr;
+
+	ImageDimensions imageDims;
+	if ( PyArray_TYPE(im1Contig) == NPY_BOOL )
+	{
+		bool* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<bool> image1In(image1InPtr, image1Dims);
+		ImageContainer<bool> image2In(image2InPtr, image2Dims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_UINT8 )
+	{
+		unsigned char* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned char> image1In(image1InPtr, image1Dims);
+		ImageContainer<unsigned char> image2In(image2InPtr, image2Dims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_UINT16 )
+	{
+		unsigned short* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned short> image1In(image1InPtr, image1Dims);
+		ImageContainer<unsigned short> image2In(image2InPtr, image2Dims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_INT16 )
+	{
+		short* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<short> image1In(image1InPtr, image1Dims);
+		ImageContainer<short> image2In(image2InPtr, image2Dims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_UINT32 )
+	{
+		unsigned int* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned int> image1In(image1InPtr, image1Dims);
+		ImageContainer<unsigned int> image2In(image2InPtr, image2Dims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_INT32 )
+	{
+		int* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<int> image1In(image1InPtr, image1Dims);
+		ImageContainer<int> image2In(image2InPtr, image2Dims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_FLOAT )
+	{
+		float* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<float> image1In(image1InPtr, image1Dims);
+		ImageContainer<float> image2In(image2InPtr, image2Dims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else if ( PyArray_TYPE(im1Contig) == NPY_DOUBLE )
+	{
+		double* image1InPtr, *image2InPtr, *imageOutPtr;
+
+		ImageDimensions image1Dims;
+		setupInputPointers(im1Contig, image1Dims, &image1InPtr);
+		ImageDimensions image2Dims;
+		setupInputPointers(im2Contig, image2Dims, &image2InPtr);
+
+		imageDims = ImageDimensions(Vec<size_t>::max(image1Dims.dims, image2Dims.dims), MAX(image1Dims.chan, image2Dims.chan), MAX(image1Dims.frame, image2Dims.frame));
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<double> image1In(image1InPtr, image1Dims);
+		ImageContainer<double> image2In(image2InPtr, image2Dims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		elementWiseDifference(image1In, image2In, imageOut, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(im1Contig);
+		Py_XDECREF(im2Contig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(im1Contig);
+	Py_XDECREF(im2Contig);
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapEntropyFilter.cpp b/src/c/Python/PyWrapEntropyFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a6edfe679918cf3b14f227eb261e9cde758253c
--- /dev/null
+++ b/src/c/Python/PyWrapEntropyFilter.cpp
@@ -0,0 +1,179 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapEntropyFilter::docString[] = "imageOut = HIP.EntropyFilter(imageIn,kernel,[device])\n\n"\
+"This calculates the entropy within the neighborhood given by the kernel.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapEntropyFilter::execute(PyObject* self, PyObject* args)
+{
+	int device = -1;
+
+	PyObject* imIn;
+	PyObject* inKern;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+							&device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		entropyFilter(imageIn, imageOut, kernel, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapGaussian.cpp b/src/c/Python/PyWrapGaussian.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f50fb2daaf3c727232f0a525d3d7f6cc4c5381b1
--- /dev/null
+++ b/src/c/Python/PyWrapGaussian.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+
+const char PyWrapGaussian::docString[] = "imageOut = HIP.Gaussian(imageIn,Sigmas,[numIterations],[device])\n\n"\
+"Gaussian smoothing.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tSigmas = This should be an array of three positive values that represent the standard deviation of a Gaussian curve.\n"\
+"\t\tZeros (0) in this array will not smooth in that direction.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapGaussian::execute(PyObject* self, PyObject* args)
+{
+	int device = -1;
+	int numIterations = 1;
+
+	PyObject* imIn;
+	PyObject* inSigmas;
+
+	if ( !PyArg_ParseTuple(args, "O!O|ii", &PyArray_Type, &imIn, &inSigmas, &numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+
+	Vec<double> sigmas;
+	if ( !pyobjToVec(inSigmas, sigmas) )
+	{
+		PyErr_SetString(PyExc_TypeError, "Sigmas must be a 3-element numeric list");
+		return nullptr;
+	}
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		gaussian(imageIn, imageOut, sigmas, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapGetMinMax.cpp b/src/c/Python/PyWrapGetMinMax.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..efdcbf7e76af9ba5912b9c286525bb068f16bdb0
--- /dev/null
+++ b/src/c/Python/PyWrapGetMinMax.cpp
@@ -0,0 +1,141 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapGetMinMax::docString[] = "minValue,maxValue = HIP.GetMinMax(imageIn, [device])\n\n"\
+"This function finds the lowest and highest value in the array that is passed in.\n"\
+"\timageIn = This is a one to five dimensional array.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\tminValue = This is the lowest value found in the array.\n"\
+"\tmaxValue = This is the highest value found in the array.\n";
+
+
+PyObject* PyWrapGetMinMax::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!|i", &PyArray_Type, &imIn, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyObject* outMinMax = PyTuple_New(2);
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyBool_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyBool_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyFloat_FromDouble(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyFloat_FromDouble(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		getMinMax(imageInPtr, imageDims.getNumElements(), minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyFloat_FromDouble(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyFloat_FromDouble(maxVal));
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(outMinMax);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return outMinMax;
+}
diff --git a/src/c/Python/PyWrapHighPassFilter.cpp b/src/c/Python/PyWrapHighPassFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..966f0c09d7de60be43d7632f8f8bfde913bd50cd
--- /dev/null
+++ b/src/c/Python/PyWrapHighPassFilter.cpp
@@ -0,0 +1,154 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapHighPassFilter::docString[] = "imageOut = HIP.Gaussian(imageIn,Sigmas,[device])\n\n"\
+"Filters out low frequency by subtracting a Gaussian blurred version of the input based on the sigmas provided.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tSigmas = This should be an array of three positive values that represent the standard deviation of a Gaussian curve.\n"\
+"\t\tZeros (0) in this array will not smooth in that direction.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapHighPassFilter::execute(PyObject* self, PyObject* args)
+{
+	int device = -1;
+
+	PyObject* imIn;
+	PyObject* inSigmas;
+
+	if ( !PyArg_ParseTuple(args, "O!O|i", &PyArray_Type, &imIn, &inSigmas, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+
+	Vec<double> sigmas;
+	if ( !pyobjToVec(inSigmas, sigmas) )
+	{
+		PyErr_SetString(PyExc_TypeError, "Sigmas must be a 3-element numeric list");
+		return nullptr;
+	}
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		highPassFilter(imageIn, imageOut, sigmas, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapLoG.cpp b/src/c/Python/PyWrapLoG.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a7e9d08a14bc31d63156c98e5ff6faeab9b0f73f
--- /dev/null
+++ b/src/c/Python/PyWrapLoG.cpp
@@ -0,0 +1,171 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapLoG::docString[] = "imageOut = HIP.LoG(imageIn,Sigmas,[device])\n\n"\
+"Apply a Lapplacian of Gaussian filter with the given sigmas.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tSigmas = This should be an array of three positive values that represent the standard deviation of a Gaussian curve.\n"\
+"\t\tZeros (0) in this array will not smooth in that direction.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapLoG::execute(PyObject* self, PyObject* args)
+{
+	int device = -1;
+
+	PyObject* imIn;
+	PyObject* inSigmas;
+
+	if ( !PyArg_ParseTuple(args, "O!O|i", &PyArray_Type, &imIn, &inSigmas, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+
+	Vec<double> sigmas;
+	if ( !pyobjToVec(inSigmas, sigmas) )
+	{
+		PyErr_SetString(PyExc_TypeError, "Sigmas must be a 3-element numeric list");
+		return nullptr;
+	}
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr;
+		float* imageOutPtr;
+
+		setupInputPointers(imContig, imageDims, &imageInPtr);
+		setupOutputPointers(&imOut, imageDims, &imageOutPtr);
+
+		// TODO: Do we really want to use float outputs here?
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		LoG(imageIn, imageOut, sigmas, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapMaxFilter.cpp b/src/c/Python/PyWrapMaxFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..17fa4931c96211f2394728ced5aa41fea26d113b
--- /dev/null
+++ b/src/c/Python/PyWrapMaxFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapMaxFilter::docString[] = "imageOut = HIP.MaxFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will set each pixel/voxel to the max value of the neighborhood defined by the given kernel.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapMaxFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		maxFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapMeanFilter.cpp b/src/c/Python/PyWrapMeanFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..140019a0ec1086f19980e4e83f883b8d13d46e04
--- /dev/null
+++ b/src/c/Python/PyWrapMeanFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+const char PyWrapMeanFilter::docString[] = "imageOut = HIP.MeanFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will take the mean of the given neighborhood.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapMeanFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		meanFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
+
diff --git a/src/c/Python/PyWrapMedianFilter.cpp b/src/c/Python/PyWrapMedianFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..44810ac71bd562a9ec0922e89e6d4ec09aabbec6
--- /dev/null
+++ b/src/c/Python/PyWrapMedianFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapMedianFilter::docString[] = "imageOut = HIP.MedianFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will calculate the median for each neighborhood defined by the kernel.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapMedianFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		medianFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapMinFilter.cpp b/src/c/Python/PyWrapMinFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ceb70296700fe1b427373cee597b6c58966115b3
--- /dev/null
+++ b/src/c/Python/PyWrapMinFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapMinFilter::docString[] = "imageOut = HIP.MinFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will set each pixel/voxel to the max value of the neighborhood defined by the given kernel.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapMinFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		minFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapMinMax.cpp b/src/c/Python/PyWrapMinMax.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..202a58803bc3d6b465b19509603c1998f794e506
--- /dev/null
+++ b/src/c/Python/PyWrapMinMax.cpp
@@ -0,0 +1,166 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+// TODO: This is a superfluous function!!!!
+
+const char PyWrapMinMax::docString[] = "minOut,maxOut = HIP.MinMax(imageIn,[device])\n\n"\
+"This returns the global min and max values.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\tminOut = This is the minimum value found in the input.\n"\
+"\tmaxOut = This is the maximum value found in the input.\n";
+
+PyObject* PyWrapMinMax::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!|i", &PyArray_Type, &imIn, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyObject* outMinMax = PyTuple_New(2);
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr;
+		bool minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyBool_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyBool_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr;
+		unsigned char minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr;
+		unsigned short minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr;
+		unsigned int minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr;
+		int minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyLong_FromLong(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyLong_FromLong(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr;
+		float minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyFloat_FromDouble(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyFloat_FromDouble(maxVal));
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr;
+		double minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+
+		minMax(imageIn, minVal, maxVal, device);
+
+		PyTuple_SetItem(outMinMax, 0, PyFloat_FromDouble(minVal));
+		PyTuple_SetItem(outMinMax, 1, PyFloat_FromDouble(maxVal));
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(outMinMax);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return outMinMax;
+}
diff --git a/src/c/Python/PyWrapMultiplySum.cpp b/src/c/Python/PyWrapMultiplySum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0fd303de43f3c05eba2f833df47d6f8c532a5d31
--- /dev/null
+++ b/src/c/Python/PyWrapMultiplySum.cpp
@@ -0,0 +1,159 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+const char PyWrapMultiplySum::docString[] = "imageOut = HIP.MultiplySum(imageIn,kernel,[numIterations],[device])\n\n"\
+"Multiplies the kernel with the neighboring values and sums these new values.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapMultiplySum::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		multiplySum(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapOpener.cpp b/src/c/Python/PyWrapOpener.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..695e8958df7f8787ec828e271eaf68ff9f492b03
--- /dev/null
+++ b/src/c/Python/PyWrapOpener.cpp
@@ -0,0 +1,161 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapOpener::docString[] = "imageOut = HIP.Opener(imageIn,kernel,[numIterations],[device])\n\n"\
+"This kernel will erode follow by a dilation.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapOpener::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		opener(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
+
diff --git a/src/c/Python/PyWrapStdFilter.cpp b/src/c/Python/PyWrapStdFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a1b2aeef9c34282094de23aab9ed9ff749a745b
--- /dev/null
+++ b/src/c/Python/PyWrapStdFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapStdFilter::docString[] = "imageOut = HIP.StdFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will take the standard deviation of the given neighborhood.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapStdFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		stdFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapSum.cpp b/src/c/Python/PyWrapSum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..950113b0b27095a4b9cbf3404d8699de7e098622
--- /dev/null
+++ b/src/c/Python/PyWrapSum.cpp
@@ -0,0 +1,157 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapSum::docString[] = "valueOut = HIP.Sum(imageIn, [device])\n\n"\
+"This sums up the entire array in.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\tvalueOut = This is the summation of the entire array.\n";
+
+
+PyObject* PyWrapSum::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!|i", &PyArray_Type, &imIn, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageDimensions imageDims;
+	PyObject* outSum = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+
+		size_t outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+
+		size_t outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+
+		size_t outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+
+		long long outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+
+		size_t outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+
+		long long outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyLong_FromLongLong(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+
+		double outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyFloat_FromDouble(outVal);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, minVal, maxVal;
+
+		setupImagePointers(imContig, &imageInPtr, imageDims);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+
+		double outVal = 0;
+		sum(imageIn, outVal, device);
+
+		outSum = PyFloat_FromDouble(outVal);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	return outSum;
+}
diff --git a/src/c/Python/PyWrapVarFilter.cpp b/src/c/Python/PyWrapVarFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..66d2c16fcd770598038e200bc3560e9e23d5c535
--- /dev/null
+++ b/src/c/Python/PyWrapVarFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapVarFilter::docString[] = "imageOut = HIP.VarFilter(imageIn,kernel,[numIterations],[device])\n\n"\
+"This will take the variance deviation of the given neighborhood.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the max neighborhood.\n"\
+"\n"\
+"\tnumIterations (optional) =  This is the number of iterations to run the max filter for a given position.\n"\
+"\t\tThis is useful for growing regions by the shape of the structuring element or for very large neighborhoods.\n"\
+"\t\tCan be empty an array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapVarFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	int numIterations = 1;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|ii", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+		&numIterations, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		varFilter(imageIn, imageOut, kernel, numIterations, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/PyWrapWienerFilter.cpp b/src/c/Python/PyWrapWienerFilter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7a2ebfea039576eb376aeebdf4b4d2779f6f7406
--- /dev/null
+++ b/src/c/Python/PyWrapWienerFilter.cpp
@@ -0,0 +1,160 @@
+#include "PyWrapCommand.h"
+
+#include "../Cuda/Vec.h"
+#include "../Cuda/CWrappers.h"
+#include "../Cuda/ImageDimensions.cuh"
+#include "../Cuda/ImageContainer.h"
+
+#include "PyKernel.h"
+
+
+const char PyWrapWienerFilter::docString[] = "imageOut = HIP.WienerFilter(imageIn,kernel,[tnoiseVariance],[device])\n\n"\
+"A Wiener filter aims to denoise an image in a linear fashion.\n"\
+"\timageIn = This is a one to five dimensional array. The first three dimensions are treated as spatial.\n"\
+"\t\tThe spatial dimensions will have the kernel applied. The last two dimensions will determine\n"\
+"\t\thow to stride or jump to the next spatial block.\n"\
+"\n"\
+"\tkernel (optional) = This is a one to three dimensional array that will be used to determine neighborhood operations.\n"\
+"\t\tIn this case, the positions in the kernel that do not equal zeros will be evaluated.\n"\
+"\t\tIn other words, this can be viewed as a structuring element for the neighborhood.\n"\
+"\t\t This can be an empty array [] and which will use a 3x3x3 neighborhood (or equivalent given input dimension).\n"\
+"\n"\
+"\tnoiseVariance (optional) =  This is the expected variance of the noise.\n"\
+"\t\tThis should be a scalar value or an empty array [].\n"\
+"\n"\
+"\tdevice (optional) = Use this if you have multiple devices and want to select one explicitly.\n"\
+"\t\tSetting this to [] allows the algorithm to either pick the best device and/or will try to split\n"\
+"\t\tthe data across multiple devices.\n"\
+"\n"\
+"\timageOut = This will be an array of the same type and shape as the input array.\n";
+
+
+PyObject* PyWrapWienerFilter::execute(PyObject* self, PyObject* args)
+{
+	PyObject* imIn;
+	PyObject* inKern;
+
+	double noiseVar = -1.0;
+	int device = -1;
+
+	if ( !PyArg_ParseTuple(args, "O!O!|di", &PyArray_Type, &imIn, &PyArray_Type, &inKern,
+							&noiseVar, &device) )
+		return nullptr;
+
+	if ( imIn == nullptr ) return nullptr;
+	if ( inKern == nullptr ) return nullptr;
+
+	PyArrayObject* kernContig = (PyArrayObject*)PyArray_FROM_OTF(inKern, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+	PyArrayObject* imContig = (PyArrayObject*)PyArray_FROM_OTF(imIn, NPY_NOTYPE, NPY_ARRAY_IN_ARRAY);
+
+	ImageContainer<float> kernel = getKernel(kernContig);
+	Py_XDECREF(kernContig);
+
+	if ( kernel.getDims().getNumElements() == 0 )
+	{
+		kernel.clear();
+
+		PyErr_SetString(PyExc_RuntimeError, "Unable to create kernel");
+		return nullptr;
+	}
+
+	ImageDimensions imageDims;
+	PyArrayObject* imOut = nullptr;
+
+	if ( PyArray_TYPE(imContig) == NPY_BOOL )
+	{
+		bool* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<bool> imageIn(imageInPtr, imageDims);
+		ImageContainer<bool> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT8 )
+	{
+		unsigned char* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned char> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned char> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT16 )
+	{
+		unsigned short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned short> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned short> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT16 )
+	{
+		short* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<short> imageIn(imageInPtr, imageDims);
+		ImageContainer<short> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_UINT32 )
+	{
+		unsigned int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<unsigned int> imageIn(imageInPtr, imageDims);
+		ImageContainer<unsigned int> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_INT32 )
+	{
+		int* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<int> imageIn(imageInPtr, imageDims);
+		ImageContainer<int> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_FLOAT )
+	{
+		float* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<float> imageIn(imageInPtr, imageDims);
+		ImageContainer<float> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else if ( PyArray_TYPE(imContig) == NPY_DOUBLE )
+	{
+		double* imageInPtr, *imageOutPtr;
+		setupImagePointers(imContig, &imageInPtr, imageDims, &imOut, &imageOutPtr);
+
+		ImageContainer<double> imageIn(imageInPtr, imageDims);
+		ImageContainer<double> imageOut(imageOutPtr, imageDims);
+
+		wienerFilter(imageIn, imageOut, kernel, noiseVar, device);
+	}
+	else
+	{
+		PyErr_SetString(PyExc_RuntimeError, "Image type not supported.");
+
+		Py_XDECREF(imContig);
+		Py_XDECREF(imOut);
+
+		return nullptr;
+	}
+
+	Py_XDECREF(imContig);
+
+	kernel.clear();
+
+	return ((PyObject*)imOut);
+}
diff --git a/src/c/Python/hip_module.cpp b/src/c/Python/hip_module.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..421b5238e6b091a6bf8822976fac090d633153c7
--- /dev/null
+++ b/src/c/Python/hip_module.cpp
@@ -0,0 +1,28 @@
+#define NUMPY_IMPORT_MODULE
+#include "PyIncludes.h"
+
+// Defined in PyWrapCommand.cpp
+extern struct PyMethodDef hip_methods[];
+
+static struct PyModuleDef hip_moduledef =
+{
+	PyModuleDef_HEAD_INIT,
+	"HIP",
+	PyDoc_STR("Python wrappers for the Hydra Image Processing Library."),
+	-1,
+	hip_methods
+};
+
+
+// Main module initialization entry point
+MODULE_INIT_FUNC(HIP)
+{
+	PyObject* hip_module = PyModule_Create(&hip_moduledef);
+	if ( !hip_module )
+		return nullptr;
+
+	// Support for numpy arrays
+	import_array();
+
+	return hip_module;
+}
diff --git a/src/c/WrapCmds/CommandList.h b/src/c/WrapCmds/CommandList.h
new file mode 100644
index 0000000000000000000000000000000000000000..537601cbe505f2441f57b96de5c54e3970db71a8
--- /dev/null
+++ b/src/c/WrapCmds/CommandList.h
@@ -0,0 +1,27 @@
+// This file is used to register commands that are callable via script languages
+BEGIN_WRAP_COMMANDS
+	// These are default commands defined for all wrapper dlls.
+	DEF_WRAP_COMMAND(Info)
+	DEF_WRAP_COMMAND(Help)
+	DEF_WRAP_COMMAND(DeviceCount)
+	DEF_WRAP_COMMAND(DeviceStats)
+	// Additional specific wrapped commands should be added here.
+	DEF_WRAP_COMMAND(Closure)
+	DEF_WRAP_COMMAND(ElementWiseDifference)
+	DEF_WRAP_COMMAND(EntropyFilter)
+	DEF_WRAP_COMMAND(Gaussian)
+	DEF_WRAP_COMMAND(GetMinMax)
+	DEF_WRAP_COMMAND(HighPassFilter)
+	DEF_WRAP_COMMAND(LoG)
+	DEF_WRAP_COMMAND(MaxFilter)
+	DEF_WRAP_COMMAND(MeanFilter)
+	DEF_WRAP_COMMAND(MedianFilter)
+	DEF_WRAP_COMMAND(MinFilter)
+	DEF_WRAP_COMMAND(MinMax)
+	DEF_WRAP_COMMAND(MultiplySum)
+	DEF_WRAP_COMMAND(Opener)
+	DEF_WRAP_COMMAND(StdFilter)
+	DEF_WRAP_COMMAND(Sum)
+	DEF_WRAP_COMMAND(VarFilter)
+	DEF_WRAP_COMMAND(WienerFilter)
+END_WRAP_COMMANDS