Image Sampler Node Update

Hi All,

I am testing out another workflow using some hand drawings, I have a thought of using the image sampler but my skills in Visual Studio are not great.

The old ImageSamplerNode visual studio code has some updates needed. I was wondering if anyone knew where I would start looking for information on what to change. 

There are "type or namespace" errors in lines

using Bentley.GenerativeComponents.Nodes;

[Replicatable, ParentNodeScope] int ArrPtsX,

I am making a guess but would this be a UtilityNode as it doesn't produce geometry, it sort of functions like the calculator node in the sample addin?

 

Any pointers would be appreciated.

Thanks

Wayne

The script in full is:

 

// Source adapted from Ralf Lindemann's imageUVSampler
// which had been based code by Marc Hoppermann (www.21cd.org)


using System;
using System.Drawing;
using System.Collections.Generic;
//using System.Windows.Forms;
//using Bentley.Geometry;
using Bentley.GenerativeComponents;
using Bentley.GenerativeComponents.Features;
using Bentley.GenerativeComponents.GCScript.NameScopes;
using Bentley.GenerativeComponents.GeneralPurpose;
using Bentley.GenerativeComponents.GCScript;
using Bentley.GenerativeComponents.MicroStation;
using Bentley.GenerativeComponents.View;
using Bentley.Interop.MicroStationDGN;
using Bentley.GenerativeComponents.GCScript.GCTypes;
using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
using Bentley.GenerativeComponents.Nodes;

namespace ImageSampler
{
[GCNamespace("User")] // This custom attribute specifies that, when this node type is loaded into GC, it will be put into
// the GC namespace "User". (So, within GC, this node type's full name will be "User.ImageSamplerNode".)

[NodeTypePaletteCategory("Utility")] // The NodeTypePaletteCategory attribute lets us specify where this
// Calculator node type will appear within GC's Node Types dialog.
// So, it will appear within a group named "Sample Add-In".

[NodeTypeIcon("Resources/ImageSamplerNode.png")] // The NodeTypeIcon attribute lets us specify the graphical image (icon)
// that will appear on the Calculator node type's button within GC's Node
// Types dialog.


public class ImageSampler: Feature
{
/// <summary>ImageSampler reading pixels</summary>
[Technique]
public NodeUpdateResult ReadPixels
(
NodeUpdateContext updateContext,
[Replicatable, ParentNodeScope] int ArrPtsX,
[Replicatable] int ArrPtsY,
[Replicatable] int GridH,
[Replicatable] int GridV,
string ImagePath,
[Out] ref int Width,
[Out] ref int Height,
[Out] ref int R,
[Out] ref int G,
[Out] ref int B,
[Out] ref double Grayscale
)
{
double x = ArrPtsX;
double y = ArrPtsY;
if(false == System.IO.File.Exists(ImagePath))
{
Width = 0;
Height = 0;
R = 0;
G = 0;
B = 0;
Grayscale = 0.0;
return new NodeUpdateResult.IncompleteInputs("ImagePath");
}

Bitmap myBitmap = new Bitmap(ImagePath);
Width = myBitmap.Width;
Height = myBitmap.Height;

// this logic needs review
double stepX = Width / GridH;
double stepY = Height / GridV;
double pixelPosX = stepX * x;
double pixelPosY = stepY * y;

// get pixel information
Color pixelValue = myBitmap.GetPixel(Convert.ToInt32(pixelPosX), Convert.ToInt32(pixelPosY));

R = pixelValue.R;
G = pixelValue.G;
B = pixelValue.B;

// calculate gray scale
Grayscale = (0.3 * R + 0.59 * G + 0.11 * B) / 255.0;

return NodeUpdateResult.Success;
}

}
}

