libvmaf/src/read_json_model.c (468 lines of code) (raw):
/**
*
* Copyright 2016-2020 Netflix, Inc.
*
* Licensed under the BSD+Patent License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSDplusPatent
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "libvmaf/model.h"
#include "model.h"
#include "pdjson.h"
#include "svm.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define MAX_FEATURE_COUNT 64 //FIXME
#define MAX_KNOT_COUNT 10 //FIXME
static int parse_feature_opts_dicts(json_stream *s, VmafModel *model)
{
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_OBJECT)
return -EINVAL;
if (i >= MAX_FEATURE_COUNT)
return -EINVAL;
while (json_peek(s) != JSON_OBJECT_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
char *key = strdup(json_get_string(s, NULL));
if (!key) return -ENOMEM;
if (json_peek(s) == JSON_NUMBER) {
const char *val = json_get_string(s, NULL);
const uint64_t flags = VMAF_DICT_DO_NOT_OVERWRITE |
VMAF_DICT_NORMALIZE_NUMERICAL_VALUES;
int err = vmaf_dictionary_set(&(model->feature[i].opts_dict),
key, val, flags);
free(key);
if (err) return err;
} else if (json_peek(s) == JSON_TRUE || json_peek(s) == JSON_FALSE) {
const uint64_t flags = VMAF_DICT_DO_NOT_OVERWRITE;
int err;
if (json_peek(s) == JSON_TRUE) {
err = vmaf_dictionary_set(&(model->feature[i].opts_dict),
key, "true", flags);
} else {
err = vmaf_dictionary_set(&(model->feature[i].opts_dict),
key, "false", flags);
}
free(key);
if (err) return err;
} else if (json_peek(s) == JSON_STRING) {
const char *val = json_get_string(s, NULL);
const uint64_t flags = VMAF_DICT_DO_NOT_OVERWRITE;
int err = vmaf_dictionary_set(&(model->feature[i].opts_dict),
key, val, flags);
free(key);
if (err) return err;
} else {
return -EINVAL; //TODO
}
json_skip(s);
}
i++;
json_skip_until(s, JSON_OBJECT_END);
}
json_skip_until(s, JSON_ARRAY_END);
return 0;
}
static int parse_intercepts(json_stream *s, VmafModel *model)
{
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
model->intercept = json_get_number(s);
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
if (i >= MAX_FEATURE_COUNT)
return -EINVAL;
model->feature[i++].intercept = json_get_number(s);
}
return 0;
}
static int parse_slopes(json_stream *s, VmafModel *model)
{
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
model->slope = json_get_number(s);
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
if (i >= MAX_FEATURE_COUNT)
return -EINVAL;
model->feature[i++].slope = json_get_number(s);
}
return 0;
}
static int parse_knots_list(struct json_stream *s, struct VmafModel *model, unsigned idx)
{
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
if (i >= 2)
return -EINVAL;
if (i == 0)
model->score_transform.knots.list[idx].x = json_get_number(s);
else
model->score_transform.knots.list[idx].y = json_get_number(s);
i++;
}
return 0;
}
static int parse_knots(json_stream *s, struct VmafModel *model)
{
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
if (i >= MAX_KNOT_COUNT)
return -EINVAL;
int err = parse_knots_list(s, model, i);
if (err) return err;
json_skip_until(s, JSON_ARRAY_END);
i++;
}
model->score_transform.knots.n_knots = i;
model->score_transform.knots.enabled = true;
return 0;
}
static int append_feature_name(VmafModel *model, const char *name,
unsigned index)
{
if (index >= MAX_FEATURE_COUNT)
return -EINVAL;
model->feature[index].name = strdup(name);
if (!model->feature[index].name)
return -ENOMEM;
return 0;
}
static int parse_feature_names(json_stream *s, VmafModel *model)
{
int err = 0;
unsigned i = 0;
while (json_peek(s) != JSON_ARRAY_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *name = json_get_string(s, NULL);
err = append_feature_name(model, name, i++);
if (err) return err;
model->n_features++;
}
json_skip_until(s, JSON_ARRAY_END);
return 0;
}
static int parse_score_transform(json_stream *s, VmafModel *model)
{
model->score_transform.enabled = false;
while (json_peek(s) != JSON_OBJECT_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *key = json_get_string(s, NULL);
if (!strcmp(key, "enabled")) {
if (json_peek(s) != JSON_TRUE && json_peek(s) != JSON_FALSE)
return -EINVAL;
model->score_transform.enabled = (json_next(s) == JSON_TRUE);
continue;
}
if (!strcmp(key, "p0")) {
if (json_peek(s) == JSON_NULL) {
model->score_transform.p0.enabled = false;
} else if (json_next(s) == JSON_NUMBER) {
model->score_transform.p0.enabled = true;
model->score_transform.p0.value = json_get_number(s);
} else {
return -EINVAL;
}
continue;
}
if (!strcmp(key, "p1")) {
if (json_peek(s) == JSON_NULL) {
model->score_transform.p1.enabled = false;
} else if (json_next(s) == JSON_NUMBER) {
model->score_transform.p1.enabled = true;
model->score_transform.p1.value = json_get_number(s);
} else {
return -EINVAL;
}
continue;
}
if (!strcmp(key, "p2")) {
if (json_peek(s) == JSON_NULL) {
model->score_transform.p2.enabled = false;
} else if (json_next(s) == JSON_NUMBER) {
model->score_transform.p2.enabled = true;
model->score_transform.p2.value = json_get_number(s);
} else {
return -EINVAL;
}
continue;
}
if (!strcmp(key, "knots")) {
if (json_peek(s) == JSON_NULL) {
model->score_transform.knots.enabled = false;
model->score_transform.knots.n_knots = 0;
} else if (json_next(s) == JSON_ARRAY) {
int err = parse_knots(s, model);
if (err) return err;
json_skip_until(s, JSON_ARRAY_END);
} else {
return -EINVAL;
}
continue;
}
if (!strcmp(key, "out_lte_in")) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *out_lte_in = json_get_string(s, NULL);
if (!strcmp(out_lte_in, "true"))
model->score_transform.out_lte_in = true;
continue;
}
if (!strcmp(key, "out_gte_in")) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *out_gte_in = json_get_string(s, NULL);
if (!strcmp(out_gte_in, "true"))
model->score_transform.out_gte_in = true;
continue;
}
json_skip(s);
}
return 0;
}
static int parse_libsvm_model(json_stream *s, VmafModel *model)
{
size_t sz;
const char *libsvm_model = json_get_string(s, &sz);
model->svm = svm_parse_model_from_buffer(libsvm_model, sz);
if (!model->svm) return -ENOMEM;
return 0;
}
static int parse_model_dict(json_stream *s, VmafModel *model,
enum VmafModelFlags flags)
{
if (json_next(s) != JSON_OBJECT)
return -EINVAL;
while (json_peek(s) != JSON_OBJECT_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *key = json_get_string(s, NULL);
if (!strcmp(key, "score_transform")) {
if (json_next(s) != JSON_OBJECT)
return -EINVAL;
int err = parse_score_transform(s, model);
if (err) return err;
if (!model->score_transform.enabled &&
(flags & VMAF_MODEL_FLAG_ENABLE_TRANSFORM)) {
model->score_transform.enabled = true;
}
json_skip_until(s, JSON_OBJECT_END);
continue;
}
if (!strcmp(key, "model_type")) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *model_type = json_get_string(s, NULL);
if (!strcmp(model_type, "RESIDUEBOOTSTRAP_LIBSVMNUSVR"))
model->type = VMAF_MODEL_RESIDUE_BOOTSTRAP_SVM_NUSVR;
else if (!strcmp(model_type, "BOOTSTRAP_LIBSVMNUSVR"))
model->type = VMAF_MODEL_BOOTSTRAP_SVM_NUSVR;
else if (!strcmp(model_type, "LIBSVMNUSVR"))
model->type = VMAF_MODEL_TYPE_SVM_NUSVR;
else
return -EINVAL;
continue;
}
if (!strcmp(key, "norm_type")) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *norm_type = json_get_string(s, NULL);
if (!strcmp(norm_type, "linear_rescale"))
model->norm_type = VMAF_MODEL_NORMALIZATION_TYPE_LINEAR_RESCALE;
else if (!strcmp(norm_type, "none"))
model->norm_type = VMAF_MODEL_NORMALIZATION_TYPE_NONE;
else
return -EINVAL;
continue;
}
if (!strcmp(key, "score_clip")) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
if (!(flags & VMAF_MODEL_FLAG_DISABLE_CLIP)) {
model->score_clip.enabled = true;
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
model->score_clip.min = json_get_number(s);
if (json_next(s) != JSON_NUMBER)
return -EINVAL;
model->score_clip.max = json_get_number(s);
}
json_skip_until(s, JSON_ARRAY_END);
continue;
}
if (!strcmp(key, "slopes")) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
int err = parse_slopes(s, model);
if (err) return err;
json_skip_until(s, JSON_ARRAY_END);
continue;
}
if (!strcmp(key, "intercepts")) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
int err = parse_intercepts(s, model);
if (err) return err;
json_skip_until(s, JSON_ARRAY_END);
continue;
}
if (!strcmp(key, "feature_names")) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
int err = parse_feature_names(s, model);
if (err) return err;
continue;
}
if (!strcmp(key, "feature_opts_dicts")) {
if (json_next(s) != JSON_ARRAY)
return -EINVAL;
int err = parse_feature_opts_dicts(s, model);
if (err) return err;
continue;
}
if (!strcmp(key, "model")) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
int err = parse_libsvm_model(s, model);
if (err) return err;
continue;
}
json_skip(s);
}
json_skip_until(s, JSON_OBJECT_END);
return 0;
}
static int model_parse(json_stream *s, VmafModel *model,
enum VmafModelFlags flags)
{
int err = -EINVAL;
if (json_next(s) != JSON_OBJECT)
return -EINVAL;
while (json_peek(s) != JSON_OBJECT_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *key = json_get_string(s, NULL);
if (!strcmp(key, "model_dict")) {
err = parse_model_dict(s, model, flags);
if (err) return err;
continue;
}
json_skip(s);
}
json_skip_until(s, JSON_OBJECT_END);
return err;
}
static int vmaf_read_json_model(VmafModel **model, VmafModelConfig *cfg,
json_stream *s)
{
VmafModel *const m = *model = malloc(sizeof(*m));
if (!m) return -ENOMEM;
memset(m, 0, sizeof(*m));
const size_t model_sz = sizeof(*m->feature) * MAX_FEATURE_COUNT;
m->feature = malloc(model_sz);
if (!m->feature) return -ENOMEM;
memset(m->feature, 0, model_sz);
m->name = vmaf_model_generate_name(cfg);
if (!m->name) return -ENOMEM;
const size_t knots_sz = sizeof(VmafPoint) * MAX_KNOT_COUNT;
m->score_transform.knots.list = malloc(knots_sz);
if (!m->score_transform.knots.list) return -ENOMEM;
memset(m->score_transform.knots.list, 0, knots_sz);
return model_parse(s, m, cfg->flags);
}
int vmaf_read_json_model_from_buffer(VmafModel **model, VmafModelConfig *cfg,
const char *data, const int data_len)
{
int err = 0;
json_stream s;
json_open_buffer(&s, data, data_len);
err = vmaf_read_json_model(model, cfg, &s);
json_close(&s);
return err;
}
int vmaf_read_json_model_from_path(VmafModel **model, VmafModelConfig *cfg,
const char *path)
{
int err = 0;
FILE *in = fopen(path, "r");
if (!in) return -EINVAL;
json_stream s;
json_open_stream(&s, in);
err = vmaf_read_json_model(model, cfg, &s);
json_close(&s);
fclose(in);
return err;
}
static int model_collection_parse(json_stream *s, VmafModel **model,
VmafModelCollection **model_collection,
VmafModelConfig *cfg)
{
int err = -EINVAL;
*model_collection = NULL;
if (json_next(s) != JSON_OBJECT)
return -EINVAL;
VmafModelConfig c = *cfg;
const char *name = c.name = vmaf_model_generate_name(cfg);
if (!c.name) return -ENOMEM;
const size_t cfg_name_sz = strlen(name) + 5 + 1;
char cfg_name[cfg_name_sz];
const size_t generated_key_sz = 4 + 1;
char generated_key[generated_key_sz];
unsigned i = 0;
while (json_peek(s) != JSON_OBJECT_END && !json_get_error(s)) {
if (json_next(s) != JSON_STRING)
return -EINVAL;
const char *key = json_get_string(s, NULL);
snprintf(generated_key, generated_key_sz, "%d", i);
if (!strcmp(key, generated_key)) {
VmafModel *m;
err = vmaf_read_json_model(&m, &c, s);
if (err) return err;
if (i == 0) {
*model = m;
c.name = cfg_name;
} else {
err = vmaf_model_collection_append(model_collection, m);
if (err) return err;
}
sprintf((char*)c.name, "%s_%04d", name, ++i);
continue;
}
json_skip(s);
}
free((char*)name);
if (!(*model_collection)) return -EINVAL;
return err;
}
int vmaf_read_json_model_collection_from_path(VmafModel **model,
VmafModelCollection **model_collection,
VmafModelConfig *cfg,
const char *path)
{
int err = 0;
FILE *in = fopen(path, "r");
if (!in) return -EINVAL;
json_stream s;
json_open_stream(&s, in);
err = model_collection_parse(&s, model, model_collection, cfg);
json_close(&s);
fclose(in);
return err;
}
int vmaf_read_json_model_collection_from_buffer(VmafModel **model,
VmafModelCollection **model_collection,
VmafModelConfig *cfg,
const char *data, const int data_len)
{
int err = 0;
json_stream s;
json_open_buffer(&s, data, data_len);
err = model_collection_parse(&s, model, model_collection, cfg);
json_close(&s);
return err;
}