Beispiel #1
0
    def declare_directory(self, value: CWLObjectType) -> ProvEntity:
        """Register any nested files/directories."""
        # FIXME: Calculate a hash-like identifier for directory
        # so we get same value if it's the same filenames/hashes
        # in a different location.
        # For now, mint a new UUID to identify this directory, but
        # attempt to keep it inside the value dictionary
        dir_id = cast(str, value.setdefault("@id", uuid.uuid4().urn))

        # New annotation file to keep the ORE Folder listing
        ore_doc_fn = dir_id.replace("urn:uuid:", "directory-") + ".ttl"
        dir_bundle = self.document.bundle(self.metadata_ns[ore_doc_fn])

        coll = self.document.entity(
            dir_id,
            [
                (PROV_TYPE, WFPROV["Artifact"]),
                (PROV_TYPE, PROV["Collection"]),
                (PROV_TYPE, PROV["Dictionary"]),
                (PROV_TYPE, RO["Folder"]),
            ],
        )
        # ORE description of ro:Folder, saved separately
        coll_b = dir_bundle.entity(
            dir_id,
            [(PROV_TYPE, RO["Folder"]), (PROV_TYPE, ORE["Aggregation"])],
        )
        self.document.mentionOf(dir_id + "#ore", dir_id, dir_bundle.identifier)

        # dir_manifest = dir_bundle.entity(
        #     dir_bundle.identifier, {PROV["type"]: ORE["ResourceMap"],
        #                             ORE["describes"]: coll_b.identifier})

        coll_attribs = [(ORE["isDescribedBy"], dir_bundle.identifier)]
        coll_b_attribs = []  # type: List[Tuple[Identifier, ProvEntity]]

        # FIXME: .listing might not be populated yet - hopefully
        # a later call to this method will sort that
        is_empty = True

        if "listing" not in value:
            get_listing(self.fsaccess, value)
        for entry in cast(MutableSequence[CWLObjectType],
                          value.get("listing", [])):
            is_empty = False
            # Declare child-artifacts
            entity = self.declare_artefact(entry)
            self.document.membership(coll, entity)
            # Membership relation aka our ORE Proxy
            m_id = uuid.uuid4().urn
            m_entity = self.document.entity(m_id)
            m_b = dir_bundle.entity(m_id)

            # PROV-O style Dictionary
            # https://www.w3.org/TR/prov-dictionary/#dictionary-ontological-definition
            # ..as prov.py do not currently allow PROV-N extensions
            # like hadDictionaryMember(..)
            m_entity.add_asserted_type(PROV["KeyEntityPair"])

            m_entity.add_attributes({
                PROV["pairKey"]: entry["basename"],
                PROV["pairEntity"]: entity,
            })

            # As well as a being a
            # http://wf4ever.github.io/ro/2016-01-28/ro/#FolderEntry
            m_b.add_asserted_type(RO["FolderEntry"])
            m_b.add_asserted_type(ORE["Proxy"])
            m_b.add_attributes({
                RO["entryName"]: entry["basename"],
                ORE["proxyIn"]: coll,
                ORE["proxyFor"]: entity,
            })
            coll_attribs.append((PROV["hadDictionaryMember"], m_entity))
            coll_b_attribs.append((ORE["aggregates"], m_b))

        coll.add_attributes(coll_attribs)
        coll_b.add_attributes(coll_b_attribs)

        # Also Save ORE Folder as annotation metadata
        ore_doc = ProvDocument()
        ore_doc.add_namespace(ORE)
        ore_doc.add_namespace(RO)
        ore_doc.add_namespace(UUID)
        ore_doc.add_bundle(dir_bundle)
        ore_doc = ore_doc.flattened()
        ore_doc_path = str(PurePosixPath(METADATA, ore_doc_fn))
        with self.research_object.write_bag_file(
                ore_doc_path) as provenance_file:
            ore_doc.serialize(provenance_file,
                              format="rdf",
                              rdf_format="turtle")
        self.research_object.add_annotation(dir_id, [ore_doc_fn],
                                            ORE["isDescribedBy"].uri)

        if is_empty:
            # Empty directory
            coll.add_asserted_type(PROV["EmptyCollection"])
            coll.add_asserted_type(PROV["EmptyDictionary"])
        self.research_object.add_uri(coll.identifier.uri)
        return coll
Beispiel #2
0
class NIDMExporter():
    """ 
    Generic class to parse a result directory to extract the pieces of 
    information to be stored in NIDM-Results and to generate a NIDM-Results 
    export.
    """
    def parse(self):
        """ 
        Parse a result directory to extract the pieces information to be 
        stored in NIDM-Results. 
        """
        # Methods: find_software, find_model_fitting, find_contrasts and
        # find_inferences should be defined in the children classes and return
        # a list of NIDM Objects as specified in the objects module

        # Object of type Software describing the neuroimaging software package
        # used for the analysis
        self.software = self._find_software()

        # List of objects of type ModelFitting describing the model fitting
        # step in NIDM-Results (main activity: Model Parameters Estimation)
        self.model_fittings = self._find_model_fitting()

        # Dictionary of (key, value) pairs where where key is a tuple
        # containing the identifier of a ModelParametersEstimation object and a
        # tuple of identifiers of ParameterEstimateMap objects and value is an
        # object of type Contrast describing the contrast estimation step in
        # NIDM-Results (main activity: Contrast Estimation)
        self.contrasts = self._find_contrasts()

        # Inference activity and entities
        # Dictionary of (key, value) pairs where key is the identifier of a
        # ContrastEstimation object and value is an object of type Inference
        # describing the inference step in NIDM-Results (main activity:
        # Inference)
        self.inferences = self._find_inferences()

        # Initialise prov document
        self.doc = ProvDocument()
        self._add_namespaces()

    def export(self):
        """ 
        Generate a NIDM-Results export. 
        """
        if not os.path.isdir(self.export_dir):
            os.mkdir(self.export_dir)

        # Initialise main bundle
        self._create_bundle(self.version)
        self.bundle.update(self.software.export())

        # Add model fitting steps
        for model_fitting in self.model_fittings:
            self.bundle.update(model_fitting.export())
            self.bundle.wasAssociatedWith(model_fitting.activity.id,
                                          self.software.id)

        # Add contrast estimation steps
        for (model_fitting_id, pe_ids), contrasts in self.contrasts.items():
            model_fitting = self._get_model_fitting(model_fitting_id)
            for contrast in contrasts:
                self.bundle.update(contrast.export())
                self.bundle.used(contrast.estimation.id,
                                 model_fitting.rms_map.id)
                self.bundle.used(contrast.estimation.id,
                                 model_fitting.mask_map.id)
                self.bundle.wasAssociatedWith(contrast.estimation.id,
                                              self.software.id)
                for pe_id in pe_ids:
                    self.bundle.used(contrast.estimation.id, pe_id)

        # Add inference steps
        for contrast_id, inferences in self.inferences.items():
            contrast = self._get_contrast(contrast_id)
            for inference in inferences:
                self.bundle.update(inference.export())
                if contrast.z_stat_map:
                    used_id = contrast.z_stat_map.id
                else:
                    used_id = contrast.stat_map.id
                self.bundle.used(inference.id, used_id)
                self.bundle.wasAssociatedWith(inference.id, self.software.id)

        # Write-out prov file
        self.save_prov_to_files()

        return self.export_dir

    def _get_model_fitting(self, mf_id):
        """ 
        Retreive model fitting with identifier 'mf_id' from the list of model 
        fitting objects stored in self.model_fittings
        """
        for model_fitting in self.model_fittings:
            if model_fitting.activity.id == mf_id:
                return model_fitting
        raise Exception("Model fitting activity with id: "+str(mf_id)+\
            " not found.")

    def _get_contrast(self, con_id):
        """ 
        Retreive contrast with identifier 'con_id' from the list of contrast 
        objects stored in self.contrasts
        """
        for contrasts in self.contrasts.values():
            for contrast in contrasts:
                if contrast.estimation.id == con_id:
                    return contrast
        raise Exception("Contrast activity with id: "+str(con_id)+\
            " not found.")

    def _add_namespaces(self):
        """ 
        Add namespaces to NIDM document. 
        """
        self.doc.add_namespace(NIDM)
        self.doc.add_namespace(NIIRI)
        self.doc.add_namespace(CRYPTO)
        self.doc.add_namespace(DCT)

    def _create_bundle(self, version):
        """ 
        Initialise NIDM-Results bundle.
        """
        software_lc = self.software.name.lower()
        software_uc = self.software.name.upper()

        bundle_id = NIIRI[str(uuid.uuid4())]
        self.bundle = ProvBundle(identifier=bundle_id)

        self.doc.entity(bundle_id,
                        other_attributes=((
                            PROV['type'],
                            PROV['Bundle'],
                        ), (PROV['label'], software_uc + " Results"),
                                          (NIDM['objectModel'],
                                           NIDM[software_uc + 'Results']),
                                          (NIDM['version'], version)))

        self.doc.wasGeneratedBy(NIIRI[software_lc + '_results_id'],
                                time=str(datetime.datetime.now().time()))

    def _get_model_parameters_estimations(self, error_model):
        """
        Infer model estimation method from the 'error_model'. Return an object
        of type ModelParametersEstimation.
        """
        if error_model.dependance == INDEPEDENT_CORR:
            if error_model.variance_homo:
                estimation_method = ESTIMATION_OLS
            else:
                estimation_method = ESTIMATION_WLS
        else:
            estimation_method = ESTIMATION_GLS

        mpe = ModelParametersEstimation(estimation_method, self.software.id)

        return mpe

    def save_prov_to_files(self, showattributes=False):
        """
        Write-out provn serialisation to nidm.provn.
        """
        self.doc.add_bundle(self.bundle)
        provn_fid = open(os.path.join(self.export_dir, 'nidm.provn'), 'w')
        provn_fid.write(self.doc.get_provn(4))
