Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Resource Distributor to enable name change #1221

Merged
merged 6 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 139 additions & 18 deletions rodan-main/code/rodan/jobs/resource_distributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@
import shutil

from rodan.jobs.base import RodanTask
from rodan.models import ResourceType
from rodan.models import ResourceType, Input, Output
from django.conf import settings as rodan_settings

import logging

logger = logging.getLogger("rodan")

def log_structure(data, level=0):
if isinstance(data, dict):
for key, value in data.items():
logger.info("%sKey: %s", " " * level, key)
log_structure(value, level + 1)
elif isinstance(data, list):
for idx, item in enumerate(data):
logger.info("%sIndex %d:", " " * level, idx)
log_structure(item, level + 1)
else:
logger.info("%sValue: %s", " " * level, data)


class ResourceDistributor(RodanTask):
Expand All @@ -16,8 +33,18 @@ class ResourceDistributor(RodanTask):
'Resource type': {
'enum': list(map(lambda rt: str(rt.mimetype), ResourceType.objects.all())),
'type': 'string',
'default': 'application/octet-stream',
'default': 'image/rgba+png',
'description': 'Specifies the eligible resource types for input'
},
'User custom prefix': {
'type': 'string',
'default': 'custom prefix - ',
'description': 'User specified prefix (please also include space, hyphen, etc.)'
},
'User custom suffix': {
'type': 'string',
'default': '- custom suffix',
'description': 'User specified suffix (please also include space, hyphen, etc.)'
}
}
}
Expand All @@ -44,21 +71,100 @@ class ResourceDistributor(RodanTask):
},
)


def _inputs(self, runjob, with_urls=False):
"""
Return a dictionary of list of input file path and input resource type.
If with_urls=True, it also includes the resource url and thumbnail urls.
"""

self.runjob = runjob

def _extract_resource(resource, resource_type_mimetype=None):
r = {
# convert 'unicode' object to 'str' object for consistency
"resource_path": str(resource.resource_file.path),
"resource_type": str(
resource_type_mimetype or resource.resource_type.mimetype
),
"resource": resource,
}
if with_urls:
r["resource_url"] = str(resource.resource_url)
r["diva_object_data"] = str(resource.diva_json_url)
r["diva_iip_server"] = getattr(rodan_settings, "IIPSRV_URL")
r["diva_image_dir"] = str(resource.diva_image_dir)
return r

input_objs = (
Input.objects.filter(run_job=runjob)
.select_related("resource", "resource__resource_type", "resource_list")
.prefetch_related("resource_list__resources")
)

inputs = {}
for input in input_objs:
ipt_name = str(input.input_port_type_name)
if ipt_name not in inputs:
inputs[ipt_name] = []
if input.resource is not None: # If resource
inputs[ipt_name].append(_extract_resource(input.resource))
elif input.resource_list is not None: # If resource_list
inputs[ipt_name].append(
map(
lambda x: _extract_resource(
x, input.resource_list.get_resource_type().mimetype
),
input.resource_list.resources.all(),
)
)
else:
raise RuntimeError(
(
"Cannot find any resource or resource list on Input" " {0}"
).format(input.uuid)
)
return inputs

def run_my_task(self, inputs, settings, outputs):
# log_structure(inputs)
input_type = inputs['Resource input'][0]['resource_type']
input_resource = inputs['Resource input'][0]['resource']
input_name = input_resource.name
valid_input_type_num = settings['Resource type']
valid_input_type = self.settings['properties']['Resource type']['enum'][valid_input_type_num] # noqa
if input_type != valid_input_type:
self.my_error_information(
None,
(
"Input cannot be of type {0}. Valid input set in setting is "
"Mismatched input of type {0}. The input type in job setting is "
"{1}"
).format(input_type, valid_input_type)
)
return False
prefix = settings["User custom prefix"]
if not isinstance(prefix, str):
self.my_error_information(
None,
("User custom prefix can only be strings")
)
return False
suffix = settings["User custom suffix"]
if not isinstance(suffix, str):
self.my_error_information(
None,
("User custom suffix can only be strings")
)
return False
new_name = prefix + input_name + suffix
assert isinstance(new_name,str)

input_resource.rename(new_name)
input_resource.save(update_fields=["resource_file"])

outfile_path = outputs['Resource output'][0]['resource_path']
infile_path = inputs['Resource input'][0]['resource_path']

shutil.copyfile(infile_path, outfile_path)
return True

Expand All @@ -69,28 +175,39 @@ def my_error_information(self, exc, traceback):
def test_my_task(self, testcase):
import PIL.Image
import numpy as np
from rodan.models import Resource, ResourceType
resource_types_list = list(map(lambda rt: str(rt.mimetype), ResourceType.objects.all()))
from model_mommy import mommy

# Create a Resource instance using mommy
resource_type, created = ResourceType.objects.get_or_create(mimetype="image/rgb+png")
rc = mommy.make(Resource, resource_type=resource_type, name="test_filename")

# Not so sure what this job is for, but I'll use image/png as the testcase.
inputs = {
"Resource input": [
{
'resource_type': 'image/rgb+png',
'resource_path': testcase.new_available_path()
"Resource input": [
{
"resource_path": testcase.new_available_path(),
"resource_type": rc.resource_type.mimetype,
"resource": rc
}
]
}
]
}
outputs = {
"Resource output": [
{
"resource_type": "image/rgb+png",
"resource_path": testcase.new_available_path()
"Resource output": [
{
"resource_type": "image/rgb+png",
"resource_path": testcase.new_available_path()
}
]
}
]
}
settings = {
"Resource type": resource_types_list.index("image/rgb+png")
}
"Resource type": resource_types_list.index("image/rgb+png"),
"User custom prefix": "test prefix - ",
"User custom suffix": "- test suffix"
}

original_image = rc.name

PIL.Image.new("RGB", size=(50, 50), color=(255, 0, 0)).save(inputs['Resource input'][0]['resource_path'], 'PNG')
array_gt = np.zeros((50, 50, 3)).astype(np.uint8)
array_gt[:, :, 0] = 255
Expand All @@ -105,3 +222,7 @@ def test_my_task(self, testcase):
testcase.assertEqual(result.format, 'PNG')
# and the data should be identical
np.testing.assert_equal(array_gt, array_result)

# Test name change
new_name = f"{settings['User custom prefix']}{original_image}{settings['User custom suffix']}"
testcase.assertEqual(inputs['Resource input'][0]['resource'].name, new_name)
4 changes: 4 additions & 0 deletions rodan-main/code/rodan/models/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ def __str__(self):

labels = models.ManyToManyField(ResourceLabel, blank=True)

def rename(self, newname):
self.name = newname
self.save()

def save(self, *args, **kwargs):
super(Resource, self).save(*args, **kwargs)
if not os.path.exists(self.resource_path):
Expand Down
Loading