try:
import IPython
except:
%pip install IPython
import IPython
from IPython.display import display, IFrame, HTML, Javascript
from IPython.core.display import HTML
HTML("""<link rel="stylesheet" type="text/css" href="src/css/notebook.css"/>""")
This notebook provided a step through the process of transforming collections data to Linked Art.
The Indianapolis Museum of Art (IMA) has transformed a sample of its collections data to Linked Art, and originated from the IMA's EMu Collections Management Systems in XML format.
The following code that has been commented out, allows you to upload an XML file of your choice.
try:
import ipywidgets
except:
!pip install ipywidgets
import ipywidgets
from ipywidgets import Layout, FileUpload
"""
# define file upload widget
upload = FileUpload(accept='.xml', multiple=False, description='Select XML file')
upload
"""
print("")
try:
import xmltodict
except:
!pip install xmltodict
import xmltodict
"""
obj = False
# get content from uploaded file
for uploaded_filename in upload.value:
content = upload.value[uploaded_filename]['content']
obj = xmltodict.parse(content)
if obj == False:
display(HTML("<div class='alert alert-block alert-danger'>Please select a file to transform</div>"))
else:
display(HTML("<div class='alert alert-block alert-success'>File uploaded</div>"))
content = upload.value[uploaded_filename]['content']
obj = xmltodict.parse(content)
"""
print("")
with open('data/ima/input/ObjectsSample.xml') as fd:
content = fd.read()
obj = xmltodict.parse(content)
allObjects = obj["table"]["tuple"]
#object dropdown options
objOptions = []
objOptions.append(("Please select an object",''))
for obj in allObjects:
title = irn = ""
# define properties variables
for prop in obj["atom"]:
propName = prop["@name"]
if "#text" in prop:
if propName == "irn":
irn = prop["#text"]
if propName == "TitMainTitle":
title = prop["#text"]
objOptions.append((irn + ' : ' + title,irn))
selectObject = ipywidgets.Dropdown(options=objOptions, description='Select Object')
The next step will create a dropdown list of artworks from the XML file.
display(selectObject)
selectedIRN = selectObject.value
if selectedIRN == "":
display(HTML("<div class='alert alert-block alert-danger'>Please select an artwork to transform</div>"))
else:
display(HTML("<div class='alert alert-block alert-success'>Artwork selected : " + selectObject.options[selectObject.index][0] + "</div"))
Dropdown(description='Select Object', index=23, options=(('Please select an object', ''), ('1032 : long-neck v…
The uploaded file was converted to a Python dictionary earlier with obj = xmltodict.parse(content).
This code selects the part of the dictionary for the selected artwork
selectedObject = {}
for obj in allObjects:
for prop in obj["atom"]:
if prop["@name"] == "irn":
irn = prop["#text"]
if irn == selectedIRN:
selectedObject = obj
break
obj = selectedObject
A mapping is needed between the collection data fields and variable used in the transformation . This is a manual process and a python dictionary mapp
holds the mapping, with key equal to variables used in the transformation code, and the values equal to the label of fields in the input data.
try:
import pandas as pd
except:
%pip install pandas
import pandas as pd
mapp = {
"id":"irn",
"accession_number":"TitAccessionNo",
"accession_date": "TitAccessionDate",
"classification" : "TitObjectType",
"title": "TitMainTitle",
"alt_title": "",
"notes": "TitTitleNotes",
"date_created":"CreDateCreated",
"date_created_earliest": "CreEarliestDate",
"date_created_latest": "CreLatestDate",
"created_period":"CreCreationPeriod",
"created_dynasty":"CreCreationDynasty",
"created_inscriptions":"CrePrimaryInscriptions",
"created_notes": "CreCreationNotes",
"creator":"",
"physical_medium": "PhyMediumAndSupport",
"physical_style": "PhyStyle",
"physical_technique": "PhyTechnique",
"physical_description": "PhyDescription",
"physical_dimensions": "PhyConvertedDims",
"created_provenance": "CreProvenance" ,
"credit_line": "",
"collection" : "PhyCollectionArea",
"current_status" : "",
"current_owner" : ""
}
display(pd.DataFrame(mapp, index=[0]).T)
0 | |
---|---|
id | irn |
accession_number | TitAccessionNo |
accession_date | TitAccessionDate |
classification | TitObjectType |
title | TitMainTitle |
alt_title | |
notes | TitTitleNotes |
date_created | CreDateCreated |
date_created_earliest | CreEarliestDate |
date_created_latest | CreLatestDate |
created_period | CreCreationPeriod |
created_dynasty | CreCreationDynasty |
created_inscriptions | CrePrimaryInscriptions |
created_notes | CreCreationNotes |
creator | |
physical_medium | PhyMediumAndSupport |
physical_style | PhyStyle |
physical_technique | PhyTechnique |
physical_description | PhyDescription |
physical_dimensions | PhyConvertedDims |
created_provenance | CreProvenance |
credit_line | |
collection | PhyCollectionArea |
current_status | |
current_owner |
The next step uses the mapp
to create a dictionary containing artwork properties from the input data.
import json
#src https://note.nkmk.me/en/python-dict-get-key-from-value/
def get_key_from_value(d, val):
keys = [k for k, v in d.items() if v == val]
if keys:
return keys[0]
return None
# map input file data to dictionary keys
def createObjProp(obj,mapp):
objProp = {"creator":[]}
for prop in obj["atom"]:
propName = prop["@name"]
propValue = ""
if "#text" in prop:
propValue = prop["#text"]
if propName in list(mapp.values()):
key = get_key_from_value(mapp, propName)
objProp[key] = propValue
for table in obj["table"]:
if table["@name"] == "Creator1":
id = name = ""
if "tuple" in table and "atom" in table["tuple"]:
for cinfo in table["tuple"]["atom"]:
if cinfo["@name"] == "irn":
id = cinfo["#text"]
if cinfo["@name"] == "SummaryData":
name = cinfo["#text"]
if cinfo["@name"] == "CreRole":
role = cinfo["#text"]
objProp["creator"].append({"id": id, "name": name, "role" : role})
objProp["current_owner"] = {"name":"Indianapolis Museum of Art at Newfields",
"location":"Indianapolis, Indiana",
"type": "http://vocab.getty.edu/aat/300312281" ,
"type_label": ""}
return objProp
objProp = createObjProp(obj,mapp)
# display transposed dataframe of data mapping
print(json.dumps(objProp, indent=2))
{ "creator": [ { "id": "11566", "name": "Steele, Theodore Clement", "role": "Artist" } ], "id": "37383", "accession_number": "89.1", "accession_date": "1889", "title": "Oaks of Vernon", "notes": "", "classification": "Visual Works: Paintings", "date_created": "1887", "date_created_earliest": "1887", "date_created_latest": "1887", "created_period": "", "created_dynasty": "", "created_inscriptions": "Signed and dated, l.l.: T.C.Steele .87.", "created_notes": "", "physical_medium": "oil on canvas", "physical_style": "", "physical_technique": "painting", "physical_description": "", "physical_dimensions": "30 x 45-1/4 in. (canvas)\n36-3/4 x 51-3/4 x 3-1/4 in. (framed)", "created_provenance": "Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889.", "collection": "901.2-American Painting and Sculpture 1800-1945", "current_owner": { "name": "Indianapolis Museum of Art at Newfields", "location": "Indianapolis, Indiana", "type": "http://vocab.getty.edu/aat/300312281", "type_label": "" } }
The next section of the notebook transforms the selected artwork description in the collection data to Linked Art. The step through the transformation process is divided into sections, looking at the representation of different entities in the Linked Art data model.
Initial variables are defined:
try:
import cromulent
except:
!pip install cromulent
import cromulent
from cromulent.model import factory, Actor, Production, BeginningOfExistence, EndOfExistence, TimeSpan, Place
from cromulent.model import InformationObject, Phase, VisualItem
from cromulent.vocab import Painting, Drawing,Miniature,add_art_setter, PrimaryName, Name, CollectionSet, instances, Sculpture
from cromulent.vocab import aat_culture_mapping, AccessionNumber, Height, Width, SupportPart, Gallery, MuseumPlace
from cromulent.vocab import BottomPart, Description, RightsStatement, MuseumOrg, Purchase
from cromulent.vocab import Furniture, Mosaic, Photograph, Coin, Vessel, Graphic, Enamel, Embroidery, PhotographPrint
from cromulent.vocab import PhotographAlbum, PhotographBook, PhotographColor, PhotographBW, Negative, Map, Clothing, Furniture
from cromulent.vocab import Sample, Architecture, Armor, Book, DecArts, Implement, Jewelry, Manuscript, SiteInstallation, Text, Print
from cromulent.vocab import TimeBasedMedia, Page, Folio, Folder, Box, Envelope, Binder, Case, FlatfileCabinet
from cromulent.vocab import HumanMadeObject,Tapestry,LocalNumber
from cromulent.vocab import Type,Set
from cromulent.vocab import TimeSpan, Group, Acquisition, Place
from cromulent.vocab import Production, TimeSpan, Actor
from cromulent.vocab import LinguisticObject
# baseURI for JSON-LD document
baseURI = "https://data.discovernewfields.org/"
factory.base_url = baseURI
factory.default_lang = "en"
This section creates a minimum representation of an artwork in Linked Art including its classification as a particular type of artwork.
There are a few core properties that every resource should have for it to be a useful part of the world of Linked Open Data:
@context
id
type
_label
CIDOC-CRM is a framework that must be extended via additional vocabularies and ontologies to be useful. The provided mechanism for doing this is the classified_as property, which refers to a term from a controlled vocabulary. This is in contrast to the type
property, which is used for CIDOC-CRM defined classes, and a few extensions as needed.
The classified_as
property is thus a way to be more specific about the sort of entity, while maintaining the core information as the class using type. Controlled vocabulary entries should not be used with type
, nor classes used with classified_as
.
While any external vocabulary of terms can be used, the Getty's Art and Architecture Thesaurus is used whenever possible for consistency and that it is already widespread in the museum domain. The set of terms that have been identified as useful are listed in the community best-practices for recommendations, and within the documentation of the model when a particular choice is essential for interoperability.
id
is a URL and has been created from the irn
value together with a URL prefix: https://data.discovernewfields.org/_label
is a human readable label, intended for developers and other people reading the data. The value is taken from the TitMainTitle
property.classification
property in objProp is used to search for a matching object type. The value is taken from the TitObjectType
property in the IMA XML data file.# dictionary of cromulent Linked Art object types
objTypes = {
"Painting": Painting(),
"Sculpture": Sculpture(),
"Drawing": Drawing(),
"Miniature": Miniature(),
"Tapestry": Tapestry(),
"Furniture": Furniture(),
"Furnishings": DecArts(),
"Mosaic": Mosaic(),
"Photograph": Photograph(),
"Coin": Coin(),
"Vessel": Vessel(),
"Graphic": Graphic(),
"Enamel": Enamel(),
"Embroidery": Embroidery(),
"PhotographPrint": PhotographPrint(),
"PhotographAlbum": PhotographAlbum(),
"PhotographBook": PhotographBook(),
"PhotographColor": PhotographColor(),
"PhotographBW": PhotographBW(),
"Negative": Negative(),
"Map": Map(),
"Clothing": Clothing(),
"Sample": Sample(),
"Architecture": Architecture(),
"Armor": Armor(),
"Book": Book(),
"DecArts": DecArts(),
"Implement": Implement(),
"Jewelry": Jewelry(),
"Manuscript": Manuscript(),
"SiteInstallation": SiteInstallation(),
"Text": Text(),
"Print": Print(),
"TimeBasedMedia": TimeBasedMedia(),
"Page": Page(),
"Folio": Folio(),
"Folder": Folder(),
"Box": Box(),
"Envelope": Envelope(),
"Binder": Binder(),
"Case": Case(),
"FlatfileCabinet": FlatfileCabinet()
}
object_uri = baseURI + objProp["id"]
objLA = None
objLA = HumanMadeObject() # linked art object
for otype in objTypes:
if otype in objProp["classification"]:
objLA = objTypes[otype]
break
objLA.id = baseURI + objProp["id"]
objLA._label = objProp["title"]
data = factory.toString(objLA, compact=False)
print(data)
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/37383", "type": "HumanMadeObject", "_label": "Oaks of Vernon", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300033618", "type": "Type", "_label": "Painting", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300435443", "type": "Type", "_label": "Type of Work" } ] } ] }
with open('src/js/visld2.js', 'r') as _jscript:
code = _jscript.read() + 'var data = ' + data + '; var selector = "#vis"; visjsonld(data, selector); '
Javascript(code)
This section creates representations of different types of identifiers for the artwork, e.g. accession number.
Many resources of interest are given external identifiers, such as accession numbers for objects, ORCIDs for people or groups, lot numbers for auctions, and so forth. Identifiers are represented in a very similar way to names, but instead use the Identifier class. Identifiers will normally have a classification determining which sort of identifier it is, to distinguish between internal repository system assigned numbers from museum assigned accession numbers, for example.
As Identifiers and Names use the same identified_by
property, the JSON will frequently have mixed classes in the array. Unlike Names
, Identifiers
are not part of human language and thus cannot have translations or a language associated with them.
accession_number
is the IMA data accession numberowner assigned number
is the IMA data idThis section represents the artwork's accession number.
An accession number is a sequential number assigned to each record or item as it is added to a to a library collection or database and which indicates the chronological order of its acquisition (src: https://libanswers.liverpool.ac.uk/faq/181287).
import json
def objAccession(objProp,object_uri):
accession = None
accession_number = objProp["accession_number"]
if accession_number != "":
accession = AccessionNumber(accession_number,value=accession_number)
return accession
data = factory.toString(objAccession(objProp,object_uri), compact=False)
print(data)
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/Identifier/89.1", "type": "Identifier", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300312355", "type": "Type", "_label": "Accession Number" } ], "content": "89.1" }
with open('src/js/visld2.js', 'r') as _jscript:
code = _jscript.read() + 'var data = ' + data + '; var selector = "#vis-an"; visjsonld(data, selector); '
Javascript(code)
def objLocalnumber(objProp,object_uri):
localnumber = None
id = str(objProp["id"])
if id != "":
localnumber = LocalNumber(id,value=id)
return localnumber
data = (factory.toString(objLocalnumber(objProp,object_uri), compact=False))
print(data)
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/Identifier/37383", "type": "Identifier", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300404621", "type": "Type", "_label": "Owner-Assigned Number" } ], "content": "37383" }
with open('src/js/visld2.js', 'r') as _jscript:
code = _jscript.read() + 'var data = ' + data + '; var selector = "#vis-ln"; visjsonld(data, selector); '
Javascript(code)
This section is concerned with names for the artwork.
As the _label
property is intended as internal documentation for the data, it is strongly recommended that every resource that should be rendered to an end user also have at least one specific name. The name could be for an object, a person, a group, an event or anything else. This pattern uses the identified_by
property, with a Name
resource. The value of the name is given in the content property of the Name
.
It is somewhat unintuitive to think of a name as identifying the resource it is associated with, as names are typically not unique. However, as the name itself is uniquely identified rather than just an anonymous string, they are no longer a shared label and instead the particular instance of a name is uniquely associated with the resource. With this formulation, the name instance does uniquely identify the resource.
If there is more than one name given, then there should be one that is classified_as
the primary name for use. This is done by adding the Primary Name
(aat:300404670) term to it. There should be exactly one primary title given per language.
Names are also part of human communication, and can have the Linguistic features of the model associated with them, such as having a particular language, or having translations.
def objAlternatename(objProp,object_uri):
alternateName = None
if "alt_title" in objProp:
alt_title = objProp["alt_title"]
alternatename = AlternateName(object_uri + "/alternate-name",value=alt_title)
return alternateName
altname = objAlternatename(objProp,object_uri)
if altname is not None:
print(factory.toString(altname, compact=False))
This section is concerned with a home page that describes the artwork.
A very common scenario is that there is a web page about the object, perhaps managed by a collections management system. For humans, this page is much more useful than the data intended for machines. It can be referenced with the subject_of
property, and points to a DigitalObject
which is classified_as
a web page, or aat:300264578
. As with digital images, the home page can have a format of "text/html" and other properties.
http://collection.imamuseum.org/artwork/
and the artwork's id
propertydef objHomepage(objProp,object_uri):
homepage = None
id = str(objProp["id"])
homepageId = "http://collection.imamuseum.org/artwork/" + id
homepage = LinguisticObject(homepageId, label="Homepage for the Object")
homepage.classified_as = Type("http://vocab/getty.edu/aat/300264578", label="Web pages (documents)")
homepage.classified_as = Type("http://vocab.getty.edu/aat/300266277", label="home pages")
homepage.format = "text/html"
return homepage
homepage = objHomepage(objProp,object_uri)
if homepage is not None:
print(factory.toString(homepage, compact=False))
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "http://collection.imamuseum.org/artwork/37383", "type": "LinguisticObject", "_label": "Homepage for the Object", "classified_as": [ { "id": "http://vocab/getty.edu/aat/300264578", "type": "Type", "_label": "Web pages (documents)" }, { "id": "http://vocab.getty.edu/aat/300266277", "type": "Type", "_label": "home pages" } ], "format": "text/html" }
This section is concerns with the representation of assertions about the artwork.
In many cases, current data does not support the level of specificity that the full ontology allows, or the information is simply best expressed in human-readable form. For example, instead of a completely modeled set of parts with materials, many museum collection management systems allow only a single human-readable string for the "medium" or "materials statement". The same is true in many other situations, including rights or allowable usage statements, dimensions, edition statements and so forth. Any time that there is a description of the resource, with or without qualification as to the type of description, then this pattern can be used to record the descriptive text.
The pattern makes use of the LinguisticObject
class that is used to identify a particular piece of textual content. These Linguistic Objects are then refered to by any other resource. They maintain the statement's text in the content property, and the language of the statement (if known) in the language property.
Use cases for this pattern include:
created_provenance
is used to construct a provenance statement about the artworkcredit_line
is used to construct a credit line statement about the artworkThis code creates a provenance statement about the artwork.
def objProvenance(objProp,object_uri):
prov = None
if "created_provenance" in objProp:
provenance = objProp["created_provenance"]
if provenance !="":
prov = LinguisticObject(object_uri + "/provenance-statement",
value=provenance,
label="Provenance Statement about the Object"
)
prov.classified_as = Type("http://vocab.getty.edu/aat/300055863", label="provenance (history of ownership)")
prov.classified_as = Type("http://vocab.getty.edu/aat/300418049", label="brief texts")
return prov
prov = objProvenance(objProp,object_uri)
if prov is not None:
print(factory.toString(prov, compact=False))
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/37383/provenance-statement", "type": "LinguisticObject", "_label": "Provenance Statement about the Object", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300055863", "type": "Type", "_label": "provenance (history of ownership)" }, { "id": "http://vocab.getty.edu/aat/300418049", "type": "Type", "_label": "brief texts" } ], "content": "Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889." }
This code created a credit link statement for the artwork.
def objCredit(objProp,object_uri):
credit = None
propCredit = "credit_line"
if propCredit in objProp:
credit_line = objProp[propCredit]
if credit_line != "":
credit = LinguisticObject(object_uri + "/credit-line",
value=credit_line,
label="Credit Line for the Object"
)
credit.classified_as = Type("http://vocab.getty.edu/aat/300026687", label="acknowledgements")
credit.classified_as = Type("http://vocab.getty.edu/aat/300418049", label="brief texts")
return credit
credit = objCredit(objProp,object_uri)
if credit is not None:
print(factory.toString(credit, compact=False))
else:
print("No credit description")
No credit description
This section is concerned with a representation in Linked Art of the production of the artwork.
The first activity in an object's lifecycle is its creation, or Production
. The relationship to the object that was produced by the activity (produced
) is added to the general activity model, along with the time, location and actors. This follows the base pattern for activities.
date_created
date_created_earliest
date_created_latest
creator
field in the mapp
dictionary, this is used for the representation of the creator role in the production event.def objProduction(objProp,object_uri):
prod = None
date_created = "date_created"
created_earliest = "date_created_earliest"
created_latest = "date_created_latest"
if date_created in objProp:
prod = Production(object_uri + "/production", label="Production of the Object")
labelTimespan = "date unknown"
if objProp[date_created] != "":
labelTimespan = objProp[date_created]
timespan = TimeSpan(object_uri + "/production/timespan", label=labelTimespan)
if created_earliest in objProp:
timespan.begin_of_the_begin = objProp[created_earliest]
if created_latest in objProp:
timespan.end_of_the_end = objProp[created_latest]
prod.timespan = timespan
propCreator = "creator"
if propCreator in objProp:
creators = objProp[propCreator]
id = label = ""
for creator in creators:
for prop in creator:
if "id" == prop:
id = creator["id"]
if "name" == prop:
label = creator["name"]
if label != "":
actor = Actor(id,label)
prod.carried_out_by = actor
return prod
prod = objProduction(objProp,object_uri)
if prod is not None:
print(factory.toString(prod, compact=False))
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/37383/production", "type": "Production", "_label": "Production of the Object", "timespan": { "id": "https://data.discovernewfields.org/37383/production/timespan", "type": "TimeSpan", "_label": "1887", "begin_of_the_begin": "1887", "end_of_the_end": "1887" }, "carried_out_by": [ { "id": "https://data.discovernewfields.org/Actor/11566", "type": "Actor", "_label": "Steele, Theodore Clement" } ] }
This section is concerned with a representation of the current owner of the artwork and also with an acquisition event.
Acquisitions are used to describe the transfer of ownership of an object from one owner to the next. The first owner is typically the artist, who would then transfer it to the second owner, to the third owner and so on. The ownership chain can be expressed by repeating this same pattern with the buyer from one acquisition being the seller in the subsequent one. If the previous owner (e.g. the seller if there is a value exchange) or the subsequent owner (e.g. the buyer) is not known for a particular acquisition, then the reference can be left out from the description.
The acquistion is not necessarily a purchase, it could be a gift, an inheritance or any other method of gaining the right of ownership of an object.
The model encodes this information with an Acquisition part of the overall Provenance Event. The acquisition is the transfer of the right of ownership of an object (referenced in transferred_title_of) from the seller (in transferred_title_from) to the buyer (in transferred_title_to).
Each object has its own Acquisition as part of the provenance event, so if a collector buys three paintings from a dealer, then there would be a single Provenance Event with three Acquisitions, all of which transfer the title of a single painting from the dealer to the collector.
accession_date
current_owner
--name
, type
, type_label
, name_location
https://linked.art/model/provenance/acquisition/#object-acquisition
def objCurrentowner(objProp,object_uri):
current_owner = None
if "current_owner" in objProp and objProp["current_owner"]["name"] != "":
cowner = objProp["current_owner"]
cowner_name = cowner["name"]
cowner_type = cowner["type"]
cowner_type_label = cowner["type_label"]
current_owner = Group( "http://vocab.getty.edu/ulan/500300517",label=cowner_name)
current_owner.classified_as = Type( cowner_type,label="museums (institutions)")
acquisition = objAcquisition(objProp,object_uri)
if acquisition is not None:
current_owner.acquired_title_through = acquisition
return current_owner
def objAcquisition(objProp,object_uri):
acquisition = None
if "accession_date" in objProp and objProp["accession_date"] != "":
acquisition = Acquisition(object_uri + "/IMA-acquisition", label = "Acquisition of the Object")
acquisition.classified_as = Type("http://vocab.getty.edu/aat/300157782",
label="acquisition (collections management)")
if "name_location" in objProp["current_owner"]:
acquisition.took_place_at = Place("http://vocab.getty.edu/tgn/7012924",
label=objProp["current_owner"]["location"])
acquisition.timespan = objAcquisitionTimespan(object_uri,objProp["accession_date"])
return acquisition
def objAcquisitionTimespan(object_uri,accession_date):
timespan = None
end = begin = ""
if len(accession_date) == 4:
begin = accession_date + "-01-01T00:00:00.000Z"
end = accession_date + "-12-31T00:00:00.000Z"
elif len(accession_date) == 8:
begin = accession_date + "01T00:00:00.000Z"
end = accession_date
if '-02-' in accession_date:
end = end + "28"
if ('-01-','-03-','-05-','-07-','-08-','-09-','-10-','-12-') in accession_date:
end = end + "31"
if ('-04-','-06-','-09-','-11-'):
end = end + "30"
end = end + "T00:00:00"
elif len(accession_date) == 10:
begin = accession_date + "T00:00:00.000Z"
end = accession_date + "T00:00:00.000Z"
else:
begin = end = ""
timespan = TimeSpan(object_uri + "/IMA-acquisition/timespan", label=accession_date)
if begin != "":
timespan.begin_of_the_begin = begin
if end != "":
timespan.end_of_the_end = end
return timespan
current_owner = objCurrentowner(objProp,object_uri)
if current_owner is not None:
print(factory.toString(current_owner, compact=False))
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "http://vocab.getty.edu/ulan/500300517", "type": "Group", "_label": "Indianapolis Museum of Art at Newfields", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300312281", "type": "Type", "_label": "museums (institutions)" } ], "acquired_title_through": [ { "id": "https://data.discovernewfields.org/37383/IMA-acquisition", "type": "Acquisition", "_label": "Acquisition of the Object", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300157782", "type": "Type", "_label": "acquisition (collections management)" } ], "timespan": { "id": "https://data.discovernewfields.org/37383/IMA-acquisition/timespan", "type": "TimeSpan", "_label": "1889", "begin_of_the_begin": "1889-01-01T00:00:00.000Z", "end_of_the_end": "1889-12-31T00:00:00.000Z" } } ] }
This section is concerned with a representation of the custody of an artwork, i.e. the entity responsible for looking after the artwork.
Objects are owned by legal entities, such as museum organizations or individual people. However there may be more information about which department is responsible within a museum for the curation of the object. This is the division between acquisitions (the legal ownership of the object) and custody (the responsibility for looking after the object). If the department is known, then it should be either part of the Provenance Event in which the object is acquired, or a separate provenance event if the object was not accessioned by a department and later came under their care, or was transferred between departments. In these latter cases, the ownership does not change, only the custody of the object.
The department becomes the current_keeper
of the object, whereas the institution is the current_owner
.
IMA data fields used in the representation:
current_status
current_owner
def objCustody(objProp,object_uri):
custody = None
if "current_status" in objProp and objProp["current_status"] != "":
current_status = objProp["current_status"]
current_owner = checkCurrentOwner(current_status)
if current_owner == False:
name = objProp["current_owner"]["name"]
type = objProp["current_owner"]["type"]
label = objProp["current_owner"]["type_label"]
custody = Group(label=name)
custody.classified_as = Type(type,
label=label)
return custody
def checkCurrentOwner(current_status):
current_owner = False
if current_status != "":
checkObjStatus = ('Accessioned','Partial Accession')
for status in checkObjStatus:
if status == current_status:
current_owner = True
if "IMA-Owned" in current_status:
current_owner = True
return current_owner
custody = objCustody(objProp,object_uri)
if custody is not None:
print(factory.toString(custody, compact=False))
This section is concerned with the representation of collections or sets that the artwork may belong to.
There are many use cases for grouping resources together, often of the same class but sometimes of varying types. These use cases are exemplified in the sections below, and range from the set of objects in an auction lot, to dealer inventories and museum collections, exhibitions, a set of related concepts, or the set of people that share a common feature such as gender or nationality.
In order to cover all of the use cases with a consistent pattern, we introduce a new Set
class from outside of CIDOC-CRM. This avoids issues with sets of resources with different types, and the semantics of the identity of objects and collections. If an equivalent class is added into the core CIDOC-CRM ontology in the future, a new major version of the specification will change to using it.
Sets are conceptual groupings, rather than physical ones. The set of objects in a virtual exhibition or simply the set of a person's favorite objects never change their physical state by being part of the Set or not. They are, thus, created by a Creation
, not by a Production
.
Like any core resource, Set must have an id and type, are likely to have additional classifications, and can have Identifiers
and Names
. They can have statements made about them, and have member resources. These member resources are included via the member
property rather than part
, or via member_of
from the included resource to the Set
.
The collection
property in the IMA data is used.
def objCollection(objProp,object_uri):
coll = None
if "collection" in objProp:
collection = objProp["collection"]
coll = Set(object_uri +"/collection/" + collection,
label= collection)
coll.classified_as = Type("http://vocab.getty.edu/aat/300025976",
label="collections (object groupings)")
return coll
coll = objCollection(objProp,object_uri)
if coll is not None:
print(factory.toString(coll, compact=False))
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/37383/collection/901.2-American Painting and Sculpture 1800-1945", "type": "Set", "_label": "901.2-American Painting and Sculpture 1800-1945", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300025976", "type": "Type", "_label": "collections (object groupings)" } ] }
Having looked at different representations in the Linked Art data model, this section now brings these together in one Linked Art JSON-LD document.
The resulting document can be copy+pasted, and visualised in the JSON-LD playground. Alternatively run the code below to visualise the Linked Art JSON-LD document in the notebook.
def createObjDesc(objProp,objTypes,object_uri):
objLA = None
objLA = HumanMadeObject() # linked art object
for otype in objTypes:
if otype in objProp["classification"]:
objLA = objTypes[otype]
break
objLA.id = object_uri
objLA._label = objProp["title"]
# IDENTIFIED_BY
accession = objAccession(objProp,object_uri)
localnumber = objLocalnumber(objProp,object_uri)
#primaryname = objPrimaryname(objProp,object_uri)
# listIds = (accession,localnumber,primaryname)
listIds = (accession,localnumber)
identified_by = False
for id in listIds:
if id is not None:
identified_by = True
break
if identified_by == True:
objLA.identified_by = []
for id in listIds:
if id is not None:
objLA.identified_by.append(id)
# REFERRED_TO_BY
objLA.referred_to_by = None
prov = objProvenance(objProp,object_uri)
credit = objCredit(objProp,object_uri)
referred_to_by = False
if prov is not None or credit is not None:
referred_to_by = True
if referred_to_by == True:
objLA.referred_to_by = []
if prov is not None:
objLA.referred_to_by.append(prov) # provenance
if credit is not None:
objLA.referred_to_by.append(credit) # credit line
# SUBJECT_OF
objLA.subject_of = None
homepage = None
homepage = objHomepage(objProp,object_uri)
if homepage is not None:
objLA.subject_of = homepage # home page
# PRODUCED_BY
objLA.produced_by = None
if "creator" in objProp:
prod = None
prod = objProduction(objProp,object_uri)
objLA.produced_by = None
if prod is not None:
objLA.produced_by = prod # production
# MEMBER_OF
objLA.member_of = None
if "collection" in objProp:
coll = None
coll = objCollection(objProp,object_uri)
if coll is not None:
objLA.member_of = coll # collection
# CURRENT_KEEPER
objLA.current_owner = None
custody = None
custody = objCustody(objProp,object_uri)
if custody is not None:
objLA.current_owner = custody
# CURRENT_OWNER
if "current_owner" in objProp and objProp["current_owner"] != "":
current_owner = objCurrentowner(objProp,object_uri)
if current_owner is not None:
objLA.current_owner = current_owner
return objLA
objProp = createObjProp(obj,mapp)
ObjLA = None
objLA = createObjDesc(objProp,objTypes,object_uri)
data = factory.toString(objLA, compact=False)
f = open("./data/ima/output/json/object_linkedart.json", "w")
f.write(data)
f.close()
print(data)
{ "@context": "https://linked.art/ns/v1/linked-art.json", "id": "https://data.discovernewfields.org/37383", "type": "HumanMadeObject", "_label": "Oaks of Vernon", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300033618", "type": "Type", "_label": "Painting", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300435443", "type": "Type", "_label": "Type of Work" } ] } ], "identified_by": [ { "id": "https://data.discovernewfields.org/Identifier/89.1", "type": "Identifier", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300312355", "type": "Type", "_label": "Accession Number" } ], "content": "89.1" }, { "id": "https://data.discovernewfields.org/Identifier/37383", "type": "Identifier", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300404621", "type": "Type", "_label": "Owner-Assigned Number" } ], "content": "37383" } ], "referred_to_by": [ { "id": "https://data.discovernewfields.org/37383/provenance-statement", "type": "LinguisticObject", "_label": "Provenance Statement about the Object", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300055863", "type": "Type", "_label": "provenance (history of ownership)" }, { "id": "http://vocab.getty.edu/aat/300418049", "type": "Type", "_label": "brief texts" } ], "content": "Purchased from the artist by the John Herron Art Institute, Indianapolis, Indiana, now the Indianapolis Museum of Art in 1889." } ], "member_of": [ { "id": "https://data.discovernewfields.org/37383/collection/901.2-American Painting and Sculpture 1800-1945", "type": "Set", "_label": "901.2-American Painting and Sculpture 1800-1945", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300025976", "type": "Type", "_label": "collections (object groupings)" } ] } ], "subject_of": [ { "id": "http://collection.imamuseum.org/artwork/37383", "type": "LinguisticObject", "_label": "Homepage for the Object", "classified_as": [ { "id": "http://vocab/getty.edu/aat/300264578", "type": "Type", "_label": "Web pages (documents)" }, { "id": "http://vocab.getty.edu/aat/300266277", "type": "Type", "_label": "home pages" } ], "format": "text/html" } ], "produced_by": { "id": "https://data.discovernewfields.org/37383/production", "type": "Production", "_label": "Production of the Object", "timespan": { "id": "https://data.discovernewfields.org/37383/production/timespan", "type": "TimeSpan", "_label": "1887", "begin_of_the_begin": "1887", "end_of_the_end": "1887" }, "carried_out_by": [ { "id": "https://data.discovernewfields.org/Actor/11566", "type": "Actor", "_label": "Steele, Theodore Clement" } ] }, "current_owner": [ { "id": "http://vocab.getty.edu/ulan/500300517", "type": "Group", "_label": "Indianapolis Museum of Art at Newfields", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300312281", "type": "Type", "_label": "museums (institutions)" } ], "acquired_title_through": [ { "id": "https://data.discovernewfields.org/37383/IMA-acquisition", "type": "Acquisition", "_label": "Acquisition of the Object", "classified_as": [ { "id": "http://vocab.getty.edu/aat/300157782", "type": "Type", "_label": "acquisition (collections management)" } ], "timespan": { "id": "https://data.discovernewfields.org/37383/IMA-acquisition/timespan", "type": "TimeSpan", "_label": "1889", "begin_of_the_begin": "1889-01-01T00:00:00.000Z", "end_of_the_end": "1889-12-31T00:00:00.000Z" } } ] } ] }
The final Linked Art JSON-LD representation of the object is visualised below.
from IPython.display import display, Javascript
with open('./src/js/visld.js', 'r') as _jscript:
code = _jscript.read() + "var file = './data/ima/output/json/object_linkedart.json';var selector = '#vis3';visjsonld(file, selector); "
display(Javascript(code))