コード例 #1
0
def construct_model_out(m_v, structure, segment_result):
    '''
    Construct ModelOutput protobuf message object using segmentation results.

    Parameters:
        m_v - django.db.ModelVersion - Database model entry for model version
        structure - django.db.Structure - Databse model entry for the structure
            corresponding to the model version.
        segment_result - [ndarray] - List of output channel data in ndarray form
    Returns:
        model_out - ModelOutput.pb - Protobuf message object for the model output
    '''
    model_out = Model_pb2.ModelOutput()
    model_out.ModelID = m_v.model_version_id
    model_out.ProcesserVersion = "{}.{}".format(m_v.major_version, \
        m_v.minor_version)
    model_out.LanguageCode = m_v.language_code
    structure_pb = structure.model_to_pb()
    for result in segment_result:
        out_channel = Model_pb2.ModelOutputChannel()
        out_channel.Structure.CopyFrom(structure_pb)
        depth, height, width = result.shape
        out_channel.Volume.Width = width
        out_channel.Volume.Height = height
        out_channel.Volume.Depth = depth
        result_bytes = result.tobytes()
        out = BytesIO()
        with gzip.GzipFile(fileobj=out, mode='w') as file_out:
            file_out.write(result_bytes)
        out_channel.Volume.Data = out.getvalue()
        out_channel.Volume.DataType = Primitives3D_pb2.VolumeData3D.DataTypes.Byte
        out_channel.Volume.CompressionMethod = 0
        model_out.Channels.append(out_channel)
    return model_out
コード例 #2
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def pb_to_model(self, pb_str):
        '''
        Converts a protobuf message into attributes for the model.
        DoubleValues are just stored as their values within the database.

        Parameters:
        pb_str - (bytestring) - protobuf message to interpret into django db model

        Returns: none
        '''

        # Parse protobuf message
        feedback_pb = Model_pb2.SegmentationFeedback()
        feedback_pb.ParseFromString(pb_str)
        assert feedback_pb.IsInitialized(), \
            "Protobuf SegmentationFeedback model not initialized after parsing Protobuf input"

        # Assign class fields
        self.client_information = feedback_pb.ClientInformation.SoftwareVersion
        self.segmentation_id = feedback_pb.SegmentationID
        self.segmentation_accepted = feedback_pb.SegmentationAccepted
        self.general_comments = feedback_pb.GeneralComments
        self.general_score = None if feedback_pb.GeneralScore is None else \
            feedback_pb.GeneralScore.Value
        self.save()

        # Iterate through structure comments and add to database
        for struc_com in feedback_pb.StructureComments:
            self.structurecomment_set.create(
                structure_id=struc_com.StructureID,
                comments=struc_com.Comments,
                score=None
                if struc_com.Score is None else struc_com.Score.Value)
コード例 #3
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def pb_to_model(self, pb_str):
        '''
        Converts a protobuf message into attributes for the model.
        DoubleValues are just stored as their values within the database.

        Parameters:
        pb_str - (bytestring) - protobuf message to interpret into django db model

        Returns: none
        '''

        # Parse protobuf message
        telemetry_pb = Model_pb2.SegmentationTelemetry()
        telemetry_pb.ParseFromString(pb_str)
        assert telemetry_pb.IsInitialized(), \
            "Protobuf SegmentationTelemetry model not initialized after parsing Protobuf input"

        # Assign class fields
        self.client_software_version = telemetry_pb.ClientInformation.SoftwareVersion
        self.upload_time_ms = telemetry_pb.UploadTimeInMilliseconds
        self.segmentation_wait_ms = telemetry_pb.SegmentationWaitInMilliseconds
        self.segmentation_down_ms = telemetry_pb.SegmentationDownloadInMilliseconds
        self.segmentation_retries = telemetry_pb.NumberOfRetries
        self.segmentation_model_id = telemetry_pb.ModelID
        self.segmentation_id = telemetry_pb.SegmentationID
        self.client_error = telemetry_pb.ClientError
        self.client_error_info = telemetry_pb.ClientErrorInformation
