Lie Analysis Documentation

Based on the latest implementations of E.J. Bekkers <e.j.bekkers@tue.nl> and M.H.J. Janssen <M.H.J.Janssen@tue.nl>, 
based on previous implementations by E.M. Franken and R. Duits <r.duits@tue.nl>.
Theory by R. Duits <r.duits@tue.nl>
Refactored, updated and extended by F.C. Martin <f.c.martin@tue.nl>

Version 0.2.1 - August 2016
University of Technology Eindhoven, Copyrights 2016

LieAnalysis Package

Literature

[1]	Title: A New Retinal Vessel Tracking Method Based on Orientation Scores
	Authors: E.J. Bekkers, R. Duits, B.M. ter Haar Romeny and T. Berendschot
  	 Short Description:  Multi-orientation analysis on retinal images using the Euclidean motion group SE(2).
	 Related Functions: OrientationScoreTransform2D, CakeWaveletStack

[2]	Title: Image Analysis and Reconstruction using a Wavelet Transform Constructed from a Reducible Representation of the Euclidean Motion Group
	Authors: R. Duits, M. Felsberg, G. GranLund and B.M. ter Haar Romeny
  	 Short Description:  Construction and reconstruction of the Euclidean motion groups SE(2) and SE(3).
	 Related Functions: OrientationScoreTransform2D, OrientationScoreTransform3D, CakeWaveletStack2D, CakeWaveletStack3D

[3]	Title: Invertible Orientation Scores of 3D Images
	Authors: M.H.J. Janssen, R. Duits and M. Breeuwer
  	 Short Description:  Construction and reconstruction of the Euclidean motion group SE(3). Containing some practical examples.
	 Related Functions: OrientationScoreTransform3D, CakeWaveletStack3D

[4]	Title: Invertible Orientation Scores as an Application of Generalized Wavelet Theory
	Authors: R. Duits, M. Duits, M. van Almsick, B.M. ter Haar Romeny
  	 Short Description: Construction and reconstruction of the Euclidean motion group SE(2). 
	 Related Functions: CakeWaveletStack, OrientationScoreTransform2D

[5]	Title: Left-invariant Evolutions of Wavelet Transforms on the Similitude Group
	Authors: U. Sharma, R. Duits
  	 Short Description:  Construction and reconstruction of the similtude group SIM(2), also containing scale.
	 Related Functions: to be implemented in future releases

[6]	Title: T.B.A (PhD thesis, 2016)
	Authors: E.J. Bekkers
  	 Short Description: PhD Thesis, Orientation Scores SE(2), Lie Group Theory and applications of the SE(2) on retinal images.
	 Related Functions: GaborWaveletStack2D, CakeWaveletStack2D, OrientationScoreTransform2D

[7]	Title: Enhancement of Crossing Elongated Structures in Images
	Authors: E.M. Franken
  	 Short Description: PhD Thesis, Construction and reconstruction of SE(2). 
	 Related Functions: GaborWaveletStack2D, CakeWaveletStack2D, OrientationScoreTransform2D

[8]	Title: Left-Invariant Parabolic Evolutions on SE(2) and Contour Enhancement via Invertible Orientation Scores, Part I
	Authors: R. Duits and E.M. Franken
  	 Short Description: Invertible orientation score theory and connection to SE(2) Fourier transform.
	 Related Functions: OrientationScoreTransform2D, CakeWaveletStack

[9]	Title: Context Models of Lines and Contours
	Authors: M. van Almsick
  	 Short Description: Wavelets in harmonic oscillator basis.

[10]	Title: Perceptual Organization in Image Analysis
	Authors: R. Duits
  	 Short Description: Invertible orientation score theory.

[11]	Title: temp
	Authors: temp
  	 Short Description: temp
	 Related Functions: temp

[11]	Kalitzin wavelets in harmonic oscillator basis.

(NB.  In preparation: fully analytic description cake wavelets in generalizedc Zernike basis based on inverse Fourier transform rather than inverse DFT)

Classes.m

BeginPackage["LieAnalysis`Classes`Classes`"];

Unprotect[
  ClearElement,
  ConvertClass,
  DeclareClass,
  DeclareInvariant,
  ElementQ,
  GetElement,
  GetProperty,
  ValidQ,
  PropertyQ,
  SetElement,
  SubpropertyQ
];

(*Usage messages*)
ClearElement::usage =
    "ClearElement[instance, elem] returns a copy of instance with the \
value of the element elem reverted to its default.";
ConvertClass::usage = "ConvertClass[instance, class] tries to convert \
instance to the class class by selecting the elements of instance \
that are class elements of class, and changing the head of the \
expression.";
DeclareClass::usage =
    "DeclareClass[superclass, class, {e_1 -> d_1, e_2 -> d_2, \
…}] declares a class class with structure inherited from \
superclass and with elements e_i and corresponding defaults d_i.";
DeclareInvariant::usage =
    "DeclareInvariant[class, invariant] declares the invariant for the \
class class based on the pure function invariant.";
ElementQ::usage =
    "ElementQ[class, element] checks whether element exists for class.";
GetElement::usage =
    "GetElement[instance, e] returns the value associated with the \
element e in instance.";
GetProperty::usage =
    "GetProperty[instance, prop] requests the property prop from \
instance.";
ValidQ::usage =
    "ValidQ[expr, class] tests whether expr satisfies the invariant of \
class.";
PropertyQ::usage =
    "PropertyQ[class, property] checks whether property exists for \
class.";
SetElement::usage =
    "SetElement[instance, e -> val] returns a copy of instance with the \
value of the elem e changed to val.";
SubpropertyQ::usage =
    "SubpropertyQ[class, property, subproperty] checks whether there \
exists a subproperty of property for class matching subproperty.";

(*Messages*)
ConvertClass::failed = "Failed to convert to target class `1`";
DeclareClass::nvclss =
    "The symbol `1` is not free of associated definitions.";
DeclareClass::ilelem =
    "One or more illegal elements supplied to function.";
DeclareClass::ilform =
    "Ill-formed invariant supplied to the option \"Invariant\". \
Invariants should be presented as pure functions.";
DeclareClass::ilinv =
    "The supplied default elements `1` do not satisfy the class \
invariants of `2` or one of its superclasses.";
DeclareInvariant::ilinv =
    "The default elements of `1` do not satisfy the supplied class \
invariant.";
General::subpx =
    "No definition/value was found for the property `1` in combination \
with the supplied subproperties `2`.";
General::propx = "No definition/value was found for the property `1`.";
ValidQ::noinv = "No invariant is defined for the class `1`.";
ValidQ::exfail =
    "The expression fails to satisfy the invariant of the class `1`.";
ValidQ::elfail =
    "The element `1` fails to satisfy the invariant of the class `2`; \
the element should `3`.";
SetElement::fail = "The set operation failed.";

(*Turn off messages*)
Off[ValidQ::noinv];

(*Options*)
Options[DeclareClass] = {"Invariant" -> None};
Options[ValidQ] = {"CheckParents" -> True, "Verbose" -> False};

(*Syntax*)
SyntaxInformation[ClearElement] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[ConvertClass] = {"ArgumentsPattern" -> {_, _, _}};
SyntaxInformation[
  DeclareClass] = {"ArgumentsPattern" -> {_, _., _,
  OptionsPattern[]}};
SyntaxInformation[DeclareInvariant] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[ElementQ] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[GetElement] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[
  GetProperty] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}};
SyntaxInformation[
  ValidQ] = {"ArgumentsPattern" -> {_, _., OptionsPattern[]}};
SyntaxInformation[PropertyQ] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[SubpropertyQ] = {"ArgumentsPattern" -> {_, _, __}};
SyntaxInformation[SetElement] = {"ArgumentsPattern" -> {_, ___}};

Begin["`Private`"];

(*$Classes variable instantiation*)
Module[{protected},

  Internal`WithLocalSettings[

    protected = Unprotect[$Classes],

    ClearAll[$Classes];
    $Classes[] = {},

    Protect @@ protected

  ]

];

(*Elisions icon*)
$ElisionsIcon = Graphics[
  {
    GrayLevel[0.5],
    Polygon[{{0.85, 0.15}, {0.15, 0.5}, {0.85, 0.85}, {0.4, 0.5}}]
  },
  PlotRange -> {{-0.05, 1.05}, {-0.05, 1.05}},
  Background -> GrayLevel[0.93],
  FrameStyle -> Directive[Thickness[Tiny], GrayLevel[0.7]],
  Axes -> False,
  AspectRatio -> 1,
  ImageSize ->
      Dynamic[{Automatic, (3.5*CurrentValue["FontCapHeight"])/
          AbsoluteCurrentValue[Magnification]}],
  Frame -> True,
  FrameTicks -> None,
  FrameStyle ->
      Directive[Opacity[0.5], Thickness[Tiny],
        RGBColor[0.368417, 0.506779, 0.709798]]
];

(*Test to check for associated values*)
NoValueQ[symbol_Symbol] := SameQ[
  Attributes[symbol],
  DownValues[symbol],
  FormatValues[symbol],
  NValues[symbol],
  OwnValues[symbol],
  SubValues[symbol],
  UpValues[symbol],
  {}
];
NoValueQ[___] := False;

(*Element operations*)
ClearElement[class_Symbol[elems : OptionsPattern[]], elem_String] :=
    class @@ FilterRules[{elems}, Except[elem]];
GetElement[class_Symbol[elems : OptionsPattern[]], elem_String] :=
    Quiet[OptionValue[class, {elems}, elem], OptionValue::nodef];
SetElement[class_Symbol[elems : OptionsPattern[]],
  opts : OptionsPattern[]] :=
    Module[{output = class[{opts, elems}]}, output /; ValidQ[output]];
SetElement[_, OptionsPattern[]] := Null /; Message[SetElement::fail];

(*Element existence check*)
ElementQ[class_Symbol, element_String] :=
    MatchQ[element, Alternatives @@ class[]["ClassElements"]];

(*Property evaluation*)
GetProperty[instance_?ValidQ, prop_String] := instance[prop];
expr : GetProperty[instance_?ValidQ, prop_String,
  opts__?OptionQ] := (Message[GetProperty::optx, {opts},
  HoldForm[expr]]; instance[prop]);
GetProperty[instance_?ValidQ, prop : {_String, ___}] :=
    instance @@ prop;
expr : GetProperty[instance_?ValidQ, prop : {_String, ___},
  opts__?OptionQ] := (Message[GetProperty::optx, {opts},
  HoldForm[expr]]; instance @@ prop);


(*Property existence checks*)
PropertyQ[class_Symbol, property_String] :=
    MatchQ[property, Alternatives @@ class[]["ClassProperties"]];
SubpropertyQ[class_Symbol, property_String, subprop__] :=
    MatchQ[Hold[subprop],
      Alternatives @@ class[]["ClassProperties", property]];

(*Default instance validation*)
ValidQ[instance : class_Symbol[___], opts : OptionsPattern[]] :=
    ValidQ[instance, SymbolName[class], opts];
ValidQ[instance : class_Symbol[___], className_String,
  opts : OptionsPattern[]] := (

  Message[ValidQ::noinv, className];

  And[

    MemberQ[$Classes[], className],
    MemberQ[$Classes[], SymbolName[class]],(**)
    Which[

      instance["ClassPrimary", "String"] === className,
      True,

      OptionValue["CheckParents"],
      ValidQ[instance, Symbol[className][]["ClassBase", "String"],
        opts],

      True,
      ValidQ[instance, instance["ClassPrimary", "String"], opts]

    ]

  ]

);
ValidQ[___] := False;

(*Invariant declaration*)
DeclareInvariant[className_String, qFun_Function] :=
    Module[{classSymbol = Symbol[className]},

    (*Check if the class is defined yet,
   and if so whether the default elements satisfy the invariant*)
      If[! NoValueQ[classSymbol] && ! qFun[classSymbol[]],
        Message[DeclareInvariant::ilinv, className]; Return[$Failed]];

      Module[{protected},

        Internal`WithLocalSettings[

          protected = Unprotect[ValidQ],

        (*Add definition for ValidQ*)
          ValidQ[(instance : class_Symbol[___]), className,
            opts : OptionsPattern[]] /;
              MemberQ[$Classes[], className] &&
                  MemberQ[$Classes[], SymbolName[class]] := Which[

            instance["ClassPrimary", "String"] === className,
            True,

            OptionValue[ValidQ, opts, "CheckParents"],

            ValidQ[instance, Symbol[className][]["ClassBase", "String"],
              opts],

            True,
            ValidQ[instance, instance["ClassPrimary", "String"], opts]

          ] &&
              If[OptionValue["Verbose"], Identity, Quiet] @
                  Check[qFun[instance] /.
                      False :> (Message[ValidQ::exfail, className]; False),
                    False, ValidQ::elfail],

          Protect @@ protected

        ]

      ]

    ];

(*Class declaration*)
DeclareClass[class_Symbol?NoValueQ, elemList : {(_String -> _) ...},
  opts : OptionsPattern[]] := Module[
  {
    className = SymbolName[class],
    elemNames = Cases[elemList, (str_String -> _) :> str],
    qFun = OptionValue["Invariant"]
  },

(*Check if all supplied elements are legal*)
  If[
    Not@
        FreeQ[elemNames,
          "Class" | "ClassBase" | "ClassChildren" | "ClassElements" |
              "ClassParents" | "ClassPrimary" | "ClassProperties" |
              "ClassReference" | "ClassTree"],
    Message[DeclareClass::ilelem];
    Return[$Failed]
  ];

  (*Declare invariant if supplied*)
  Switch[
    qFun,

    _Function,
    If[DeclareInvariant[className, qFun] === $Failed,
      Return[$Failed]],

    Except[None],
    Message[DeclareClass::ilform]; Return[$Failed]

  ];

  (*Check validity of the invariant/elements*)
  Block[{$Classes = {className, "tempClass"} &, tempClass},

    Options[tempClass] =
        Prepend[elemList, "ClassReference" -> class];
    _tempClass["ClassPrimary", "String"] = className;

    If[
      Not@TrueQ[
        ValidQ[tempClass[], className, "CheckParents" -> False]],
      Message[DeclareClass::ilinv, elemList, className];
      Return[$Failed]]

  ];

  (*Register class*)
  Module[{protected},

    Internal`WithLocalSettings[

      protected = Unprotect[$Classes],

      AppendTo[$Classes[], className];

      $Classes[className] = {},

      Protect @@ protected

    ]

  ];

  (*Set class default elements*)
  Options[class] = Prepend[elemList, "ClassReference" -> class];

  (*Set class usage*)
  class::usage =
      className <> "[args] represents an instance of the class " <>
          className <> ", with elements stored in the arguments.";

  Begin["`Private`"];

  (*Set primary-specific class properties*)
  _class["Class"] = class;
  _class["Class", "Description"] =
      "Information on the class type.";
  _class["Class", "String"] = className;
  _class["Class", "Symbol"] = class;
  _class["ClassBase"] = Null;
  _class["ClassBase", "Description"] = "The direct parent class.";
  _class["ClassBase", "String"] = "";
  _class["ClassBase", "Symbol"] = Null;
  _class["ClassChildren"] :=
      Rest@VertexOutComponent[Graph[$Classes[className]], className];
  _class["ClassChildren", "Description"] =
      "The list of class children.";
  _class["ClassElements"] =
      Sort@PrependTo[elemNames, "ClassReference"];
  _class["ClassElements", "Description"] =
      "A list of possible elements that can be included in objects of \
the class.";
  _class["ClassInvariant"] =
      With[{str = className}, ValidQ[#, str, "CheckParents" -> False] &];
  _class["ClassInvariant", "Description"] =
      "A function that returns true if an expression doesn't break the \
invariant of the class.";
  _class["ClassProperties"] :=
      Cases[Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
        1], (Verbatim[Pattern][_, Verbatim[_class]] | Verbatim[_class])[
        string_String] :> string, Infinity];
  _class["ClassProperties", "Description"] =
      "A list of requestable properties.";
  _class["ClassProperties", prop_String] :=
      DeleteDuplicates@
          Cases[Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
            1], _[prop, x___] :> Hold[x], Infinity];
  _class["ClassParents"] = {};
  _class["ClassParents", "Description"] =
      "The list of class parents.";
  _class["ClassParents", "String"] = {};
  _class["ClassParents", "Symbol"] = {};
  _class["ClassPrimary"] = class;
  _class["ClassPrimary", "Description"] =
      "The primary parent class.";
  _class["ClassPrimary", "String"] = className;
  _class["ClassPrimary", "Symbol"] = class;
  _class["ClassTree"] :=
      TreeGraph[$Classes[className], VertexLabels -> "Name",
        ImagePadding -> 20, GraphLayout -> "LayeredDigraphEmbedding"];
  _class["ClassTree", "Description"] =
      "A tree graph showing the hierarchy to which the class belongs.";

  _class["ClassReference", "Description"] =
      "The symbol associated with the object class. Its value is \
retained when passing class elements to parents for property \
evaluation.";

  Module[{guard = True},

    (input : _class[___]) /; guard :=
        Block[{guard = False, result},

          result = Quiet[input, {class::propx, class::subpx}];

          result /; result =!= Unevaluated[input]

        ]

  ];
  _class[str_String] := Null /; Message[class::propx, str];
  _class[str_String, subprops__] :=
      Null /; Message[class::subpx, str, {subprops}];

  (*Set element properties*)
  With[{elem = #},

    instance_class[elem] := GetElement[instance, elem];

    class /: Part[instance_class, elem, args___] := Module[
      {

        full = Append[List @@ instance, elem -> instance[elem]]
      },

      With[
        {
          full = full,
          pos = Position[full, elem][[1, 1]]
        },

        Unevaluated[Part[full, pos, 2]]

      ]

    ]

  ] & /@ elemNames;

  (*Split lists of properties*)
  instance_class[properties : {_String ..}] :=
      instance /@ properties;

  (*Define default instance format*)
  MakeBoxes[input : class[elems : OptionsPattern[]], fmt_] ^:=
      With[{ref = input["ClassReference"]},

        Module[{instance = ref[elems], alwaysGrid, sometimesGrid},

          alwaysGrid = {

            BoxForm`MakeSummaryItem[{"Primary: ",
              instance["ClassPrimary", "String"]}, fmt],

            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {

            BoxForm`MakeSummaryItem[{"Elements: ",
              ElisionsDump`expandablePane[
                instance["ClassElements"] /.
                    Thread[Thread[x : Flatten[{elems}][[All, 1]]] :>
                        Style[x, Bold]]]}, fmt],

            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]},
              fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ref,

            ToString[ref] <>
                "[<>]", (*Ideally this should be adapted in the output form \
when BoxForm`UseTextFormattingQ is True.*)
            $ElisionsIcon,
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False

          ]

        ]

      ];

  End[];

  (*Set class attributes*)
  Attributes[class] = {Protected, ReadProtected};

];
DeclareClass[class_Symbol, {(_String -> _) ...}, OptionsPattern[]] :=
    Null /; Message[DeclareClass::nvclss, ToString@class];

DeclareClass[base_Symbol /; MemberQ[$Classes[], SymbolName@base],
  class_Symbol?NoValueQ, elemList : {(_String -> _) ...},
  opts : OptionsPattern[]] := Module[
  {
    basename = SymbolName[base],
    className = SymbolName[class],
    elemNames = Cases[elemList, (str_String -> _) :> str],
    parentNames,
    parents = Append[base[]["ClassParents"], base],
    primary = base[]["ClassPrimary"],
    primaryName = base[]["ClassPrimary", "String"],
    qFun = OptionValue["Invariant"]
  },

  parentNames =
      Append[base[]["ClassParents", "String"], basename];

  (*Check if all supplied elements are legal*)(*FIX*)
  If[
    Not@
        FreeQ[elemNames,
          "Class" | "ClassBase" | "ClassChildren" | "ClassElements" |
              "ClassParents" | "ClassPrimary" | "ClassProperties" |
              "ClassReference" | "ClassTree"],
    Message[DeclareClass::ilelem]; Return[$Failed]
  ];

  (*Declare invariant if supplied*)
  Switch[
    qFun,

    _Function,
    If[DeclareInvariant[className, qFun] === $Failed,
      Return[$Failed]],

    Except[None],
    Message[DeclareClass::ilform]; Return[$Failed]

  ];

  (*Check validity of the invariant/elements*)
  Block[{$Classes =
      Evaluate[Join[$Classes[], {className, "tempClass"}]] &,
    tempClass},


    Options[tempClass] =
        Join[Prepend[elemList, "ClassReference" -> class],
          Options[base]];
    _tempClass["ClassPrimary", "String"] = className;

    If[
      And[

        Not@TrueQ[
          ValidQ[tempClass[], className, "CheckParents" -> False]],
        Not@TrueQ[base["ClassInvariant"][class[Options[class]]]]
      ],
      Message[DeclareClass::ilinv, elemList, className];
      Return[$Failed]
    ]

  ];

  (*Store class name*)
  Module[{protected},

    Internal`WithLocalSettings[

      protected = Unprotect[$Classes],

      AppendTo[$Classes[], className];

      AppendTo[$Classes[primaryName],
        basename \[DirectedEdge] className],

      Protect @@ protected

    ]

  ];

  (*Set class default elements*)
  Options[class] =
      Join[FilterRules[Options[base],
        Except@PrependTo[elemNames, "ClassReference"]],
        Prepend[elemList, "ClassReference" -> class]];

  (*Set class usage*)
  class::usage =
      className <> "[args] represents an instance of the class " <>
          className <> ", with elements stored in the arguments.";

  Begin["`Private`"];

  (*Set class properties*)
  _class["Class"] = class;
  _class["Class", "String"] = className;
  _class["Class", "Symbol"] = class;
  _class["ClassChildren"] :=
      Rest@VertexOutComponent[Graph[$Classes[primaryName]], className];
  _class["ClassElements"] =
      Join[Cases[
        FilterRules[Options[base],
          Except[elemNames]], (str_String -> _) :> str], elemNames];
  _class["ClassInvariant"] =
      With[{str = className}, ValidQ[#, str, "CheckParents" -> False] &];
  _class["ClassProperties"] :=
      With[{baseprops = base[]["ClassProperties"]},

        Join[

          DeleteDuplicates@
              Cases[Cases[
                Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
                  1], _[x_String, ___] :> x, Infinity],
                Except[Alternatives @@ baseprops]],
          baseprops
        ]

      ];
  _class["ClassProperties", prop_String] :=
      With[{baseprops = base[]["ClassProperties", prop]},

        Join[

          DeleteDuplicates@
              Cases[Cases[
                Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
                  1], _[prop, x___] :> Hold[x], Infinity],
                Except[Alternatives @@ Verbatim /@ baseprops]],
          baseprops
        ]

      ];
  _class["ClassBase"] = base;
  _class["ClassBase", "String"] = basename;
  _class["ClassBase", "Symbol"] = base;
  _class["ClassParents"] = parents;
  _class["ClassParents", "String"] = parentNames;
  _class["ClassParents", "Symbol"] = parents;
  _class["ClassPrimary"] = primary;
  _class["ClassPrimary", "String"] = primaryName;
  _class["ClassPrimary", "Symbol"] = primary;

  (*Set element properties*)
  With[{elem = #},

    instance_class[elem] := GetElement[instance, elem];

    class /: Part[instance_class, elem, args___] := Module[
      {

        full = Append[List @@ instance, elem -> instance[elem]]
      },

      With[
        {
          full = full,
          pos = Position[full, elem][[1, 1]]
        },

        Unevaluated[Part[full, pos, 2]]

      ]

    ]

  ] & /@ elemNames;

  (*Forward unknown properties to parent*)
  Module[{guard},

    (input : class[elems___][str_String, subprops___]) /; !
        TrueQ[guard[Unevaluated@input]] := Block[{guard, result},

      guard[Unevaluated@input] = True;

      result = Quiet[input, {class::propx, class::subpx}];

      If[
        result =!= Unevaluated[input],
        result,
        base[elems, Options[class]][str, subprops]
      ]

    ]

  ];

  (*Split lists of properties*)
  instance_class[properties : {_String ..}] :=
      instance /@ properties;

  (*Define default instance format*)
  (*MakeBoxes[instance : class[elems : OptionsPattern[]],
   fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

   		    	alwaysGrid = {
   		   			BoxForm`MakeSummaryItem[{"Primary: ", instance[
   "ClassPrimary", "String"]}, fmt],
   		   			BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
   		   		};

   		    	sometimesGrid = {
   		   			BoxForm`MakeSummaryItem[{"Elements: ",
   ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[
   Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
   		   			BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[
   instance]}, fmt]
   		   		};

   		    	BoxForm`ArrangeSummaryBox[
   			   		class,
   			   		ToString[class] <>
   "[<>]", (*Ideally this should be adapted in the output form when \
BoxForm`UseTextFormattingQ is True.*)
   			   		$ElisionsIcon,
   			   		alwaysGrid,
   			   		sometimesGrid,
   			   		fmt,
   			   		"Interpretable" -> False

   		    	]

   		   	];*)

  Format[class[elems : OptionsPattern[]]] ^:=
      base[elems, Options[class]];

  End[];

  (*Set class attributes*)
  Attributes[class] = {Protected, ReadProtected};

];
DeclareClass[base_Symbol /; MemberQ[$Classes[], SymbolName@base],
  class_Symbol, {(_String -> _) ...}, OptionsPattern[]] :=
    Null /; Message[DeclareClass::nvclss, ToString@class];

(*Class conversion*)
ConvertClass[
  instance : _Symbol[elems : OptionsPattern[]] /;
      ValidQ[instance], {newElems : OptionsPattern[]}, class_String] :=
    Module[
      {
        targetSymbol = Symbol[class]
      },

      SetElement[
        targetSymbol[
          FilterRules[{newElems, elems},
            targetSymbol[]["ClassElements"]]], "Source" -> instance]

    ];
ConvertClass[instance_ ?ValidQ, class_String] :=
    ConvertClass[instance, {}, class];

(*Bulletproofing*)
SetAttributes[
  {
    ClearElement,
    ConvertClass,
    DeclareClass,
    DeclareInvariant,
    ElementQ,
    GetElement,
    GetProperty,
    ValidQ,
    PropertyQ,
    SetElement,
    SubpropertyQ
  },
  {Protected, ReadProtected}
];

End[];

EndPackage[];

ObjCakeWavelet