Beispiel #3
0
class NIDMExporter():
    """
    Generic class to parse a result directory to extract the pieces of
    information to be stored in NIDM-Results and to generate a NIDM-Results
    export.
    """
    def __init__(self, version, out_dir, zipped=True):
        out_dirname = os.path.basename(out_dir)
        out_path = os.path.dirname(out_dir)

        # Create output path from output name
        self.zipped = zipped
        if not self.zipped:
            out_dirname = out_dirname + ".nidm"
        else:
            out_dirname = out_dirname + ".nidm.zip"
        out_dir = os.path.join(out_path, out_dirname)

        # Quit if output path already exists and user doesn't want to overwrite
        # it
        if os.path.exists(out_dir):
            msg = out_dir + " already exists, overwrite?"
            if not input("%s (y/N) " % msg).lower() == 'y':
                quit("Bye.")
            if os.path.isdir(out_dir):
                shutil.rmtree(out_dir)
            else:
                os.remove(out_dir)
        self.out_dir = out_dir

        if version == "dev":
            self.version = {
                'major': 10000,
                'minor': 0,
                'revision': 0,
                'num': version
            }
        else:
            major, minor, revision = version.split(".")
            if "-rc" in revision:
                revision, rc = revision.split("-rc")
            else:
                rc = -1
            self.version = {
                'major': int(major),
                'minor': int(minor),
                'revision': int(revision),
                'rc': int(rc),
                'num': version
            }

        # Initialise prov document
        self.doc = ProvDocument()
        self._add_namespaces()

        # A temp directory that will contain the exported data
        self.export_dir = tempfile.mkdtemp(prefix="nidm-", dir=out_path)

        self.prepend_path = ''

    def parse(self):
        """
        Parse a result directory to extract the pieces information to be
        stored in NIDM-Results.
        """

        try:
            # Methods: find_software, find_model_fitting, find_contrasts and
            # find_inferences should be defined in the children classes and
            # return a list of NIDM Objects as specified in the objects module

            # Object of type Software describing the neuroimaging software
            # package used for the analysis
            self.software = self._find_software()

            # List of objects of type ModelFitting describing the
            # model fitting step in NIDM-Results (main activity: Model
            # Parameters Estimation)
            self.model_fittings = self._find_model_fitting()

            # Dictionary of (key, value) pairs where where key is a tuple
            # containing the identifier of a ModelParametersEstimation object
            # and a tuple of identifiers of ParameterEstimateMap objects and
            # value is an object of type Contrast describing the contrast
            # estimation step in NIDM-Results (main activity: Contrast
            # Estimation)
            self.contrasts = self._find_contrasts()

            # Inference activity and entities
            # Dictionary of (key, value) pairs where key is the identifier of a
            # ContrastEstimation object and value is an object of type
            # Inference describing the inference step in NIDM-Results (main
            # activity: Inference)
            self.inferences = self._find_inferences()
        except Exception:
            self.cleanup()
            raise

    def cleanup(self):
        if os.path.isdir(self.export_dir):
            shutil.rmtree(self.export_dir)

    def add_object(self, nidm_object, export_file=True):
        """
        Add a NIDMObject to a NIDM-Results export.
        """
        if not export_file:
            export_dir = None
        else:
            export_dir = self.export_dir

        if not isinstance(nidm_object, NIDMFile):
            nidm_object.export(self.version, export_dir)
        else:
            nidm_object.export(self.version, export_dir, self.prepend_path)
        # ProvDocument: add object to the bundle
        if nidm_object.prov_type == PROV['Activity']:
            self.bundle.activity(nidm_object.id,
                                 other_attributes=nidm_object.attributes)
        elif nidm_object.prov_type == PROV['Entity']:
            self.bundle.entity(nidm_object.id,
                               other_attributes=nidm_object.attributes)
        elif nidm_object.prov_type == PROV['Agent']:
            self.bundle.agent(nidm_object.id,
                              other_attributes=nidm_object.attributes)
        # self.bundle.update(nidm_object.p)

    def export(self):
        """
        Generate a NIDM-Results export.
        """
        try:
            if not os.path.isdir(self.export_dir):
                os.mkdir(self.export_dir)

            # Initialise main bundle
            self._create_bundle(self.version)

            self.add_object(self.software)

            # Add model fitting steps
            if not isinstance(self.model_fittings, list):
                self.model_fittings = list(self.model_fittings.values())

            for model_fitting in self.model_fittings:
                # Design Matrix
                # model_fitting.activity.used(model_fitting.design_matrix)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.design_matrix.id)
                self.add_object(model_fitting.design_matrix)
                # *** Export visualisation of the design matrix
                self.add_object(model_fitting.design_matrix.image)

                if model_fitting.design_matrix.image.file is not None:
                    self.add_object(model_fitting.design_matrix.image.file)

                if model_fitting.design_matrix.hrf_models is not None:
                    # drift model
                    self.add_object(model_fitting.design_matrix.drift_model)

                if self.version['major'] > 1 or \
                        (self.version['major'] == 1 and
                         self.version['minor'] >= 3):
                    # Machine
                    # model_fitting.data.wasAttributedTo(model_fitting.machine)
                    self.bundle.wasAttributedTo(model_fitting.data.id,
                                                model_fitting.machine.id)
                    self.add_object(model_fitting.machine)

                    # Imaged subject or group(s)
                    for sub in model_fitting.subjects:
                        self.add_object(sub)
                        # model_fitting.data.wasAttributedTo(sub)
                        self.bundle.wasAttributedTo(model_fitting.data.id,
                                                    sub.id)

                # Data
                # model_fitting.activity.used(model_fitting.data)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.data.id)
                self.add_object(model_fitting.data)

                # Error Model
                # model_fitting.activity.used(model_fitting.error_model)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.error_model.id)
                self.add_object(model_fitting.error_model)

                # Parameter Estimate Maps
                for param_estimate in model_fitting.param_estimates:
                    # param_estimate.wasGeneratedBy(model_fitting.activity)
                    self.bundle.wasGeneratedBy(param_estimate.id,
                                               model_fitting.activity.id)
                    self.add_object(param_estimate)
                    self.add_object(param_estimate.coord_space)
                    self.add_object(param_estimate.file)

                    if param_estimate.derfrom is not None:
                        self.bundle.wasDerivedFrom(param_estimate.id,
                                                   param_estimate.derfrom.id)
                        self.add_object(param_estimate.derfrom)
                        self.add_object(param_estimate.derfrom.file,
                                        export_file=False)

                # Residual Mean Squares Map
                # model_fitting.rms_map.wasGeneratedBy(model_fitting.activity)
                self.add_object(model_fitting.rms_map)
                self.bundle.wasGeneratedBy(model_fitting.rms_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.rms_map.coord_space)
                self.add_object(model_fitting.rms_map.file)
                if model_fitting.rms_map.derfrom is not None:
                    self.bundle.wasDerivedFrom(
                        model_fitting.rms_map.id,
                        model_fitting.rms_map.derfrom.id)
                    self.add_object(model_fitting.rms_map.derfrom)
                    self.add_object(model_fitting.rms_map.derfrom.file,
                                    export_file=False)

                # Resels per Voxel Map
                if model_fitting.rpv_map is not None:
                    self.add_object(model_fitting.rpv_map)
                    self.bundle.wasGeneratedBy(model_fitting.rpv_map.id,
                                               model_fitting.activity.id)
                    self.add_object(model_fitting.rpv_map.coord_space)
                    self.add_object(model_fitting.rpv_map.file)
                    if model_fitting.rpv_map.inf_id is not None:
                        self.bundle.used(model_fitting.rpv_map.inf_id,
                                         model_fitting.rpv_map.id)
                    if model_fitting.rpv_map.derfrom is not None:
                        self.bundle.wasDerivedFrom(
                            model_fitting.rpv_map.id,
                            model_fitting.rpv_map.derfrom.id)
                        self.add_object(model_fitting.rpv_map.derfrom)
                        self.add_object(model_fitting.rpv_map.derfrom.file,
                                        export_file=False)

                # Mask
                # model_fitting.mask_map.wasGeneratedBy(model_fitting.activity)
                self.bundle.wasGeneratedBy(model_fitting.mask_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.mask_map)
                if model_fitting.mask_map.derfrom is not None:
                    self.bundle.wasDerivedFrom(
                        model_fitting.mask_map.id,
                        model_fitting.mask_map.derfrom.id)
                    self.add_object(model_fitting.mask_map.derfrom)
                    self.add_object(model_fitting.mask_map.derfrom.file,
                                    export_file=False)

                # Create coordinate space export
                self.add_object(model_fitting.mask_map.coord_space)
                # Create "Mask map" entity
                self.add_object(model_fitting.mask_map.file)

                # Grand Mean map
                # model_fitting.grand_mean_map.wasGeneratedBy(model_fitting.activity)
                self.bundle.wasGeneratedBy(model_fitting.grand_mean_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.grand_mean_map)
                # Coordinate space entity
                self.add_object(model_fitting.grand_mean_map.coord_space)
                # Grand Mean Map entity
                self.add_object(model_fitting.grand_mean_map.file)

                # Model Parameters Estimation activity
                self.add_object(model_fitting.activity)
                self.bundle.wasAssociatedWith(model_fitting.activity.id,
                                              self.software.id)
                # model_fitting.activity.wasAssociatedWith(self.software)
                # self.add_object(model_fitting)

            # Add contrast estimation steps
            analysis_masks = dict()
            for (model_fitting_id,
                 pe_ids), contrasts in list(self.contrasts.items()):
                for contrast in contrasts:
                    model_fitting = self._get_model_fitting(model_fitting_id)
                    # for contrast in contrasts:
                    # contrast.estimation.used(model_fitting.rms_map)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.rms_map.id)
                    # contrast.estimation.used(model_fitting.mask_map)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.mask_map.id)
                    analysis_masks[contrast.estimation.id] = \
                        model_fitting.mask_map.id
                    self.bundle.used(contrast.estimation.id,
                                     contrast.weights.id)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.design_matrix.id)
                    # contrast.estimation.wasAssociatedWith(self.software)
                    self.bundle.wasAssociatedWith(contrast.estimation.id,
                                                  self.software.id)

                    for pe_id in pe_ids:
                        # contrast.estimation.used(pe_id)
                        self.bundle.used(contrast.estimation.id, pe_id)

                    # Create estimation activity
                    self.add_object(contrast.estimation)

                    # Create contrast weights
                    self.add_object(contrast.weights)

                    if contrast.contrast_map is not None:
                        # Create contrast Map
                        # contrast.contrast_map.wasGeneratedBy(contrast.estimation)
                        self.bundle.wasGeneratedBy(contrast.contrast_map.id,
                                                   contrast.estimation.id)
                        self.add_object(contrast.contrast_map)
                        self.add_object(contrast.contrast_map.coord_space)
                        # Copy contrast map in export directory
                        self.add_object(contrast.contrast_map.file)

                        if contrast.contrast_map.derfrom is not None:
                            self.bundle.wasDerivedFrom(
                                contrast.contrast_map.id,
                                contrast.contrast_map.derfrom.id)
                            self.add_object(contrast.contrast_map.derfrom)
                            self.add_object(contrast.contrast_map.derfrom.file,
                                            export_file=False)

                    # Create Std Err. Map (T-tests) or Explained Mean Sq. Map
                    # (F-tests)
                    # contrast.stderr_or_expl_mean_sq_map.wasGeneratedBy
                    # (contrast.estimation)
                    stderr_explmeansq_map = (
                        contrast.stderr_or_expl_mean_sq_map)
                    self.bundle.wasGeneratedBy(stderr_explmeansq_map.id,
                                               contrast.estimation.id)
                    self.add_object(stderr_explmeansq_map)
                    self.add_object(stderr_explmeansq_map.coord_space)
                    if isinstance(stderr_explmeansq_map,
                                  ContrastStdErrMap) and \
                            stderr_explmeansq_map.contrast_var:
                        self.add_object(stderr_explmeansq_map.contrast_var)
                        if stderr_explmeansq_map.var_coord_space:
                            self.add_object(
                                stderr_explmeansq_map.var_coord_space)
                        if stderr_explmeansq_map.contrast_var.coord_space:
                            self.add_object(
                                stderr_explmeansq_map.contrast_var.coord_space)
                        self.add_object(
                            stderr_explmeansq_map.contrast_var.file,
                            export_file=False)
                        self.bundle.wasDerivedFrom(
                            stderr_explmeansq_map.id,
                            stderr_explmeansq_map.contrast_var.id)
                    self.add_object(stderr_explmeansq_map.file)

                    # Create Statistic Map
                    # contrast.stat_map.wasGeneratedBy(contrast.estimation)
                    self.bundle.wasGeneratedBy(contrast.stat_map.id,
                                               contrast.estimation.id)
                    self.add_object(contrast.stat_map)
                    self.add_object(contrast.stat_map.coord_space)
                    # Copy Statistical map in export directory
                    self.add_object(contrast.stat_map.file)

                    if contrast.stat_map.derfrom is not None:
                        self.bundle.wasDerivedFrom(
                            contrast.stat_map.id, contrast.stat_map.derfrom.id)
                        self.add_object(contrast.stat_map.derfrom)
                        self.add_object(contrast.stat_map.derfrom.file,
                                        export_file=False)

                    # Create Z Statistic Map
                    if contrast.z_stat_map:
                        # contrast.z_stat_map.wasGeneratedBy(contrast.estimation)
                        self.bundle.wasGeneratedBy(contrast.z_stat_map.id,
                                                   contrast.estimation.id)
                        self.add_object(contrast.z_stat_map)
                        self.add_object(contrast.z_stat_map.coord_space)
                        # Copy Statistical map in export directory
                        self.add_object(contrast.z_stat_map.file)

                    # self.add_object(contrast)

            # Add inference steps
            for contrast_id, inferences in list(self.inferences.items()):
                contrast = self._get_contrast(contrast_id)

                for inference in inferences:
                    if contrast.z_stat_map:
                        used_id = contrast.z_stat_map.id
                    else:
                        used_id = contrast.stat_map.id
                    # inference.inference_act.used(used_id)
                    self.bundle.used(inference.inference_act.id, used_id)
                    # inference.inference_act.wasAssociatedWith(self.software)
                    self.bundle.wasAssociatedWith(inference.inference_act.id,
                                                  self.software.id)

                    # self.add_object(inference)
                    # Excursion set
                    # inference.excursion_set.wasGeneratedBy(inference.inference_act)
                    self.bundle.wasGeneratedBy(inference.excursion_set.id,
                                               inference.inference_act.id)
                    self.add_object(inference.excursion_set)
                    self.add_object(inference.excursion_set.coord_space)
                    if inference.excursion_set.visu is not None:
                        self.add_object(inference.excursion_set.visu)
                        if inference.excursion_set.visu.file is not None:
                            self.add_object(inference.excursion_set.visu.file)
                    # Copy "Excursion set map" file in export directory
                    self.add_object(inference.excursion_set.file)
                    if inference.excursion_set.clust_map is not None:
                        self.add_object(inference.excursion_set.clust_map)
                        self.add_object(inference.excursion_set.clust_map.file)
                        self.add_object(
                            inference.excursion_set.clust_map.coord_space)

                    if inference.excursion_set.mip is not None:
                        self.add_object(inference.excursion_set.mip)
                        self.add_object(inference.excursion_set.mip.file)

                    # Height threshold
                    if inference.height_thresh.equiv_thresh is not None:
                        for equiv in inference.height_thresh.equiv_thresh:
                            self.add_object(equiv)
                    self.add_object(inference.height_thresh)

                    # Extent threshold
                    if inference.extent_thresh.equiv_thresh is not None:
                        for equiv in inference.extent_thresh.equiv_thresh:
                            self.add_object(equiv)
                    self.add_object(inference.extent_thresh)

                    # Display Mask (potentially more than 1)
                    if inference.disp_mask:
                        for mask in inference.disp_mask:
                            # inference.inference_act.used(mask)
                            self.bundle.used(inference.inference_act.id,
                                             mask.id)
                            self.add_object(mask)
                            # Create coordinate space entity
                            self.add_object(mask.coord_space)
                            # Create "Display Mask Map" entity
                            self.add_object(mask.file)

                            if mask.derfrom is not None:
                                self.bundle.wasDerivedFrom(
                                    mask.id, mask.derfrom.id)
                                self.add_object(mask.derfrom)
                                self.add_object(mask.derfrom.file,
                                                export_file=False)

                    # Search Space
                    self.bundle.wasGeneratedBy(inference.search_space.id,
                                               inference.inference_act.id)
                    # inference.search_space.wasGeneratedBy(inference.inference_act)
                    self.add_object(inference.search_space)
                    self.add_object(inference.search_space.coord_space)
                    # Copy "Mask map" in export directory
                    self.add_object(inference.search_space.file)

                    # Peak Definition
                    if inference.peak_criteria:
                        # inference.inference_act.used(inference.peak_criteria)
                        self.bundle.used(inference.inference_act.id,
                                         inference.peak_criteria.id)
                        self.add_object(inference.peak_criteria)

                    # Cluster Definition
                    if inference.cluster_criteria:
                        # inference.inference_act.used(inference.cluster_criteria)
                        self.bundle.used(inference.inference_act.id,
                                         inference.cluster_criteria.id)
                        self.add_object(inference.cluster_criteria)

                    if inference.clusters:
                        # Clusters and peaks
                        for cluster in inference.clusters:
                            # cluster.wasDerivedFrom(inference.excursion_set)
                            self.bundle.wasDerivedFrom(
                                cluster.id, inference.excursion_set.id)
                            self.add_object(cluster)
                            for peak in cluster.peaks:
                                self.bundle.wasDerivedFrom(peak.id, cluster.id)
                                self.add_object(peak)
                                self.add_object(peak.coordinate)

                            if cluster.cog is not None:
                                self.bundle.wasDerivedFrom(
                                    cluster.cog.id, cluster.id)
                                self.add_object(cluster.cog)
                                self.add_object(cluster.cog.coordinate)

                    # Inference activity
                    # inference.inference_act.wasAssociatedWith(inference.software_id)
                    # inference.inference_act.used(inference.height_thresh)
                    self.bundle.used(inference.inference_act.id,
                                     inference.height_thresh.id)
                    # inference.inference_act.used(inference.extent_thresh)
                    self.bundle.used(inference.inference_act.id,
                                     inference.extent_thresh.id)
                    self.bundle.used(inference.inference_act.id,
                                     analysis_masks[contrast.estimation.id])
                    self.add_object(inference.inference_act)

            # Write-out prov file
            self.save_prov_to_files()

            return self.out_dir
        except Exception:
            self.cleanup()
            raise

    def _get_model_fitting(self, mf_id):
        """
        Retreive model fitting with identifier 'mf_id' from the list of model
        fitting objects stored in self.model_fitting
        """
        for model_fitting in self.model_fittings:
            if model_fitting.activity.id == mf_id:
                return model_fitting

        raise Exception("Model fitting activity with id: " + str(mf_id) +
                        " not found.")

    def _get_contrast(self, con_id):
        """
        Retreive contrast with identifier 'con_id' from the list of contrast
        objects stored in self.contrasts
        """
        for contrasts in list(self.contrasts.values()):
            for contrast in contrasts:
                if contrast.estimation.id == con_id:
                    return contrast
        raise Exception("Contrast activity with id: " + str(con_id) +
                        " not found.")

    def _add_namespaces(self):
        """
        Add namespaces to NIDM document.
        """
        self.doc.add_namespace(NIDM)
        self.doc.add_namespace(NIIRI)
        self.doc.add_namespace(CRYPTO)
        self.doc.add_namespace(DCT)
        self.doc.add_namespace(DC)
        self.doc.add_namespace(NFO)
        self.doc.add_namespace(OBO)
        self.doc.add_namespace(SCR)
        self.doc.add_namespace(NIF)

    def _create_bundle(self, version):
        """
        Initialise NIDM-Results bundle.
        """
        # *** Bundle entity
        if not hasattr(self, 'bundle_ent'):
            self.bundle_ent = NIDMResultsBundle(nidm_version=version['num'])

        self.bundle = ProvBundle(identifier=self.bundle_ent.id)

        self.bundle_ent.export(self.version, self.export_dir)

        # # provn export
        # self.bundle = ProvBundle(identifier=bundle_id)

        self.doc.entity(self.bundle_ent.id,
                        other_attributes=self.bundle_ent.attributes)

        # *** NIDM-Results Export Activity
        if version['num'] not in ["1.0.0", "1.1.0"]:
            if not hasattr(self, 'export_act'):
                self.export_act = NIDMResultsExport()
            self.export_act.export(self.version, self.export_dir)
            # self.doc.update(self.export_act.p)
            self.doc.activity(self.export_act.id,
                              other_attributes=self.export_act.attributes)

        # *** bundle was Generated by NIDM-Results Export Activity
        if not hasattr(self, 'export_time'):
            self.export_time = str(datetime.datetime.now().time())

        if version['num'] in ["1.0.0", "1.1.0"]:
            self.doc.wasGeneratedBy(entity=self.bundle_ent.id,
                                    time=self.export_time)
        else:
            # provn
            self.doc.wasGeneratedBy(entity=self.bundle_ent.id,
                                    activity=self.export_act.id,
                                    time=self.export_time)

        # *** NIDM-Results Exporter (Software Agent)
        if version['num'] not in ["1.0.0", "1.1.0"]:
            if not hasattr(self, 'exporter'):
                self.exporter = self._get_exporter()
            self.exporter.export(self.version, self.export_dir)
            # self.doc.update(self.exporter.p)
            self.doc.agent(self.exporter.id,
                           other_attributes=self.exporter.attributes)

            self.doc.wasAssociatedWith(self.export_act.id, self.exporter.id)

    def _get_model_parameters_estimations(self, error_model):
        """
        Infer model estimation method from the 'error_model'. Return an object
        of type ModelParametersEstimation.
        """
        if error_model.dependance == NIDM_INDEPEDENT_ERROR:
            if error_model.variance_homo:
                estimation_method = STATO_OLS
            else:
                estimation_method = STATO_WLS
        else:
            estimation_method = STATO_GLS

        mpe = ModelParametersEstimation(estimation_method, self.software.id)

        return mpe

    def use_prefixes(self, ttl):
        prefix_file = os.path.join(os.path.dirname(__file__), 'prefixes.csv')
        context = dict()
        with open(prefix_file, encoding="ascii") as csvfile:
            reader = csv.reader(csvfile)
            next(reader, None)  # skip the headers
            for alphanum_id, prefix, uri in reader:
                if alphanum_id in ttl:
                    context[prefix] = uri
                    ttl = "@prefix " + prefix + ": <" + uri + "> .\n" + ttl
                    ttl = ttl.replace(alphanum_id, prefix + ":")
                    if uri in ttl:
                        ttl = ttl.replace(alphanum_id, prefix + ":")
                elif uri in ttl:
                    context[prefix] = uri
                    ttl = "@prefix " + prefix + ": <" + uri + "> .\n" + ttl
                    ttl = ttl.replace(alphanum_id, prefix + ":")
        return (ttl, context)

    def save_prov_to_files(self, showattributes=False):
        """
        Write-out provn serialisation to nidm.provn.
        """
        self.doc.add_bundle(self.bundle)
        # provn_file = os.path.join(self.export_dir, 'nidm.provn')
        # provn_fid = open(provn_file, 'w')
        # # FIXME None
        # # provn_fid.write(self.doc.get_provn(4).replace("None", "-"))
        # provn_fid.close()

        ttl_file = os.path.join(self.export_dir, 'nidm.ttl')
        ttl_txt = self.doc.serialize(format='rdf', rdf_format='turtle')
        ttl_txt, json_context = self.use_prefixes(ttl_txt)

        # Add namespaces to json-ld context
        for namespace in self.doc._namespaces.get_registered_namespaces():
            json_context[namespace._prefix] = namespace._uri
        for namespace in \
                list(self.doc._namespaces._default_namespaces.values()):
            json_context[namespace._prefix] = namespace._uri
        json_context["xsd"] = "http://www.w3.org/2000/01/rdf-schema#"

        # Work-around to issue with INF value in rdflib (reported in
        # https://github.com/RDFLib/rdflib/pull/655)
        ttl_txt = ttl_txt.replace(' inf ', ' "INF"^^xsd:float ')
        with open(ttl_file, 'w') as ttl_fid:
            ttl_fid.write(ttl_txt)

        # print(json_context)
        jsonld_file = os.path.join(self.export_dir, 'nidm.json')
        jsonld_txt = self.doc.serialize(format='rdf',
                                        rdf_format='json-ld',
                                        context=json_context)
        with open(jsonld_file, 'w') as jsonld_fid:
            jsonld_fid.write(jsonld_txt)

        # provjsonld_file = os.path.join(self.export_dir, 'nidm.provjsonld')
        # provjsonld_txt = self.doc.serialize(format='jsonld')
        # with open(provjsonld_file, 'w') as provjsonld_fid:
        #     provjsonld_fid.write(provjsonld_txt)

        # provn_file = os.path.join(self.export_dir, 'nidm.provn')
        # provn_txt = self.doc.serialize(format='provn')
        # with open(provn_file, 'w') as provn_fid:
        #     provn_fid.write(provn_txt)

        # Post-processing
        if not self.zipped:
            # Just rename temp directory to output_path
            os.rename(self.export_dir, self.out_dir)
        else:
            # Create a zip file that contains the content of the temp directory
            os.chdir(self.export_dir)
            zf = zipfile.ZipFile(os.path.join("..", self.out_dir), mode='w')
            try:
                for root, dirnames, filenames in os.walk("."):
                    for filename in filenames:
                        zf.write(os.path.join(filename))
                shutil.rmtree(os.path.join("..", self.export_dir))
            finally:
                zf.close()
                os.chdir("..")
