3

I am working on a simple nodejs console utility that will upload images for the training of a Custom Vision model. I do this mainly because the customvision web app won't let you tag multiple images at once.

tl;dr: How to post images into the CreateImagesFromFiles API endpoint?

I cannot figure out how to pass images that I want to upload. The documentation just defines a string as a type for one of the properties (content I guess). I tried passing path to local file, url to online file and even base64 encoded image as a string. Nothing passed.

They got a testing console (blue button "Open API testing console" at the linked docs page) but once again... it's vague and won't tell you what kind of data it actually expects.

The code here isn't that relevant, but maybe it helps...

const options = {
    host: 'southcentralus.api.cognitive.microsoft.com',
    path: `/customvision/v2.0/Training/projects/${projectId}/images/files`,
    method: 'POST',
    headers: {
        'Training-Key': trainingKey,
        'Content-Type': 'application/json'
    }
};

const data = {
    images: [
        {
            name: 'xxx',
            contents: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEklEQVR42mP8z8AARKiAkQaCAFxlCfyG/gCwAAAAAElFTkSuQmCC',
            tagIds: [],
            regions: []
        }
    ],
    tagIds: []
}

const req = http.request(options, res => {
  ...
})
req.write(JSON.stringify(data));
req.end();

Response:

BODY: { "statusCode": 404, "message": "Resource not found" }
No more data in response.
Damb
  • 14,410
  • 6
  • 47
  • 49

1 Answers1

3

I got it working using the "API testing console" feature, so I can help you to identify your issue (but sorry, I'm not expert in node.js so I will guide you with C# code)

Format of content for API

You are right, the documentation is not clear about the content the API is waiting for. I made some search and found a project in a Microsoft's Github repository called Cognitive-CustomVision-Windows, here.

What is saw is that they use a class called ImageFileCreateEntry whose signature is visible here:

public ImageFileCreateEntry(string name = default(string), byte[] contents = default(byte[]), IList<System.Guid> tagIds = default(IList<System.Guid>))

So I guessed it's using a byte[].

You can also see in their sample how they did for this "batch" mode:

// Or uploaded in a single batch 
var imageFiles = japaneseCherryImages.Select(img => new ImageFileCreateEntry(Path.GetFileName(img), File.ReadAllBytes(img))).ToList();
trainingApi.CreateImagesFromFiles(project.Id, new ImageFileCreateBatch(imageFiles, new List<Guid>() { japaneseCherryTag.Id }));

Then this byte array is serialized with Newtonsoft.Json: if you look at their documentation (here) it says that byte[] are converted to String (base 64 encoded). That's our target.

Implementation

As you mentioned that you tried with base64 encoded image, I gave it a try to check. I took my StackOverflow profile picture that I downloaded locally. Then using the following, I got the base64 encoded string:

Image img = Image.FromFile(@"\\Mac\Home\Downloads\Picto.jpg");
byte[] arr;
using (MemoryStream ms = new MemoryStream())
{
    img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
    arr = ms.ToArray();
}

var content = Convert.ToBase64String(arr);

Later on, I called the API with no tags to ensure that the image is posted and visible:

POST https://southcentralus.api.cognitive.microsoft.com/customvision/v2.2/Training/projects/MY_PROJECT_ID/images/files HTTP/1.1
Host: southcentralus.api.cognitive.microsoft.com
Training-Key: MY_OWN_TRAINING_KEY
Content-Type: application/json

{
  "images": [
    {
      "name": "imageSentByApi",
      "contents": "/9j/4AAQSkZJRgA...TOO LONG FOR STACK OVERFLOW...",
      "tagIds": [],
      "regions": []
    }
  ],
  "tagIds": []
}

Response received: 200 OK

{
  "isBatchSuccessful": true,
  "images": [{
    "sourceUrl": "imageSentByApi",
    "status": "OK",
    "image": {
      "id": "GENERATED_ID_OF_IMAGE",
      "created": "2018-11-05T22:33:31.6513607",
      "width": 328,
      "height": 328,
      "resizedImageUri": "https://irisscuprodstore.blob.core.windows.net/...",
      "thumbnailUri": "https://irisscuprodstore.blob.core.windows.net/...",
      "originalImageUri": "https://irisscuprodstore.blob.core.windows.net/..."
    }
  }]
}

And my image is here in Custom Vision portal!

image in custom vision

Debugging your code

In order to debug, you should 1st try to submit your content again with tagIds and regions arrays empty like in my test, then provide the content of the API reply

Nicolas R
  • 13,812
  • 2
  • 28
  • 57
  • Thanks! This helped me to get a step further. It indeed takes base64 string but it wasn't passing because of the tags. I have updated the code to what I am running now. It still won't pass within my nodejs app. Running the same request from their web sonsole works. I just cannot see the difference :) – Damb Nov 06 '18 at 16:17
  • You got a 404, meaning that the URL is not correct. Change "v2.0" to "v2.2" as it seems to be the last one (like in the console), maybe 2.0 endpoint has been disabled – Nicolas R Nov 06 '18 at 16:19
  • Nice catch. I knew it's a wrong request but couldn't figure out why as I copied the URLs from the api directly. Was totally blind to that version difference there. Unfortunately, even after version change, there is still the 404 response. There must be something wrong with the way how I build the request in nodejs. I'll try a different way. – Damb Nov 06 '18 at 17:07
  • 2 other strange things in your code / request: https is not mentioned. And the quotes used in the "path" field look strange here – Nicolas R Nov 06 '18 at 17:10
  • The strange quotes are needed to interpret the ${projectId} part in the string and the https part is missing because nodejs wants it that way so it should be ok :) That's why the issue is so annoying. This thing is no magic. It's a simple http request to an API. I was hoping something would point out a thing I missed (like you did with the v2.2), but there doesn't seem to be any major flaw in the code itself :) – Damb Nov 06 '18 at 19:05
  • Ok, I'm back home so I will quickly try if I can implement it in Node.js – Nicolas R Nov 06 '18 at 22:16
  • It's working on my side in Node.js, if... if I set https! I used `const http = require('https');`, not `const http = require('http');` ! With http I got 404, with https I got 200 – Nicolas R Nov 06 '18 at 22:40