Parents
  • Anik is right, probably your best source of information is the latest version of GC's sample solution.

    Looking at your source code, I see the following two issues, which would prevent it from compiling under the latest released version:

    The namespace Bentley.GenerativeComponents.Nodes is now named Bentley.GenerativeComponents.UtilityNodes.

    The attribute ParentNodeScope is now named DgnModelProvider. But, you should just delete that attribute, anyway. Your input parameter, ArrPtsX, is just a basic 'int' type, so it would never carry any information about a parent node scope or a DGN model.

    HTH

    Answer Verified By: Wayne Dickerson 

  • Thanks Jeff,

    I suspected the namespace of UtilityNode. 

    Thanks for the tips on the ParentNodeScope.

    I had been looking at the Calculator script and trying to adapt that script as I thought it would be a similar node not generating any geometry in the file.

    What I was a bit confused about was in the Calculator script it lists

    public class Calculator: UtilityNode

    which makes sense to me as it is creating a utilitynode.

    In the imagesampler code it uses:

    public class ImageSampler: Feature

    Which is more inline with the SimpleLine example. 

    I assume it is related to this comment.

    // One of the fundamental differences between the Feature-based node architecture and the Node-based node architecture
    // is that, in the former, reflection is used to extract the technique names, the documentation, and the names and types
    // of the inputs and outputs, directly from the compiled C# class. GC provides a number of custom attributes to provide
    // more information to the that reflection process.

    I need to do some more C# work.

    Is there any Bentley Documentation on things like the attributes or namespaces used in GC?

    But for now the Image Sampler is up and running, now to make some adjustments to suit the workflow I have in mind.

    Thanks

    Wayne

Reply
  • Thanks Jeff,

    I suspected the namespace of UtilityNode. 

    Thanks for the tips on the ParentNodeScope.

    I had been looking at the Calculator script and trying to adapt that script as I thought it would be a similar node not generating any geometry in the file.

    What I was a bit confused about was in the Calculator script it lists

    public class Calculator: UtilityNode

    which makes sense to me as it is creating a utilitynode.

    In the imagesampler code it uses:

    public class ImageSampler: Feature

    Which is more inline with the SimpleLine example. 

    I assume it is related to this comment.

    // One of the fundamental differences between the Feature-based node architecture and the Node-based node architecture
    // is that, in the former, reflection is used to extract the technique names, the documentation, and the names and types
    // of the inputs and outputs, directly from the compiled C# class. GC provides a number of custom attributes to provide
    // more information to the that reflection process.

    I need to do some more C# work.

    Is there any Bentley Documentation on things like the attributes or namespaces used in GC?

    But for now the Image Sampler is up and running, now to make some adjustments to suit the workflow I have in mind.

    Thanks

    Wayne

