/// Copyright (c) 2021 Iiro Iivanainen, Harri Linna, Jere Pakkanen, Riikka Vilavaara
///
/// Permission is hereby granted, free of charge, to any person obtaining
/// a copy of this software and associated documentation files (the
/// "Software"), to deal in the Software without restriction, including
/// without limitation the rights to use, copy, modify, merge, publish,
/// distribute, sublicense, and/or sell copies of the Software, and to
/// permit persons to whom the Software is furnished to do so, subject to
/// the following conditions:
///
/// The above copyright notice and this permission notice shall be included
/// in all copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
/// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
/// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
/// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
/// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
/// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
/// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CoreLibrary;
using System.IO;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace GroundhogApp
{
///
/// View Model class for the MainWindow
///
class MainWindowViewModel : ViewModelBase
{
private Sample sample;
public bool CanReadImage
{
get; set;
}
///
/// MainWindowViewModel constructor
///
public MainWindowViewModel()
{
XAxisColor = Colors.Red;
YAxisColor = Colors.Blue;
ZAxisColor = Colors.Green;
DataOnMapColor = Colors.Red;
DataOnMapHighlightColor = Colors.Orange;
UserName = Utilities.GetEnvironmentUser();
NewSample();
}
///
/// Settings when creating new sample.
///
internal void NewSample()
{
CanReadImage = false;
sample = new();
ShowAxes = false;
ShowDataOnMap = true;
LevelMin = 0.0;
LevelMax = 100.0;
XZDepth = 1;
ZYDepth = 1;
XYDepth = 1;
XYexists = false;
XZexists = false;
ZYexists = false;
XYdatalist = null;
ZYdatalist = null;
XZdatalist = null;
XYsource = null;
ZYsource = null;
XZsource = null;
EnableUI = false;
UpdateMarkCount();
IsSaved = true;
}
private int[] _XYMarkAmounts;
///
/// How many marks are associated with each XY-slice.
///
public int[] XYMarkAmounts
{
get { return _XYMarkAmounts; }
set
{
_XYMarkAmounts = value;
RaisePropertyChanged(nameof(XYMarkAmounts));
}
}
private int[] _ZYMarkAmounts;
///
/// How many marks are associated with each ZY-slice.
///
public int[] ZYMarkAmounts
{
get { return _ZYMarkAmounts; }
set
{
_ZYMarkAmounts = value;
RaisePropertyChanged(nameof(ZYMarkAmounts));
}
}
private int[] _XZMarkAmounts;
///
/// How many marks are associated with each XZ-slice.
///
public int[] XZMarkAmounts
{
get { return _XZMarkAmounts; }
set
{
_XZMarkAmounts = value;
RaisePropertyChanged(nameof(XZMarkAmounts));
}
}
private int _XYDepth;
///
/// Which slice is being shown from the XY-plane.
///
public int XYDepth
{
get { return _XYDepth; }
set
{
_XYDepth = value;
RaisePropertyChanged(nameof(XYDepth)); UpdateStackZ();
}
}
private int _ZYdepth;
///
/// Which slice is being shown from the YZ-plane.
///
public int ZYDepth
{
get { return _ZYdepth; }
set
{
_ZYdepth = value;
RaisePropertyChanged(nameof(ZYDepth));
UpdateStackX();
}
}
private int _XZDepth;
///
/// Which slice is being shown from the XZ-plane.
///
public int XZDepth
{
get { return _XZDepth; }
set
{
_XZDepth = value;
RaisePropertyChanged(nameof(XZDepth));
UpdateStackY();
}
}
private BitmapImage _XYsource;
///
/// The image that is shown from XY plane.
///
public BitmapImage XYsource
{
get { return _XYsource; }
set
{
_XYsource = value;
RaisePropertyChanged(nameof(XYsource));
}
}
private BitmapImage _ZYsource;
///
/// The image that is shown from ZY plane.
///
public BitmapImage ZYsource
{
get { return _ZYsource; }
set
{
_ZYsource = value;
RaisePropertyChanged(nameof(ZYsource));
}
}
private BitmapImage _XZsource;
///
/// The image that is shown from XZ plane.
///
public BitmapImage XZsource
{
get { return _XZsource; }
set
{
_XZsource = value;
RaisePropertyChanged(nameof(XZsource));
}
}
private List _XYdatalist;
///
/// List of attached data on currently shown XY slice.
///
public List XYdatalist
{
get { return _XYdatalist; }
set
{
_XYdatalist = value;
RaisePropertyChanged(nameof(XYdatalist));
}
}
private List _ZYdatalist;
///
/// List of attached data on currently shown ZY slice.
///
public List ZYdatalist
{
get { return _ZYdatalist; }
set
{
_ZYdatalist = value;
RaisePropertyChanged(nameof(ZYdatalist));
}
}
private List _XZdatalist;
///
/// List of attached data on currently shown XZ slice.
///
public List XZdatalist
{
get { return _XZdatalist; }
set
{
_XZdatalist = value;
RaisePropertyChanged(nameof(XZdatalist));
}
}
private double _LevelMin;
///
/// Image level minimum in percentage.
///
public double LevelMin
{
get { return _LevelMin; }
set
{
_LevelMin = value;
RaisePropertyChanged(nameof(LevelMin));
AdjustLevel();
}
}
private double _LevelMax;
///
/// Image level maximum in percentage.
///
public double LevelMax
{
get { return _LevelMax; }
set
{
_LevelMax = value;
RaisePropertyChanged(nameof(LevelMax));
AdjustLevel();
}
}
private bool _XYexists;
///
/// If XY map exists or not. Used for enabling UI elements.
///
public bool XYexists
{
get { return _XYexists; }
set
{
_XYexists = value;
RaisePropertyChanged(nameof(XYexists));
}
}
private bool _ZYexists;
///
/// If ZY map exists or not. Used for enabling UI elements.
///
public bool ZYexists
{
get { return _ZYexists; }
set
{
_ZYexists = value;
RaisePropertyChanged(nameof(ZYexists));
}
}
private bool _XZexists;
///
/// If XY map exists or not. Used for enabling UI elements.
///
public bool XZexists
{
get { return _XZexists; }
set
{
_XZexists = value;
RaisePropertyChanged(nameof(XZexists));
}
}
#region Color settings
private Color _XAxisColor;
///
/// Color of the X axis.
///
public Color XAxisColor
{
get { return _XAxisColor; }
set
{
_XAxisColor = value;
RaisePropertyChanged(nameof(XAxisColor));
}
}
private Color _YAxisColor;
///
/// Color of the Y axis.
///
public Color YAxisColor
{
get { return _YAxisColor; }
set
{
_YAxisColor = value;
RaisePropertyChanged(nameof(YAxisColor));
}
}
private Color _ZAxisColor;
///
/// Color of the Z axis.
///
public Color ZAxisColor
{
get { return _ZAxisColor; }
set
{
_ZAxisColor = value;
RaisePropertyChanged(nameof(ZAxisColor));
}
}
private Color _DataOnMapColor;
///
/// Color of Attached data points shown on map.
///
public Color DataOnMapColor
{
get { return _DataOnMapColor; }
set
{
_DataOnMapColor = value;
RaisePropertyChanged(nameof(DataOnMapColor));
}
}
private Color _DataOnMapHighlightColor;
///
/// Color of attached data point shown on map
/// when mouse hovers over them.
///
public Color DataOnMapHighlightColor
{
get { return _DataOnMapHighlightColor; }
set
{
_DataOnMapHighlightColor = value;
RaisePropertyChanged(nameof(DataOnMapHighlightColor));
}
}
#endregion
#region Viewing settings
private bool _ShowAxes;
///
/// Wether color-coded axes are shown or not.
///
public bool ShowAxes
{
get { return _ShowAxes; }
set
{
_ShowAxes = value;
RaisePropertyChanged(nameof(ShowAxes));
}
}
private bool _ShowDataOnMap;
///
/// Are Attached data points shown on Map or not.
///
public bool ShowDataOnMap
{
get { return _ShowDataOnMap; }
set
{
_ShowDataOnMap = value;
RaisePropertyChanged(nameof(ShowDataOnMap));
}
}
#endregion
///
/// Are changed saved or not.
///
public bool IsSaved
{
get; set;
}
///
/// Bit depth of the Map.
///
public int BitDepth
{
get; set;
}
///
/// Saves already saved sample to the same filepath.
///
internal void SaveSample()
{
if (!(sample.Filepath == null))
{
sample.SetSampleRelations(sample.Filepath);
sample.MetaList.SetData("Last edited", Utilities.GetDateTime());
FileManager.SerializeSync(this.sample, sample.Filepath);
IsSaved = true;
}
}
///
/// Save sample as a JSON file.
///
///
internal void SaveAsSample(string filepath)
{
sample.Filepath = filepath;
sample.SetSampleRelations(filepath);
sample.MetaList.SetData("Created by", UserName);
sample.MetaList.SetData("Time created", Utilities.GetDateTime());
FileManager.SerializeSync(this.sample, sample.Filepath);
IsSaved = true;
}
///
/// Checks if save file path already exists or not.
///
/// True if sample has a filepath saved.
public bool SavePathExists()
{
if (GetSampleFilePath() != null) return true;
return false;
}
///
/// Loads sample from JSON file.
///
/// File path of sample.
internal void LoadSample(string filepath)
{
sample = FileManager.Dezerialize(filepath);
sample.Filepath = filepath;
sample.SetSampleRelations(filepath);
if (sample.GetFilepathZ() != null)
{
CanReadImage = true;
XYDepth = 1;
XYexists = true;
UpdateStackZ();
EnableUI = true;
UpdateMarkCount();
BitDepth = sample.GetBitDepth();
}
else
{
CanReadImage = false;
XYexists = false;
XYDepth = 1;
EnableUI = false;
}
if (sample.GetFilepathX() != null)
{
ZYDepth = 1;
ZYexists = true;
UpdateStackX();
UpdateMarkCount();
}
else
{
ZYexists = false;
ZYDepth = 1;
}
if (sample.GetFilepathY() != null)
{
XZDepth = 1;
XZexists = true;
UpdateStackY();
UpdateMarkCount();
}
else
{
XZexists = false;
XZDepth = 1;
}
IsSaved = true;
}
///
/// Creates X stack from the Z stack of the sample and write it
/// into the harddrive.
///
/// Filepath where new stack is written
/// Task that writes new stack to harddrive
internal async Task CreateXStack(string filepath)
{
try
{
int nameIndex = filepath.LastIndexOf("\\");
string newStackPath = filepath.Substring(0, nameIndex);
string newStackName
= filepath.Substring(nameIndex + 1, filepath.Length - nameIndex - 1);
string zPath = sample.Map.StackZ.Filepath.GetPath();
string zTemplate = sample.Map.StackZ.Template;
int zHeight = sample.Map.GetZHeigth();
int zWidth = sample.Map.GetZWidth();
int zDepth = sample.Map.GetZDepth();
int zByteDepth = sample.Map.ByteDepth;
switch (sample.Map.MapType)
{
case Map.FileType.ImageSequence:
await Task.Run(()
=> MapReader.MakeSeqXStack(zPath,
zTemplate, newStackPath, newStackName));
break;
case Map.FileType.Tiff3D:
break;
case Map.FileType.Raw:
await Task.Run(()
=> MapReader.MakeRawXStack(zPath,
newStackPath, newStackName, zHeight,
zWidth, zDepth, zByteDepth));
break;
default:
break;
}
sample.SetMapTemplateX(newStackName);
string filetype = sample.GetMapFileType();
sample.SetFilepathX(Path.ChangeExtension(filepath, null) + filetype);
ZYexists = true;
UpdateStackX();
}
catch(StackWriteException)
{
sample.Map.StackX = new Slice();
throw;
}
finally
{
IsSaved = false;
}
}
///
/// Creates Y stack from the Z stack of the sample and write it
/// into the harddrive.
///
/// Filepath to where new stack is written
/// Task that writes new stack to harddrive
internal async Task CreateYStack(string filepath)
{
try
{
int nameIndex = filepath.LastIndexOf("\\");
string newStackPath = filepath.Substring(0, nameIndex);
string newStackName
= filepath.Substring(nameIndex + 1, filepath.Length - nameIndex - 1);
string zPath = sample.Map.StackZ.Filepath.GetPath();
string zTemplate = sample.Map.StackZ.Template;
int zHeight = sample.Map.GetZHeigth();
int zWidth = sample.Map.GetZWidth();
int zDepth = sample.Map.GetZDepth();
int zByteDepth = sample.Map.ByteDepth;
switch (sample.Map.MapType)
{
case Map.FileType.ImageSequence:
await Task.Run(()
=> MapReader.MakeSeqYStack(zPath,
zTemplate, newStackPath, newStackName));
break;
case Map.FileType.Tiff3D:
break;
case Map.FileType.Raw:
await Task.Run(()
=> MapReader.MakeRawYStack(zPath,
newStackPath, newStackName, zHeight,
zWidth, zDepth, zByteDepth));
break;
default:
break;
}
sample.SetMapTemplateY(newStackName);
string filetype = sample.GetMapFileType();
sample.SetFilepathY(Path.ChangeExtension(filepath, null) + filetype);
XZexists = true;
UpdateStackY();
}
catch(StackWriteException)
{
sample.Map.StackY = new Slice();
throw;
}
finally
{
IsSaved = false;
}
}
///
/// Check to see if there are generated stacks.
///
/// True if there are generated stacks, false otherwise
internal bool GeneratedStacks()
{
return ZYexists || XZexists;
}
///
/// If Z stack is generated, returns its filename.
///
/// Filename of the z stack or "" if stack not specified
public string GetStackZName()
{
if (sample.GetFilepathZ() != null)
{
if(sample.Map.MapType == Map.FileType.ImageSequence) return
Path.GetFileNameWithoutExtension(sample.GetFilepathZ()).TrimEnd(
new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
return Path.GetFileNameWithoutExtension(sample.GetFilepathZ());
}
return "";
}
///
/// Returns folder where Z stack is saved
///
/// Folder of the Z stack
public string GetStackZPath()
{
if (sample.GetFilepathZ() != null) return Path.GetDirectoryName(sample.GetFilepathZ());
return "";
}
///
/// Returns the info if the map file is sequence
///
/// True if map is sequence, false otherwise
public bool IsSequence()
{
return sample.Map.MapType == Map.FileType.ImageSequence;
}
///
/// Returns the info if the map file is 3Dtiff
///
/// True if map is 3Dtiff, false otherwise
public bool Is3DTiff()
{
return sample.Map.MapType == Map.FileType.Tiff3D;
}
///
/// Returns the info if the map file is raw
///
/// True if map is raw, false otherwise
public bool IsRaw()
{
return sample.Map.MapType == Map.FileType.Raw;
}
///
/// Delete stacks and if sample is saved, save the sample
/// after removing added stacks.
///
internal void DeleteGeneratedStacks()
{
// Shouldn't access Slice filepaths directly
// from here. Map's and Slice's attributes have
// too high publicity.
if (sample.Map.StackX.Filepath != null)
{
switch (sample.Map.MapType)
{
case Map.FileType.ImageSequence:
MapReader.DeleteStack(sample.Map.StackX.Filepath.GetPath(),
sample.Map.StackX.Template, true);
break;
case Map.FileType.Tiff3D:
MapReader.DeleteStack(sample.Map.StackX.Filepath.GetPath(),
"", false);
break;
case Map.FileType.Raw:
MapReader.DeleteStack(sample.Map.StackX.Filepath.GetPath(),
"", false);
break;
default:
break;
}
sample.Map.StackX.Filepath = null;
sample.Map.StackX.Template = null;
}
if (sample.Map.StackY.Filepath != null)
{
switch (sample.Map.MapType)
{
case Map.FileType.ImageSequence:
MapReader.DeleteStack(sample.Map.StackY.Filepath.GetPath(),
sample.Map.StackY.Template, true);
break;
case Map.FileType.Tiff3D:
MapReader.DeleteStack(sample.Map.StackY.Filepath.GetPath(),
"", false);
break;
case Map.FileType.Raw:
MapReader.DeleteStack(sample.Map.StackY.Filepath.GetPath(),
"", false);
break;
default:
break;
}
sample.Map.StackY.Filepath = null;
sample.Map.StackY.Template = null;
}
if (IsSaved) SaveSample();
}
private bool _EnableUI;
///
/// If specific UI elements are enabled or not.
///
public bool EnableUI
{
get { return _EnableUI; }
set { _EnableUI = value; RaisePropertyChanged(nameof(EnableUI)); }
}
///
/// List of attached data points.
///
public List AttData
{
get
{ return sample.AttDataList.AttachmentList; }
}
private string _UserName;
public string UserName
{
get { return _UserName; }
set { _UserName = value; RaisePropertyChanged(nameof(UserName)); }
}
///
/// Setting map as Image Sequence.
///
/// File path
/// Image template
internal void SetImageSeq(string path, string template)
{
sample.SetImageSequence();
sample.SetMapTemplateZ(template);
SetMap(path);
}
///
/// Setting Map as multiframe tiff.
///
/// File path
internal void SetTiff3D(string path)
{
sample.SetTiff3D();
SetMap(path);
}
///
/// Setting Map as Raw file.
///
/// File path.
/// Byte depth.
/// Width of Map.
/// Height of Map.
/// True if little endian, false if big
/// endian.
internal void SetRaw(string path, int byteDepth, int mapWidth, int mapHeight,
bool? littleEndian)
{
sample.SetRaw();
sample.SetByteDepth(byteDepth);
sample.SetMapWidth(mapWidth);
sample.SetMapHeight(mapHeight);
sample.SetLittleEndian((bool)littleEndian);
SetMap(path);
}
///
/// Settings for Map.
///
/// File path.
private void SetMap(string path)
{
sample.SetFilepathZ(path);
sample.SetSlicesZ(path);
EnableUI = true;
CanReadImage = true;
XYDepth = 1;
XYexists = true;
UpdateStackZ();
sample.SetMapWidth((int)XYsource.Width);
sample.SetMapHeight((int)XYsource.Height);
UpdateMarkCount();
BitDepth = sample.GetBitDepth();
IsSaved = false;
}
///
/// Adjust levels of slices by percentage.
///
private void AdjustLevel()
{
if (!CanReadImage) return;
if (XYsource != null)
{
sample.AdjustLevelZ(LevelMin, LevelMax);
ConvertStack(x => XYsource = x, () => sample.GetSliceZ());
}
if (ZYsource != null)
{
sample.AdjustLevelX(LevelMin, LevelMax);
ConvertStack(x => ZYsource = x, () => sample.GetSliceX());
}
if (XZsource != null)
{
sample.AdjustLevelY(LevelMin, LevelMax);
ConvertStack(x => XZsource = x, () => sample.GetSliceY());
}
}
///
/// Adds attached data with specified values and adds default metadata.
///
/// File destination of the attached data.
/// Given name for the attached data.
/// Metadata description.
/// Coordinates given for the attached data.
/// If the data be copied to sample folder.
public void AddAttData(string filePath, string dataName, string dataDescription,
List> coordinates, bool copyToFolder)
{
var copyPath = GetSampleSaveFolder();
if (copyToFolder)
{
var folderName
= Path.GetFileNameWithoutExtension(GetSampleFilePath()) + "_AttachedData";
filePath = Utilities.CopyToFolder(filePath, folderName, copyPath);
}
var ad = new AttachedData(filePath, dataName, coordinates);
string username = UserName;
string dateTime = Utilities.GetDateTime();
ad.SetMetaData("Description", dataDescription);
ad.SetMetaData("Added by", username);
ad.SetMetaData("Time added", dateTime);
sample.AttDataList.AttachmentList.Add(ad);
UpdateMarkCount();
UpdateDataOnMap();
IsSaved = false;
}
///
/// Applies Metadata list of edited Metas-object to the target Metas-object.
///
/// Target metas object.
/// New metalist.
public void ApplyMetadataChanges(Metas target, Metas edited)
{
target.MetaList = edited.MetaList;
IsSaved = false;
}
///
/// Update the datalists that contain attached data
/// on current slices.
///
private void UpdateDataOnMap()
{
XYdatalist = sample.AttachmentsByZCoordinate(XYDepth);
ZYdatalist = sample.AttachmentsByXCoordinate(ZYDepth);
XZdatalist = sample.AttachmentsByYCoordinate(XZDepth);
}
///
/// Update the mark counts for the MarkedSlider.
///
private void UpdateMarkCount()
{
XYMarkAmounts = sample.GetMarkCount(2); // Z
XZMarkAmounts = sample.GetMarkCount(1); // Y
ZYMarkAmounts = sample.GetMarkCount(0); // X
}
#region ListViewFiltering
public string CollectionFilterText { get; set; }
public bool CollectionFilter(object item)
{
if (string.IsNullOrEmpty(CollectionFilterText))
return true;
else
return (item as AttachedData).DataName.Contains(CollectionFilterText,
StringComparison.OrdinalIgnoreCase);
}
#endregion
///
/// Removes selected attached data from the sample.
///
///
public void DeleteAttData(object connector) {
var attData = connector as AttachedData;
sample.AttDataList.RemoveData(attData);
UpdateMarkCount();
UpdateDataOnMap();
IsSaved = false;
}
///
/// Reading the image to be shown as stack Z.
///
private void UpdateStackZ()
{
if (!CanReadImage) return;
if (!XYexists) return;
sample.UpdateStackZ(XYDepth - 1);
sample.AdjustLevelZ(LevelMin, LevelMax);
ConvertStack(x => XYsource = x, () => sample.GetSliceZ());
}
///
/// Reading the image to be shown as stack X.
///
private void UpdateStackX()
{
if (!CanReadImage) return;
if (!ZYexists) return;
sample.UpdateStackX(ZYDepth - 1);
ConvertStack(x => ZYsource = x, () => sample.GetSliceX());
}
///
/// Reading the image to be shown as stack Y.
///
private void UpdateStackY()
{
if (!CanReadImage) return;
if (!XZexists) return;
sample.UpdateStackY(XZDepth - 1);
ConvertStack(x => XZsource = x, () => sample.GetSliceY());
}
///
/// Goes to the first coordinate point of the connector.
/// If the some slices are out of bounds then they are not set.
///
/// Connector given
public void GoToConnector(AttachedData connector)
{
var points = connector.AttachmentPoints;
if (points.Count < 1) return;
var point = points[0];
int x = (int)point.X;
int y = (int)point.Y;
int z = (int)point.Z;
if (XYexists && XYMarkAmounts.Length >= z) XYDepth = z;
if (ZYexists && ZYMarkAmounts.Length >= x) ZYDepth = x;
if (XZexists && XZMarkAmounts.Length >= y) XZDepth = y;
}
///
/// Updating specified stack and displaying it.
///
/// Which source image attribute is
/// changed.
/// Which slice should be retrieved from
/// Sample.
private void ConvertStack( Action setSourceDelegate,
Func getSliceDelegate)
{
if (!CanReadImage) return;
byte[] bytes = getSliceDelegate();
if (bytes != null)
{
var image = new BitmapImage();
using (var mem = new MemoryStream(bytes))
{
mem.Position = 0;
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = null;
image.StreamSource = mem;
image.EndInit();
}
setSourceDelegate(image);
}
else setSourceDelegate(null);
UpdateDataOnMap();
}
///
/// Gets the metas associated with the Sample.
///
/// Metas associated with the Sample
public Metas GetSampleMetaData()
{
return sample.MetaList;
}
///
/// Get sample save folder path
///
/// Path to sample save folder
///
/// Gets the save folder for the sample.
///
/// Save folder for the sample
public string GetSampleSaveFolder()
{
return Path.GetDirectoryName(sample.GetFilepath());
}
///
/// Gets the filepath of the sample.
///
/// filepath where the sample is saved
public string GetSampleFilePath()
{
return sample.GetFilepath();
}
///
/// Searching attachments by its name.
///
/// Name to be searched.
/// AttachedData with specified name.
internal AttachedData GetAttachmentByName(string name)
{
return sample.GetAttachment(name);
}
///
/// Removes X stack related things.
///
internal void CleanZYStack()
{
ZYsource = null;
ZYexists = false;
sample.CleanXStack();
}
///
/// Removes Y stack related things.
///
internal void CleanXZStack()
{
XZsource = null;
XZexists = false;
sample.CleanYStack();
}
}
}