diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py b/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1ed0ce1fa3d472f8da523901244a6565f68343d
--- /dev/null
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/as/initialize-master-data.py
@@ -0,0 +1,73 @@
+#
+# Copyright 2014 ETH Zuerich, Scientific IT Services
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+# MasterDataRegistrationTransaction Class
+from ch.ethz.sis.openbis.generic.server.asapi.v3 import ApplicationServerApi
+from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
+from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
+import sys
+
+helper = MasterDataRegistrationHelper(sys.path)
+api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
+sessionToken = api.loginAsSystem()
+props = CustomASServiceExecutionOptions().withParameter('xls', helper.listXlsByteArrays()) \
+    .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'IMAGING').withParameter('update_mode', 'UPDATE_IF_EXISTS') \
+    .withParameter('scripts', helper.getAllScripts())
+result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+api.logout(sessionToken)
+print("======================== imaging-master-data xls ingestion result ========================")
+print(result)
+print("======================== imaging-data xls ingestion result ========================")
+
+
+
+# from ch.ethz.sis.openbis.generic.server.asapi.v3 import ApplicationServerApi
+# from ch.systemsx.cisd.openbis.generic.server import CommonServiceProvider
+# from ch.ethz.sis.openbis.generic.asapi.v3.dto.service.id import CustomASServiceCode
+# from ch.ethz.sis.openbis.generic.asapi.v3.dto.service import CustomASServiceExecutionOptions
+# from ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl import MasterDataRegistrationHelper
+# import sys
+#
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNFixes
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNAnnotationsMigration
+# from ch.systemsx.cisd.openbis.generic.server.hotfix import ELNCollectionTypeMigration
+#
+# api = CommonServiceProvider.getApplicationContext().getBean(ApplicationServerApi.INTERNAL_SERVICE_NAME)
+# sessionToken = api.loginAsSystem()
+#
+# if ELNFixes.isELNInstalled():
+#     ELNFixes.beforeUpgrade(sessionToken)
+#     ELNAnnotationsMigration.beforeUpgrade(sessionToken)
+#     ELNCollectionTypeMigration.beforeUpgrade(sessionToken)
+#
+# helper = MasterDataRegistrationHelper(sys.path)
+# props = CustomASServiceExecutionOptions().withParameter('xls', helper.getByteArray("common-data-model.xls"))\
+#     .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\
+#     .withParameter('scripts', helper.getAllScripts())
+# result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+#
+# if not ELNFixes.isMultiGroup():
+#     props = CustomASServiceExecutionOptions().withParameter('xls', helper.getByteArray("single-group-data-model.xls"))\
+#         .withParameter('method', 'import').withParameter('zip', False).withParameter('xls_name', 'ELN-LIMS').withParameter('update_mode', 'UPDATE_IF_EXISTS')\
+#         .withParameter('scripts', helper.getAllScripts())
+#     result = api.executeCustomASService(sessionToken, CustomASServiceCode("xls-import"), props)
+#
+# ELNCollectionTypeMigration.afterUpgrade()
+# api.logout(sessionToken)
+# print("======================== master-data xls ingestion result ========================")
+# print(result)
+# print("======================== master-data xls ingestion result ========================")
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls
new file mode 100644
index 0000000000000000000000000000000000000000..8f88765f4618a3462bf4118c977d56eb9f621c5d
Binary files /dev/null and b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/imaging-data-model.xls differ
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
index 8f06d3e37447b12cd2a8010ea42e1930e6f7f9da..6766c2ff21171002a28224386024fbd241407c28 100644
--- a/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/as/master-data/scripts/imaging_dataset_config_validation.py
@@ -13,17 +13,170 @@
 #   limitations under the License.
 #
 