Children
  • Hi Wayne,

    Good to know that ImageSampler is working for now.
    Can you please share the modified script? 

  • Yeah sure sorry I should have posted it here:

    I ended up creating a new C# file inside the Sample Solution and the code is:

    There are still some redundant references in the top part which could be removed. I also renamed it so I could identify my updates.

    using System;
    using System.Drawing;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    using Bentley.GenerativeComponents.Features;

    namespace SampleAddIn
    {
    [GCNamespace("User")] // This custom attribute specifies that, when this node type is loaded into GC, it will be put into
    // the GC namespace "User". (So, within GC, this node type's full name will be "User.ImageSamplerNode".)

    [NodeTypePaletteCategory("Sample Add-In")] // The NodeTypePaletteCategory attribute lets us specify where this
    // Calculator node type will appear within GC's Node Types dialog.
    // So, it will appear within a group named "Sample Add-In".

    [NodeTypeIcon("Resources/ImageSamplerNode.png")] // The NodeTypeIcon attribute lets us specify the graphical image (icon)
    // that will appear on the Calculator node type's button within GC's Node
    // Types dialog.


    public class WayneImageSampler: Feature
    {
    /// <summary>ImageSampler reading pixels</summary>
    [Technique]
    public NodeUpdateResult ReadPixels
    (
    NodeUpdateContext updateContext,
    [Replicatable] int ArrPtsX,
    [Replicatable] int ArrPtsY,
    [Replicatable] int GridH,
    [Replicatable] int GridV,
    string ImagePath,
    [Out] ref int Width,
    [Out] ref int Height,
    [Out] ref int R,
    [Out] ref int G,
    [Out] ref int B,
    [Out] ref double Grayscale
    )
    {
    double x = ArrPtsX;
    double y = ArrPtsY;
    if (false == System.IO.File.Exists(ImagePath))
    {
    Width = 0;
    Height = 0;
    R = 0;
    G = 0;
    B = 0;
    Grayscale = 0.0;
    return new NodeUpdateResult.IncompleteInputs("ImagePath");
    }

    Bitmap myBitmap = new Bitmap(ImagePath);
    Width = myBitmap.Width;
    Height = myBitmap.Height;

    // this logic needs review
    double stepX = Width / GridH;
    double stepY = Height / GridV;
    double pixelPosX = stepX * x;
    double pixelPosY = stepY * y;

    // get pixel information
    Color pixelValue = myBitmap.GetPixel(Convert.ToInt32(pixelPosX), Convert.ToInt32(pixelPosY));

    R = pixelValue.R;
    G = pixelValue.G;
    B = pixelValue.B;

    // calculate gray scale
    Grayscale = (0.3 * R + 0.59 * G + 0.11 * B) / 255.0;

    return NodeUpdateResult.Success;
    }

    }
    }

  • Is there any Bentley Documentation on things like the attributes or namespaces used in GC?

    Unfortunately, no. We're aware that this is a significant deficiency in GC's documentation.

  • Thanks Jeff,

    Appreciate the responses.

    I have been experimenting with some changes to the image sampler. The current code doesn't look that complex which is a nice way to get into it.

    I have added some basic math to sample the centre point of the sample grid, along with some check boxes (bools). Also added some average tests so I could do a sample area.

    It looked quite easy to add additional outputs so I added a few to test my math and see what it was doing along the way.

    I have been using the attached image as a sample image, gives a good change in samples for testing centre etc. It looks like there is maybe a minor 1-2 digit difference on some values but that could be the image.

     

    A Question for you or anyone on the forum.

    One item I would like is not to have to input for the GridH and GridV. Is there a way of passing into visual studio code the count of ArrPtsX and ArrPtsY? This could then be used as the GridH and GridV.

    Another item is the Image path, how do you implement the three dots so you can browse to the file location?

     

    My working code below with some comments (mainly for myself but you can see what I am testing)

    Thanks again

    Wayne

    example outputs:

    using System;
    using System.Drawing;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Bentley.GenerativeComponents;
    using Bentley.GenerativeComponents.GCScript;
    using Bentley.GenerativeComponents.GCScript.GCTypes;
    using Bentley.GenerativeComponents.GCScript.NameScopes;
    using Bentley.GenerativeComponents.GCScript.ReflectedNativeTypeSupport;
    using Bentley.GenerativeComponents.GeneralPurpose;
    using Bentley.GenerativeComponents.UtilityNodes;
    using Bentley.GenerativeComponents.View;
    using Bentley.GenerativeComponents.Features;
    using Bentley.GenerativeComponents.ScriptEditor;
    
    namespace SampleAddIn
    {
        [GCNamespace("User")]                                   // This custom attribute specifies that, when this node type is loaded into GC, it will be put into
                                                                // the GC namespace "User". (So, within GC, this node type's full name will be "User.ImageSamplerNode".)
    
        [NodeTypePaletteCategory("Sample Add-In")]              // The NodeTypePaletteCategory attribute lets us specify where this
                                                                // Calculator node type will appear within GC's Node Types dialog.
                                                                // So, it will appear within a group named "Sample Add-In".
    
        [NodeTypeIcon("Resources/ImageSamplerNode.png")]        // The NodeTypeIcon attribute lets us specify the graphical image (icon)
                                                                // that will appear on the Calculator node type's button within GC's Node
                                                                // Types dialog.
    
    
        public class WayneImageSampler: Feature
        {
            /// <summary>ImageSampler reading pixels</summary>
            [Technique]
            public NodeUpdateResult ReadPixels
            (
                NodeUpdateContext updateContext,
                [Replicatable] int ArrPtsX,
                [Replicatable] int ArrPtsY,
                [Replicatable] int GridH,
                [Replicatable] int GridV,
                // Maybe add some checkboxes to decide if we wanted to calculate the centre pixel or an average
                bool CentrePt,
                bool AverageResults,
                
                //FolderPathBrowser ImagePath = new FolderPathBrowser.get
                //string  ImagePath = FolderPathBrowser,
                string ImagePath,
                [Out] ref int Width,
                [Out] ref int Height,
                [Out] ref int R,
                [Out] ref int G,
                [Out] ref int B,
                [Out] ref double Grayscale,
                // As a check lets output the sample pixel for each
                [Out] ref int SampleX,
                [Out] ref int SampleY
            )
        {
            double x = ArrPtsX;
            double y = ArrPtsY;
            
            if (false == System.IO.File.Exists(ImagePath))
            {
               Width = 0;
               Height = 0;
               R = 0;
               G = 0;
               B = 0;
               Grayscale = 0.0;
               return new NodeUpdateResult.IncompleteInputs("ImagePath");
            }
    
            Bitmap myBitmap = new Bitmap(ImagePath);
            Width = myBitmap.Width;
            Height = myBitmap.Height;
    
            // this logic needs review
            // Assume that it would be best to have the GridH and GridV match the count of the 
            // series(*,*,*) that is used for ArrPtsX and ArrPtsY. Then you would get a step across the image divided by the series count.
            // If this count coul be passed into this process they wouldn't need to be input by the user?
            double stepX = Width / GridH;
            double stepY = Height / GridV;
            double pixelPosX = stepX * x;
            double pixelPosY = stepY * y;
                // This pixel position is at the start of the sample square ie stepY * 0? Does that work pixel 0
                // This could be adjusted to find the centre of the sample square.
                if (CentrePt)
                {
                    pixelPosX = pixelPosX + stepX / 2;
                    pixelPosY = pixelPosY + stepY / 2;
                }
    
    
                // Could you get an average of the sample square of pixels
                // It could be done with a loop of the stepX and stepY
                // Then add all teh pixelValues together and divide by stepX*stepY
                // This could take a while for a large image. 4000*3000 would be 12Million calculations
                // Maybe a percentage say 10% of the pixels would be ok.
                // or maybe better is like photoshop get a 4x4 sample area of pixels. We could even start with a 5 samples,
                // one in the centre and then offset say 2 pixels each way
    
                if (AverageResults)
                {
                    double pixelPosx1 = pixelPosX;
    
                    double pixelPosx2 = pixelPosX + 2;
                    if (pixelPosx2 > Width)
                    {
                        pixelPosx2 = Width;
                    }
                    double pixelPosx3 = pixelPosX - 2;
                    if (pixelPosx3 < 0)
                    {
                        pixelPosx3 = 0;
                    }
                    double pixelPosy1 = pixelPosY;
    
                    double pixelPosy2 = pixelPosY + 2;
                    if (pixelPosy2 > Height)
                    {
                        pixelPosy2 = Height;
                    }
                    double pixelPosy3 = pixelPosY - 2;
                    if (pixelPosy3 < 0)
                    {
                        pixelPosy3 = 0;
                    }
    
    
                    Color pixelValue1 = myBitmap.GetPixel(Convert.ToInt32(pixelPosx1), Convert.ToInt32(pixelPosy1));
                    Color pixelValue2 = myBitmap.GetPixel(Convert.ToInt32(pixelPosx2), Convert.ToInt32(pixelPosy1));
                    Color pixelValue3 = myBitmap.GetPixel(Convert.ToInt32(pixelPosx3), Convert.ToInt32(pixelPosy1));
                    Color pixelValue4 = myBitmap.GetPixel(Convert.ToInt32(pixelPosx1), Convert.ToInt32(pixelPosy2));
                    Color pixelValue5 = myBitmap.GetPixel(Convert.ToInt32(pixelPosx1), Convert.ToInt32(pixelPosy3));
    
                    R = (pixelValue1.R + pixelValue2.R + pixelValue3.R + pixelValue4.R + pixelValue5.R) / 5;
                    G = (pixelValue1.G + pixelValue2.G + pixelValue3.G + pixelValue4.G + pixelValue5.G) / 5;
                    B = (pixelValue1.B + pixelValue2.B + pixelValue3.B + pixelValue4.B + pixelValue5.B) / 5;
    
    
                }
                else
                {
                    // get pixel information
                    Color pixelValue = myBitmap.GetPixel(Convert.ToInt32(pixelPosX), Convert.ToInt32(pixelPosY));
    
                    R = pixelValue.R;
                    G = pixelValue.G;
                    B = pixelValue.B;
                }
            // calculate gray scale
            Grayscale = (0.3 * R + 0.59 * G + 0.11 * B) / 255.0;
    
            // for checking the sample point
            SampleX = (Convert.ToInt32(pixelPosX));
            SampleY = (Convert.ToInt32(pixelPosY));
    
                return NodeUpdateResult.Success;
        }
    
    }
    }