Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions QuickLook.Plugin/QuickLook.Plugin.Hdf5Viewer/Hdf5SummaryBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using HDF5.NET;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QuickLook.Plugin.Hdf5Viewer;

internal static class Hdf5SummaryBuilder
{
private const int MaxDepth = 16;
private const int MaxChildrenPerGroup = 250;
private const int MaxAttributesPerObject = 20;

public static string Build(string path)
{
var sb = new StringBuilder(32 * 1024);

using (var file = H5File.OpenRead(path))
{
sb.AppendLine("HDF5 structure summary");
sb.AppendLine();

AppendObject(file, sb, depth: 0);
}

return sb.ToString();
}

private static void AppendObject(H5Object h5Object, StringBuilder sb, int depth)
{
if (depth > MaxDepth)
{
sb.AppendLine($"{Indent(depth)}... depth limit reached");
return;
}

switch (h5Object)
{
case H5Group group:
AppendGroup(group, sb, depth);
return;
case H5Dataset dataset:
AppendDataset(dataset, sb, depth);
return;
case H5CommitedDatatype committedDatatype:
sb.AppendLine($"{Indent(depth)}[DATATYPE] {SafeName(committedDatatype.Name)}");
return;
case H5UnresolvedLink unresolvedLink:
sb.AppendLine($"{Indent(depth)}[UNRESOLVED] {SafeName(unresolvedLink.Name)}");
return;
default:
sb.AppendLine($"{Indent(depth)}[{h5Object.GetType().Name}] {SafeName(h5Object.Name)}");
return;
}
}

private static void AppendGroup(H5Group group, StringBuilder sb, int depth)
{
sb.AppendLine($"{Indent(depth)}[GROUP] {SafeName(group.Name)}");
AppendAttributes(group, sb, depth + 1);

var children = SafeReadChildren(group).ToList();
var visibleChildren = children.Take(MaxChildrenPerGroup).ToList();

foreach (var child in visibleChildren)
AppendObject(child, sb, depth + 1);

if (children.Count > MaxChildrenPerGroup)
sb.AppendLine($"{Indent(depth + 1)}... {children.Count - MaxChildrenPerGroup} more children");
}

private static void AppendDataset(H5Dataset dataset, StringBuilder sb, int depth)
{
var dimensions = string.Join(" x ", dataset.Space.Dimensions.Select(d => d.ToString()));
var shape = string.IsNullOrWhiteSpace(dimensions) ? "scalar" : dimensions;

sb.AppendLine(
$"{Indent(depth)}[DATASET] {SafeName(dataset.Name)} | " +
$"shape={shape}, dtype={dataset.Type.Class}, itemSize={dataset.Type.Size}B, layout={dataset.Layout.Class}");

AppendAttributes(dataset, sb, depth + 1);
}

private static void AppendAttributes(H5AttributableObject attributable, StringBuilder sb, int depth)
{
var visible = new List<H5Attribute>(MaxAttributesPerObject);
var hasMoreAttributes = false;

try
{
var count = 0;

foreach (var attribute in attributable.Attributes)
{
count++;

if (count <= MaxAttributesPerObject)
{
visible.Add(attribute);
}
else
{
hasMoreAttributes = true;
break;
}
}
}
catch (Exception ex)
{
sb.AppendLine($"{Indent(depth)}@attributes: <error: {ex.Message}>");
return;
}

if (visible.Count == 0)
return;

foreach (var attribute in visible)
{
var dimensions = string.Join(" x ", attribute.Space.Dimensions.Select(d => d.ToString()));
var shape = string.IsNullOrWhiteSpace(dimensions) ? "scalar" : dimensions;

sb.AppendLine(
$"{Indent(depth)}@{SafeName(attribute.Name)} " +
$"(type={attribute.Type.Class}, shape={shape}, itemSize={attribute.Type.Size}B)");
}

if (hasMoreAttributes)
sb.AppendLine($"{Indent(depth)}... more attributes");
}

private static IEnumerable<H5Object> SafeReadChildren(H5Group group)
{
try
{
return group.Children;
}
catch
{
return Array.Empty<H5Object>();
}
}

private static string SafeName(string name)
{
return string.IsNullOrEmpty(name) ? "/" : name;
}

private static string Indent(int level)
{
return new string(' ', level * 2);
}
}
32 changes: 32 additions & 0 deletions QuickLook.Plugin/QuickLook.Plugin.Hdf5Viewer/Hdf5TextPanel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Windows.Controls;
using System.Windows.Media;

