yeandy commented on code in PR #17462: URL: https://github.com/apache/beam/pull/17462#discussion_r875054664
########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005001.JPEG': '681', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005002.JPEG': '333', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005003.JPEG': '711', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005004.JPEG': '286', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005005.JPEG': '433', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005006.JPEG': '290', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005007.JPEG': '890', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005008.JPEG': '592', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005009.JPEG': '406', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005010.JPEG': '996', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005011.JPEG': '327', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005012.JPEG': '573' +} + + +def process_outputs(filepath): + with FileSystems().open(filepath) as f: + lines = f.readlines() + lines = [l.decode('utf-8').strip('\n') for l in lines] + return lines + + [email protected]( + torch is None, + 'Missing dependencies. ' + 'Test depends on torch, torchvision and pillow') +class PyTorchInference(unittest.TestCase): + @pytest.mark.uses_pytorch + @pytest.mark.it_postcommit + @pytest.mark.sickbay_direct + @pytest.mark.sickbay_spark + @pytest.mark.sickbay_flink + def test_predictions_output_file(self): + test_pipeline = TestPipeline(is_integration_test=True) + output_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/outputs' + output = '/'.join([output_file_dir, str(uuid.uuid4()), 'result']) + input_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/imagenet_samples.csv' Review Comment: will `temp_storage_end_to_end_testing` be renamed to something like `end_to_end_testing`? ########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005001.JPEG': '681', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005002.JPEG': '333', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005003.JPEG': '711', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005004.JPEG': '286', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005005.JPEG': '433', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005006.JPEG': '290', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005007.JPEG': '890', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005008.JPEG': '592', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005009.JPEG': '406', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005010.JPEG': '996', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005011.JPEG': '327', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005012.JPEG': '573' +} + + +def process_outputs(filepath): + with FileSystems().open(filepath) as f: + lines = f.readlines() + lines = [l.decode('utf-8').strip('\n') for l in lines] + return lines + + [email protected]( + torch is None, + 'Missing dependencies. ' + 'Test depends on torch, torchvision and pillow') +class PyTorchInference(unittest.TestCase): + @pytest.mark.uses_pytorch + @pytest.mark.it_postcommit + @pytest.mark.sickbay_direct + @pytest.mark.sickbay_spark + @pytest.mark.sickbay_flink + def test_predictions_output_file(self): + test_pipeline = TestPipeline(is_integration_test=True) + output_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/outputs' + output = '/'.join([output_file_dir, str(uuid.uuid4()), 'result']) + input_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/imagenet_samples.csv' + images_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs' + + model_path = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/models/mobilenet_v2.pt' + extra_opts = { + 'input': input_file_dir, + 'output': output, + 'model_path': model_path, + 'images_dir': images_dir, + } + pytorch_image_classification.run( + test_pipeline.get_full_options_as_args(**extra_opts), + save_main_session=False) + + output_file = output + '.txt' Review Comment: Might be cleaner to have `output` already have the `.txt` suffix, and then when we pass it into `beam.io.WriteToText()`, we don't need to add `beam.io.WriteToText(file_name_suffix='.txt')` So combined with the change above, we could get rid of this line 88 `output_file = output + '.txt'`, and just set line 73 to `output_file = '/'.join([output_file_dir, str(uuid.uuid4()), 'result.txt'])` ########## sdks/python/apache_beam/ml/inference/examples/pytorch_image_classification.py: ########## @@ -0,0 +1,122 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +import argparse +import io +import os +from functools import partial + +import apache_beam as beam +import torch +import torchvision +from apache_beam.io.filesystems import FileSystems +from apache_beam.ml.inference.api import RunInference +from apache_beam.ml.inference.pytorch import PytorchModelLoader +from apache_beam.options.pipeline_options import PipelineOptions +from apache_beam.options.pipeline_options import SetupOptions +from PIL import Image +from torchvision import transforms + +_IMG_SIZE = (224, 224) +normalize = transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + +transform = transforms.Compose([ + transforms.Resize(_IMG_SIZE), + transforms.ToTensor(), + normalize, +]) + + +def read_image(path_to_file: str, path_to_dir: str): Review Comment: ```suggestion def read_image(image_file_name: str, path_to_dir: str): ``` ########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005001.JPEG': '681', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005002.JPEG': '333', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005003.JPEG': '711', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005004.JPEG': '286', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005005.JPEG': '433', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005006.JPEG': '290', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005007.JPEG': '890', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005008.JPEG': '592', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005009.JPEG': '406', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005010.JPEG': '996', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005011.JPEG': '327', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005012.JPEG': '573' +} + + +def process_outputs(filepath): + with FileSystems().open(filepath) as f: + lines = f.readlines() + lines = [l.decode('utf-8').strip('\n') for l in lines] + return lines + + [email protected]( + torch is None, + 'Missing dependencies. ' + 'Test depends on torch, torchvision and pillow') +class PyTorchInference(unittest.TestCase): + @pytest.mark.uses_pytorch + @pytest.mark.it_postcommit + @pytest.mark.sickbay_direct + @pytest.mark.sickbay_spark + @pytest.mark.sickbay_flink + def test_predictions_output_file(self): + test_pipeline = TestPipeline(is_integration_test=True) + output_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/outputs' + output = '/'.join([output_file_dir, str(uuid.uuid4()), 'result']) + input_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/imagenet_samples.csv' Review Comment: ```suggestion file_of_image_names = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/imagenet_samples.csv' ``` ########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005001.JPEG': '681', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005002.JPEG': '333', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005003.JPEG': '711', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005004.JPEG': '286', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005005.JPEG': '433', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005006.JPEG': '290', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005007.JPEG': '890', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005008.JPEG': '592', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005009.JPEG': '406', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005010.JPEG': '996', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005011.JPEG': '327', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005012.JPEG': '573' +} + + +def process_outputs(filepath): + with FileSystems().open(filepath) as f: + lines = f.readlines() + lines = [l.decode('utf-8').strip('\n') for l in lines] + return lines + + [email protected]( + torch is None, + 'Missing dependencies. ' + 'Test depends on torch, torchvision and pillow') +class PyTorchInference(unittest.TestCase): + @pytest.mark.uses_pytorch + @pytest.mark.it_postcommit + @pytest.mark.sickbay_direct + @pytest.mark.sickbay_spark + @pytest.mark.sickbay_flink + def test_predictions_output_file(self): + test_pipeline = TestPipeline(is_integration_test=True) + output_file_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/outputs' + output = '/'.join([output_file_dir, str(uuid.uuid4()), 'result']) Review Comment: I think it would be more clear to make a ```suggestion base_output_files_dir = 'gs://apache-beam-ml/temp_storage_end_to_end_testing/outputs' output_file = '/'.join([output_file_dir, str(uuid.uuid4()), 'result']) ``` ########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { Review Comment: Nonblocking. Just curious. We hardcode the expected outputs here. Would it allow us to generalize more if we put this in a file on GCS? e.g. store it in a file `'/'.join([output_file_dir, 'expected_outputs.txt'])` ########## sdks/python/apache_beam/ml/inference/examples/pytorch_image_classification.py: ########## @@ -0,0 +1,122 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +import argparse +import io +import os +from functools import partial + +import apache_beam as beam +import torch +import torchvision +from apache_beam.io.filesystems import FileSystems +from apache_beam.ml.inference.api import RunInference +from apache_beam.ml.inference.pytorch import PytorchModelLoader +from apache_beam.options.pipeline_options import PipelineOptions +from apache_beam.options.pipeline_options import SetupOptions +from PIL import Image +from torchvision import transforms + +_IMG_SIZE = (224, 224) +normalize = transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + +transform = transforms.Compose([ + transforms.Resize(_IMG_SIZE), + transforms.ToTensor(), + normalize, +]) Review Comment: Why are these defined outside of `preprocess_data()`? Would it be cleaner if it were inside the `preprocess_data` function? ########## sdks/python/apache_beam/ml/inference/pytorch_it_test.py: ########## @@ -0,0 +1,99 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# pylint: skip-file + +"""End-to-End test for Pytorch Inference""" + +import logging +import unittest +import uuid + +import pytest + +from apache_beam.io.filesystems import FileSystems +from apache_beam.testing.test_pipeline import TestPipeline + +try: + import torch + from apache_beam.ml.inference.examples import pytorch_image_classification +except ImportError as e: + torch = None + +_EXPECTED_OUTPUTS = { + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005001.JPEG': '681', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005002.JPEG': '333', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005003.JPEG': '711', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005004.JPEG': '286', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005005.JPEG': '433', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005006.JPEG': '290', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005007.JPEG': '890', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005008.JPEG': '592', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005009.JPEG': '406', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005010.JPEG': '996', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005011.JPEG': '327', + 'gs://apache-beam-ml/temp_storage_end_to_end_testing/inputs/ILSVRC2012_val_00005012.JPEG': '573' +} + + +def process_outputs(filepath): + with FileSystems().open(filepath) as f: + lines = f.readlines() + lines = [l.decode('utf-8').strip('\n') for l in lines] + return lines + + [email protected]( + torch is None, + 'Missing dependencies. ' + 'Test depends on torch, torchvision and pillow') +class PyTorchInference(unittest.TestCase): + @pytest.mark.uses_pytorch + @pytest.mark.it_postcommit + @pytest.mark.sickbay_direct + @pytest.mark.sickbay_spark + @pytest.mark.sickbay_flink Review Comment: Is using the "sickbay" tag appropriate? I see what you're trying to do since we're just testing dataflow, but the "sickbay" term suggests that it should also work for the other runners. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