コード例 #4
0
def get_models(request):
    '''
    Endpoint for API GET Model Collections request.

    Returns:
        1. ModelsCollection protobuf message if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''
    # Tries to construct collection of model families, returning an error if failure.
    try:
        # Creates protobuf collection object
        response = Model_pb2.ModelsCollection()

        # Iterates through all model familys and adds to collection
        for model_fam in ModelFamily.objects.all():

            # Check if pb file exists.
            if model_fam.pb == '':
                fname = model_fam.canonical_name
                fname.replace(' ', '-')
                pb_msg = model_fam.model_to_pb().SerializeToString()
                file_io = io.BytesIO(bytes(pb_msg))
                model_fam.pb.save(fname + ".pb", File(file_io))
                model_fam.save()

            mf_file = open(model_fam.pb.path, 'rb')
            file_pb = mf_file.read()
            mf_file.close()

            # Check if fields are filled.
            if model_fam.canonical_name == '':
                model_fam.pb_to_model(file_pb)
                model_fam.save()

            mf_pb = Model_pb2.ModelFamily()
            mf_pb.ParseFromString(file_pb)
            response.Models.append(mf_pb)

        return format_and_send_response(request, response)

    # On exception, returns invalid request error message pb.
    except:
        msg = "Invalid request."
        details = "The request is not acceptable."
        return bad_request_helper(request, msg, details, 406)
コード例 #5
0
def post_segmentation(request, model_id):
    '''
    Endpoint for API POST Segmentation Job requests.
    Enforces protobuf msg input.

    Parameters:
        model_id (str) - model_id with which the segmentation job should use.

    Returns:
        1. SegmentationTask protobuf message if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''

    # Tries to create a segmentation task entry in the database.
    seg_job = SegmentationJob()
    try:
        # Set model ID
        seg_job.model_id = model_id
        # Generate Segmentation ID and 'time_created' using current datetime with TZ support.
        seg_job.time_field = timezone.now()
        # Generate file on disk and point database to location
        request_data = bytes(request.body)
        # Check valid model input
        seg_pb = Model_pb2.ModelInput()
        seg_pb.ParseFromString(request_data)
        # Save model input
        file_io = io.BytesIO(request_data)
        fname = "{}.pb".format("Segmentation_{}".format(
            seg_job.segmentation_id))
        seg_job.model_input.save(fname, File(file_io))
        seg_job.save()

    except:
        seg_job.delete()
        msg = "Invalid request."
        details = "The posted model input is not valid. "+\
         "It might have empty fields that are required."
        return bad_request_helper(request, msg, details, 400)

    # Start asynchronous segmentation with state machine

    m_v = ModelVersion.objects.filter(model_version_id=model_id)[0]
    if m_v.model_type == ModelVersion.ModelVersionType.Phantom:
        start_phantom_segmentation.delay(model_id, seg_job.segmentation_id)
    elif m_v.model_type == ModelVersion.ModelVersionType.Pytorch:
        start_pytorch_segmentation_single_structure.delay(
            model_id, seg_job.segmentation_id)
    elif m_v.model_type == ModelVersion.ModelVersionType.Tensorflow:
        start_phantom_segmentation.delay(model_id, seg_job.segmentation_id)
    else:
        start_phantom_segmentation.delay(model_id, seg_job.segmentation_id)

    # Construct SegmentationTask response.
    response = seg_job.get_task_response()
    return format_and_send_response(request, response)