(*************************************************************************************************************************
** ObjCakeWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjCakeWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`OrientationScores3D`Visualisation`",
  "LieAnalysis`Classes`ObjOrientationWavelet3D`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjOrientationWavelet3D, ObjCakeWavelet3D,
  {
    "DcFilter"                        -> Null,
    "RadialWindow"                    -> Null,
    "WaveletsF"                       -> Null,
    "Topology"                        -> Null,
    "OrientationList"                 -> {},
    "Method"                          -> "JMIV",
    "LMax"                            -> 1,
    "SigmaAngle"                      -> 0.3,
    "DcStandardDeviation"             -> 8,
    "MnStandardDeviation"             -> Automatic,
    "Gamma"                           -> 0.6,
    "Epsilon"                         -> 10^(-5)
  }
];



Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareInvariant["ObjCakeWavelet3D",

    (* Validate Elements *)
    Block[

      {
        dcfilter            = GetElement[#, "DcFilter"],
        method              = GetElement[#, "Method"],
        lmax                = GetElement[#, "LMax"],
        sigmaangle          = GetElement[#, "SigmaAngle"],
        dcstd               = GetElement[#, "DcStandardDeviation"],
        mnstd               = GetElement[#, "MnStandardDeviation"],
        gamma               = GetElement[#, "Gamma"],
        epsilon             = GetElement[#, "Epsilon"],
        topology            = GetElement[#, "Topology"]
        (*shcoefficients      = GetElement[#, "SphericalHarmonicsCoefficients"]*)
      },


      And[

        (MatchQ[dcfilter,{{{___},___},___}] || dcfilter === Null)
          /. False :> (Message[ValidQ::elfail, "DcFilter", "be a 3D Matrix"]; False),

        MatchQ[method,"JMIV"]
          /. False :> (Message[ValidQ::elfail, "Method", "be JMIV or ..."]; False),

        Internal`PositiveIntegerQ[lmax]
          /. False :> (Message[ValidQ::elfail, "LMax", "be a positive integer"]; False),

        Positive[sigmaangle]
          /. False :> (Message[ValidQ::elfail, "SigmaAngle", "be a positive real number"]; False),

        Positive[dcstd]
          /. False :> (Message[ValidQ::elfail, "DcStandardDeviation", "be a positive real number"]; False),

        (Positive[mnstd] || mnstd === Automatic)
          /. False :> (Message[ValidQ::elfail, "MnStandardDeviation", "be a positive real number"]; False),

        (gamma > 0 && gamma < 1)
          /. False :> (Message[ValidQ::elfail, "Gamma", "be a number between 0 and 1"]; False),

        Positive[epsilon]
          /. False :> (Message[ValidQ::elfail, "Epsilon", "be a positive real number"]; False),

        MatchQ[topology, {{_,_,_}..} | Null]
          /.False :> (Message[ValidQ::elfail, "Topology", "be a matrix of indices"]; False)
              
      ]

    ] &

  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      object_ObjCakeWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
        Module[

        (* Local Variables *)
          {
            wRe = Re@object["Data"][[1]]
          },

          ListContourPlot3D[
            wRe,
            Sequence @@ {ImageSize -> 50, Mesh -> False, ContourStyle -> {Directive[Blue, Opacity[0.8], Specularity[White, 30]], Directive[Orange, Opacity[0.8], Specularity[White, 30]]}, MaxPlotPoints -> 50},
            Contours -> {0.155 Min[wRe], 0.06 Max[wRe]},
            PerformanceGoal->"Speed",
            Axes->False, Boxed->False
          ]

        ],
        LieAnalysis`Classes`Classes`Private`$ElisionsIcon
      ]
      ,
      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      MakeBoxes[instance : ObjCakeWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjCakeWavelet3D,
          ToString[ObjCakeWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

ObjCakeWavelet.m

(*************************************************************************************************************************
** ObjCakeWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjCakeWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjOrientationWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjOrientationWavelet, ObjCakeWavelet,
  {
    "Design"              -> "N",
    "InflectionPoint"     -> 0.8,
    "SplineOrder"         -> 3,
    "MnOrder"             -> 8,
    "DcStandardDeviation" -> 3,
    "DcFilter"            -> Null,
    "FourierDcFilter"     -> Null,
    "OverlapFactor"       -> 1,
    "FourierCake"         -> Null,
    "SpatialWindow"       -> False
  }
];



Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareInvariant["ObjCakeWavelet",

    (* Validate Elements *)
    Block[

      {
        design            = GetElement[#, "Design"],
        inflectionpoint   = GetElement[#, "InflectionPoint"],
        splineoder        = GetElement[#, "SplineOrder"],
        mnorder           = GetElement[#, "MnOrder"],
        dcstddev          = GetElement[#, "DcStandardDeviation"],
        dcfilter          = GetElement[#, "DcFilter"],
        fourierdcfilter   = GetElement[#, "FourierDcFilter"],
        overlapfactor     = GetElement[#, "OverlapFactor"],
        fouriercake       = GetElement[#, "FourierCake"],
        prespatialfilters = GetElement[#, "PreSpatialFilters"],
        spatialwindow     = GetElement[#, "SpatialWindow"]
      },

      And[

        MatchQ[design, "N"|"M"]
            /. False :> (Message[ValidQ::elfail, "Design", "be either N or M"]; False),

        (inflectionpoint > 0 && inflectionpoint <= 1)
          /. False :> (Message[ValidQ::elfail, "InflectionPoint","be between 0 and 1"]; False),

        Internal`PositiveIntegerQ[splineoder]
            /. False :> (Message[ValidQ::elfail, "SplineOrder","be a positive integer"]; False),

        Internal`PositiveIntegerQ[mnorder]
          /. False :> (Message[ValidQ::elfail, "MnOrder","be a positive integer"]; False),

        (MatrixQ[dcfilter] || dcfilter === Null)
            /. False :> (Message[ValidQ::elfail, "DcFilter","be a matrix"]; False),

        (MatrixQ[fourierdcfilter] || fourierdcfilter === Null)
            /. False :> (Message[ValidQ::elfail, "FourierDcFilter", "be a matrix"]; False),

        (Internal`PositiveIntegerQ[overlapfactor])
          /. False :> (Message[ValidQ::elfail, "OverlapFactor", "be a positive integer"]; False)
		
      ]

    ] &

  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet],

        Null

      ,
      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet],

      MakeBoxes[instance : ObjCakeWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Design: ", Subscript["φ", instance["Design"]]}, fmt],
          BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
          BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
          BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjCakeWavelet,
          ToString[ObjCakeWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

Object.m

BeginPackage["LieAnalysis`Classes`Object`", {
  "LieAnalysis`Classes`Classes`"
  }
];



DeclareClass[Object,
  {
    "Data"                            -> Automatic
  }
];




  Begin["`Private`"];


    DeclareInvariant["Object",

      Block[
        {
          data = GetElement[#, "Data"]
        },

        And[

          (data === Null || NumericQ[data] || True )
              /. False :> (Message[ValidQ::elfail, "Data", "Object", "be a number or an array of numbers or Automatic"]; False)

        ]

      ] &

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[Object],



        object_Object["Image"] := Module[

          {image},

          image = Switch[object["Data"],
            _?MatrixQ,  ImageAdjust[Image[object["Data"]]],
            Automatic,  LieAnalysis`Classes`Classes`Private`$ElisionsIcon,
            _,          LieAnalysis`Classes`Classes`Private`$ElisionsIcon
          ];

          Return[image]

        ];
        object_Object["DataType"] := Head[object["Data"]];



        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[Object],

        MakeBoxes[instance : Object[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt],
            BoxForm`MakeSummaryItem[{"Data Type: ", instance["DataType"]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            Object,
            ToString[Object] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

      Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjGaborWavelet.m

(*************************************************************************************************************************
** ObjGaborWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjGaborWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjOrientationWavelet`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjOrientationWavelet, ObjGaborWavelet,
    {
      "Scale"              -> Automatic,
      "K0"                 -> {0,3},
      "Epsilon"            -> 4
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjGaborWavelet",

      (* Validate Elements *)
      Block[

        {
          scale            = GetElement[#, "Scale"],
          k0               = GetElement[#, "K0"],
          epsilon          = GetElement[#, "Epsilon"]
        },

        And[

          (Positive[scale] || scale === Automatic)
              /. False :> (Message[ValidQ::elfail, "Scale", "be a positive integer"]; False),

          (MatchQ[k0, {_Integer, _Integer}])
              /. False :> (Message[ValidQ::elfail, "K0", "be list of two positive integers"]; False),

          ((NumericQ[epsilon] && Positive[epsilon]))
              /. False :> (Message[ValidQ::elfail, "Epsilon", "be a positive number"]; False)


        ]

       ]&


    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjGaborWavelet],

          Null

        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjGaborWavelet],

        MakeBoxes[instance : ObjGaborWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
            BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
            BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
            BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
            BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjGaborWavelet,
            ToString[ObjGaborWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjOrientationScores2D.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationScores2D`", {
  "LieAnalysis`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjWaveletTransform, ObjOrientationScores2D,
    {
      "DcFilteredImage"   -> 0,
      "Symmetry"          -> Pi
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjOrientationScores2D",

      Block[
        {
          dcfilteredimage = GetElement[#, "DcFilteredImage"],
          symmetry = GetElement[#, "Symmetry"]
        },

        And[

          (MatrixQ[dcfilteredimage] || dcfilteredimage == 0)
              /. False :> (Message[ValidQ::elfail, "DcFilteredImage", "be a matrix or zero"]; False)

          (MatchQ[symmetry, Pi, -Pi, 2Pi])
              /. False :> (Message[ValidQ::elfail, "Symmetry", "Should be Pi, -Pi or 2Pi"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores2D],

          (* Construct 0-2pi orientation scores *)
          object_ObjOrientationScores2D["FullData"] := Switch[object["Symmetry"],
            Pi  , Join[object["Data"], Conjugate[object["Data"]],3],
            -Pi , Join[object["Data"], -Conjugate[object["Data"]],3],
            2Pi , object["Data"]
          ];

          object_ObjOrientationScores2D["AngularResolution"] := Abs[object["Symmetry"] / object["NumberOfLayers"]];

        ,Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Intertnal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores2D],

        MakeBoxes[instance : ObjOrientationScores2D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfLayers"]}, fmt],
            BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjOrientationScores2D,
            ToString[ObjOrientationScores2D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjOrientationScores3D.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationScores3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjWaveletTransform, ObjOrientationScores3D,
    {
      "DcData"    -> Null
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjOrientationScores3D",

      Block[
        {
          dcdata = GetElement[#,"DcData"]
        },

        And[

          (dcdata === Null || MatchQ[dcdata, {{{__}...}...}])
            /. False :> (Message[ValidQ::elfail, "DcData", "should be a 3D matrix"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores3D],

            Null
          ,
          Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores3D],

        MakeBoxes[instance : ObjOrientationScores3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfLayers"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjOrientationScores3D,
            ToString[ObjOrientationScores3D] <> "[<>]",
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjOrientationWavelet3D.m

(*************************************************************************************************************************
** ObjOrientationWavelet.m
** This .m file describes a data-container for 2D oriented wavelets
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjWavelet, ObjOrientationWavelet3D,
  {
    "OrientationCoordinates" -> Null
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjOrientationWavelet3D",

  (* Validate Elements *)
  Block[

    {
      coords    = GetElement[#, "OrientationCoordinates"]
    },

    And[

      MatchQ[coords, {{_Real, _Real, _Real} ...} | Null]
      /. False :> (Message[ValidQ::elfail, "OrientationCoordinates", "be a list of 3D coordinates {p1,p2,...}"]; False)

    ]

  ] &

];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    object_ObjOrientationWavelet3D["Orientations"]     := object["NumberOfWavelets"];
    object_ObjOrientationWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
      Module[

        (* Local Variables *)
        {
          wavelet = Re@object["Data"][[1]]
        },

        ImageResize[
          Image3D[((wavelet - Min[wavelet])/Max[wavelet])^(1.75),BoxRatios->1],
          {5,5,5}
        ]

      ],
      LieAnalysis`Classes`Classes`Private`$ElisionsIcon
    ]
    ,
    Protect@@protected
  ];
];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    MakeBoxes[instance : ObjOrientationWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

      alwaysGrid = {
        BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
        BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
        BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
      };

      sometimesGrid = {
        BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
        BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
      };

      BoxForm`ArrangeSummaryBox[
        ObjOrientationWavelet3D,
        ToString[ObjOrientationWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
        instance["Image"],
        alwaysGrid,
        sometimesGrid,
        fmt,
        "Interpretable" -> False
      ]

    ];,

    Protect@@protected
  ]
];



End[];



EndPackage[];

ObjOrientationWavelet.m

(*************************************************************************************************************************
** ObjOrientationWavelet.m
** This .m file describes a data-container for 2D oriented wavelets
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjWavelet, ObjOrientationWavelet,
  {
    "Directional"     -> False,
    "Symmetry"        -> Pi
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjOrientationWavelet",

  (* Validate Elements *)
  Block[

    {
      directional = GetElement[#, "Directional"],
      symmetry = GetElement[#, "Symmetry"]
    },

    And[

      BooleanQ[directional]
      /. False :> (Message[ValidQ::elfail, "Directional", "be a boolean"]; False),

      MatchQ[symmetry, Pi | -Pi | 2Pi ]
          /. False :> (Message[ValidQ::elfail, "Symmetry", "be either π, -π or 2π"]; False)

    ]

  ] &

];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet],

    (* Gives the number of orientations between 0-2pi *)
    object_ObjOrientationWavelet["Orientations"] := Switch[object["Symmetry"],
      Pi | -Pi , 2 object["NumberOfWavelets"],
      2Pi | _, object["NumberOfWavelets"]
    ];

    (* Computes the angular resultion *)
    object_ObjOrientationWavelet["AngularResolution"] := Abs[object["Symmetry"] / object["NumberOfWavelets"]];

    (* Construct 0-2pi orientation scores *)
    object_ObjOrientationWavelet["FullData"] := Switch[object["Symmetry"],
      Pi  , Join[object["Data"], Conjugate[object["Data"]]],
      -Pi , Join[object["Data"], -Conjugate[object["Data"]]],
      2Pi , object["Data"]
    ];

    ,Protect@@protected
  ];
];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet],

    MakeBoxes[instance : ObjOrientationWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

      alwaysGrid = {
        BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
        BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
        BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
        BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
        BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
        BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
      };

      sometimesGrid = {
        BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
        BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
      };

      BoxForm`ArrangeSummaryBox[
        ObjOrientationWavelet,
        ToString[ObjOrientationWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
        instance["Image"],
        alwaysGrid,
        sometimesGrid,
        fmt,
        "Interpretable" -> False
      ]

    ];,

    Protect@@protected
  ]
];



End[];



EndPackage[];

ObjWavelet3D.m

(*************************************************************************************************************************
** ObjWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWavelet3D,
  {
    "WaveletSize"         -> Automatic
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjWavelet3D",

  Block[
    {
      waveletsize       = GetElement[#, "WaveletSize"]
    },

    And[

      (Internal`PositiveIntegerQ[waveletsize] || waveletsize === Automatic)
        /. False :> (Message[ValidQ::elfail, "WaveletSize", "be a positive integer"])

    ]

  ] &

];


End[];



EndPackage[];

ObjWavelet.m

(*************************************************************************************************************************
** ObjWavelet.m
** This .m file contains the blue print for an 2D wavelet object.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.2
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWavelet,
  {
    "CoordinateSystem"    -> "Matrix",
    "PreSpatialFilters"   -> Automatic
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjWavelet",

  Block[
    {
      coordinatesystem  = GetElement[#, "CoordinateSystem"]
    },

    And[

      MatchQ[coordinatesystem, "Matrix" | "Image" ]
          /. False :> (Message[ValidQ::elfail, "CoordinateSystem", "be either Image or Matrix"]; False)

    ]

  ] &

];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjWavelet],

      object_ObjWavelet["NumberOfWavelets"] := If[object["Data"] =!= Automatic, Length[object["Data"]],0];
      object_ObjWavelet["WaveletSize"]      := If[object["Data"] =!= Automatic, Dimensions[ object["Data"]][[2;;-1]],0];
      object_ObjWavelet["Image"]            := If[object["Data"] =!= Automatic, ImageResize[ImageAdjust@Image[Re@object["Data"][[1]]],{50,50}],LieAnalysis`Classes`Classes`Private`$ElisionsIcon];,

      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjWavelet],

      MakeBoxes[instance : ObjWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Number of Wavelets: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjWavelet,
          ToString[ObjWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

ObjWaveletTransform.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWaveletTransform`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWaveletTransform,
  {
    "Wavelets"          -> None,
    "InputData"         -> None
  }
];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjWaveletTransform",

      Block[
        {
          wavelets = GetElement[#, "Wavelets"],
          inputdata = GetElement[#, "InputData"]
        },

        And[

          (ValidQ[wavelets, "ObjWavelet"] || wavelets === None)
              /. False :> (Message[ValidQ::elfail, "Wavelets", "ObjWaveletTransform", "be a WaveletObj"]; False),

          (ValidQ[inputdata, "ObjWaveletTransform"] || MatrixQ[inputdata] || MatchQ[inputdata,{{{__}...}...}] || inputdata === None)
              /. False :> (Message[ValidQ::elfail, "InputData", "ObjWaveletTransform", "be a matrix"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

          object_ObjWaveletTransform["NumberOfLayers"] := If[object["Data"] =!= Automatic, Dimensions[object["Data"]][[-1]], 0];

          object_ObjWaveletTransform["InputDimensions"] := Which[
            ValidQ[object["InputData"],"ObjOrientationScores2D"]  , Dimensions[object["InputData"]["Data"]],
            ValidQ[object["InputData"],"ObjOrientationScores3D"]  , Dimensions[object["InputData"]["Data"]],
            ArrayQ[object["InputData"]]                           , Dimensions[object["InputData"]],
            MatchQ[object["InputData"], None | _]                 , None
          ];

          object_ObjWaveletTransform["WaveletType"] := If[object["Wavelets"] =!= None, Head[object["Wavelets"]], None];

          object_ObjWaveletTransform["Image"] := If[object["Data"] =!= Automatic,
            If[Length[Dimensions@object["Data"]] > 3,
              (*3D Case *)
              Module[

                {
                  data = object["Data"],
                  orientation,max
                },

                orientation = Transpose@Map[First@First@Position[#,Max[#]]&,Transpose[Re[data],{3,2,1}],{2}];
                orientation = orientation-Min[orientation];
                orientation = orientation/Max[orientation];

                max = Transpose@Map[Max,Transpose[Re[data],{3,2,1}],{2}];
                max = max/Max[max];

                Show[SetAlphaChannel[
                  Colorize[ImageAdjust[Image[orientation]], ColorFunction -> Function[orientation, Hue[orientation]]],
                  Image[max]
                ],ImageSize->50]

              ],
              (* 2D Case *)
                ImageAdjust[ImageResize[Image[Re@object["Data"][[All,All,1]]],{50,50}]]
              ],

              LieAnalysis`Classes`Classes`Private`$ElisionsIcon
            ]

        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

        MakeBoxes[instance : ObjWaveletTransform[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjWaveletTransform,
            ToString[ObjWaveletTransform] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];

  End[];



EndPackage[];

ExampleData.m

(*************************************************************************************************************************
** ExampleData.m
** This .m file contains functions to load example data to demonstrate the functions
**
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`ExampleData`ExampleData`"];




  Unprotect[ExampleData];



    ExampleData[{"LieAnalysis Package", "Retina"}] := ColorConvert[Import["https://upload.wikimedia.org/wikipedia/en/thumb/b/b3/HealthyEyeFundus.jpg/1280px-HealthyEyeFundus.jpg"],"GrayScale"];

    ExampleData[{"LieAnalysis Package", "Spagetti"}] := ColorConvert[Import["https://pixabay.com/static/uploads/photo/2015/05/24/14/02/spaghetti-781795_960_720.jpg"],"GrayScale"];




  Protect[ExampleData];



EndPackage[];

Common.m

(*************************************************************************************************************************
** Common.m
** This .m file contains common function used for the orientation scores 2D.
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Common`"];

	ErfSet::usage = "todo";

	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ErfSet- - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		ErfSet = Compile[{{size,_Real},{No,_Integer},{periodicity, _Real}}, 
			Module[{dθ},
				Table[
					Table[
						1/2 (1+Erf[x Cos[(periodicity (-1+i))/No]+y Sin[(periodicity (-1+i))/No]]),
						{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
						{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
					],
					{i,1,No}
				]
			]
		];

	
	

	End[];



EndPackage[];

Construction.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains functions to construct a multi-stack and multi-orientation using different kind of filters
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.2
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Construction`",{
	"LieAnalysis`",
	"LieAnalysis`Common`",
	"LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`",
  "LieAnalysis`Classes`ObjCakeWavelet`",
  "LieAnalysis`Classes`ObjGaborWavelet`",
  "LieAnalysis`Classes`ObjWaveletTransform`",
  "LieAnalysis`Classes`ObjOrientationScores2D`",
  "LieAnalysis`OrientationScores2D`Filters`CakeWavelets`",
  "LieAnalysis`OrientationScores2D`Filters`GaborWavelets`"
}];




	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* WaveletTransform- - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		WaveletTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		


		WaveletTransform[img_, waveletObj_/;ValidQ[waveletObj,"ObjWavelet"]] := Module[

    	(* Local Varialble Declaration *)
			{wt2d, image, multiObj, kernels = waveletObj["Data"]},

			(* Container to store the Multi-space in *)
			multiObj = ObjWaveletTransform["Wavelets"->waveletObj];

			(* Process input image *)
      image = ImageInputProcessor[img];
			multiObj = SetElement[multiObj, "InputData"->image];

			(* Compute the Tranform stack *)
			wt2d = WaveletTransform[image, kernels];
			multiObj = SetElement[multiObj, "Data"->wt2d];

			(* Return the Multi-space object *)
			Return[multiObj]

		];



		WaveletTransform[img_, kernels_List, opts:OptionsPattern[]] := Module[

			(* Local Varialble Declaration *)
			{image, os},

			(* Process the input image *)
      image = ImageInputProcessor[img];

      os = Developer`ToPackedArray@Map[
        ListConvolve[#, image, Ceiling[Dimensions[#]/2]]&,
        kernels
      ];

			(* Strip List if only one kernel is provided *)
			If[Length[kernels] == 1, os = First[os]];
		    
		  (* Return the multi-space *)
			Return[os]
		   	
		];
		

		
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* OrientationScoresTransform- - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    OrientationScoreTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		
		
		
		Options[OrientationScoreTransform] = DeleteDuplicates@Join[Options[CakeWaveletStack], Options[GaborWaveletStack],{
			"Type"->"Cake",
			"Orientations"->12, (* The number of orientations to construct the Oriented WaveletStack *)
			"Scale"->1 (* The gabor scale parameter *)
		}];



		OrientationScoreTransform[img_, cwsObj_/;ValidQ[cwsObj, "ObjOrientationWavelet"]] := Module[

			(* Local Variables Declaration *)
			{image, wtObj, osObj, dcFilteredImage},

    	(* Process the input image *)
      image = ImageInputProcessor[img];

      (* Create the Orientation Score *)
      wtObj = WaveletTransform[image, cwsObj];

			(* Select appropiate method per filters *)
			Switch[Head[cwsObj],

					ObjCakeWavelet,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"]; (* Convert to OS2D child *)
            dcFilteredImage = WaveletTransform[image, {cwsObj["DcFilter"]}];
            osObj = SetElement[osObj, "DcFilteredImage"->dcFilteredImage];,

					ObjGaborWavelet,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"];,

					_,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"];

			];

			(* Re-arrange to x,y,theta and set symmetry property *)
			osObj = SetElement[osObj, {
				"Data"->Reorder[osObj["Data"], "SpatialFirst"],
				"Symmetry"->cwsObj["Symmetry"]
			}];

			(* Return the Orientation Score *)
      Return[osObj]

		];



		OrientationScoreTransform[img_, opts:OptionsPattern[]] := Module[

			(* Local Variables Declaration*)
			{
				type = OptionValue["Type"],
				scale = OptionValue["Scale"],
        orientations = OptionValue["Orientations"],
				wsObj, osObj
			},

			(* Input validation *)
			And[

				MatchQ[type, "Cake" | "Gabor"]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[type], "Type", "Should be either Cake or Gabor"]; False),

				Internal`PositiveIntegerQ[scale]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[scale], "Scale", "Should be a positive integer"]; False),

				Internal`PositiveIntegerQ[orientations]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[orientations], "Orientations", "Should be a positive integer"]; False)

			];

			(* Construct the desired wavelets *)
			Switch[type,
				"Gabor",
      		(* Construct Gabor Wavelet Stack *)
        	wsObj = GaborWaveletStack[orientations, scale, FilterRules[{opts}, Options[GaborWaveletStack]]],

				"Cake" | _,
					(* Compute Cake Wavelet Stack*)
					wsObj = CakeWaveletStack[orientations, FilterRules[{opts}, Options[CakeWaveletStack]]]

			];

			(* Compute OS2D *)
			osObj = OrientationScoreTransform[img, wsObj];

			(* Return the Orientation Score and metaData *)
			Return[osObj]

		];



	End[];



EndPackage[];

OrientationScoreDerivatives.m

(*************************************************************************************************************************
** Common.m
** This .m file contains functions to compute the cartesian derivatives in se(2)
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Derivatives`OrientationScoreDerivatives`", {
    "LieAnalysis`",
    "LieAnalysis`Common`",
    "LieAnalysis`Classes`Classes`",
    "LieAnalysis`Classes`ObjOrientationScores2D`"
  }
];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrientationScoreDerivatives - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[OrientationScoreDerivatives] = {
      "Type" -> "LeftInvariant"
    };



    OrientationScoreDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], {sigmaSpatial_, sigmaOrientation_}, derivativeIndex_, opts:OptionsPattern[] ] := Module[

      (*Local variable declaration *)
      {
        type = OptionValue["Type"],
        sigmaOD, derivatives
      },

      (* Convert to discrete orientation sigma *)
      sigmaOD = sigmaOrientation / osObj["AngularResolution"];

      (* Select the correct derivatives *)
      derivatives = Switch[type,
        "Cartesian"  , CartesianDerivatives[osObj, sigmaSpatial, sigmaOD, derivativeIndex],
        "LeftInvariant" | _ , LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOD, derivativeIndex]
      ];

      (* Return the Derivatives *)
      Return[derivatives];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* CartesianDerivatives- - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_List] :=
        CartesianDerivatives[osObj, sigmaSpatial, sigmaOrientation, #]&/@order;



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_String] :=
        CartesianDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[order]];



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_Integer] := Module[

      (* Local variables *)
      {dir, orderDigits, orderList, derivatives},

      (* Convert to the format of Gaussian Derivative *)
      orderList = Count[IntegerDigits[order],#]&/@{1,2,3};

      (* Compute the derivatives *)
      (*todo: make the theta-axis circular *)
      derivatives = GaussianDerivative[osObj["Data"], {sigmaSpatial, sigmaSpatial, sigmaOrientation}, orderList];

      (* Return the derivatives *)
      Return[derivatives];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* LeftInvariantDerivatives- - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_List] :=
        LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, #]&/@order;



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_?(StringMatchQ[ToString[IntegerReverse[#]], ___ ~~ "3" ~~ ("*1*" | "*2*")]&)] := Module[

    (*Local Variables Declaration *)
      {
        orderString = ToString[order],  (* illegal order *)
        derivatives, orders, derivativeString, operators, derivative, osObjOut
      },

      (* Recompute Order *)
      derivativeString = StringSplit[OrderTheDerivatives[StringReverse[orderString]],{"+"->"+","-"->"-"}];
      {orders, operators} = GatherBy[derivativeString,MatchQ[#,"-"|"+"]&];  (* legal derivative orders that need to be computed *)

      (* Compute derivatives *)
      derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[StringReverse[#]]&/@orders];

      (* Add operators again and sum to the final derivative *)
      derivative = Plus @@ Prepend[MapThread[If[#2 == "-", -#1["Data"], #1["Data"]] &, {derivatives[[2 ;; -1]], operators}], derivatives[[1]]["Data"]];

      (* Store derivative in container *)
      osObjOut = ObjOrientationScores2D["Data"->derivative, "Symmetry"->derivatives[[1]]["Symmetry"], "InputData"->osObj];

      (* Return the derivative *)
      Return[osObjOut];

    ];




    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_String] := Module[

      (* Local Variables Declaration *)
      {derivatives},

      (* Get derivatives *)
      (MatchQ[ToUpperCase[order], "GRADIENT"])
          /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, {1,2,3}]);

      (MatchQ[ToUpperCase[order], "HESSIAN"])
        /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, {11,12,13,12,22,23,31,32,33}]);

      (MatchQ[ToExpression[order], _Integer])
          /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[order]]);

      (* Return Derivatives *)
      Return[derivatives];

    ];



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_Integer] := Module[

      (* Local Variables Declaration *)
      {

        angles, derivative, derOrder, scaledSigmaSpatial, scaledSigmaOrientation, n, gt, dgT, dx, dy, dt,
        symmetry,osObjOut
      },

      angles = Range[0, Abs[osObj["Symmetry"]] - osObj["AngularResolution"], osObj["AngularResolution"]];

      (* List of Derivatives that need to be computed *)
      derOrder = Reverse[IntegerDigits[order]];

      (* scale sigmas to the number of derivatives *)
      n = Length[derOrder];
      {scaledSigmaSpatial, scaledSigmaOrientation} = 1/Sqrt[n] {sigmaSpatial, sigmaOrientation};

      (* Compute Derivative *)
      derivative = osObj["Data"];
      symmetry = osObj["Symmetry"];

      Do[

        (* Either spatial derivative or orientation derivative *)
        Switch[dir,
          1 | 2,

            (* Blur the OS in thera-direciton *)
            gt = GaussianOrientationBlur[derivative, scaledSigmaSpatial, scaledSigmaOrientation, symmetry];

            (* Compute the x and y-derivative *)
            dgT = Reorder[gt, "OrientationFirst"];
            dx = GaussianDerivative[#, {scaledSigmaSpatial,scaledSigmaSpatial}, {1,0},"Fixed"]&/@dgT;
            dy = GaussianDerivative[#, {scaledSigmaSpatial,scaledSigmaSpatial}, {0,1},"Fixed"]&/@dgT;

            (* Rotate to the eta, xi frame *)
            derivative = Switch[dir,
              1, MapThread[( #1 Cos[#3] + #2 Sin[#3])&, {dx, dy, angles}],
              2, MapThread[(-#1 Sin[#3] + #2 Cos[#3])&, {dx, dy, angles}]
            ];

            (* Reorder x,y,theta *)
            derivative = Reorder[derivative, "SpatialFirst"];

            (* Update Symmetry *)
            symmetry = Switch[symmetry,
                 Pi, -Pi,
                -Pi,  Pi,
                2Pi, 2Pi
            ],

          3,

            (* Compute the theta-derivative *)
            dt = GaussianDerivative[derivative, {scaledSigmaSpatial, scaledSigmaSpatial, scaledSigmaOrientation}, {0,0,1}, "Periodic"];
            derivative = dt / osObj["AngularResolution"];

        ];

        ,{dir, derOrder}

      ];

      (* ObjOrientationScore Object *)
      osObjOut = ObjOrientationScores2D["Data"->derivative, "Symmetry"->symmetry, "InputData"->osObj];

      (* Return Derivatives *)
      Return[osObjOut];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GaussianOrientationBlur - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GaussianOrientationBlur[os_, sigmaSpatial_, sigmaOrientation_, symmetry_] := Module[

      (* Local Variable Declaration *)
      {blurredOs},

      (* Full periodic blurring in theta-direction *)
      With[

        (* If the periodicity is pi, extend it to 2pi *)
        {
          fullPeriodDerivative = Switch[symmetry,
            Pi, Join[os, Conjugate[os],3],
            -Pi, Join[os, -Conjugate[os],3],
            2Pi, os
          ]
        },

        (* Blur in theta-direction *)
        blurredOs = GaussianFilter[fullPeriodDerivative, {4 {0,0,sigmaOrientation}+1,{0,0,sigmaOrientation}}, Padding->"Periodic"];

        (* Cut off pi-2pi section if periodicity is pi *)
        blurredOs = If[MatchQ[symmetry, Pi | -Pi], blurredOs[[All,All,1;;Dimensions[os][[3]]]], blurredOs];

        (* Return the blurred OS *)
        Return[blurredOs];


      ];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrderTheDerivatives - - - - - - - - - - - - - - - - - - - *)
    (* Theta derivative should always be first derivative,
       see P116-119, Erik Franken PhD Thesis - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    OrderTheDerivatives[order_Integer] := OrderTheDerivatives[ToString[order]];



    OrderTheDerivatives[order_String] := Module[

      (* Define substitution rules *)
      {
        thetaXi   = prior___~~"31"~~post___:>prior<>"2"<>post<>"+"<>prior<>"13"<>post,
        thetaEta  = prior___~~"32"~~post___:>prior<>"23"<>post<>"-"<>prior<>"1"<>post,
        orderTerms, derivativeOrderReWritten, previousResult, result
      },

      (* Save the original *)
      result = order;

      (* Repeat the process untill theta is the first derivative *)
      While[previousResult =!= result,

        (* Update the itteration condition *)
        previousResult = result;
        orderTerms = StringSplit[result,{"+"->"+","-"->"-"}];

        (* Substitute *)
        derivativeOrderReWritten = StringReplace[#,{thetaEta,thetaXi}]&/@orderTerms;
        result = StringJoin[derivativeOrderReWritten];

      ];

      (* Return the reorderd result *)
      Return[result];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GaussianDerivatives - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GaussianDerivative[data_, sigmas_, orders_, padding_:"Periodic"] := Module[

      (* Local Variables Declaration *)
      {
        singlePaddingMethod = MatchQ[padding,"Periodic" | "Fixed" | "Reflected" | "Reversed" | None],
        derivative
      },

      (* Compute the derivative(s) depending on input *)
      (MatchQ[orders, {_Integer..}] && singlePaddingMethod)
          /. True :> (derivative = GaussianFilter[data, {4 sigmas+1, sigmas}, orders, Method -> "Gaussian", Standardized -> False, Padding->padding]);

      (MatchQ[orders, {{_Integer..}..}] && singlePaddingMethod)
          /. True :> (derivative = GaussianFilter[data, {4 sigmas+1, sigmas}, #, Method -> "Gaussian", Standardized -> False, Padding->padding]&/@orders);

      (MatchQ[orders, {{_Integer..}..}] && !singlePaddingMethod)
          /. True :> (derivative = MapThread[GaussianFilter[data, {4 sigmas+1, sigmas}, #1, Method -> "Gaussian", Standardized -> False, Padding->#2]&,{orders, padding}]);

      (* Return the derivative *)
      Return[derivative];

    ];



  End[];



EndPackage[];

CakeWavelets.m

(*************************************************************************************************************************
** CakeWavelets.m
** This .m file contains functions to create cake-wavelets.
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
** Notes:
**	- The option DcStandardDeviation is used to remove the low frequencies from the OS. The Standard Deviation is related
** 		to the sigma of a Gaussian in the SPATIAL domain. This is valid if designtype N is used, but when the cake is scaled
** 		to create design M (M = Sqrt[N]) this gaussian is scaled in the Fourier domain, changing the spatial shape in the
**		spatial domain. This might be resolved in a later version.
**	- The spatial window option is included to remove the long-tails of the filter in the spatial domain. This is more or
**		less the same as removing the low-frequencies in the Fourier domain, what is done by the Dc-Filter. Therefor the spatial
** 		window has become reduntant and might be removed in future releases.
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Filters`CakeWavelets`",{
	"LieAnalysis`Common`",
	"LieAnalysis`OrientationScores2D`Common`",
	"LieAnalysis`Classes`Classes`",
	"LieAnalysis`Classes`ObjCakeWavelet`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStack  - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		
		Options[CakeWaveletStack] = {
			"WaveletSize"->"Automatic", (* The x,y dimensions for the construction of the CakeWaveletStack *)
			"CoordinateSystem"->"Matrix", (* Determines whenever the output is given in Matrix coordinates or Image coordinates *)
			"Directional"->False, (* Determines whenever the filter goes in both directions *)
			"Design"->"N", (* Indicates which design is used N = Subscript[N, ψ] or M = Subscript[M, ψ] *)
			"InflectionPoint"->0.8, (* Is the location of the inflection point as a factor in (positive) radial direction *)
			"SplineOrder"->3, (* Order of the B-Spline that is used to construct the wavelet *)
			"MnOrder"->8, (* The order of the (Taylor expansion) gaussian decay used to construct the wavelet *)
			"DcStandardDeviation"->8, (* The standard deviation of the gaussian window (in the Spatial domain) that removes the center of the pie, to avoid long tails in the spatial domain *)
			"VectorOrientationScore"->False, (* Special kinds of output can be requested *)
			"SpatialWindow"->False, (* Indicates whenever the spatial (gaussian) window is used or not, and if used which size it is *)
			"OverlapFactor"->1 (* How much the cakepieces overlaps in ϕ-direction, this can be seen as subsampling the angular direction *)
		};
		
		

		CakeWaveletStack::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		


		CakeWaveletStack[nOrientations_, opts:OptionsPattern[]] := Module[
	
			(* Local Variables Declaration *)
			{
				splineOrder          = OptionValue["SplineOrder"],
				overlapFactor        = OptionValue["OverlapFactor"],
				inflectionPoint      = OptionValue["InflectionPoint"],
				mnOrder              = OptionValue["MnOrder"],
				dcStdDev             = OptionValue["DcStandardDeviation"], (*ToDo: implement auto method, to ensure the Mn and Dc do not overlap *)
				spatialWindow        = If[OptionValue["SpatialWindow"] === "Automatic", (size-1)/4, OptionValue["SpatialWindow"]],
				coordinateSystem     = OptionValue["CoordinateSystem"],
				directional          = OptionValue["Directional"],
				design               = OptionValue["Design"],
				vectorOS             = OptionValue["VectorOrientationScore"],
				size				 				 = OptionValue["WaveletSize"],
				cakeObj
			},

			(* Compute Wavelet Size Depending based on the inflectionPoint *)
			size = If[size == "Automatic",
				Round[70/inflectionPoint], (* Fix the size of the cake in the Fourier domain to 70px *)
				size
			];
			size = If[EvenQ[size],size+1,size];
		
			(* Validate the Input *)
			If[EvenQ[size],Message[CakeWaveletStack::invalidInput,size,"Size","Should be an odd value"]];
			If[!Internal`PositiveIntegerQ[nOrientations], Message[CakeWaveletStack::invalidInput, nOrientations, "nOrientations", "Should be a positive integer"]];
			If[!BooleanQ[directional],Message[CakeWaveletStack::invalidInput, directional, "Directional","Should be a boolean"]];
			If[!BooleanQ[vectorOS],Message[CakeWaveletStack::invalidInput, vectorOS,"VectorOrientationScore", "Should be a boolean"]];
			If[!IntegerQ[splineOrder], Message[CakeWaveletStack::invalidInput, splineOrder, "SplineOrder", "Should be an integer"]];
			If[splineOrder < 3, Message[CakeWaveletStack::invalidInput, splineOrder, "SplineOrder", "Should be greater than 3"]];
			If[!IntegerQ[overlapFactor],Message[CakeWaveletStack::invalidInput, overlapFactor, "OverlapFactor", "Should be an integer"]];
			If[overlapFactor < 1, Message[CakeWaveletStack::invalidInput, overlapFactor, "OverlapFactor", "Should be greater or equal to 1"]];
			If[!NumericQ[inflectionPoint] || inflectionPoint > 1 || inflectionPoint < 0, Message[CakeWaveletStack::invalidInput, inflectionPoint, "InflectionPoint","Should be an number between 0 and 1"]];
			If[!IntegerQ[mnOrder], Message[CakeWaveletStack::invalidInput, mnOrder, "MnOrder","Should be an integer"]];
			If[mnOrder < 3, Message[CakeWaveletStack::invalidInput, mnOrder, "MnOrder","Should be greater than 2"]];
			If[!NumericQ[dcStdDev], Message[CakeWaveletStack::invalidInput, dcStdDev, "DcStandardDeviation","Should be an number"]];
			If[dcStdDev < 0, Message[CakeWaveletStack::invalidInput, dcStdDev, "DcStandardDeviation","Should be greater than 0"]]; 
			If[(!NumericQ[spatialWindow] || !Positive[spatialWindow]) && spatialWindow, Message[CakeWaveletStack::invalidInput, spatialWindow, "SpatialWindow", "Should be positive and numeric"]];
			If[coordinateSystem != "Matrix" && coordinateSystem != "Image", Message[CakeWaveletStack::invalidInput, coordinateSystem, "CoordinateSystem","Should be either \"Matrix\" or \"Image\""]];
			If[design != "M" && design != "N", Message[CakeWaveletStack::invalidInput, design, "Design","Should be either \"M\" or \"N\""]];
		
			(* Compute the cakewavelet-stack *)
			cakeObj = CakeWaveletStack[
				size, nOrientations, design, inflectionPoint, mnOrder, splineOrder, overlapFactor, dcStdDev, directional, spatialWindow, coordinateSystem
			];

			(* ToDo: do some post creation checking: 1) does the Fourier-cake go to 0? 2) do the Dc and Mn window interfere? *)

			(* Create Packed Array *)
			(* ToDo: check this earlier *)
			(*cake = If[!Developer`PackedArrayQ[cake], Developer`ToPackedArray[cake], cake];*)

			(* Return the cakewavelet stack *)
			Return[cakeObj];
			
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStack- - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		

		
		CakeWaveletStack[size_, nOrientations_, design_, inflectionPoint_, mnOrder_, splineOrder_, overlapFactor_, dcStdDev_, directional_, spatialWindow_, coordinateSystem_] := Module[
				
			{cakeObj, cake, useWindow, filters, cakeF, dcFilter, window, s, dcSigma},

			(* Create contianer to store the cakewavelets in *)
			cakeObj = ObjCakeWavelet[{
				"WaveletSize"->size,
				"CoordinateSystem"->coordinateSystem,
				"Directional"->directional,
				"Symmetry"->Pi,
				"Design"->design,
				"InflectionPoint"->inflectionPoint,
				"SplineOrder"->splineOrder,
				"MnOrder"->mnOrder,
				"DcStandardDeviation"->dcStdDev,
				"SpatialWindow"->spatialWindow,
				"OverlapFactor"->overlapFactor
			}];

			(* Check if the spatial window is used *)
			useWindow = If[spatialWindow =!= False, True, False];
			
			(* Convert the dc sigma to the Fourier domain *)
			dcSigma = (1/dcStdDev)*(size/Pi);
		
			(* Compute the Fourier cake *)
			filters = CakeWaveletStackFourier[size,2Pi/nOrientations,splineOrder,overlapFactor,inflectionPoint,mnOrder,dcSigma,useWindow];
			cakeF = filters[[1;;-2]];
			dcFilter = filters[[-1]];
		
			(* Scale the cake according to its design*)
			Switch[design,
				"M", cakeF = Sqrt[cakeF]; dcFilter = Sqrt[dcFilter]
			];
			cakeObj = SetElement[cakeObj, "FourierCake"->cakeF, "FourierDcFilter"->dcFilter];
			
			(* Inverse (centered) Fourier Transform *)
			cake = CenteredInverseFourier/@cakeF;
			cakeObj = SetElement[cakeObj, "PreSpatialFilters"->cake];
		
			(* Apply the spatial window *)
			(***********TO BE REMOVED *****************)
			If[spatialWindow =!= False,
		
				(* The center pixel of the cake must sum up to 1 *)
				s=2; (* Preferably should be 0, however this seems to work better *)
				cakeF[[All,Ceiling[(size-1)/2]+1-s;;Ceiling[(size-1)/2]+1+s,Ceiling[(size-1)/2]+1-s;;Ceiling[(size-1)/2]+1+s]]=1/nOrientations;
				
				(* compute the inverse Fourier *)
				cake = CenteredInverseFourier/@cakeF;
				
				(* Apply the spatial window *)
				window = WindowGauss[size,spatialWindow];
				cake = (window*#)&/@cake;
				 
			];
			(***********************************)
		
			(* Cut the single wavelets in two directional wavelets *)
			If[directional,
				cake = cake * ErfSet[size, (overlapFactor*nOrientations) / 2,Pi];
			];	
		
			(* Adjust the coordinate system to Image coordinates *)
			cake = Switch[coordinateSystem,
				"Image", Transpose/@cake,
				"Matrix", cake
			];

      (* Append the DC-filter, if the spatial window is used this should be a spike *)
			cakeObj = SetElement[cakeObj, "Data"->cake, "DcFilter"->CenteredInverseFourier[dcFilter]];
			
			(* Return the Stack *)
			Return[cakeObj];
			
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStackFourier - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		
		CakeWaveletStackFourier = Compile[{{size,_Integer,0},{sφ,_Real,0},{splineOrder,_Integer,0},{overlapFactor,_Integer,0},{inflectionPoint,_Real,0},{mnOrder,_Integer,0},{dcStdDev,_Real,0},{usesWindow,True|False}},
			
			Module[{dcWindow = ConstantArray[1.,{size,size}], mnWindow, angleGrid,cake,filters},
				
				(* Gaussian center window to remove pie center, if a window in the spatial domain is used this is omitted *)
				If[!usesWindow,
					dcWindow = dcWindow - WindowGauss[size,dcStdDev];
				];
				
				(* Gaussian decay window *)
				mnWindow  = MnWindow[size,mnOrder,inflectionPoint];
		
				(* Construct the cake pieces *)
				angleGrid = PolarCoordinateGridAngular[size];
				cake = Table[
					dcWindow * mnWindow * (BSplineMatrixFunc[splineOrder,Mod[angleGrid - θ - π/2 , 2π , -π ] / sφ]) / overlapFactor,
					{θ,π,2π-sφ/overlapFactor,(sφ/overlapFactor)}
				];
				
		
				(* Add Dc filter to the output *)
				filters = Append[cake,(1-dcWindow)];		
		
				(* Return the Fourier cake *)
				Return[filters]
		
			], {{WindowGauss[size,dcStdDev],_Real,2},{MnWindow[size,mnOrder,inflectionPoint],_Real,2},{PolarCoordinateGridAngular[size],_Real,2}}
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* BSplineMatrixFunc - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		
		BSplineMatrixFunc = Compile[{{n,_Integer},{x,_Real,2}},
			Module[{function,intervalCheck},
				Total[
					Table[
						function=1/(2 (n+1-1)!) Total[Table[Binomial[n+1,k](x+(n+1)/2-k)^(n+1-1) (-1)^k Sign[i+(n+1)/2-k],{k,0,n+1}]];
						intervalCheck=Round[If[i<(n+1-1)/2,UnitStep[x-(i-1/2+$MachineEpsilon)]*UnitStep[-(x-(i+1/2))],UnitStep[x-(i-1/2+$MachineEpsilon)]*UnitStep[-(x-(i+1/2-$MachineEpsilon))]]];
						function*intervalCheck,
						{i,(1-n-1)/2,(n-1+1)/2}
					]
				]
			]
		];




		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* MnWindow - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		MnWindow = Compile[{{size,_Integer},{n,_Integer},{inflectionPoint,_Real}},
			Module[{ρMatrix},
					ρMatrix = $MachineEpsilon + 1/Sqrt[(2*(inflectionPoint)^2)/(1+2 n)] PolarCoordinateGridRadial[size];
					Total[Table[E^-ρMatrix^2*ρMatrix^(2k)/k!,{k,0,n}]]
				]
				,{{k!,_Real},{PolarCoordinateGridRadial[size],_Real,2}}
		];
		


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* PolarCoordinateGridRadial - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		PolarCoordinateGridRadial = Compile[{{size,_Integer}},
			Table[
				Sqrt[x^2+y^2] /((size-1)/2),
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
				


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* PolarCoordinateGridAngular- - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		PolarCoordinateGridAngular = Compile[{{size,_Integer}},
			Table[
				Arg[x+I y],
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
		
		
		
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* WindowGauss - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		WindowGauss = Compile[{{size,_Integer},{σs,_Real}},
			Table[
				E^(-(x^2/(2σs^2))-y^2/(2σs^2)),
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
		
		
				
	End[];

EndPackage[];

GaborWavelets.m

(*************************************************************************************************************************
** GaborWavelets.m
** This .m file contains functions to create gabor-wavelets
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Filters`GaborWavelets`",{
	"LieAnalysis`Common`",
	"LieAnalysis`OrientationScores2D`Common`",
  "LieAnalysis`Classes`Classes`",
	"LieAnalysis`Classes`ObjGaborWavelet`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GaborWaveletStack - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[GaborWaveletStack] = {
			"WaveletSize"->"Automatic",
			"Epsilon" -> 4, (* Defines the anisotropy of the wavelet *)
			"K0" -> {0, 3},
			"Directional" -> False,
			"CoordinateSystem" -> "Matrix"
		};



		GaborWaveletStack::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";



		GaborWaveletStack[nOrientations_, scale_, opts:OptionsPattern[]] := Module[
			
			(* Local Variables Declaration *)	
			{
				k0 								= OptionValue["K0"],
				epsilon 					= OptionValue["Epsilon"],
				directional 			= OptionValue["Directional"],
				coordinateSystem 	= OptionValue["CoordinateSystem"],
				size 							= OptionValue["WaveletSize"],
				gaborObj
			},

			(* Compute the size *)
			size = If[size == "Automatic",
				Floor[8*Sqrt[epsilon]*scale],
				size
			];
			size = If[EvenQ[size], size + 1, size];

			(* Validate the Input *)
			And[

				(OddQ[size] && Internal`PositiveIntegerQ[size] )
					/. False :> (Message[GaborWaveletStack::invalidInput, size, "size", "Sould be an odd positive integer"]; False),

				Internal`PositiveIntegerQ[nOrientations]
					/. False :> (Message[CakeWaveletStack::invalidInput, nOrientations, "nOrientations", "Should be a positive integer"]; False),

				(NumericQ[scale] && Positive[scale])
					/. False :> (Message[GaborWaveletStack::invalidInput, scale, "scale", "Should be a positive number"]; False),

				MatchQ[k0, {_,_}]
					/. False :> (Message[GaborWaveletStack::invalidInput,k0,"K0","Should be a vector2"]; False),

				Positive[epsilon]
					/. False :> (Message[GaborWaveletStack::invalidInput, epsilon, "Epsilon","Should be a positive number"]; False),

				BooleanQ[directional]
					/. False :> (Message[GaborWaveletStack::invalidInput, directional, "Directional", "Should be a Boolean"]; False),

				MatchQ[coordinateSystem, "Image" | "Matrix"]
					/. False :> (Message[GaborWaveletStack::invalidInput, coordinateSystem, "CoordinateSystem","Should be either \"Image\" of \"Matrix\""]; False)

			] /. False :> Abort[];

			(* Compute Gabor-Wavelet Stack *)
			gaborObj = GaborWaveletStack[size, nOrientations, scale, k0, epsilon, directional, coordinateSystem];

		 	(* Return the stack *)
			Return[gaborObj]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GaborWaveletStack - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		GaborWaveletStack[size_, nOrientations_, scale_, k0_, epsilon_, directional_, coordinateSystem_] := Module[

			(* Local Variable Declaration *)
			{gaborObj, wavelets, A},

			(* Creating container to save wavelet *)
			gaborObj = ObjGaborWavelet[];

			(* Save Options *)
			gaborObj = SetElement[gaborObj,
				"WaveletSize"->size,
				"Scale"->scale,
				"K0"->k0,
				"Epsilon"->epsilon,
        "Symmetry"->Pi,
				"Directional"->directional,
				"CoordinateSystem"->coordinateSystem
			];

			(* Compute A matrix *)
      A =({{epsilon^(-1/2), 0}, {0, 1}});

			(* Compute the Gabor Wavelet Stack *)
      wavelets = GenerateKernelSetGabor[A, k0, scale, 2Pi/nOrientations, size];
	  	gaborObj = SetElement[gaborObj, "PreSpatialFilters"->wavelets];

      (* Cut the single wavelets in two directional wavelets *)
      If[directional,
        wavelets = wavelets * ErfSet[size, nOrientations/2, Pi];
      ];

      (* Adjust the coordinate system to Image coordinates *)
      wavelets = Switch[coordinateSystem,
        "Image", Transpose/@wavelets,
        "Matrix", wavelets
      ];
      gaborObj = SetElement[gaborObj, "Data"->Developer`ToPackedArray@wavelets];

			(* Return Gabor-wavelets *)
			Return[gaborObj]


		];


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GenerateKernelSetGabor- - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		GenerateKernelSetGabor = Compile[{{A, _Real, 2}, {k0, _Real, 1}, {a, _Real}, {dTheta, _Real}, {dim, _Real}},
  		
  			Module[{},

					(* Construct the wavelets *)
   				$MachineEpsilon + (1/((4 a Pi)/E^(9/2))) (1/a)*Table
   				[
     	 			Table[
     	 				With[
	     	 					{
	         					xx = {Cos[-Theta + Pi/2],-Sin[-Theta + Pi/2]}.{x, y},
	         					yy = {Sin[-Theta + Pi/2], Cos[-Theta + Pi/2]}.{x, y}
	         				},
	        				Exp[I (k0.({yy, xx}/a))] Exp[-(1/2) Norm[(A.({yy, xx}/a))]^2]
							],
       				{x, -Floor[dim/2], Floor[dim/2] - (1 - 2*Mod[dim/2, 1])},
       				{y, -Floor[dim/2], Floor[dim/2] - (1 - 2*Mod[dim/2, 1])}
       			],
      			{Theta, Pi, 2*Pi-dTheta, dTheta}
					]
   			]
   			
  		];
  	
  	
	  
	End[];
  
  

EndPackage[];

KalitzinWavelets.m

Reconstruction.m

(*************************************************************************************************************************
** Reconstruction.m
** This .m file contains functions to reconstruct an image from an 2D Orientation Score
**
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Reconstruction`",{
	"LieAnalysis`",
	"LieAnalysis`Common`",
	"LieAnalysis`Classes`Classes`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* InverseOrientationTransform2D - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[InverseOrientationScoreTransform] = {
			"Method" -> "Summation"
		};



		InverseOrientationScoreTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";



		InverseOrientationScoreTransform[ostObj_/;ValidQ[ostObj,"ObjOrientationScores2D"],opts:OptionsPattern[]] := Module[

			(* Local Variables Declaration *)
			{
				image,
				method = OptionValue["Method"]
			},

			(* Check Method in combination with the construction method *)
			And[

				MatchQ[method, "Summation" | "L2" | "Exact"]
					/. False :> (Message[InverseOrientationScoreTransform::invalidInput, ToString[method], "Method", "Should be either \"Exact\", \"L2\" or \"Summation\""]; False)

			];

			(* Reconstruct image according to specified method *)
			image = Switch[method,
				"Exact", Exact[ostObj],
				"L2", L2Adjoint[ostObj],
				"Summation" | _, Summation[ostObj]
			];

			(* Return the re-constructed image *)
			Return[image];

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Summation - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Summation[ostObj_] := Module[

			{os, image},

			(* Reorder the OS*)
			os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Integrate in theta direction and add the DC component *)
			image = Total[Re[os]] + Re[ostObj["DcFilteredImage"]];

			Return[image];
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* L2Adjoint - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		L2Adjoint[ostObj_] := Module[

			(* Local Variables Declaration *)
			{os, image, convMultiSpace},

			(* Reorder the OS *)
      os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Convolve the layers once more with the kernel *)
      convMultiSpace = MapThread[WaveletTransform[#1, {#2}] &, {os, ostObj["Wavelets"]["Data"]}];

			(* Integrate in theta direction and add the dc-component *)
			image = Total[Re[convMultiSpace]] + Re[ostObj["DcFilteredImage"]];

			(* Return the reconstructed image *)
			Return[image]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Exact - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Exact[ostObj_, threshold_:10^(-4)] := Module[

			{os, image, convMultiSpace, Mpsi, TMpsi, MphiInv,cakeF},

    	(* Reorder the OS *)
      os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Convolve all layers again with their corresponding wavelets *)
      convMultiSpace = MapThread[WaveletTransform[#1, {#2}] &, {os, ostObj["Wavelets"]["Data"]}];

			(* Compute Mphi-inverse *)
			cakeF = CenteredFourier/@ostObj["Wavelets"]["Data"];
			Mpsi = Total[Abs[cakeF]^2] + ostObj["Wavelets"]["FourierDcFilter"]^2;

			(* Chop to avoid division by small numbers *)
			TMpsi = Chop[Mpsi,threshold]/.{0->threshold};
			MphiInv = Re[CenteredInverseFourier[TMpsi^(-1)]];

			(* Convolve OS integrated over theta with Mphi and add the DC-component *)
			image = WaveletTransform[Re[Total[convMultiSpace]] + Re[ostObj["DcFilteredImage"]],{MphiInv}];

			(* Return the reconstructed image*)
			Return[Re[image]]

		];



	End[];



EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains functions to visualise 2D Orientation Scores.
** 
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Visualisation`",{
	"LieAnalysis`OrientationScores2D`Filters`CakeWavelets`"
}];


	Begin["`Private`"];
	



	End[];

EndPackage[];

Common.m

(*************************************************************************************************************************
** Common.m
** This .m file contains common function used for the 3D Orientation Scores
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Common`"];



  CartesianToSphericalCoordinates::usage = "todo"; (*todo: should not be public *)



  vertexAreas::usage = "vertexAreas [vertices_,polygons_] Determine the areas surrounding each vertex according to the formula's in thesis Franken."; (*todo: schould not be public *)



  Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* CartesianToSphericalCoordinates - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  CartesianToSphericalCoordinates[qvecs_] := CartesianToSphericalCoordinatesC[qvecs];
  CartesianToSphericalCoordinatesC = Compile[{{vec,_Real,1}},
    Module[{qx,qy,qz,R,θ,ϕ},
      {qx,qy,qz} = vec;
      R = Sqrt[qx^2+qy^2+qz^2];
      If[Chop[R]==0,Return[{0.,0.,0.}]];
      {qx,qy,qz} = {qx,qy,qz}/R;
      If[qz<1-10^-8,
        If[qz>-1+10^-8,
          {θ,ϕ} = {ArcCos[qz/Sqrt[qx^2+qy^2+qz^2]],ArcTan[qx,qy]};,
          {θ,ϕ} = {π,0};
        ];,
        {θ,ϕ} = {0,0};
      ];
      {R,θ,ϕ}
    ],RuntimeAttributes->{Listable}
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* OTHER - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  tpv[p_,v_] := p-(v.p)v;

  dS2[v1_,v2_] := ArcCos[v1.v2/(Norm[v1]Norm[v2])];

  triangleArea[v1_,v2_,v3_] := dS2[tpv[v2,v1],tpv[v3,v1]]+dS2[tpv[v1,v2],tpv[v3,v2]]+dS2[tpv[v1,v3],tpv[v2,v3]]-π;

  VertexAreas[vertices_,polygons_] := Module[

    (* Local Variables Declaration *)
    {
      areas=ConstantArray[0,Length[vertices]]
    },

    (* Compute area *)
    Map[(areas[[#]] += 1/3 triangleArea[Sequence@@(vertices[[#]])])&, polygons]; (* todo:clean *)

    (* Return area *)
    Return[areas]

  ];



End[];

EndPackage[];

Construction.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains common function used to construct the 3D Orientation Scores.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Construction`",{
  "LieAnalysis`",
  "LieAnalysis`Common`",
  "LieAnalysis`OrientationScores3D`Filters`CakeWavelets`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`Classes`ObjOrientationScores3D`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* WaveletTransform3D- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    WaveletTransform3D[data3d_, wavelet3dObj_/;ValidQ[wavelet3dObj,"ObjOrientationWavelet3D"]] := Module[

      (* Local Variables Declaration *)
      {data, objwtf3d, wt3d},

      (* Process the input *)
      data = ImageInputProcessor3D[data3d];

      (* Compute convolved volumes *)
      wt3d = WaveletTransform3D[data, wavelet3dObj["Data"]];

	    (* Create Object *)
	    objwtf3d = ObjWaveletTransform[{"Data"->wt3d, "InputData"->data, "Wavelets"->wavelet3dObj}];

      (* Return the transformed *)
      Return[objwtf3d]

    ];

    WaveletTransform3D[data3d_List, kernels_List, opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {wt3d},

      (* Compute convolved volumes *) (*todo: only the orientation scores should be transposed, so re-locate *)
      wt3d = Transpose[MonitoredMap[ListConvolve[#, data3d, Ceiling[Dimensions[#]/2]] &, Conjugate[kernels]], {4, 1, 2, 3}];

      (* Remove Brackets if only a single kernels is used *)
      wt3d = If[Length[kernels] == 1,
        wt3d[[All,All,All,1]],
        wt3d
      ];

      (* Return the transformed image *)
      Return[wt3d]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrientationScoreTransform3D - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[OrientationScoreTransform3D] = Join[
      {
        "Orientations"  ->  12,
        "WaveletSize"   ->  11
      },
      Options[CakeWaveletStack3D]
    ];
    OrientationScoreTransform3D[data3d_, opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {
        waveletSize = OptionValue["WaveletSize"],
        orientations = OptionValue["Orientations"],
        cws3d, os3d
      },

      (* Create cakewaveletstack 3d *)
      cws3d = CakeWaveletStack3D[orientations,waveletSize,FilterRules[{opts}, Options[CakeWaveletStack3D]]];

      (* Compute OS 3D *)
      os3d = OrientationScoreTransform3D[data3d, cws3d];

      (* Return the Orientation Score Transformed image *)
      Return[os3d];

    ];

    OrientationScoreTransform3D[data3d_, cwsObj3D_/;ValidQ[cwsObj3D, "ObjCakeWavelet3D"]] := Module[

      (* Local Variables Declaration *)
      {data, dc3d, os3d, os3dObj},

      (* Process the input *)
      data = ImageInputProcessor3D[data3d];

      (* Compute wavelet transform *)
      os3d = WaveletTransform3D[data, cwsObj3D];
      os3dObj = ConvertClass[os3d,"ObjOrientationScores3D"];

	    (* DC Component *)
      dc3d = WaveletTransform3D[data,{cwsObj3D["DcFilter"]}];
      os3dObj = SetElement[os3dObj, "DcData" -> dc3d];

      (* Return the Orientation Score Transformed image *)
      Return[os3dObj];

    ];



  End[];



EndPackage[];

CakeWavelets.m

(*************************************************************************************************************************
** CakeWavelets.m
** This .m file contains functions to create a 3D Cake Wavelet Stack.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Filters`CakeWavelets`",{
  "PolyhedronOperations`",
  "LieAnalysis`Common`",
  "LieAnalysis`OrientationScores3D`Common`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`OrientationScores3D`Filters`ElectrostaticRepulsion`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* CakeWaveletStack3D- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[CakeWaveletStack3D] = {
      "Epsilon" -> 0.00001,
      "SigmaAngle" -> Automatic,
      "Gamma" -> 0.8,
      "DcStandardDeviation" -> 8
    };



    CakeWaveletStack3D[nOrientations_, size_, opts:OptionsPattern[]] := Module[

      (*Local Variables Declaration *)
      {
        epsilon = OptionValue["Epsilon"],
        sigmaAngle = OptionValue["SigmaAngle"],
        gamma = OptionValue["Gamma"],
        DcStandardDeviation = OptionValue["DcStandardDeviation"],
        sigmaRadialHigh, cws3dObj
      },

      (*todo: input validation *)


      (* Compute the radial (gaussian) cut-off *)
      sigmaRadialHigh = (1 - gamma)/2;

      (* Create cakewavelet 3d stack *)
      cws3dObj = CakeWaveletStack3D[nOrientations, size, sigmaAngle, gamma, sigmaRadialHigh, DcStandardDeviation, epsilon];

      (*Return the Orientation Score Transformed image *)
      Return[cws3dObj]

    ];

    CakeWaveletStack3D[nOrientations_, size_, sigmaAngle2_, gamma_, sigmaRadialHigh_, DcStandardDeviation_, epsilon_] := Module[

      (* Local Variables Declaration *)
      {
        DcStandardDeviationF = (*size*)1/(DcStandardDeviation*Pi), (* Convert Dc Standard deviation to the Fourier Domain *)
        scale, sigmaAngle = sigmaAngle2, (*todo: *)
        shCoefficientsVector, shAntiSymmetrizationMatrix,shFunkTransformMatrix,shRotationMatrix,sphericalHarmonicsTable,
        dcFilter, wavelet, waveletF, gridOrientationCoords, lMax, waveletOrientationCoords, cake3DObj,
        mnWindow3D, dcWindow3D, topology, mesh, meanVertexDistance
      },

      (* Create container to store the wavelets in *)
      cake3DObj = ObjCakeWavelet3D[];

      (* Compute uniform sampled coordinates on the sphere *)
      waveletOrientationCoords = SphereSampleCoordinates[nOrientations];
      waveletOrientationCoords = Join[waveletOrientationCoords,-waveletOrientationCoords];
      cake3DObj = SetElement[cake3DObj, "OrientationList" -> waveletOrientationCoords];

      (* Determine the neighbors of each orientation *)
      mesh = ConvexHullMesh[waveletOrientationCoords];
      topology = MeshCells[mesh,2][[All,1]];

      (* Compute the sigmaAngle based on the orientations *)
      If[sigmaAngle === Automatic,
        meanVertexDistance = Mean[ArcCos[#[[1]].#[[2]]] & /@ MeshPrimitives[mesh, 1][[All, 1]]];
        sigmaAngle = (meanVertexDistance/2)/Sqrt[Log[2]];
      ];
      scale = (sigmaAngle^2) / 2;(* Scale of the spherical harmonics *)

      (* Compute maximal SH order L *)
      lMax = DetermineMaxOrderL[scale, epsilon];

      (* Compute grid upon which the Spherical Harmonics can be defined *)
      gridOrientationCoords = GridOrientationsTable[size];

      (* Compute the spherical harmonics table upon the gridOrientations up to order lMax *)
      sphericalHarmonicsTable = SphericalHarmonicTable[gridOrientationCoords, lMax, False]; (* = Y_{m}^{l}(\theta, \phi) *)

      (* Create the matrix used to perform the rotation of a function expressed in spherical harmonics towards the input orientation *)
      shRotationMatrix = SphericalHarmonicsRotateMatrix[waveletOrientationCoords, lMax, True];(* R_{\alpha, \beta, \gamma} *)

      (* Create the matrix used to perform the Funk transform of a function expressed in spherical harmonics *)
      shFunkTransformMatrix = SphericalHarmonicFunkTransformMatrix[lMax]; (* c -> 2Pi P_l c *)

      (* Create the matrix used to perform the anti-symmetrization of a function expressed in spherical harmonics *)
      shAntiSymmetrizationMatrix = SphericalHarmonicAntiSymmetrizationMatrix[lMax]; (* c -> (1 - (-1)^l ) c *)

      (* Determine Spherical Harmonic coefficients up to order 'L' of the diffusion kernel for diffusion scale 'scale' *)
      shCoefficientsVector = SphericalHarmonicCoefficientsDiffusionKernel[lMax, scale];

      (* Apply the Funk-transform and Anti-Symmetrization to the coefficients *)
      shCoefficientsVector = shFunkTransformMatrix.shCoefficientsVector + shAntiSymmetrizationMatrix.shCoefficientsVector; (* F.s + A.s *)

      (* Rotate (steer the coefficients) the wavelets *)
      shCoefficientsVector = Map[#.shCoefficientsVector &, shRotationMatrix];

      (* Compute/'Lookup' wavelets from spherical harmnics table *)
      waveletF = MonitoredMap[ArrayReshape[ sphericalHarmonicsTable.# , {2 size+1, 2 size+1, 2 size+1}]&, shCoefficientsVector];

       (*Remove the high frequencies by applying a Gaussian decay in radial direction*)
      mnWindow3D = MnWindow3D[size, sigmaRadialHigh, gamma];  (*analog to the MnWindow in the 2D case*)

       (*Remove the low frequencies by removing the center*)
      dcWindow3D =  DcWindow3D[size, DcStandardDeviationF];

       (*Apply dc and mn window to the fourier wavelet*)
      waveletF = Map[#*(mnWindow3D - dcWindow3D)&, waveletF];
      cake3DObj = SetElement[cake3DObj, "WaveletsF" -> waveletF];

       (*Compute Fourier Inverse*)
      wavelet = Map[CenteredInverseFourier, waveletF];
      dcFilter = CenteredInverseFourier[dcWindow3D];

       (*Add meta-data*)
      cake3DObj = SetElement[cake3DObj, {
        "Data"                      -> wavelet,
        "RadialWindow"              -> (mnWindow3D - dcWindow3D),
        "DcFilter"                  -> dcFilter,
        "OrientationCoordinates"    -> waveletOrientationCoords,
        "Topology"                  -> topology,
        "Method"                    -> "JMIV",
        "LMax"                      -> lMax,
        "SigmaAngle"                -> sigmaAngle,
        "DcStandardDeviation"       -> DcStandardDeviationF,
        "MnStandardDeviation"       -> sigmaRadialHigh,
        "Gamma"                     -> gamma,
        "Epsilon"                   -> epsilon
      }];

       (*Return the 3D cake wavelets*)
      Return[cake3DObj]
    ];


    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* MnWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    MnWindow3D[size_, MnStandardDeviation_, gamma_] := RadialFunctionErf[MnStandardDeviation,gamma(**size*)][GridRadiusTable[size]];
    NormalizedComplementaryErrorFunction[x_,sigma_,offset_]:=Erfc[(x-offset)/(Sqrt[2]sigma)]/2;
    RadialFunctionErf[sigmaHigh_,offsetHigh_] := Compile[{ρ}, Evaluate[NormalizedComplementaryErrorFunction[ρ,sigmaHigh,offsetHigh]], RuntimeAttributes -> {Listable}];


    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DcWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DcWindow3D[size_, DcStandardDeviation_] := Gaussian[DcStandardDeviation][GridRadiusTable[size]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DcWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Gaussian[sigma_] := Compile[{ρ},Evaluate[E^(-ρ^2/(2*sigma^2)) ],RuntimeAttributes->{Listable}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicFunkTransformMatrix- - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicCoefficientsDiffusionKernel[L_,so_] := Table[If[SHIndexj2m[j]==0, Sqrt[(2SHIndexj2l[j]+1)/(4π)] E^(-(SHIndexj2l[j]+1)SHIndexj2l[j]so), 0], {j, 1, SHIndexlm2j[L, L]}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicFunkTransformMatrix- - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicFunkTransformMatrix[s_List] := s.Transpose[SphericalHarmonicFunkTransformMatrix[SHIndexj2l[Dimensions[s][[-1]]]]];
    SphericalHarmonicFunkTransformMatrix[lMax_Integer] := DiagonalMatrix[Table[LegendreP[SHIndexj2l[jp],0],{jp,1,SHIndexlm2j[lMax,lMax]}]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicsRotateMatrix- - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicAntiSymmetrizationMatrix[s_List]:=s.Transpose[SHAntiSymmetrization[SHIndexj2l[Dimensions[s][[-1]]]]];
    SphericalHarmonicAntiSymmetrizationMatrix[lMax_Integer] := DiagonalMatrix[Table[1-(-1)^SHIndexj2l[jp],{jp,1,SHIndexlm2j[lMax,lMax]}]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicsRotateMatrix- - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicsRotateMatrix[gridOrientationCoords_, lMax_, symmetricQ_]:= Module[

      {gamma, beta, rotationMatrix},

      (* Convert to Spherical coordinates *)
      {beta, gamma} = Transpose[CartesianToSphericalCoordinates[gridOrientationCoords], Join[Range[2, Depth[gridOrientationCoords] - 1], {1}]][[2;;3]];

      (* Compute rotation matrix *)
      rotationMatrix = RotateMatrixZY[gamma, beta, lMax, symmetricQ];

      (* Return the rotation Matrix *)
      Return[rotationMatrix];

    ];
    RotateMatrixZY[gamma_, beta_, lMax_, symmetricQ_]:= Module[

      {compiledRotateZY, j = SHIndexlm2j, l = SHIndexj2l, m = SHIndexj2m},

      compiledRotateZY = Compile[{ggamma, bbeta},
        Evaluate[
          Module[{M = ConstantArray[0, {j[lMax, lMax], j[lMax, lMax]}]},
            If[symmetricQ,
              Table[(M[[jp, j[l[jp], 0]]] = WignerD[{l[jp], m[jp], 0}, -ggamma, -bbeta, 0]), {jp, 1, j[lMax, lMax]}],(*we only need the values for m' = 0*)
              Table[Table[(M[[jp, j[l[jp], mp]]] = WignerD[{l[jp], m[jp], mp}, -ggamma, -bbeta, -0]), {mp, -l[jp], l[jp]}], {jp, 1, j[lMax, lMax]}]
            ];
            M
          ]
        ],
        RuntimeAttributes-> Listable
      ];

      Return[compiledRotateZY[gamma, beta]]

    ];
    (*todo: SHCompiledRotateZYZ function? Where is it good for? *)



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DetermineMaxOrderL- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DetermineMaxOrderL[so_,epsilon_:10^-3] := Module[

    (* Local Varialble Declaration *)
      {l = 0},

    (* Compute the maximal order L *)
      While[
        Sqrt[(2l+1)] * E^(-(l+1)l * so)>epsilon,
        l++
      ];

      (* Return the maximal order *)
      Return[l];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicTable- - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


    (* todo: cleanup *)
    SphericalHarmonicTable[orientations_, lMax_Integer, bool_] := Module[{Mmat, R, θ, ϕ, CSHT = CompiledSHTable[lMax,bool]},
      {R, θ, ϕ} = Transpose[CartesianToSphericalCoordinates[orientations], Join[Range[2, Depth[orientations] - 1], {1}]];
      Mmat = CSHT[θ, ϕ];
      Mmat
    ];
    CompiledSHTable[lMax_Integer, bool_] := Compile[{th,ph},
      Evaluate[
        If[bool,
          N[ExpToTrig[Table[If[SHIndexj2m[j]==0,SphericalHarmonicY[SHIndexj2l[j],SHIndexj2m[j],th,ph],0],{j,1,(lMax+1)^2}]]],
          N[ExpToTrig[Table[SphericalHarmonicY[SHIndexj2l[j],SHIndexj2m[j],th,ph],{j,1,(lMax+1)^2}]]]
        ]
      ],
      RuntimeAttributes->Listable
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GridOrientationsTable - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GridOrientationsTable = Compile[{{size,_Integer}},

      Flatten[
        Table[
          If[x==0&&y==0&&z==0,
            {1,0.,0.},
            N[{x,y,z}/Sqrt[x^2+y^2+z^2]]
          ],
          {x,-size,size},{y,-size,size},{z,-size,size}
        ],2
      ]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GridRadiusTable - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GridRadiusTable = Compile[{{size,_Integer}},

      Table[
        Sqrt[x^2+y^2+z^2]/size,
        {x,-size,size},{y,-size,size},{z,-size,size}
      ]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Spherical Harmonics Indexing Functions- - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SHIndexj2l[j_] := Floor[Sqrt[j-1]];
    SHIndexlm2j[l_,m_] := Round[(l^2+l+1)+m];
    SHIndexj2m[j_] := Round[j-1-(SHIndexj2l[j])^2-SHIndexj2l[j]];




End[];



EndPackage[];

ElectrostaticRepulsion.m

(*************************************************************************************************************************
** ElectrostaticRepulsion.m
** This .m file contains common function used for evenly sampled sphere by using electrostatic repulsion on a sphere.
**
** Author: M. Froeling <~>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Filters`ElectrostaticRepulsion`"];

  SphereSampleCoordinates::usage = "
    todo
    ";

  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphereSampleCoordinates - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphereSampleCoordinates[n_, iterations_:1000] := Block[

      (* Local Variables Declaration*)
      {points},

      (* Random sample the sphere *)
      points = SphereRandomSampleCoordinates[n];

      (* Optimize the position in 'iterations' steps *)
      Do[
        points = GradOptimize[points],
        {iterations}
      ];

      (* Return the (alsmost) uniformly sampled sphere coordinates *)
      Return[points];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphereRandomSampleCoordinates - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphereRandomSampleCoordinates[n_] := Sign[#[[3]]+10.^-16] Normalize[#]&/@RandomReal[NormalDistribution[],{n,3}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GradOptimize  - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GradOptimize = Compile[{{points,_Real,2}},Block[

      {n,pointsi,pointsmat,distmatxyz,diag,distmat,velocity,pointsnew},

      n=Length[points];
      pointsi=Join[points,-points];
      pointsmat=ConstantArray[pointsi,2 n];
      distmatxyz=pointsmat-Transpose[pointsmat];
      diag=DiagonalMatrix[ConstantArray[10.^32,2 n]];
      distmat=Total[Transpose[distmatxyz,{2,3,1}]^2]+diag;
      velocity=Total[(distmatxyz/distmat)];
      pointsnew=(Sign[#[[3]]+10.^-16]*Normalize[#]&/@(pointsi+velocity));
      pointsnew[[;;n]]]

    ];



  End[];



EndPackage[];

Reconstruction.m

(*************************************************************************************************************************
** Reconstruction.m
** This .m file contains functions used for the reconstrucion of 3D Orientation Scores.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Reconstruction`",{
  "LieAnalysis`",
  "LieAnalysis`Common`",
  "LieAnalysis`Classes`Classes`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* InverseOrientationScoreTransform3D- - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[InverseOrientationScoreTransform3D] = {
      "Method" -> "Summation"
    };



    InverseOrientationScoreTransform3D::optfail = "The value provided for option `1` is not recognised. Should `2`";



    InverseOrientationScoreTransform3D[ost3dObj_/;ValidQ[ost3dObj, "ObjOrientationScores3D"], opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {
        image,
        method = OptionValue["Method"]
      },

      If[!MatchQ[method, "Summation" | "L2" |"Exact"], Message[InverseOrientationScoreTransform3D::optfail, "Method","be either \"Summation\", \"L2\" or \"Exact\""]];

      (* Execute the chosen reconstruction method *)
      image = Switch[method,
        "Exact", Exact[ost3dObj],
        "L2", L2Adjoint[ost3dObj],
        "Summation" | _, Summation[ost3dObj]
      ];

      (* Return the Orientation Score Transformed image *)
      Return[image];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summation - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Summation[ost3dObj_] := Module[

      {
        os, data
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[ost3dObj["Data"], {2, 3, 4, 1}];

      (* Integrate over the orientation *)
      data = Total[Re[os]]*( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]];

      (* Return the re-constructed volume *)
      Return[data];

    ];




    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* L2Adjoint - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    L2Adjoint[ost3dObj_] := Module[

      {
        image,
        os = ost3dObj["Data"],
        kernels = ost3dObj["Wavelets"]["Data"]
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[os, {2, 3, 4, 1}];

      (* Correlate the os-layers with their corresponding wavelet *)
      image = MapThread[ListCorrelate[#1, #2, Floor[Dimensions[#1]/2]]&,{kernels,os}];

      (* Integrate the orientation *)
      image = Re@Total[image] *( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]];

      (* Return re-constructed volume *)
      Return[image]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Exact - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Exact[ost3dObj_, threshold_:10^(-4)] := Module[

      {
        os, TMPsi, recmediate, rec, filtersF, Mpsi, data, MpsiInv,
        kernels = ost3dObj["Wavelets"]["Data"]
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[ost3dObj["Data"], {2, 3, 4, 1}];

      (* Convole all layers again with their corresponding wavelets*)
      recmediate = MapThread[ListCorrelate[#1, #2, Floor[Dimensions[#1]/2]]&,{kernels,os}];

      (* Compute Mphi *)
      filtersF = CenteredFourier/@ost3dObj["Wavelets"]["Data"];
      Mpsi = Total[Abs[filtersF]^2] + CenteredFourier[ost3dObj["Wavelets"]["DcFilter"]]^2;

      (* Chop to avoid division small numbers *)
      TMPsi = Chop[Mpsi, threshold] /. {0 -> threshold};
      MpsiInv = CenteredInverseFourier[TMPsi^(-1)];

      (* Add the dc-component and integrate the orientations *)
      data = WaveletTransform3D[Re[Total[recmediate]] * ( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]],{MpsiInv}];

      (* Undo construction and deconstruction effect of the used kernel *)
      Return[data]

    ];



  End[];



EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains functions to visualise 3D Orientation Scores.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Visualisation`", {
  "LieAnalysis`Classes`Classes`"
  }
];



  Begin["`Private`"];




  End[];



EndPackage[];

Common.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains common functions used for the LieAnalysis package
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: R.Duits <r.duits@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Common`"];



	MonitoredMap::usage 						= "
	MonitoredMap[f, expr, levelspec], behaves the same as Map and MapIndexed only shows the progress
	with an ProcessIndicator bar. This should only be used if a single itteration consumes a lot
	of time, else the overhead of creating the ProcessIndicator is larger than the computation itself.
	";

	ImageInputProcessor::usage 			= "
	ImageInputProcessor[image], tries to create a valid image data matrix from the input.
	It convert a RGB image to Grayscale and converts graphics and images to image data
	";

	ImageInputProcessor3D::usage 		= "
	ImageInputProcessor3D, tries to create valid 3D volume from the input. It converts Image3D to imagedata.
	";

	CenteredFourier::usage 					= "
	CenteredFourier[im_], computes the Fourier and puts the low frequencies to the center.
	Note that the Fourier transformed is not scaled to the number of samples!
	";

	CenteredInverseFourier::usage 	= "
	CenteredInverseFourier[im_], computes the Inverse Fourier from a Centered Fourier spectrum.
	";

	Reorder::usage = "
	Reorder[os_, order_], reorders the orientation array. Order can either be 'OrientationFirst' or 'SpatialFirst'
	";



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CenteredFourier- - - - - - - - - - - - - - - - - - - - -  *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		CenteredFourier[data_]:=
      RotateRight[Fourier[RotateLeft[data,Floor[Dimensions[data]/2]],FourierParameters->{1,1}],Floor[Dimensions[data]/2]];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CenteredInverseFourier - - - - - - - - - - - - - - - - -  *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		CenteredInverseFourier[data_]:=
			RotateRight[InverseFourier[RotateLeft[data,Floor[Dimensions[data]/2]],FourierParameters->{1,1}],Floor[Dimensions[data]/2]];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ImageInputProcessor - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		ImageInputProcessor::fail = "Failed to create imagedata from input...";



  	ImageInputProcessor[im_] := Block[

			(* Local Variables Declaration *)
			{image},

			(* Convert Graphics or Image to Image Data *)
			image = If[ImageQ[im] || Head[im] === Graphics, ImageData[im], im];

			(* If the image is an RGB image, convert it to grayscale *)
			image = If[Length[Dimensions[image]] > 2,ImageData[ColorConvert[Image[image],"Grayscale"]],image];

			(* If the output is not an matrix abort the execution *)
      If[!MatrixQ[image], Message[ImageInputProcessor::fail]; Abort[];];

			(* Else return the imagedata *)
			Return[image]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ImageInputProcessor - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		ImageInputProcessor3D[data_] := Block[

			{image = data},

			(* Convert 3D image to data *)
			image = If[Head[image] === Image3D, ImageData@image, image];

			If[!MatchQ[image, {{{__}..}..}], ImageInputProcessor::fail];

			Return[image]

		];


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* MonitoredMap- - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[MonitoredMap] = Options[MapIndexed];



		MonitoredMap[f_, expr_, lvl_:{1}, opts:OptionsPattern[]] := Block[

			(* Local Variable Declaration *)
			{
				n = Length[expr],
				i
			},

			Monitor[
				MapIndexed[(i = First[#2]; f[#1]) &, expr, lvl, opts],
				ProgressIndicator[i/n]
			]
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Reorder - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		Reorder[os_, order_String] := Which[
			(MatchQ[order, "OrientationFirst"] && Depth[os] == 4),
				Transpose[os, {2,3,1}],
      (MatchQ[order, "SpatialFirst"] && Depth[os] == 4),
				Transpose[os, {3,1,2}],
      (MatchQ[order, "OrientationFirst"] && Depth[os] == 5),
				Transpose[os, {2,3,4,1}],
      (MatchQ[order, "SpatialFirst"] && Depth[os] == 5),
				Transpose[os, {4,1,2,3}]
		];



	End[];



EndPackage[];

LieAnalysis.m

(*************************************************************************************************************************
** LieAnalysis.m
** This .m file contains the usage messgaes from the public functions
**
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`"];



	(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
	(* OrientationScores2D - - - - - - - - - - - - - - - - - - - *)
	(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



	GaborWaveletStack::usage = "
	GaborWaveletStack[n_, scale_, OptionsPattern_] returns a set of 'n' rotated Gabor wavelets at scale, with standard or specified paramters.
	";
	GaborWaveletStack = LieAnalysis`OrientationScores2D`Filters`GaborWavelets`Private`GaborWaveletStack;



	CakeWaveletStack::usage = "
	CakeWaveletStack[n_, OptionsPattern_] creates a 2D Cake Wavelet Stack with 'n' orientations and using the default or specified parameters.
	";
	CakeWaveletStack = LieAnalysis`OrientationScores2D`Filters`CakeWavelets`Private`CakeWaveletStack;



	WaveletTransform::usage = "
	WaveletTransform[image_, kernels_] correlates each kernel with the image and returns the stack of transformed images.
	WaveletTransform[image_, ObjWavelet] correlates each kernel from the ObjWavelet with the image and returns the stack of transformed images.
	";
	WaveletTransform = LieAnalysis`OrientationScores2D`Construction`Private`WaveletTransform;



	OrientationScoreTransform::usage = "
	OrientationScoreTransform[image, OptionsPattern_] constructs an Orientation Score from the image using Cake Wavelets with default or suplied paramters.
	OrientationScoreTransform[image, ObjOrientationWavelet_] constructs an Orientation Score from the image using the ObjOrientationWavelet.
	";
	OrientationScoreTransform = LieAnalysis`OrientationScores2D`Construction`Private`OrientationScoreTransform;



	InverseOrientationScoreTransform::usage = "
	InverseOrientationScoreTransform[ObjOrientationScores2D_, OptionsPattern_] performs reconstruction of the data from an Orientation Score 2D by (default) Summation, L2 or Exact.
	";
	InverseOrientationScoreTransform = LieAnalysis`OrientationScores2D`Reconstruction`Private`InverseOrientationScoreTransform;



	OrientationScoreDerivatives::usage = "
	OrientationScoreDerivatives[objOrientationScores2D_, OptionsPattern_] computes the derivatives of the SE(2) space. By default the cartesian derivatives are returned.
	";
	OrientationScoreDerivatives = LieAnalysis`OrientationScores2D`Derivatives`OrientationScoreDerivatives`Private`OrientationScoreDerivatives;



	(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
	(* OrientationScores3D - - - - - - - - - - - - - - - - - - - *)
	(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



	CakeWaveletStack3D::usage = "
	CakeWaveletStack3D[n, size_, OptionsPattern_] constructs a 3D cakewavelet stack with 'n' orientations with dimensions sizexsizexsize and using default parameters.
	";
	CakeWaveletStack3D = LieAnalysis`OrientationScores3D`Filters`CakeWavelets`Private`CakeWaveletStack3D;



	OrientationScoreTransform3D::usage = "
  OrientationScoreTransform3D[data_,OptionsPatten_] creates an orientation score from data using the default parameters or the specified options for the cake wavelets.
  OrientationScoreTransform3D[data_, ObjCakeWavelet3D_] creates an orientation score using the supplied ObjCakeWavelet3D
  ";
	OrientationScoreTransform3D = LieAnalysis`OrientationScores3D`Construction`Private`OrientationScoreTransform3D;



	WaveletTransform3D::usage = "
  WaveletTransform3D[data_, kernels_] computes the wavelet transform of data using the supplied kernels.
  WaveletTransform3D[data_, ObjWavelet3D] computes the wavelet transform of data using the kernels from ObjWavelet3D.
  ";
	WaveletTransform3D = LieAnalysis`OrientationScores3D`Construction`Private`WaveletTransform3D;



	InverseOrientationScoreTransform3D::usage = "
  InverseOrientationScoreTransform3D[data_, ObjOrientationScores3D_, OptionsPattern_] performs reconstruction of the data from an Orientation Score 3D by (default) Summation, L2 or Exact.
  ";
	InverseOrientationScoreTransform3D = LieAnalysis`OrientationScores3D`Reconstruction`Private`InverseOrientationScoreTransform3D;



EndPackage[];

PacletInfo.m

(* Paclet Info File *)

(* created 2016/05/27*)

Paclet[
    Name -> "LieAnalysis",
    Version -> "0.0.1",
    MathematicaVersion -> "9+",
    Extensions -> 
        {
            {"Documentation", Language -> "English", MainPage -> "Guides/OrientationScores2D"}
        }
]

Ultilities.m

BeginPackage["LieAnalysis`Ultilities`"];


	


	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* EmbedLieAnalysisPackage - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		
	
	
	End[];



EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains common function used to store visualization function used in more than one specific area
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Visualisation`",{
  "LieAnalysis`OrientationScores2D`Visualisation`",
  "LieAnalysis`OrientationScores3D`Visualisation`",
  "LieAnalysis`Classes`ObjCakeWavelet`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`Classes`ObjOrientationScores2D`",
  "LieAnalysis`Classes`ObjOrientationScores3D`"
}];



QuickVisualise::usage = "QuickVisualise[Object] visualises the object using its default visualisation.";



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* QuickVisualise- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


    (* ObjOrientationScores2D *)
    QuickVisualise[cwsObj_ObjOrientationScores2D] := Module[

      {
        data = cwsObj["Data"],
        orientation,max
      },

      orientation = Transpose@Map[First@First@Position[#,Max[#]]&,Transpose[Re[data],{3,2,1}],{2}];
      orientation = orientation-Min[orientation];
      orientation = orientation/Max[orientation];

      max = Transpose@Map[Max,Transpose[Re[data],{3,2,1}],{2}];
      max = max/Max[max];

      SetAlphaChannel[
        Colorize[ImageAdjust[Image[orientation]], ColorFunction -> Function[orientation, Hue[orientation]]],
        Image[max]
      ]
    ];



    (* ObjOrientationScores3D *)
    QuickVisualise[cwsObj_ObjOrientationScores3D] := Module[

      {},

      Graphics[Text["To be implemented"]]

    ];


    (* ObjCakeWavelet2D *)
    QuickVisualise[cwsObj_ObjCakeWavelet] := Manipulate[
          ImageAdjust[Image[Re@cwsObj["Data"][[i]], ImageSize -> 300]],
          {{i, 1, "Orientation"}, 1, Length[cwsObj["Data"]], 1}
    ];



    (* ObjCakeWavelet3D *)
    QuickVisualise[Cake3DObj_ObjCakeWavelet3D] := Module[

      {
        wRe = Re[Cake3DObj["Data"][[1]]],
        cs = ContourStyle -> {Directive[Blue, Opacity[0.8], Specularity[White, 30]], Directive[Orange, Opacity[0.8], Specularity[White, 30]]},
        otherOptions
      },

      otherOptions = Sequence @@ {ImageSize -> 150, Mesh -> False, cs, MaxPlotPoints -> 50};

      ListContourPlot3D[
        wRe,
        otherOptions,
        Contours -> {0.155 Min[wRe], 0.06 Max[wRe]},
        PerformanceGoal->"Speed",
        Axes->False, Boxed->False
      ]
    ];



  End[];



EndPackage[];

Ultilities.m

BeginPackage["LieAnalysis`Ultilities`"];


	


	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* EmbedLieAnalysisPackage - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		
	
	
	End[];



EndPackage[];

Common.m

(*************************************************************************************************************************
** Common.m
** This .m file contains common function used for the orientation scores 2D.
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Common`"];

	ErfSet::usage = "todo";

	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ErfSet- - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		ErfSet = Compile[{{size,_Real},{No,_Integer},{periodicity, _Real}}, 
			Module[{dθ},
				Table[
					Table[
						1/2 (1+Erf[x Cos[(periodicity (-1+i))/No]+y Sin[(periodicity (-1+i))/No]]),
						{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
						{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
					],
					{i,1,No}
				]
			]
		];

	
	

	End[];



EndPackage[];

Classes.m

BeginPackage["LieAnalysis`Classes`Classes`"];

Unprotect[
  ClearElement,
  ConvertClass,
  DeclareClass,
  DeclareInvariant,
  ElementQ,
  GetElement,
  GetProperty,
  ValidQ,
  PropertyQ,
  SetElement,
  SubpropertyQ
];

(*Usage messages*)
ClearElement::usage =
    "ClearElement[instance, elem] returns a copy of instance with the \
value of the element elem reverted to its default.";
ConvertClass::usage = "ConvertClass[instance, class] tries to convert \
instance to the class class by selecting the elements of instance \
that are class elements of class, and changing the head of the \
expression."
DeclareClass::usage =
    "DeclareClass[superclass, class, {e_1 -> d_1, e_2 -> d_2, \
…}] declares a class class with structure inherited from \
superclass and with elements e_i and corresponding defaults d_i.";
DeclareInvariant::usage =
    "DeclareInvariant[class, invariant] declares the invariant for the \
class class based on the pure function invariant.";
ElementQ::usage =
    "ElementQ[class, element] checks whether element exists for class.";
GetElement::usage =
    "GetElement[instance, e] returns the value associated with the \
element e in instance.";
GetProperty::usage =
    "GetProperty[instance, prop] requests the property prop from \
instance.";
ValidQ::usage =
    "ValidQ[expr, class] tests whether expr satisfies the invariant of \
class.";
PropertyQ::usage =
    "PropertyQ[class, property] checks whether property exists for \
class.";
SetElement::usage =
    "SetElement[instance, e -> val] returns a copy of instance with the \
value of the elem e changed to val.";
SubpropertyQ::usage =
    "SubpropertyQ[class, property, subproperty] checks whether there \
exists a subproperty of property for class matching subproperty.";

(*Messages*)
ConvertClass::failed = "Failed to convert to target class `1`";
DeclareClass::nvclss =
    "The symbol `1` is not free of associated definitions.";
DeclareClass::ilelem =
    "One or more illegal elements supplied to function.";
DeclareClass::ilform =
    "Ill-formed invariant supplied to the option \"Invariant\". \
Invariants should be presented as pure functions.";
DeclareClass::ilinv =
    "The supplied default elements `1` do not satisfy the class \
invariants of `2` or one of its superclasses.";
DeclareInvariant::ilinv =
    "The default elements of `1` do not satisfy the supplied class \
invariant.";
General::subpx =
    "No definition/value was found for the property `1` in combination \
with the supplied subproperties `2`.";
General::propx = "No definition/value was found for the property `1`.";
ValidQ::noinv = "No invariant is defined for the class `1`.";
ValidQ::exfail =
    "The expression fails to satisfy the invariant of the class `1`.";
ValidQ::elfail =
    "The element `1` fails to satisfy the invariant of the class `2`; \
the element should `3`.";
SetElement::fail = "The set operation failed.";

(*Turn off messages*)
Off[ValidQ::noinv];

(*Options*)
Options[DeclareClass] = {"Invariant" -> None};
Options[ValidQ] = {"CheckParents" -> True, "Verbose" -> False};

(*Syntax*)
SyntaxInformation[ClearElement] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[ConvertClass] = {"ArgumentsPattern" -> {_, _, _}};
SyntaxInformation[
  DeclareClass] = {"ArgumentsPattern" -> {_, _., _,
  OptionsPattern[]}};
SyntaxInformation[DeclareInvariant] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[ElementQ] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[GetElement] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[
  GetProperty] = {"ArgumentsPattern" -> {_, _, OptionsPattern[]}};
SyntaxInformation[
  ValidQ] = {"ArgumentsPattern" -> {_, _., OptionsPattern[]}};
SyntaxInformation[PropertyQ] = {"ArgumentsPattern" -> {_, _}};
SyntaxInformation[SubpropertyQ] = {"ArgumentsPattern" -> {_, _, __}};
SyntaxInformation[SetElement] = {"ArgumentsPattern" -> {_, ___}};

Begin["`Private`"];

(*$Classes variable instantiation*)
Module[{protected},

  Internal`WithLocalSettings[

    protected = Unprotect[$Classes],

    ClearAll[$Classes];
    $Classes[] = {},

    Protect @@ protected

  ]

];

(*Elisions icon*)
$ElisionsIcon = Graphics[
  {
    GrayLevel[0.5],
    Polygon[{{0.85, 0.15}, {0.15, 0.5}, {0.85, 0.85}, {0.4, 0.5}}]
  },
  PlotRange -> {{-0.05, 1.05}, {-0.05, 1.05}},
  Background -> GrayLevel[0.93],
  FrameStyle -> Directive[Thickness[Tiny], GrayLevel[0.7]],
  Axes -> False,
  AspectRatio -> 1,
  ImageSize ->
      Dynamic[{Automatic, (3.5*CurrentValue["FontCapHeight"])/
          AbsoluteCurrentValue[Magnification]}],
  Frame -> True,
  FrameTicks -> None,
  FrameStyle ->
      Directive[Opacity[0.5], Thickness[Tiny],
        RGBColor[0.368417, 0.506779, 0.709798]]
];

(*Test to check for associated values*)
NoValueQ[symbol_Symbol] := SameQ[
  Attributes[symbol],
  DownValues[symbol],
  FormatValues[symbol],
  NValues[symbol],
  OwnValues[symbol],
  SubValues[symbol],
  UpValues[symbol],
  {}
];
NoValueQ[___] := False;

(*Element operations*)
ClearElement[class_Symbol[elems : OptionsPattern[]], elem_String] :=
    class @@ FilterRules[{elems}, Except[elem]];
GetElement[class_Symbol[elems : OptionsPattern[]], elem_String] :=
    Quiet[OptionValue[class, {elems}, elem], OptionValue::nodef];
SetElement[class_Symbol[elems : OptionsPattern[]],
  opts : OptionsPattern[]] :=
    Module[{output = class[{opts, elems}]}, output /; ValidQ[output]];
SetElement[_, OptionsPattern[]] := Null /; Message[SetElement::fail];

(*Element existence check*)
ElementQ[class_Symbol, element_String] :=
    MatchQ[element, Alternatives @@ class[]["ClassElements"]];

(*Property evaluation*)
GetProperty[instance_?ValidQ, prop_String] := instance[prop];
expr : GetProperty[instance_?ValidQ, prop_String,
  opts__?OptionQ] := (Message[GetProperty::optx, {opts},
  HoldForm[expr]]; instance[prop]);
GetProperty[instance_?ValidQ, prop : {_String, ___}] :=
    instance @@ prop;
expr : GetProperty[instance_?ValidQ, prop : {_String, ___},
  opts__?OptionQ] := (Message[GetProperty::optx, {opts},
  HoldForm[expr]]; instance @@ prop);


(*Property existence checks*)
PropertyQ[class_Symbol, property_String] :=
    MatchQ[property, Alternatives @@ class[]["ClassProperties"]];
SubpropertyQ[class_Symbol, property_String, subprop__] :=
    MatchQ[Hold[subprop],
      Alternatives @@ class[]["ClassProperties", property]];

(*Default instance validation*)
ValidQ[instance : class_Symbol[___], opts : OptionsPattern[]] :=
    ValidQ[instance, SymbolName[class], opts];
ValidQ[instance : class_Symbol[___], className_String,
  opts : OptionsPattern[]] := (

  Message[ValidQ::noinv, className];

  And[

    MemberQ[$Classes[], className],
    MemberQ[$Classes[], SymbolName[class]],(**)
    Which[

      instance["ClassPrimary", "String"] === className,
      True,

      OptionValue["CheckParents"],
      ValidQ[instance, Symbol[className][]["ClassBase", "String"],
        opts],

      True,
      ValidQ[instance, instance["ClassPrimary", "String"], opts]

    ]

  ]

);
ValidQ[___] := False;

(*Invariant declaration*)
DeclareInvariant[className_String, qFun_Function] :=
    Module[{classSymbol = Symbol[className]},

    (*Check if the class is defined yet,
   and if so whether the default elements satisfy the invariant*)
      If[! NoValueQ[classSymbol] && ! qFun[classSymbol[]],
        Message[DeclareInvariant::ilinv, className]; Return[$Failed]];

      Module[{protected},

        Internal`WithLocalSettings[

          protected = Unprotect[ValidQ],

        (*Add definition for ValidQ*)
          ValidQ[(instance : class_Symbol[___]), className,
            opts : OptionsPattern[]] /;
              MemberQ[$Classes[], className] &&
                  MemberQ[$Classes[], SymbolName[class]] := Which[

            instance["ClassPrimary", "String"] === className,
            True,

            OptionValue[ValidQ, opts, "CheckParents"],

            ValidQ[instance, Symbol[className][]["ClassBase", "String"],
              opts],

            True,
            ValidQ[instance, instance["ClassPrimary", "String"], opts]

          ] &&
              If[OptionValue["Verbose"], Identity, Quiet] @
                  Check[qFun[instance] /.
                      False :> (Message[ValidQ::exfail, className]; False),
                    False, ValidQ::elfail],

          Protect @@ protected

        ]

      ]

    ];

(*Class declaration*)
DeclareClass[class_Symbol?NoValueQ, elemList : {(_String -> _) ...},
  opts : OptionsPattern[]] := Module[
  {
    className = SymbolName[class],
    elemNames = Cases[elemList, (str_String -> _) :> str],
    qFun = OptionValue["Invariant"]
  },

(*Check if all supplied elements are legal*)
  If[
    Not@
        FreeQ[elemNames,
          "Class" | "ClassBase" | "ClassChildren" | "ClassElements" |
              "ClassParents" | "ClassPrimary" | "ClassProperties" |
              "ClassReference" | "ClassTree"],
    Message[DeclareClass::ilelem];
    Return[$Failed]
  ];

  (*Declare invariant if supplied*)
  Switch[
    qFun,

    _Function,
    If[DeclareInvariant[className, qFun] === $Failed,
      Return[$Failed]],

    Except[None],
    Message[DeclareClass::ilform]; Return[$Failed]

  ];

  (*Check validity of the invariant/elements*)
  Block[{$Classes = {className, "tempClass"} &, tempClass},

    Options[tempClass] =
        Prepend[elemList, "ClassReference" -> class];
    _tempClass["ClassPrimary", "String"] = className;

    If[
      Not@TrueQ[
        ValidQ[tempClass[], className, "CheckParents" -> False]],
      Message[DeclareClass::ilinv, elemList, className];
      Return[$Failed]]

  ];

  (*Register class*)
  Module[{protected},

    Internal`WithLocalSettings[

      protected = Unprotect[$Classes],

      AppendTo[$Classes[], className];

      $Classes[className] = {},

      Protect @@ protected

    ]

  ];

  (*Set class default elements*)
  Options[class] = Prepend[elemList, "ClassReference" -> class];

  (*Set class usage*)
  class::usage =
      className <> "[args] represents an instance of the class " <>
          className <> ", with elements stored in the arguments.";

  Begin["`Private`"];

  (*Set primary-specific class properties*)
  _class["Class"] = class;
  _class["Class", "Description"] =
      "Information on the class type.";
  _class["Class", "String"] = className;
  _class["Class", "Symbol"] = class;
  _class["ClassBase"] = Null;
  _class["ClassBase", "Description"] = "The direct parent class.";
  _class["ClassBase", "String"] = "";
  _class["ClassBase", "Symbol"] = Null;
  _class["ClassChildren"] :=
      Rest@VertexOutComponent[Graph[$Classes[className]], className];
  _class["ClassChildren", "Description"] =
      "The list of class children.";
  _class["ClassElements"] =
      Sort@PrependTo[elemNames, "ClassReference"];
  _class["ClassElements", "Description"] =
      "A list of possible elements that can be included in objects of \
the class.";
  _class["ClassInvariant"] =
      With[{str = className}, ValidQ[#, str, "CheckParents" -> False] &];
  _class["ClassInvariant", "Description"] =
      "A function that returns true if an expression doesn't break the \
invariant of the class.";
  _class["ClassProperties"] :=
      Cases[Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
        1], (Verbatim[Pattern][_, Verbatim[_class]] | Verbatim[_class])[
        string_String] :> string, Infinity];
  _class["ClassProperties", "Description"] =
      "A list of requestable properties.";
  _class["ClassProperties", prop_String] :=
      DeleteDuplicates@
          Cases[Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
            1], _[prop, x___] :> Hold[x], Infinity];
  _class["ClassParents"] = {};
  _class["ClassParents", "Description"] =
      "The list of class parents.";
  _class["ClassParents", "String"] = {};
  _class["ClassParents", "Symbol"] = {};
  _class["ClassPrimary"] = class;
  _class["ClassPrimary", "Description"] =
      "The primary parent class.";
  _class["ClassPrimary", "String"] = className;
  _class["ClassPrimary", "Symbol"] = class;
  _class["ClassTree"] :=
      TreeGraph[$Classes[className], VertexLabels -> "Name",
        ImagePadding -> 20, GraphLayout -> "LayeredDigraphEmbedding"];
  _class["ClassTree", "Description"] =
      "A tree graph showing the hierarchy to which the class belongs.";

  _class["ClassReference", "Description"] =
      "The symbol associated with the object class. Its value is \
retained when passing class elements to parents for property \
evaluation.";

  Module[{guard = True},

    (input : _class[___]) /; guard :=
        Block[{guard = False, result},

          result = Quiet[input, {class::propx, class::subpx}];

          result /; result =!= Unevaluated[input]

        ]

  ];
  _class[str_String] := Null /; Message[class::propx, str];
  _class[str_String, subprops__] :=
      Null /; Message[class::subpx, str, {subprops}];

  (*Set element properties*)
  With[{elem = #},

    instance_class[elem] := GetElement[instance, elem];

    class /: Part[instance_class, elem, args___] := Module[
      {

        full = Append[List @@ instance, elem -> instance[elem]]
      },

      With[
        {
          full = full,
          pos = Position[full, elem][[1, 1]]
        },

        Unevaluated[Part[full, pos, 2]]

      ]

    ]

  ] & /@ elemNames;

  (*Split lists of properties*)
  instance_class[properties : {_String ..}] :=
      instance /@ properties;

  (*Define default instance format*)
  MakeBoxes[input : class[elems : OptionsPattern[]], fmt_] ^:=
      With[{ref = input["ClassReference"]},

        Module[{instance = ref[elems], alwaysGrid, sometimesGrid},

          alwaysGrid = {

            BoxForm`MakeSummaryItem[{"Primary: ",
              instance["ClassPrimary", "String"]}, fmt],

            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {

            BoxForm`MakeSummaryItem[{"Elements: ",
              ElisionsDump`expandablePane[
                instance["ClassElements"] /.
                    Thread[Thread[x : Flatten[{elems}][[All, 1]]] :>
                        Style[x, Bold]]]}, fmt],

            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]},
              fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ref,

            ToString[ref] <>
                "[<>]", (*Ideally this should be adapted in the output form \
when BoxForm`UseTextFormattingQ is True.*)
            $ElisionsIcon,
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False

          ]

        ]

      ];

  End[];

  (*Set class attributes*)
  Attributes[class] = {Protected, ReadProtected};

];
DeclareClass[class_Symbol, {(_String -> _) ...}, OptionsPattern[]] :=
    Null /; Message[DeclareClass::nvclss, ToString@class];

DeclareClass[base_Symbol /; MemberQ[$Classes[], SymbolName@base],
  class_Symbol?NoValueQ, elemList : {(_String -> _) ...},
  opts : OptionsPattern[]] := Module[
  {
    basename = SymbolName[base],
    className = SymbolName[class],
    elemNames = Cases[elemList, (str_String -> _) :> str],
    parentNames,
    parents = Append[base[]["ClassParents"], base],
    primary = base[]["ClassPrimary"],
    primaryName = base[]["ClassPrimary", "String"],
    qFun = OptionValue["Invariant"]
  },

  parentNames =
      Append[base[]["ClassParents", "String"], basename];

  (*Check if all supplied elements are legal*)(*FIX*)
  If[
    Not@
        FreeQ[elemNames,
          "Class" | "ClassBase" | "ClassChildren" | "ClassElements" |
              "ClassParents" | "ClassPrimary" | "ClassProperties" |
              "ClassReference" | "ClassTree"],
    Message[DeclareClass::ilelem]; Return[$Failed]
  ];

  (*Declare invariant if supplied*)
  Switch[
    qFun,

    _Function,
    If[DeclareInvariant[className, qFun] === $Failed,
      Return[$Failed]],

    Except[None],
    Message[DeclareClass::ilform]; Return[$Failed]

  ];

  (*Check validity of the invariant/elements*)
  Block[{$Classes =
      Evaluate[Join[$Classes[], {className, "tempClass"}]] &,
    tempClass},


    Options[tempClass] =
        Join[Prepend[elemList, "ClassReference" -> class],
          Options[base]];
    _tempClass["ClassPrimary", "String"] = className;

    If[
      And[

        Not@TrueQ[
          ValidQ[tempClass[], className, "CheckParents" -> False]],
        Not@TrueQ[base["ClassInvariant"][class[Options[class]]]]
      ],
      Message[DeclareClass::ilinv, elemList, className];
      Return[$Failed]
    ]

  ];

  (*Store class name*)
  Module[{protected},

    Internal`WithLocalSettings[

      protected = Unprotect[$Classes],

      AppendTo[$Classes[], className];

      AppendTo[$Classes[primaryName],
        basename \[DirectedEdge] className],

      Protect @@ protected

    ]

  ];

  (*Set class default elements*)
  Options[class] =
      Join[FilterRules[Options[base],
        Except@PrependTo[elemNames, "ClassReference"]],
        Prepend[elemList, "ClassReference" -> class]];

  (*Set class usage*)
  class::usage =
      className <> "[args] represents an instance of the class " <>
          className <> ", with elements stored in the arguments.";

  Begin["`Private`"];

  (*Set class properties*)
  _class["Class"] = class;
  _class["Class", "String"] = className;
  _class["Class", "Symbol"] = class;
  _class["ClassChildren"] :=
      Rest@VertexOutComponent[Graph[$Classes[primaryName]], className];
  _class["ClassElements"] =
      Join[Cases[
        FilterRules[Options[base],
          Except[elemNames]], (str_String -> _) :> str], elemNames];
  _class["ClassInvariant"] =
      With[{str = className}, ValidQ[#, str, "CheckParents" -> False] &];
  _class["ClassProperties"] :=
      With[{baseprops = base[]["ClassProperties"]},

        Join[

          DeleteDuplicates@
              Cases[Cases[
                Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
                  1], _[x_String, ___] :> x, Infinity],
                Except[Alternatives @@ baseprops]],
          baseprops
        ]

      ];
  _class["ClassProperties", prop_String] :=
      With[{baseprops = base[]["ClassProperties", prop]},

        Join[

          DeleteDuplicates@
              Cases[Cases[
                Cases[SubValues[class], (x_HoldPattern :> _) :> First[x],
                  1], _[prop, x___] :> Hold[x], Infinity],
                Except[Alternatives @@ Verbatim /@ baseprops]],
          baseprops
        ]

      ];
  _class["ClassBase"] = base;
  _class["ClassBase", "String"] = basename;
  _class["ClassBase", "Symbol"] = base;
  _class["ClassParents"] = parents;
  _class["ClassParents", "String"] = parentNames;
  _class["ClassParents", "Symbol"] = parents;
  _class["ClassPrimary"] = primary;
  _class["ClassPrimary", "String"] = primaryName;
  _class["ClassPrimary", "Symbol"] = primary;

  (*Set element properties*)
  With[{elem = #},

    instance_class[elem] := GetElement[instance, elem];

    class /: Part[instance_class, elem, args___] := Module[
      {

        full = Append[List @@ instance, elem -> instance[elem]]
      },

      With[
        {
          full = full,
          pos = Position[full, elem][[1, 1]]
        },

        Unevaluated[Part[full, pos, 2]]

      ]

    ]

  ] & /@ elemNames;

  (*Forward unknown properties to parent*)
  Module[{guard},

    (input : class[elems___][str_String, subprops___]) /; !
        TrueQ[guard[Unevaluated@input]] := Block[{guard, result},

      guard[Unevaluated@input] = True;

      result = Quiet[input, {class::propx, class::subpx}];

      If[
        result =!= Unevaluated[input],
        result,
        base[elems, Options[class]][str, subprops]
      ]

    ]

  ];

  (*Split lists of properties*)
  instance_class[properties : {_String ..}] :=
      instance /@ properties;

  (*Define default instance format*)
  (*MakeBoxes[instance : class[elems : OptionsPattern[]],
   fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

   		    	alwaysGrid = {
   		   			BoxForm`MakeSummaryItem[{"Primary: ", instance[
   "ClassPrimary", "String"]}, fmt],
   		   			BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
   		   		};

   		    	sometimesGrid = {
   		   			BoxForm`MakeSummaryItem[{"Elements: ",
   ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[
   Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
   		   			BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[
   instance]}, fmt]
   		   		};

   		    	BoxForm`ArrangeSummaryBox[
   			   		class,
   			   		ToString[class] <>
   "[<>]", (*Ideally this should be adapted in the output form when \
BoxForm`UseTextFormattingQ is True.*)
   			   		$ElisionsIcon,
   			   		alwaysGrid,
   			   		sometimesGrid,
   			   		fmt,
   			   		"Interpretable" -> False

   		    	]

   		   	];*)

  Format[class[elems : OptionsPattern[]]] ^:=
      base[elems, Options[class]];

  End[];

  (*Set class attributes*)
  Attributes[class] = {Protected, ReadProtected};

];
DeclareClass[base_Symbol /; MemberQ[$Classes[], SymbolName@base],
  class_Symbol, {(_String -> _) ...}, OptionsPattern[]] :=
    Null /; Message[DeclareClass::nvclss, ToString@class];

(*Class conversion*)
ConvertClass[
  instance : _Symbol[elems : OptionsPattern[]] /;
      ValidQ[instance], {newElems : OptionsPattern[]}, class_String] :=
    Module[
      {
        targetSymbol = Symbol[class]
      },

      SetElement[
        targetSymbol[
          FilterRules[{newElems, elems},
            targetSymbol[]["ClassElements"]]], "Source" -> instance]

    ];
ConvertClass[instance_ ?ValidQ, class_String] :=
    ConvertClass[instance, {}, class];

(*Bulletproofing*)
SetAttributes[
  {
    ClearElement,
    ConvertClass,
    DeclareClass,
    DeclareInvariant,
    ElementQ,
    GetElement,
    GetProperty,
    ValidQ,
    PropertyQ,
    SetElement,
    SubpropertyQ
  },
  {Protected, ReadProtected}
];

End[];

EndPackage[];

Object.m

BeginPackage["LieAnalysis`Classes`Object`", {
  "LieAnalysis`Classes`Classes`"
  }
];



DeclareClass[Object,
  {
    "Data"                            -> Automatic
  }
];




  Begin["`Private`"];


    DeclareInvariant["Object",

      Block[
        {
          data = GetElement[#, "Data"]
        },

        And[

          (data === Null || NumericQ[data] || True )
              /. False :> (Message[ValidQ::elfail, "Data", "Object", "be a number or an array of numbers or Automatic"]; False)

        ]

      ] &

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[Object],



        object_Object["Image"] := Module[

          {image},

          image = Switch[object["Data"],
            _?MatrixQ,  ImageAdjust[Image[object["Data"]]],
            Automatic,  LieAnalysis`Classes`Classes`Private`$ElisionsIcon,
            _,          LieAnalysis`Classes`Classes`Private`$ElisionsIcon
          ];

          Return[image]

        ];
        object_Object["DataType"] := Head[object["Data"]];



        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[Object],

        MakeBoxes[instance : Object[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt],
            BoxForm`MakeSummaryItem[{"Data Type: ", instance["DataType"]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            Object,
            ToString[Object] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

      Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjWavelet3D.m

(*************************************************************************************************************************
** ObjWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWavelet3D,
  {
    "WaveletSize"         -> Automatic
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjWavelet3D",

  Block[
    {
      waveletsize       = GetElement[#, "WaveletSize"]
    },

    And[

      (Internal`PositiveIntegerQ[waveletsize] || waveletsize === Automatic)
        /. False :> (Message[ValidQ::elfail, "WaveletSize", "be a positive integer"])

    ]

  ] &

];


End[];



EndPackage[];

ObjWavelet.m

(*************************************************************************************************************************
** ObjWavelet.m
** This .m file contains the blue print for an 2D wavelet object.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.2
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWavelet,
  {
    "CoordinateSystem"    -> "Matrix",
    "PreSpatialFilters"   -> Automatic
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjWavelet",

  Block[
    {
      coordinatesystem  = GetElement[#, "CoordinateSystem"]
    },

    And[

      MatchQ[coordinatesystem, "Matrix" | "Image" ]
          /. False :> (Message[ValidQ::elfail, "CoordinateSystem", "be either Image or Matrix"]; False)

    ]

  ] &

];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjWavelet],

      object_ObjWavelet["NumberOfWavelets"] := If[object["Data"] =!= Automatic, Length[object["Data"]],0];
      object_ObjWavelet["WaveletSize"]      := If[object["Data"] =!= Automatic, Dimensions[ object["Data"]][[2;;-1]],0];
      object_ObjWavelet["Image"]            := If[object["Data"] =!= Automatic, ImageResize[ImageAdjust@Image[Re@object["Data"][[1]]],{50,50}],LieAnalysis`Classes`Classes`Private`$ElisionsIcon];,

      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjWavelet],

      MakeBoxes[instance : ObjWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Number of Wavelets: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjWavelet,
          ToString[ObjWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

ObjWaveletTransform.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWaveletTransform`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWaveletTransform,
  {
    "Wavelets"          -> None,
    "InputData"         -> None
  }
];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjWaveletTransform",

      Block[
        {
          wavelets = GetElement[#, "Wavelets"],
          inputdata = GetElement[#, "InputData"]
        },

        And[

          (ValidQ[wavelets, "ObjWavelet"] || wavelets === None)
              /. False :> (Message[ValidQ::elfail, "Wavelets", "ObjWaveletTransform", "be a WaveletObj"]; False),

          (ValidQ[inputdata, "ObjWaveletTransform"] || MatrixQ[inputdata] || MatchQ[inputdata,{{{__}...}...}] || inputdata === None)
              /. False :> (Message[ValidQ::elfail, "InputData", "ObjWaveletTransform", "be a matrix"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

          object_ObjWaveletTransform["NumberOfLayers"] := If[object["Data"] =!= Automatic, Dimensions[object["Data"]][[-1]], 0];

          object_ObjWaveletTransform["InputDimensions"] := Which[
            ValidQ[object["InputData"],"ObjOrientationScores2D"]  , Dimensions[object["InputData"]["Data"]],
            ValidQ[object["InputData"],"ObjOrientationScores3D"]  , Dimensions[object["InputData"]["Data"]],
            ArrayQ[object["InputData"]]                           , Dimensions[object["InputData"]],
            MatchQ[object["InputData"], None | _]                 , None
          ];

          object_ObjWaveletTransform["WaveletType"] := If[object["Wavelets"] =!= None, Head[object["Wavelets"]], None];

          object_ObjWaveletTransform["Image"] := If[object["Data"] =!= Automatic,
            If[Length[Dimensions@object["Data"]] > 3,
              (*3D Case *)
              Module[

                {
                  data = object["Data"],
                  orientation,max
                },

                orientation = Transpose@Map[First@First@Position[#,Max[#]]&,Transpose[Re[data],{3,2,1}],{2}];
                orientation = orientation-Min[orientation];
                orientation = orientation/Max[orientation];

                max = Transpose@Map[Max,Transpose[Re[data],{3,2,1}],{2}];
                max = max/Max[max];

                Show[SetAlphaChannel[
                  Colorize[ImageAdjust[Image[orientation]], ColorFunction -> Function[orientation, Hue[orientation]]],
                  Image[max]
                ],ImageSize->50]

              ],
              (* 2D Case *)
                ImageAdjust[ImageResize[Image[Re@object["Data"][[All,All,1]]],{50,50}]]
              ],

              LieAnalysis`Classes`Classes`Private`$ElisionsIcon
            ]

        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

        MakeBoxes[instance : ObjWaveletTransform[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjWaveletTransform,
            ToString[ObjWaveletTransform] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];

  End[];



EndPackage[];

ObjOrientationWavelet3D.m

(*************************************************************************************************************************
** ObjOrientationWavelet.m
** This .m file describes a data-container for 2D oriented wavelets
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjWavelet, ObjOrientationWavelet3D,
  {
    "OrientationCoordinates" -> Null
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjOrientationWavelet3D",

  (* Validate Elements *)
  Block[

    {
      coords    = GetElement[#, "OrientationCoordinates"]
    },

    And[

      MatchQ[coords, {{_Real, _Real, _Real} ...} | Null]
      /. False :> (Message[ValidQ::elfail, "OrientationCoordinates", "be a list of 3D coordinates {p1,p2,...}"]; False)

    ]

  ] &

];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    object_ObjOrientationWavelet3D["Orientations"]     := object["NumberOfWavelets"];
    object_ObjOrientationWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
      Module[

        (* Local Variables *)
        {
          wavelet = Re@object["Data"][[1]]
        },

        ImageResize[
          Image3D[((wavelet - Min[wavelet])/Max[wavelet])^(1.75),BoxRatios->1],
          {5,5,5}
        ]

      ],
      LieAnalysis`Classes`Classes`Private`$ElisionsIcon
    ]
    ,
    Protect@@protected
  ];
];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    MakeBoxes[instance : ObjOrientationWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

      alwaysGrid = {
        BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
        BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
        BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
      };

      sometimesGrid = {
        BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
        BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
      };

      BoxForm`ArrangeSummaryBox[
        ObjOrientationWavelet3D,
        ToString[ObjOrientationWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
        instance["Image"],
        alwaysGrid,
        sometimesGrid,
        fmt,
        "Interpretable" -> False
      ]

    ];,

    Protect@@protected
  ]
];



End[];



EndPackage[];

ObjOrientationWavelet.m

(*************************************************************************************************************************
** ObjOrientationWavelet.m
** This .m file describes a data-container for 2D oriented wavelets
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjWavelet, ObjOrientationWavelet,
  {
    "Directional"     -> False,
    "Symmetry"        -> Pi
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjOrientationWavelet",

  (* Validate Elements *)
  Block[

    {
      directional = GetElement[#, "Directional"],
      symmetry = GetElement[#, "Symmetry"]
    },

    And[

      BooleanQ[directional]
      /. False :> (Message[ValidQ::elfail, "Directional", "be a boolean"]; False),

      MatchQ[symmetry, Pi | -Pi | 2Pi ]
          /. False :> (Message[ValidQ::elfail, "Symmetry", "be either π, -π or 2π"]; False)

    ]

  ] &

];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet],

    (* Gives the number of orientations between 0-2pi *)
    object_ObjOrientationWavelet["Orientations"] := Switch[object["Symmetry"],
      Pi | -Pi , 2 object["NumberOfWavelets"],
      2Pi | _, object["NumberOfWavelets"]
    ];

    (* Computes the angular resultion *)
    object_ObjOrientationWavelet["AngularResolution"] := Abs[object["Symmetry"] / object["NumberOfWavelets"]];

    (* Construct 0-2pi orientation scores *)
    object_ObjOrientationWavelet["FullData"] := Switch[object["Symmetry"],
      Pi  , Join[object["Data"], Conjugate[object["Data"]]],
      -Pi , Join[object["Data"], -Conjugate[object["Data"]]],
      2Pi , object["Data"]
    ];

    ,Protect@@protected
  ];
];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet],

    MakeBoxes[instance : ObjOrientationWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

      alwaysGrid = {
        BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
        BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
        BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
        BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
        BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
        BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
      };

      sometimesGrid = {
        BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
        BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
      };

      BoxForm`ArrangeSummaryBox[
        ObjOrientationWavelet,
        ToString[ObjOrientationWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
        instance["Image"],
        alwaysGrid,
        sometimesGrid,
        fmt,
        "Interpretable" -> False
      ]

    ];,

    Protect@@protected
  ]
];



End[];



EndPackage[];

ObjCakeWavelet3D.m

(*************************************************************************************************************************
** ObjCakeWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjCakeWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`OrientationScores3D`Visualisation`",
  "LieAnalysis`Classes`ObjOrientationWavelet3D`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjOrientationWavelet3D, ObjCakeWavelet3D,
  {
    "DcFilter"                        -> Null,
    "RadialWindow"                    -> Null,
    "WaveletsF"                       -> Null,
    "Topology"                        -> Null,
    "OrientationList"                 -> {},
    "Method"                          -> "JMIV",
    "LMax"                            -> 1,
    "SigmaAngle"                      -> 0.3,
    "DcStandardDeviation"             -> 8,
    "MnStandardDeviation"             -> Automatic,
    "Gamma"                           -> 0.6,
    "Epsilon"                         -> 10^(-5)
  }
];



Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareInvariant["ObjCakeWavelet3D",

    (* Validate Elements *)
    Block[

      {
        dcfilter            = GetElement[#, "DcFilter"],
        method              = GetElement[#, "Method"],
        lmax                = GetElement[#, "LMax"],
        sigmaangle          = GetElement[#, "SigmaAngle"],
        dcstd               = GetElement[#, "DcStandardDeviation"],
        mnstd               = GetElement[#, "MnStandardDeviation"],
        gamma               = GetElement[#, "Gamma"],
        epsilon             = GetElement[#, "Epsilon"],
        topology            = GetElement[#, "Topology"]
        (*shcoefficients      = GetElement[#, "SphericalHarmonicsCoefficients"]*)
      },


      And[

        (MatchQ[dcfilter,{{{___},___},___}] || dcfilter === Null)
          /. False :> (Message[ValidQ::elfail, "DcFilter", "be a 3D Matrix"]; False),

        MatchQ[method,"JMIV"]
          /. False :> (Message[ValidQ::elfail, "Method", "be JMIV or ..."]; False),

        Internal`PositiveIntegerQ[lmax]
          /. False :> (Message[ValidQ::elfail, "LMax", "be a positive integer"]; False),

        Positive[sigmaangle]
          /. False :> (Message[ValidQ::elfail, "SigmaAngle", "be a positive real number"]; False),

        Positive[dcstd]
          /. False :> (Message[ValidQ::elfail, "DcStandardDeviation", "be a positive real number"]; False),

        (Positive[mnstd] || mnstd === Automatic)
          /. False :> (Message[ValidQ::elfail, "MnStandardDeviation", "be a positive real number"]; False),

        (gamma > 0 && gamma < 1)
          /. False :> (Message[ValidQ::elfail, "Gamma", "be a number between 0 and 1"]; False),

        Positive[epsilon]
          /. False :> (Message[ValidQ::elfail, "Epsilon", "be a positive real number"]; False),

        MatchQ[topology, {{_,_,_}..} | Null]
          /.False :> (Message[ValidQ::elfail, "Topology", "be a matrix of indices"]; False)
              
      ]

    ] &

  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      object_ObjCakeWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
        Module[

        (* Local Variables *)
          {
            wRe = Re@object["Data"][[1]]
          },

          ListContourPlot3D[
            wRe,
            Sequence @@ {ImageSize -> 50, Mesh -> False, ContourStyle -> {Directive[Blue, Opacity[0.8], Specularity[White, 30]], Directive[Orange, Opacity[0.8], Specularity[White, 30]]}, MaxPlotPoints -> 50},
            Contours -> {0.155 Min[wRe], 0.06 Max[wRe]},
            PerformanceGoal->"Speed",
            Axes->False, Boxed->False
          ]

        ],
        LieAnalysis`Classes`Classes`Private`$ElisionsIcon
      ]
      ,
      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      MakeBoxes[instance : ObjCakeWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjCakeWavelet3D,
          ToString[ObjCakeWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

ObjCakeWavelet.m

(*************************************************************************************************************************
** ObjCakeWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjCakeWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjOrientationWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjOrientationWavelet, ObjCakeWavelet,
  {
    "Design"              -> "N",
    "InflectionPoint"     -> 0.8,
    "SplineOrder"         -> 3,
    "MnOrder"             -> 8,
    "DcStandardDeviation" -> 3,
    "DcFilter"            -> Null,
    "FourierDcFilter"     -> Null,
    "OverlapFactor"       -> 1,
    "FourierCake"         -> Null,
    "SpatialWindow"       -> False
  }
];



Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareInvariant["ObjCakeWavelet",

    (* Validate Elements *)
    Block[

      {
        design            = GetElement[#, "Design"],
        inflectionpoint   = GetElement[#, "InflectionPoint"],
        splineoder        = GetElement[#, "SplineOrder"],
        mnorder           = GetElement[#, "MnOrder"],
        dcstddev          = GetElement[#, "DcStandardDeviation"],
        dcfilter          = GetElement[#, "DcFilter"],
        fourierdcfilter   = GetElement[#, "FourierDcFilter"],
        overlapfactor     = GetElement[#, "OverlapFactor"],
        fouriercake       = GetElement[#, "FourierCake"],
        prespatialfilters = GetElement[#, "PreSpatialFilters"],
        spatialwindow     = GetElement[#, "SpatialWindow"]
      },

      And[

        MatchQ[design, "N"|"M"]
            /. False :> (Message[ValidQ::elfail, "Design", "be either N or M"]; False),

        (inflectionpoint > 0 && inflectionpoint <= 1)
          /. False :> (Message[ValidQ::elfail, "InflectionPoint","be between 0 and 1"]; False),

        Internal`PositiveIntegerQ[splineoder]
            /. False :> (Message[ValidQ::elfail, "SplineOrder","be a positive integer"]; False),

        Internal`PositiveIntegerQ[mnorder]
          /. False :> (Message[ValidQ::elfail, "MnOrder","be a positive integer"]; False),

        (MatrixQ[dcfilter] || dcfilter === Null)
            /. False :> (Message[ValidQ::elfail, "DcFilter","be a matrix"]; False),

        (MatrixQ[fourierdcfilter] || fourierdcfilter === Null)
            /. False :> (Message[ValidQ::elfail, "FourierDcFilter", "be a matrix"]; False),

        (Internal`PositiveIntegerQ[overlapfactor])
          /. False :> (Message[ValidQ::elfail, "OverlapFactor", "be a positive integer"]; False)
		
      ]

    ] &

  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet],

        Null

      ,
      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet],

      MakeBoxes[instance : ObjCakeWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Design: ", Subscript["φ", instance["Design"]]}, fmt],
          BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
          BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
          BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjCakeWavelet,
          ToString[ObjCakeWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains functions to visualise 2D Orientation Scores.
** 
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Visualisation`",{
	"LieAnalysis`OrientationScores2D`Filters`CakeWavelets`"
}];


	Begin["`Private`"];
	



	End[];

EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains functions to visualise 3D Orientation Scores.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Visualisation`", {
  "LieAnalysis`Classes`Classes`"
  }
];



  Begin["`Private`"];




  End[];



EndPackage[];

ObjOrientationWavelet3D.m

(*************************************************************************************************************************
** ObjOrientationWavelet.m
** This .m file describes a data-container for 2D oriented wavelets
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjWavelet, ObjOrientationWavelet3D,
  {
    "OrientationCoordinates" -> Null
  }
];



Begin["`Private`"];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareInvariant["ObjOrientationWavelet3D",

  (* Validate Elements *)
  Block[

    {
      coords    = GetElement[#, "OrientationCoordinates"]
    },

    And[

      MatchQ[coords, {{_Real, _Real, _Real} ...} | Null]
      /. False :> (Message[ValidQ::elfail, "OrientationCoordinates", "be a list of 3D coordinates {p1,p2,...}"]; False)

    ]

  ] &

];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    object_ObjOrientationWavelet3D["Orientations"]     := object["NumberOfWavelets"];
    object_ObjOrientationWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
      Module[

        (* Local Variables *)
        {
          wavelet = Re@object["Data"][[1]]
        },

        ImageResize[
          Image3D[((wavelet - Min[wavelet])/Max[wavelet])^(1.75),BoxRatios->1],
          {5,5,5}
        ]

      ],
      LieAnalysis`Classes`Classes`Private`$ElisionsIcon
    ]
    ,
    Protect@@protected
  ];
];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



Module[{protected},
  Internal`WithLocalSettings[
    protected = Unprotect[ObjOrientationWavelet3D],

    MakeBoxes[instance : ObjOrientationWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

      alwaysGrid = {
        BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
        BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
        BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
      };

      sometimesGrid = {
        BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
        BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
      };

      BoxForm`ArrangeSummaryBox[
        ObjOrientationWavelet3D,
        ToString[ObjOrientationWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
        instance["Image"],
        alwaysGrid,
        sometimesGrid,
        fmt,
        "Interpretable" -> False
      ]

    ];,

    Protect@@protected
  ]
];



End[];



EndPackage[];

ObjCakeWavelet3D.m

(*************************************************************************************************************************
** ObjCakeWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjCakeWavelet3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`OrientationScores3D`Visualisation`",
  "LieAnalysis`Classes`ObjOrientationWavelet3D`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[ObjOrientationWavelet3D, ObjCakeWavelet3D,
  {
    "DcFilter"                        -> Null,
    "RadialWindow"                    -> Null,
    "WaveletsF"                       -> Null,
    "Topology"                        -> Null,
    "OrientationList"                 -> {},
    "Method"                          -> "JMIV",
    "LMax"                            -> 1,
    "SigmaAngle"                      -> 0.3,
    "DcStandardDeviation"             -> 8,
    "MnStandardDeviation"             -> Automatic,
    "Gamma"                           -> 0.6,
    "Epsilon"                         -> 10^(-5)
  }
];



Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareInvariant["ObjCakeWavelet3D",

    (* Validate Elements *)
    Block[

      {
        dcfilter            = GetElement[#, "DcFilter"],
        method              = GetElement[#, "Method"],
        lmax                = GetElement[#, "LMax"],
        sigmaangle          = GetElement[#, "SigmaAngle"],
        dcstd               = GetElement[#, "DcStandardDeviation"],
        mnstd               = GetElement[#, "MnStandardDeviation"],
        gamma               = GetElement[#, "Gamma"],
        epsilon             = GetElement[#, "Epsilon"],
        topology            = GetElement[#, "Topology"]
        (*shcoefficients      = GetElement[#, "SphericalHarmonicsCoefficients"]*)
      },


      And[

        (MatchQ[dcfilter,{{{___},___},___}] || dcfilter === Null)
          /. False :> (Message[ValidQ::elfail, "DcFilter", "be a 3D Matrix"]; False),

        MatchQ[method,"JMIV"]
          /. False :> (Message[ValidQ::elfail, "Method", "be JMIV or ..."]; False),

        Internal`PositiveIntegerQ[lmax]
          /. False :> (Message[ValidQ::elfail, "LMax", "be a positive integer"]; False),

        Positive[sigmaangle]
          /. False :> (Message[ValidQ::elfail, "SigmaAngle", "be a positive real number"]; False),

        Positive[dcstd]
          /. False :> (Message[ValidQ::elfail, "DcStandardDeviation", "be a positive real number"]; False),

        (Positive[mnstd] || mnstd === Automatic)
          /. False :> (Message[ValidQ::elfail, "MnStandardDeviation", "be a positive real number"]; False),

        (gamma > 0 && gamma < 1)
          /. False :> (Message[ValidQ::elfail, "Gamma", "be a number between 0 and 1"]; False),

        Positive[epsilon]
          /. False :> (Message[ValidQ::elfail, "Epsilon", "be a positive real number"]; False),

        MatchQ[topology, {{_,_,_}..} | Null]
          /.False :> (Message[ValidQ::elfail, "Topology", "be a matrix of indices"]; False)
              
      ]

    ] &

  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      object_ObjCakeWavelet3D["Image"]            := If[object["Data"] =!= Automatic,
        Module[

        (* Local Variables *)
          {
            wRe = Re@object["Data"][[1]]
          },

          ListContourPlot3D[
            wRe,
            Sequence @@ {ImageSize -> 50, Mesh -> False, ContourStyle -> {Directive[Blue, Opacity[0.8], Specularity[White, 30]], Directive[Orange, Opacity[0.8], Specularity[White, 30]]}, MaxPlotPoints -> 50},
            Contours -> {0.155 Min[wRe], 0.06 Max[wRe]},
            PerformanceGoal->"Speed",
            Axes->False, Boxed->False
          ]

        ],
        LieAnalysis`Classes`Classes`Private`$ElisionsIcon
      ]
      ,
      Protect@@protected
    ];
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  Module[{protected},
    Internal`WithLocalSettings[
      protected = Unprotect[ObjCakeWavelet3D],

      MakeBoxes[instance : ObjCakeWavelet3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

        alwaysGrid = {
          BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
          BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
          BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
        };

        sometimesGrid = {
          BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
          BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
        };

        BoxForm`ArrangeSummaryBox[
          ObjCakeWavelet3D,
          ToString[ObjCakeWavelet3D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
          instance["Image"],
          alwaysGrid,
          sometimesGrid,
          fmt,
          "Interpretable" -> False
        ]

      ];,

      Protect@@protected
    ]
  ];



End[];



EndPackage[];

ObjWaveletTransform.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjWaveletTransform`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`Object`"
}];



(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
(* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



DeclareClass[Object, ObjWaveletTransform,
  {
    "Wavelets"          -> None,
    "InputData"         -> None
  }
];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjWaveletTransform",

      Block[
        {
          wavelets = GetElement[#, "Wavelets"],
          inputdata = GetElement[#, "InputData"]
        },

        And[

          (ValidQ[wavelets, "ObjWavelet"] || wavelets === None)
              /. False :> (Message[ValidQ::elfail, "Wavelets", "ObjWaveletTransform", "be a WaveletObj"]; False),

          (ValidQ[inputdata, "ObjWaveletTransform"] || MatrixQ[inputdata] || MatchQ[inputdata,{{{__}...}...}] || inputdata === None)
              /. False :> (Message[ValidQ::elfail, "InputData", "ObjWaveletTransform", "be a matrix"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

          object_ObjWaveletTransform["NumberOfLayers"] := If[object["Data"] =!= Automatic, Dimensions[object["Data"]][[-1]], 0];

          object_ObjWaveletTransform["InputDimensions"] := Which[
            ValidQ[object["InputData"],"ObjOrientationScores2D"]  , Dimensions[object["InputData"]["Data"]],
            ValidQ[object["InputData"],"ObjOrientationScores3D"]  , Dimensions[object["InputData"]["Data"]],
            ArrayQ[object["InputData"]]                           , Dimensions[object["InputData"]],
            MatchQ[object["InputData"], None | _]                 , None
          ];

          object_ObjWaveletTransform["WaveletType"] := If[object["Wavelets"] =!= None, Head[object["Wavelets"]], None];

          object_ObjWaveletTransform["Image"] := If[object["Data"] =!= Automatic,
            If[Length[Dimensions@object["Data"]] > 3,
              (*3D Case *)
              Module[

                {
                  data = object["Data"],
                  orientation,max
                },

                orientation = Transpose@Map[First@First@Position[#,Max[#]]&,Transpose[Re[data],{3,2,1}],{2}];
                orientation = orientation-Min[orientation];
                orientation = orientation/Max[orientation];

                max = Transpose@Map[Max,Transpose[Re[data],{3,2,1}],{2}];
                max = max/Max[max];

                Show[SetAlphaChannel[
                  Colorize[ImageAdjust[Image[orientation]], ColorFunction -> Function[orientation, Hue[orientation]]],
                  Image[max]
                ],ImageSize->50]

              ],
              (* 2D Case *)
                ImageAdjust[ImageResize[Image[Re@object["Data"][[All,All,1]]],{50,50}]]
              ],

              LieAnalysis`Classes`Classes`Private`$ElisionsIcon
            ]

        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjWaveletTransform],

        MakeBoxes[instance : ObjWaveletTransform[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjWaveletTransform,
            ToString[ObjWaveletTransform] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];

  End[];



EndPackage[];

ObjOrientationScores2D.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationScores2D`", {
  "LieAnalysis`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjWaveletTransform, ObjOrientationScores2D,
    {
      "DcFilteredImage"   -> 0,
      "Symmetry"          -> Pi
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjOrientationScores2D",

      Block[
        {
          dcfilteredimage = GetElement[#, "DcFilteredImage"],
          symmetry = GetElement[#, "Symmetry"]
        },

        And[

          (MatrixQ[dcfilteredimage] || dcfilteredimage == 0)
              /. False :> (Message[ValidQ::elfail, "DcFilteredImage", "be a matrix or zero"]; False)

          (MatchQ[symmetry, Pi, -Pi, 2Pi])
              /. False :> (Message[ValidQ::elfail, "Symmetry", "Should be Pi, -Pi or 2Pi"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores2D],

          (* Construct 0-2pi orientation scores *)
          object_ObjOrientationScores2D["FullData"] := Switch[object["Symmetry"],
            Pi  , Join[object["Data"], Conjugate[object["Data"]],3],
            -Pi , Join[object["Data"], -Conjugate[object["Data"]],3],
            2Pi , object["Data"]
          ];

          object_ObjOrientationScores2D["AngularResolution"] := Abs[object["Symmetry"] / object["NumberOfLayers"]];

        ,Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Intertnal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores2D],

        MakeBoxes[instance : ObjOrientationScores2D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfLayers"]}, fmt],
            BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjOrientationScores2D,
            ToString[ObjOrientationScores2D] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

ObjOrientationScores3D.m

(*************************************************************************************************************************
** ObjOrientationScores2D.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjOrientationScores3D`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjWaveletTransform, ObjOrientationScores3D,
    {
      "DcData"    -> Null
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjOrientationScores3D",

      Block[
        {
          dcdata = GetElement[#,"DcData"]
        },

        And[

          (dcdata === Null || MatchQ[dcdata, {{{__}...}...}])
            /. False :> (Message[ValidQ::elfail, "DcData", "should be a 3D matrix"]; False)

        ]
      ]&
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores3D],

            Null
          ,
          Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjOrientationScores3D],

        MakeBoxes[instance : ObjOrientationScores3D[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Input Dimensions: ", instance["InputDimensions"]}, fmt],
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfLayers"]}, fmt],
            BoxForm`MakeSummaryItem[{"Wavelets: ", instance["WaveletType"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjOrientationScores3D,
            ToString[ObjOrientationScores3D] <> "[<>]",
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

Visualisation.m

(*************************************************************************************************************************
** Visualisation.m
** This .m file contains common function used to store visualization function used in more than one specific area
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Visualisation`",{
  "LieAnalysis`OrientationScores2D`Visualisation`",
  "LieAnalysis`OrientationScores3D`Visualisation`",
  "LieAnalysis`Classes`ObjCakeWavelet`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`Classes`ObjOrientationScores2D`",
  "LieAnalysis`Classes`ObjOrientationScores3D`"
}];



QuickVisualise::usage = "QuickVisualise[Object] visualises the object using its default visualisation.";



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* QuickVisualise- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


    (* ObjOrientationScores2D *)
    QuickVisualise[cwsObj_ObjOrientationScores2D] := Module[

      {
        data = cwsObj["Data"],
        orientation,max
      },

      orientation = Transpose@Map[First@First@Position[#,Max[#]]&,Transpose[Re[data],{3,2,1}],{2}];
      orientation = orientation-Min[orientation];
      orientation = orientation/Max[orientation];

      max = Transpose@Map[Max,Transpose[Re[data],{3,2,1}],{2}];
      max = max/Max[max];

      SetAlphaChannel[
        Colorize[ImageAdjust[Image[orientation]], ColorFunction -> Function[orientation, Hue[orientation]]],
        Image[max]
      ]
    ];



    (* ObjOrientationScores3D *)
    QuickVisualise[cwsObj_ObjOrientationScores3D] := Module[

      {},

      Graphics[Text["To be implemented"]]

    ];


    (* ObjCakeWavelet2D *)
    QuickVisualise[cwsObj_ObjCakeWavelet] := Manipulate[
          ImageAdjust[Image[Re@cwsObj["Data"][[i]], ImageSize -> 300]],
          {{i, 1, "Orientation"}, 1, Length[cwsObj["Data"]], 1}
    ];



    (* ObjCakeWavelet3D *)
    QuickVisualise[Cake3DObj_ObjCakeWavelet3D] := Module[

      {
        wRe = Re[Cake3DObj["Data"][[1]]],
        cs = ContourStyle -> {Directive[Blue, Opacity[0.8], Specularity[White, 30]], Directive[Orange, Opacity[0.8], Specularity[White, 30]]},
        otherOptions
      },

      otherOptions = Sequence @@ {ImageSize -> 150, Mesh -> False, cs, MaxPlotPoints -> 50};

      ListContourPlot3D[
        wRe,
        otherOptions,
        Contours -> {0.155 Min[wRe], 0.06 Max[wRe]},
        PerformanceGoal->"Speed",
        Axes->False, Boxed->False
      ]
    ];



  End[];



EndPackage[];

Common.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains common functions used for the LieAnalysis package
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: R.Duits <r.duits@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Common`"];



	MonitoredMap::usage 						= "
	MonitoredMap[f, expr, levelspec], behaves the same as Map and MapIndexed only shows the progress
	with an ProcessIndicator bar. This should only be used if a single itteration consumes a lot
	of time, else the overhead of creating the ProcessIndicator is larger than the computation itself.
	";

	ImageInputProcessor::usage 			= "
	ImageInputProcessor[image], tries to create a valid image data matrix from the input.
	It convert a RGB image to Grayscale and converts graphics and images to image data
	";

	ImageInputProcessor3D::usage 		= "
	ImageInputProcessor3D, tries to create valid 3D volume from the input. It converts Image3D to imagedata.
	";

	CenteredFourier::usage 					= "
	CenteredFourier[im_], computes the Fourier and puts the low frequencies to the center.
	Note that the Fourier transformed is not scaled to the number of samples!
	";

	CenteredInverseFourier::usage 	= "
	CenteredInverseFourier[im_], computes the Inverse Fourier from a Centered Fourier spectrum.
	";

	Reorder::usage = "
	Reorder[os_, order_], reorders the orientation array. Order can either be 'OrientationFirst' or 'SpatialFirst'
	";



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CenteredFourier- - - - - - - - - - - - - - - - - - - - -  *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		CenteredFourier[data_]:=
      RotateRight[Fourier[RotateLeft[data,Floor[Dimensions[data]/2]],FourierParameters->{1,1}],Floor[Dimensions[data]/2]];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CenteredInverseFourier - - - - - - - - - - - - - - - - -  *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		CenteredInverseFourier[data_]:=
			RotateRight[InverseFourier[RotateLeft[data,Floor[Dimensions[data]/2]],FourierParameters->{1,1}],Floor[Dimensions[data]/2]];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ImageInputProcessor - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		ImageInputProcessor::fail = "Failed to create imagedata from input...";



  	ImageInputProcessor[im_] := Block[

			(* Local Variables Declaration *)
			{image},

			(* Convert Graphics or Image to Image Data *)
			image = If[ImageQ[im] || Head[im] === Graphics, ImageData[im], im];

			(* If the image is an RGB image, convert it to grayscale *)
			image = If[Length[Dimensions[image]] > 2,ImageData[ColorConvert[Image[image],"Grayscale"]],image];

			(* If the output is not an matrix abort the execution *)
      If[!MatrixQ[image], Message[ImageInputProcessor::fail]; Abort[];];

			(* Else return the imagedata *)
			Return[image]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* ImageInputProcessor - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		ImageInputProcessor3D[data_] := Block[

			{image = data},

			(* Convert 3D image to data *)
			image = If[Head[image] === Image3D, ImageData@image, image];

			If[!MatchQ[image, {{{__}..}..}], ImageInputProcessor::fail];

			Return[image]

		];


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* MonitoredMap- - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[MonitoredMap] = Options[MapIndexed];



		MonitoredMap[f_, expr_, lvl_:{1}, opts:OptionsPattern[]] := Block[

			(* Local Variable Declaration *)
			{
				n = Length[expr],
				i
			},

			Monitor[
				MapIndexed[(i = First[#2]; f[#1]) &, expr, lvl, opts],
				ProgressIndicator[i/n]
			]
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Reorder - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		Reorder[os_, order_String] := Which[
			(MatchQ[order, "OrientationFirst"] && Depth[os] == 4),
				Transpose[os, {2,3,1}],
      (MatchQ[order, "SpatialFirst"] && Depth[os] == 4),
				Transpose[os, {3,1,2}],
      (MatchQ[order, "OrientationFirst"] && Depth[os] == 5),
				Transpose[os, {2,3,4,1}],
      (MatchQ[order, "SpatialFirst"] && Depth[os] == 5),
				Transpose[os, {4,1,2,3}]
		];



	End[];



EndPackage[];

ExampleData.m

(*************************************************************************************************************************
** ExampleData.m
** This .m file contains functions to load example data to demonstrate the functions
**
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`ExampleData`ExampleData`"];




  Unprotect[ExampleData];



    ExampleData[{"LieAnalysis Package", "Retina"}] := ColorConvert[Import["https://upload.wikimedia.org/wikipedia/en/thumb/b/b3/HealthyEyeFundus.jpg/1280px-HealthyEyeFundus.jpg"],"GrayScale"];

    ExampleData[{"LieAnalysis Package", "Spagetti"}] := ColorConvert[Import["https://pixabay.com/static/uploads/photo/2015/05/24/14/02/spaghetti-781795_960_720.jpg"],"GrayScale"];




  Protect[ExampleData];



EndPackage[];

CakeWavelets.m

(*************************************************************************************************************************
** CakeWavelets.m
** This .m file contains functions to create cake-wavelets.
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
** Notes:
**	- The option DcStandardDeviation is used to remove the low frequencies from the OS. The Standard Deviation is related
** 		to the sigma of a Gaussian in the SPATIAL domain. This is valid if designtype N is used, but when the cake is scaled
** 		to create design M (M = Sqrt[N]) this gaussian is scaled in the Fourier domain, changing the spatial shape in the
**		spatial domain. This might be resolved in a later version.
**	- The spatial window option is included to remove the long-tails of the filter in the spatial domain. This is more or
**		less the same as removing the low-frequencies in the Fourier domain, what is done by the Dc-Filter. Therefor the spatial
** 		window has become reduntant and might be removed in future releases.
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Filters`CakeWavelets`",{
	"LieAnalysis`Common`",
	"LieAnalysis`OrientationScores2D`Common`",
	"LieAnalysis`Classes`Classes`",
	"LieAnalysis`Classes`ObjCakeWavelet`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStack  - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		
		Options[CakeWaveletStack] = {
			"WaveletSize"->"Automatic", (* The x,y dimensions for the construction of the CakeWaveletStack *)
			"CoordinateSystem"->"Matrix", (* Determines whenever the output is given in Matrix coordinates or Image coordinates *)
			"Directional"->False, (* Determines whenever the filter goes in both directions *)
			"Design"->"N", (* Indicates which design is used N = Subscript[N, ψ] or M = Subscript[M, ψ] *)
			"InflectionPoint"->0.8, (* Is the location of the inflection point as a factor in (positive) radial direction *)
			"SplineOrder"->3, (* Order of the B-Spline that is used to construct the wavelet *)
			"MnOrder"->8, (* The order of the (Taylor expansion) gaussian decay used to construct the wavelet *)
			"DcStandardDeviation"->8, (* The standard deviation of the gaussian window (in the Spatial domain) that removes the center of the pie, to avoid long tails in the spatial domain *)
			"VectorOrientationScore"->False, (* Special kinds of output can be requested *)
			"SpatialWindow"->False, (* Indicates whenever the spatial (gaussian) window is used or not, and if used which size it is *)
			"OverlapFactor"->1 (* How much the cakepieces overlaps in ϕ-direction, this can be seen as subsampling the angular direction *)
		};
		
		

		CakeWaveletStack::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		


		CakeWaveletStack[nOrientations_, opts:OptionsPattern[]] := Module[
	
			(* Local Variables Declaration *)
			{
				splineOrder          = OptionValue["SplineOrder"],
				overlapFactor        = OptionValue["OverlapFactor"],
				inflectionPoint      = OptionValue["InflectionPoint"],
				mnOrder              = OptionValue["MnOrder"],
				dcStdDev             = OptionValue["DcStandardDeviation"], (*ToDo: implement auto method, to ensure the Mn and Dc do not overlap *)
				spatialWindow        = If[OptionValue["SpatialWindow"] === "Automatic", (size-1)/4, OptionValue["SpatialWindow"]],
				coordinateSystem     = OptionValue["CoordinateSystem"],
				directional          = OptionValue["Directional"],
				design               = OptionValue["Design"],
				vectorOS             = OptionValue["VectorOrientationScore"],
				size				 				 = OptionValue["WaveletSize"],
				cakeObj
			},

			(* Compute Wavelet Size Depending based on the inflectionPoint *)
			size = If[size == "Automatic",
				Round[70/inflectionPoint], (* Fix the size of the cake in the Fourier domain to 70px *)
				size
			];
			size = If[EvenQ[size],size+1,size];
		
			(* Validate the Input *)
			If[EvenQ[size],Message[CakeWaveletStack::invalidInput,size,"Size","Should be an odd value"]];
			If[!Internal`PositiveIntegerQ[nOrientations], Message[CakeWaveletStack::invalidInput, nOrientations, "nOrientations", "Should be a positive integer"]];
			If[!BooleanQ[directional],Message[CakeWaveletStack::invalidInput, directional, "Directional","Should be a boolean"]];
			If[!BooleanQ[vectorOS],Message[CakeWaveletStack::invalidInput, vectorOS,"VectorOrientationScore", "Should be a boolean"]];
			If[!IntegerQ[splineOrder], Message[CakeWaveletStack::invalidInput, splineOrder, "SplineOrder", "Should be an integer"]];
			If[splineOrder < 3, Message[CakeWaveletStack::invalidInput, splineOrder, "SplineOrder", "Should be greater than 3"]];
			If[!IntegerQ[overlapFactor],Message[CakeWaveletStack::invalidInput, overlapFactor, "OverlapFactor", "Should be an integer"]];
			If[overlapFactor < 1, Message[CakeWaveletStack::invalidInput, overlapFactor, "OverlapFactor", "Should be greater or equal to 1"]];
			If[!NumericQ[inflectionPoint] || inflectionPoint > 1 || inflectionPoint < 0, Message[CakeWaveletStack::invalidInput, inflectionPoint, "InflectionPoint","Should be an number between 0 and 1"]];
			If[!IntegerQ[mnOrder], Message[CakeWaveletStack::invalidInput, mnOrder, "MnOrder","Should be an integer"]];
			If[mnOrder < 3, Message[CakeWaveletStack::invalidInput, mnOrder, "MnOrder","Should be greater than 2"]];
			If[!NumericQ[dcStdDev], Message[CakeWaveletStack::invalidInput, dcStdDev, "DcStandardDeviation","Should be an number"]];
			If[dcStdDev < 0, Message[CakeWaveletStack::invalidInput, dcStdDev, "DcStandardDeviation","Should be greater than 0"]]; 
			If[(!NumericQ[spatialWindow] || !Positive[spatialWindow]) && spatialWindow, Message[CakeWaveletStack::invalidInput, spatialWindow, "SpatialWindow", "Should be positive and numeric"]];
			If[coordinateSystem != "Matrix" && coordinateSystem != "Image", Message[CakeWaveletStack::invalidInput, coordinateSystem, "CoordinateSystem","Should be either \"Matrix\" or \"Image\""]];
			If[design != "M" && design != "N", Message[CakeWaveletStack::invalidInput, design, "Design","Should be either \"M\" or \"N\""]];
		
			(* Compute the cakewavelet-stack *)
			cakeObj = CakeWaveletStack[
				size, nOrientations, design, inflectionPoint, mnOrder, splineOrder, overlapFactor, dcStdDev, directional, spatialWindow, coordinateSystem
			];

			(* ToDo: do some post creation checking: 1) does the Fourier-cake go to 0? 2) do the Dc and Mn window interfere? *)

			(* Create Packed Array *)
			(* ToDo: check this earlier *)
			(*cake = If[!Developer`PackedArrayQ[cake], Developer`ToPackedArray[cake], cake];*)

			(* Return the cakewavelet stack *)
			Return[cakeObj];
			
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStack- - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		

		
		CakeWaveletStack[size_, nOrientations_, design_, inflectionPoint_, mnOrder_, splineOrder_, overlapFactor_, dcStdDev_, directional_, spatialWindow_, coordinateSystem_] := Module[
				
			{cakeObj, cake, useWindow, filters, cakeF, dcFilter, window, s, dcSigma},

			(* Create contianer to store the cakewavelets in *)
			cakeObj = ObjCakeWavelet[{
				"WaveletSize"->size,
				"CoordinateSystem"->coordinateSystem,
				"Directional"->directional,
				"Symmetry"->Pi,
				"Design"->design,
				"InflectionPoint"->inflectionPoint,
				"SplineOrder"->splineOrder,
				"MnOrder"->mnOrder,
				"DcStandardDeviation"->dcStdDev,
				"SpatialWindow"->spatialWindow,
				"OverlapFactor"->overlapFactor
			}];

			(* Check if the spatial window is used *)
			useWindow = If[spatialWindow =!= False, True, False];
			
			(* Convert the dc sigma to the Fourier domain *)
			dcSigma = (1/dcStdDev)*(size/Pi);
		
			(* Compute the Fourier cake *)
			filters = CakeWaveletStackFourier[size,2Pi/nOrientations,splineOrder,overlapFactor,inflectionPoint,mnOrder,dcSigma,useWindow];
			cakeF = filters[[1;;-2]];
			dcFilter = filters[[-1]];
		
			(* Scale the cake according to its design*)
			Switch[design,
				"M", cakeF = Sqrt[cakeF]; dcFilter = Sqrt[dcFilter]
			];
			cakeObj = SetElement[cakeObj, "FourierCake"->cakeF, "FourierDcFilter"->dcFilter];
			
			(* Inverse (centered) Fourier Transform *)
			cake = CenteredInverseFourier/@cakeF;
			cakeObj = SetElement[cakeObj, "PreSpatialFilters"->cake];
		
			(* Apply the spatial window *)
			(***********TO BE REMOVED *****************)
			If[spatialWindow =!= False,
		
				(* The center pixel of the cake must sum up to 1 *)
				s=2; (* Preferably should be 0, however this seems to work better *)
				cakeF[[All,Ceiling[(size-1)/2]+1-s;;Ceiling[(size-1)/2]+1+s,Ceiling[(size-1)/2]+1-s;;Ceiling[(size-1)/2]+1+s]]=1/nOrientations;
				
				(* compute the inverse Fourier *)
				cake = CenteredInverseFourier/@cakeF;
				
				(* Apply the spatial window *)
				window = WindowGauss[size,spatialWindow];
				cake = (window*#)&/@cake;
				 
			];
			(***********************************)
		
			(* Cut the single wavelets in two directional wavelets *)
			If[directional,
				cake = cake * ErfSet[size, (overlapFactor*nOrientations) / 2,Pi];
			];	
		
			(* Adjust the coordinate system to Image coordinates *)
			cake = Switch[coordinateSystem,
				"Image", Transpose/@cake,
				"Matrix", cake
			];

      (* Append the DC-filter, if the spatial window is used this should be a spike *)
			cakeObj = SetElement[cakeObj, "Data"->cake, "DcFilter"->CenteredInverseFourier[dcFilter]];
			
			(* Return the Stack *)
			Return[cakeObj];
			
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* CakeWaveletStackFourier - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


		
		CakeWaveletStackFourier = Compile[{{size,_Integer,0},{sφ,_Real,0},{splineOrder,_Integer,0},{overlapFactor,_Integer,0},{inflectionPoint,_Real,0},{mnOrder,_Integer,0},{dcStdDev,_Real,0},{usesWindow,True|False}},
			
			Module[{dcWindow = ConstantArray[1.,{size,size}], mnWindow, angleGrid,cake,filters},
				
				(* Gaussian center window to remove pie center, if a window in the spatial domain is used this is omitted *)
				If[!usesWindow,
					dcWindow = dcWindow - WindowGauss[size,dcStdDev];
				];
				
				(* Gaussian decay window *)
				mnWindow  = MnWindow[size,mnOrder,inflectionPoint];
		
				(* Construct the cake pieces *)
				angleGrid = PolarCoordinateGridAngular[size];
				cake = Table[
					dcWindow * mnWindow * (BSplineMatrixFunc[splineOrder,Mod[angleGrid - θ - π/2 , 2π , -π ] / sφ]) / overlapFactor,
					{θ,π,2π-sφ/overlapFactor,(sφ/overlapFactor)}
				];
				
		
				(* Add Dc filter to the output *)
				filters = Append[cake,(1-dcWindow)];		
		
				(* Return the Fourier cake *)
				Return[filters]
		
			], {{WindowGauss[size,dcStdDev],_Real,2},{MnWindow[size,mnOrder,inflectionPoint],_Real,2},{PolarCoordinateGridAngular[size],_Real,2}}
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* BSplineMatrixFunc - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		
		BSplineMatrixFunc = Compile[{{n,_Integer},{x,_Real,2}},
			Module[{function,intervalCheck},
				Total[
					Table[
						function=1/(2 (n+1-1)!) Total[Table[Binomial[n+1,k](x+(n+1)/2-k)^(n+1-1) (-1)^k Sign[i+(n+1)/2-k],{k,0,n+1}]];
						intervalCheck=Round[If[i<(n+1-1)/2,UnitStep[x-(i-1/2+$MachineEpsilon)]*UnitStep[-(x-(i+1/2))],UnitStep[x-(i-1/2+$MachineEpsilon)]*UnitStep[-(x-(i+1/2-$MachineEpsilon))]]];
						function*intervalCheck,
						{i,(1-n-1)/2,(n-1+1)/2}
					]
				]
			]
		];




		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* MnWindow - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		MnWindow = Compile[{{size,_Integer},{n,_Integer},{inflectionPoint,_Real}},
			Module[{ρMatrix},
					ρMatrix = $MachineEpsilon + 1/Sqrt[(2*(inflectionPoint)^2)/(1+2 n)] PolarCoordinateGridRadial[size];
					Total[Table[E^-ρMatrix^2*ρMatrix^(2k)/k!,{k,0,n}]]
				]
				,{{k!,_Real},{PolarCoordinateGridRadial[size],_Real,2}}
		];
		


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* PolarCoordinateGridRadial - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		PolarCoordinateGridRadial = Compile[{{size,_Integer}},
			Table[
				Sqrt[x^2+y^2] /((size-1)/2),
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
				


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* PolarCoordinateGridAngular- - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		PolarCoordinateGridAngular = Compile[{{size,_Integer}},
			Table[
				Arg[x+I y],
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
		
		
		
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* WindowGauss - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		WindowGauss = Compile[{{size,_Integer},{σs,_Real}},
			Table[
				E^(-(x^2/(2σs^2))-y^2/(2σs^2)),
				{x,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]},
				{y,-((size-1)/2)-Mod[(size-1)/2,1],(size-1)/2-Mod[(size-1)/2,1]}
			]
		];
		
		
				
	End[];

EndPackage[];

ObjGaborWavelet.m

(*************************************************************************************************************************
** ObjGaborWavelet.m
** This .m file contains common function used for the orientation scores 2D.
**
** Author: T.C.J. Dela Haije <T.C.J.Dela.Haije@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`Classes`ObjGaborWavelet`", {
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjOrientationWavelet`"
}];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* Declare Class - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  DeclareClass[ObjOrientationWavelet, ObjGaborWavelet,
    {
      "Scale"              -> Automatic,
      "K0"                 -> {0,3},
      "Epsilon"            -> 4
    }
  ];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Declare Invariant - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DeclareInvariant["ObjGaborWavelet",

      (* Validate Elements *)
      Block[

        {
          scale            = GetElement[#, "Scale"],
          k0               = GetElement[#, "K0"],
          epsilon          = GetElement[#, "Epsilon"]
        },

        And[

          (Positive[scale] || scale === Automatic)
              /. False :> (Message[ValidQ::elfail, "Scale", "be a positive integer"]; False),

          (MatchQ[k0, {_Integer, _Integer}])
              /. False :> (Message[ValidQ::elfail, "K0", "be list of two positive integers"]; False),

          ((NumericQ[epsilon] && Positive[epsilon]))
              /. False :> (Message[ValidQ::elfail, "Epsilon", "be a positive number"]; False)


        ]

       ]&


    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Get Properties- - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjGaborWavelet],

          Null

        ,
        Protect@@protected
      ];
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summary Box - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Module[{protected},
      Internal`WithLocalSettings[
        protected = Unprotect[ObjGaborWavelet],

        MakeBoxes[instance : ObjGaborWavelet[elems : OptionsPattern[]], fmt_] ^:= Module[{alwaysGrid, sometimesGrid},

          alwaysGrid = {
            BoxForm`MakeSummaryItem[{"Orientations: ", instance["NumberOfWavelets"]}, fmt],
            BoxForm`MakeSummaryItem[{"WaveletSize: ", instance["WaveletSize"]}, fmt],
            BoxForm`MakeSummaryItem[{"Symmetry: ", instance["Symmetry"]}, fmt],
            BoxForm`MakeSummaryItem[{"Directional: ", instance["Directional"]}, fmt],
            BoxForm`MakeSummaryItem[{"CoordinateSystem: ", instance["CoordinateSystem"]}, fmt],
            BoxForm`MakeSummaryItem[{"Valid: ", ValidQ[instance]}, fmt]
          };

          sometimesGrid = {
            BoxForm`MakeSummaryItem[{"Elements: ", ElisionsDump`expandablePane[instance["ClassElements"] /. Thread[Thread[x : Flatten[{elems}][[All, 1]]] :> Style[x, Bold]]]}, fmt],
            BoxForm`MakeSummaryItem[{"ByteCount: ", ByteCount[instance]}, fmt]
          };

          BoxForm`ArrangeSummaryBox[
            ObjGaborWavelet,
            ToString[ObjGaborWavelet] <> "[<>]", (*Ideally this should be adapted in the output form when BoxForm`UseTextFormattingQ is True.*)
            instance["Image"],
            alwaysGrid,
            sometimesGrid,
            fmt,
            "Interpretable" -> False
          ]

        ];,

        Protect@@protected
      ]
    ];



  End[];



EndPackage[];

GaborWavelets.m

(*************************************************************************************************************************
** GaborWavelets.m
** This .m file contains functions to create gabor-wavelets
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1	
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Filters`GaborWavelets`",{
	"LieAnalysis`Common`",
	"LieAnalysis`OrientationScores2D`Common`",
  "LieAnalysis`Classes`Classes`",
	"LieAnalysis`Classes`ObjGaborWavelet`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GaborWaveletStack - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[GaborWaveletStack] = {
			"WaveletSize"->"Automatic",
			"Epsilon" -> 4, (* Defines the anisotropy of the wavelet *)
			"K0" -> {0, 3},
			"Directional" -> False,
			"CoordinateSystem" -> "Matrix"
		};



		GaborWaveletStack::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";



		GaborWaveletStack[nOrientations_, scale_, opts:OptionsPattern[]] := Module[
			
			(* Local Variables Declaration *)	
			{
				k0 								= OptionValue["K0"],
				epsilon 					= OptionValue["Epsilon"],
				directional 			= OptionValue["Directional"],
				coordinateSystem 	= OptionValue["CoordinateSystem"],
				size 							= OptionValue["WaveletSize"],
				gaborObj
			},

			(* Compute the size *)
			size = If[size == "Automatic",
				Floor[8*Sqrt[epsilon]*scale],
				size
			];
			size = If[EvenQ[size], size + 1, size];

			(* Validate the Input *)
			And[

				(OddQ[size] && Internal`PositiveIntegerQ[size] )
					/. False :> (Message[GaborWaveletStack::invalidInput, size, "size", "Sould be an odd positive integer"]; False),

				Internal`PositiveIntegerQ[nOrientations]
					/. False :> (Message[CakeWaveletStack::invalidInput, nOrientations, "nOrientations", "Should be a positive integer"]; False),

				(NumericQ[scale] && Positive[scale])
					/. False :> (Message[GaborWaveletStack::invalidInput, scale, "scale", "Should be a positive number"]; False),

				MatchQ[k0, {_,_}]
					/. False :> (Message[GaborWaveletStack::invalidInput,k0,"K0","Should be a vector2"]; False),

				Positive[epsilon]
					/. False :> (Message[GaborWaveletStack::invalidInput, epsilon, "Epsilon","Should be a positive number"]; False),

				BooleanQ[directional]
					/. False :> (Message[GaborWaveletStack::invalidInput, directional, "Directional", "Should be a Boolean"]; False),

				MatchQ[coordinateSystem, "Image" | "Matrix"]
					/. False :> (Message[GaborWaveletStack::invalidInput, coordinateSystem, "CoordinateSystem","Should be either \"Image\" of \"Matrix\""]; False)

			] /. False :> Abort[];

			(* Compute Gabor-Wavelet Stack *)
			gaborObj = GaborWaveletStack[size, nOrientations, scale, k0, epsilon, directional, coordinateSystem];

		 	(* Return the stack *)
			Return[gaborObj]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GaborWaveletStack - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		GaborWaveletStack[size_, nOrientations_, scale_, k0_, epsilon_, directional_, coordinateSystem_] := Module[

			(* Local Variable Declaration *)
			{gaborObj, wavelets, A},

			(* Creating container to save wavelet *)
			gaborObj = ObjGaborWavelet[];

			(* Save Options *)
			gaborObj = SetElement[gaborObj,
				"WaveletSize"->size,
				"Scale"->scale,
				"K0"->k0,
				"Epsilon"->epsilon,
        "Symmetry"->Pi,
				"Directional"->directional,
				"CoordinateSystem"->coordinateSystem
			];

			(* Compute A matrix *)
      A =({{epsilon^(-1/2), 0}, {0, 1}});

			(* Compute the Gabor Wavelet Stack *)
      wavelets = GenerateKernelSetGabor[A, k0, scale, 2Pi/nOrientations, size];
	  	gaborObj = SetElement[gaborObj, "PreSpatialFilters"->wavelets];

      (* Cut the single wavelets in two directional wavelets *)
      If[directional,
        wavelets = wavelets * ErfSet[size, nOrientations/2, Pi];
      ];

      (* Adjust the coordinate system to Image coordinates *)
      wavelets = Switch[coordinateSystem,
        "Image", Transpose/@wavelets,
        "Matrix", wavelets
      ];
      gaborObj = SetElement[gaborObj, "Data"->Developer`ToPackedArray@wavelets];

			(* Return Gabor-wavelets *)
			Return[gaborObj]


		];


		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* GenerateKernelSetGabor- - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		GenerateKernelSetGabor = Compile[{{A, _Real, 2}, {k0, _Real, 1}, {a, _Real}, {dTheta, _Real}, {dim, _Real}},
  		
  			Module[{},

					(* Construct the wavelets *)
   				$MachineEpsilon + (1/((4 a Pi)/E^(9/2))) (1/a)*Table
   				[
     	 			Table[
     	 				With[
	     	 					{
	         					xx = {Cos[-Theta + Pi/2],-Sin[-Theta + Pi/2]}.{x, y},
	         					yy = {Sin[-Theta + Pi/2], Cos[-Theta + Pi/2]}.{x, y}
	         				},
	        				Exp[I (k0.({yy, xx}/a))] Exp[-(1/2) Norm[(A.({yy, xx}/a))]^2]
							],
       				{x, -Floor[dim/2], Floor[dim/2] - (1 - 2*Mod[dim/2, 1])},
       				{y, -Floor[dim/2], Floor[dim/2] - (1 - 2*Mod[dim/2, 1])}
       			],
      			{Theta, Pi, 2*Pi-dTheta, dTheta}
					]
   			]
   			
  		];
  	
  	
	  
	End[];
  
  

EndPackage[];

Construction.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains functions to construct a multi-stack and multi-orientation using different kind of filters
** 
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.2
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Construction`",{
	"LieAnalysis`",
	"LieAnalysis`Common`",
	"LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjWavelet`",
  "LieAnalysis`Classes`ObjCakeWavelet`",
  "LieAnalysis`Classes`ObjGaborWavelet`",
  "LieAnalysis`Classes`ObjWaveletTransform`",
  "LieAnalysis`Classes`ObjOrientationScores2D`",
  "LieAnalysis`OrientationScores2D`Filters`CakeWavelets`",
  "LieAnalysis`OrientationScores2D`Filters`GaborWavelets`"
}];




	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* WaveletTransform- - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		
		
		
		WaveletTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		


		WaveletTransform[img_, waveletObj_/;ValidQ[waveletObj,"ObjWavelet"]] := Module[

    	(* Local Varialble Declaration *)
			{wt2d, image, multiObj, kernels = waveletObj["Data"]},

			(* Container to store the Multi-space in *)
			multiObj = ObjWaveletTransform["Wavelets"->waveletObj];

			(* Process input image *)
      image = ImageInputProcessor[img];
			multiObj = SetElement[multiObj, "InputData"->image];

			(* Compute the Tranform stack *)
			wt2d = WaveletTransform[image, kernels];
			multiObj = SetElement[multiObj, "Data"->wt2d];

			(* Return the Multi-space object *)
			Return[multiObj]

		];



		WaveletTransform[img_, kernels_List, opts:OptionsPattern[]] := Module[

			(* Local Varialble Declaration *)
			{image, os},

			(* Process the input image *)
      image = ImageInputProcessor[img];

      os = Developer`ToPackedArray@Map[
        ListConvolve[#, image, Ceiling[Dimensions[#]/2]]&,
        kernels
      ];

			(* Strip List if only one kernel is provided *)
			If[Length[kernels] == 1, os = First[os]];
		    
		  (* Return the multi-space *)
			Return[os]
		   	
		];
		

		
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* OrientationScoresTransform- - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    OrientationScoreTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";
		
		
		
		Options[OrientationScoreTransform] = DeleteDuplicates@Join[Options[CakeWaveletStack], Options[GaborWaveletStack],{
			"Type"->"Cake",
			"Orientations"->12, (* The number of orientations to construct the Oriented WaveletStack *)
			"Scale"->1 (* The gabor scale parameter *)
		}];



		OrientationScoreTransform[img_, cwsObj_/;ValidQ[cwsObj, "ObjOrientationWavelet"]] := Module[

			(* Local Variables Declaration *)
			{image, wtObj, osObj, dcFilteredImage},

    	(* Process the input image *)
      image = ImageInputProcessor[img];

      (* Create the Orientation Score *)
      wtObj = WaveletTransform[image, cwsObj];

			(* Select appropiate method per filters *)
			Switch[Head[cwsObj],

					ObjCakeWavelet,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"]; (* Convert to OS2D child *)
            dcFilteredImage = WaveletTransform[image, {cwsObj["DcFilter"]}];
            osObj = SetElement[osObj, "DcFilteredImage"->dcFilteredImage];,

					ObjGaborWavelet,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"];,

					_,
        		osObj = ConvertClass[wtObj,"ObjOrientationScores2D"];

			];

			(* Re-arrange to x,y,theta and set symmetry property *)
			osObj = SetElement[osObj, {
				"Data"->Reorder[osObj["Data"], "SpatialFirst"],
				"Symmetry"->cwsObj["Symmetry"]
			}];

			(* Return the Orientation Score *)
      Return[osObj]

		];



		OrientationScoreTransform[img_, opts:OptionsPattern[]] := Module[

			(* Local Variables Declaration*)
			{
				type = OptionValue["Type"],
				scale = OptionValue["Scale"],
        orientations = OptionValue["Orientations"],
				wsObj, osObj
			},

			(* Input validation *)
			And[

				MatchQ[type, "Cake" | "Gabor"]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[type], "Type", "Should be either Cake or Gabor"]; False),

				Internal`PositiveIntegerQ[scale]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[scale], "Scale", "Should be a positive integer"]; False),

				Internal`PositiveIntegerQ[orientations]
					/. False :> (Message[OrientationScoreTransform::invalidInput, ToString[orientations], "Orientations", "Should be a positive integer"]; False)

			];

			(* Construct the desired wavelets *)
			Switch[type,
				"Gabor",
      		(* Construct Gabor Wavelet Stack *)
        	wsObj = GaborWaveletStack[orientations, scale, FilterRules[{opts}, Options[GaborWaveletStack]]],

				"Cake" | _,
					(* Compute Cake Wavelet Stack*)
					wsObj = CakeWaveletStack[orientations, FilterRules[{opts}, Options[CakeWaveletStack]]]

			];

			(* Compute OS2D *)
			osObj = OrientationScoreTransform[img, wsObj];

			(* Return the Orientation Score and metaData *)
			Return[osObj]

		];



	End[];



EndPackage[];

Reconstruction.m

(*************************************************************************************************************************
** Reconstruction.m
** This .m file contains functions to reconstruct an image from an 2D Orientation Score
**
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Reconstruction`",{
	"LieAnalysis`",
	"LieAnalysis`Common`",
	"LieAnalysis`Classes`Classes`"
}];



	Begin["`Private`"];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* InverseOrientationTransform2D - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Options[InverseOrientationScoreTransform] = {
			"Method" -> "Summation"
		};



		InverseOrientationScoreTransform::invalidInput = "The supplied value '`1`' for option `2` is invalid. (`3`)";



		InverseOrientationScoreTransform[ostObj_/;ValidQ[ostObj,"ObjOrientationScores2D"],opts:OptionsPattern[]] := Module[

			(* Local Variables Declaration *)
			{
				image,
				method = OptionValue["Method"]
			},

			(* Check Method in combination with the construction method *)
			And[

				MatchQ[method, "Summation" | "L2" | "Exact"]
					/. False :> (Message[InverseOrientationScoreTransform::invalidInput, ToString[method], "Method", "Should be either \"Exact\", \"L2\" or \"Summation\""]; False)

			];

			(* Reconstruct image according to specified method *)
			image = Switch[method,
				"Exact", Exact[ostObj],
				"L2", L2Adjoint[ostObj],
				"Summation" | _, Summation[ostObj]
			];

			(* Return the re-constructed image *)
			Return[image];

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Summation - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Summation[ostObj_] := Module[

			{os, image},

			(* Reorder the OS*)
			os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Integrate in theta direction and add the DC component *)
			image = Total[Re[os]] + Re[ostObj["DcFilteredImage"]];

			Return[image];
		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* L2Adjoint - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		L2Adjoint[ostObj_] := Module[

			(* Local Variables Declaration *)
			{os, image, convMultiSpace},

			(* Reorder the OS *)
      os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Convolve the layers once more with the kernel *)
      convMultiSpace = MapThread[WaveletTransform[#1, {#2}] &, {os, ostObj["Wavelets"]["Data"]}];

			(* Integrate in theta direction and add the dc-component *)
			image = Total[Re[convMultiSpace]] + Re[ostObj["DcFilteredImage"]];

			(* Return the reconstructed image *)
			Return[image]

		];



		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* Exact - - - - - - - - - - - - - - - - - - - - - - - - - - *)
		(* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



		Exact[ostObj_, threshold_:10^(-4)] := Module[

			{os, image, convMultiSpace, Mpsi, TMpsi, MphiInv,cakeF},

    	(* Reorder the OS *)
      os = Reorder[ostObj["Data"], "OrientationFirst"];

			(* Convolve all layers again with their corresponding wavelets *)
      convMultiSpace = MapThread[WaveletTransform[#1, {#2}] &, {os, ostObj["Wavelets"]["Data"]}];

			(* Compute Mphi-inverse *)
			cakeF = CenteredFourier/@ostObj["Wavelets"]["Data"];
			Mpsi = Total[Abs[cakeF]^2] + ostObj["Wavelets"]["FourierDcFilter"]^2;

			(* Chop to avoid division by small numbers *)
			TMpsi = Chop[Mpsi,threshold]/.{0->threshold};
			MphiInv = Re[CenteredInverseFourier[TMpsi^(-1)]];

			(* Convolve OS integrated over theta with Mphi and add the DC-component *)
			image = WaveletTransform[Re[Total[convMultiSpace]] + Re[ostObj["DcFilteredImage"]],{MphiInv}];

			(* Return the reconstructed image*)
			Return[Re[image]]

		];



	End[];



EndPackage[];

OrientationScoreDerivatives.m

(*************************************************************************************************************************
** Common.m
** This .m file contains functions to compute the cartesian derivatives in se(2)
**
** Author: F.C. Martin <f.c.martin@tue.nl>
** Author: E.J. Bekkers <e.j.bekkers@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores2D`Derivatives`OrientationScoreDerivatives`", {
    "LieAnalysis`",
    "LieAnalysis`Common`",
    "LieAnalysis`Classes`Classes`",
    "LieAnalysis`Classes`ObjOrientationScores2D`"
  }
];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrientationScoreDerivatives - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[OrientationScoreDerivatives] = {
      "Type" -> "LeftInvariant"
    };



    OrientationScoreDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], {sigmaSpatial_, sigmaOrientation_}, derivativeIndex_, opts:OptionsPattern[] ] := Module[

      (*Local variable declaration *)
      {
        type = OptionValue["Type"],
        sigmaOD, derivatives
      },

      (* Convert to discrete orientation sigma *)
      sigmaOD = sigmaOrientation / osObj["AngularResolution"];

      (* Select the correct derivatives *)
      derivatives = Switch[type,
        "Cartesian"  , CartesianDerivatives[osObj, sigmaSpatial, sigmaOD, derivativeIndex],
        "LeftInvariant" | _ , LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOD, derivativeIndex]
      ];

      (* Return the Derivatives *)
      Return[derivatives];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* CartesianDerivatives- - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_List] :=
        CartesianDerivatives[osObj, sigmaSpatial, sigmaOrientation, #]&/@order;



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_String] :=
        CartesianDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[order]];



    CartesianDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_Integer] := Module[

      (* Local variables *)
      {dir, orderDigits, orderList, derivatives},

      (* Convert to the format of Gaussian Derivative *)
      orderList = Count[IntegerDigits[order],#]&/@{1,2,3};

      (* Compute the derivatives *)
      (*todo: make the theta-axis circular *)
      derivatives = GaussianDerivative[osObj["Data"], {sigmaSpatial, sigmaSpatial, sigmaOrientation}, orderList];

      (* Return the derivatives *)
      Return[derivatives];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* LeftInvariantDerivatives- - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_List] :=
        LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, #]&/@order;



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_?(StringMatchQ[ToString[IntegerReverse[#]], ___ ~~ "3" ~~ ("*1*" | "*2*")]&)] := Module[

    (*Local Variables Declaration *)
      {
        orderString = ToString[order],  (* illegal order *)
        derivatives, orders, derivativeString, operators, derivative, osObjOut
      },

      (* Recompute Order *)
      derivativeString = StringSplit[OrderTheDerivatives[StringReverse[orderString]],{"+"->"+","-"->"-"}];
      {orders, operators} = GatherBy[derivativeString,MatchQ[#,"-"|"+"]&];  (* legal derivative orders that need to be computed *)

      (* Compute derivatives *)
      derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[StringReverse[#]]&/@orders];

      (* Add operators again and sum to the final derivative *)
      derivative = Plus @@ Prepend[MapThread[If[#2 == "-", -#1["Data"], #1["Data"]] &, {derivatives[[2 ;; -1]], operators}], derivatives[[1]]["Data"]];

      (* Store derivative in container *)
      osObjOut = ObjOrientationScores2D["Data"->derivative, "Symmetry"->derivatives[[1]]["Symmetry"], "InputData"->osObj];

      (* Return the derivative *)
      Return[osObjOut];

    ];




    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_String] := Module[

      (* Local Variables Declaration *)
      {derivatives},

      (* Get derivatives *)
      (MatchQ[ToUpperCase[order], "GRADIENT"])
          /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, {1,2,3}]);

      (MatchQ[ToUpperCase[order], "HESSIAN"])
        /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, {11,12,13,12,22,23,31,32,33}]);

      (MatchQ[ToExpression[order], _Integer])
          /. True :> (derivatives = LeftInvariantDerivatives[osObj, sigmaSpatial, sigmaOrientation, ToExpression[order]]);

      (* Return Derivatives *)
      Return[derivatives];

    ];



    LeftInvariantDerivatives[osObj_ /; ValidQ[osObj, "ObjOrientationScores2D"], sigmaSpatial_, sigmaOrientation_, order_Integer] := Module[

      (* Local Variables Declaration *)
      {

        angles, derivative, derOrder, scaledSigmaSpatial, scaledSigmaOrientation, n, gt, dgT, dx, dy, dt,
        symmetry,osObjOut
      },

      angles = Range[0, Abs[osObj["Symmetry"]] - osObj["AngularResolution"], osObj["AngularResolution"]];

      (* List of Derivatives that need to be computed *)
      derOrder = Reverse[IntegerDigits[order]];

      (* scale sigmas to the number of derivatives *)
      n = Length[derOrder];
      {scaledSigmaSpatial, scaledSigmaOrientation} = 1/Sqrt[n] {sigmaSpatial, sigmaOrientation};

      (* Compute Derivative *)
      derivative = osObj["Data"];
      symmetry = osObj["Symmetry"];

      Do[

        (* Either spatial derivative or orientation derivative *)
        Switch[dir,
          1 | 2,

            (* Blur the OS in thera-direciton *)
            gt = GaussianOrientationBlur[derivative, scaledSigmaSpatial, scaledSigmaOrientation, symmetry];

            (* Compute the x and y-derivative *)
            dgT = Reorder[gt, "OrientationFirst"];
            dx = GaussianDerivative[#, {scaledSigmaSpatial,scaledSigmaSpatial}, {1,0},"Fixed"]&/@dgT;
            dy = GaussianDerivative[#, {scaledSigmaSpatial,scaledSigmaSpatial}, {0,1},"Fixed"]&/@dgT;

            (* Rotate to the eta, xi frame *)
            derivative = Switch[dir,
              1, MapThread[( #1 Cos[#3] + #2 Sin[#3])&, {dx, dy, angles}],
              2, MapThread[(-#1 Sin[#3] + #2 Cos[#3])&, {dx, dy, angles}]
            ];

            (* Reorder x,y,theta *)
            derivative = Reorder[derivative, "SpatialFirst"];

            (* Update Symmetry *)
            symmetry = Switch[symmetry,
                 Pi, -Pi,
                -Pi,  Pi,
                2Pi, 2Pi
            ],

          3,

            (* Compute the theta-derivative *)
            dt = GaussianDerivative[derivative, {scaledSigmaSpatial, scaledSigmaSpatial, scaledSigmaOrientation}, {0,0,1}, "Periodic"];
            derivative = dt / osObj["AngularResolution"];

        ];

        ,{dir, derOrder}

      ];

      (* ObjOrientationScore Object *)
      osObjOut = ObjOrientationScores2D["Data"->derivative, "Symmetry"->symmetry, "InputData"->osObj];

      (* Return Derivatives *)
      Return[osObjOut];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GaussianOrientationBlur - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GaussianOrientationBlur[os_, sigmaSpatial_, sigmaOrientation_, symmetry_] := Module[

      (* Local Variable Declaration *)
      {blurredOs},

      (* Full periodic blurring in theta-direction *)
      With[

        (* If the periodicity is pi, extend it to 2pi *)
        {
          fullPeriodDerivative = Switch[symmetry,
            Pi, Join[os, Conjugate[os],3],
            -Pi, Join[os, -Conjugate[os],3],
            2Pi, os
          ]
        },

        (* Blur in theta-direction *)
        blurredOs = GaussianFilter[fullPeriodDerivative, {4 {0,0,sigmaOrientation}+1,{0,0,sigmaOrientation}}, Padding->"Periodic"];

        (* Cut off pi-2pi section if periodicity is pi *)
        blurredOs = If[MatchQ[symmetry, Pi | -Pi], blurredOs[[All,All,1;;Dimensions[os][[3]]]], blurredOs];

        (* Return the blurred OS *)
        Return[blurredOs];


      ];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrderTheDerivatives - - - - - - - - - - - - - - - - - - - *)
    (* Theta derivative should always be first derivative,
       see P116-119, Erik Franken PhD Thesis - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    OrderTheDerivatives[order_Integer] := OrderTheDerivatives[ToString[order]];



    OrderTheDerivatives[order_String] := Module[

      (* Define substitution rules *)
      {
        thetaXi   = prior___~~"31"~~post___:>prior<>"2"<>post<>"+"<>prior<>"13"<>post,
        thetaEta  = prior___~~"32"~~post___:>prior<>"23"<>post<>"-"<>prior<>"1"<>post,
        orderTerms, derivativeOrderReWritten, previousResult, result
      },

      (* Save the original *)
      result = order;

      (* Repeat the process untill theta is the first derivative *)
      While[previousResult =!= result,

        (* Update the itteration condition *)
        previousResult = result;
        orderTerms = StringSplit[result,{"+"->"+","-"->"-"}];

        (* Substitute *)
        derivativeOrderReWritten = StringReplace[#,{thetaEta,thetaXi}]&/@orderTerms;
        result = StringJoin[derivativeOrderReWritten];

      ];

      (* Return the reorderd result *)
      Return[result];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GaussianDerivatives - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GaussianDerivative[data_, sigmas_, orders_, padding_:"Periodic"] := Module[

      (* Local Variables Declaration *)
      {
        singlePaddingMethod = MatchQ[padding,"Periodic" | "Fixed" | "Reflected" | "Reversed" | None],
        derivative
      },

      (* Compute the derivative(s) depending on input *)
      (MatchQ[orders, {_Integer..}] && singlePaddingMethod)
          /. True :> (derivative = GaussianFilter[data, {4 sigmas+1, sigmas}, orders, Method -> "Gaussian", Standardized -> False, Padding->padding]);

      (MatchQ[orders, {{_Integer..}..}] && singlePaddingMethod)
          /. True :> (derivative = GaussianFilter[data, {4 sigmas+1, sigmas}, #, Method -> "Gaussian", Standardized -> False, Padding->padding]&/@orders);

      (MatchQ[orders, {{_Integer..}..}] && !singlePaddingMethod)
          /. True :> (derivative = MapThread[GaussianFilter[data, {4 sigmas+1, sigmas}, #1, Method -> "Gaussian", Standardized -> False, Padding->#2]&,{orders, padding}]);

      (* Return the derivative *)
      Return[derivative];

    ];



  End[];



EndPackage[];

Common.m

(*************************************************************************************************************************
** Common.m
** This .m file contains common function used for the 3D Orientation Scores
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Common`"];



  CartesianToSphericalCoordinates::usage = "todo"; (*todo: should not be public *)



  vertexAreas::usage = "vertexAreas [vertices_,polygons_] Determine the areas surrounding each vertex according to the formula's in thesis Franken."; (*todo: schould not be public *)



  Begin["`Private`"];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* CartesianToSphericalCoordinates - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  CartesianToSphericalCoordinates[qvecs_] := CartesianToSphericalCoordinatesC[qvecs];
  CartesianToSphericalCoordinatesC = Compile[{{vec,_Real,1}},
    Module[{qx,qy,qz,R,θ,ϕ},
      {qx,qy,qz} = vec;
      R = Sqrt[qx^2+qy^2+qz^2];
      If[Chop[R]==0,Return[{0.,0.,0.}]];
      {qx,qy,qz} = {qx,qy,qz}/R;
      If[qz<1-10^-8,
        If[qz>-1+10^-8,
          {θ,ϕ} = {ArcCos[qz/Sqrt[qx^2+qy^2+qz^2]],ArcTan[qx,qy]};,
          {θ,ϕ} = {π,0};
        ];,
        {θ,ϕ} = {0,0};
      ];
      {R,θ,ϕ}
    ],RuntimeAttributes->{Listable}
  ];



  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* OTHER - - - - - - - - - - - - - - - - - - - - - - - - - - *)
  (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



  tpv[p_,v_] := p-(v.p)v;

  dS2[v1_,v2_] := ArcCos[v1.v2/(Norm[v1]Norm[v2])];

  triangleArea[v1_,v2_,v3_] := dS2[tpv[v2,v1],tpv[v3,v1]]+dS2[tpv[v1,v2],tpv[v3,v2]]+dS2[tpv[v1,v3],tpv[v2,v3]]-π;

  VertexAreas[vertices_,polygons_] := Module[

    (* Local Variables Declaration *)
    {
      areas=ConstantArray[0,Length[vertices]]
    },

    (* Compute area *)
    Map[(areas[[#]] += 1/3 triangleArea[Sequence@@(vertices[[#]])])&, polygons]; (* todo:clean *)

    (* Return area *)
    Return[areas]

  ];



End[];

EndPackage[];

ElectrostaticRepulsion.m

(*************************************************************************************************************************
** ElectrostaticRepulsion.m
** This .m file contains common function used for evenly sampled sphere by using electrostatic repulsion on a sphere.
**
** Author: M. Froeling <~>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Filters`ElectrostaticRepulsion`"];

  SphereSampleCoordinates::usage = "
    todo
    ";

  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphereSampleCoordinates - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphereSampleCoordinates[n_, iterations_:1000] := Block[

      (* Local Variables Declaration*)
      {points},

      (* Random sample the sphere *)
      points = SphereRandomSampleCoordinates[n];

      (* Optimize the position in 'iterations' steps *)
      Do[
        points = GradOptimize[points],
        {iterations}
      ];

      (* Return the (alsmost) uniformly sampled sphere coordinates *)
      Return[points];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphereRandomSampleCoordinates - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphereRandomSampleCoordinates[n_] := Sign[#[[3]]+10.^-16] Normalize[#]&/@RandomReal[NormalDistribution[],{n,3}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GradOptimize  - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GradOptimize = Compile[{{points,_Real,2}},Block[

      {n,pointsi,pointsmat,distmatxyz,diag,distmat,velocity,pointsnew},

      n=Length[points];
      pointsi=Join[points,-points];
      pointsmat=ConstantArray[pointsi,2 n];
      distmatxyz=pointsmat-Transpose[pointsmat];
      diag=DiagonalMatrix[ConstantArray[10.^32,2 n]];
      distmat=Total[Transpose[distmatxyz,{2,3,1}]^2]+diag;
      velocity=Total[(distmatxyz/distmat)];
      pointsnew=(Sign[#[[3]]+10.^-16]*Normalize[#]&/@(pointsi+velocity));
      pointsnew[[;;n]]]

    ];



  End[];



EndPackage[];

CakeWavelets.m

(*************************************************************************************************************************
** CakeWavelets.m
** This .m file contains functions to create a 3D Cake Wavelet Stack.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Filters`CakeWavelets`",{
  "PolyhedronOperations`",
  "LieAnalysis`Common`",
  "LieAnalysis`OrientationScores3D`Common`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`OrientationScores3D`Filters`ElectrostaticRepulsion`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* CakeWaveletStack3D- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[CakeWaveletStack3D] = {
      "Epsilon" -> 0.00001,
      "SigmaAngle" -> Automatic,
      "Gamma" -> 0.8,
      "DcStandardDeviation" -> 8
    };



    CakeWaveletStack3D[nOrientations_, size_, opts:OptionsPattern[]] := Module[

      (*Local Variables Declaration *)
      {
        epsilon = OptionValue["Epsilon"],
        sigmaAngle = OptionValue["SigmaAngle"],
        gamma = OptionValue["Gamma"],
        DcStandardDeviation = OptionValue["DcStandardDeviation"],
        sigmaRadialHigh, cws3dObj
      },

      (*todo: input validation *)


      (* Compute the radial (gaussian) cut-off *)
      sigmaRadialHigh = (1 - gamma)/2;

      (* Create cakewavelet 3d stack *)
      cws3dObj = CakeWaveletStack3D[nOrientations, size, sigmaAngle, gamma, sigmaRadialHigh, DcStandardDeviation, epsilon];

      (*Return the Orientation Score Transformed image *)
      Return[cws3dObj]

    ];

    CakeWaveletStack3D[nOrientations_, size_, sigmaAngle2_, gamma_, sigmaRadialHigh_, DcStandardDeviation_, epsilon_] := Module[

      (* Local Variables Declaration *)
      {
        DcStandardDeviationF = (*size*)1/(DcStandardDeviation*Pi), (* Convert Dc Standard deviation to the Fourier Domain *)
        scale, sigmaAngle = sigmaAngle2, (*todo: *)
        shCoefficientsVector, shAntiSymmetrizationMatrix,shFunkTransformMatrix,shRotationMatrix,sphericalHarmonicsTable,
        dcFilter, wavelet, waveletF, gridOrientationCoords, lMax, waveletOrientationCoords, cake3DObj,
        mnWindow3D, dcWindow3D, topology, mesh, meanVertexDistance
      },

      (* Create container to store the wavelets in *)
      cake3DObj = ObjCakeWavelet3D[];

      (* Compute uniform sampled coordinates on the sphere *)
      waveletOrientationCoords = SphereSampleCoordinates[nOrientations];
      waveletOrientationCoords = Join[waveletOrientationCoords,-waveletOrientationCoords];
      cake3DObj = SetElement[cake3DObj, "OrientationList" -> waveletOrientationCoords];

      (* Determine the neighbors of each orientation *)
      mesh = ConvexHullMesh[waveletOrientationCoords];
      topology = MeshCells[mesh,2][[All,1]];

      (* Compute the sigmaAngle based on the orientations *)
      If[sigmaAngle === Automatic,
        meanVertexDistance = Mean[ArcCos[#[[1]].#[[2]]] & /@ MeshPrimitives[mesh, 1][[All, 1]]];
        sigmaAngle = (meanVertexDistance/2)/Sqrt[Log[2]];
      ];
      scale = (sigmaAngle^2) / 2;(* Scale of the spherical harmonics *)

      (* Compute maximal SH order L *)
      lMax = DetermineMaxOrderL[scale, epsilon];

      (* Compute grid upon which the Spherical Harmonics can be defined *)
      gridOrientationCoords = GridOrientationsTable[size];

      (* Compute the spherical harmonics table upon the gridOrientations up to order lMax *)
      sphericalHarmonicsTable = SphericalHarmonicTable[gridOrientationCoords, lMax, False]; (* = Y_{m}^{l}(\theta, \phi) *)

      (* Create the matrix used to perform the rotation of a function expressed in spherical harmonics towards the input orientation *)
      shRotationMatrix = SphericalHarmonicsRotateMatrix[waveletOrientationCoords, lMax, True];(* R_{\alpha, \beta, \gamma} *)

      (* Create the matrix used to perform the Funk transform of a function expressed in spherical harmonics *)
      shFunkTransformMatrix = SphericalHarmonicFunkTransformMatrix[lMax]; (* c -> 2Pi P_l c *)

      (* Create the matrix used to perform the anti-symmetrization of a function expressed in spherical harmonics *)
      shAntiSymmetrizationMatrix = SphericalHarmonicAntiSymmetrizationMatrix[lMax]; (* c -> (1 - (-1)^l ) c *)

      (* Determine Spherical Harmonic coefficients up to order 'L' of the diffusion kernel for diffusion scale 'scale' *)
      shCoefficientsVector = SphericalHarmonicCoefficientsDiffusionKernel[lMax, scale];

      (* Apply the Funk-transform and Anti-Symmetrization to the coefficients *)
      shCoefficientsVector = shFunkTransformMatrix.shCoefficientsVector + shAntiSymmetrizationMatrix.shCoefficientsVector; (* F.s + A.s *)

      (* Rotate (steer the coefficients) the wavelets *)
      shCoefficientsVector = Map[#.shCoefficientsVector &, shRotationMatrix];

      (* Compute/'Lookup' wavelets from spherical harmnics table *)
      waveletF = MonitoredMap[ArrayReshape[ sphericalHarmonicsTable.# , {2 size+1, 2 size+1, 2 size+1}]&, shCoefficientsVector];

       (*Remove the high frequencies by applying a Gaussian decay in radial direction*)
      mnWindow3D = MnWindow3D[size, sigmaRadialHigh, gamma];  (*analog to the MnWindow in the 2D case*)

       (*Remove the low frequencies by removing the center*)
      dcWindow3D =  DcWindow3D[size, DcStandardDeviationF];

       (*Apply dc and mn window to the fourier wavelet*)
      waveletF = Map[#*(mnWindow3D - dcWindow3D)&, waveletF];
      cake3DObj = SetElement[cake3DObj, "WaveletsF" -> waveletF];

       (*Compute Fourier Inverse*)
      wavelet = Map[CenteredInverseFourier, waveletF];
      dcFilter = CenteredInverseFourier[dcWindow3D];

       (*Add meta-data*)
      cake3DObj = SetElement[cake3DObj, {
        "Data"                      -> wavelet,
        "RadialWindow"              -> (mnWindow3D - dcWindow3D),
        "DcFilter"                  -> dcFilter,
        "OrientationCoordinates"    -> waveletOrientationCoords,
        "Topology"                  -> topology,
        "Method"                    -> "JMIV",
        "LMax"                      -> lMax,
        "SigmaAngle"                -> sigmaAngle,
        "DcStandardDeviation"       -> DcStandardDeviationF,
        "MnStandardDeviation"       -> sigmaRadialHigh,
        "Gamma"                     -> gamma,
        "Epsilon"                   -> epsilon
      }];

       (*Return the 3D cake wavelets*)
      Return[cake3DObj]
    ];


    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* MnWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    MnWindow3D[size_, MnStandardDeviation_, gamma_] := RadialFunctionErf[MnStandardDeviation,gamma(**size*)][GridRadiusTable[size]];
    NormalizedComplementaryErrorFunction[x_,sigma_,offset_]:=Erfc[(x-offset)/(Sqrt[2]sigma)]/2;
    RadialFunctionErf[sigmaHigh_,offsetHigh_] := Compile[{ρ}, Evaluate[NormalizedComplementaryErrorFunction[ρ,sigmaHigh,offsetHigh]], RuntimeAttributes -> {Listable}];


    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DcWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DcWindow3D[size_, DcStandardDeviation_] := Gaussian[DcStandardDeviation][GridRadiusTable[size]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DcWindow3D- - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Gaussian[sigma_] := Compile[{ρ},Evaluate[E^(-ρ^2/(2*sigma^2)) ],RuntimeAttributes->{Listable}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicFunkTransformMatrix- - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicCoefficientsDiffusionKernel[L_,so_] := Table[If[SHIndexj2m[j]==0, Sqrt[(2SHIndexj2l[j]+1)/(4π)] E^(-(SHIndexj2l[j]+1)SHIndexj2l[j]so), 0], {j, 1, SHIndexlm2j[L, L]}];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicFunkTransformMatrix- - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicFunkTransformMatrix[s_List] := s.Transpose[SphericalHarmonicFunkTransformMatrix[SHIndexj2l[Dimensions[s][[-1]]]]];
    SphericalHarmonicFunkTransformMatrix[lMax_Integer] := DiagonalMatrix[Table[LegendreP[SHIndexj2l[jp],0],{jp,1,SHIndexlm2j[lMax,lMax]}]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicsRotateMatrix- - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicAntiSymmetrizationMatrix[s_List]:=s.Transpose[SHAntiSymmetrization[SHIndexj2l[Dimensions[s][[-1]]]]];
    SphericalHarmonicAntiSymmetrizationMatrix[lMax_Integer] := DiagonalMatrix[Table[1-(-1)^SHIndexj2l[jp],{jp,1,SHIndexlm2j[lMax,lMax]}]];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicsRotateMatrix- - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SphericalHarmonicsRotateMatrix[gridOrientationCoords_, lMax_, symmetricQ_]:= Module[

      {gamma, beta, rotationMatrix},

      (* Convert to Spherical coordinates *)
      {beta, gamma} = Transpose[CartesianToSphericalCoordinates[gridOrientationCoords], Join[Range[2, Depth[gridOrientationCoords] - 1], {1}]][[2;;3]];

      (* Compute rotation matrix *)
      rotationMatrix = RotateMatrixZY[gamma, beta, lMax, symmetricQ];

      (* Return the rotation Matrix *)
      Return[rotationMatrix];

    ];
    RotateMatrixZY[gamma_, beta_, lMax_, symmetricQ_]:= Module[

      {compiledRotateZY, j = SHIndexlm2j, l = SHIndexj2l, m = SHIndexj2m},

      compiledRotateZY = Compile[{ggamma, bbeta},
        Evaluate[
          Module[{M = ConstantArray[0, {j[lMax, lMax], j[lMax, lMax]}]},
            If[symmetricQ,
              Table[(M[[jp, j[l[jp], 0]]] = WignerD[{l[jp], m[jp], 0}, -ggamma, -bbeta, 0]), {jp, 1, j[lMax, lMax]}],(*we only need the values for m' = 0*)
              Table[Table[(M[[jp, j[l[jp], mp]]] = WignerD[{l[jp], m[jp], mp}, -ggamma, -bbeta, -0]), {mp, -l[jp], l[jp]}], {jp, 1, j[lMax, lMax]}]
            ];
            M
          ]
        ],
        RuntimeAttributes-> Listable
      ];

      Return[compiledRotateZY[gamma, beta]]

    ];
    (*todo: SHCompiledRotateZYZ function? Where is it good for? *)



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* DetermineMaxOrderL- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    DetermineMaxOrderL[so_,epsilon_:10^-3] := Module[

    (* Local Varialble Declaration *)
      {l = 0},

    (* Compute the maximal order L *)
      While[
        Sqrt[(2l+1)] * E^(-(l+1)l * so)>epsilon,
        l++
      ];

      (* Return the maximal order *)
      Return[l];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* SphericalHarmonicTable- - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)


    (* todo: cleanup *)
    SphericalHarmonicTable[orientations_, lMax_Integer, bool_] := Module[{Mmat, R, θ, ϕ, CSHT = CompiledSHTable[lMax,bool]},
      {R, θ, ϕ} = Transpose[CartesianToSphericalCoordinates[orientations], Join[Range[2, Depth[orientations] - 1], {1}]];
      Mmat = CSHT[θ, ϕ];
      Mmat
    ];
    CompiledSHTable[lMax_Integer, bool_] := Compile[{th,ph},
      Evaluate[
        If[bool,
          N[ExpToTrig[Table[If[SHIndexj2m[j]==0,SphericalHarmonicY[SHIndexj2l[j],SHIndexj2m[j],th,ph],0],{j,1,(lMax+1)^2}]]],
          N[ExpToTrig[Table[SphericalHarmonicY[SHIndexj2l[j],SHIndexj2m[j],th,ph],{j,1,(lMax+1)^2}]]]
        ]
      ],
      RuntimeAttributes->Listable
    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GridOrientationsTable - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GridOrientationsTable = Compile[{{size,_Integer}},

      Flatten[
        Table[
          If[x==0&&y==0&&z==0,
            {1,0.,0.},
            N[{x,y,z}/Sqrt[x^2+y^2+z^2]]
          ],
          {x,-size,size},{y,-size,size},{z,-size,size}
        ],2
      ]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* GridRadiusTable - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    GridRadiusTable = Compile[{{size,_Integer}},

      Table[
        Sqrt[x^2+y^2+z^2]/size,
        {x,-size,size},{y,-size,size},{z,-size,size}
      ]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Spherical Harmonics Indexing Functions- - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    SHIndexj2l[j_] := Floor[Sqrt[j-1]];
    SHIndexlm2j[l_,m_] := Round[(l^2+l+1)+m];
    SHIndexj2m[j_] := Round[j-1-(SHIndexj2l[j])^2-SHIndexj2l[j]];




End[];



EndPackage[];

Construction.m

(*************************************************************************************************************************
** Construction.m
** This .m file contains common function used to construct the 3D Orientation Scores.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Construction`",{
  "LieAnalysis`",
  "LieAnalysis`Common`",
  "LieAnalysis`OrientationScores3D`Filters`CakeWavelets`",
  "LieAnalysis`Classes`Classes`",
  "LieAnalysis`Classes`ObjCakeWavelet3D`",
  "LieAnalysis`Classes`ObjOrientationScores3D`",
  "LieAnalysis`Classes`ObjWaveletTransform`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* WaveletTransform3D- - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    WaveletTransform3D[data3d_, wavelet3dObj_/;ValidQ[wavelet3dObj,"ObjOrientationWavelet3D"]] := Module[

      (* Local Variables Declaration *)
      {data, objwtf3d, wt3d},

      (* Process the input *)
      data = ImageInputProcessor3D[data3d];

      (* Compute convolved volumes *)
      wt3d = WaveletTransform3D[data, wavelet3dObj["Data"]];

	    (* Create Object *)
	    objwtf3d = ObjWaveletTransform[{"Data"->wt3d, "InputData"->data, "Wavelets"->wavelet3dObj}];

      (* Return the transformed *)
      Return[objwtf3d]

    ];

    WaveletTransform3D[data3d_List, kernels_List, opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {wt3d},

      (* Compute convolved volumes *) (*todo: only the orientation scores should be transposed, so re-locate *)
      wt3d = Transpose[MonitoredMap[ListConvolve[#, data3d, Ceiling[Dimensions[#]/2]] &, Conjugate[kernels]], {4, 1, 2, 3}];

      (* Remove Brackets if only a single kernels is used *)
      wt3d = If[Length[kernels] == 1,
        wt3d[[All,All,All,1]],
        wt3d
      ];

      (* Return the transformed image *)
      Return[wt3d]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* OrientationScoreTransform3D - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[OrientationScoreTransform3D] = Join[
      {
        "Orientations"  ->  12,
        "WaveletSize"   ->  11
      },
      Options[CakeWaveletStack3D]
    ];
    OrientationScoreTransform3D[data3d_, opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {
        waveletSize = OptionValue["WaveletSize"],
        orientations = OptionValue["Orientations"],
        cws3d, os3d
      },

      (* Create cakewaveletstack 3d *)
      cws3d = CakeWaveletStack3D[orientations,waveletSize,FilterRules[{opts}, Options[CakeWaveletStack3D]]];

      (* Compute OS 3D *)
      os3d = OrientationScoreTransform3D[data3d, cws3d];

      (* Return the Orientation Score Transformed image *)
      Return[os3d];

    ];

    OrientationScoreTransform3D[data3d_, cwsObj3D_/;ValidQ[cwsObj3D, "ObjCakeWavelet3D"]] := Module[

      (* Local Variables Declaration *)
      {data, dc3d, os3d, os3dObj},

      (* Process the input *)
      data = ImageInputProcessor3D[data3d];

      (* Compute wavelet transform *)
      os3d = WaveletTransform3D[data, cwsObj3D];
      os3dObj = ConvertClass[os3d,"ObjOrientationScores3D"];

	    (* DC Component *)
      dc3d = WaveletTransform3D[data,{cwsObj3D["DcFilter"]}];
      os3dObj = SetElement[os3dObj, "DcData" -> dc3d];

      (* Return the Orientation Score Transformed image *)
      Return[os3dObj];

    ];



  End[];



EndPackage[];

Reconstruction.m

(*************************************************************************************************************************
** Reconstruction.m
** This .m file contains functions used for the reconstrucion of 3D Orientation Scores.
**
** Author: M.H.J.Janssen <m.h.j.janssen@tue.nl>
** Author: F.C. Martin <f.c.martin@tue.nl>
** Version: 0.1
**************************************************************************************************************************)
BeginPackage["LieAnalysis`OrientationScores3D`Reconstruction`",{
  "LieAnalysis`",
  "LieAnalysis`Common`",
  "LieAnalysis`Classes`Classes`"
}];



  Begin["`Private`"];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* InverseOrientationScoreTransform3D- - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Options[InverseOrientationScoreTransform3D] = {
      "Method" -> "Summation"
    };



    InverseOrientationScoreTransform3D::optfail = "The value provided for option `1` is not recognised. Should `2`";



    InverseOrientationScoreTransform3D[ost3dObj_/;ValidQ[ost3dObj, "ObjOrientationScores3D"], opts:OptionsPattern[]] := Module[

      (* Local Variables Declaration *)
      {
        image,
        method = OptionValue["Method"]
      },

      If[!MatchQ[method, "Summation" | "L2" |"Exact"], Message[InverseOrientationScoreTransform3D::optfail, "Method","be either \"Summation\", \"L2\" or \"Exact\""]];

      (* Execute the chosen reconstruction method *)
      image = Switch[method,
        "Exact", Exact[ost3dObj],
        "L2", L2Adjoint[ost3dObj],
        "Summation" | _, Summation[ost3dObj]
      ];

      (* Return the Orientation Score Transformed image *)
      Return[image];

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Summation - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Summation[ost3dObj_] := Module[

      {
        os, data
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[ost3dObj["Data"], {2, 3, 4, 1}];

      (* Integrate over the orientation *)
      data = Total[Re[os]]*( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]];

      (* Return the re-constructed volume *)
      Return[data];

    ];




    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* L2Adjoint - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    L2Adjoint[ost3dObj_] := Module[

      {
        image,
        os = ost3dObj["Data"],
        kernels = ost3dObj["Wavelets"]["Data"]
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[os, {2, 3, 4, 1}];

      (* Correlate the os-layers with their corresponding wavelet *)
      image = MapThread[ListCorrelate[#1, #2, Floor[Dimensions[#1]/2]]&,{kernels,os}];

      (* Integrate the orientation *)
      image = Re@Total[image] *( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]];

      (* Return re-constructed volume *)
      Return[image]

    ];



    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* Exact - - - - - - - - - - - - - - - - - - - - - - - - - - *)
    (* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *)



    Exact[ost3dObj_, threshold_:10^(-4)] := Module[

      {
        os, TMPsi, recmediate, rec, filtersF, Mpsi, data, MpsiInv,
        kernels = ost3dObj["Wavelets"]["Data"]
      },

      (* Re-arange the orientation score to {theta,x,y,z } *)
      os = Transpose[ost3dObj["Data"], {2, 3, 4, 1}];

      (* Convole all layers again with their corresponding wavelets*)
      recmediate = MapThread[ListCorrelate[#1, #2, Floor[Dimensions[#1]/2]]&,{kernels,os}];

      (* Compute Mphi *)
      filtersF = CenteredFourier/@ost3dObj["Wavelets"]["Data"];
      Mpsi = Total[Abs[filtersF]^2] + CenteredFourier[ost3dObj["Wavelets"]["DcFilter"]]^2;

      (* Chop to avoid division small numbers *)
      TMPsi = Chop[Mpsi, threshold] /. {0 -> threshold};
      MpsiInv = CenteredInverseFourier[TMPsi^(-1)];

      (* Add the dc-component and integrate the orientations *)
      data = WaveletTransform3D[Re[Total[recmediate]] * ( 4*Pi / ost3dObj["Wavelets"]["Orientations"]) + Re[ost3dObj["DcData"]],{MpsiInv}];

      (* Undo construction and deconstruction effect of the used kernel *)
      Return[data]

    ];



  End[];



EndPackage[];

Documentation

CakeWaveletStack

Details

CakeWaveletStack creates a set of (complex) orientation sensitive wavelets that can be used to create an Orientation Score.

Cake wavelets (((OverHat[ψ])^cake)) are constructed in the Fourier domain as ((OverHat[ψ])^cake)(ω) = (Subscript[B, k])((((φ mod 2π) - π/2)/(Subscript[s, θ]))) (Subscript[ℳ, N])(ρ), with ω = (ρ cos φ, ρ sin φ).
(Subscript[B, k]) denotes the k-th order B-spline given by (Subscript[B, k])(x) = ((Subscript[B, k-1]) * (Subscript[B, 0]))(x), with (Subscript[B, 0])=({{{, ({{1, if -1/2 < x < +1/2}, {0, otherwise}})}}).
(Subscript[ℳ, N])(ρ) specifies the radial function given by (Subscript[ℳ, N])(ρ) = (e^((ρ^2)/t))(∑_(k=0)^N)(((((ρ^2)(t^(-1))))^k)/(k!))≤ 1.

The function returns a object (ObjCakeWavelet) containing the Cake Wavelet Stack, metadata and some intermediate results. The following properties are accessable from the results:

cwsObj = CakeWaveletStack[12];
Framed@Multicolumn[Sort[cwsObj["ClassProperties"]]]

The function returns the cake wavelets using the default options. The following options can be set:

opts = Options[CakeWaveletStack]; 
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"The size of the wavelet",
"Determines the output axes",
"Whenever the spatial wavelets are single or double headed",
"Specifies the design constraints for the wavelets",
"Specifies the inflection point in radial direction of the gaussian window",
"Specifies the order of the B-Spline that is used to create the wavelets",
"Specifies the order of the Mn Window",
"The standard deviation of the gaussian that is used to remove the center",
"Whenever the DC-filter is outputted or not",
"If the spatial window is used or not",
"Indicates how many B-Splines overlap, thereby not narrowing the angular resolution"
}}],Frame->All]

If the Directional option is set to True, the OrientationScoreTransform is not symmetric.

The used Fourier transform does not scale internal, therefor to show the wavelet the ImageAdjust function needs to be used.

If the inflectionPoint is set to Automatic, the inflectionpoint is set 80% from the center of the image to the edge (which often is called the Fourier Cake).

Increasing the SplineOrder will result in blurring in angular direction thereby making the spatial filters less sharp in angular direction.

The center of the Fourier cake is removed by a gaussian (e.g. removing low frequencies), the σ of the gaussian is controlled by DcStandaardDeviation. Note that this parameter indicates the σ in the spatial domain.

The output of CakeWaveletStack (ObjCakeWavelet) can be used as input for the function WaveletTransform2D, OrientationScoreTransform2D or OrientationScoreDerivatives.

Examples

Simple Usage

By using all default settings a cake wavelet stack can be created by only providing angular resolution. The output of the function is the (oriented) cake wavelets object which includes the used setting to create the stack and intermediate results for diagnostics.

cwsObj = CakeWaveletStack[n=12,"DcStandardDeviation"->5]

The (spatial) wavelets can be displayed as:

GraphicsRow[ImageAdjust@Image[Re[#]]&/@cwsObj["Data"],ImageSize->Full]

If we wish to see the Fourier wavelets and their coverage we could use the second output:

Column@{
	GraphicsRow[Join[ImageAdjust@Image[#]&/@cwsObj["FourierCake"],{Image[Total[cwsObj["FourierCake"]]]}],ImageSize->Full]
}

Or if we only would like to extract the options used to construct the wavelets:

cwsObj["ClassProperties"]

Directional Cake Wavelets

Directional cake wavelets are cake wavelets of which the tail is cut of. Therefor it is usefull to also set the periodicity to 2π. It is also good to note that by adding the wavelet at θ = 0 and θ = π, the ‘full’ original wavelet is found.

cwsObj = CakeWaveletStack[n=24,"Directional"->True,"DcStandardDeviation"->5];
GraphicsRow[ImageAdjust@Image[Re[#]]&/@cwsObj["FullData"],ImageSize->Full]

Controlling the Shape of the Cake Wavelets

The shape of the wavelets in the Fourier domain can be controlled by the options: InflictionPoint, SplineOrder, MnOrder, OverlapFactor and DcStandardDeviation.

cwss=CakeWaveletStack[n=12,"SplineOrder"->#[[1]], "MnOrder"->#[[2]], "OverlapFactor"->#[[3]],"DcStandardDeviation"->#[[4]],"InflectionPoint"->#[[5]]]&/@{
	{3,8,1,3,0.7},
	{10,8,1,3,0.7},
	{3,3,1,3,0.7},
	{3,8,4,3,0.7},
	{3,8,1,20,0.7},
	{3,8,1,8,0.2}
};
GraphicsGrid[{
	{"","Default","SplineOrder = 10 \n (default = 3)","MnOrder = 3 \n (default = 8)","OverlapFactor = 4 \n (default = 1)","DcStd = 20 \n (default = 3)","InflectionPoint = 0.2 \n (default = 0.7)"},
	Prepend[Image[Total[#["FourierCake"]]]&/@cwss,"FourierCake"],
	Prepend[Image[#["FourierCake"][[1]]]&/@cwss, "FourierSlice"],
	Prepend[ImageAdjust@Image[Re[#["Data"][[1]]]]&/@cwss,"Spatial Wavelet θ = 0"]
},ImageSize->800]

Options

WaveletSize

The default size is set to (70/inflectionPoint), which keeps the outer diameter of the Fourier cake constant in pixels. However to control the wavelet size the dimensions can be set manually by this option. Note that this option does not infleunce the shape of the wavelet.

cwsObj1 = CakeWaveletStack[12]; cwsObj2 = CakeWaveletStack[12,"WaveletSize"->201];
GraphicsRow@{ImageAdjust[Image[Re[cwsObj1["Data"][[1]]]]],ImageAdjust[Image[Re[cwsObj2["Data"][[1]]]]]}

CoordinateSystem

The default coorindate system used is Matrix. This sets the y-axes horizontal and x-axes vertical. The orientation scores move counter-clockwise. If you prefer to output to have the x-axes horizontal, this option should be set to Image.

cws1 = CakeWaveletStack[12]; cws2 = CakeWaveletStack[12,"CoordinateSystem"->"Image"];
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

Directional

The tails and head of the wavelet can be seperated into two layers by setting this option to True. Note that to get all orientations one should get the full orientation range by using FullData.

cws = CakeWaveletStack[12,"Directional"->True];
ImageAdjust@Image[Re[#]]&/@cws["FullData"]

Design

The default design used is (Subscript[N, ψ])=∫|(Subscript[(OverHat[ψ]), n])(ω)| dθ(n) = 1. Another design that can be used is (Subscript[M, ψ])=∫(((|(Subscript[(OverHat[ψ]), n])(ω) |))^2)dθ(n) = 1. Note that the reconstruction method depends on this setting (See OrientationScoreTransform2D).

cws1 = CakeWaveletStack[12,"Design"->"N"]; cws2 = CakeWaveletStack[12,"Design"->"M"];
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

SplineOrder

The spline order indicates the order of the used b-spline to construct the wavelets. A higher order means more overlap between the wavelets, which means that each orientation layer is repsonsive to a wider range of orientations.

cws1 = CakeWaveletStack[12,"SplineOrder"->8]; cws2 = CakeWaveletStack[12,"SplineOrder"->3];
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

InflectionPoint

The inflection point indicates the cut off for high frequencies (gaussian decay). Note that this should be sufficiently small that the cake has a smooth ending towards the edge of the image. The gaussian decay window is described as follows:
(Subscript[ℳ, N])((ρ^2)(t^(-1)))= (e^(-((ρ^2)/t))) (∑_(k=0)^N)(((((ρ^2)(t^(-1))))^k)/(k!))≤ 1, in which ρ denotes the inflection point and N is the order of the Taylor series (see option MnOrder).

cws1 = CakeWaveletStack[12]; cws2 = CakeWaveletStack[12,"InflectionPoint"->0.1];
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

MnOrder

The order of the Taylor series, to ensure slower (gaussian) decay. The higher the order the quicker the decay is. The Gaussian window is described by: (Subscript[ℳ, N])((ρ^2)(t^(-1)))= (e^(-((ρ^2)/t))) (∑_(k=0)^N)(((((ρ^2)(t^(-1))))^k)/(k!))≤ 1, in which N denotes the order and ρ the inflection point (See option inflectionPoint). This best can be seen in the Fourier domain.

cws1 = CakeWaveletStack[12]; cws2 = CakeWaveletStack[12,"MnOrder"->50];
GraphicsRow@{Image[Total[cws1["FourierCake"]]],Image[Total[cws2["FourierCake"]]]}

DcStandardDeviation

The DC filter is used to remove the low-frequencie component from the Cake-Wavelets. This is required to remove long tails in the spatial domain. It also ensures that the cake pieces are smooth in the Fourier domain... This also best can be viewed in the Fourier domain. The option value is the standard deviation of a gaussian that is to remove the low frequencies in the spatial domain.

cws1 = CakeWaveletStack[12,"DcStandardDeviation"->12]; cws2 = CakeWaveletStack[12];
GraphicsRow@{Image[Total[cws1["FourierCake"]]],Image[Total[cws2["FourierCake"]]]}
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

SpatialWindow

Instead of the DC filter (or in combination with) the long tails in the spatial domain can be removed by a window in the spatial domain. Which is basically a gaussian window over the spatial filter. To Remove the influence of the DC filter the DcStandardDeviation is set very large.

cws1 = CakeWaveletStack[12,"DcStandardDeviation"->100]; cws2 = CakeWaveletStack[12,"DcStandardDeviation"->100,"SpatialWindow"->10];
GraphicsRow@{ImageAdjust[Image[Re[cws1["Data"][[1]]]]],ImageAdjust[Image[Re[cws2["Data"][[1]]]]]}

OverlapFactor

The overlap factor is used to sub-sample the fourier spectrum in angular direction without narrowing the cake pieces. The resulting Orientation Score (OS) has smoother transitions between layers in θ-direction.

cws1 = CakeWaveletStack[12]; cws2 = CakeWaveletStack[12,"OverlapFactor"->2];
ImageAdjust@Image[Re[#]]&/@cws1["Data"]
ImageAdjust@Image[Re[#]]&/@cws2["Data"]

Possible Issues

Not a Smooth Fourier Spectrum

Filters look unstable. This could be due that the Fourier spectrum is not smooth. Let us look at an example:

cws = CakeWaveletStack[12,"InflectionPoint"->1,"WaveletSize"->31,"MnOrder"->3];
GraphicsRow@{ImageAdjust[Image[Re[cws["Data"][[3]]]]],Image[Total[cws["FourierCake"]]]}

GaborWaveletStack

Details

GaborWaveletStack generates a stack of oriented gabor filters that can be used for image processing.

The function returns a structure containing both the cake wavelet stack and some metadata. The metadata both contains the settings used to construct the wavelets and some intermediate results as a set of rules:

gwsObj=GaborWaveletStack[12,1];
Framed@Multicolumn[gwsObj["ClassProperties"]]

The function returns the cake wavelets using the default options. The following options can be set:

opts = Options[GaborWaveletStack]; 
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"The size of the wavelet",
"Epsilon controls how anisotropic the wavelet is",
"Defines the spatial frequency of the complex exponential",
"Whenever the spatial wavelets are single or double headed",
"Determines the output axes"
}}],Frame->All]

The output can be used by the WaveletTransform2D.

Examples

Simple Usage

A oriented Gabor wavelet stack can be creatid by providing the angular Resolution and scale:

gwsObj = GaborWaveletStack[n=12,scale=4]

The wavelets can be shown by:

GraphicsRow[Image[Re[#]]&/@gwsObj["FullData"],ImageSize->Full]

Options

WaveletSize

By default the wavelet size is computed as Floor[8a(Sqrt[ϵ])], in which a is the scale and ϵ is the parameter from the Gaussian. The wavelet size can manually be set by:

gwsObj1 = GaborWaveletStack[12,1]; gwsObj2 = GaborWaveletStack[12,1,"WaveletSize"->101];
{
	Image[Re[gwsObj1["Data"][[1]]]],
	Image[Re[gwsObj2["Data"][[1]]]]
}

Epsilon

The Epsilon parameter determines how anisotropic the wavelets is:

gwsObj1=GaborWaveletStack[12,1,"Epsilon"->1];gwsObj2=GaborWaveletStack[12,1,"Epsilon"->100];
{
	Image[Re[gwsObj1["Data"][[1]]]],
	Image[Re[gwsObj2["Data"][[1]]]]
}

K0

K0 defines the spatial frequency of the complex exponential:

gwsObj1=GaborWaveletStack[12,3,"K0"->{0,100}];gwsObj2=GaborWaveletStack[12,3,"K0"->{0,0}];
GraphicsRow@{
	Image[Re[gwsObj1["Data"][[1]]]],
	Image[Re[gwsObj2["Data"][[1]]]]
}

Directional

The tails and head of the wavelet can be seperated into two layers by setting this option to True. Note that is usefull to set the periodicity to 2π as then all directions are used.

cws = GaborWaveletStack[6,5,"Directional"->True];
GraphicsRow[ImageAdjust@Image[Re[#]]&/@cws["FullData"],ImageSize->Full]

CoordinateSystem

The default coorindate system used is Matrix. This sets the y-axes horizontal and x-axes vertical. The orientation scores move counter-clockwise. If you prefer to output to have the x-axes horizontal, this option should be set to Image.

gwsObj1 = GaborWaveletStack[12,1]; gwsObj2 = GaborWaveletStack[12,1,"CoordinateSystem"->"Image"];
GraphicsRow@{ImageAdjust[Image[Re[gwsObj1["Data"][[1]]]]],ImageAdjust[Image[Re[gwsObj2["Data"][[1]]]]]}

WaveletTransform

Details

WaveletTransform convolves input image with the wavelets/kernels provided.

If only a single kernel is provided the function returns the circular convolved image.

The OrientationScoreTransform uses this function.

Examples

Simple Usage

Any kernel can be provided. For example a simple derivate:

Image[WaveletTransform[,{{{-1,1},{-1,1}}}]]

Stack of Kernels

Instead of providing a single kernel a stack of kernels can be provided:

gwsObj = GaborWaveletStack[8,3];
wt2dObj = WaveletTransform[,gwsObj]
GraphicsRow[ImageAdjust[Image@Re@#]&/@wt2dObj["Data"],ImageSize->Full]

OrientationScoreTransform

Details

Orientationtransform internally uses the functions CakeWaveletStack and WaveletsTransform2D. All options from both these methods can be used for this function also.

The orientation score is constructed according to (Subscript[U, f])(x,θ) :=(Subscript[𝒲, ψ])f = ((OverHat[(OverBar[ψ])])⋆f)(x) = (∫_(ℝ^2)) (OverBar[ψ ((Subscript[R, θ])^(-1))(y-x)]) f(y)dy, where ψ is the anisotropic (cake) kernel oriented along the x-axis which we identify with θ = 0, and where (Subscript[𝒲, ψ]) denotes the transformation between image f and orientation score (Subscript[U, f]).

The following options (including those from CakeWaveletStack) can be provided:

opts = Options[OrientationScoreTransform];
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"The size of the wavelet",
"Determines the output axes",
"Whenever the spatial wavelets are single or double headed",
"(Cake only) Specifies the design constraints for the wavelets",
"(Cake only)Specifies the inflection point in radial direction of the gaussian window",
"(Cake only) Specifies the order of the B-Spline that is used to create the wavelets",
"(Cake only) Specifies the order of the Mn Window",
"(Cake only) The standard deviation of the gaussian that is used to remove the center",
"(Cake only) Whenever the DC-filter is outputted or not",
"(Cake only) If the spatial window is used or not",
"(Cake only) Indicates how many B-Splines overlap, thereby not narrowing the angular resolution",
"(Gabor only) Defines the anisotropy of the wavelet",
"(Gabor only)",
"Type specifies whenever Cake or Oriented Gabor Filters are used",
"(Gabor only) Specifies the number of orientations",
"(Gabor only) Indicates the scale"
}}],Frame->All]

The Orientation Score produced is identical to providing a CakeWaveletStack to the WaveletTransfrom2D with the same image.

Examples

Simple Usage

The input can either be an image (color images are converted to grayscale) or an matrix. The option Orientations specifies the number of used orientations for the Cake Wavelet Stack.

ostObj=OrientationScoreTransform[,"Orientations"->36]

The layers can be viewed as:

ListAnimate[ImageAdjust[Image[Re[#]]]&/@Transpose[ostObj["Data"],{2,3,1}],1]

Using the Options from the CakeWavelet

The options from the function CakeWaveletStack can be used to shape the Orientation Scores.

ostObj = OrientationScoreTransform[,"Orientations"->12,"OverlapFactor"->3,"DcStandardDeviation"->10];
Image3D[Re[ostObj["Data"]]/Max[Re[ostObj["Data"]]],BoxRatios->1];

Application Example: Basic Power Law Stretching

ostObj = OrientationScoreTransform[,"Orientations"->12,"DcStandardDeviation"->50,"InflectionPoint"->(100/300),"WaveletSize"->301,"OverlapFactor"->2];
osn = Total[((Abs[ostObj["FullData"]])^2),3];
osp = Sign[Re[ostObj["FullData"]]]((Abs[Re[ostObj["FullData"]]])^1.5);
osp = osp * osn / Total[((Abs[osp])^2),3];
ostObj2 = SetElement[ostObj, "Data"->osp];
mask=ImageData[Erosion[Image[UnitStep[ostObj["InputData"]-0.1]],20]];
GraphicsRow[
	{
		ImageAdjust@Image[mask*InverseOrientationScoreTransform[ostObj,"Method"->"Summation"]],
		ImageAdjust@Image[mask*InverseOrientationScoreTransform[ostObj2,"Method"->"Summation"]],
		ImageAdjust@Image[mask*Total[Transpose[osp,{2,3,1}]]] (* Leave DC-filtered image out *)
	},ImageSize->Full
]

Options

Orientations

Is the angular resolution of the Cake Wavelets

ostObj = OrientationScoreTransform[,"Orientations"->36];
Dimensions[ostObj["Data"]]

Because of the symmetry to save data only 18 orientations are saved. The full Orientation Score can be obtained by

Dimensions[ostObj["FullData"]]

InverseOrientationScoreTransform

Details

This is the inverse of the OrientationScoreTransform2D.

Three following options can be provided:

opts = Options[InverseOrientationScoreTransform]; 
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"The type of reconstruction that is used"
}}],Frame->All]

Examples

Simple Usage

ostObj = OrientationScoreTransform[,"Orientations"->36];
GraphicsRow[{Image[ostObj["InputData"]],Image3D[Re[ostObj["Data"]]/Max[Re[ostObj["Data"]]]],Image[InverseOrientationScoreTransform[ostObj]]},ImageSize->Large]

Options

Method

With this option you can specify the reconstruction method that is used. Note that this is also dependable on the construction method used for the creation of the Cake Wavelets (Design).

ostObj = OrientationScoreTransform[,"Orientations"->12,"Design"->"M","InflectionPoint"->0.2,"DcStandardDeviation"->1];

GraphicsGrid[
	{
		{"Input Image", "Exact Reconstruction", "Reconstruction by Summation"},
		{
			Image[ostObj["InputData"]],
			Image[InverseOrientationScoreTransform[ostObj,"Method"->"Exact"]],
			Image[InverseOrientationScoreTransform[ostObj,"Method"->"Summation"]]
		}
	}
]

OrientationScoreDerivatives

Details

OrientationScoreDerivatives computes the, cartesian or left-invariant, derivatives from an orientation score.

Internally the function makes use of the GaussianFilter to compute the Gaussian derivatives

Cartesian derivatives are defined as ((∂_x)^k), ((∂_y)^k) and ((∂_θ)^k), with k the order.

Left-Invariant derivatives are defined as ((∂_ξ)^k), ((∂_η)^k) and ((∂_θ)^k), with k the order. The spatial derivatives ξ and η align such that the ξ-direction coincides with the orientation θ.

The order of derivatives is specified by a integer, in which the first digit indicates the first derivative to be computed.

For example if we would to compute first the θ-derivative and than the ξ-derivative the input integer would be 31.

The derivative in orientation direction should always be the first derivative to be computed.

Therefor internally the (∂_θ)(∂_ξ) is rewritten by using the relation (∂_θ)(∂_ξ) = (∂_ξ)(∂_θ)+(∂_η).

The symmetry of odd-spatial-derivatives is -π.

The output of the function is again an Orientation Score

Examples

Simple Usage

ost = OrientationScoreTransform[,"Orientations"->16];

Let’s say that we want to compute (Subscript[A, 311])

σSpatial = 4; σOrientation = 3;
A311 = OrientationScoreDerivatives[ost, {σSpatial, σOrientation}, 113]

The original Orientation Score is then still available through

A113["InputData"]

Computing Multiple Derivatives at Once

ost = OrientationScoreTransform[,"Orientations"->16];

σSpatial = 4; σOrientation = 3;
OrientationScoreDerivatives[ost, {σSpatial, σOrientation}, {1,11,23}]

Computing the Hessian or Gradient

ost = OrientationScoreTransform[,"Orientations"->16];

σSpatial = 4; σOrientation = 3;
OrientationScoreDerivatives[ost, {σSpatial, σOrientation}, "Gradient"]

OrientationScoreDerivatives[ost, {σSpatial, σOrientation}, "Hessian"]

CakeWaveletStack3D

Details

CakeWaveletStack3D creates a set of (complex) orientation sensitive wavelets in 3D that can be used to create an Orientation Score.

The real part of the wavelet is a tube-detector and the imaganairy part is a plate detector.

The function returns a object (ObjCakeWavelet3D) containing the Cake Wavelet Stack, metadata and some intermediate results. The following properties are accessable from the results:

cwsObj3D = CakeWaveletStack3D[12,11];
Framed@Multicolumn[Sort[cwsObj3D["ClassProperties"]]]

The function returns the 3D cake wavelets using the default options. The following options can be set:

opts = Options[CakeWaveletStack3D]; 
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"Determines what order spherical harminics should be taken into account",
"Width of the diffusion kernel",
"(1-Gamma) is the distance between inflection point of the error function with the Nyquist frequency ",
"Scale of the low-pass filter in the spatial domain"
}}],Frame->All]

(Subscript[L, Max]) is computed from Epsilon as: Max{(Subscript[L, Max])|(Sqrt[2(Subscript[L, max])+1])(e^(-((Subscript[L, max])+1) (Subscript[L, max]) * scale))> ϵ}.

The output of CakeWaveletStack (ObjCakeWavelet3D) can be used as input for the function WaveletTransform3D or OrientationScoreTransform3D.

Examples

Simple Usage

By using all the default options, only the number of orientations and the wavelet size need to be specified.

cwsObj3d = CakeWaveletStack3D[12,11]

The (spatial) wavelet can quickly be shown by using the QuickVisualise function (which is also shown in the object vieuw, see above)

QuickVisualise[cwsObj3d]

Visualisation of the Wavelets

Compute Cake Wavelet Stack of 12 orientations and 11x11x11:

cwsObj3d = CakeWaveletStack3D[12,11];

A not very usefull, but colorfull visualisation:

GraphicsRow[ImageAdjust[Image[Re[#]]]&/@cwsObj3d["Data"],ImageSize->Full]

A visualisation that makes a little more sense:

Clear[va,vp];
vp={0,0,5};va=20 Degree;
GraphicsRow[ListContourPlot3D[Re@#, Contours->{0.155Min[Re@#],0.06Max[Re@#]},Boxed->False, Mesh->None, ViewPoint->Dynamic[vp], ViewAngle->Dynamic[va]]&/@cwsObj3d["Data"],ImageSize->Full]

Note that they more look like worstenbrood than cake...

Options

Epsilon (Spherical Harmonics Order)

The parameter ϵ controls the order of spherical harmonics used as follows:

Lmax[so_,epsilon_:10^-3] := Module[{l = 0},While[Sqrt[(2l+1)] * E^(-(l+1)l * so)>epsilon,l++];l];

scale = ((sigmaAngle=0.6)^2) / 2;
Plot[{Lmax[scale,ϵ],Lmax[scale/2,ϵ]},{ϵ,$MachineEpsilon,0.1},PlotRange->{{0,0.1},{0,18}},PlotLegends->"Expressions",AxesLabel->{ϵ,Lmax}]

A very extreme case:

cwsObj3dE = CakeWaveletStack3D[12,11,"Epsilon"->0.9];

In this case the shape is seriously distorted:

GraphicsRow[ListContourPlot3D[Re@#, Contours->{0.155Min[Re@#],0.06Max[Re@#]},Boxed->False, Mesh->None]&/@{cwsObj3d["Data"][[1]],cwsObj3dE["Data"][[1]]},ImageSize->Large]

Note that using higher order spherical harmonics (obviously) lead to longer computation time:

CakeWaveletStack3D[12,11,"Epsilon"->0.9];//AbsoluteTiming
CakeWaveletStack3D[12,11,"Epsilon"->10^-4];//AbsoluteTiming

SigmaAngle (Angular Profile)

The sigma angle constrols the width of the diffusion kernel.

DiffusionKernel[L_,so_,θ_,ϕ_] := Sum[(Sqrt[(2l+1)/(4π)]) (E^(-(l+1)l so)) * SphericalHarmonicY[l,0,θ,ϕ], {l,0,L}];

The angular shape of the kernels is controlled by this:

Manipulate[
scale = ((sigmaAngle)^2) / 2;
L = Lmax[scale,epsilon];
kernel = N[DiffusionKernel[L,scale, θtmp, 0]];
GraphicsRow[
{
	Plot[{kernel},{θtmp,-π,π},PlotRange->All,PlotLabel->"sigmaAngle="<>ToString[sigmaAngle]<>"ϕ=0",AxesLabel->{"θ",None}],
	SphericalPlot3D[{kernel},{θtmp,0,Pi},{ϕ,0,2Pi},PlotRange->All,Axes-> True]},PlotLabel->"Angular function of (Subscript[(OverHat[ψ]), 1]): (Subscript[G, (Subscript[s, o])]^(S^2))(θ,ϕ),     L="<>ToString[L]
],
{{sigmaAngle,0.6},0.1,1},
{{epsilon,10^-4},$MachineEpsilon, 10^-3}
]

To show the difference in the spatial kernel, lets use a sharp angular schape. (This is analog to the B-spline order in the 2D case)

cwsObj3dA = CakeWaveletStack3D[12,11,"SigmaAngle"->π/12];
GraphicsRow[ListContourPlot3D[Re@#, Contours->{0.155Min[Re@#],0.06Max[Re@#]},Boxed->False, Mesh->None]&/@{cwsObj3d["Data"][[1]],cwsObj3dA["Data"][[1]]},ImageSize->Large]

Gamma (Radial Profile)

NormalizedComplementaryErrorFunction[x_,sigma_,offset_]:=((Erfc[(x-offset)/((Sqrt[2])sigma)])/2);
RadialFunctionErfCompiled[sigmaHigh_,offsetHigh_] := Compile[{ρ}, Evaluate[NormalizedComplementaryErrorFunction[ρ,sigmaHigh,offsetHigh]], RuntimeAttributes -> {Listable}];

Is a measure for the distance (1-γ) between the Nyquist frequency and the inflectionpoint of the error function.  To demonstrate how the parameters relate:

Manipulate[
rhoTest = Range[0,size,1];
ListLinePlot[RadialFunctionErfCompiled[σhigh,γ*size][rhoTest], PlotRange->All, PlotLabel->"Radial Function of (Subscript[(OverHat[ψ]), 1])"],
{{γ,0.3},0,1},
{{σhigh,3},1,10},
{{size,11},{11,25,51,101}}]

DcStandardDeviation (Radial Profile)

sigmaRadialHigh = 1;
γ = 0.7;
size = 25;
sigmaLowFrequenciesFourier = 3;
CompiledGaussian[sigma_] := Compile[{ρ},Evaluate[E^(-ρ^2/(2sigma^2)) ],RuntimeAttributes->{Listable}];
ListLinePlot[{RadialFunctionErfCompiled[sigmaRadialHigh,γ*size][rhoTest]-CompiledGaussian[sigmaLowFrequenciesFourier][rhoTest]}, PlotRange->All, PlotLabel->"Radial Function of (Subscript[(OverHat[ψ]), 1])"]

A low frequency filter is used to create a smooth transition in the Fourier domain. The DcStandardDeviation is the low pass filter in the spatial domain

DcStandardDeviationFourier[DcStandardDeviation_] := (size/(DcStandardDeviation π));

Then the radial part looks as follows:

Manipulate[
rhoTest = Range[0,size,1];
ListLinePlot[{RadialFunctionErfCompiled[σhigh,γ*size][rhoTest]-CompiledGaussian[σlow][rhoTest]}, PlotRange->All, PlotLabel->"Radial Function of (Subscript[(OverHat[ψ]), 1])"],
{{γ,0.7},0,1},
{{σhigh,3},1,10},
{{σlow,2},1,10},
{{size,25},{11,25,51,101}}]

WaveletTransform3D

Details

WaveletTransform3D convolves the (3D) dataset with the (3D) wavelets/kernels provided.

The OrientationScoreTransform3D makes use of the function internally.

Examples

Simple Usage

A simple filter would be the averega mean of a volume:

wtf3d = WaveletTransform3D[ImageData[ExampleData[{"TestImage3D","MRknee"}]],{ConstantArray[1,{5,5,5}]/(5*5*5)}];
GraphicsRow@{ExampleData[{"TestImage3D","MRknee"}],wtf3d//Image3D}

GraphicsRow@{ListContourPlot3D[ImageData@ExampleData[{"TestImage3D","MRknee"}], Contours->{Min[ImageData@ExampleData[{"TestImage3D","MRknee"}]],0.06Max[ImageData@ExampleData[{"TestImage3D","MRknee"}]]},Boxed->False, Mesh->None],
ListContourPlot3D[wtf3d, Contours->{Min[wtf3d],0.06Max[wtf3d]},Boxed->False, Mesh->None]}

Stack of Kernels

Also a stack of kernels can be provided, for example a stack of 3D cakewavelets:

cws3dObj = CakeWaveletStack3D[12,11];
wtf3d = WaveletTransform3D[ImageData[ExampleData[{"TestImage3D","MRknee"}]],cws3dObj["Data"]];

You also can provide the 3D cakewavelets object directly to the WaveletTransform3D function:

cws3dObj = CakeWaveletStack3D[12,11];
wtf3d = WaveletTransform3D[ImageData[ExampleData[{"TestImage3D","MRknee"}]],cws3dObj]

orientation = 1;
GraphicsRow[
	{
		ListContourPlot3D[Re@cwsObj3d["Data"][[orientation]], Contours->(0.155 MinMax[Re@cwsObj3d["Data"][[orientation]]]),MaxPlotPoints -> 50,Boxed->False, Mesh->None, ViewPoint->Dynamic[vp], ViewAngle->Dynamic[va]],
		ListContourPlot3D[Re@wtf3d["Data"][[All,All,All,orientation]], Contours->(0.1 MinMax[Re@wtf3d["Data"][[All,All,All,orientation]]]),MaxPlotPoints -> 50,Boxed->False, Mesh->None,ViewPoint->Dynamic[vp], ViewAngle->Dynamic[va]]
	},ImageSize->Large
]

OrientationScoreTransform3D

Details

OrientationScoreTransform3D computes the Orientation Score from a 3D volume as: (Subscript[𝒲, ψ])[f](x,n) = (∫_(ℝ^3))(OverBar[(Subscript[ψ, n])(x'-x)])f(x')dx', in which (Subscript[𝒲, n]) is an oriented wavelet.

The function returns a object (ObjOrientationScores3D) which contains the following properties:

ost3d = ObjOrientationScores3D[];
Framed@Multicolumn[Sort[ost3d["ClassProperties"]]]

The following options (including those from CakeWaveletStack3D)

opts = Options[OrientationScoreTransform3D]; 
TextGrid[Transpose[{opts[[All,1]],opts[[All,2]],{
"The number of Orientations",
"The size of the wavelets nxnxn",
"Controls which order of Spherical harmonics are taken into account",
"The width of the diffusion kernel (angular direction)",
"The distance (1-γ) between the Nyquist frequency and the inflection point of the radial function",
"The σ valie for the Gaussian low-pass filter"
}}],Frame->All]

Examples

Simple Usage

Constructing a 3D orientation score from a dataset using the default settings:

data = ImageData[ExampleData[{"TestImage3D","MRknee"}]];
ost3dObj = OrientationScoreTransform3D[data]

Using the Options from the CakeWaveletStack3D

The options that can be used for the 3D cakewavelets can also be used for the construction of the 3D Orientation Scores.

Options[CakeWaveletStack3D]

E.g. if we would like to have a smaller low frequency filter:

ost3d = OrientationScoreTransform3D[data,"DcStandardDeviation"->1];

Use pre-computed 3D  Cake Wavelets

We also can use a ObjCakeWavelet3D as input for the OrientationScoreTransform3D

cws3dObj = CakeWaveletStack3D[12,11,"DcStandardDeviation"->1]

ost3d = OrientationScoreTransform3D[data,cws3dObj]

Extracting the used 3D Cake Wavelets

The ObjOrientationScoreTransform3D contains the Wavelets used to create itself. They can be extracted as follows:

ost3d = OrientationScoreTransform3D[data,"DcStandardDeviation"->1];
cws3dObj = ost3d["Wavelets"]

Options

The options for the OrientationScoreTransform3D are related to shaping the CakeWavelet3D (See above):

Options[OrientationScoreTransform3D]

InverseOrientationScoreTransform3D

Details

Examples

Simple Usage

Options

Method