Get image size from traced torchscript model - c++

When exporting a yolo5-model to torchscript (using yolo5's export.py), the input image size has to be fixed using the --imgsz-argument.
How can I access this value (the expected image size) later using torch's C++-API?
I know that the information is there: the exported model is a zip-archive in disguise and, after unzipping, the size appears in extra/config.txt.
So I could theoretically use a zip-library to retrieve this information.
However, it feels hacky and might be unstable.

You can get the files in the extra/ directory in the zip archive using an overload of the torch::jit::load method:
torch::jit::ExtraFilesMap extra_files{{"config.txt", ""}};
m_module = torch::jit::load("path/to/model.torchscript", gpu_id, extra_files);
std::cout << extra_files.at("config.txt") << std::endl;
Will yield the content of config.txt, which contains JSON, i.a. the required shape of the input image.
It should be easy to troll the desired value from that string.
Note that this solution only works if the config.txt file has been added to the zip archive explicitly.
yolov5's export.py does that.

I am not a c++ expert but this code works for me. I saved two double variables on python and was able to read them in c++. I tried to memcpy straight from the string but I wasn't able to, that's why I created this sequence of char.
torch::jit::script::Module module;
torch::jit::ExtraFilesMap extra_files{{"var1", ""}, {"var2", ""}};
module = torch::jit::load("model.pt", device, extra_files);
std::string var1_str = extra_files.at("var1");
std::string var2_str = extra_files.at("var2");
double var1;
char b[] = {var1_str[0], var1_str[1], var1_str[2], var1_str[3], var1_str[4], var1_str[5], var1_str[6], var1_str[7]};
memcpy(&var1, &b, sizeof(var1));
printf("Var1 read: %.17g\n", var1);
This is the python code I used to save the variables:
traced_script_module = torch.jit.trace(model, sample)
extra_files = {'var1': np.array(var1).tobytes(), 'var2': np.array(var2).tobytes()}
traced_script_module.save('model.pt', _extra_files=extra_files)
This was based on #pasbi answer.

Related

Parsing a float number from JSON using nlohmann::json

I want to get a decimal number from a json file into a float variable by using the nlohmann::json library and allways get the output 86700.2031 instead of 86700.2. First I used a numeric json type:
{
"test": 86700.2,
}
and tried this way:
using json = nlohmann::json;
std::string config_path = "C:/config/default.json";
std::ifstream config_file(config_path);
json config = json::parse(config_file);
float test = config["test"]; //Output: 86700.2031
After that I changed the json type to string and tried this with the same outcome:
float test = std::stof(config["test"].get<std::string>()); //Output: 86700.2031
Reading integers or strings works fine. How do I read a float value properly?
"float" has very low precision. Never use float but use double instead. (One day you will know where you can go against that advice. Before that, use double). The result will not be exactly 86700.2 but very close to it.
Weird though that your library returns a string when the value clearly isn't a string. I'd check the documentation for what function you should call. You should never have to convert data yourself like that.

Save the variables of an object to then be able to initialise another object with those variables

What I am trying to achieve is this:
Let's say I have a class Score. This class has an int variable and a char* variable.
Now when I have an object Score score, I would like to be able to save the value of those variables (I guess to a file). So now this file has an int variable and a char* variable that I can then access later to create a new Score object.
So I create Score score(10, "Bert");. I either do something like score.SaveScore(); or the score gets saved when the game is over or the program exits, it doesn't matter.
Basically I am looking for the equivalent/correct way of doing this:
score.SaveScore(FILE file)
{
file.var1 = score.score;
file.var2 = score.name;
}
I realize this is probably very stupid and not done this way whatsoever! This is just me trying to explain what I am trying to achieve in the simplest way possible.
Anyway, when I run the program again, that original Score score(10, "Bert") does not exist any more. But I would like to be able to access the saved score(from file or wherever it may be) and create another Score object.
So it may look something like:
LoadScore(FILE file)
{
Score newScore(file.var1, file.var2);
}
Again, just trying to show what I am trying to achieve.
The reason why I want to be able to access the variables again is to eventually have a Scoreboard, the Scoreboard would load a bunch of scores from the file.
Then when a new score is created, it is added to the scoreboard, compared to the other scores currently in the scoreboard and inserted in the right position (like a score of 6 would go in between 9 and 4).
I feel like this was a bit long winded but I was trying to really explain myself well! Which I hope I did!
Anyway, I am not looking for someone to tell me how to do all of that.
All I am after is how to do the initial save to a file.
Thank you for any suggestions.
I would use the <fstream> library, like this;
//example values
int x=10;
float y=10.5;
const char* chars = "some random value";
string str(chars); //make string buffer for sizing
str.resize(20); //make sure its fixed size
//open a test.txt file, in the same dir for output
std::ofstream os("test.txt", std::ios::out | std::ios::binary); //make it output binary
//(char*) cast &x, sizeof(type) for values/write to file chars for x and y
os.write((char*)&x, sizeof(int)); //only sizeof(int) starting at &x
os.write((char*)&y, sizeof(float)); //cast as a char pointer
os.write(str.data(), sizeof(char)*str.size()); //write str data
os.close();
//the file test.txt will now have binary data in it
//to read it back in, just ifstream, and put that info in new containers, like this;
int in_x = 0; //new containters set to 0 for debug
float in_y = 0;
char inchar[20]; //buffer to write 20 chars to
ifstream is("test.txt", std::ios::in | std::ios::binary); //read in binary
is.read((char*)&in_x, sizeof(int)); //write to new containers
is.read((char*)&in_y, sizeof(float));
is.read((char*)&inchar, sizeof(char)*20); //write char assuming 20 size
is.close();
//outputting will show the values are correctly read into the new containers
cout << in_x << endl;
cout << in_y << endl;
cout << inchar << endl;
I realize this is probably very stupid and not done this way whatsoever!
The entire software industry was stupid enough to have it done so many times that even a special term was invented for this operation - serialization and nearly all C++ frameworks and libraries have implemented this in a various ways.
Since question is tagged with C++ I would suggest you to look at boost serialization but there are many other implementations.
Do you need that file to be readable by a human? If yes than consider, for example, XML or JSON formats.
You don't need it be readable but want it be as compact as possible? Consider google protobuf
Just start doing it and come with a more specific question(s).
As it was mentioned before, keep strings as std:string objects rather then char*
About writing/reading to/from files in C++ read about fstream

Converting 900 MB .csv into ROOT (CERN) TTree

I am new to programming and ROOT (CERN), so go easy on me. Simply, I want to convert a ~900 MB (11M lines x 10 columns) .csv file into a nicely organized .root TTree. Could someone provide the best way to go about this?
Here is an example line of data with headers (it's 2010 US census block population and population density data):
"Census County Code","Census Tract Code","Census Block Code","County/State","Block Centroid Latitude (degrees)","Block Centroid W Longitude (degrees)","Block Land Area (sq mi)","Block Land Area (sq km)","Block Population","Block Population Density (people/sq km)"
1001,201,1000,Autauga AL,32.469683,-86.480959,0.186343,0.482626154,61,126.3918241
I've pasted the what I've wrote so far below.
I particularly can’t figure out this error when running: "C:41:1: error: unknown type name ‘UScsvToRoot’”.
This may be really really stupid, but how do you read in strings in ROOT (for reading in the County/State name)? Like what is the data type? Do I just have to use char’s? I’m blanking.
#include "Riostream.h"
#include "TString.h"
#include "TFile.h"
#include "TNtuple.h"
#include "TSystem.h"
void UScsvToRoot() {
TString dir = gSystem->UnixPathName(__FILE__);
dir.ReplaceAll("UScsvToRoot.C","");
dir.ReplaceAll("/./","/");
ifstream in;
in.open(Form("%sUSPopDens.csv",dir.Data()));
Int_t countyCode,tractCode,blockCode;
// how to import County/State string?
Float_t lat,long,areaMi,areaKm,pop,popDens;
Int_t nlines = 0;
TFile *f = new TFile("USPopDens.root","RECREATE");
TNtuple *ntuple = new TNtuple("ntuple","data from csv file","countyCode:tractCode:blockCode:countyState:lat:long:areaMi:areaKm:pop:popDens");
while (1) {
in >> countyCode >> tractCode >> blockCode >> countyState >> lat >> long >> areaMi >> areaKm >> pop >> popDens;
if (!in.good()) break;
ntuple->Fill(countyCode,tractCode,blockCode,countyState,lat,long,areaMi,areaKm,pop,popDens);
nlines++;
}
in.close();
f->Write();
}`
Ok, so I am going to give this a shot, but a few comments up front:
for questions on root, you should strongly consider going to the root homepage and then to the forum. While stackoverflow is an excellent source of information, specific questions on the root framework are better suited on the root homepage.
If you are new to root, you should take a look at the tutorial page; it has many examples on how to use the various features of root.
You should also make use of the root reference guide that has documentation on all root classes.
To your code: if you look at the documentation for the class TNtuple that you are using you see that in the description it plainly says:
A simple tree restricted to a list of float variables only.
so trying to store any string into a TNtuple will not work. You need to use the more general class TTree for that.
To read your file and store the information in a tree you have two options:
either you manually define the branches and then fill the tree as you loop over the file:
void UScsvToRoot() {
TString dir = gSystem->UnixPathName(__FILE__);
dir.ReplaceAll("UScsvToRoot.C","");
dir.ReplaceAll("/./","/");
ifstream in;
in.open(Form("%sUSPopDens.csv",dir.Data()));
Int_t countyCode,tractCode,blockCode;
char countyState[1024];
Float_t lat,lon,areaMi,areaKm,pop,popDens;
Int_t nlines = 0;
TFile *f = new TFile("USPopDens.root","RECREATE");
TTree *tree = new TTree("ntuple","data from csv file");
tree->Branch("countyCode",&countyCode,"countyCode/I");
tree->Branch("tractCode",&tractCode,"tractCode/I");
tree->Branch("blockCode",&blockCode,"blockCode/I");
tree->Branch("countyState",countyState,"countyState/C");
tree->Branch("lat",&lat,"lat/F");
tree->Branch("long",&lon,"lon/F");
tree->Branch("areaMi",&areaMi,"areaMi/F");
tree->Branch("areaKm",&areaKm,"areaKm/F");
tree->Branch("pop",&pop,"pop/F");
tree->Branch("popDens",&popDens,"popDens/F");
while (1) {
in >> countyCode >> tractCode >> blockCode >> countyState >> lat >> lon >> areaMi >> areaKm >> pop >> popDens;
if (!in.good()) break;
tree->Fill();
nlines++;
}
in.close();
f->Write();
}
The command TTree::Branch basically tells root
the name of your branch
the address of the variable from which root will read the information
the format of the branch
The TBranch that contains the string information is of type C which if you look at the TTree documentation means
C : a character string terminated by the 0 character
N.B. I gave the character array a certain size, you should see yourself what size is appropriate for your data.
The other possibility that you can use is to do away with the ifstream and simply make use of the ReadFile method of the TTree which you would employ like this
#include "Riostream.h"
#include "TString.h"
#include "TFile.h"
#include "TTree.h"
#include "TSystem.h"
void UScsvToRoot() {
TString dir = gSystem->UnixPathName(__FILE__);
dir.ReplaceAll("UScsvToRoot.C","");
dir.ReplaceAll("/./","/");
TFile *f = new TFile("USPopDens.root","RECREATE");
TTree *tree = new TTree("ntuple","data from csv file");
tree->ReadFile("USPopDens.csv","countyCode/I:tractCode/I:blockCode/I:countyState/C:lat/F:lon/F:areaMi/F:areaKm/F:pop/F:popDens/F",',');
f->Write();
}
You can read the section on TTress in the root users guide on for more information; among many other things it also has an example using TTree:ReadFile.
Let me know if this helps
I think you might be better off just using root_pandas. In the comprehensive answer by #Erik you still end up specifying the variables of interest by hand (countryCode/I,…). Which has its advantages (I just list generic: you know what you'll get. error message in case an expected variable is missing, ). But on the other hand it gives you the chance of introducing typos, if you read multiple csv files you won't notice if any of them have more variables … and ultimately copying variable names and determining variable types is something a computer should be very good at.
In root_pandas your code should be something like
import pandas
df = pandas.read_csv("USPopDens.csv")
from root_pandas import readwrite
df.to_root("USPopDens.root")
I'd like to highlight one detail from Erik's answer: the fact that the TFile is created BEFORE the TTree has implications in the size of the root file resulting from the program. I was dealing with a similar problem (need to read a CSV file of ~1 GB) into a root tree and saving to a file but created the TTree first and then the TFile to store the tree. The resulting root file was a factor ~10 larger than when creating first the TTree and then the TFile.
The reason for this behavior is the difference in the compression ratio of the branches in the TTree. Basically, no compression is applied if the tree is written into memory, while a higher compression ratio is applied when writing the tree to disk.
ref: https://root-forum.cern.ch/t/ttree-compression-factor-1-00/31850/11

libjson parses integers incorrectly

I am trying to parse following JSON data (created with JSON.stringify). I am using libJSON 7. My code follows:
wstring jsonstr = _T("{\"Id\":0,\"Note\":\"\",\"Username\":\"user\",\"Password\":\"pass\",\"Enabled\":true,\"ProfileId\":\"\",\"UserAgent\":\"\",\"LastUsage\":0,\"Failures\":0,\"Abuses\":0}");
JSONNode jsondata = libjson::parse(jsonstr);
auto i = jsondata.begin();
auto num = i->as_float();
int idIsNan = _isnan(num);// Nonzero here
Now I expect that num == (double)0, however, it equals 1.#QNAN00000000000 instead. Does anyone know how to bypass this bug?
Also if I use as_int instead of as_float, it parses data correctly; so, it looks like this bug takes place for as_float method only.
I am guessing a bit here, since you don't show all the relevant code:
Your json structure is data, json seems to be the string you are parsing. So json.begin() would be the first character in that string, not the first json value. Since that first character is double quote, it cannot be parsed as a float -> you get the nonsense you see. Maybe this is what you want:
JSONNode data = libjson::parse(json);
auto i = data.begin(); //<-- data, not json!
auto num = i->as_float();
Please remember to give all the relevant code next time, that includes definition of all used variables (in this case, 'json').
PS: The json string you posted ends with an unmatched } - that might confuse the parser.

Reading in image files without specifying name

Are there any facilities in SDL or C++ that allow you to read image files in from a folder without specifying their name, like reading them in sequential order, etc.? If not are there any techniques you use to accomplish something along the same lines?
Doing something like this:
foo_ani[0] = LoadImage("Animations/foo1.png");
foo_ani[1] = LoadImage("Animations/foo2.png");
foo_ani[2] = LoadImage("Animations/foo3.png");
can become quite tedious, and a loop can't be used because the file name is specific each time.
The only way I could really think of is maybe having a string that you modify through each loop iterator and insert the loop number into the specific part of the string assuming that's how your files are labeled, and using that string as the LoadImage parameter. That seems like more work though than just doing the above.
Use boost::filesystem.
The tiny program shown here lists all files in the directory files/, matching the pattern fileN.type, where N is from 0 and upwards, unspecified.
#include <iostream>
#include <sstream>
#include <string>
#include <boost/filesystem.hpp>
using namespace std;
namespace fs = boost::filesystem;
int main(int argc, char** argv)
{
fs::path dir ("./files");
string prefix = "file";
string suffix = "type";
int i = 0;
fs::path file;
do {
stringstream ss;
ss << prefix << i++ << "." << suffix;
file = fs::path(dir / fs::path(ss.str()));
if(fs::exists(file)) {
cout << file.leaf() << " exists." << endl;
}
} while(fs::exists(file));
return 0;
}
Link with -lboost_filesystem.
boost::filesystem also provides a simple directory iterator.
For this type of situation, you would typically get a list of the filenames in the directory (with opendir/readdir or FindFirstFile/FindNextFile as appropriate), and loop on each filename in the directory. Given each filename, you can call LoadImage() and append the result to your array.
This technique doesn't require that you know the filenames ahead of time.
How about loading all files in that directory automatically?
foo_ani = LoadImages("Animations/");
Just traverse the directory given and load all files inside that fit.
Another solution, if you have several animations with different prefix is to use regular expressions. I suggest you use boost for that or std::tr1::regex, like this:
foo_ani = LoadImageSet("Animations/", std::tr1::regex("foo*.png"));
Given that you are are currently hard coding the name of the frames, I'm going to assume you know / have control over the naming scheme of the files. I'm also assuming you want them sequentially since it seems to be frames in an animation. Finally I'm assuming you know how many frames there are since you seem to have an array big enough to accommodate them all ready and waiting.
Given the names of the files presented in the question, you can't just do FindFirst / FindNext because once you get past 10 frames, they're almost certainly going to come in out of order (given the naming scheme presented).
So I think that you're right that the best way to do it is in a loop, but wrong that it's more effort than doing it by hand.
char* fname = new char[50]; // buffer big enough to hold filenames
int numFrames = 8; // or however many, you seem to know what this value should be
for(int i = 0; i < numFrames; ++i)
{
sprint(fname, "Animations/foo%d.png",(i+1));
foo_ani[i] = LoadImage(fname);
}
delete[] fname;
That's about 6 lines of code. So for animations of more than 6 frames, I'd say that was easier.