コード例 #6
0
def ping(request):
    '''
    Endpoint for API ping GET requests.

    Returns either:
        1. Protobuf-serialized response if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''
    version = MAJOR_VERSION + MINOR_VERSION
    api_info = Model_pb2.ApiInformation()
    api_info.Version = version
    return format_and_send_response(request, api_info)
コード例 #7
0
ファイル: tests.py プロジェクト: pyfdrw/SegInt-Research
    def test_get_credits_protobuf(self):
        '''
        Test for endpoint: /api/v2/Credits
        With accept-type "application/x-protobuf"
        '''
        response = self.client.get('/api/v2/Credits',\
            **{'HTTP_ACCEPT':'application/x-protobuf'})
        self.assertEqual(response.status_code, 200, \
            msg='/api/v2/Credits endpoint did not return 200 status code.')

        response_pb = Model_pb2.Credits()
        response_pb.ParseFromString(response.content)
        self.assertEqual(response_pb.TotalCredits, 100000.0, \
            msg='/api/v2/Credits endpoint did not return proper Protobuf message.')
コード例 #8
0
ファイル: tests.py プロジェクト: pyfdrw/SegInt-Research
    def test_api_ping_protobuf(self):
        '''
        Test for endpoint: /api/ping
        With accept-type "application/x-protobuf"
        '''
        response = self.client.get('/api/ping', \
            **{'HTTP_ACCEPT':'application/x-protobuf'})
        self.assertEqual(response.status_code, 200, \
            msg='/api/ping endpoint did not return 200 status code.')

        response_pb = Model_pb2.ApiInformation()
        response_pb.ParseFromString(response.content)
        self.assertNotEqual(response_pb.Version, None, \
            msg='/api/ping endpoint did not return proper Protobuf message.')
コード例 #9
0
def get_segmentation_result(request, model_id, segmentation_id):
    '''
    Endpoint for API GET Segmentation Job result requests.

    Parameters:
        model_id (str) - model_id with which the segmentation job should use.
        segmentation_id (str) - UUID str for the segmentation job

    Returns:
        1. ModelOutput protobuf message if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''

    # Database query for segmentation job with segmentation_id parameter.
    try:
        seg_job = SegmentationJob.objects.get(model_id=model_id,
                                              segmentation_id=segmentation_id)
    except:
        msg = "Invalid request."
        details = "The segmentation job does not exist."
        return bad_request_helper(request, msg, details, 400)

    job_status = seg_job.get_job_progress()

    # Return unfinished bad request if progress isn't 100.
    if job_status.Progress < 100:
        msg = "Invalid request."
        details = "The segmentation job is still processing."
        return bad_request_helper(request, msg, details, 400)

    try:
        # Attempts to read the file as bytestring
        model_out_path = seg_job.model_output.path
        f_in = open(model_out_path, 'rb')
        model_out = f_in.read()
        f_in.close()

        if request.headers["accept"] == "application/x-protobuf":
            seg_job.delete()
            return HttpResponse(model_out, status=200)
        # Else
        response = Model_pb2.ModelOutput()
        response.ParseFromString(model_out)
        seg_job.delete()
        return JsonResponse(json_format.MessageToDict(response), status=200)
    except:
        msg = "Invalid request."
        details = "The segmentation job has encountered an error."
        return bad_request_helper(request, msg, details, 400)
コード例 #10
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def get_task_response(self):
        '''
        Generates Segmentation Task response protobuf message from the stored Segmentation Job.

        Parameters: none

        Returns:
            response - Model_pb2.SegmentationTask - Protobuf Segmentation Task object ready for
            serialization.
        '''
        timestamp = Timestamp()
        timestamp.FromDatetime(self.time_field)
        response = Model_pb2.SegmentationTask()
        response.SegmentationID = str(self.segmentation_id)
        response.StartTime.seconds = timestamp.seconds
        response.StartTime.nanos = timestamp.nanos
        return response
コード例 #11
0
ファイル: tests.py プロジェクト: pyfdrw/SegInt-Research
    def test_get_models_protobuf(self):
        '''
        Test for endpoint: /api/v2/Model/
        With accept-type "application/x-protobuf"
        '''
        response = self.client.get('/api/v2/Model/', \
            **{'HTTP_ACCEPT':'application/x-protobuf'})
        self.assertEqual(response.status_code, 200, \
            msg='/api/v2/Model/ endpoint did not return 200 status code.')

        response_pb = Model_pb2.ModelsCollection()
        response_pb.ParseFromString(response.content)
        self.assertEqual(len(response_pb.Models), 1, \
            msg='/api/v2/Model/ endpoint did not return correct JSON message.')
        self.assertEqual(response_pb.Models[0].CanonicalName, \
            GetModelsTestCase.model_family.canonical_name, \
            msg='/api/v2/Model/ endpoint did not return correct Phantom model.')