+import json
+
+
+def assert_control(control_config):
+    if not control_config:
+        return True, ''
+    obligatory_tags = ['@type', 'label', 'type']
+    for tag in obligatory_tags:
+        if tag not in control_config:
+            return False, tag+': is missing!'
+        if control_config[tag] is None:
+            return False, tag+': can not be empty!'
+
+    list_tags = ['values', 'range', 'speeds', 'visibility']
+    for list_tag in list_tags:
+        if list_tag in control_config and control_config[list_tag] is not None and not isinstance(control_config[list_tag], list):
+            return False, list_tag+': must be a list or null!'
+
+    boolean_tags = ['playable', 'multiselect']
+    for boolean_tag in boolean_tags:
+        if boolean_tag in control_config and control_config[boolean_tag] is not None and not isinstance(control_config[boolean_tag], bool):
+            return False, boolean_tag+': must be a boolean or empty!'
+
+    if 'visibility' in control_config and control_config['visibility'] is not None:
+        visibility = control_config['visibility']
+        for vis in visibility:
+            tags = ['label', 'values']
+            all_tags = tags + ['range', 'unit']
+            for tag in ['label', 'values']:
+                if tag not in vis:
+                    return False, 'visibility->'+tag+': is missing!'
+                if vis[tag] is None:
+                    return False, 'visibility->'+tag+': can not be empty!'
+
+            for tag in ['values', 'range']:
+                if tag in vis and vis[tag] is not None and not isinstance(vis[tag], list):
+                    return False, 'visibility->'+tag+': must be a list!'
+
+    if 'metadata' in control_config and control_config['metadata'] is not None and not isinstance(control_config['metadata'], dict):
+        return False, '->metadata: must be a dictionary or null!'
+
+    return True, ''
+
+
+def assert_config(json_config):
+    if 'config' in json_config:
+        config = json_config['config']
+        obligatory_tags = ['@type', 'adaptor', 'version', 'playable', 'exports', 'inputs']
+        for tag in obligatory_tags:
+            if tag not in config:
+                return False, 'config->' + tag + ': is missing!'
+            if config[tag] is None:
+                return False, 'config->' + tag + ': can not be empty!'
+
+        if config['adaptor'].strip() == '':
+            return False, 'config->adaptor: can not be blank!'
+
+        list_tags = ['speeds', 'resolutions', 'exports', 'inputs']
+        for list_tag in list_tags:
+            if list_tag in config and config[list_tag] is not None and not isinstance(config[list_tag], list):
+                return False, '\''+list_tag+'\' must be a list or null!'
+
+        if not isinstance(config['playable'], bool):
+            return False, 'config->playable: must be a boolean!'
+
+        for control in config['exports']:
+            result, err = assert_control(control)
+            if not result:
+                return result, 'config->exports->' + err
+
+        for control in config['inputs']:
+            result, err = assert_control(control)
+            if not result:
+                return result, 'config->inputs->' + err
+
+        if 'metadata' in config and config['metadata'] is not None and not isinstance(config['metadata'], dict):
+            return False, 'config->metadata: must be a dictionary or null!'
+    else:
+        return False, 'Missing \'config\' tag in configuration!'
+    return True, ''
+
+
+def assert_preview(preview_config):
+    obligatory_tags = ['@type', 'format', 'show']
+    for tag in obligatory_tags:
+        if tag not in config:
+            return False, tag + ': is missing!'
+        if config[tag] is None:
+            return False, tag + ': can not be empty!'
+
+    if not isinstance(preview_config['show'], bool):
+        return False, 'show: must be boolean!'
+
+    if not isinstance(preview_config['format'], str):
+        return False, 'format: must be string!'
+
+    if 'bytes' in preview_config and preview_config['bytes'] is not None and not isinstance(preview_config['bytes'], str):
+        return False, 'bytes: must be a base64 encoded string or null!'
+
+    if 'metadata' in preview_config and preview_config['metadata'] is not None and not isinstance(preview_config['metadata'], dict):
+        return False, 'metadata: must be a dictionary or null!'
+
+    if 'config' in preview_config and preview_config['config'] is not None and not isinstance(preview_config['config'], dict):
+        return False, 'config: must be a dictionary or null!'
+
+    return True, ''
+
+
+def assert_images(json_config):
+    if 'images' in json_config:
+        images = json_config['images']
+        if images is None:
+            return False, '\'images\' tag can not be null!'
+        if not isinstance(images, list):
+            return False, '\'images\' tag must be a list!'
+
+        for image in images:
+            obligatory_tags = ['@type']
+            for tag in obligatory_tags:
+                if tag not in image:
+                    return False, 'images->' + tag + ': missing tag!'
+                if image[tag] is None:
+                    return False, 'images->' + tag + ': can not be empty!'
+
+            if 'metadata' in image and image['metadata'] is not None and not isinstance(image['metadata'], dict):
+                return False, 'images->metadata: must be a dictionary or null!'
+
+            if 'previews' in image and image['previews'] is not None and not isinstance(image['previews'], list):
+                return False, 'images->previews: must be a list or null!'
+
+            for preview in image['previews']:
+                res, err = assert_preview(preview)
+                if not res:
+                    return result, 'images->previews->' + err
+
+    else:
+        return False, 'Missing \'images\' tag in configuration!'
+    return True, ''
+
 
 def get_rendered_property(entity, property):
-    value = entity.property(property)
-    if value is not None:
-        return value.renderedValue()
+    properties = entity.externalDataPE().getProperties()
+    for prop in properties:
+        etpt = prop.getEntityTypePropertyType()
+        pt = etpt.getPropertyType()
+        code = pt.getCode()
+        if code == property:
+            return prop.tryGetUntypedValue()
+    return None
 
 
 def validate(entity, is_new):
     imaging_dataset_config = get_rendered_property(entity, "$IMAGING_DATA_CONFIG")
     if imaging_dataset_config is None or imaging_dataset_config == "":
         return "Imaging dataset config can not be empty!"
+    elif "test_validation_failure" in imaging_dataset_config:
+        return "Imaging dataset config validation failure!"
     else:
-        # TODO add deserialization and validation of particular fields
-        pass
+
+        try:
+            config = json.loads(imaging_dataset_config)
+        except Exception as e:
+            return "Could not parse JSON: " + e
+
+        result, err = assert_config(config)
+        if not result:
+            return err