From a6062f325187e3a198bf3e71bf4b814408ff82ec Mon Sep 17 00:00:00 2001 From: RepoErik <84872500+BiologyTools@users.noreply.github.com> Date: Thu, 2 May 2024 07:21:46 +0300 Subject: [PATCH] 2.4.0 OME type slides fix. Support for 16 bit OME slides. --- .vs/ProjectEvaluation/biolib.metadata.v7.bin | Bin 319193 -> 401510 bytes .vs/ProjectEvaluation/biolib.projects.v7.bin | Bin 1607050 -> 1781219 bytes BioLib.csproj | 6 +- Source/Bio.cs | 1310 +++++++++--------- Source/Bio/ISlideSource.cs | 600 ++++++++ Source/Bio/SlideBase.cs | 146 ++ Source/Bio/SlideImage.cs | 311 +++++ Source/Bio/SlideSliceLayer.cs | 60 + Source/Bio/SlideTileLayer.cs | 81 ++ Source/Bio/Utilities.cs | 367 +++++ Source/ImageJ.cs | 4 +- 11 files changed, 2234 insertions(+), 651 deletions(-) create mode 100644 Source/Bio/ISlideSource.cs create mode 100644 Source/Bio/SlideBase.cs create mode 100644 Source/Bio/SlideImage.cs create mode 100644 Source/Bio/SlideSliceLayer.cs create mode 100644 Source/Bio/SlideTileLayer.cs create mode 100644 Source/Bio/Utilities.cs diff --git a/.vs/ProjectEvaluation/biolib.metadata.v7.bin b/.vs/ProjectEvaluation/biolib.metadata.v7.bin index 65a528351a10ba7d250caebe3223f6619755db4a..ef54a02f1a9f889eb8f6cc70a7ac2324cb1d860a 100644 GIT binary patch delta 23364 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 delta 1427 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 delta 57 zcmV~$$q_>U006)t?pxxj&sz~5>q9%b@k}Fj|Ic<_>n;%bh{O`9Os-I>)Ecc$@9SqU Jn#}id9xq2e672v0 diff --git a/BioLib.csproj b/BioLib.csproj index cd414d8..4b528e6 100644 --- a/BioLib.csproj +++ b/BioLib.csproj @@ -11,9 +11,9 @@ banner.jpg README.md https://github.com/BiologyTools/BioLib - 2.3.0 - 2.3.0 - 2.3.0 + 2.4.0 + 2.4.0 + 2.4.0 GPL-3.0-only True https://biologytools.github.io/ diff --git a/Source/Bio.cs b/Source/Bio.cs index bc5d7c4..f8b5289 100644 --- a/Source/Bio.cs +++ b/Source/Bio.cs @@ -1,4 +1,4 @@ -using AForge; +using AForge; using AForge.Imaging; using AForge.Imaging.Filters; using BitMiracle.LibTiff.Classic; @@ -24,7 +24,9 @@ using Gtk; using System.Linq; using NetVips; -using javax.swing.text.html; +using System.Threading.Tasks; +using OpenSlideGTK; +using Bio; namespace BioLib { @@ -32,15 +34,8 @@ namespace BioLib public static class Images { public static List images = new List(); - /// - /// The function `GetImage` returns a `BioImage` object from a list of images based on a given - /// ID or file name. - /// - /// The parameter "ids" is a string that represents the ID or file name of an - /// image. - /// - /// The method is returning a BioImage object. - /// + /// + /// @param ids The id of the image you want to get. public static BioImage GetImage(string ids) { for (int i = 0; i < images.Count; i++) @@ -50,60 +45,46 @@ public static BioImage GetImage(string ids) } return null; } - - /// - /// The function `AddImage` adds a `BioImage` object to a collection of images if it is not - /// already present. - /// - /// The BioImage parameter is an object that represents an image in a - /// biological context. It likely contains properties such as the image file name, ID, and - /// possibly other metadata related to the image. - /// The "newtab" parameter is a boolean value that determines whether the - /// image should be added to a new tab or not. If "newtab" is set to true, the image will be - /// added to a new tab. If "newtab" is set to false, the image will be added - /// - /// If the condition `if (images.Contains(im))` is true, then the method will return and nothing - /// will be returned explicitly. - /// + /// It adds an image to the list of images + /// + /// @param BioImage A class that contains the image data and other information. public static void AddImage(BioImage im, bool newtab) { if (images.Contains(im)) return; im.Filename = GetImageName(im.ID); - im.ID = im.Filename; + int c = GetImageCountByName(im.ID); + if(c > 1) + im.ID = im.Filename + "-" + c; + else + im.ID = im.Filename; images.Add(im); - - //App.Image = im; - //NodeView.viewer.AddTab(im); + } - - /// - /// The function `GetImageCountByName` returns the count of images with a specific name. - /// - /// The parameter "s" is a string that represents the name of an image - /// file. - /// - /// The method is returning the count of images with the given name. - /// + /// It takes a string as an argument, and returns the number of times that string appears in the + /// list of images + /// + /// @param s The name of the image + /// + /// @return The number of images that contain the name of the image. public static int GetImageCountByName(string s) { + string name = Path.GetFileName(s); + string ext = Path.GetExtension(s); + if(name.Split('-').Length > 2) + name = name.Remove(name.LastIndexOf('-'),name.Length - name.LastIndexOf('-')); int i = 0; - string name = Path.GetFileNameWithoutExtension(s); for (int im = 0; im < images.Count; im++) { - if (images[im].ID == s) + if (images[im].ID.Contains(name) && images[im].ID.EndsWith(ext)) i++; } return i; } - - /// - /// The GetImageName function generates a unique image name based on the input string. - /// - /// The parameter "s" is a string that represents the file path of an - /// image. - /// - /// The method returns a string that represents the unique name for an image. - /// + /// It takes a string, and returns a string + /// + /// @param s The path to the image. + /// + /// @return The name of the image. public static string GetImageName(string s) { //Here we create a unique ID for an image. @@ -111,48 +92,29 @@ public static string GetImageName(string s) if (i == 0) return Path.GetFileName(s); string name = Path.GetFileNameWithoutExtension(s); - string ext = Path.GetExtension(s); - int sti = name.LastIndexOf("-"); - if (sti == -1) + if (Path.GetFileNameWithoutExtension(name)!=name) + name = Path.GetFileNameWithoutExtension(name); + string ext = s.Substring(s.IndexOf('.'),s.Length-s.IndexOf('.')); + if(name.Split('-').Length > 2) { - return name + "-" + i + ext; - + string sts = name.Remove(name.LastIndexOf('-'),name.Length-name.LastIndexOf('-')); + return sts + "-" + i + ext; } else - { - string stb = name.Substring(0, sti); - string sta = name.Substring(sti + 1, name.Length - sti - 1); - int ind; - if (int.TryParse(sta, out ind)) - { - return stb + "-" + (ind + 1).ToString() + ext; - } - else - return name + "-" + i + ext; - } + return name + "-" + i + ext; } - - /// - /// The function "RemoveImage" takes a BioImage object as a parameter and removes the image with - /// the corresponding ID. - /// - /// The BioImage is a class that represents an image in a biological - /// context. It likely contains properties such as ID, name, file path, and other relevant - /// information about the image. + /// This function removes an image from the table + /// + /// @param BioImage This is the image that you want to remove. public static void RemoveImage(BioImage im) { RemoveImage(im.ID); } - - /// - /// The RemoveImage function removes an image from a collection and disposes of it. - /// - /// The "id" parameter is a string that represents the unique identifier of the - /// image that needs to be removed. - /// - /// If the `im` object is `null`, then nothing is being returned. The method will simply exit - /// and no further code will be executed. - /// + /// It removes an image from the table + /// + /// @param id The id of the image to remove. + /// + /// @return The image is being returned. public static void RemoveImage(string id) { BioImage im = GetImage(id); @@ -160,17 +122,12 @@ public static void RemoveImage(string id) return; images.Remove(im); im.Dispose(); - im = null; } - - /// - /// The function `UpdateImage` updates an image in a list of images based on its ID. - /// - /// BioImage is a class that represents an image object. It likely has - /// properties such as ID, image data, and other relevant information. - /// - /// The method is returning nothing (void). - /// + /// It updates an image from the table + /// + /// @param id The id of the image to update. + /// @param im The BioImage to update with. + /// @return The image is being returned. public static void UpdateImage(BioImage im) { for (int i = 0; i < images.Count; i++) @@ -183,7 +140,7 @@ public static void UpdateImage(BioImage im) } } } - + /* A struct that is used to store the resolution of an image. */ /* A struct that is used to store the resolution of an image. */ public struct Resolution { @@ -262,8 +219,6 @@ public int RGBChannelsCount return 4; } } - /* The above code is defining a property called "SizeInBytes" in C#. This property returns the - size of an image in bytes based on its pixel format. */ public long SizeInBytes { get @@ -281,11 +236,6 @@ public long SizeInBytes throw new NotSupportedException(format + " is not supported."); } } - /* The above code is defining a constructor for a class called "Resolution". The constructor - takes in several parameters including the width and height of an image, the number of pixels - per unit of physical measurement, the number of bits per pixel, the physical dimensions of - the image, and the stage dimensions of the image. The constructor assigns these values to - the corresponding properties of the class. */ public Resolution(int w, int h, int omePx, int bitsPerPixel, double physX, double physY, double physZ, double stageX, double stageY, double stageZ) { x = w; @@ -298,10 +248,6 @@ public Resolution(int w, int h, int omePx, int bitsPerPixel, double physX, doubl py = physY; pz = physZ; } - /* The above code is defining a constructor for a class called "Resolution". The constructor - takes in several parameters including width (w), height (h), pixel format (f), physical - dimensions (physX, physY, physZ), and stage dimensions (stageX, stageY, stageZ). The - constructor assigns the parameter values to corresponding class variables. */ public Resolution(int w, int h, PixelFormat f, double physX, double physY, double physZ, double stageX, double stageY, double stageZ) { x = w; @@ -335,11 +281,6 @@ public struct RectangleD public double W { get { return w; } set { w = value; } } public double H { get { return h; } set { h = value; } } - /* The above code is defining a constructor for a class called RectangleD in C#. The - constructor takes four parameters: X, Y, W, and H, which represent the x-coordinate, - y-coordinate, width, and height of the rectangle, respectively. Inside the constructor, the - values of these parameters are assigned to the corresponding instance variables x, y, w, and - h. */ public RectangleD(double X, double Y, double W, double H) { x = X; @@ -347,30 +288,16 @@ public RectangleD(double X, double Y, double W, double H) w = W; h = H; } - /// - /// The function converts the coordinates and dimensions of a rectangle from float to integer - /// values. - /// - /// - /// The method is returning a new instance of the Rectangle class, with the X, Y, W, and H - /// values casted to integers. - /// public Rectangle ToRectangleInt() { return new Rectangle((int)X, (int)Y, (int)W, (int)H); } - - /// - /// The function checks if a given rectangle intersects with another rectangle. - /// - /// The RectangleD is a custom data type that represents a rectangle in - /// 2D space. It has four properties: X (the x-coordinate of the top-left corner), Y (the - /// y-coordinate of the top-left corner), W (the width of the rectangle), and H (the height of - /// the - /// - /// The method is returning a boolean value. If the rectangles intersect, it returns true. If - /// they do not intersect, it returns false. - /// + /// If any of the four corners of the rectangle are inside the polygon, then the rectangle + /// intersects with the polygon + /// + /// @param RectangleD The rectangle that is being checked for intersection. + /// + /// @return a boolean value. public bool IntersectsWith(RectangleD rect1) { if (rect1.X + rect1.W < X || X + W < rect1.X || @@ -380,32 +307,21 @@ public bool IntersectsWith(RectangleD rect1) } return true; } - - /// - /// The function checks if a given point intersects with another point. - /// - /// The PointD class represents a point in a two-dimensional space. It - /// typically has two properties: X and Y, which represent the coordinates of the point on the - /// x-axis and y-axis, respectively. - /// - /// The method is returning a boolean value. - /// + /// > Returns true if the point is inside the rectangle + /// + /// @param PointD + /// + /// @return The return value is a boolean value. public bool IntersectsWith(PointD p) { return IntersectsWith(p.X, p.Y); } - - /// - /// The function checks if a given point (x, y) intersects with a rectangle defined by its - /// top-left corner (X, Y) and its width (W) and height (H). - /// - /// The x-coordinate of the point to check for intersection. - /// The y parameter represents the y-coordinate of a point. - /// - /// The method is returning a boolean value. It returns true if the given coordinates (x, y) - /// intersect with the rectangle defined by the X, Y, W, and H properties. Otherwise, it returns - /// false. - /// + /// If the point is within the rectangle, return true. Otherwise, return false + /// + /// @param x The x coordinate of the point to check + /// @param y The y coordinate of the point to test. + /// + /// @return A boolean value. public bool IntersectsWith(double x, double y) { if (X <= x && (X + W) >= x && Y <= y && (Y + H) >= y) @@ -413,29 +329,17 @@ public bool IntersectsWith(double x, double y) else return false; } - - /// - /// The function converts the values of X, Y, W, and H into a RectangleF object and returns it. - /// - /// - /// The method is returning a new instance of the RectangleF class, with the X, Y, W, and H - /// values converted to float. - /// + /// It converts a RectangleD to a RectangleF + /// + /// @return A RectangleF object. public RectangleF ToRectangleF() { return new RectangleF((float)X, (float)Y, (float)W, (float)H); } - - /// - /// The ToString() function returns a string representation of the object's properties, rounded - /// to two decimal places. - /// - /// - /// The method is returning a string representation of the object's properties, specifically the - /// values of X, Y, W, and H. The values are rounded to two decimal places using the - /// MidpointRounding.ToZero rounding method. The values are concatenated with commas and spaces - /// to form the returned string. - /// + /// This function returns a string that contains the values of the X, Y, W, and H properties of + /// the object + /// + /// @return The X, Y, W, and H values of the rectangle. public override string ToString() { double w = Math.Round(W, 2, MidpointRounding.ToZero); @@ -584,21 +488,6 @@ public List PointsD return Points; } } - /// - /// The function "ImagePoints" takes a Resolution object as input and returns an array of PointD - /// objects that have been converted to image space using the provided resolution values. - /// - /// The "Resolution" parameter is an object that contains information - /// about the resolution of an image. It typically includes properties such as the stage size - /// (width and height), physical size (width and height), and possibly other properties related - /// to the image resolution. - /// - /// The method is returning an array of PointD objects. - /// - public PointD[] ImagePoints(Resolution res) - { - return BioImage.ToImageSpace(PointsD, (int)res.StageSizeX, (int)res.StageSizeY, (int)res.PhysicalSizeX, (int)res.PhysicalSizeY); - } private List selectBoxs = new List(); public List selectedPoints = new List(); public RectangleD BoundingBox; @@ -621,15 +510,15 @@ public PointD[] ImagePoints(Resolution res) public int shapeIndex = 0; public bool closed = false; bool selected = false; - public bool Selected - { - get { return selected; } - set - { + public bool Selected + { + get { return selected; } + set + { if (roiMask != null) - roiMask.Selected = value; + roiMask.Selected = value; selected = value; - } + } } public bool subPixel = false; public Mask roiMask { get; set; } @@ -651,24 +540,24 @@ public class Mask : IDisposable Gdk.Pixbuf pixbuf; Gdk.Pixbuf colored; bool selected = false; - internal bool Selected - { - get { return selected; } - set - { + internal bool Selected + { + get { return selected; } + set + { selected = value; if (selected) UpdateColored(Color.Blue); else updateColored = true; - } + } } public bool IsSelected(int x, int y) { int ind = y * width + x; if (ind > mask.Length) throw new ArgumentException("Point " + x + "," + y + " is outside the mask."); - if (mask[ind] > min) + if (mask[ind]>min) { return true; } @@ -798,9 +687,9 @@ public byte[] GetBytes() } public void Dispose() { - if (pixbuf != null) + if(pixbuf!=null) pixbuf.Dispose(); - if (colored != null) + if(colored!=null) colored.Dispose(); mask = null; } @@ -885,7 +774,7 @@ public string Text /// @param scale the scale of the image /// /// @return A rectangle with the following properties: - public RectangleD GetSelectBound(double scaleX, double scaleY) + public RectangleD GetSelectBound(double scaleX,double scaleY) { if (type == Type.Mask) return BoundingBox; @@ -900,7 +789,21 @@ public ROI() strokeColor = Color.Yellow; BoundingBox = new RectangleD(0, 0, 1, 1); } - + /// + /// The function "ImagePoints" takes a Resolution object as input and returns an array of PointD + /// objects that have been converted to image space using the provided resolution values. + /// + /// The "Resolution" parameter is an object that contains information + /// about the resolution of an image. It typically includes properties such as the stage size + /// (width and height), physical size (width and height), and possibly other properties related + /// to the image resolution. + /// + /// The method is returning an array of PointD objects. + /// + public PointD[] ImagePoints(Resolution res) + { + return BioImage.ToImageSpace(PointsD, (int)res.StageSizeX, (int)res.StageSizeY, (int)res.PhysicalSizeX, (int)res.PhysicalSizeY); + } /// It returns an array of RectangleF objects that are used to draw the selection boxes around /// the points of the polygon /// @@ -1013,16 +916,16 @@ public static ROI CreateFreeform(ZCT coord, PointD[] pts) an.closed = true; return an; } - public static ROI CreateMask(ZCT coord, float[] mask, int width, int height, PointD loc, double physicalX, double physicalY) + public static ROI CreateMask(ZCT coord, float[] mask,int width,int height,PointD loc, double physicalX, double physicalY) { ROI an = new ROI(); an.coord = coord; an.type = Type.Mask; - an.roiMask = new Mask(mask, width, height, physicalX, physicalY); + an.roiMask = new Mask(mask, width, height,physicalX,physicalY); an.AddPoint(loc); return an; } - public static ROI CreateMask(ZCT coord, byte[] mask, int width, int height, PointD loc, double physicalX, double physicalY) + public static ROI CreateMask(ZCT coord, byte[] mask,int width,int height,PointD loc,double physicalX,double physicalY) { ROI an = new ROI(); an.coord = coord; @@ -1185,7 +1088,7 @@ public string PointsToString(BioImage b) /// public void UpdateBoundingBox() { - if (type == Type.Mask) + if(type == Type.Mask) { double minx = double.MaxValue; double miny = double.MaxValue; @@ -1239,7 +1142,7 @@ public void UpdateBoundingBox() } public void Dispose() { - if (roiMask != null) + if(roiMask!=null) { roiMask.Dispose(); } @@ -1282,15 +1185,11 @@ public Filt(string s, IFilter f, Type t) public static class Filters { public static int[,] indexs; - - /// - /// The function "GetFilter" returns a filter object from a list of filters based on the given - /// name. - /// - /// The name of the filter that you want to retrieve. - /// - /// The method is returning an object of type Filt. - /// + /// It takes a string as an argument and returns a filter object + /// + /// @param name The name of the filter. + /// + /// @return The value of the key "name" in the dictionary "filters" public static Filt GetFilter(string name) { foreach (Filt item in filters) @@ -1300,37 +1199,26 @@ public static Filt GetFilter(string name) } return null; } - - /// - /// The function "GetFilter" returns a filter based on the given type and index. - /// - /// The type parameter is an integer that represents the type of filter. It - /// is used to determine which array to access in order to retrieve the filter. - /// The index parameter is used to specify the position of the filter within - /// the filters array. - /// - /// The method is returning a filter object. - /// + /// It returns a filter from the filters dictionary, using the indexs array to find the index of + /// the filter in the dictionary + /// + /// @param type The type of filter you want to get. + /// @param index The index of the filter in the list of filters. + /// + /// @return The value of the filter at the index of the type and index. public static Filt GetFilter(int type, int index) { return filters[indexs[type, index]]; } public static List filters = new List(); - - /// - /// The Base function applies a filter to a BioImage object and returns the modified image. - /// - /// The id parameter is a string that represents the unique identifier of the - /// image. It is used to retrieve the image from a collection or database. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// filter should be applied to the original image or to a copy of the image. If "inPlace" is - /// set to true, the filter will be applied to the original image. If "inPlace" is set to - /// false, - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, applies a filter to it, and returns the filtered image + /// + /// @param id The id of the image to apply the filter to. + /// @param name The name of the filter. + /// @param inPlace If true, the image will be modified in place. If false, a new image will be + /// created. + /// + /// @return The image that was filtered. public static BioImage Base(string id, string name, bool inPlace) { BioImage img = Images.GetImage(id); @@ -1349,8 +1237,6 @@ public static BioImage Base(string id, string name, bool inPlace) { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Base(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); } catch (Exception e) { @@ -1359,24 +1245,16 @@ public static BioImage Base(string id, string name, bool inPlace) } return img; } - - /// - /// The Base2 function applies a filter to an image, using another image as an overlay, and - /// returns the resulting image. - /// - /// The parameter "id" is a string that represents the ID of the first - /// image. - /// The parameter "id2" is a string that represents the ID of the second - /// image. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the images. - /// The "inPlace" parameter is a boolean value that determines whether the - /// filter should be applied to the image in-place or create a new image with the filtered - /// result. If "inPlace" is set to true, the filter will be applied directly to the input image - /// and modify it. If "in - /// - /// The method is returning a BioImage object. - /// + /// It takes two images, applies a filter to the first image, and then applies the second image + /// to the first image + /// + /// @param id the image id + /// @param id2 The image to be filtered + /// @param name The name of the filter. + /// @param inPlace If true, the image will be modified in place. If false, a new image will be + /// created. + /// + /// @return The image that was filtered. public static BioImage Base2(string id, string id2, string name, bool inPlace) { BioImage c2 = Images.GetImage(id); @@ -1396,8 +1274,6 @@ public static BioImage Base2(string id, string id2, string name, bool inPlace) { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Base2(" + '"' + id + '"' + "," + - // '"' + id2 + '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); return img; } catch (Exception e) @@ -1407,22 +1283,14 @@ public static BioImage Base2(string id, string id2, string name, bool inPlace) } return img; } - - /// - /// The function takes an image ID, filter name, and a boolean indicating whether to apply the - /// filter in place or create a copy, and applies the filter to the image. - /// - /// The "id" parameter is a string that represents the unique identifier of the - /// image. It is used to retrieve the image from the Images collection. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// filter should be applied in place or create a new copy of the image. If "inPlace" is true, - /// the filter will be applied directly to the image buffers. If "inPlace" is false, a copy of - /// the - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, applies a filter to it, and returns the image + /// + /// @param id The id of the image to apply the filter to. + /// @param name The name of the filter + /// @param inPlace If true, the image will be modified in place. If false, a copy of the image + /// will be created and modified. + /// + /// @return The image that was passed in. public static BioImage InPlace(string id, string name, bool inPlace) { BioImage img = Images.GetImage(id); @@ -1440,8 +1308,6 @@ public static BioImage InPlace(string id, string name, bool inPlace) { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.InPlace(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); return img; } catch (Exception e) @@ -1451,24 +1317,14 @@ public static BioImage InPlace(string id, string name, bool inPlace) } return img; } - - /// - /// The function "InPlace2" applies a filter to an image, either in place or by creating a copy, - /// and returns the resulting image. - /// - /// The parameter "id" is a string that represents the ID of the first - /// image. - /// The parameter "id2" is a string that represents the ID of the second - /// image. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the images. - /// The "inPlace" parameter is a boolean value that determines whether the - /// filter should be applied in place or not. If "inPlace" is true, the filter will be applied - /// directly to the input image ("img") without creating a copy. If "inPlace" is false, a copy - /// of - /// - /// The method is returning a BioImage object. - /// + /// This function takes an image, applies a filter to it, and returns the filtered image + /// + /// @param id the image id + /// @param id2 the image to be filtered + /// @param name The name of the filter + /// @param inPlace true or false + /// + /// @return The image that was passed in. public static BioImage InPlace2(string id, string id2, string name, bool inPlace) { BioImage c2 = Images.GetImage(id); @@ -1488,8 +1344,6 @@ public static BioImage InPlace2(string id, string id2, string name, bool inPlace { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.InPlace2(" + '"' + id + '"' + "," + - // '"' + id2 + '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); return img; } catch (Exception e) @@ -1499,23 +1353,14 @@ public static BioImage InPlace2(string id, string id2, string name, bool inPlace } return img; } - - /// - /// The function takes an image ID, filter name, and a boolean flag indicating whether to apply - /// the filter in place or create a copy, and applies the filter to the image buffers - /// accordingly. - /// - /// The id parameter is a string that represents the unique identifier of the - /// image. It is used to retrieve the image from the Images collection. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// filter should be applied in place or create a new copy of the image. If "inPlace" is true, - /// the filter will be applied directly to the image buffers. If "inPlace" is false, a copy of - /// the - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, applies a filter to it, and returns the filtered image + /// + /// @param id the id of the image to be filtered + /// @param name The name of the filter + /// @param inPlace If true, the image is modified in place. If false, a copy of the image is + /// created and modified. + /// + /// @return The image that was passed in. public static BioImage InPlacePartial(string id, string name, bool inPlace) { BioImage img = Images.GetImage(id); @@ -1533,8 +1378,6 @@ public static BioImage InPlacePartial(string id, string name, bool inPlace) { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.InPlacePartial(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); return img; } catch (Exception e) @@ -1544,26 +1387,16 @@ public static BioImage InPlacePartial(string id, string name, bool inPlace) } return img; } - - /// - /// The function resizes a BioImage object using a specified filter and returns the resized - /// image. - /// - /// The id parameter is a string that represents the unique identifier of the - /// image. It is used to retrieve the image from the Images collection. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// image should be resized in place or if a new copy of the image should be created. If - /// "inPlace" is set to true, the image will be resized directly, modifying the original image. - /// If "inPlace" is - /// The parameter "w" in the Resize method represents the desired width of the - /// resized image. - /// The parameter "h" in the code represents the desired height of the resized - /// image. - /// - /// The method is returning a BioImage object. - /// + /// This function takes an image, resizes it, and returns the resized image + /// + /// @param id the id of the image to be resized + /// @param name The name of the filter to use. + /// @param inPlace If true, the image will be modified in place. If false, a new image will be + /// created. + /// @param w width + /// @param h height + /// + /// @return The image that was resized. public static BioImage Resize(string id, string name, bool inPlace, int w, int h) { BioImage img = Images.GetImage(id); @@ -1583,8 +1416,6 @@ public static BioImage Resize(string id, string name, bool inPlace, int w, int h { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Resize(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + "," + w + "," + h + ");"); } catch (Exception e) { @@ -1593,34 +1424,18 @@ public static BioImage Resize(string id, string name, bool inPlace, int w, int h } return img; } - - /// - /// The function takes an image, rotates it by a specified angle, and returns the rotated image. - /// - /// The id parameter is a string that represents the unique identifier of the - /// image. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter determines whether the rotation should be - /// applied to the original image or a copy of it. If "inPlace" is set to true, the rotation - /// will be applied to the original image. If "inPlace" is set to false, a copy of the image - /// will be - /// The angle parameter is the amount of rotation in degrees that you want - /// to apply to the image. - /// The parameter "a" represents the alpha value of the fill color. Alpha value - /// determines the transparency of the color, with 0 being fully transparent and 255 being fully - /// opaque. - /// The parameter "r" represents the red component of the fill color. It is an - /// integer value ranging from 0 to 255, where 0 represents no red and 255 represents maximum - /// red intensity. - /// The parameter "g" in the Rotate method represents the green component of the - /// fill color used in the rotation operation. It is an integer value ranging from 0 to 255, - /// where 0 represents no green and 255 represents maximum green intensity. - /// The parameter "b" represents the blue component of the fill color used in - /// the rotation operation. - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, rotates it, and returns the rotated image + /// + /// @param id the id of the image to be rotated + /// @param name The name of the filter + /// @param inPlace whether to apply the filter to the original image or to a copy of it + /// @param angle the angle to rotate the image + /// @param a alpha + /// @param r red + /// @param g the image to be rotated + /// @param b blue + /// + /// @return The image that was rotated. public static BioImage Rotate(string id, string name, bool inPlace, float angle, int a, int r, int g, int b) { BioImage img = Images.GetImage(id); @@ -1640,9 +1455,6 @@ public static BioImage Rotate(string id, string name, bool inPlace, float angle, { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Rotate(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + "," + angle.ToString() + "," + - // a + "," + r + "," + g + "," + b + ");"); } catch (Exception e) { @@ -1652,26 +1464,16 @@ public static BioImage Rotate(string id, string name, bool inPlace, float angle, return img; } - - /// - /// The function takes an image ID, a filter name, a boolean indicating whether the - /// transformation should be applied in place, and an angle, and applies the specified - /// transformation filter to the image. - /// - /// The id parameter is a string that represents the unique identifier of the - /// bioimage. - /// The "name" parameter is a string that represents the name of the - /// transformation filter to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// transformation should be applied to the original image or a copy of it. If "inPlace" is set - /// to true, the transformation will be applied to the original image. If it is set to false, a - /// copy of the image - /// The angle parameter is a float value that represents the rotation angle - /// in degrees. It is used to specify the amount of rotation to be applied to the image during - /// the transformation. - /// - /// The method is returning a BioImage object. - /// + /// This function takes an image, applies a transformation filter to it, and returns the + /// transformed image + /// + /// @param id the id of the image to be transformed + /// @param name The name of the filter. + /// @param inPlace If true, the image will be modified in place. If false, a new image will be + /// created. + /// @param angle the angle of rotation + /// + /// @return The image that was transformed. public static BioImage Transformation(string id, string name, bool inPlace, float angle) { BioImage img = Images.GetImage(id); @@ -1689,8 +1491,6 @@ public static BioImage Transformation(string id, string name, bool inPlace, floa { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Transformation(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + "," + angle + ");"); } catch (Exception e) { @@ -1699,23 +1499,14 @@ public static BioImage Transformation(string id, string name, bool inPlace, floa } return img; } - - /// - /// The function `Copy` takes an image ID, a filter name, and a boolean flag indicating whether - /// to perform the operation in place, and returns a copy of the image with the specified filter - /// applied. - /// - /// The id parameter is a string that represents the unique identifier of the - /// bioimage. - /// The "name" parameter is a string that represents the name of the filter - /// to be applied to the image. - /// The "inPlace" parameter is a boolean value that determines whether the - /// image processing operation should be performed on the original image or on a copy of the - /// image. If "inPlace" is set to true, the operation will be performed on the original image. - /// If "inPlace" is set to - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, applies a filter to it, and returns the filtered image + /// + /// @param id the id of the image to be filtered + /// @param name The name of the filter to apply + /// @param inPlace If true, the image will be modified in place. If false, a copy of the image + /// will be created and the copy will be modified. + /// + /// @return The image that was copied. public static BioImage Copy(string id, string name, bool inPlace) { BioImage img = Images.GetImage(id); @@ -1733,8 +1524,6 @@ public static BioImage Copy(string id, string name, bool inPlace) { Images.AddImage(img, true); } - //Recorder.AddLine("Filters.Copy(" + '"' + id + - // '"' + "," + '"' + name + '"' + "," + inPlace.ToString().ToLower() + ");"); } catch (Exception e) { @@ -1743,23 +1532,15 @@ public static BioImage Copy(string id, string name, bool inPlace) } return img; } - - /// - /// The Crop function takes an image ID, coordinates, and dimensions, crops the image based on - /// those parameters, applies thresholding and stacking filters, and returns the cropped image. - /// - /// The id parameter is a string that represents the identifier of the - /// bioimage. - /// The x-coordinate of the top-left corner of the crop area. - /// The parameter "y" in the Crop method represents the y-coordinate of the - /// top-left corner of the rectangle to be cropped from the image. - /// The parameter "w" in the Crop method represents the width of the rectangle - /// to be cropped from the image. - /// The parameter "h" in the Crop method represents the height of the rectangle - /// to be cropped from the image. - /// - /// The method is returning a BioImage object. - /// + /// It takes an image, crops it, and returns the cropped image + /// + /// @param id the id of the image to crop + /// @param x x coordinate of the top left corner of the rectangle + /// @param y y-coordinate of the top-left corner of the rectangle + /// @param w width + /// @param h height + /// + /// @return The cropped image. public static BioImage Crop(string id, double x, double y, double w, double h) { BioImage c = Images.GetImage(id); @@ -1775,31 +1556,20 @@ public static BioImage Crop(string id, double x, double y, double w, double h) img.StackThreshold(true); else img.StackThreshold(false); - //Recorder.AddLine("Filters.Crop(" + '"' + id + '"' + "," + x + "," + y + "," + w + "," + h + ");"); - //App.tabsView.AddTab(img); return img; } - - /// - /// The Crop function takes an image ID and a rectangle, and returns a cropped version of the - /// image. - /// - /// The "id" parameter is a string that represents the identifier of the bio - /// image that you want to crop. - /// The RectangleD is a class that represents a rectangle with double - /// precision coordinates. It has four properties: - /// - /// The method is returning a BioImage object. - /// + /// This function takes a string and a rectangle and returns a BioImage + /// + /// @param id the id of the image to crop + /// @param RectangleD + /// + /// @return A BioImage object. public static BioImage Crop(string id, RectangleD r) { return Crop(id, r.X, r.Y, r.W, r.H); } - - /// - /// The Init() function initializes a list of filters with their corresponding names and types. - /// + /// It creates a dictionary of filters and their names public static void Init() { //Base Filters @@ -2154,13 +1924,9 @@ public int Series set { series = value; } } - /// - /// The Copy function creates a new ImageInfo object and copies the values of the properties from - /// the current object to the new object. - /// - /// - /// The method is returning an instance of the ImageInfo class. - /// + /// Copy() is a function that copies the values of the ImageInfo class to a new ImageInfo class + /// + /// @return A copy of the ImageInfo object. public ImageInfo Copy() { ImageInfo inf = new ImageInfo(); @@ -2278,14 +2044,23 @@ public enum ImageType public List Resolutions = new List(); public List Buffers = new List(); public List vipPages = new List(); - public int Resolution + int level = 0; + public int Level { - get { return resolution; } - set + get { - resolution = value; - if (Type == ImageType.well) + if (openslideBase != null) + return OpenSlideGTK.TileUtil.GetLevel(openslideBase.Schema.Resolutions, Resolution); + else + if (slideBase != null) + return LevelFromResolution(Resolution); + return level; + } + set + { + if(Type == ImageType.well) { + level = value; reader.setId(file); reader.setSeries(value); // read the image data bytes @@ -2349,8 +2124,18 @@ public Statistics Statistics } private int sizeZ, sizeC, sizeT; private Statistics statistics; - private int resolution = 0; - + private double resolution = 1; + public double Resolution + { + get { return resolution; } + set + { + if (value < 0) + return; + resolution = value; + } + } + ImageInfo imageInfo = new ImageInfo(); /// It copies the BioImage b and returns a new BioImage object. /// @@ -2457,6 +2242,53 @@ public static BioImage CopyInfo(BioImage b, bool copyAnnotations, bool copyChann bi.statistics = b.statistics; return bi; } + /// + /// Get the downsampling factor of a given level. + /// + /// The desired level. + /// + /// The downsampling factor for this level. + /// + /// + public double GetLevelDownsample(int level) + { + int originalWidth = Resolutions[0].SizeX; // Width of the original level + int nextLevelWidth = Resolutions[level].SizeX; // Width of the next level (downsampled) + return (double)originalWidth / (double)nextLevelWidth; + } + public double[] GetLevelDownsamples() + { + double[] ds = new double[Resolutions.Count]; + for (int i = 0; i < Resolutions.Count; i++) + { + ds[i] = Resolutions[0].PhysicalSizeX * GetLevelDownsample(i); + } + return ds; + } + /// + /// Returns the level of a given resolution. + /// + /// + /// + public int LevelFromResolution(double Resolution) + { + double[] ds = GetLevelDownsamples(); + for (int i = 0; i < ds.Length; i++) + { + if (ds[i] > Resolution) + return i - 1; + } + return Resolutions.Count-1; + } + /// + /// Get Unit Per Pixel for pyramidal images. + /// + /// + /// + public double GetUnitPerPixel(int level) + { + return Resolutions[0].PhysicalSizeX * GetLevelDownsample(level); + } public string ID { get { return id; } @@ -2471,35 +2303,45 @@ public int ImageCount } public double PhysicalSizeX { - get { return Resolutions[Resolution].PhysicalSizeX; } + get { return Resolutions[Level].PhysicalSizeX; } } public double PhysicalSizeY { - get { return Resolutions[Resolution].PhysicalSizeY; } + get { return Resolutions[Level].PhysicalSizeY; } } public double PhysicalSizeZ { - get { return Resolutions[Resolution].PhysicalSizeZ; } + get { return Resolutions[Level].PhysicalSizeZ; } } public double StageSizeX { get { - return Resolutions[Resolution].StageSizeX; + return Resolutions[Level].StageSizeX; } set { imageInfo.StageSizeX = value; } } public double StageSizeY { - get { return Resolutions[Resolution].StageSizeY; } + get { return Resolutions[Level].StageSizeY; } set { imageInfo.StageSizeY = value; } } public double StageSizeZ { - get { return Resolutions[Resolution].StageSizeZ; } + get { return Resolutions[Level].StageSizeZ; } set { imageInfo.StageSizeZ = value; } } - + Size s = new Size(1920, 1080); + public Size PyramidalSize { get { return s; } set { s = value; } } + PointD pyramidalOrigin = new PointD(0, 0); + public PointD PyramidalOrigin + { + get { return pyramidalOrigin; } + set + { + pyramidalOrigin = value; + } + } public int series { get @@ -2513,6 +2355,10 @@ public int series } static bool initialized = false; + OpenSlideBase openslideBase; + public OpenSlideBase OpenSlideBase { get { return openslideBase; } } + SlideBase slideBase; + public SlideBase SlideBase { get { return slideBase; } } public Channel RChannel { get @@ -2722,7 +2568,7 @@ public int SizeT get { return sizeT; } } public static bool Planes = false; - public object Tag { get; set; } + public object Tag { get;set; } public IntRange RRange { get @@ -2793,7 +2639,6 @@ public bool isPyramidal } } public string file; - public static double progressValue = 0; public static string status; public static string progFile; public static bool Initialized @@ -2945,6 +2790,7 @@ public void To16Bit() } Channels[c].BitsPerPixel = 16; } + bitsPerPixel = 16; } else if (Buffers[0].PixelFormat == PixelFormat.Format24bppRgb) { @@ -3131,7 +2977,7 @@ public void To48Bit() bs[0] = new Bitmap(ID, SizeX, SizeY, Buffers[i].PixelFormat, Buffers[i].Bytes, new ZCT(Buffers[i].Coordinate.Z, 0, Buffers[i].Coordinate.T), i, Buffers[i].Plane); bs[1] = new Bitmap(ID, SizeX, SizeY, Buffers[i + 1].PixelFormat, Buffers[i + 1].Bytes, new ZCT(Buffers[i + 1].Coordinate.Z, 0, Buffers[i + 1].Coordinate.T), i + 1, Buffers[i + 1].Plane); if (Channels.Count > 2) - bs[2] = new Bitmap(ID, SizeX, SizeY, Buffers[i + 2].PixelFormat, Buffers[i + 2].Bytes, new ZCT(Buffers[i].Coordinate.Z, 0, Buffers[i + 2].Coordinate.T), i + 2, Buffers[i].Plane); + bs[2] = new Bitmap(ID, SizeX, SizeY, Buffers[i+2].PixelFormat, Buffers[i+2].Bytes, new ZCT(Buffers[i].Coordinate.Z, 0, Buffers[i+2].Coordinate.T), i+2, Buffers[i].Plane); Bitmap bbs = Bitmap.RGB16To48(bs); for (int b = 0; b < 3; b++) { @@ -3204,8 +3050,8 @@ public void To48Bit() AutoThreshold(this, false); StackThreshold(true); } - public int? MacroResolution { get; set; } - public int? LabelResolution { get; set; } + public int? MacroResolution { get;set; } + public int? LabelResolution { get;set; } void SetLabelMacroResolutions() { int i = 0; @@ -3485,34 +3331,6 @@ public PointD ToImageSpace(PointD p) pp.Y = (float)((p.Y - StageSizeY) / PhysicalSizeY); return pp; } - /// - /// The function takes a list of points in stage space and converts them to image space using - /// the provided stage and physical size parameters. - /// - /// A list of PointD objects representing points in stage space. - /// The width of the stage or canvas in pixels. - /// The stageSizeY parameter represents the size of the stage or canvas - /// in the Y-axis direction. It is used to calculate the Y-coordinate of each point in the p - /// list in image space. - /// The physical size of the X-axis in the image space. - /// The physical size of the Y-axis in the coordinate system of the - /// stage or image. - /// - /// The method is returning an array of PointD objects. - /// - public static PointD[] ToImageSpace(List p, int stageSizeX, int stageSizeY, int physicalSizeX, int physicalSizeY) - { - PointD[] ps = new PointD[p.Count]; - for (int i = 0; i < p.Count; i++) - { - PointD pp = new PointD(); - pp.X = ((p[i].X - stageSizeX) / physicalSizeX); - pp.Y = ((p[i].Y - stageSizeY) / physicalSizeY); - ps[i] = pp; - } - return ps; - } - /// Convert a list of points from stage space to image space /// /// @param p List of points in stage space @@ -3573,8 +3391,8 @@ public PointD ToStageSpace(PointD p) PointD pp = new PointD(); if (isPyramidal) { - pp.X = ((p.X * Resolutions[resolution].PhysicalSizeX) + Volume.Location.X); - pp.Y = ((p.Y * Resolutions[resolution].PhysicalSizeY) + Volume.Location.Y); + pp.X = ((p.X * Resolutions[Level].PhysicalSizeX) + Volume.Location.X); + pp.Y = ((p.Y * Resolutions[Level].PhysicalSizeY) + Volume.Location.Y); return pp; } else @@ -4043,7 +3861,6 @@ public BioImage[] SplitChannels() bms[c] = b; } } - Statistics.ClearCalcBuffer(); return bms; } @@ -4103,26 +3920,6 @@ public Bitmap GetBitmap(int z, int c, int t) /// /// The stridey is the height of the image in bytes. /// - /// The stridex and stridey are the same for a square image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The stridex and stridey are different for a rectangular image. - /// - /// The - /// /// @param ix x coordinate of the pixel /// @param iy The y coordinate of the pixel /// @@ -4687,12 +4484,109 @@ private static void InitWriter() initialized = true; } + private static List GetResolutions() + { + List rss = new List(); + int seriesCount = reader.getSeriesCount(); + for (int s = 0; s < seriesCount; s++) + { + reader.setSeries(s); + IMetadata meta = (IMetadata)reader.getMetadataStore(); + for (int r = 0; r < reader.getResolutionCount(); r++) + { + Resolution res = new Resolution(); + try + { + int rgbc = reader.getRGBChannelCount(); + int bps = reader.getBitsPerPixel(); + PixelFormat px; + try + { + px = GetPixelFormat(rgbc, meta.getPixelsType(r)); + } + catch (Exception) + { + px = GetPixelFormat(rgbc, bps); + } + res.PixelFormat = px; + res.SizeX = reader.getSizeX(); + res.SizeY = reader.getSizeY(); + if (meta.getPixelsPhysicalSizeX(r) != null) + { + res.PhysicalSizeX = meta.getPixelsPhysicalSizeX(r).value().doubleValue(); + } + else + res.PhysicalSizeX = (96 / 2.54) / 1000; + if (meta.getPixelsPhysicalSizeY(r) != null) + { + res.PhysicalSizeY = meta.getPixelsPhysicalSizeY(r).value().doubleValue(); + } + else + res.PhysicalSizeY = (96 / 2.54) / 1000; + + if (meta.getStageLabelX(r) != null) + res.StageSizeX = meta.getStageLabelX(r).value().doubleValue(); + if (meta.getStageLabelY(r) != null) + res.StageSizeY = meta.getStageLabelY(r).value().doubleValue(); + if (meta.getStageLabelZ(r) != null) + res.StageSizeZ = meta.getStageLabelZ(r).value().doubleValue(); + else + res.StageSizeZ = 1; + if (meta.getPixelsPhysicalSizeZ(r) != null) + { + res.PhysicalSizeZ = meta.getPixelsPhysicalSizeZ(r).value().doubleValue(); + } + else + { + res.PhysicalSizeZ = 1; + } + } + catch (Exception e) + { + Console.WriteLine("No Stage Coordinates. PhysicalSize:(" + res.PhysicalSizeX + "," + res.PhysicalSizeY + "," + res.PhysicalSizeZ + ")"); + } + rss.Add(res); + } + } + return rss; + } + private static int GetPyramidCount() + { + List rss = GetResolutions(); + //We need to determine if this image is pyramidal or not. + //We do this by seeing if the resolutions are downsampled or not. + //We also need to determine number of pyramids in this image and which belong to the series we are opening. + List> prs = new List>(); + int? sr = null; + for (int r = 0; r < rss.Count - 1; r++) + { + if (rss[r].SizeX > rss[r + 1].SizeX && rss[r].PixelFormat == rss[r + 1].PixelFormat) + { + if (sr == null) + { + sr = r; + prs.Add(new Tuple(r, 0)); + } + } + else + { + if (rss[prs[prs.Count - 1].Item1].PixelFormat == rss[r].PixelFormat) + prs[prs.Count - 1] = new Tuple(prs[prs.Count - 1].Item1, r); + sr = null; + } + } + return prs.Count; + } public static int GetSeriesCount(string file) { reader.setId(file); int i = reader.getSeriesCount(); + int prs = GetPyramidCount(); reader.close(); - return i; + if (prs > 0) + return prs; + else + return i; } /// This function takes a string array of file names and a string ID and saves the files to the /// database @@ -4798,14 +4692,12 @@ public static void SaveSeries(string[] IDs, string file) offset += stride; } image.WriteDirectory(); - progressValue = (float)im / (float)b.ImageCount; im++; } } } } image.Dispose(); - } /// It opens a tiff file, reads the number of pages, reads the number of channels, and then /// reads each page into a BioImage object. @@ -4996,14 +4888,13 @@ public static BioImage OpenFile(string file, int series, bool tab, bool addToIma bool tiled = IsTiffTiled(file); Console.WriteLine("IsTiled=" + tiled.ToString()); tile = tiled; - + Stopwatch st = new Stopwatch(); st.Start(); status = "Opening Image"; progFile = file; - progressValue = 0; BioImage b = new BioImage(file); - if (tiled && file.EndsWith(".tif") && !file.EndsWith(".ome.tif")) + if(tiled && file.EndsWith(".tif") && !file.EndsWith(".ome.tif")) { //To open this we need libvips vips = VipsSupport(b.file); @@ -5222,7 +5113,6 @@ public static BioImage OpenFile(string file, int series, bool tab, bool addToIma Bitmap inf = new Bitmap(file, SizeX, SizeY, b.Resolutions[series].PixelFormat, bytes, new ZCT(0, 0, 0), p, null, b.littleEndian, inter); b.Buffers.Add(inf); Statistics.CalcStatistics(inf); - progressValue = (float)p / (float)(series + 1) * pages; } } image.Close(); @@ -5233,7 +5123,7 @@ public static BioImage OpenFile(string file, int series, bool tab, bool addToIma Gdk.Pixbuf pf = new Gdk.Pixbuf(file); b.littleEndian = BitConverter.IsLittleEndian; PixelFormat px = GetPixelFormat(pf.NChannels, pf.BitsPerSample); - b.Resolutions.Add(new Resolution(pf.Width, pf.Height, px, 96 * (1 / 2.54) / 1000, 96 * (1 / 2.54) / 1000, 96 * (1 / 2.54) / 1000, 0, 0, 0)); + b.Resolutions.Add(new Resolution(pf.Width, pf.Height,px,96 * (1/2.54) / 1000, 96 * (1 / 2.54) / 1000, 96 * (1 / 2.54) / 1000,0,0,0)); b.bitsPerPixel = pf.BitsPerSample; b.Buffers.Add(new Bitmap(pf.Width, pf.Height, pf.Width * pf.NChannels, px, pf.Pixels)); b.Buffers.Last().ID = Bitmap.CreateID(file, 0); @@ -5389,12 +5279,15 @@ public static void SaveOMESeries(BioImage[] files, string f, bool planes) if (File.Exists(f)) File.Delete(f); loci.formats.meta.IMetadata omexml = service.createOMEXMLMetadata(); - progressValue = 0; status = "Saving OME Image Metadata."; for (int fi = 0; fi < files.Length; fi++) { int serie = fi; BioImage b = files[fi]; + if (b.isPyramidal) + { + b = OpenOME(b.file, b.Level, false, false, true,0, 0,1080, 1920); + } // create OME-XML metadata store omexml.setImageID("Image:" + serie, serie); @@ -5732,7 +5625,6 @@ public static void SaveOMESeries(BioImage[] files, string f, bool planes) writer.setSeries(i); for (int bu = 0; bu < b.Buffers.Count; bu++) { - progressValue = (float)bu / (float)b.Buffers.Count; byte[] bts = b.Buffers[bu].GetSaveBytes(BitConverter.IsLittleEndian); writer.saveBytes(bu, bts); } @@ -5801,7 +5693,7 @@ public static void SaveOMEPyramidal(BioImage[] bms, string file, Enums.ForeignTi Dictionary max = new Dictionary(); for (int i = 0; i < bms.Length; i++) { - Resolution res = bms[i].Resolutions[bms[i].Resolution]; + Resolution res = bms[i].Resolutions[bms[i].Level]; if (bis.ContainsKey(res.PhysicalSizeX)) { bis[res.PhysicalSizeX].Add(bms[i]); @@ -5864,7 +5756,7 @@ public static void SaveOMEPyramidal(BioImage[] bms, string file, Enums.ForeignTi met += "" + " 8) met += "\" Type= \"uint16\">"; @@ -6029,7 +5921,7 @@ public static void OpenVips(BioImage b, int pagecount) { Console.WriteLine(e.Message); } - + } /// The function ExtractRegionFromTiledTiff takes a BioImage object, coordinates, width, height, /// and resolution as input, and returns a Bitmap object representing the extracted region from @@ -6110,7 +6002,7 @@ public static Bitmap ExtractRegionFromTiledTiff(BioImage b, int x, int y, int wi /// @param tileSizeY The tileSizeY parameter is the height of each tile in pixels when tiling /// the images. /// The function "OpenOME" opens a bioimage file, with options to specify the series, whether to - public static BioImage OpenOME(string file, int serie, bool tab, bool addToImages, bool tile, int tilex, int tiley, int tileSizeX, int tileSizeY) + public static BioImage OpenOME(string file, int serie, bool tab, bool addToImages, bool tile, int tilex, int tiley, int tileSizeX, int tileSizeY, bool useOpenSlide = true) { if (file == null || file == "") throw new InvalidDataException("File is empty or null"); @@ -6120,11 +6012,11 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage Thread.Sleep(10); } while (!initialized); Console.WriteLine("OpenOME " + file); + reader = new ImageReader(); if (tileSizeX == 0) tileSizeX = 1920; if (tileSizeY == 0) tileSizeY = 1080; - progressValue = 0; progFile = file; BioImage b = new BioImage(file); b.Type = ImageType.stack; @@ -6139,9 +6031,17 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage { reader.close(); reader.setMetadataStore(b.meta); - reader.setId(f); + try + { + reader.setId(f); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return null; + } } - + //status = "Reading OME Metadata."; reader.setSeries(serie); int RGBChannelCount = reader.getRGBChannelCount(); @@ -6257,11 +6157,11 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage b.Coords = new int[b.SizeZ, b.SizeC, b.SizeT]; int resc = reader.getResolutionCount(); - + try { int wells = b.meta.getWellCount(0); - if (wells > 0) + if(wells>0) { b.Type = ImageType.well; b.Plate = new WellPlate(b); @@ -6334,7 +6234,9 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage } reader.setSeries(serie); - + int pyramidCount = 0; + int pyramidResolutions = 0; + List> prs = new List>(); //We need to determine if this image is pyramidal or not. //We do this by seeing if the resolutions are downsampled or not. if (rss.Count > 1 && b.Type != ImageType.well) @@ -6344,36 +6246,37 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage b.Type = ImageType.pyramidal; tile = true; //We need to determine number of pyramids in this image and which belong to the series we are opening. - List> ims = new List>(); int? sr = null; for (int r = 0; r < rss.Count - 1; r++) { - if (rss[r].SizeX > rss[r + 1].SizeX) + if (rss[r].SizeX > rss[r + 1].SizeX && rss[r].PixelFormat == rss[r + 1].PixelFormat) { if (sr == null) { sr = r; - ims.Add(new Tuple(r, 0)); + prs.Add(new Tuple(r, 0)); } } else { - ims[ims.Count - 1] = new Tuple(ims[ims.Count - 1].Item1, r); + if(rss[prs[prs.Count - 1].Item1].PixelFormat == rss[r].PixelFormat) + prs[prs.Count - 1] = new Tuple(prs[prs.Count - 1].Item1, r); sr = null; } } - if (ims[serie].Item2 == 0) + pyramidCount = prs.Count; + for (int p = 0; p < prs.Count; p++) { - ims[serie] = new Tuple(ims[serie].Item1, rss.Count); + pyramidResolutions += (prs[p].Item2 - prs[p].Item1)+1; } - for (int r = ims[serie].Item1; r < ims[serie].Item2; r++) + + if (prs[serie].Item2 == 0) { - b.Resolutions.Add(rss[r]); + prs[serie] = new Tuple(prs[serie].Item1, rss.Count); } - if (b.Resolutions.Last().PixelFormat == b.Resolutions.First().PixelFormat && rss.Last().PixelFormat != b.Resolutions.Last().PixelFormat) + for (int r = prs[serie].Item1; r < prs[serie].Item2; r++) { - b.Resolutions.Add(rss.Last()); - b.Resolutions.Add(rss[rss.Count - 2]); + b.Resolutions.Add(rss[r]); } } else @@ -6383,8 +6286,13 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage } else b.Resolutions.AddRange(rss); - - + + //If we have 2 resolutions that we're not added they are the label & macro resolutions so we add them to the image. + if(rss.Count - pyramidResolutions == 2) + { + b.Resolutions.Add(rss[rss.Count - 2]); + b.Resolutions.Add(rss[rss.Count - 1]); + } b.Volume = new VolumeD(new Point3D(b.StageSizeX, b.StageSizeY, b.StageSizeZ), new Point3D(b.PhysicalSizeX * SizeX, b.PhysicalSizeY * SizeY, b.PhysicalSizeZ * SizeZ)); int rc = b.meta.getROICount(); @@ -6683,7 +6591,7 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage { byte[] bts = b.meta.getMaskBinData(im, sc); bool end = b.meta.getMaskBinDataBigEndian(im, sc).booleanValue(); - an = ROI.CreateMask(co, bts, b.Resolutions[0].SizeX, b.Resolutions[0].SizeY, new PointD(b.StageSizeX, b.StageSizeY), b.PhysicalSizeX, b.PhysicalSizeY); + an = ROI.CreateMask(co, bts, b.Resolutions[0].SizeX, b.Resolutions[0].SizeY,new PointD(b.StageSizeX,b.StageSizeY),b.PhysicalSizeX,b.PhysicalSizeY); an.Text = b.meta.getMaskText(im, sc); an.id = b.meta.getMaskID(im, sc); ome.xml.model.primitives.NonNegativeInteger nz = b.meta.getMaskTheZ(im, sc); @@ -6721,23 +6629,26 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage serFiles.AddRange(reader.getSeriesUsedFiles()); b.Buffers = new List(); - if (!file.EndsWith("ome.tif")) - try + if(b.Type == ImageType.pyramidal) + try + { + string st = OpenSlideImage.DetectVendor(file); + if (st != null && !file.EndsWith("ome.tif") && useOpenSlide) { - string st = OpenSlideGTK.OpenSlideImage.DetectVendor(file); - if (st != null) - { - b.openSlideImage = OpenSlideGTK.OpenSlideImage.Open(file); - b.openSlideSource = OpenSlideGTK.SlideSourceBase.Create(file); - b.Type = ImageType.pyramidal; - tile = true; - } + b.openSlideImage = OpenSlideImage.Open(file); + b.openslideBase = (OpenSlideBase)OpenSlideGTK.SlideSourceBase.Create(file); } - catch (Exception e) + else { - Console.WriteLine(e.Message.ToString()); + b.slideBase = new SlideBase(b,SlideImage.Open(b)); } - + } + catch (Exception e) + { + Console.WriteLine(e.Message.ToString()); + b.slideBase = new SlideBase(b, SlideImage.Open(b)); + } + // read the image data bytes int pages = reader.getImageCount(); bool inter = reader.isInterleaved(); @@ -6745,16 +6656,51 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage int c = 0; int t = 0; if (!tile) - for (int p = 0; p < pages; p++) + { + try + { + for (int p = 0; p < pages; p++) + { + Bitmap bf; + byte[] bytes = reader.openBytes(p); + bf = new Bitmap(file, SizeX, SizeY, PixelFormat, bytes, new ZCT(z, c, t), p, null, b.littleEndian, inter); + b.Buffers.Add(bf); + } + } + catch (Exception) { - Bitmap bf; - progressValue = (float)p / (float)pages; - byte[] bytes = reader.openBytes(p); - bf = new Bitmap(file, SizeX, SizeY, PixelFormat, bytes, new ZCT(z, c, t), p, null, b.littleEndian, inter); - b.Buffers.Add(bf); + //If we failed to read an entire plane it is likely too large so we open as pyramidal. + b.Type = ImageType.pyramidal; + try + { + string st = OpenSlideImage.DetectVendor(file); + if (st != null && !file.EndsWith("ome.tif") && useOpenSlide) + { + b.openSlideImage = OpenSlideImage.Open(file); + b.openslideBase = (OpenSlideBase)OpenSlideGTK.SlideSourceBase.Create(file); + } + else + { + b.slideBase = new SlideBase(b, SlideImage.Open(b)); + } + } + catch (Exception e) + { + Console.WriteLine(e.Message.ToString()); + b.slideBase = new SlideBase(b, SlideImage.Open(b)); + } + b.imRead = reader; + for (int p = 0; p < pages; p++) + { + b.Buffers.Add(GetTile(b, p, serie, tilex, tiley, tileSizeX, tileSizeY)); + Statistics.CalcStatistics(b.Buffers.Last()); + } } + + } else { + b.imRead = reader; for (int p = 0; p < pages; p++) { b.Buffers.Add(GetTile(b, p, serie, tilex, tiley, tileSizeX, tileSizeY)); @@ -6804,37 +6750,39 @@ public static BioImage OpenOME(string file, int serie, bool tab, bool addToImage { Thread.Sleep(50); } while (b.Buffers[b.Buffers.Count - 1].Stats == null); + b.SetLabelMacroResolutions(); Statistics.ClearCalcBuffer(); AutoThreshold(b, true); if (b.bitsPerPixel > 8) b.StackThreshold(true); else b.StackThreshold(false); - if (addToImages) - Images.AddImage(b, tab); - //We use a try block to close the reader as sometimes this will cause an error. - bool stop = false; - do + if (!tile) { - try - { - reader.close(); - stop = true; - } - catch (Exception e) + //We use a try block to close the reader as sometimes this will cause an error. + bool stop = false; + do { - Console.WriteLine(e.Message); - } - } while (!stop); + try + { + reader.close(); + stop = true; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } while (!stop); + } + if (addToImages) + Images.AddImage(b, tab); b.Loading = false; - b.SetLabelMacroResolutions(); Console.WriteLine("Opening complete " + file); return b; } public ImageReader imRead; public Tiff tifRead; - public OpenSlideGTK.OpenSlideImage openSlideImage; - public OpenSlideGTK.ISlideSource openSlideSource; + public OpenSlideImage openSlideImage; static Bitmap bm; /// It reads a tile from a file, and returns a bitmap /// @@ -6861,14 +6809,14 @@ public static Bitmap GetTile(BioImage b, ZCT coord, int serie, int tilex, int ti return new Bitmap(tileSizeX, tileSizeY, AForge.PixelFormat.Format32bppArgb, b.openSlideImage.ReadRegion(serie, tilex, tiley, tileSizeX, tileSizeY), new ZCT(), ""); } - string curfile = reader.getCurrentFile(); + string curfile = b.imRead.getCurrentFile(); if (curfile == null) { b.meta = (IMetadata)((OMEXMLService)factory.getInstance(typeof(OMEXMLService))).createOMEXMLMetadata(); - reader.close(); - reader.setMetadataStore(b.meta); + b.imRead.close(); + b.imRead.setMetadataStore(b.meta); Console.WriteLine(b.file); - reader.setId(b.file); + b.imRead.setId(b.file); } else { @@ -6876,20 +6824,21 @@ public static Bitmap GetTile(BioImage b, ZCT coord, int serie, int tilex, int ti string cf = curfile.Replace("\\", "/"); if (cf != fi) { - reader.close(); + b.imRead.close(); b.meta = (IMetadata)((OMEXMLService)factory.getInstance(typeof(OMEXMLService))).createOMEXMLMetadata(); - reader.setMetadataStore(b.meta); - reader.setId(b.file); + b.imRead.setMetadataStore(b.meta); + b.imRead.setId(b.file); } } - if (reader.getSeries() != serie) - reader.setSeries(serie); - int SizeX = reader.getSizeX(); - int SizeY = reader.getSizeY(); - bool flat = reader.hasFlattenedResolutions(); + if (b.imRead.getSeries() != serie) + b.imRead.setSeries(serie); + int SizeX = b.imRead.getSizeX(); + int SizeY = b.imRead.getSizeY(); + bool flat = b.imRead.hasFlattenedResolutions(); int p = b.Coords[coord.Z, coord.C, coord.T]; - bool littleEndian = reader.isLittleEndian(); + bool littleEndian = b.imRead.isLittleEndian(); PixelFormat PixelFormat = b.Resolutions[serie].PixelFormat; + bool interleaved = b.imRead.isInterleaved(); if (tilex < 0) tilex = 0; if (tiley < 0) @@ -6911,8 +6860,7 @@ public static Bitmap GetTile(BioImage b, ZCT coord, int serie, int tilex, int ti return null; try { - byte[] bytesr = reader.openBytes(b.Coords[coord.Z, coord.C, coord.T], tilex, tiley, sx, sy); - bool interleaved = reader.isInterleaved(); + byte[] bytesr = b.imRead.openBytes(b.Coords[coord.Z, coord.C, coord.T], tilex, tiley, sx, sy); return new Bitmap(b.file, sx, sy, PixelFormat, bytesr, coord, p, null, littleEndian, interleaved); } catch (Exception e) @@ -6934,14 +6882,14 @@ public static Bitmap GetTile(BioImage b, int index, int serie, int tilex, int ti return new Bitmap(tileSizeX, tileSizeY, AForge.PixelFormat.Format32bppArgb, b.openSlideImage.ReadRegion(serie, tilex, tiley, tileSizeX, tileSizeY), new ZCT(), ""); } - string curfile = reader.getCurrentFile(); + string curfile = b.imRead.getCurrentFile(); if (curfile == null) { b.meta = (IMetadata)((OMEXMLService)factory.getInstance(typeof(OMEXMLService))).createOMEXMLMetadata(); - reader.close(); - reader.setMetadataStore(b.meta); + b.imRead.close(); + b.imRead.setMetadataStore(b.meta); Console.WriteLine(b.file); - reader.setId(b.file); + b.imRead.setId(b.file); } else { @@ -6949,18 +6897,19 @@ public static Bitmap GetTile(BioImage b, int index, int serie, int tilex, int ti string cf = curfile.Replace("\\", "/"); if (cf != fi) { - reader.close(); + b.imRead.close(); b.meta = (IMetadata)((OMEXMLService)factory.getInstance(typeof(OMEXMLService))).createOMEXMLMetadata(); - reader.setMetadataStore(b.meta); - reader.setId(b.file); + b.imRead.setMetadataStore(b.meta); + b.imRead.setId(b.file); } } - if (reader.getSeries() != serie) - reader.setSeries(serie); - int SizeX = reader.getSizeX(); - int SizeY = reader.getSizeY(); - bool flat = reader.hasFlattenedResolutions(); - bool littleEndian = reader.isLittleEndian(); + if (b.imRead.getSeries() != serie) + b.imRead.setSeries(serie); + int SizeX = b.imRead.getSizeX(); + int SizeY = b.imRead.getSizeY(); + bool flat = b.imRead.hasFlattenedResolutions(); + bool littleEndian = b.imRead.isLittleEndian(); + bool interleaved = b.imRead.isInterleaved(); PixelFormat PixelFormat = b.Resolutions[serie].PixelFormat; if (tilex < 0) tilex = 0; @@ -6983,8 +6932,7 @@ public static Bitmap GetTile(BioImage b, int index, int serie, int tilex, int ti return null; try { - byte[] bytesr = reader.openBytes(index, tilex, tiley, sx, sy); - bool interleaved = reader.isInterleaved(); + byte[] bytesr = b.imRead.openBytes(index, tilex, tiley, sx, sy); return new Bitmap(b.file, sx, sy, PixelFormat, bytesr, new ZCT(), index, null, littleEndian, interleaved); } catch (Exception e) @@ -7083,6 +7031,33 @@ public static int GetBitMaxValue(int bt) else return 65535; } + /// + /// The function takes a list of points in stage space and converts them to image space using + /// the provided stage and physical size parameters. + /// + /// A list of PointD objects representing points in stage space. + /// The width of the stage or canvas in pixels. + /// The stageSizeY parameter represents the size of the stage or canvas + /// in the Y-axis direction. It is used to calculate the Y-coordinate of each point in the p + /// list in image space. + /// The physical size of the X-axis in the image space. + /// The physical size of the Y-axis in the coordinate system of the + /// stage or image. + /// + /// The method is returning an array of PointD objects. + /// + public static PointD[] ToImageSpace(List p, int stageSizeX, int stageSizeY, int physicalSizeX, int physicalSizeY) + { + PointD[] ps = new PointD[p.Count]; + for (int i = 0; i < p.Count; i++) + { + PointD pp = new PointD(); + pp.X = ((p[i].X - stageSizeX) / physicalSizeX); + pp.Y = ((p[i].Y - stageSizeY) / physicalSizeY); + ps[i] = pp; + } + return ps; + } /// If the bits per pixel is 8, then the pixel format is either 8bppIndexed, 24bppRgb, or /// 32bppArgb. If the bits per pixel is 16, then the pixel format is either 16bppGrayScale or /// 48bppRgb @@ -7203,7 +7178,7 @@ public static async Task OpenAsync(string[] files, bool OME, bool tab, bool imag { foreach (string file in files) { - await OpenAsync(file, OME, tab, images, 0); + await OpenAsync(file, OME, tab, images,0); } } /// It opens a file @@ -7271,18 +7246,60 @@ public static void Update(BioImage b) { b = OpenFile(b.file); } + /// + /// Updates the Buffers based on current pyramidal origin and resolution. + /// + public async void UpdateBuffersPyramidal() + { + for (int i = 0; i < Buffers.Count; i++) + { + Buffers[i].Dispose(); + } + Buffers.Clear(); + for (int i = 0; i < imagesPerSeries; i++) + { + if (openSlideImage != null) + { + byte[] bts = openslideBase.GetSlice(new OpenSlideGTK.SliceInfo(PyramidalOrigin.X, PyramidalOrigin.Y, PyramidalSize.Width, PyramidalSize.Height, resolution)); + Buffers.Add(new Bitmap((int)Math.Round(OpenSlideBase.destExtent.Width), (int)Math.Round(OpenSlideBase.destExtent.Height), PixelFormat.Format24bppRgb, bts, new ZCT(), "")); + } + else + { + start: + byte[] bts = await slideBase.GetSlice(new SliceInfo(PyramidalOrigin.X, PyramidalOrigin.Y, PyramidalSize.Width, PyramidalSize.Height, resolution,Coordinate)); + if(bts == null) + { + if(PyramidalOrigin.X == 0 && PyramidalOrigin.Y == 0) + { + Resolution = 1; + } + pyramidalOrigin = new PointD(0, 0); + goto start; + } + Buffers.Add(new Bitmap((int)Math.Round(SlideBase.destExtent.Width), (int)Math.Round(SlideBase.destExtent.Height), PixelFormat.Format24bppRgb, bts, new ZCT(), "")); + } + } + BioImage.AutoThreshold(this, true); + if (bitsPerPixel > 8) + StackThreshold(true); + else + StackThreshold(false); + } /// > Update() is a function that calls the Update() function of the parent class public void Update() { Update(this); } - + static string openfile; static bool omes, tab, add; static int serie; static void OpenThread() { - OpenFile(openfile, serie, tab, add); + if(omes) + OpenOME(openfile,serie,tab,add,false,0,0,0,0); + else + OpenFile(openfile, serie, tab, add); } static string savefile, saveid; static bool some; @@ -7330,15 +7347,15 @@ static void SaveSeriesThread() else SaveSeries(sts.ToArray(), savefile); } - /// - /// The function `SaveSeriesAsync` saves a series of `BioImage` objects to a file asynchronously. - /// - /// imgs is an array of BioImage objects. - /// The "file" parameter is a string that represents the file path where the - /// series of BioImages will be saved. - /// The "ome" parameter is a boolean flag that indicates whether the images - /// should be saved in OME-TIFF format or not. If "ome" is set to true, the images will be saved - /// in OME-TIFF format. If "ome" is set to false, the images will be + /// + /// The function `SaveSeriesAsync` saves a series of `BioImage` objects to a file asynchronously. + /// + /// imgs is an array of BioImage objects. + /// The "file" parameter is a string that represents the file path where the + /// series of BioImages will be saved. + /// The "ome" parameter is a boolean flag that indicates whether the images + /// should be saved in OME-TIFF format or not. If "ome" is set to true, the images will be saved + /// in OME-TIFF format. If "ome" is set to false, the images will be public static async Task SaveSeriesAsync(BioImage[] imgs, string file, bool ome) { sts.Clear(); @@ -7351,7 +7368,7 @@ public static async Task SaveSeriesAsync(BioImage[] imgs, string file, bool ome) await Task.Run(SaveSeriesThread); } static Enums.ForeignTiffCompression comp; - static int compLev = 0; + static int compLev= 0; static BioImage[] bms; static void SavePyramidalThread() { @@ -7790,7 +7807,7 @@ public static List OpenOMEROIs(string file, int series) { byte[] bts = meta.getMaskBinData(im, sc); bool end = meta.getMaskBinDataBigEndian(im, sc).booleanValue(); - an = ROI.CreateMask(co, bts, SizeX, SizeY, new PointD(stageSizeX, stageSizeY), physicalSizeX, physicalSizeY); + an = ROI.CreateMask(co, bts, SizeX, SizeY, new PointD(stageSizeX, stageSizeY),physicalSizeX,physicalSizeY); an.Text = meta.getMaskText(im, sc); an.id = meta.getMaskID(im, sc); ome.xml.model.primitives.NonNegativeInteger nz = meta.getMaskTheZ(im, sc); @@ -8140,11 +8157,11 @@ public static void AutoThreshold(BioImage b, bool updateImageStats) for (int t = 0; t < b.SizeT; t++) { int ind; - if (b.Channels.Count > b.SizeC) + if(b.Channels.Count > b.SizeC) { ind = b.Coords[z, 0, t]; } - else + else ind = b.Coords[z, c, t]; if (b.Buffers[ind].RGBChannelsCount == 1) sts[0].AddStatistics(b.Buffers[ind].Stats[0]); @@ -8390,4 +8407,5 @@ public override string ToString() return a; } } + } diff --git a/Source/Bio/ISlideSource.cs b/Source/Bio/ISlideSource.cs new file mode 100644 index 0000000..37ed5a9 --- /dev/null +++ b/Source/Bio/ISlideSource.cs @@ -0,0 +1,600 @@ +using BruTile; +using BruTile.Cache; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using AForge; +using Image = SixLabors.ImageSharp.Image; +namespace BioLib +{ + public class LruCache + { + public class Info + { + public ZCT Coordinate { get; set; } + public TileIndex Index { get; set; } + } + private readonly int capacity; + private Dictionary> cacheMap = new Dictionary>(); + private LinkedList<(Info key, TValue value)> lruList = new LinkedList<(Info key, TValue value)>(); + + public LruCache(int capacity) + { + this.capacity = capacity; + } + + public TValue Get(Info key) + { + foreach (LinkedListNode<(Info key, TValue value)> item in cacheMap.Values) + { + Info k = item.Value.key; + if(k.Coordinate == key.Coordinate && k.Index == key.Index) + { + lruList.Remove(item); + lruList.AddLast(item); + return item.Value.value; + } + } + return default(TValue); + } + + public void Add(Info key, TValue value) + { + if (cacheMap.Count >= capacity) + { + var oldest = lruList.First; + if (oldest != null) + { + lruList.RemoveFirst(); + cacheMap.Remove(oldest.Value.key); + } + } + + if (cacheMap.ContainsKey(key)) + { + lruList.Remove(cacheMap[key]); + } + + var newNode = new LinkedListNode<(Info key, TValue value)>((key, value)); + lruList.AddLast(newNode); + cacheMap[key] = newNode; + } + public void Dispose() + { + foreach (LinkedListNode<(Info key, TValue value)> item in cacheMap.Values) + { + lruList.Remove(item); + } + } + } + public class TileCache + { + private LruCache cache; + private int capacity; + SlideSourceBase source = null; + public TileCache(SlideSourceBase source, int capacity = 1000) + { + this.source = source; + this.capacity = capacity; + this.cache = new LruCache(capacity); + } + + public async Task GetTile(TileInformation info) + { + LruCache.Info inf = new LruCache.Info(); + inf.Coordinate = info.Coordinate; + inf.Index = info.Index; + byte[] data = cache.Get(inf); + if (data != null) + { + return data; + } + byte[] tile = await LoadTile(info); + if(tile!=null) + AddTile(info, tile); + return tile; + } + + private void AddTile(TileInformation tileId, byte[] tile) + { + LruCache.Info inf = new LruCache.Info(); + inf.Coordinate = tileId.Coordinate; + inf.Index = tileId.Index; + cache.Add(inf, tile); + } + + private async Task LoadTile(TileInformation tileId) + { + try + { + return await source.GetTileAsync(tileId); + } + catch (Exception e) + { + return null; + } + } + public void Dispose() + { + cache.Dispose(); + } + } + + public class TileInformation + { + public TileIndex Index { get; set; } + public Extent Extent { get; set; } + public ZCT Coordinate { get; set; } + } + + public abstract class SlideSourceBase : ISlideSource, IDisposable + { + #region Static + public static bool UseRealResolution { get; set; } = true; + + private static IDictionary> keyValuePairs = new Dictionary>(); + + /// + /// resister decode for Specific format + /// + /// dot and extension upper + /// file path,enable cache,decoder + public static void Resister(string extensionUpper, Func factory) + { + keyValuePairs.Add(extensionUpper, factory); + } + + + public static ISlideSource Create(BioImage source, SlideImage im, bool enableCache = true) + { + + var ext = Path.GetExtension(source.file).ToUpper(); + try + { + if (keyValuePairs.TryGetValue(ext, out var factory) && factory != null) + return factory.Invoke(source.file, enableCache); + + if (!string.IsNullOrEmpty(SlideBase.DetectVendor(source.file))) + { + SlideBase b = new SlideBase(source, im, enableCache); + + } + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + return null; + } + #endregion + public double MinUnitsPerPixel { get; protected set; } + public static Extent destExtent; + public static Extent sourceExtent; + public static double curUnitsPerPixel = 1; + public static bool UseVips = true; + public TileCache cache = null; + public async Task GetSlice(SliceInfo sliceInfo) + { + if (cache == null) + cache = new TileCache(this); + var curLevel = Image.BioImage.LevelFromResolution(sliceInfo.Resolution); + var curUnitsPerPixel = Schema.Resolutions[curLevel].UnitsPerPixel; + var tileInfos = Schema.GetTileInfos(sliceInfo.Extent, curLevel); + List> tiles = new List>(); + foreach (BruTile.TileInfo t in tileInfos) + { + TileInformation tf = new TileInformation(); + tf.Extent = t.Extent; + tf.Coordinate = sliceInfo.Coordinate; + tf.Index = t.Index; + byte[] c = await cache.GetTile(tf); + if(c!=null) + tiles.Add(Tuple.Create(t.Extent.WorldToPixelInvertedY(curUnitsPerPixel), c)); + } + var srcPixelExtent = sliceInfo.Extent.WorldToPixelInvertedY(curUnitsPerPixel); + var dstPixelExtent = sliceInfo.Extent.WorldToPixelInvertedY(sliceInfo.Resolution); + var dstPixelHeight = sliceInfo.Parame.DstPixelHeight > 0 ? sliceInfo.Parame.DstPixelHeight : dstPixelExtent.Height; + var dstPixelWidth = sliceInfo.Parame.DstPixelWidth > 0 ? sliceInfo.Parame.DstPixelWidth : dstPixelExtent.Width; + destExtent = new Extent(0, 0, dstPixelWidth, dstPixelHeight); + sourceExtent = srcPixelExtent; + if (UseVips) + { + try + { + NetVips.Image im = null; + if (this.Image.BioImage.Resolutions[curLevel].PixelFormat == PixelFormat.Format16bppGrayScale) + im = ImageUtil.JoinVips16(tiles, srcPixelExtent, new Extent(0, 0, dstPixelWidth, dstPixelHeight)); + else if(this.Image.BioImage.Resolutions[curLevel].PixelFormat == PixelFormat.Format24bppRgb) + im = ImageUtil.JoinVipsRGB24(tiles, srcPixelExtent, new Extent(0, 0, dstPixelWidth, dstPixelHeight)); + return im.WriteToMemory(); + } + catch (Exception e) + { + UseVips = false; + Console.WriteLine("Failed to use LibVips please install Libvips for your platform."); + Console.WriteLine(e.Message); + } + } + try + { + Image im = null; + if (this.Image.BioImage.Resolutions[curLevel].PixelFormat == PixelFormat.Format16bppGrayScale) + { + im = ImageUtil.Join16(tiles, srcPixelExtent, new Extent(0, 0, dstPixelWidth, dstPixelHeight)); + byte[] bts = Get16Bytes((Image)im); + im.Dispose(); + return bts; + } + else if (this.Image.BioImage.Resolutions[curLevel].PixelFormat == PixelFormat.Format24bppRgb) + { + im = ImageUtil.JoinRGB24(tiles, srcPixelExtent, new Extent(0, 0, dstPixelWidth, dstPixelHeight)); + byte[] bts = GetRgb24Bytes((Image)im); + im.Dispose(); + return bts; + } + } + catch (Exception er) + { + Console.WriteLine(er.Message); + return null; + } + return null; + } + public byte[] GetRgb24Bytes(Image image) + { + int width = image.Width; + int height = image.Height; + byte[] rgbBytes = new byte[width * height * 3]; // 3 bytes per pixel (RGB) + + int byteIndex = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Rgb24 pixel = image[x, y]; + rgbBytes[byteIndex++] = pixel.B; + rgbBytes[byteIndex++] = pixel.G; + rgbBytes[byteIndex++] = pixel.R; + } + } + + return rgbBytes; + } + public byte[] Get16Bytes(Image image) + { + int width = image.Width; + int height = image.Height; + byte[] bytes = new byte[width * height * 2]; + + int byteIndex = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + L16 pixel = image[x, y]; + byte[] bts = BitConverter.GetBytes(pixel.PackedValue); + bytes[byteIndex++] = bts[0]; + bytes[byteIndex++] = bts[1]; + } + } + + return bytes; + } + + public SlideImage Image { get; set; } + + public ITileSchema Schema { get; protected set; } + + public string Name { get; protected set; } + + public Attribution Attribution { get; protected set; } + + public IReadOnlyDictionary ExternInfo { get; protected set; } + + public string Source { get; protected set; } + + public abstract IReadOnlyDictionary GetExternImages(); + + #region IDisposable + private bool disposedValue; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + //_bgraCache.Dispose(); + } + disposedValue = true; + } + } + + ~SlideSourceBase() + { + Dispose(disposing: false); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public async Task GetTileAsync(TileInformation tileInfo) + { + if (tileInfo == null) + return null; + var r = Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var tileWidth = Schema.Resolutions[tileInfo.Index.Level].TileWidth; + var tileHeight = Schema.Resolutions[tileInfo.Index.Level].TileHeight; + var curLevelOffsetXPixel = tileInfo.Extent.MinX / Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var curLevelOffsetYPixel = -tileInfo.Extent.MaxY / Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var curTileWidth = (int)(tileInfo.Extent.MaxX > Schema.Extent.Width ? tileWidth - (tileInfo.Extent.MaxX - Schema.Extent.Width) / r : tileWidth); + var curTileHeight = (int)(-tileInfo.Extent.MinY > Schema.Extent.Height ? tileHeight - (-tileInfo.Extent.MinY - Schema.Extent.Height) / r : tileHeight); + var bgraData = await Image.ReadRegionAsync(tileInfo.Index.Level, (long)curLevelOffsetXPixel, (long)curLevelOffsetYPixel, curTileWidth, curTileHeight,tileInfo.Coordinate); + return bgraData; + } + public async Task GetTileAsync(BruTile.TileInfo tileInfo) + { + if (tileInfo == null) + return null; + var r = Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var tileWidth = Schema.Resolutions[tileInfo.Index.Level].TileWidth; + var tileHeight = Schema.Resolutions[tileInfo.Index.Level].TileHeight; + var curLevelOffsetXPixel = tileInfo.Extent.MinX / Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var curLevelOffsetYPixel = -tileInfo.Extent.MaxY / Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var curTileWidth = (int)(tileInfo.Extent.MaxX > Schema.Extent.Width ? tileWidth - (tileInfo.Extent.MaxX - Schema.Extent.Width) / r : tileWidth); + var curTileHeight = (int)(-tileInfo.Extent.MinY > Schema.Extent.Height ? tileHeight - (-tileInfo.Extent.MinY - Schema.Extent.Height) / r : tileHeight); + var bgraData = await Image.ReadRegionAsync(tileInfo.Index.Level, (long)curLevelOffsetXPixel, (long)curLevelOffsetYPixel, curTileWidth, curTileHeight, new ZCT()); + return bgraData; + } + public static byte[] ConvertRgbaToRgb(byte[] rgbaArray) + { + // Initialize a new byte array for RGB24 format + byte[] rgbArray = new byte[(rgbaArray.Length / 4) * 3]; + + for (int i = 0, j = 0; i < rgbaArray.Length; i += 4, j += 3) + { + // Copy the R, G, B values, skip the A value + rgbArray[j] = rgbaArray[i + 2]; // B + rgbArray[j + 1] = rgbaArray[i + 1]; // G + rgbArray[j + 2] = rgbaArray[i]; // R + } + + return rgbArray; + } + #endregion + } + + /// + /// + /// + public interface ISlideSource : ITileSource, ISliceProvider, ISlideExternInfo + { + + } + + /// + /// + public interface ISlideExternInfo + { + /// + /// File path. + /// + string Source { get; } + + /// + /// Extern info. + /// + IReadOnlyDictionary ExternInfo { get; } + + /// + /// Extern image. + /// + /// + IReadOnlyDictionary GetExternImages(); + } + + /// + /// + public interface ISliceProvider + { + /// + /// um/pixel + /// + double MinUnitsPerPixel { get; } + + /// + /// Get slice. + /// + /// Slice info + /// + Task GetSlice(SliceInfo sliceInfo); + } + + /// + /// Slice info. + /// + public class SliceInfo + { + public SliceInfo() { } + + /// + /// Create a world extent by pixel and resolution. + /// + /// pixel x + /// pixel y + /// pixel width + /// pixel height + /// um/pixel + public SliceInfo(double xPixel, double yPixel, double widthPixel, double heightPixel, double unitsPerPixel, ZCT coord) + { + Extent = new Extent(xPixel, yPixel, xPixel + widthPixel,yPixel + heightPixel).PixelToWorldInvertedY(unitsPerPixel); + Resolution = unitsPerPixel; + } + + /// + /// um/pixel + /// + public double Resolution + { + get; + set; + } = 1; + /// + /// ZCT Coordinate + /// + public ZCT Coordinate { get; set; } + + /// + /// World extent. + /// + public Extent Extent + { + get; + set; + } + public SliceParame Parame + { + get; + set; + } = new SliceParame(); + } + + public class SliceParame + { + /// + /// Scale to width,default 0(no scale) + /// /// + public int DstPixelWidth { get; set; } = 0; + + /// + /// Scale to height,default 0(no scale) + /// + public int DstPixelHeight { get; set; } = 0; + + /// + /// Sample mode. + /// + public SampleMode SampleMode { get; set; } = SampleMode.Nearest; + + /// + /// Image quality. + /// + public int? Quality { get; set; } + } + + + public enum SampleMode + { + /// + /// Nearest. + /// + Nearest = 0, + /// + /// Nearest up. + /// + NearestUp, + /// + /// Nearest down. + /// + NearestDown, + /// + /// Top. + /// + Top, + /// + /// Bottom. + /// + /// + /// maybe very slow, just for clearer images. + /// + Bottom, + } + + /// + /// Image type. + /// + public enum ImageType : int + { + /// + /// + Label, + + /// + /// + Title, + + /// + /// + Preview, + } + + public static class ExtentEx + { + /// + /// Convert OSM world to pixel + /// + /// world extent + /// resolution,um/pixel + /// + public static Extent WorldToPixelInvertedY(this Extent extent, double unitsPerPixel) + { + return new Extent(extent.MinX / unitsPerPixel, -extent.MaxY / unitsPerPixel, extent.MaxX / unitsPerPixel, -extent.MinY / unitsPerPixel); + } + + + /// + /// Convert pixel to OSM world. + /// + /// pixel extent + /// resolution,um/pixel + /// + public static Extent PixelToWorldInvertedY(this Extent extent, double unitsPerPixel) + { + return new Extent(extent.MinX * unitsPerPixel, -extent.MaxY * unitsPerPixel, extent.MaxX * unitsPerPixel, -extent.MinY * unitsPerPixel); + } + + /// + /// Convert double to int. + /// + /// + /// + public static Extent ToIntegerExtent(this Extent extent) + { + return new Extent((int)Math.Round(extent.MinX), (int)Math.Round(extent.MinY), (int)Math.Round(extent.MaxX), (int)Math.Round(extent.MaxY)); + } + } + + public static class ObjectEx + { + /// + /// Get fields and properties + /// + /// + /// + public static Dictionary GetFieldsProperties(this object obj) + { + Dictionary keys = new Dictionary(); + foreach (var item in obj.GetType().GetFields()) + { + keys.Add(item.Name, item.GetValue(obj)); + } + foreach (var item in obj.GetType().GetProperties()) + { + try + { + if (item.GetIndexParameters().Any()) continue; + keys.Add(item.Name, item.GetValue(obj)); + } + catch (Exception) { } + } + return keys; + } + } +} \ No newline at end of file diff --git a/Source/Bio/SlideBase.cs b/Source/Bio/SlideBase.cs new file mode 100644 index 0000000..b134243 --- /dev/null +++ b/Source/Bio/SlideBase.cs @@ -0,0 +1,146 @@ +using BruTile; +using BruTile.Cache; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.PixelFormats; + +namespace BioLib +{ + public class SlideBase : SlideSourceBase + { + public readonly SlideImage SlideImage; + public SlideBase(BioImage source, SlideImage im, bool enableCache = true) + { + Source = source.file; + SlideImage = im; + Image = im; + double minUnitsPerPixel; + if (source.PhysicalSizeX < source.PhysicalSizeY) minUnitsPerPixel = source.PhysicalSizeX; else minUnitsPerPixel = source.PhysicalSizeY; + MinUnitsPerPixel = UseRealResolution ? minUnitsPerPixel : 1; + if (MinUnitsPerPixel <= 0) MinUnitsPerPixel = 1; + var height = SlideImage.Dimensions.Height; + var width = SlideImage.Dimensions.Width; + //ExternInfo = GetInfo(); + Schema = new TileSchema + { + YAxis = YAxis.OSM, + Format = "jpg", + Extent = new Extent(0, -height, width, 0), + OriginX = 0, + OriginY = 0, + }; + InitResolutions(Schema.Resolutions, 256, 256); + } + + public static string DetectVendor(string source) + { + return SlideImage.DetectVendor(source); + } + + + public override IReadOnlyDictionary GetExternImages() + { + throw new NotImplementedException(); + /* + Dictionary images = new Dictionary(); + var r = Math.Max(Schema.Extent.Height, Schema.Extent.Width) / 512; + images.Add("preview", GetSlice(new SliceInfo { Extent = Schema.Extent, Resolution = r })); + foreach (var item in SlideImage.GetAssociatedImages()) + { + var dim = item.Value.Dimensions; + images.Add(item.Key, ImageUtil.GetJpeg(item.Value.Data, 4, 4 * (int)dim.Width, (int)dim.Width, (int)dim.Height)); + } + return images; + */ + } + private static Image CreateImageFromRgbaData(byte[] rgbaData, int width, int height) + { + Image image = new Image(width, height); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int index = (y * width + x) * 4; + byte r = rgbaData[index]; + byte g = rgbaData[index + 1]; + byte b = rgbaData[index + 2]; + // byte a = rgbaData[index + 3]; // Alpha channel, not used in Rgb24 + + image[x, y] = new Rgb24(r, g, b); + } + } + + return image; + } + public byte[] GetTile(TileInfo tileInfo) + { + var r = Schema.Resolutions[tileInfo.Index.Level].UnitsPerPixel; + var tileWidth = Schema.Resolutions[tileInfo.Index.Level].TileWidth; + var tileHeight = Schema.Resolutions[tileInfo.Index.Level].TileHeight; + var curLevelOffsetXPixel = tileInfo.Extent.MinX / MinUnitsPerPixel; + var curLevelOffsetYPixel = -tileInfo.Extent.MaxY / MinUnitsPerPixel; + var curTileWidth = (int)(tileInfo.Extent.MaxX > Schema.Extent.Width ? tileWidth - (tileInfo.Extent.MaxX - Schema.Extent.Width) / r : tileWidth); + var curTileHeight = (int)(-tileInfo.Extent.MinY > Schema.Extent.Height ? tileHeight - (-tileInfo.Extent.MinY - Schema.Extent.Height) / r : tileHeight); + var bgraData = SlideImage.ReadRegion(tileInfo.Index.Level, (long)curLevelOffsetXPixel, (long)curLevelOffsetYPixel, curTileWidth, curTileHeight); + //We check to see if the data is valid. + if (bgraData.Length != curTileWidth * curTileHeight * 4) + return null; + byte[] bm = ConvertRgbaToRgb(bgraData); + return bm; + } + public static byte[] ConvertRgbaToRgb(byte[] rgbaArray) + { + // Initialize a new byte array for RGB24 format + byte[] rgbArray = new byte[(rgbaArray.Length / 4) * 3]; + + for (int i = 0, j = 0; i < rgbaArray.Length; i += 4, j += 3) + { + // Copy the R, G, B values, skip the A value + rgbArray[j] = rgbaArray[i]; // B + rgbArray[j + 1] = rgbaArray[i + 1]; // G + rgbArray[j + 2] = rgbaArray[i + 2]; // R + } + + return rgbArray; + } + + protected void InitResolutions(IDictionary resolutions, int tileWidth, int tileHeight) + { + for (int i = 0; i < SlideImage.LevelCount; i++) + { + /* + bool useInternalWidth = int.TryParse(ExternInfo.TryGetValue($"openslide.level[{i}].tile-width", out var _w) ? (string)_w : null, out var w) && w >= tileWidth; + bool useInternalHeight = int.TryParse(ExternInfo.TryGetValue($"openslide.level[{i}].tile-height", out var _h) ? (string)_h : null, out var h) && h >= tileHeight; + + bool useInternalSize = useInternalHeight && useInternalWidth; + var tw = useInternalSize ? w : tileWidth; + var th = useInternalSize ? h : tileHeight; + resolutions.Add(i, new Resolution(i, MinUnitsPerPixel * SlideImage.GetLevelDownsample(i), tw, th)); + */ + resolutions.Add(i, new BruTile.Resolution(i, SlideImage.BioImage.GetUnitPerPixel(i), tileWidth, tileHeight)); + } + } + + #region IDisposable + private bool disposedValue; + protected override void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + SlideImage.Dispose(); + } + disposedValue = true; + } + base.Dispose(disposing); + } + + #endregion + } +} diff --git a/Source/Bio/SlideImage.cs b/Source/Bio/SlideImage.cs new file mode 100644 index 0000000..a288920 --- /dev/null +++ b/Source/Bio/SlideImage.cs @@ -0,0 +1,311 @@ +using AForge; +using OpenSlideGTK; +using OpenSlideGTK.Interop; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace BioLib +{ + /// + /// openslide wrapper + /// + public partial class SlideImage : IDisposable + { + public BioImage BioImage { get; set; } + + + /// + /// Quickly determine whether a whole slide image is recognized. + /// + /// + /// If OpenSlide recognizes the file referenced by , + /// return a string identifying the slide format vendor.This is equivalent to the + /// value of the property. Calling + /// on this file will return a valid + /// OpenSlide object or an OpenSlide object in error state. + /// + /// Otherwise, return .Calling on this file will also + /// return . + /// The filename to check. On Windows, this must be in UTF-8. + /// An identification of the format vendor for this file, or NULL. + public static string DetectVendor(string filename) + { + return filename; + } + /// + /// + /// + /// + /// close handle when disposed + /// + public SlideImage() + { + + } + + /// + /// Add .dll directory to PATH + /// + /// + /// + public static void Initialize(string path = null) + { + + } + static SlideImage() + { + Initialize(); + } + + /// + /// Open. + /// + /// + /// + /// + public static SlideImage Open(BioImage b) + { + SlideImage im = new SlideImage(); + im.BioImage = b; + return im; + } + + /// + /// Get the number of levels in the whole slide image. + /// + /// The number of levels, or -1 if an error occurred. + /// + public int LevelCount + { + get + { + if (BioImage.MacroResolution.HasValue) + { + return BioImage.Resolutions.Count - 2; + } + else + return BioImage.Resolutions.Count; + } + } + + private ImageDimension? _dimensionsRef; + private readonly object _dimensionsSynclock = new object(); + + /// + /// Get the dimensions of level 0 (the largest level). Exactly + /// equivalent to calling GetLevelDimensions(0). + /// + /// + public ImageDimension Dimensions + { + get + { + if (_dimensionsRef == null) + { + lock (_dimensionsSynclock) + { + if (_dimensionsRef == null) + _dimensionsRef = GetLevelDimension(0); + } + } + return _dimensionsRef.Value; + } + } + /// + /// Get the dimensions of a level. + /// + /// The desired level. + /// + public ImageDimension GetLevelDimension(int level) + { + return new ImageDimension(BioImage.Resolutions[level].SizeX, BioImage.Resolutions[level].SizeY); + } + + /// + /// Get all level dimensions. + /// + /// + /// + public IEnumerable GetLevelDimensions() + { + var count = LevelCount; + for (int i = 0; i < count; i++) + { + yield return GetLevelDimension(i); + } + } + /// + /// Calculates the base downsampling factor between two levels of a slide. + /// + /// The dimension (width or height) of the original level. + /// The dimension (width or height) of the next level. + /// The base downsampling factor. + public static double CalculateBaseFactor(int originalResolution, int lastLevelResolution, int totalLevels) + { + if (totalLevels <= 1) + { + throw new ArgumentException("Total levels must be greater than 1 to calculate a base factor."); + } + if (lastLevelResolution <= 0 || originalResolution <= 0) + { + throw new ArgumentException("Resolutions must be greater than 0."); + } + + // Calculate the base downsampling factor + double baseFactor = Math.Pow((double)originalResolution / lastLevelResolution, 1.0 / (totalLevels - 1)); + return baseFactor; + } + + /// + /// Calculates the downsample factors for each level of a slide. + /// + /// The downsample factor between each level. + /// Total number of levels in the slide. + /// A list of downsample factors for each level. + public static List GetLevelDownsamples(double baseDownsampleFactor, int totalLevels) + { + var levelDownsamples = new List(); + + for (int level = 0; level < totalLevels; level++) + { + // Calculate the downsample factor for the current level. + // Math.Pow is used to raise the baseDownsampleFactor to the power of the level. + double downsampleFactorAtLevel = Math.Pow(baseDownsampleFactor, level); + levelDownsamples.Add(downsampleFactorAtLevel); + } + + return levelDownsamples; + } + + + /* + /// + /// Get the best level to use for displaying the given downsample. + /// + /// The downsample factor. + /// The level identifier, or -1 if an error occurred. + /// + public int GetBestLevelForDownsample(double downsample) + { + if (NativeMethods.isWindows) + { + var result = NativeMethods.Windows.GetBestLevelForDownsample(Handle, downsample); + return result != -1 ? result : CheckIfThrow(result); + } else if (NativeMethods.isLinux) + { + var result = NativeMethods.Linux.GetBestLevelForDownsample(Handle, downsample); + return result != -1 ? result : CheckIfThrow(result); + } + else + { + var result = NativeMethods.OSX.GetBestLevelForDownsample(Handle, downsample); + return result != -1 ? result : CheckIfThrow(result); + } + } + */ + /// + /// Copy pre-multiplied BGRA data from a whole slide image. + /// + /// The desired level. + /// The top left x-coordinate, in the level 0 reference frame. + /// The top left y-coordinate, in the level 0 reference frame. + /// The width of the region. Must be non-negative. + /// The height of the region. Must be non-negative. + /// The pixel data of this region. + /// + /// + public unsafe byte[] ReadRegion(int level, long x, long y, long width, long height) + { + return BioImage.GetTile(BioImage, BioImage.Coordinate, level, (int)x, (int)y, (int)width, (int)height).Bytes; + } + + /// + /// Copy pre-multiplied BGRA data from a whole slide image. + /// + /// The desired level. + /// The top left x-coordinate, in the level 0 reference frame. + /// The top left y-coordinate, in the level 0 reference frame. + /// The width of the region. Must be non-negative. + /// The height of the region. Must be non-negative. + /// The BGRA pixel data of this region. + /// + public unsafe bool TryReadRegion(int level, long x, long y, long width, long height, out byte[] data, ZCT zct) + { + data = BioImage.GetTile(BioImage, zct, level, (int)x, (int)y, (int)width, (int)height).Bytes; + if (data == null) + return false; + else + return true; + } + + /// + ///Close an OpenSlide object. + /// + /// + ///No other threads may be using the object. + ///After this call returns, the object cannot be used anymore. + /// + public void Close() + { + + } + + #region IDisposable + + private bool disposedValue; + + /// + /// Dispose + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + } + + Close(); + disposedValue = true; + } + } + + /// + /// + ~SlideImage() + { + Dispose(disposing: false); + } + + /// + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public async Task ReadRegionAsync(int level, long curLevelOffsetXPixel, long curLevelOffsetYPixel, int curTileWidth, int curTileHeight, ZCT coord) + { + try + { + byte[] bts; + TryReadRegion(level, curLevelOffsetXPixel, curLevelOffsetYPixel, curTileWidth, curTileHeight,out bts,coord); + return bts; + } + catch (Exception e) + { + return null; + } + } + #endregion + } +} diff --git a/Source/Bio/SlideSliceLayer.cs b/Source/Bio/SlideSliceLayer.cs new file mode 100644 index 0000000..9c5cad9 --- /dev/null +++ b/Source/Bio/SlideSliceLayer.cs @@ -0,0 +1,60 @@ +using BruTile; +using Mapsui; +using Mapsui.Fetcher; +using Mapsui.Layers; +using Mapsui.Providers; +using Mapsui.Styles; +using Mapsui.Tiling.Extensions; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BioLib +{ + /// + /// Slide slice layer + /// + public class SlideSliceLayer : BaseLayer + { + private ISlideSource _slideSource; + private double _lastResolution = 0; + private IEnumerable _lastFeatures = new List();//new Features(new[] { new Feature() }); + private Extent _lastExtent; + + public SlideSliceLayer(ISlideSource slideSource) : base() + { + _slideSource = slideSource; + Name = "SliceLayer"; + Extent = slideSource.Schema.Extent.ToMRect(); + } + + public override IEnumerable GetFeatures(MRect box, double resolution) + { + if (box is null) return Enumerable.Empty(); + // Repaint on debouncing, resolution changed(zoom map) or box changed(resize control) . + if (_lastExtent.ToMRect().Centroid.Distance(box.Centroid) > 2 * resolution || _lastResolution != resolution || _lastExtent.Width != box.Width || _lastExtent.Height != box.Height) + { + _lastExtent = box.ToExtent(); + _lastResolution = resolution; + MRect box2 = box.Grow(SymbolStyle.DefaultWidth * 2.0 * resolution, SymbolStyle.DefaultHeight * 2.0 * resolution); + var sliceInfo = new SliceInfo() { Extent = box2.ToExtent(), Resolution = resolution }; + var bytes = _slideSource.GetSlice(sliceInfo); + if (bytes != null && _lastFeatures.FirstOrDefault() is IFeature feature) + { + feature = new RasterFeature(new MRaster(bytes.Result, box2)); + } + } + return _lastFeatures; + } + + /* + public override void RefreshData(MRect extent, double resolution, ChangeType changeType) + { + OnDataChanged(new DataChangedEventArgs()); + } + */ + } +} diff --git a/Source/Bio/SlideTileLayer.cs b/Source/Bio/SlideTileLayer.cs new file mode 100644 index 0000000..35ce89d --- /dev/null +++ b/Source/Bio/SlideTileLayer.cs @@ -0,0 +1,81 @@ +using BruTile; +using Mapsui; +using Mapsui.Fetcher; +using Mapsui.Layers; +using Mapsui.Providers; +using Mapsui.Rendering; +using Mapsui.Tiling.Fetcher; +using Mapsui.Tiling.Layers; +using Mapsui.Tiling.Rendering; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BioLib +{ + /// + /// Slide tile layer + /// + public class SlideTileLayer : TileLayer + { + private ISlideSource _slideSource; + + public SlideTileLayer( + ISlideSource source = null, + int minTiles = 200, + int maxTiles = 300, + IDataFetchStrategy dataFetchStrategy = null, + IRenderFetchStrategy renderFetchStrategy = null, + int minExtraTiles = -1, + int maxExtraTiles = -1, + Func> fetchTileAsFeature = null) + : base(source, minTiles, maxTiles, dataFetchStrategy, renderFetchStrategy, minExtraTiles, maxExtraTiles, (Func>)fetchTileAsFeature) + { + Name = "TileLayer"; + _slideSource = source; + } + + public override IReadOnlyList Resolutions + { + get + { + var resolution = new List() + { + 0.0625,0.125,0.25,0.5,1,2,4,8,16,32,64,128,256,512,1024,2048,4096 + }; + var values = _slideSource.Schema.Resolutions.Values.Select(_ => _.UnitsPerPixel); + return GetNearest(resolution, values); + } + } + + + private IReadOnlyList GetNearest(IEnumerable rs, IEnumerable r) + { + if (r.Count() > 1) + return rs.ToList(); + var input = r.OrderBy(_ => _); + var output = rs.OrderBy(_ => _); + var min = input.FirstOrDefault(); + var max = input.LastOrDefault(); + var minIndex = -1; + var maxIndex = -1; + var lastItem = -1d; + var index = 0; + foreach (var item in output) + { + if (lastItem < min && item >= min) + minIndex = index - 1; + if (lastItem <= max && item > max) + { + maxIndex = index + 1; + break; + } + index++; + lastItem = item; + } + return output.Skip(minIndex).Take(maxIndex - minIndex).ToList(); + } + } +} + diff --git a/Source/Bio/Utilities.cs b/Source/Bio/Utilities.cs new file mode 100644 index 0000000..36e7232 --- /dev/null +++ b/Source/Bio/Utilities.cs @@ -0,0 +1,367 @@ +using BruTile; +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.IO; +using NetVips; +using System.Drawing.Imaging; +using AForge; + +namespace BioLib +{ + public class ImageUtil + { + /// + /// Join by and cut by then scale to (only height an width is useful). + /// + /// tile with tile extent collection + /// canvas extent + /// jpeg output size + /// + public static Image JoinRGB24(IEnumerable> srcPixelTiles, Extent srcPixelExtent, Extent dstPixelExtent) + { + if (srcPixelTiles == null || srcPixelTiles.Count() == 0) + return null; + srcPixelExtent = srcPixelExtent.ToIntegerExtent(); + dstPixelExtent = dstPixelExtent.ToIntegerExtent(); + int canvasWidth = (int)srcPixelExtent.Width; + int canvasHeight = (int)srcPixelExtent.Height; + var dstWidth = (int)dstPixelExtent.Width; + var dstHeight = (int)dstPixelExtent.Height; + Image canvas = new Image(canvasWidth, canvasHeight); + foreach (var tile in srcPixelTiles) + { + try + { + var tileExtent = tile.Item1.ToIntegerExtent(); + var intersect = srcPixelExtent.Intersect(tileExtent); + if (intersect.Width == 0 || intersect.Height == 0) + continue; + if(tile.Item2 == null) + continue; + Image tileRawData = (Image)CreateImageFromBytes(tile.Item2, (int)tileExtent.Width, (int)tileExtent.Height,AForge.PixelFormat.Format24bppRgb); + var tileOffsetPixelX = (int)Math.Ceiling(intersect.MinX - tileExtent.MinX); + var tileOffsetPixelY = (int)Math.Ceiling(intersect.MinY - tileExtent.MinY); + var canvasOffsetPixelX = (int)Math.Ceiling(intersect.MinX - srcPixelExtent.MinX); + var canvasOffsetPixelY = (int)Math.Ceiling(intersect.MinY - srcPixelExtent.MinY); + //We copy the tile region to the canvas. + for (int y = 0; y < intersect.Height; y++) + { + for (int x = 0; x < intersect.Width; x++) + { + int indx = canvasOffsetPixelX + x; + int indy = canvasOffsetPixelY + y; + int tindx = tileOffsetPixelX + x; + int tindy = tileOffsetPixelY + y; + canvas[indx, indy] = tileRawData[tindx, tindy]; + } + } + tileRawData.Dispose(); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + + } + if (dstWidth != canvasWidth || dstHeight != canvasHeight) + { + try + { + canvas.Mutate(x => x.Resize(dstWidth, dstHeight)); + return canvas; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + } + return canvas; + } + + /// + /// Join by and cut by then scale to (only height an width is useful). + /// + /// tile with tile extent collection + /// canvas extent + /// jpeg output size + /// + public static ImageJoin16(IEnumerable> srcPixelTiles, Extent srcPixelExtent, Extent dstPixelExtent) + { + if (srcPixelTiles == null || srcPixelTiles.Count() == 0) + return null; + srcPixelExtent = srcPixelExtent.ToIntegerExtent(); + dstPixelExtent = dstPixelExtent.ToIntegerExtent(); + int canvasWidth = (int)srcPixelExtent.Width; + int canvasHeight = (int)srcPixelExtent.Height; + var dstWidth = (int)dstPixelExtent.Width; + var dstHeight = (int)dstPixelExtent.Height; + Image canvas = new Image(canvasWidth, canvasHeight); + foreach (var tile in srcPixelTiles) + { + try + { + var tileExtent = tile.Item1.ToIntegerExtent(); + var intersect = srcPixelExtent.Intersect(tileExtent); + if (intersect.Width == 0 || intersect.Height == 0) + continue; + if (tile.Item2 == null) + continue; + Image tileRawData = (Image)CreateImageFromBytes(tile.Item2, (int)tileExtent.Width, (int)tileExtent.Height, AForge.PixelFormat.Format16bppGrayScale); + var tileOffsetPixelX = (int)Math.Ceiling(intersect.MinX - tileExtent.MinX); + var tileOffsetPixelY = (int)Math.Ceiling(intersect.MinY - tileExtent.MinY); + var canvasOffsetPixelX = (int)Math.Ceiling(intersect.MinX - srcPixelExtent.MinX); + var canvasOffsetPixelY = (int)Math.Ceiling(intersect.MinY - srcPixelExtent.MinY); + //We copy the tile region to the canvas. + for (int y = 0; y < intersect.Height; y++) + { + for (int x = 0; x < intersect.Width; x++) + { + int indx = canvasOffsetPixelX + x; + int indy = canvasOffsetPixelY + y; + int tindx = tileOffsetPixelX + x; + int tindy = tileOffsetPixelY + y; + canvas[indx, indy] = tileRawData[tindx, tindy]; + } + } + tileRawData.Dispose(); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + + } + if (dstWidth != canvasWidth || dstHeight != canvasHeight) + { + try + { + canvas.Mutate(x => x.Resize(dstWidth, dstHeight)); + return canvas; + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + + } + return canvas; + } + + /// + /// Join by and cut by then scale to (only height an width is useful). + /// + /// tile with tile extent collection + /// canvas extent + /// jpeg output size + /// + public static unsafe NetVips.Image JoinVipsRGB24(IEnumerable> srcPixelTiles, Extent srcPixelExtent, Extent dstPixelExtent) + { + if (srcPixelTiles == null || !srcPixelTiles.Any()) + return null; + + srcPixelExtent = srcPixelExtent.ToIntegerExtent(); + dstPixelExtent = dstPixelExtent.ToIntegerExtent(); + int canvasWidth = (int)srcPixelExtent.Width; + int canvasHeight = (int)srcPixelExtent.Height; + + // Create a base canvas. Adjust as necessary, for example, using a transparent image if needed. + NetVips.Image canvas = NetVips.Image.Black(canvasWidth, canvasHeight, bands: 3); + + foreach (var tile in srcPixelTiles) + { + if (tile.Item2 == null) + continue; + + fixed (byte* pTileData = tile.Item2) + { + var tileExtent = tile.Item1.ToIntegerExtent(); + NetVips.Image tileImage = NetVips.Image.NewFromMemory((IntPtr)pTileData, (ulong)tile.Item2.Length, (int)tileExtent.Width, (int)tileExtent.Height, 3, Enums.BandFormat.Uchar); + + // Calculate positions and sizes for cropping and inserting + var intersect = srcPixelExtent.Intersect(tileExtent); + if (intersect.Width == 0 || intersect.Height == 0) + continue; + + int tileOffsetPixelX = (int)Math.Ceiling(intersect.MinX - tileExtent.MinX); + int tileOffsetPixelY = (int)Math.Ceiling(intersect.MinY - tileExtent.MinY); + int canvasOffsetPixelX = (int)Math.Ceiling(intersect.MinX - srcPixelExtent.MinX); + int canvasOffsetPixelY = (int)Math.Ceiling(intersect.MinY - srcPixelExtent.MinY); + + using (var croppedTile = tileImage.Crop(tileOffsetPixelX, tileOffsetPixelY, (int)intersect.Width, (int)intersect.Height)) + { + // Instead of inserting directly, we composite over the base canvas + canvas = canvas.Composite2(croppedTile, Enums.BlendMode.Over, canvasOffsetPixelX, canvasOffsetPixelY); + } + } + } + + // Resize if the destination extent differs from the source canvas size + if ((int)dstPixelExtent.Width != canvasWidth || (int)dstPixelExtent.Height != canvasHeight) + { + double scaleX = (double)dstPixelExtent.Width / canvasWidth; + double scaleY = (double)dstPixelExtent.Height / canvasHeight; + canvas = canvas.Resize(scaleX, vscale: scaleY, kernel: Enums.Kernel.Nearest); + } + + return canvas; + } + + /// + /// Join by and cut by then scale to (only height an width is useful). + /// + /// tile with tile extent collection + /// canvas extent + /// jpeg output size + /// + public static unsafe NetVips.Image JoinVips16(IEnumerable> srcPixelTiles, Extent srcPixelExtent, Extent dstPixelExtent) + { + if (srcPixelTiles == null || !srcPixelTiles.Any()) + return null; + + srcPixelExtent = srcPixelExtent.ToIntegerExtent(); + dstPixelExtent = dstPixelExtent.ToIntegerExtent(); + int canvasWidth = (int)srcPixelExtent.Width; + int canvasHeight = (int)srcPixelExtent.Height; + + // Create a base canvas. Adjust as necessary, for example, using a transparent image if needed. + Bitmap bf = new Bitmap(canvasWidth, canvasHeight, AForge.PixelFormat.Format16bppGrayScale); + NetVips.Image canvas = NetVips.Image.NewFromMemory(bf.Bytes, bf.SizeX, bf.SizeX, 1, Enums.BandFormat.Ushort); + + foreach (var tile in srcPixelTiles) + { + if (tile.Item2 == null) + continue; + + fixed (byte* pTileData = tile.Item2) + { + var tileExtent = tile.Item1.ToIntegerExtent(); + NetVips.Image tileImage = NetVips.Image.NewFromMemory((IntPtr)pTileData, (ulong)tile.Item2.Length, (int)tileExtent.Width, (int)tileExtent.Height, 1, Enums.BandFormat.Ushort); + + // Calculate positions and sizes for cropping and inserting + var intersect = srcPixelExtent.Intersect(tileExtent); + if (intersect.Width == 0 || intersect.Height == 0) + continue; + + int tileOffsetPixelX = (int)Math.Ceiling(intersect.MinX - tileExtent.MinX); + int tileOffsetPixelY = (int)Math.Ceiling(intersect.MinY - tileExtent.MinY); + int canvasOffsetPixelX = (int)Math.Ceiling(intersect.MinX - srcPixelExtent.MinX); + int canvasOffsetPixelY = (int)Math.Ceiling(intersect.MinY - srcPixelExtent.MinY); + + using (var croppedTile = tileImage.Crop(tileOffsetPixelX, tileOffsetPixelY, (int)intersect.Width, (int)intersect.Height)) + { + // Instead of inserting directly, we composite over the base canvas + canvas = canvas.Composite2(croppedTile, Enums.BlendMode.Over, canvasOffsetPixelX, canvasOffsetPixelY); + } + } + } + + // Resize if the destination extent differs from the source canvas size + if ((int)dstPixelExtent.Width != canvasWidth || (int)dstPixelExtent.Height != canvasHeight) + { + double scaleX = (double)dstPixelExtent.Width / canvasWidth; + double scaleY = (double)dstPixelExtent.Height / canvasHeight; + canvas = canvas.Resize(scaleX, vscale: scaleY, kernel: Enums.Kernel.Nearest); + } + + return canvas; + } + + public static SixLabors.ImageSharp.Image CreateImageFromBytes(byte[] rgbBytes, int width, int height, AForge.PixelFormat px) + { + if (px == AForge.PixelFormat.Format24bppRgb) + { + if (rgbBytes.Length != width * height * 3) + { + throw new ArgumentException("Byte array size does not match the dimensions of the image"); + } + + // Create a new image of the specified size + Image image = new Image(width, height); + + // Index for the byte array + int byteIndex = 0; + + // Iterate over the image pixels + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Create a color from the next three bytes + Rgb24 color = new Rgb24(rgbBytes[byteIndex], rgbBytes[byteIndex + 1], rgbBytes[byteIndex + 2]); + byteIndex += 3; + // Set the pixel + image[x, y] = color; + } + } + + return image; + } + else + if (px == AForge.PixelFormat.Format16bppGrayScale) + { + if (rgbBytes.Length != width * height * 2) + { + throw new ArgumentException("Byte array size does not match the dimensions of the image"); + } + + // Create a new image of the specified size + Image image = new Image(width, height); + + // Index for the byte array + int byteIndex = 0; + + // Iterate over the image pixels + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Create a color from the next three bytes + L16 color = new L16(BitConverter.ToUInt16(rgbBytes, byteIndex)); + byteIndex += 2; + // Set the pixel + image[x, y] = color; + } + } + + return image; + } + else + if (px == AForge.PixelFormat.Format32bppArgb) + { + if (rgbBytes.Length != width * height * 4) + { + throw new ArgumentException("Byte array size does not match the dimensions of the image"); + } + + // Create a new image of the specified size + Image image = new Image(width, height); + + // Index for the byte array + int byteIndex = 0; + + // Iterate over the image pixels + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Create a color from the next three bytes + Bgra32 color = new Bgra32(rgbBytes[byteIndex], rgbBytes[byteIndex + 1], rgbBytes[byteIndex + 2], rgbBytes[byteIndex + 3]); + byteIndex += 4; + // Set the pixel + image[x, y] = color; + } + } + + return image; + } + return null; + } + + } + +} diff --git a/Source/ImageJ.cs b/Source/ImageJ.cs index ba56d17..3c0ed90 100644 --- a/Source/ImageJ.cs +++ b/Source/ImageJ.cs @@ -1344,7 +1344,7 @@ void write(BioImage bi, ROI roi, FileStream f) */ if (roi.PointsD.Count == 4) { - PointD[] pts = roi.ImagePoints(bi.Resolutions[bi.Resolution]); + PointD[] pts = roi.ImagePoints(bi.Resolutions[bi.Level]); putFloat(RoiDecoder.XD, (float)pts[0].X); putFloat(RoiDecoder.YD, (float)pts[0].Y); //putFloat(RoiDecoder.WIDTHD, p.xpoints[1] - roi.PointsD[0]); @@ -1386,7 +1386,7 @@ void write(BioImage bi, ROI roi, FileStream f) if(type == line) //(roi instanceof Line) { - PointD[] pts = roi.ImagePoints(bi.Resolutions[bi.Resolution]); + PointD[] pts = roi.ImagePoints(bi.Resolutions[bi.Level]); //Line line = (Line)roi; putFloat(RoiDecoder.X1, (float)pts[0].X); putFloat(RoiDecoder.Y1, (float)pts[0].Y);