コード例 #12
0
def acquire_model_input(seg_job):
    '''
    Acquires the ModelInput protobuf message object from the corresponding
    segmentation job.

    Parameters:
        seg_job - django.db.SegmentationJob - Django model for a segmentation
            job.
    Returns:
        model_in - ModelInput.pb - Protobuf message object for the model input
    '''
    file_path = open(seg_job.model_input.path, 'rb')
    file_input = file_path.read()
    file_path.close()
    model_in = Model_pb2.ModelInput()
    model_in.ParseFromString(file_input)
    return model_in
コード例 #13
0
def bad_request_helper(request, msg, details, status):
    '''
    Helper method for generating bad request responses.

    Parameters:
        request - The original request
        msg - str - Error message
        details - str - Details regarding the bad request response.
        status - int - Status code to use in response.

    Returns:
        HttpResponse - Model_pb2.BadRequestResponse - If 'accept' type is 'application/x-protobuf'
        JsonResponse - Model_pb2.BadRequestResponse - Otherwise

    '''
    response = Model_pb2.BadRequestResponse()
    response.ErrorMessage = msg
    response.ExceptionDetails = details
    return format_and_send_response(request, response, status=status)
コード例 #14
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def model_to_pb(self):
        '''
        Converts the db model into a protobuf message.

        Parameters: None

        Returns: 
            structure - protobuf message for a structure.
        '''
        structure = Model_pb2.Structure()
        structure.Name = self.name
        structure.Color.R = self.color_r
        structure.Color.G = self.color_g
        structure.Color.B = self.color_b
        structure.Type = self.structure_type
        structure.FMACode = self.FMA_code
        structure.InputChannelID = self.input_channel_id
        structure.StructureID = self.structure_id
        return structure
コード例 #15
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def get_job_progress(self):
        '''
        Generates Segmentation Progress response protobuf message from the stored Segmentation Job.

        Parameters: none

        Returns:
            response - Model_pb2.Segmentationprogress - Protobuf Segmentation Progress object.
        '''
        response = Model_pb2.SegmentationProgress()
        if self.model_output != "":
            # 100 progress
            response.Progress = 100
            response.Errors = ""
            response.ErrorCode = 0
            return response
        # Mock response with 0 progress
        response.Progress = 0
        response.Errors = ""
        response.ErrorCode = 0
        return response
