class SnapshotModel(BaseModel): __tablename__ = 'snapshot' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) #:Metadata for IAP measurement_tool = db.Column(db.String) #:The ID of the phenobox which was used to create this snapshot phenobox_id = db.Column(UUID(as_uuid=True), nullable=False) #:Metadata for IAP camera_position = db.Column( ENUM('vis.side', 'vis.top', 'fluo.top', 'fluo.side', 'nir.top', 'nir.side', 'ir.top', 'ir.side', name='camera_position_enum'), nullable=False, server_default='vis.side') #:Flag used to exclude this snapshot from analysis excluded = db.Column(db.Boolean, server_default='f', default=False, nullable=False) #:Foreign key to the corresponding Timestamp timestamp_id = db.Column(db.Integer, db.ForeignKey('timestamp.id'), nullable=False) #:SQLAlchemy relationship to the corresponding Timestamp timestamp = db.relationship("TimestampModel", back_populates="snapshots", single_parent=True) #:Foreign key to the corresponding Timestamp plant_id = db.Column(db.Integer, db.ForeignKey('plant.id'), nullable=False) #:SQLAlchemy relationship to the corresponding Plant plant = db.relationship("PlantModel", back_populates="snapshots", single_parent=True) #:SQLAlchemy relationship to all images belonging to this snapshot images = db.relationship("ImageModel", back_populates="snapshot", cascade="all, delete-orphan", lazy='dynamic') #:SQLAlchemy relationship to all analyses performed on this snapshot postprocesses = db.relationship("PostprocessModel", secondary='postprocess_snapshot', back_populates='snapshots') db.UniqueConstraint(plant_id, timestamp_id, name=u'uq_snapshot_plant_id_timestamp_id') def purge(self): # Only allow to delete a snapshot from an uncompleted timestamp if not self.timestamp.completed: shared_folder_map = current_app.config['SHARED_FOLDER_MAP'] raw_image_path = None for image in self.images: if image.type == 'raw': if raw_image_path is None: raw_image_path = get_local_path_from_smb(image.path, shared_folder_map) os.remove(os.path.join(raw_image_path, image.filename)) db.session.delete(image) db.session.delete(self) db.session.commit() return True else: return False # TODO throw exceptions instead of returning true or false def __init__(self, plant_id, timestamp_id, camera_position, measurement_tool, phenobox_id): self.plant_id = plant_id self.timestamp_id = timestamp_id self.camera_position = camera_position self.measurement_tool = measurement_tool self.phenobox_id = phenobox_id def __repr__(self): return '<Snapshot %d of plant %r>' % (self.id, self.plant.name)
class AnalysisModel(BaseModel): __tablename__ = 'analysis' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) #:The IAP ID of the Analyis results in IAP iap_id = db.Column(db.String, unique=True) #:The path, as SMB URL, where the exported IAP results are stored export_path = db.Column(db.String, nullable=True) #:The id of the IAP pipeline with which this analysis has been processed pipeline_id = db.Column(db.String, nullable=False) #:Timestamp to indicate the start time of the analysis started_at = db.Column(db.DateTime, nullable=True) #:Timestamp to indicate the end time of the analysis finished_at = db.Column(db.DateTime, nullable=True) #:Foreign key to the corresponding Timestamp timestamp_id = db.Column(db.Integer, db.ForeignKey('timestamp.id'), nullable=False) #:SQLAlchemy relationship to the corresponding Timestamp timestamp = db.relationship("TimestampModel", back_populates="analyses", single_parent=True) #:SQLAlchemy relationship to all Postprocessings which have been applied to this analysis postprocessings = db.relationship("PostprocessModel", back_populates="analysis", cascade="all, delete-orphan") #:SQLAlchemy relationship to all Snapshots processed by this analysis db.UniqueConstraint(timestamp_id, pipeline_id, name=u'uq_analysis_timestamp_id_pipeline_id') @staticmethod def get_or_create(timestamp_id, pipeline_id, session=None): if session is None: session = db.session try: return session.query(AnalysisModel).filter_by(timestamp_id=timestamp_id, pipeline_id=pipeline_id).one(), False except NoResultFound: entry = AnalysisModel(timestamp_id, pipeline_id) try: session.add(entry) session.flush() return entry, True except IntegrityError: session.rollback() return session.query(AnalysisModel).filter_by(timestamp_id=timestamp_id, pipeline_id=pipeline_id).one(), False def purge(self): shared_folder_map = current_app.config['SHARED_FOLDER_MAP'] for postprocess in self.postprocessings: postprocess.purge() local_path = get_local_path_from_smb(self.report_path, shared_folder_map) shutil.rmtree(local_path) for image in self.timestamp.snapshots.images.where(ImageModel.type == 'segmented'): db.session.delete(image) def __init__(self, timestamp_id, pipeline_id): self.timestamp_id = timestamp_id self.pipeline_id = pipeline_id def __repr__(self): return '<Analysis %d with Pipeline %r of timestamp %d>' % (self.id, self.pipeline_id, self.timestamp_id)
class ImageModel(BaseModel): __tablename__ = 'image' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) #:The path, as SMB URL, to the image file path = db.Column(db.String, nullable=False) #:The filename of the image filename = db.Column(db.String, nullable=False) #:The type of the image. Indicates whether this image was processed or not type = db.Column(ENUM('raw', 'segmented', name='image_type_enum'), default="raw", server_default='raw', nullable=False) #:The angle at which the image was taken angle = db.Column(db.Integer, nullable=False) #:Foreign key to the corresponding Snapshot snapshot_id = db.Column(db.Integer, db.ForeignKey('snapshot.id'), nullable=False) #:SQLAlchemy relationship to the corresponding Snapshot snapshot = db.relationship("SnapshotModel", back_populates="images", single_parent=True) db.UniqueConstraint(snapshot_id, type, angle, name=u'uq_image_snapshot_id_type_angle') db.UniqueConstraint(path, filename, name=u'uq_image_path_filename') def __init__(self, snapshot_id, path, filename, angle, image_type): self.snapshot_id = snapshot_id self.path = path self.filename = filename self.type = image_type self.angle = angle def __repr__(self): return '<Image %d of plant %r>' % (self.id, self.filename)
class PlantModel(BaseModel): __tablename__ = 'plant' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) #:The detailed name of the plant name = db.Column(db.String(80)) #:The index of the plant in the group index = db.Column(db.Integer) #:Foreign key to the corresponding Sample Group sample_group_id = db.Column(db.Integer, db.ForeignKey('sample_group.id')) #:SQLAlchemy relationship to the corresponding Sample Group sample_group = db.relationship("SampleGroupModel", back_populates="plants", single_parent=True) #:SQLAlchemy relationship to all Snapshots that belong to this plant snapshots = db.relationship("SnapshotModel", back_populates="plant", cascade="all, delete-orphan") db.UniqueConstraint(index, sample_group_id, name=u'uq_plant_index_sample_group_id') @hybrid_property def full_name(self): if self.name != '': return '{}_{}_{}'.format(self.sample_group.name, self.index, self.name) else: return '{}_{}'.format(self.sample_group.name, self.index) def purge(self): for snapshot in self.snapshots: snapshot.purge() def __init__(self, index, name, sample_group_id): self.index = index self.name = name self.sample_group_id = sample_group_id def __repr__(self): return '<Plant %r>' % self.name
class SampleGroupModel(BaseModel): __tablename__ = 'sample_group' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) #:The name of the sample group name = db.Column(db.String(80), nullable=False) #:A detailed description of the group description = db.Column(db.Text) #:Metadata for IAP species = db.Column(db.String) #:Metadata for IAP genotype = db.Column(db.String) #:Metadata for IAP variety = db.Column(db.String) #:Metadata for IAP growth_conditions = db.Column(db.String) #:Metadata for IAP treatment = db.Column( db.String, nullable=True ) # TODO change to False after all values have been updated #:Indicates whether this group is a control group or not is_control = db.Column(db.Boolean, nullable=False, default=False, server_default='f') #:Foreign key to the corresponding Experiment experiment_id = db.Column(db.Integer, db.ForeignKey('experiment.id'), nullable=False) #:SQLAlchemy relationship to the corresponding Experiment experiment = db.relationship("ExperimentModel", back_populates="sample_groups", single_parent=True) #:SQLAlchemy relationship to all plants belonging to this sample group plants = db.relationship("PlantModel", back_populates="sample_group", cascade="all, delete-orphan") db.UniqueConstraint(name, experiment_id) db.UniqueConstraint(treatment, experiment_id) def __init__(self, name, treatment, description, species, genotype, variety, growth_conditions, experiment_id, is_control=False): if name and name.strip() == "": name = None if treatment and treatment.strip() == "": treatment = None self.name = name self.treatment = treatment self.description = description self.experiment_id = experiment_id self.species = species self.genotype = genotype self.variety = variety self.growth_conditions = growth_conditions self.is_control = is_control @classmethod def fromdict(self, group_data, experiment_id): for key, value in group_data.items(): if type(value) is unicode and value.strip() == "": del group_data[key] return self(group_data.get('name'), group_data.get('treatment'), group_data.get('description'), group_data.get('species'), group_data.get('genotype'), group_data.get('variety'), group_data.get('growth_conditions'), experiment_id=experiment_id, is_control=group_data.get('is_control')) def __repr__(self): return '<SampleGroup %r>' % self.name
class PostprocessModel(BaseModel): __tablename__ = 'postprocess' #:Primary key of the Model id = db.Column(db.Integer, primary_key=True) snapshot_hash = db.Column(db.BIGINT, nullable=False) #:Path, as SMB URL, to the results of this postprocess result_path = db.Column(db.String, nullable=True) #:The ID of the postprocessing stack that was applied postprocessing_stack_id = db.Column(db.String, nullable=False) #:The ID of the sample group used as control group control_group_id = db.Column(db.Integer, db.ForeignKey("sample_group.id"), nullable=False) #:Timestamp to indicate the start time of the postprocessing started_at = db.Column(db.DateTime, nullable=True) #:Timestamp to indicate the end time of the postprocessing finished_at = db.Column(db.DateTime, nullable=True) #:A note from the user to make it easier to identify a postprocess note = db.Column(db.Text, nullable=True) #:Foreign key to the corresponding analysis analysis_id = db.Column(db.Integer, db.ForeignKey('analysis.id'), nullable=False) #:SQLAlchemy relationship to the corresponding analysis analysis = db.relationship("AnalysisModel", back_populates="postprocessings", single_parent=True) #:SQLAlchemy relationship to all Snapshots processed by this postprocess snapshots = db.relationship("SnapshotModel", secondary='postprocess_snapshot', back_populates='postprocesses') #:SQLAlchemy relationship to the sample group used as control group control_group = db.relationship("SampleGroupModel") db.UniqueConstraint( snapshot_hash, postprocessing_stack_id, analysis_id, control_group_id, name= u'uq_postprocess_snapshot_hash_postprocessing_stack_id_analysis_id_control_group_id' ) def purge(self): shared_folder_map = current_app.config['SHARED_FOLDER_MAP'] local_path = get_local_path_from_smb(self.result_path, shared_folder_map) shutil.rmtree(local_path) @staticmethod def calculate_snapshot_hash(snapshots): return hash(frozenset([snapshot.id for snapshot in snapshots])) @staticmethod def get_or_create(analysis_id, postprocessing_stack_id, control_group_id, snapshots, note=None, session=None): snap_hash = PostprocessModel.calculate_snapshot_hash(snapshots) if session is None: session = db.session try: return session.query(PostprocessModel).filter_by( analysis_id=analysis_id, postprocessing_stack_id=postprocessing_stack_id, snapshot_hash=snap_hash).one(), False except NoResultFound: entry = PostprocessModel(analysis_id, postprocessing_stack_id, control_group_id, snapshots, note, snap_hash) try: session.add(entry) session.flush() return entry, True except IntegrityError: session.rollback() return session.query(PostprocessModel).filter_by( analyis_id=analysis_id, postprocessing_stack_id=postprocessing_stack_id, snapshot_hash=snap_hash).one(), False def __init__(self, analysis_id, postprocessing_stack_id, control_group_id, snapshots, note=None, snapshot_hash=None): self.analysis_id = analysis_id self.postprocessing_stack_id = postprocessing_stack_id self.control_group_id = control_group_id self.snapshots = snapshots self.note = note # TODO always calculate snapshot hash to prevent incorrect hashes? if snapshot_hash is not None: self.snapshot_hash = snapshot_hash else: self.snapshot_hash = self.calculate_snapshot_hash(snapshots) def __repr__(self): return '<Postprocessing %d with stack %r of analysis %d>' % ( self.id, self.postprocessing_stack_id, self.analysis_id)