Beispiel #4
0
class NIDMExporter():

    """
    Generic class to parse a result directory to extract the pieces of
    information to be stored in NIDM-Results and to generate a NIDM-Results
    export.
    """

    def __init__(self, version, out_dir, zipped=True):
        out_dirname = os.path.basename(out_dir)
        out_path = os.path.dirname(out_dir)

        # Create output path from output name
        self.zipped = zipped
        if not self.zipped:
            out_dirname = out_dirname+".nidm"
        else:
            out_dirname = out_dirname+".nidm.zip"
        out_dir = os.path.join(out_path, out_dirname)

        # Quit if output path already exists and user doesn't want to overwrite
        # it
        if os.path.exists(out_dir):
            msg = out_dir+" already exists, overwrite?"
            if not input("%s (y/N) " % msg).lower() == 'y':
                quit("Bye.")
            if os.path.isdir(out_dir):
                shutil.rmtree(out_dir)
            else:
                os.remove(out_dir)
        self.out_dir = out_dir

        if version == "dev":
            self.version = {'major': 10000, 'minor': 0, 'revision': 0,
                            'num': version}
        else:
            major, minor, revision = version.split(".")
            if "-rc" in revision:
                revision, rc = revision.split("-rc")
            else:
                rc = -1
            self.version = {'major': int(major), 'minor': int(minor),
                            'revision': int(revision), 'rc': int(rc),
                            'num': version}

        # Initialise prov document
        self.doc = ProvDocument()
        self._add_namespaces()

        # A temp directory that will contain the exported data
        self.export_dir = tempfile.mkdtemp(prefix="nidm-", dir=out_path)

        self.prepend_path = ''

    def parse(self):
        """
        Parse a result directory to extract the pieces information to be
        stored in NIDM-Results.
        """

        try:
            # Methods: find_software, find_model_fitting, find_contrasts and
            # find_inferences should be defined in the children classes and
            # return a list of NIDM Objects as specified in the objects module

            # Object of type Software describing the neuroimaging software
            # package used for the analysis
            self.software = self._find_software()

            # List of objects of type ModelFitting describing the
            # model fitting step in NIDM-Results (main activity: Model
            # Parameters Estimation)
            self.model_fittings = self._find_model_fitting()

            # Dictionary of (key, value) pairs where where key is a tuple
            # containing the identifier of a ModelParametersEstimation object
            # and a tuple of identifiers of ParameterEstimateMap objects and
            # value is an object of type Contrast describing the contrast
            # estimation step in NIDM-Results (main activity: Contrast
            # Estimation)
            self.contrasts = self._find_contrasts()

            # Inference activity and entities
            # Dictionary of (key, value) pairs where key is the identifier of a
            # ContrastEstimation object and value is an object of type
            # Inference describing the inference step in NIDM-Results (main
            # activity: Inference)
            self.inferences = self._find_inferences()
        except Exception:
            self.cleanup()
            raise

    def cleanup(self):
        if os.path.isdir(self.export_dir):
            shutil.rmtree(self.export_dir)

    def add_object(self, nidm_object, export_file=True):
        """
        Add a NIDMObject to a NIDM-Results export.
        """
        if not export_file:
            export_dir = None
        else:
            export_dir = self.export_dir

        if not isinstance(nidm_object, NIDMFile):
            nidm_object.export(self.version, export_dir)
        else:
            nidm_object.export(self.version, export_dir, self.prepend_path)
        # ProvDocument: add object to the bundle
        if nidm_object.prov_type == PROV['Activity']:
            self.bundle.activity(nidm_object.id,
                                 other_attributes=nidm_object.attributes)
        elif nidm_object.prov_type == PROV['Entity']:
            self.bundle.entity(nidm_object.id,
                               other_attributes=nidm_object.attributes)
        elif nidm_object.prov_type == PROV['Agent']:
            self.bundle.agent(nidm_object.id,
                              other_attributes=nidm_object.attributes)
        # self.bundle.update(nidm_object.p)

    def export(self):
        """
        Generate a NIDM-Results export.
        """
        try:
            if not os.path.isdir(self.export_dir):
                os.mkdir(self.export_dir)

            # Initialise main bundle
            self._create_bundle(self.version)

            self.add_object(self.software)

            # Add model fitting steps
            if not isinstance(self.model_fittings, list):
                self.model_fittings = list(self.model_fittings.values())

            for model_fitting in self.model_fittings:
                # Design Matrix
                # model_fitting.activity.used(model_fitting.design_matrix)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.design_matrix.id)
                self.add_object(model_fitting.design_matrix)
                # *** Export visualisation of the design matrix
                self.add_object(model_fitting.design_matrix.image)

                if model_fitting.design_matrix.image.file is not None:
                    self.add_object(model_fitting.design_matrix.image.file)

                if model_fitting.design_matrix.hrf_models is not None:
                    # drift model
                    self.add_object(model_fitting.design_matrix.drift_model)

                if self.version['major'] > 1 or \
                        (self.version['major'] == 1 and
                         self.version['minor'] >= 3):
                    # Machine
                    # model_fitting.data.wasAttributedTo(model_fitting.machine)
                    self.bundle.wasAttributedTo(model_fitting.data.id,
                                                model_fitting.machine.id)
                    self.add_object(model_fitting.machine)

                    # Imaged subject or group(s)
                    for sub in model_fitting.subjects:
                        self.add_object(sub)
                        # model_fitting.data.wasAttributedTo(sub)
                        self.bundle.wasAttributedTo(model_fitting.data.id,
                                                    sub.id)

                # Data
                # model_fitting.activity.used(model_fitting.data)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.data.id)
                self.add_object(model_fitting.data)

                # Error Model
                # model_fitting.activity.used(model_fitting.error_model)
                self.bundle.used(model_fitting.activity.id,
                                 model_fitting.error_model.id)
                self.add_object(model_fitting.error_model)

                # Parameter Estimate Maps
                for param_estimate in model_fitting.param_estimates:
                    # param_estimate.wasGeneratedBy(model_fitting.activity)
                    self.bundle.wasGeneratedBy(param_estimate.id,
                                               model_fitting.activity.id)
                    self.add_object(param_estimate)
                    self.add_object(param_estimate.coord_space)
                    self.add_object(param_estimate.file)

                    if param_estimate.derfrom is not None:
                        self.bundle.wasDerivedFrom(param_estimate.id,
                                                   param_estimate.derfrom.id)
                        self.add_object(param_estimate.derfrom)
                        self.add_object(param_estimate.derfrom.file,
                                        export_file=False)

                # Residual Mean Squares Map
                # model_fitting.rms_map.wasGeneratedBy(model_fitting.activity)
                self.add_object(model_fitting.rms_map)
                self.bundle.wasGeneratedBy(model_fitting.rms_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.rms_map.coord_space)
                self.add_object(model_fitting.rms_map.file)
                if model_fitting.rms_map.derfrom is not None:
                    self.bundle.wasDerivedFrom(
                        model_fitting.rms_map.id,
                        model_fitting.rms_map.derfrom.id)
                    self.add_object(model_fitting.rms_map.derfrom)
                    self.add_object(model_fitting.rms_map.derfrom.file,
                                    export_file=False)

                # Resels per Voxel Map
                if model_fitting.rpv_map is not None:
                    self.add_object(model_fitting.rpv_map)
                    self.bundle.wasGeneratedBy(model_fitting.rpv_map.id,
                                               model_fitting.activity.id)
                    self.add_object(model_fitting.rpv_map.coord_space)
                    self.add_object(model_fitting.rpv_map.file)
                    if model_fitting.rpv_map.inf_id is not None:
                        self.bundle.used(model_fitting.rpv_map.inf_id,
                                         model_fitting.rpv_map.id)
                    if model_fitting.rpv_map.derfrom is not None:
                        self.bundle.wasDerivedFrom(
                            model_fitting.rpv_map.id,
                            model_fitting.rpv_map.derfrom.id)
                        self.add_object(model_fitting.rpv_map.derfrom)
                        self.add_object(model_fitting.rpv_map.derfrom.file,
                                        export_file=False)

                # Mask
                # model_fitting.mask_map.wasGeneratedBy(model_fitting.activity)
                self.bundle.wasGeneratedBy(model_fitting.mask_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.mask_map)
                if model_fitting.mask_map.derfrom is not None:
                    self.bundle.wasDerivedFrom(
                        model_fitting.mask_map.id,
                        model_fitting.mask_map.derfrom.id)
                    self.add_object(model_fitting.mask_map.derfrom)
                    self.add_object(model_fitting.mask_map.derfrom.file,
                                    export_file=False)

                # Create coordinate space export
                self.add_object(model_fitting.mask_map.coord_space)
                # Create "Mask map" entity
                self.add_object(model_fitting.mask_map.file)

                # Grand Mean map
                # model_fitting.grand_mean_map.wasGeneratedBy(model_fitting.activity)
                self.bundle.wasGeneratedBy(model_fitting.grand_mean_map.id,
                                           model_fitting.activity.id)
                self.add_object(model_fitting.grand_mean_map)
                # Coordinate space entity
                self.add_object(model_fitting.grand_mean_map.coord_space)
                # Grand Mean Map entity
                self.add_object(model_fitting.grand_mean_map.file)

                # Model Parameters Estimation activity
                self.add_object(model_fitting.activity)
                self.bundle.wasAssociatedWith(model_fitting.activity.id,
                                              self.software.id)
                # model_fitting.activity.wasAssociatedWith(self.software)
                # self.add_object(model_fitting)

            # Add contrast estimation steps
            analysis_masks = dict()
            for (model_fitting_id, pe_ids), contrasts in list(
                    self.contrasts.items()):
                for contrast in contrasts:
                    model_fitting = self._get_model_fitting(model_fitting_id)
                    # for contrast in contrasts:
                    # contrast.estimation.used(model_fitting.rms_map)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.rms_map.id)
                    # contrast.estimation.used(model_fitting.mask_map)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.mask_map.id)
                    analysis_masks[contrast.estimation.id] = \
                        model_fitting.mask_map.id
                    self.bundle.used(contrast.estimation.id,
                                     contrast.weights.id)
                    self.bundle.used(contrast.estimation.id,
                                     model_fitting.design_matrix.id)
                    # contrast.estimation.wasAssociatedWith(self.software)
                    self.bundle.wasAssociatedWith(contrast.estimation.id,
                                                  self.software.id)

                    for pe_id in pe_ids:
                        # contrast.estimation.used(pe_id)
                        self.bundle.used(contrast.estimation.id, pe_id)

                    # Create estimation activity
                    self.add_object(contrast.estimation)

                    # Create contrast weights
                    self.add_object(contrast.weights)

                    if contrast.contrast_map is not None:
                        # Create contrast Map
                        # contrast.contrast_map.wasGeneratedBy(contrast.estimation)
                        self.bundle.wasGeneratedBy(contrast.contrast_map.id,
                                                   contrast.estimation.id)
                        self.add_object(contrast.contrast_map)
                        self.add_object(contrast.contrast_map.coord_space)
                        # Copy contrast map in export directory
                        self.add_object(contrast.contrast_map.file)

                        if contrast.contrast_map.derfrom is not None:
                            self.bundle.wasDerivedFrom(
                                contrast.contrast_map.id,
                                contrast.contrast_map.derfrom.id)
                            self.add_object(contrast.contrast_map.derfrom)
                            self.add_object(contrast.contrast_map.derfrom.file,
                                            export_file=False)

                    # Create Std Err. Map (T-tests) or Explained Mean Sq. Map
                    # (F-tests)
                    # contrast.stderr_or_expl_mean_sq_map.wasGeneratedBy
                    # (contrast.estimation)
                    stderr_explmeansq_map = (
                        contrast.stderr_or_expl_mean_sq_map)
                    self.bundle.wasGeneratedBy(
                        stderr_explmeansq_map.id,
                        contrast.estimation.id)
                    self.add_object(stderr_explmeansq_map)
                    self.add_object(
                        stderr_explmeansq_map.coord_space)
                    if isinstance(stderr_explmeansq_map,
                                  ContrastStdErrMap) and \
                            stderr_explmeansq_map.contrast_var:
                        self.add_object(
                            stderr_explmeansq_map.contrast_var)
                        if stderr_explmeansq_map.var_coord_space:
                            self.add_object(
                                stderr_explmeansq_map.var_coord_space)
                        if stderr_explmeansq_map.contrast_var.coord_space:
                            self.add_object(
                                stderr_explmeansq_map.contrast_var.coord_space)
                        self.add_object(
                            stderr_explmeansq_map.contrast_var.file,
                            export_file=False)
                        self.bundle.wasDerivedFrom(
                            stderr_explmeansq_map.id,
                            stderr_explmeansq_map.contrast_var.id)
                    self.add_object(stderr_explmeansq_map.file)

                    # Create Statistic Map
                    # contrast.stat_map.wasGeneratedBy(contrast.estimation)
                    self.bundle.wasGeneratedBy(contrast.stat_map.id,
                                               contrast.estimation.id)
                    self.add_object(contrast.stat_map)
                    self.add_object(contrast.stat_map.coord_space)
                    # Copy Statistical map in export directory
                    self.add_object(contrast.stat_map.file)

                    if contrast.stat_map.derfrom is not None:
                        self.bundle.wasDerivedFrom(
                            contrast.stat_map.id,
                            contrast.stat_map.derfrom.id)
                        self.add_object(contrast.stat_map.derfrom)
                        self.add_object(contrast.stat_map.derfrom.file,
                                        export_file=False)

                    # Create Z Statistic Map
                    if contrast.z_stat_map:
                        # contrast.z_stat_map.wasGeneratedBy(contrast.estimation)
                        self.bundle.wasGeneratedBy(contrast.z_stat_map.id,
                                                   contrast.estimation.id)
                        self.add_object(contrast.z_stat_map)
                        self.add_object(contrast.z_stat_map.coord_space)
                        # Copy Statistical map in export directory
                        self.add_object(contrast.z_stat_map.file)

                    # self.add_object(contrast)

            # Add inference steps
            for contrast_id, inferences in list(self.inferences.items()):
                contrast = self._get_contrast(contrast_id)

                for inference in inferences:
                    if contrast.z_stat_map:
                        used_id = contrast.z_stat_map.id
                    else:
                        used_id = contrast.stat_map.id
                    # inference.inference_act.used(used_id)
                    self.bundle.used(inference.inference_act.id, used_id)
                    # inference.inference_act.wasAssociatedWith(self.software)
                    self.bundle.wasAssociatedWith(inference.inference_act.id,
                                                  self.software.id)

                    # self.add_object(inference)
                    # Excursion set
                    # inference.excursion_set.wasGeneratedBy(inference.inference_act)
                    self.bundle.wasGeneratedBy(inference.excursion_set.id,
                                               inference.inference_act.id)
                    self.add_object(inference.excursion_set)
                    self.add_object(inference.excursion_set.coord_space)
                    if inference.excursion_set.visu is not None:
                        self.add_object(inference.excursion_set.visu)
                        if inference.excursion_set.visu.file is not None:
                            self.add_object(inference.excursion_set.visu.file)
                    # Copy "Excursion set map" file in export directory
                    self.add_object(inference.excursion_set.file)
                    if inference.excursion_set.clust_map is not None:
                        self.add_object(inference.excursion_set.clust_map)
                        self.add_object(inference.excursion_set.clust_map.file)
                        self.add_object(
                            inference.excursion_set.clust_map.coord_space)

                    if inference.excursion_set.mip is not None:
                        self.add_object(inference.excursion_set.mip)
                        self.add_object(inference.excursion_set.mip.file)

                    # Height threshold
                    if inference.height_thresh.equiv_thresh is not None:
                        for equiv in inference.height_thresh.equiv_thresh:
                            self.add_object(equiv)
                    self.add_object(inference.height_thresh)

                    # Extent threshold
                    if inference.extent_thresh.equiv_thresh is not None:
                        for equiv in inference.extent_thresh.equiv_thresh:
                            self.add_object(equiv)
                    self.add_object(inference.extent_thresh)

                    # Display Mask (potentially more than 1)
                    if inference.disp_mask:
                        for mask in inference.disp_mask:
                            # inference.inference_act.used(mask)
                            self.bundle.used(inference.inference_act.id,
                                             mask.id)
                            self.add_object(mask)
                            # Create coordinate space entity
                            self.add_object(mask.coord_space)
                            # Create "Display Mask Map" entity
                            self.add_object(mask.file)

                            if mask.derfrom is not None:
                                self.bundle.wasDerivedFrom(mask.id,
                                                           mask.derfrom.id)
                                self.add_object(mask.derfrom)
                                self.add_object(mask.derfrom.file,
                                                export_file=False)

                    # Search Space
                    self.bundle.wasGeneratedBy(inference.search_space.id,
                                               inference.inference_act.id)
                    # inference.search_space.wasGeneratedBy(inference.inference_act)
                    self.add_object(inference.search_space)
                    self.add_object(inference.search_space.coord_space)
                    # Copy "Mask map" in export directory
                    self.add_object(inference.search_space.file)

                    # Peak Definition
                    if inference.peak_criteria:
                        # inference.inference_act.used(inference.peak_criteria)
                        self.bundle.used(inference.inference_act.id,
                                         inference.peak_criteria.id)
                        self.add_object(inference.peak_criteria)

                    # Cluster Definition
                    if inference.cluster_criteria:
                        # inference.inference_act.used(inference.cluster_criteria)
                        self.bundle.used(inference.inference_act.id,
                                         inference.cluster_criteria.id)
                        self.add_object(inference.cluster_criteria)

                    if inference.clusters:
                        # Clusters and peaks
                        for cluster in inference.clusters:
                            # cluster.wasDerivedFrom(inference.excursion_set)
                            self.bundle.wasDerivedFrom(
                                cluster.id, inference.excursion_set.id)
                            self.add_object(cluster)
                            for peak in cluster.peaks:
                                self.bundle.wasDerivedFrom(peak.id, cluster.id)
                                self.add_object(peak)
                                self.add_object(peak.coordinate)

                            if cluster.cog is not None:
                                self.bundle.wasDerivedFrom(cluster.cog.id,
                                                           cluster.id)
                                self.add_object(cluster.cog)
                                self.add_object(cluster.cog.coordinate)

                    # Inference activity
                    # inference.inference_act.wasAssociatedWith(inference.software_id)
                    # inference.inference_act.used(inference.height_thresh)
                    self.bundle.used(inference.inference_act.id,
                                     inference.height_thresh.id)
                    # inference.inference_act.used(inference.extent_thresh)
                    self.bundle.used(inference.inference_act.id,
                                     inference.extent_thresh.id)
                    self.bundle.used(inference.inference_act.id,
                                     analysis_masks[contrast.estimation.id])
                    self.add_object(inference.inference_act)

            # Write-out prov file
            self.save_prov_to_files()

            return self.out_dir
        except Exception:
            self.cleanup()
            raise

    def _get_model_fitting(self, mf_id):
        """
        Retreive model fitting with identifier 'mf_id' from the list of model
        fitting objects stored in self.model_fitting
        """
        for model_fitting in self.model_fittings:
            if model_fitting.activity.id == mf_id:
                return model_fitting

        raise Exception("Model fitting activity with id: " + str(mf_id) +
                        " not found.")

    def _get_contrast(self, con_id):
        """
        Retreive contrast with identifier 'con_id' from the list of contrast
        objects stored in self.contrasts
        """
        for contrasts in list(self.contrasts.values()):
            for contrast in contrasts:
                if contrast.estimation.id == con_id:
                    return contrast
        raise Exception("Contrast activity with id: " + str(con_id) +
                        " not found.")

    def _add_namespaces(self):
        """
        Add namespaces to NIDM document.
        """
        self.doc.add_namespace(NIDM)
        self.doc.add_namespace(NIIRI)
        self.doc.add_namespace(CRYPTO)
        self.doc.add_namespace(DCT)
        self.doc.add_namespace(DC)
        self.doc.add_namespace(NFO)
        self.doc.add_namespace(OBO)
        self.doc.add_namespace(SCR)
        self.doc.add_namespace(NIF)

    def _create_bundle(self, version):
        """
        Initialise NIDM-Results bundle.
        """
        # *** Bundle entity
        if not hasattr(self, 'bundle_ent'):
            self.bundle_ent = NIDMResultsBundle(nidm_version=version['num'])

        self.bundle = ProvBundle(identifier=self.bundle_ent.id)

        self.bundle_ent.export(self.version, self.export_dir)

        # # provn export
        # self.bundle = ProvBundle(identifier=bundle_id)

        self.doc.entity(self.bundle_ent.id,
                        other_attributes=self.bundle_ent.attributes)

        # *** NIDM-Results Export Activity
        if version['num'] not in ["1.0.0", "1.1.0"]:
            if not hasattr(self, 'export_act'):
                self.export_act = NIDMResultsExport()
            self.export_act.export(self.version, self.export_dir)
            # self.doc.update(self.export_act.p)
            self.doc.activity(self.export_act.id,
                              other_attributes=self.export_act.attributes)

        # *** bundle was Generated by NIDM-Results Export Activity
        if not hasattr(self, 'export_time'):
            self.export_time = str(datetime.datetime.now().time())

        if version['num'] in ["1.0.0", "1.1.0"]:
            self.doc.wasGeneratedBy(entity=self.bundle_ent.id,
                                    time=self.export_time)
        else:
            # provn
            self.doc.wasGeneratedBy(
                entity=self.bundle_ent.id, activity=self.export_act.id,
                time=self.export_time)

        # *** NIDM-Results Exporter (Software Agent)
        if version['num'] not in ["1.0.0", "1.1.0"]:
            if not hasattr(self, 'exporter'):
                self.exporter = self._get_exporter()
            self.exporter.export(self.version, self.export_dir)
            # self.doc.update(self.exporter.p)
            self.doc.agent(self.exporter.id,
                           other_attributes=self.exporter.attributes)

            self.doc.wasAssociatedWith(self.export_act.id, self.exporter.id)

    def _get_model_parameters_estimations(self, error_model):
        """
        Infer model estimation method from the 'error_model'. Return an object
        of type ModelParametersEstimation.
        """
        if error_model.dependance == NIDM_INDEPEDENT_ERROR:
            if error_model.variance_homo:
                estimation_method = STATO_OLS
            else:
                estimation_method = STATO_WLS
        else:
            estimation_method = STATO_GLS

        mpe = ModelParametersEstimation(estimation_method, self.software.id)

        return mpe

    def use_prefixes(self, ttl):
        prefix_file = os.path.join(os.path.dirname(__file__), 'prefixes.csv')
        context = dict()
        with open(prefix_file, encoding="ascii") as csvfile:
            reader = csv.reader(csvfile)
            next(reader, None)  # skip the headers
            for alphanum_id, prefix, uri in reader:
                if alphanum_id in ttl:
                    context[prefix] = uri
                    ttl = "@prefix " + prefix + ": <" + uri + "> .\n" + ttl
                    ttl = ttl.replace(alphanum_id, prefix + ":")
                    if uri in ttl:
                        ttl = ttl.replace(alphanum_id, prefix + ":")
                elif uri in ttl:
                    context[prefix] = uri
                    ttl = "@prefix " + prefix + ": <" + uri + "> .\n" + ttl
                    ttl = ttl.replace(alphanum_id, prefix + ":")
        return (ttl, context)

    def save_prov_to_files(self, showattributes=False):
        """
        Write-out provn serialisation to nidm.provn.
        """
        self.doc.add_bundle(self.bundle)
        # provn_file = os.path.join(self.export_dir, 'nidm.provn')
        # provn_fid = open(provn_file, 'w')
        # # FIXME None
        # # provn_fid.write(self.doc.get_provn(4).replace("None", "-"))
        # provn_fid.close()

        ttl_file = os.path.join(self.export_dir, 'nidm.ttl')
        ttl_txt = self.doc.serialize(format='rdf', rdf_format='turtle')
        ttl_txt, json_context = self.use_prefixes(ttl_txt)

        # Add namespaces to json-ld context
        for namespace in self.doc._namespaces.get_registered_namespaces():
            json_context[namespace._prefix] = namespace._uri
        for namespace in \
                list(self.doc._namespaces._default_namespaces.values()):
            json_context[namespace._prefix] = namespace._uri
        json_context["xsd"] = "http://www.w3.org/2000/01/rdf-schema#"

        # Work-around to issue with INF value in rdflib (reported in
        # https://github.com/RDFLib/rdflib/pull/655)
        ttl_txt = ttl_txt.replace(' inf ', ' "INF"^^xsd:float ')
        with open(ttl_file, 'w') as ttl_fid:
            ttl_fid.write(ttl_txt)

        # print(json_context)
        jsonld_file = os.path.join(self.export_dir, 'nidm.json')
        jsonld_txt = self.doc.serialize(format='rdf', rdf_format='json-ld',
                                        context=json_context)
        with open(jsonld_file, 'w') as jsonld_fid:
            jsonld_fid.write(jsonld_txt)

        # provjsonld_file = os.path.join(self.export_dir, 'nidm.provjsonld')
        # provjsonld_txt = self.doc.serialize(format='jsonld')
        # with open(provjsonld_file, 'w') as provjsonld_fid:
        #     provjsonld_fid.write(provjsonld_txt)

        # provn_file = os.path.join(self.export_dir, 'nidm.provn')
        # provn_txt = self.doc.serialize(format='provn')
        # with open(provn_file, 'w') as provn_fid:
        #     provn_fid.write(provn_txt)

        # Post-processing
        if not self.zipped:
            # Just rename temp directory to output_path
            os.rename(self.export_dir, self.out_dir)
        else:
            # Create a zip file that contains the content of the temp directory
            os.chdir(self.export_dir)
            zf = zipfile.ZipFile(os.path.join("..", self.out_dir), mode='w')
            try:
                for root, dirnames, filenames in os.walk("."):
                    for filename in filenames:
                        zf.write(os.path.join(filename))
            finally:
                zf.close()
                # Need to move up before deleting the folder
                os.chdir("..")
                shutil.rmtree(os.path.join("..", self.export_dir))