コード例 #16
0
def get_credits(request):
    '''
    Endpoint for API GET Credits request.  Since this is a research server, mock information
    will be returned.  The user will have plenty of credits.  In addition, a message will be
    returned to indicate that credits will not apply.

    Returns either:
        1. Protobuf-serialized response if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''

    total_cred = 100000
    display_warning = True
    message = "Local Research Server - Credits will not apply."
    language = "English"

    credits_info = Model_pb2.Credits()
    credits_info.TotalCredits = total_cred
    credits_info.DisplayCreditsWarning = display_warning
    credits_info.CreditsWarningMessage = message
    credits_info.LanguageCode = language
    return format_and_send_response(request, credits_info)
コード例 #17
0
def get_vendor_status(request):
    '''
    Endpoint for API GET Vendor Status requests.

    Since this is a private research server, all information is mocked up.

    Returns:
        1. ModelOutput protobuf message if "accept" header is "application/x-protobuf"
        2. JSON response otherwise
    '''
    response = Model_pb2.VendorStatus()
    response.TotalCredits = 100000
    response.LowCreditsWarningMessage = "Local Research Server - Credits will not apply."
    response.ClientCountryCode = "US"
    response.SegmentationServiceStatus = Model_pb2.VendorStatus.VendorServiceStatus.Available
    ip_addr = get_ip_address()
    response.SegmentationServiceUrl = "http://" + ip_addr + ":8000/api/v2/"
    response.AvailableSegmentationServiceLocations.append("US")
    response.VendorName = "Research Server"
    response.VendorDescriptionHtml = ""
    response.LanguageCode = "en-US"
    return format_and_send_response(request, response)
コード例 #18
0
ファイル: tests.py プロジェクト: pyfdrw/SegInt-Research
    def test_post_job_retrieve_results_json(self):
        '''
        Test for endpoints:
            /api/v2/Model/{modelId}/segmentation
            /api/v2/Model/{modelId}/segmentation/{segmentationId}
            /api/v2/Model/{modelId}/segmentation/{segmentationId}/result

        Since database objects are not maintained between test cases, 3 end-points are tested
        here.
        Retrieve JSON results
        '''

        #----------------------------------------------------------------------
        # Testing POST /api/v2/Model/{modelId}/segmentation
        #----------------------------------------------------------------------
        # Post job to temporary server
        model_id = PostSegmentationTestCase.model_version.model_version_id.replace(" ", "%20")
        post_response = self.client.post('/api/v2/Model/{}/segmentation'.format(model_id), \
            self.seg_job, \
            content_type='application/x-protobuf', \
            **{'HTTP_ACCEPT':'application/x-protobuf'})
        self.assertEqual(post_response.status_code, 200, \
            msg='/api/v2/Model/{}/segmentation endpoint did not return 200 status code.'.format(\
                model_id))
        # Append orphaned segmentation job file path to path list for teardown.
        db_seg_path = SegmentationJob.objects.all()[0].model_input.path
        self.path_list.append(db_seg_path)
        # Parse protobuf message output
        seg_task = Model_pb2.SegmentationTask()
        seg_task.ParseFromString(post_response.content)
        seg_id = seg_task.SegmentationID

        #----------------------------------------------------------------------
        # Testing /api/v2/Model/{modelId}/segmentation/{segmentationId}
        #----------------------------------------------------------------------
        # Get progress from temporary server.  Since the test client is running with eager Celery
        # scheduling, it should always return 100% progress.
        get_response = self.client.get('/api/v2/Model/{}/segmentation/{}'.format( \
            model_id, seg_id), \
            **{'HTTP_ACCEPT':'application/x-protobuf'})
        self.assertEqual(get_response.status_code, 200, \
            msg='/api/v2/Model/{}/segmentation/{} endpoint did not return 200 status code.'.format(\
                model_id, seg_id))
        # Parse protobuf message output
        progress = Model_pb2.SegmentationProgress()
        progress.ParseFromString(get_response.content)
        self.assertEqual(progress.Progress, 100, \
            msg='/api/v2/Model/{}/segmentation/{} endpoint did not return 100% progress.'.format(\
                model_id, seg_id))
        # Append orphaned segmentation job file path to path list for teardown.
        db_results_path = SegmentationJob.objects.all()[0].model_output.path
        self.path_list.append(db_results_path)

        #----------------------------------------------------------------------
        # Testing /api/v2/Model/{modelId}/segmentation/{segmentationId}/result
        #----------------------------------------------------------------------
        seg_response = self.client.get('/api/v2/Model/{}/segmentation/{}/result'.format( \
            model_id, seg_id), \
            **{'HTTP_ACCEPT':'application/json'})
        self.assertEqual(seg_response.status_code, 200, \
            msg='/api/v2/Model/{}/segmentation/{}/result endpoint did not return 200 status code.'\
            .format(model_id, seg_id))
        seg_result = seg_response.json()
        self.assertEqual(seg_result['ModelID'], model_id.replace("%20"," "), \
            msg='/api/v2/Model/{}/segmentation/{}/result endpoint did not fetch correct result.'\
            .format(model_id, seg_id))
コード例 #19
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def pb_to_model(self, pb_str):
        '''
        Converts a protobuf message into attributes for the model.
        DoubleValues are just stored as their values within the database.

        Parameters:
        pb_str - (bytestring) - protobuf message to interpret into django db model

        Returns: none
        '''
        model_family = Model_pb2.ModelFamily()
        model_family.ParseFromString(pb_str)

        # Protobuf top-level fields
        self.canonical_name = model_family.CanonicalName
        self.language_code = model_family.LanguageCode

        # Model family description
        self.name = model_family.FamilyDescription.Name
        self.description = model_family.FamilyDescription.Description
        self.clinical_domain = model_family.FamilyDescription.ClinicalDomain
        self.anatomical_region = model_family.FamilyDescription.AnatomicalRegion
        self.primary_structure = model_family.FamilyDescription.PrimaryStructure
        self.modalities = model_family.FamilyDescription.Modalities
        self.gender = model_family.FamilyDescription.Constraints.Gender

        # Model family constraints body parts examined
        for body_part in model_family.FamilyDescription.Constraints.BodyPartsExamined:
            self.bodypartexamined_set.create(part_type=body_part)

        # Model channel descriptions
        for model_channel in model_family.FamilyDescription.InputChannels:
            self.modelchanneldescription_set.create(
                channel_id=model_channel.ChannelID,
                spacing_min_x=model_channel.Constraints.
                SpacingMinInMillimeters.X,
                spacing_min_y=model_channel.Constraints.
                SpacingMinInMillimeters.Y,
                spacing_min_z=model_channel.Constraints.
                SpacingMinInMillimeters.Z,
                spacing_max_x=model_channel.Constraints.
                SpacingMaxInMillimeters.X,
                spacing_max_y=model_channel.Constraints.
                SpacingMaxInMillimeters.Y,
                spacing_max_z=model_channel.Constraints.
                SpacingMaxInMillimeters.Z,
                dimensions_min_x=model_channel.Constraints.
                DimensionsMinInPixels.X,
                dimensions_min_y=model_channel.Constraints.
                DimensionsMinInPixels.Y,
                dimensions_min_z=model_channel.Constraints.
                DimensionsMinInPixels.Z,
                dimensions_max_x=model_channel.Constraints.
                DimensionsMaxInPixels.X,
                dimensions_max_y=model_channel.Constraints.
                DimensionsMaxInPixels.Y,
                dimensions_max_z=model_channel.Constraints.
                DimensionsMaxInPixels.Z,
                req_original=model_channel.Constraints.OriginalDataRequired,
                is_axial=model_channel.Constraints.IsAxial,
                accepted_modalities_pb=model_channel.Constraints.
                AcceptedModalities)

        # Model Versions
        for model_version in model_family.ModelVersions:
            # Create model version in db
            db_mv = self.modelversion_set.create(
                model_version_id=model_version.ID,
                model_version_desc=model_version.VersionDescription,
                created_time=model_version.CreatedOn.ToDatetime().replace(
                    tzinfo=pytz.utc),
                credits_req=model_version.NumberOfCreditsRequired,
                major_version=model_version.MajorVersion,
                minor_version=model_version.MinorVersion,
                language_code=model_version.LanguageCode)
            # Iterative add structures
            for struc in model_version.Structures:
                db_mv.structure_set.create(
                    name=struc.Name,
                    color_r=struc.Color.R,
                    color_g=struc.Color.G,
                    color_b=struc.Color.B,
                    structure_type=struc.Type,
                    FMA_code=struc.FMACode,
                    input_channel_id=struc.InputChannelID,
                    structure_id=struc.StructureID)
コード例 #20
0
ファイル: models.py プロジェクト: pyfdrw/SegInt-Research
    def model_to_pb(self, filename=None):  # Not implemented
        '''
        Converts the db model into a protobuf message.

        Parameters: None

        Returns:
            m_f - protobuf message for a model family.
        '''
        m_f = Model_pb2.ModelFamily()

        # Protobuf top-level fields
        m_f.CanonicalName = self.canonical_name
        m_f.LanguageCode = self.language_code

        # Family Description fields
        m_f.FamilyDescription.Name = self.name
        m_f.FamilyDescription.Description = self.description
        m_f.FamilyDescription.ClinicalDomain = self.clinical_domain
        m_f.FamilyDescription.AnatomicalRegion = self.anatomical_region
        m_f.FamilyDescription.PrimaryStructure = self.primary_structure
        m_f.FamilyDescription.Modalities = self.modalities

        # Family Description Input Channels
        for chan_desc in self.modelchanneldescription_set.all():

            model_channel = Model_pb2.ModelChannelDescription()
            model_channel.ChannelID = chan_desc.channel_id

            for mode in ast.literal_eval(chan_desc.accepted_modalities_pb):
                model_channel.Constraints.AcceptedModalities.append(mode)

            model_channel.Constraints.OriginalDataRequired = chan_desc.req_original
            model_channel.Constraints.IsAxial = chan_desc.is_axial

            model_channel.Constraints.SpacingMinInMillimeters.X = chan_desc.spacing_min_x
            model_channel.Constraints.SpacingMinInMillimeters.Y = chan_desc.spacing_min_y
            model_channel.Constraints.SpacingMinInMillimeters.Z = chan_desc.spacing_min_z

            model_channel.Constraints.SpacingMaxInMillimeters.X = chan_desc.spacing_max_x
            model_channel.Constraints.SpacingMaxInMillimeters.Y = chan_desc.spacing_max_y
            model_channel.Constraints.SpacingMaxInMillimeters.Z = chan_desc.spacing_max_z

            model_channel.Constraints.DimensionsMinInPixels.X = chan_desc.dimensions_min_x
            model_channel.Constraints.DimensionsMinInPixels.Y = chan_desc.dimensions_min_y
            model_channel.Constraints.DimensionsMinInPixels.Z = chan_desc.dimensions_min_z

            model_channel.Constraints.DimensionsMaxInPixels.X = chan_desc.dimensions_max_x
            model_channel.Constraints.DimensionsMaxInPixels.Y = chan_desc.dimensions_max_y
            model_channel.Constraints.DimensionsMaxInPixels.Z = chan_desc.dimensions_max_z

            m_f.FamilyDescription.InputChannels.append(model_channel)

        # Family Description Constraint fields
        m_f.FamilyDescription.Constraints.Gender = self.gender
        for bpe in self.bodypartexamined_set.all():
            m_f.FamilyDescription.Constraints.BodyPartsExamined.append(
                bpe.part_type)

        # Model Version fields
        for m_v in self.modelversion_set.all():
            model_version = Model_pb2.ModelVersion()
            model_version.ID = m_v.model_version_id
            model_version.VersionDescription = m_v.model_version_desc

            timestamp = Timestamp()
            timestamp.FromDatetime(m_v.created_time)
            model_version.CreatedOn.seconds = timestamp.seconds
            model_version.CreatedOn.nanos = timestamp.nanos

            model_version.NumberOfCreditsRequired = m_v.credits_req
            model_version.MajorVersion = m_v.major_version
            model_version.MinorVersion = m_v.minor_version
            model_version.LanguageCode = m_v.language_code

            for m_struct in m_v.structure_set.all():
                model_structure = Model_pb2.Structure()
                model_structure.Name = m_struct.name
                model_structure.Color.R = m_struct.color_r
                model_structure.Color.G = m_struct.color_g
                model_structure.Color.B = m_struct.color_b
                model_structure.Type = m_struct.structure_type
                model_structure.FMACode = m_struct.FMA_code
                model_structure.InputChannelID = m_struct.input_channel_id
                model_structure.StructureID = m_struct.structure_id

                model_version.Structures.append(model_structure)

            m_f.ModelVersions.append(model_version)

        return m_f