This project has moved and is read-only. For the latest updates, please go here.

Connected Components area threshold

Aug 22, 2016 at 6:29 AM
Edited Aug 23, 2016 at 7:38 AM
I'm very new to ImageMagick so I apologise if this is really simple; I want to use Connected Components to identify whether objects exist in an image, and create a .png representing this. In the Command Line, I am able to achieve this through:
magick convert compared.png -define connected-components:verbose=true -define connected-components:area-threshold=200 -connected-components 4 -auto-level connectedcomponents.png
... the output image filters out all components with less than 200 pixels in area. I am trying to recreate this in C# Magick.NET and having some issues finding the right way to do so. I am currently trying this:
Bitmap connectedComponents;

using (MagickImage compared = new MagickImage(filepath)) // filepath is a string
{ 
    compared.Settings.SetDefine(MagickFormat.Png, "connected-components:area-threshold", "200");
    IEnumerable<ConnectedComponent> check = compared.ConnectedComponents(4);
    compared.AutoLevel();

    connectedComponents = compared.ToBitmap(System.Drawing.Imaging.ImageFormat.Png);
}
connectedComponents.Save("..\\..\\Test Images\\connectedcomponents.png", System.Drawing.Imaging.ImageFormat.Png);
I initially thought this didn't work because my "connected-components:area-threshold" was incorrect, but it's probably because "compared" isn't changing. How can I factor this change on to my image?
Aug 23, 2016 at 8:17 AM
I may have a solution but I don't think it's a particularly clean one.

I originally thought that using SetDefine(MagickFormat.Png, ..., ...) would prepare the software to use what is stored as a .png file. But if I just call SetDefine(..., ...), and applied the settings whilst reading in the image I'd have some success. After calling ConnectedComponents() the output image would look correct, filtered down as per the area threshold. However, the IEnumerable that was returned would still have the amount of elements as if it hadn't been filtered. To correct this, I ran the filtered image back through ConnectedComponents() so I could get 'the number of elements in the input image'.

Code is as follows:
MagickReadSettings settings = new MagickReadSettings();
settings.SetDefine("connected-components:area-threshold", "200"); // area > 200px
using (MagickImage compared = new MagickImage(filepath, settings))
{
    // Find the connected components
    IEnumerable<ConnectedComponent> check = compared.ConnectedComponents(4);
    // check.Count = 1596, 'compared' has changed
    check = compared.ConnectedComponents(4);
    // check.Count = 11

    compared.AutoLevel();

    compared.Write("..\\..\\Test Images\\connectedcomponents.png");
}
For now, it works enough for me to progress but because I'm calling the same thing twice and don't understand why this happens, it doesn't feel right.

I also ran some tests to compare against the Command Line version of Connected Components and area thresholding, and I noticed that the IEnumerable<ConnectedComponent> in Magick.NET returned 11 components while the command line returned 8. In .NET, the first 8 ConnectedComponent elements match the Command Line exactly. Every other element has its surrounding rectangle listed as starting at 1280, 720 (my image is 1280x720) with rectangle size -1219, -719, encompassing the whole image backwards. I can just ignore these values, but I figured I should mention it. I wouldn't have expected something like that, but it might be some weird side-effect of calling ConnectedComponents() twice in succession.
Aug 23, 2016 at 7:45 PM
Edited Aug 24, 2016 at 1:38 PM
You should use the following method:
using (MagickImage compared = new MagickImage(filepath))
{
  compared.Settings.SetDefine("connected-components:area-threshold", "200");
  compared.ConnectedComponents(4);
}
But I think I should create something like this:
using (MagickImage compared = new MagickImage(filepath))
{
  ConnectedComponentsSettings settings = new ConnectedComponentsSettings()
  {
    AreaThreshold = 200,
    Connectivity = 4
  };
  compared.ConnectedComponents(settings);
}
This will make it a lot easier for you. I don't know why you get a different result on the command line. Do you have an example image that I could use to reproduce your issue? Feel free to contact me through CodePlex if you cannot share it publicly.
Aug 24, 2016 at 4:31 AM
It's a bit strange, when I use the method your way (which I initially tried) I was able to perform call ConnectedComponents(4) and get the image, but the area threshold wouldn't be applied. I only have it applied when I set it via new MagickImage(filepath, settings).

Your second Class does seem more intuitive to me. With SetDefine you can set any name, so you have no way to verify that your name was valid; a Class like that would remove any confusion there and you'd be able to see exactly what settings you can use.