namespace QuickLook.Plugin.Hdf5Viewer;

public sealed class Hdf5TextPanel : UserControl
{
private readonly TextBox _textBox;

public Hdf5TextPanel()
{
_textBox = new TextBox
{
IsReadOnly = true,
TextWrapping = System.Windows.TextWrapping.NoWrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
FontFamily = new FontFamily("Consolas"),
FontSize = 13,
BorderThickness = new System.Windows.Thickness(0),
Padding = new System.Windows.Thickness(12, 8, 12, 8)
};

Content = _textBox;
}

public void SetText(string text)
{
_textBox.Text = text ?? string.Empty;
_textBox.CaretIndex = 0;
}
}
109 changes: 109 additions & 0 deletions QuickLook.Plugin/QuickLook.Plugin.Hdf5Viewer/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using QuickLook.Common.Plugin;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace QuickLook.Plugin.Hdf5Viewer;

public sealed class Plugin : IViewer
{
private static readonly byte[] Hdf5Signature = { 0x89, 0x48, 0x44, 0x46, 0x0D, 0x0A, 0x1A, 0x0A };
private static readonly string[] SupportedExtensions = { ".h5", ".hdf5", ".hdf", ".he5" };
private static readonly long[] SignatureProbeOffsets = { 0, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288 };
private Hdf5TextPanel _panel;

public int Priority => 0;

public void Init()
{
}

public bool CanHandle(string path)
{
if (Directory.Exists(path))
return false;

var extension = Path.GetExtension(path);
if (!SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
return false;

return HasHdf5Signature(path);
}

public void Prepare(string path, ContextObject context)
{
context.PreferredSize = new Size { Width = 1100, Height = 760 };
}

public void View(string path, ContextObject context)
{
_panel = new Hdf5TextPanel();
context.ViewerContent = _panel;
context.Title = Path.GetFileName(path);
context.IsBusy = true;

var panel = _panel;

Task.Run(() =>
{
try
{
return Hdf5SummaryBuilder.Build(path);
}
catch (Exception ex)
{
return
$"Failed to open HDF5 file.{Environment.NewLine}{Environment.NewLine}" +
$"{ex.GetType().Name}: {ex.Message}";
}
}).ContinueWith(t =>
{
if (panel is not null)
panel.SetText(t.Result);

context.IsBusy = false;
}, TaskScheduler.FromCurrentSynchronizationContext());
}

public void Cleanup()
{
_panel = null;
}

private static bool HasHdf5Signature(string path)
{
try
{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (stream.Length < Hdf5Signature.Length)
return false;

var header = new byte[Hdf5Signature.Length];

foreach (var offset in SignatureProbeOffsets)
{
if (offset + Hdf5Signature.Length > stream.Length)
break;

stream.Position = offset;

var read = stream.Read(header, 0, header.Length);
if (read != header.Length)
continue;

if (header.SequenceEqual(Hdf5Signature))
return true;
}
}
}
catch
{
return false;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net462</TargetFramework>
<RootNamespace>QuickLook.Plugin.Hdf5Viewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.Hdf5Viewer</AssemblyName>
<FileAlignment>512</FileAlignment>
<SignAssembly>false</SignAssembly>
<UseWPF>true</UseWPF>
<LangVersion>latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<ProjectGuid>{69D60E22-9190-4433-9A6E-1D889CF5CA52}</ProjectGuid>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.Hdf5Viewer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.Hdf5Viewer\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Build\Debug\QuickLook.Plugin\QuickLook.Plugin.Hdf5Viewer\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Build\Release\QuickLook.Plugin\QuickLook.Plugin.Hdf5Viewer\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="HDF5.NET" Version="1.0.0-alpha.22" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\QuickLook.Common\QuickLook.Common.csproj">
<Project>{85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}</Project>
<Name>QuickLook.Common</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\GitVersion.cs">
<Link>Properties\GitVersion.cs</Link>
</Compile>
</ItemGroup>

</Project>
Loading