The images would probably be fine to post here but I've sent them to you directly just to play it safe.
Aug 24, 2016 at 1:34 PM
I got your e-mail, will take a look at it tomorrow. I just realized that my first example would not work. You will need to call image.SetArtifact instead. And looking at the code I decided to add the class from my previous post. This will be available in the next release. I can publish a minor release if this is urgent for you.
Aug 25, 2016 at 4:11 AM
Hi dlemstra,

Thanks for your help. I really appreciate you doing this. The project is getting urgent but I can use my work-arounds for now and I wouldn't want you to go out of your way for me.

I'll confirm that image.SetArtifact does work. I don't have to read in my images with the settings applied. However I still need to called image.ConnectedComponents twice to get the correct IEnumerable returned. It feels like it returns the components of the original image whilst modifying image to become correct. Not sure if you're observing that - I'm probably doing something wrong.

Thanks,
Connor

Aug 25, 2016 at 3:55 PM
Edited Aug 25, 2016 at 3:55 PM
It turns out that my implementation of getting the components did not have the correct implementation. I just publish a new version of Magick.NET to resolve this. This new version also includes the new ConnectedComponentsSettings class. Please let me know if that resolves your issue.
Aug 26, 2016 at 2:39 AM
Hi dlemstra,

I'm happy to report that the software now works exactly as planned. This extends to both the image.SetArtifact and ConnectedComponentsSettings approaches. Thankyou very much for your help!
Aug 26, 2016 at 10:21 AM
Edited Aug 26, 2016 at 10:24 AM
Now that I've tested for the day, I've found something that might still be off. I've found I'm getting ImageMagick.MagickResourceLimitErrorException on my call to image.ConnectedComponents(settings). This line is run from a BackgroundWorker that periodically calls this line. Exception message is ""memory allocation failed `' @ error/vision.c/ConnectedComponentsImage/220". Stack trace is:
at ImageMagick.NativeInstance.CheckException(IntPtr exception, IntPtr result)
at ImageMagick.MagickImage.NativeMagickImage.ConnectedComponents(Int32 connectivity, IntPtr& objects)
at ImageMagick.MagickImage.ConnectedComponents(ConnectedComponentsSettings settings)
at [my call on image.ConnectedComponents(settings)]
at Project.Form1.objectLeftBackgroundWorker_DoWork(Object sender, DoWorkEventArgs e) in [path]:line 574 (<- the calling method)

I've made sure to call MagickImage.Dispose() on all my ImageMagick files when not in use. Is this something on my end, or at least something I can act upon? Maybe I'm making my calls too quickly. Since you've just added in the ConnectedComponentsSettings code, I figured I should report this at least.
Aug 26, 2016 at 11:22 AM
You are most likely getting this error because you are running out of memory. Do you see the memory increase while your program is running? It is possible that ImageMagick/Magick.NET is leaking memory. Can you create a small example program that produces this issue? Maybe you can reproduce this by loading an image and doing the operation on it in a 'never ending loop'?
Aug 29, 2016 at 2:04 AM
Edited Aug 29, 2016 at 5:46 AM
Hi dlemstra, I'll create a sample program when I get the chance. The memory is definitely increasing while the program runs. I can confirm, via process of elimination, that the memory leak is likely connected to image.ConnectedComponents(), and happens whether I call the method via ConnectedComponents(int) or ConnectedComponents(ConnectedComponentsSettings). It does not appear to be connected to ConnectedComponentsSettings, as I see the same behaviour when using an older version of the software, before you'd written this in.

Edit: I have created a sample program and emailed it to you again. The urgency behind this is picking up, so I really appreciate how responsive you've been.
Aug 29, 2016 at 10:52 AM
Thank you so much for this program. It made it really easy to track down the issue. It turns out that the ImageMagick code of ConnectedComponents forgets to decrement a reference counter. When you Dispose the image it cannot delete it because it still thinks that something is using it. I will publish a new release later today to resolve this issue. I have to fix something else first so it might take a couple of hours before the release is available.
Aug 30, 2016 at 8:32 AM
The new release has been published can you let me know if that resolves your memory issue?
Aug 30, 2016 at 9:55 AM
I downloaded the update this morning and the memory issues I was having seem to be resolved. Once again, thank you so much, you're a life saver! I'm hoping all goes well from here, but I will let you know if I cross any other irregularities related to ConnectedComponents.