parser.add_argument( '--cytomine_id_tags_for_images', help= "List of tags (id), separated by comma, that images must have to be used in dataset. " "If unset, all images in the project are used.", default=None) parser.add_argument('--cytomine_id_job') params, _ = parser.parse_known_args(sys.argv[1:]) with Cytomine(params.cytomine_host, params.cytomine_public_key, params.cytomine_private_key) as c: id_tags_for_images = params.cytomine_id_tags_for_images id_project = params.cytomine_id_project image_tags = id_tags_for_images if id_tags_for_images else None images = ImageInstanceCollection(tags=image_tags).fetch_with_filter( "project", id_project) image_ids = [image.id for image in images] groundtruths = AnnotationCollection() groundtruths.showTerm = True groundtruths.showWKT = True groundtruths.images = image_ids groundtruths.fetch() predictions = AnnotationCollection() predictions.showTerm = True predictions.showWKT = True predictions.images = image_ids predictions.job = params.cytomine_id_job predictions.fetch()
def main(argv): with CytomineJob.from_cli(argv) as conn: conn.job.update(status=Job.RUNNING, progress=0, statusComment="Initialization...") base_path = os.getenv("HOME") # Mandatory for Singularity working_path = os.path.join(base_path, str(conn.job.id)) # Loading pre-trained Stardist model np.random.seed(17) # use local model file in ~/models/2D_versatile_HE/ model = StarDist2D(None, name='2D_versatile_HE', basedir='/models/') # Select images to process images = ImageInstanceCollection().fetch_with_filter( "project", conn.parameters.cytomine_id_project) if conn.parameters.cytomine_id_images == 'all': list_imgs = [int(image.id) for image in images] else: list_imgs = [ int(id_img) for id_img in conn.parameters.cytomine_id_images.split(',') ] # Go over images for id_image in conn.monitor(list_imgs, prefix="Running detection on image", period=0.1): # Dump ROI annotations in img from Cytomine server to local images roi_annotations = AnnotationCollection( project=conn.parameters.cytomine_id_project, term=conn.parameters.cytomine_id_roi_term, image=id_image, showWKT=True).fetch() print(roi_annotations) # Go over ROI in this image for roi in roi_annotations: # Get Cytomine ROI coordinates for remapping to whole-slide # Cytomine cartesian coordinate system, (0,0) is bottom left corner print( "----------------------------ROI------------------------------" ) roi_geometry = wkt.loads(roi.location) print(f"ROI Geometry from Shapely: {roi_geometry}") print("ROI Bounds") print(roi_geometry.bounds) minx, miny = roi_geometry.bounds[0], roi_geometry.bounds[3] # Dump ROI image into local PNG file roi_path = os.path.join(working_path, str(roi_annotations.project), str(roi_annotations.image), str(roi.id)) roi_png_filename = os.path.join(roi_path, f'{roi.id}.png') print(f"roi_png_filename: {roi_png_filename}") roi.dump(dest_pattern=roi_png_filename, mask=True, alpha=True) # Stardist works with TIFF images without alpha channel, flattening PNG alpha mask to TIFF RGB im = Image.open(roi_png_filename) bg = Image.new("RGB", im.size, (255, 255, 255)) bg.paste(im, mask=im.split()[3]) roi_tif_filename = os.path.join(roi_path, f'{roi.id}.tif') bg.save(roi_tif_filename, quality=100) X_files = sorted(glob(os.path.join(roi_path, f'{roi.id}*.tif'))) X = list(map(imread, X_files)) n_channel = 3 if X[0].ndim == 3 else X[0].shape[-1] axis_norm = ( 0, 1 ) # normalize channels independently (0,1,2) normalize channels jointly if n_channel > 1: type = 'jointly' if axis_norm is None or 2 in axis_norm else 'independently' print(f"Normalizing image channels {type}.") # Going over ROI images in ROI directory (in our case: one ROI per directory) for x in range(0, len(X)): print( f"------------------- Processing ROI file {X}: {roi_tif_filename}" ) img = normalize(X[x], conn.parameters.stardist_norm_perc_low, conn.parameters.stardist_norm_perc_high, axis=axis_norm) # Stardist model prediction with thresholds labels, details = model.predict_instances( img, prob_thresh=conn.parameters.stardist_prob_t, nms_thresh=conn.parameters.stardist_nms_t) print("Number of detected polygons: %d" % len(details['coord'])) cytomine_annotations = AnnotationCollection() # Go over detections in this ROI, convert and upload to Cytomine for polygroup in details['coord']: # Converting to Shapely annotation points = list() for i in range(len(polygroup[0])): # Cytomine cartesian coordinate system, (0,0) is bottom left corner # Mapping Stardist polygon detection coordinates to Cytomine ROI in whole slide image p = Point(minx + polygroup[1][i], miny - polygroup[0][i]) points.append(p) annotation = Polygon(points) # Append to Annotation collection cytomine_annotations.append( Annotation( location=annotation.wkt, id_image= id_image, # conn.parameters.cytomine_id_image, id_project=conn.parameters.cytomine_id_project, id_terms=[ conn.parameters.cytomine_id_cell_term ])) print(".", end='', flush=True) print() # Send Annotation Collection (for this ROI) to Cytomine server in one http request cytomine_annotations.save() conn.job.update(status=Job.TERMINATED, progress=100, statusComment="Finished.")
def main(argv): with CytomineJob.from_cli(argv) as cj: images = ImageInstanceCollection().fetch_with_filter( "project", cj.parameters.cytomine_id_project) for image in cj.monitor(images, prefix="Running detection on image", period=0.1): # Resize image if needed resize_ratio = max(image.width, image.height) / cj.parameters.max_image_size if resize_ratio < 1: resize_ratio = 1 resized_width = int(image.width / resize_ratio) resized_height = int(image.height / resize_ratio) image.dump(dest_pattern="/tmp/{id}.jpg", max_size=max(resized_width, resized_height), bits=image.bitDepth) img = cv2.imread(image.filename, cv2.IMREAD_GRAYSCALE) thresholded_img = cv2.adaptiveThreshold( img, 2**image.bitDepth, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, cj.parameters.threshold_blocksize, cj.parameters.threshold_constant) kernel = np.ones((5, 5), np.uint8) eroded_img = cv2.erode(thresholded_img, kernel, iterations=cj.parameters.erode_iterations) dilated_img = cv2.dilate( eroded_img, kernel, iterations=cj.parameters.dilate_iterations) extension = 10 extended_img = cv2.copyMakeBorder(dilated_img, extension, extension, extension, extension, cv2.BORDER_CONSTANT, value=2**image.bitDepth) components = find_components(extended_img) zoom_factor = image.width / float(resized_width) for i, component in enumerate(components): converted = [] for point in component[0]: x = int((point[0] - extension) * zoom_factor) y = int(image.height - ((point[1] - extension) * zoom_factor)) converted.append((x, y)) components[i] = Polygon(converted) # Find largest component (whole image) largest = max(components, key=attrgetter('area')) components.remove(largest) # Only keep components greater than 5% of whole image min_area = int(0.05 * image.width * image.height) annotations = AnnotationCollection() for component in components: if component.area > min_area: annotations.append( Annotation( location=component.wkt, id_image=image.id, id_terms=[ cj.parameters.cytomine_id_predicted_term ], id_project=cj.parameters.cytomine_id_project)) if len(annotations) % 100 == 0: annotations.save() annotations = AnnotationCollection() annotations.save() cj.job.update(statusComment="Finished.")
def run(self): self.super_admin = Cytomine.get_instance().current_user connect_as(self.super_admin, True) users = UserCollection().fetch() users_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("user-collection") ][0] remote_users = UserCollection() for u in json.load(open(os.path.join(self.working_path, users_json))): remote_users.append(User().populate(u)) roles = ["project_manager", "project_contributor", "ontology_creator"] if self.with_images: roles += ["image_creator", "image_reviewer"] if self.with_userannotations: roles += ["userannotation_creator", "userannotationterm_creator"] roles = set(roles) remote_users = [ u for u in remote_users if len(roles.intersection(set(u.roles))) > 0 ] for remote_user in remote_users: user = find_first( [u for u in users if u.username == remote_user.username]) if not user: user = copy.copy(remote_user) if not user.password: user.password = random_string(8) if not self.with_original_date: user.created = None user.updated = None user.save() self.id_mapping[remote_user.id] = user.id # -------------------------------------------------------------------------------------------------------------- logging.info("1/ Import ontology and terms") """ Import the ontology with terms and relation terms that are stored in pickled files in working_path. If the ontology exists (same name and same terms), the existing one is used. Otherwise, an ontology with an available name is created with new terms and corresponding relationships. """ ontologies = OntologyCollection().fetch() ontology_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("ontology") ][0] remote_ontology = Ontology().populate( json.load(open(os.path.join(self.working_path, ontology_json)))) remote_ontology.name = remote_ontology.name.strip() terms = TermCollection().fetch() terms_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("term-collection") ] remote_terms = TermCollection() if len(terms_json) > 0: for t in json.load( open(os.path.join(self.working_path, terms_json[0]))): remote_terms.append(Term().populate(t)) def ontology_exists(): compatible_ontology = find_first([ o for o in ontologies if o.name == remote_ontology.name.strip() ]) if compatible_ontology: set1 = set((t.name, t.color) for t in terms if t.ontology == compatible_ontology.id) difference = [ term for term in remote_terms if (term.name, term.color) not in set1 ] if len(difference) == 0: return True, compatible_ontology return False, None else: return True, None i = 1 remote_name = remote_ontology.name found, existing_ontology = ontology_exists() while not found: remote_ontology.name = "{} ({})".format(remote_name, i) found, existing_ontology = ontology_exists() i += 1 # SWITCH to ontology creator user connect_as(User().fetch(self.id_mapping[remote_ontology.user])) if not existing_ontology: ontology = copy.copy(remote_ontology) ontology.user = self.id_mapping[remote_ontology.user] if not self.with_original_date: ontology.created = None ontology.updated = None ontology.save() self.id_mapping[remote_ontology.id] = ontology.id logging.info("Ontology imported: {}".format(ontology)) for remote_term in remote_terms: logging.info("Importing term: {}".format(remote_term)) term = copy.copy(remote_term) term.ontology = self.id_mapping[term.ontology] term.parent = None if not self.with_original_date: term.created = None term.updated = None term.save() self.id_mapping[remote_term.id] = term.id logging.info("Term imported: {}".format(term)) remote_relation_terms = [(term.parent, term.id) for term in remote_terms] for relation in remote_relation_terms: parent, child = relation if parent: rt = RelationTerm(self.id_mapping[parent], self.id_mapping[child]).save() logging.info("Relation term imported: {}".format(rt)) else: self.id_mapping[remote_ontology.id] = existing_ontology.id ontology_terms = [ t for t in terms if t.ontology == existing_ontology.id ] for remote_term in remote_terms: self.id_mapping[remote_term.id] = find_first([ t for t in ontology_terms if t.name == remote_term.name ]).id logging.info( "Ontology already encoded: {}".format(existing_ontology)) # SWITCH USER connect_as(self.super_admin, True) # -------------------------------------------------------------------------------------------------------------- logging.info("2/ Import project") """ Import the project (i.e. the Cytomine Project domain) stored in pickled file in working_path. If a project with the same name already exists, append a (x) suffix where x is an increasing number. """ projects = ProjectCollection().fetch() project_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("project") ][0] remote_project = Project().populate( json.load(open(os.path.join(self.working_path, project_json)))) remote_project.name = remote_project.name.strip() def available_name(): i = 1 existing_names = [o.name for o in projects] new_name = project.name while new_name in existing_names: new_name = "{} ({})".format(project.name, i) i += 1 return new_name project = copy.copy(remote_project) project.name = available_name() project.discipline = None project.ontology = self.id_mapping[project.ontology] project_contributors = [ u for u in remote_users if "project_contributor" in u.roles ] project.users = [self.id_mapping[u.id] for u in project_contributors] project_managers = [ u for u in remote_users if "project_manager" in u.roles ] project.admins = [self.id_mapping[u.id] for u in project_managers] if not self.with_original_date: project.created = None project.updated = None project.save() self.id_mapping[remote_project.id] = project.id logging.info("Project imported: {}".format(project)) # -------------------------------------------------------------------------------------------------------------- logging.info("3/ Import images") storages = StorageCollection().fetch() abstract_images = AbstractImageCollection().fetch() images_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("imageinstance-collection") ] remote_images = ImageInstanceCollection() if len(images_json) > 0: for i in json.load( open(os.path.join(self.working_path, images_json[0]))): remote_images.append(ImageInstance().populate(i)) remote_images_dict = {} for remote_image in remote_images: image = copy.copy(remote_image) # Fix old image name due to urllib3 limitation remote_image.originalFilename = bytes( remote_image.originalFilename, 'utf-8').decode('ascii', 'ignore') if remote_image.originalFilename not in remote_images_dict.keys(): remote_images_dict[remote_image.originalFilename] = [ remote_image ] else: remote_images_dict[remote_image.originalFilename].append( remote_image) logging.info("Importing image: {}".format(remote_image)) # SWITCH user to image creator user connect_as(User().fetch(self.id_mapping[remote_image.user])) # Get its storage storage = find_first([ s for s in storages if s.user == Cytomine.get_instance().current_user.id ]) if not storage: storage = storages[0] # Check if image is already in its storage abstract_image = find_first([ ai for ai in abstract_images if ai.originalFilename == remote_image.originalFilename and ai.width == remote_image.width and ai.height == remote_image. height and ai.resolution == remote_image.resolution ]) if abstract_image: logging.info( "== Found corresponding abstract image. Linking to project." ) ImageInstance(abstract_image.id, self.id_mapping[remote_project.id]).save() else: logging.info("== New image starting to upload & deploy") filename = os.path.join( self.working_path, "images", image.originalFilename.replace("/", "-")) Cytomine.get_instance().upload_image( self.host_upload, filename, storage.id, self.id_mapping[remote_project.id]) time.sleep(0.8) # SWITCH USER connect_as(self.super_admin, True) # Waiting for all images... n_new_images = -1 new_images = None count = 0 while n_new_images != len( remote_images) and count < len(remote_images) * 5: new_images = ImageInstanceCollection().fetch_with_filter( "project", self.id_mapping[remote_project.id]) n_new_images = len(new_images) if count > 0: time.sleep(5) count = count + 1 print("All images have been deployed. Fixing image-instances...") # Fix image instances meta-data: for new_image in new_images: remote_image = remote_images_dict[new_image.originalFilename].pop() if self.with_original_date: new_image.created = remote_image.created new_image.updated = remote_image.updated new_image.reviewStart = remote_image.reviewStart if hasattr( remote_image, 'reviewStart') else None new_image.reviewStop = remote_image.reviewStop if hasattr( remote_image, 'reviewStop') else None new_image.reviewUser = self.id_mapping[ remote_image.reviewUser] if hasattr( remote_image, 'reviewUser') and remote_image.reviewUser else None new_image.instanceFilename = remote_image.instanceFilename new_image.update() self.id_mapping[remote_image.id] = new_image.id self.id_mapping[remote_image.baseImage] = new_image.baseImage new_abstract = AbstractImage().fetch(new_image.baseImage) if self.with_original_date: new_abstract.created = remote_image.created new_abstract.updated = remote_image.updated if new_abstract.resolution is None: new_abstract.resolution = remote_image.resolution if new_abstract.magnification is None: new_abstract.magnification = remote_image.magnification new_abstract.update() print("All image-instances have been fixed.") # -------------------------------------------------------------------------------------------------------------- logging.info("4/ Import user annotations") annots_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("user-annotation-collection") ] remote_annots = AnnotationCollection() if len(annots_json) > 0: for a in json.load( open(os.path.join(self.working_path, annots_json[0]))): remote_annots.append(Annotation().populate(a)) def _add_annotation(remote_annotation, id_mapping, with_original_date): if remote_annotation.project not in id_mapping.keys() \ or remote_annotation.image not in id_mapping.keys(): return annotation = copy.copy(remote_annotation) annotation.project = id_mapping[remote_annotation.project] annotation.image = id_mapping[remote_annotation.image] annotation.user = id_mapping[remote_annotation.user] annotation.term = [id_mapping[t] for t in remote_annotation.term] if not with_original_date: annotation.created = None annotation.updated = None annotation.save() for user in [ u for u in remote_users if "userannotation_creator" in u.roles ]: remote_annots_for_user = [ a for a in remote_annots if a.user == user.id ] # SWITCH to annotation creator user connect_as(User().fetch(self.id_mapping[user.id])) Parallel(n_jobs=-1, backend="threading")( delayed(_add_annotation)(remote_annotation, self.id_mapping, self.with_original_date) for remote_annotation in remote_annots_for_user) # SWITCH back to admin connect_as(self.super_admin, True) # -------------------------------------------------------------------------------------------------------------- logging.info( "5/ Import metadata (properties, attached files, description)") obj = Model() obj.id = -1 obj.class_ = "" properties_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("properties") ] for property_json in properties_json: for remote_prop in json.load( open(os.path.join(self.working_path, property_json))): prop = Property(obj).populate(remote_prop) prop.domainIdent = self.id_mapping[prop.domainIdent] prop.save() attached_files_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("attached-files") ] for attached_file_json in attached_files_json: for remote_af in json.load( open(os.path.join(self.working_path, attached_file_json))): af = AttachedFile(obj).populate(remote_af) af.domainIdent = self.id_mapping[af.domainIdent] af.filename = os.path.join(self.working_path, "attached_files", remote_af.filename) af.save() descriptions_json = [ f for f in os.listdir(self.working_path) if f.endswith(".json") and f.startswith("description") ] for description_json in descriptions_json: desc = Description(obj).populate( json.load( open(os.path.join(self.working_path, description_json)))) desc.domainIdent = self.id_mapping[desc.domainIdent] desc._object.class_ = desc.domainClassName desc._object.id = desc.domainIdent desc.save()
def main(argv): # 0. Initialize Cytomine client and job with CytomineJob.from_cli(argv) as cj: cj.job.update(status=Job.RUNNING, progress=0, statusComment="Initialisation...") # 1. Create working directories on the machine: # - WORKING_PATH/in: input images # - WORKING_PATH/out: output images # - WORKING_PATH/ground_truth: ground truth images # - WORKING_PATH/tmp: temporary path base_path = "{}".format(os.getenv("HOME")) gt_suffix = "_lbl" working_path = os.path.join(base_path, str(cj.job.id)) in_path = os.path.join(working_path, "in") out_path = os.path.join(working_path, "out") gt_path = os.path.join(working_path, "ground_truth") tmp_path = os.path.join(working_path, "tmp") if not os.path.exists(working_path): os.makedirs(working_path) os.makedirs(in_path) os.makedirs(out_path) os.makedirs(gt_path) os.makedirs(tmp_path) # 2. Download the images (first input, then ground truth image) cj.job.update( progress=1, statusComment="Downloading images (to {})...".format(in_path)) image_instances = ImageInstanceCollection().fetch_with_filter( "project", cj.parameters.cytomine_id_project) input_images = [ i for i in image_instances if gt_suffix not in i.originalFilename ] gt_images = [ i for i in image_instances if gt_suffix in i.originalFilename ] for input_image in input_images: input_image.download(os.path.join(in_path, "{id}.tif")) for gt_image in gt_images: related_name = gt_image.originalFilename.replace(gt_suffix, '') related_image = [ i for i in input_images if related_name == i.originalFilename ] if len(related_image) == 1: gt_image.download( os.path.join(gt_path, "{}.tif".format(related_image[0].id))) # 3. Call the image analysis workflow using the run script cj.job.update(progress=25, statusComment="Launching workflow...") # load data cj.job.update(progress=30, statusComment="Workflow: preparing data...") dims = (cj.parameters.image_height, cj.parameters.image_width, cj.parameters.n_channels) mask_dims = (dims[0], dims[1], cj.parameters.n_classes) # load input images imgs = load_data( cj, dims, in_path, **{ "start": 35, "end": 45, "period": 0.1, "prefix": "Workflow: load training input images" }) train_mean = np.mean(imgs) train_std = np.std(imgs) imgs -= train_mean imgs /= train_std # load masks masks = load_data(cj, mask_dims, gt_path, dtype=np.int, is_masks=True, n_classes=cj.parameters.n_classes, **{ "start": 45, "end": 55, "period": 0.1, "prefix": "Workflow: load training masks images" }) cj.job.update(progress=56, statusComment="Workflow: build model...") unet = create_unet(dims, n_classes=cj.parameters.n_classes) unet.compile(optimizer=Adam(lr=cj.parameters.learning_rate), loss='binary_crossentropy') cj.job.update(progress=60, statusComment="Workflow: prepare training...") datagen = ImageDataGenerator( rotation_range=cj.parameters.aug_rotation, width_shift_range=cj.parameters.aug_width_shift, height_shift_range=cj.parameters.aug_height_shift, shear_range=cj.parameters.aug_shear_range, horizontal_flip=cj.parameters.aug_hflip, vertical_flip=cj.parameters.aug_vflip) weight_filepath = os.path.join(tmp_path, 'weights.hdf5') callbacks = [ ModelCheckpoint(weight_filepath, monitor='loss', save_best_only=True) ] cj.job.update(progress=65, statusComment="Workflow: train...") unet.fit_generator(datagen.flow(imgs, masks, batch_size=cj.parameters.batch_size, seed=42), steps_per_epoch=math.ceil(imgs.shape[0] / cj.parameters.batch_size), epochs=cj.parameters.epochs, callbacks=callbacks) # save model and metadata cj.job.update(progress=85, statusComment="Save model...") AttachedFile(cj.job, domainIdent=cj.job.id, filename=weight_filepath, domainClassName="be.cytomine.processing.Job").upload() cj.job.update(progress=90, statusComment="Save metadata...") Property(cj.job, key="image_width", value=cj.parameters.image_width).save() Property(cj.job, key="image_height", value=cj.parameters.image_height).save() Property(cj.job, key="n_channels", value=cj.parameters.n_channels).save() Property(cj.job, key="train_mean", value=float(train_mean)).save() Property(cj.job, key="image_width", value=float(train_std)).save() cj.job.update(status=Job.TERMINATED, progress=100, statusComment="Finished.")
def run(self): logging.info("Export will be done in directory {}".format(self.project_path)) os.makedirs(self.project_path) if self.with_metadata or self.with_annotation_metadata: self.attached_file_path = os.path.join(self.project_path, "attached_files") os.makedirs(self.attached_file_path) # -------------------------------------------------------------------------------------------------------------- logging.info("1/ Export project {}".format(self.project.id)) self.save_object(self.project) logging.info("1.1/ Export project managers") admins = UserCollection(admin=True).fetch_with_filter("project", self.project.id) for admin in admins: self.save_user(admin, "project_manager") logging.info("1.2/ Export project contributors") users = UserCollection().fetch_with_filter("project", self.project.id) for user in users: self.save_user(user, "project_contributor") if self.with_metadata: logging.info("1.3/ Export project metadata") self.export_metadata([self.project]) # -------------------------------------------------------------------------------------------------------------- logging.info("2/ Export ontology {}".format(self.project.ontology)) ontology = Ontology().fetch(self.project.ontology) self.save_object(ontology) logging.info("2.1/ Export ontology creator") user = User().fetch(ontology.user) self.save_user(user, "ontology_creator") if self.with_metadata: logging.info("2.2/ Export ontology metadata") self.export_metadata([ontology]) # -------------------------------------------------------------------------------------------------------------- logging.info("3/ Export terms") terms = TermCollection().fetch_with_filter("project", self.project.id) self.save_object(terms) if self.with_metadata: logging.info("3.1/ Export term metadata") self.export_metadata(terms) # -------------------------------------------------------------------------------------------------------------- logging.info("4/ Export images") images = ImageInstanceCollection().fetch_with_filter("project", self.project.id) self.save_object(images) if self.with_image_download: image_path = os.path.join(self.project_path, "images") os.makedirs(image_path) def _download_image(image, path): logging.info("Download file for image {}".format(image)) image.download(os.path.join(path, image.originalFilename), override=False, parent=True) # Temporary use threading as backend, as we need to connect to Cytomine in every other processes. Parallel(n_jobs=-1, backend="threading")(delayed(_download_image)(image, image_path) for image in images) logging.info("4.1/ Export image slices") slices = SliceInstanceCollection() for image in images: slices += SliceInstanceCollection().fetch_with_filter("imageinstance", image.id) self.save_object(slices) logging.info("4.2/ Export image creator users") image_users = set([image.user for image in images]) for image_user in image_users: user = User().fetch(image_user) self.save_user(user, "image_creator") logging.info("4.3/ Export image reviewer users") image_users = set([image.reviewUser for image in images if image.reviewUser]) for image_user in image_users: user = User().fetch(image_user) self.save_user(user, "image_reviewer") if self.with_metadata: logging.info("4.4/ Export image metadata") self.export_metadata(images) # -------------------------------------------------------------------------------------------------------------- logging.info("4/ Export user annotations") user_annotations = AnnotationCollection(showWKT=True, showTerm=True, project=self.project.id).fetch() self.save_object(user_annotations, filename="user-annotation-collection") logging.info("4.1/ Export user annotation creator users") annotation_users = set([annotation.user for annotation in user_annotations]) for annotation_user in annotation_users: user = User().fetch(annotation_user) self.save_user(user, "userannotation_creator") logging.info("4.2/ Export user annotation term creator users") annotation_users = set([annotation.userTerm for annotation in user_annotations if hasattr(annotation, "userTerm") and annotation.userTerm]) for annotation_user in annotation_users: user = User().fetch(annotation_user) self.save_user(user, "userannotationterm_creator") if self.with_annotation_metadata: logging.info("4.3/ Export user annotation metadata") self.export_metadata(user_annotations) # -------------------------------------------------------------------------------------------------------------- logging.info("5/ Export users") if self.anonymize: for i, user in enumerate(self.users): user.username = "******".format(i + 1) user.firstname = "Anonymized" user.lastname = "User {}".format(i + 1) user.email = "anonymous{}@unknown.com".format(i + 1) self.save_object(self.users) # Disabled due to core issue. # if self.with_metadata: # logging.info("5.1/ Export user metadata") # self.export_metadata(self.users) # -------------------------------------------------------------------------------------------------------------- logging.info("Finished.")
def get_images_mask_per_annotation_per_user(proj_id, image_id, user_id, scale_factor, dest): im = ImageInstanceCollection() im.project = proj_id im.image = image_id im.fetch_with_filter("project", proj_id) image_width = int(im[0].width) image_height = int(im[0].height) print(image_height, image_width) annotations = AnnotationCollection() annotations.project = proj_id annotations.image = image_id annotations.user = user_id annotations.showWKT = True annotations.showMeta = True annotations.showTerm = True annotations.showGIS = True annotations.showImage = True annotations.showUser = True annotations.fetch() dct_anotations = {} for a in annotations: print(a.user) if len(a.term) == 1: term = a.term[0] if term not in dct_anotations: dct_anotations[term] = [] dct_anotations[term].append(a.location) else: warnings.warn("Not suited for multiple or no annotation term") for t, lanno in dct_anotations.items(): result_image = Image.new(mode='1', size=(int(image_width * scale_factor), int(image_height * scale_factor)), color=0) for pwkt in lanno: if pwkt.startswith("POLYGON"): label = "POLYGON" elif pwkt.startswith("MULTIPOLYGON"): label = "MULTIPOLYGON" coordinatesStringList = pwkt.replace(label, '') if label == "POLYGON": coordinates_string_lists = [coordinatesStringList] elif label == "MULTIPOLYGON": coordinates_string_lists = coordinatesStringList.split( ')), ((') coordinates_string_lists = [ coordinatesStringList.replace('(', '').replace(')', '') for coordinatesStringList in coordinates_string_lists ] for coordinatesStringList in coordinates_string_lists: # create lists of x and y coordinates x_coords = [] y_coords = [] for point in coordinatesStringList.split(','): point = point.strip( string.whitespace) # remove leading and ending spaces point = point.strip( string.punctuation ) # Have seen some strings have a ')' at the end so remove it x_coords.append(round(float(point.split(' ')[0]))) y_coords.append(round(float(point.split(' ')[1]))) x_coords_correct_lod = [ int(x * scale_factor) for x in x_coords ] y_coords_correct_lod = [ image_height * scale_factor - int(x * scale_factor) for x in y_coords ] coords = [ (i, j) for i, j in zip(x_coords_correct_lod, y_coords_correct_lod) ] # draw the polygone in an image and fill it ImageDraw.Draw(result_image).polygon(coords, outline=1, fill=1) result_image.save(params.dest + '/' + str(t) + '.png')
def preprocess(cytomine, working_path, id_project, id_terms=None, id_tags_for_images=None): """ Get data from Cytomine in order to train YOLO. :param cytomine: The Cytomine client :param working_path: The path where files will be stored :param id_project: The Cytomine project ID used to get data :param id_terms: The Cytomine term IDS used to get data :param id_tags_for_images: The Cytomine tags IDS associated to images used to get data :return: classes_filename: The name of the file with classes image_filenames: A list of image filenames annotation_filenames: A list of filenames with annotations in YOLO format """ if not os.path.exists(working_path): os.makedirs(working_path) images_path = os.path.join(working_path, IMG_DIRECTORY) if not os.path.exists(images_path): os.makedirs(images_path) annotations_path = os.path.join(working_path, ANNOTATION_DIRECTORY) if not os.path.exists(annotations_path): os.makedirs(annotations_path) terms = TermCollection().fetch_with_filter("project", id_project) if id_terms: filtered_term_ids = [int(id_term) for id_term in id_terms.split(',')] filtered_terms = [term for term in terms if term.id in filtered_term_ids] else: filtered_terms = terms terms_indexes = {term.id: i for i, term in enumerate(filtered_terms)} # https://github.com/eriklindernoren/PyTorch-YOLOv3#train-on-custom-dataset # Write obj.names classes_filename = os.path.join(working_path, CLASSES_FILENAME) with open(classes_filename, 'w') as f: for term in filtered_terms: f.write(term.name + os.linesep) # Download images image_filenames = [] image_tags = id_tags_for_images if id_tags_for_images else None images = ImageInstanceCollection(tags=image_tags).fetch_with_filter("project", id_project) for image in images: image.dump(os.path.join(working_path, IMG_DIRECTORY, "{id}.png"), override=False) image_filenames.append(image.filename) # Create annotation files annotation_filenames = [] for image in images: annotations = AnnotationCollection() annotations.image = image.id annotations.terms = [t.id for t in filtered_terms] if id_terms else None annotations.showWKT = True annotations.showTerm = True annotations.fetch() filename = os.path.join(working_path, ANNOTATION_DIRECTORY, "{}.txt".format(image.id)) with open(filename, 'w') as f: for annotation in annotations: geometry = wkt.loads(annotation.location) x, y, w, h = geometry_to_yolo(geometry, image.width, image.height) for term_id in annotation.term: # <object-class> <x_center> <y_center> <width> <height> f.write("{} {:.12f} {:.12f} {:.12f} {:.12f}".format(terms_indexes[term_id], x, y, w, h) + os.linesep) annotation_filenames.append(filename) return classes_filename, image_filenames, annotation_filenames
def main(argv): with CytomineJob.from_cli(argv) as cj: cj.job.update(progress=1, statusComment="Initialisation") cj.log(str(cj.parameters)) term_ids = [int(term_id) for term_id in cj.parameters.cytomine_id_terms.split(",")] terms = TermCollection().fetch_with_filter("project", cj.parameters.cytomine_id_project) terms = [term for term in terms if term.id in term_ids] image_ids = [int(image_id) for image_id in cj.parameters.cytomine_id_images.split(",")] images = ImageInstanceCollection(light=True).fetch_with_filter("project", cj.parameters.cytomine_id_project) images = [image for image in images if image.id in image_ids] if hasattr(cj.parameters, "cytomine_id_users") and cj.parameters.cytomine_id_users is not None: user_ids = [int(user_id) for user_id in cj.parameters.cytomine_id_users.split(",")] else: user_ids = [] if hasattr(cj.parameters, "cytomine_id_jobs") and cj.parameters.cytomine_id_jobs is not None: job_ids = [int(job_id) for job_id in cj.parameters.cytomine_id_jobs.split(",")] jobs = JobCollection(project=cj.parameters.cytomine_id_project).fetch() jobs = [job for job in jobs if job.id in job_ids] else: jobs = [] userjobs_ids = [job.userJob for job in jobs] all_user_ids = user_ids + userjobs_ids cj.job.update(progress=20, statusComment="Collect data") ac = AnnotationCollection() ac.terms = term_ids ac.images = image_ids ac.showMeta = True ac.showGIS = True ac.showTerm = True ac.reviewed = True if cj.parameters.cytomine_reviewed_only else None ac.users = all_user_ids if len(all_user_ids) > 0 else None ac.fetch() cj.job.update(progress=55, statusComment="Compute statistics") data = dict() for image in images: d = dict() areas = [a.area for a in ac if a.image == image.id] total_area = np.sum(areas) d['total'] = total_area d['count'] = len(areas) d['ratio'] = 1.0 for term in terms: annotations = [a for a in ac if a.image == image.id and term.id in a.term] areas = [a.area for a in annotations] d[term.name] = dict() d[term.name]['total'] = np.sum(areas) d[term.name]['count'] = len(annotations) d[term.name]['ratio'] = d[term.name]['total'] / float(total_area) if total_area > 0 else 0 d[term.name]['mean'] = np.mean(areas) d[term.name]['annotations'] = [{"created": a.created, "area": a.area} for a in annotations] data[image.instanceFilename] = d cj.job.update(progress=90, statusComment="Write CSV report") with open("stat-area.csv", "w") as f: for l in write_csv(data, terms): f.write("{}\n".format(l)) job_data = JobData(id_job=cj.job.id, key="Area CSV report", filename="stat-area.csv") job_data = job_data.save() job_data.upload("stat-area.csv") cj.job.update(statusComment="Finished.", progress=100)
def main(argv): with CytomineJob.from_cli(argv) as conn: conn.job.update(status=Job.RUNNING, progress=0, statusComment='Intialization...') base_path = "{}".format(os.getenv('HOME')) # Mandatory for Singularity working_path = os.path.join(base_path, str(conn.job.id)) # Loading models from models directory with tf.device('/cpu:0'): h_model = load_model('/models/head_dice_sm_9976.hdf5', compile=False) # head model h_model.compile(optimizer='adam', loss=dice_coef_loss, metrics=['accuracy']) op_model = load_model('/models/op_ce_sm_9991.hdf5', compile=True) # operculum model #op_model.compile(optimizer='adam', loss=dice_coef_loss, #metrics=['accuracy']) # Select images to process images = ImageInstanceCollection().fetch_with_filter('project', conn.parameters.cytomine_id_project) if conn.parameters.cytomine_id_images != 'all': # select only given image instances = [image for image in image_instances if image.id in id_list] images = [_ for _ in images if _.id in map(lambda x: int(x.strip()), conn.parameters.cytomine_id_images.split(','))] images_id = [image.id for image in images] # Download selected images into 'working_directory' img_path = os.path.join(working_path, 'images') # if not os.path.exists(img_path): os.makedirs(img_path) for image in conn.monitor( images, start=2, end=50, period=0.1, prefix='Downloading images into working directory...'): fname, fext = os.path.splitext(image.filename) if image.download(dest_pattern=os.path.join( img_path, "{}{}".format(image.id, fext))) is not True: # images are downloaded with image_ids as names print('Failed to download image {}'.format(image.filename)) # Prepare image file paths from image directory for execution conn.job.update(progress=50, statusComment="Preparing data for execution..") image_paths = glob.glob(os.path.join(img_path, '*')) std_size = (1032,1376) #maximum size that the model can handle model_size = 256 for i in range(len(image_paths)): org_img = Image.open(image_paths[i]) filename = os.path.basename(image_paths[i]) fname, fext = os.path.splitext(filename) fname = int(fname) org_img = img_to_array(org_img) img = org_img.copy() org_size = org_img.shape[:2] asp_ratio = org_size[0] / org_size[1] #for cropping and upscaling to original size if org_size[1] > std_size[1]: img = tf.image.resize(img, (675,900), method='nearest') img = tf.image.resize_with_crop_or_pad(img, std_size[0],std_size[1]) h_mask = predict_mask(img, h_model,model_size) h_mask = crop_to_aspect(h_mask, asp_ratio) h_mask = tf.image.resize(h_mask, std_size, method='nearest') h_up_mask = tf.image.resize_with_crop_or_pad(h_mask, 675,900) h_up_mask = tf.image.resize(h_up_mask, org_size, method='nearest') h_up_mask = np.asarray(h_up_mask).astype(np.uint8) _, h_up_mask = cv.threshold(h_up_mask, 0.001, 255, 0) kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (17, 17)) h_up_mask = cv.morphologyEx(h_up_mask, cv.MORPH_OPEN, kernel, iterations=5) h_up_mask = cv.morphologyEx(h_up_mask, cv.MORPH_CLOSE, kernel, iterations=1) #h_up_mask = cv.erode(h_up_mask ,kernel,iterations = 3) #h_up_mask = cv.dilate(h_up_mask ,kernel,iterations = 3) h_up_mask = np.expand_dims(h_up_mask, axis=-1) else: h_mask = predict_mask(img, h_model, model_size) h_mask = crop_to_aspect(h_mask, asp_ratio) h_up_mask = tf.image.resize(h_mask, org_size, method='nearest') h_up_mask = np.asarray(h_up_mask).astype(np.uint8) _, h_up_mask = cv.threshold(h_up_mask, 0.001, 255, 0) kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (5, 5)) #kernel = np.ones((9,9),np.uint8) h_up_mask = cv.morphologyEx(h_up_mask, cv.MORPH_CLOSE, kernel, iterations=3) h_up_mask = np.expand_dims(h_up_mask, axis=-1) box = bb_pts(h_up_mask) # bounding box points for operculum (x_min, y_min, x_max, y_max) w = box[0] h = box[1] tr_h = box[3] - box[1] # target height tr_w = box[2] - box[0] # target width crop_op_img = tf.image.crop_to_bounding_box(org_img, h, w, tr_h, tr_w) op_asp_ratio = crop_op_img.shape[0] / crop_op_img.shape[1] op_mask = predict_mask(crop_op_img, op_model, model_size) op_mask = crop_to_aspect(op_mask, op_asp_ratio) op_mask = tf.image.resize(op_mask, (crop_op_img.shape[0], crop_op_img.shape[1]), method='nearest') op_up_mask = np.zeros((org_img.shape[0],org_img.shape[1],1)).astype(np.uint8) # array of zeros to be filled with op mask op_up_mask[box[1]:box[3], box[0]:box[2]] = op_mask # paste op_mask in org_img (reversing the crop operation) #op_up_mask = tf.image.resize_with_crop_or_pad(op_mask, org_size[0], org_size[1]) h_polygon = h_make_polygon(h_up_mask) op_polygon = o_make_polygon(op_up_mask) conn.job.update( status=Job.RUNNING, progress=95, statusComment="Uploading new annotations to Cytomine server..") annotations = AnnotationCollection() annotations.append(Annotation(location=h_polygon[0].wkt, id_image=fname, id_terms=143971108, id_project=conn.parameters.cytomine_id_project)) annotations.append(Annotation(location=op_polygon[0].wkt, id_image=fname, id_term=143971084, id_project=conn.parameters.cytomine_id_project)) annotations.save() conn.job.update(status=Job.TERMINATED, status_comment="Finish", progress=100) # 524787186
def run(cyto_job, parameters): logging.info("----- segmentation_prediction v%s -----", __version__) logging.info("Entering run(cyto_job=%s, parameters=%s)", cyto_job, parameters) job = cyto_job.job project = cyto_job.project current_tile_annotation = None working_path = os.path.join("tmp", str(job.id)) if not os.path.exists(working_path): logging.info("Creating annotation directory: %s", working_path) os.makedirs(working_path) try: # Initialization pyxit_target_width = parameters.pyxit_target_width pyxit_target_height = parameters.pyxit_target_height tile_size = parameters.cytomine_tile_size zoom = parameters.cytomine_zoom_level predictionstep = int(parameters.cytomine_predict_step) mindev = parameters.cytomine_tile_min_stddev maxmean = parameters.cytomine_tile_max_mean logging.info("Loading prediction model (local)") fp = open(parameters.pyxit_load_from, "r") logging.debug(fp) pickle.load(fp) # classes => not needed pyxit = pickle.load(fp) pyxit.n_jobs = parameters.pyxit_nb_jobs # multithread subwindows extraction in pyxit pyxit.base_estimator.n_jobs = parameters.pyxit_nb_jobs # multithread tree propagation # loop for images in the project id TODO let user specify the images to process images = ImageInstanceCollection().fetch_with_filter( "project", project.id) nb_images = len(images) logging.info("# images in project: %d", nb_images) progress = 0 progress_delta = 100 / nb_images # Go through all images for (i, image) in enumerate(images): image_str = "{} ({}/{})".format(image.instanceFilename, i + 1, nb_images) job.update(progress=progress, statusComment="Analyzing image {}...".format(image_str)) logging.debug( "Image id: %d width: %d height: %d resolution: %f magnification: %d filename: %s", image.id, image.width, image.height, image.resolution, image.magnification, image.filename) image.colorspace = "RGB" # required for correct handling in CytomineReader # Create local object to access the remote whole slide logging.debug( "Creating connector to Slide Image from Cytomine server") whole_slide = WholeSlide(image) logging.debug("Wholeslide: %d x %d pixels", whole_slide.width, whole_slide.height) # endx and endy allow to stop image analysis at a given x, y position (for debugging) endx = parameters.cytomine_endx if parameters.cytomine_endx else whole_slide.width endy = parameters.cytomine_endy if parameters.cytomine_endy else whole_slide.height # initialize variables and tools for ROI nx = tile_size ny = tile_size local_tile_component = ([(0, 0), (0, ny), (nx, ny), (nx, 0), (0, 0)], []) # We can apply the segmentation model either in the whole slide (including background area), or only within # multiple ROIs (of a given term) # For example ROI could be generated first using a thresholding step to detect the tissue # Here we build a polygon union containing all roi_annotations locations (user or reviewed annotations) to # later match tile with roi masks if parameters.cytomine_roi_term: logging.debug("Retrieving ROI annotations") roi_annotations = AnnotationCollection( image=image.id, term=parameters.cytomine_roi_term, showWKT=True, showTerm=True, reviewed=parameters.cytomine_reviewed_roi).fetch() roi_annotations_locations = [] for roi_annotation in roi_annotations: roi_annotations_locations.append( shapely.wkt.loads(roi_annotation.location)) roi_annotations_union = shapely.ops.unary_union( roi_annotations_locations) else: # no ROI used # We build a rectangular roi_mask corresponding to the whole image filled with ones logging.debug("Processing all tiles") roi_mask = np.ones((ny, nx), dtype=np.bool) # Initiate the reader object which browse the whole slide image with tiles of size tile_size logging.info("Initiating the Slide reader") reader = CytomineReader( whole_slide, window_position=Bounds(parameters.cytomine_startx, parameters.cytomine_starty, tile_size, tile_size), zoom=zoom, # overlap needed because the predictions at the borders of the tile are removed overlap=pyxit_target_width + 1) wsi = 0 # tile number logging.info("Starting browsing the image using tiles") while True: tile_component = reader.convert_to_real_coordinates( [local_tile_component])[0] tile_polygon = shapely.geometry.Polygon( tile_component[0], tile_component[1]) # Get rasterized roi mask to match with this tile (if no ROI used, the roi_mask was built before and # corresponds to the whole image). if parameters.cytomine_roi_term: roi_mask = rasterize_tile_roi_union( nx, ny, tile_polygon, roi_annotations_union, reader) if np.count_nonzero(roi_mask) == 0: logging.info( "Tile %d is not included in any ROI, skipping processing", wsi) else: # Browse the whole slide image with catch exception while True: try: reader.read() break except socket.timeout: logging.error("Socket timeout for tile %d: %s", wsi, socket.timeout) time.sleep(1) except socket.error: logging.error("Socket error for tile %d: %s", wsi, socket.error) time.sleep(1) tile = reader.data # Get statistics about the current tile logging.info("Computing tile %d statistics", wsi) pos = reader.window_position logging.debug( "Tile zoom: %d, posx: %d, posy: %d, poswidth: %d, posheight: %d", zoom, pos.x, pos.y, pos.width, pos.height) tilemean = ImageStat.Stat(tile).mean logging.debug("Tile mean pixel values: %d %d %d", tilemean[0], tilemean[1], tilemean[2]) tilestddev = ImageStat.Stat(tile).stddev logging.debug("Tile stddev pixel values: %d %d %d", tilestddev[0], tilestddev[1], tilestddev[2]) # Criteria to determine if tile is empty, specific to this application if ((tilestddev[0] < mindev and tilestddev[1] < mindev and tilestddev[2] < mindev) or (tilemean[0] > maxmean and tilemean[1] > maxmean and tilemean[2] > maxmean)): logging.info( "Tile %d empty (filtered by min stddev or max mean)", wsi) else: # This tile is not empty, we process it # Add current tile annotation on server just for progress visualization purpose current_tile_annotation = Annotation( tile_polygon.wkt, image.id).save() # Save the tile image locally image_filename = "%s/%d-zoom_%d-tile_%d_x%d_y%d_w%d_h%d.png" \ % (working_path, image.id, zoom, wsi, pos.x, pos.y, pos.width, pos.height) tile.save(image_filename, "PNG") logging.debug("Tile file: %s", image_filename) logging.info("Extraction of subwindows in tile %d", wsi) width, height = tile.size half_subwindow_width = int(pyxit_target_width / 2) half_subwindow_height = int(pyxit_target_height / 2) # Coordinates of centers of extracted subwindows y_roi = range(half_subwindow_height, height - half_subwindow_height, predictionstep) x_roi = range(half_subwindow_width, width - half_subwindow_width, predictionstep) logging.info("%d subwindows to extract", len(x_roi) * len(y_roi)) n_jobs = parameters.cytomine_nb_jobs n_jobs, _, starts = _partition_images( n_jobs, len(y_roi)) # Parallel extraction of subwindows in the current tile all_data = Parallel(n_jobs=n_jobs)( delayed(_parallel_crop_boxes) (y_roi[starts[k]:starts[k + 1]], x_roi, image_filename, half_subwindow_width, half_subwindow_height, parameters.pyxit_colorspace) for k in xrange(n_jobs)) # Reduce boxes = np.vstack(box for box, _ in all_data) _X = np.vstack([X for _, X in all_data]) logging.info("Prediction of subwindows for tile %d", wsi) # Propagate subwindow feature vectors (X) into trees and get probabilities _Y = pyxit.base_estimator.predict_proba(_X) # Warning: we get output vectors for all classes for pixel (0,0) for all subwindows, then pixel # predictions for pixel (0,1) for all subwindows, ... We do not get predictions window after # window, but output after output # => Y is a list of length m, where m = nb of pixels by subwindow ; # each element of the list is itself a list of size n, where n = nb of subwindows # for each subwindow, the probabilities for each class are given # <optimized code logging.info( "Parallel construction of confidence map in current tile" ) pixels = range(pyxit_target_width * pyxit_target_height) n_jobs, _, starts = _partition_images( n_jobs, len(pixels)) all_votes_class = Parallel(n_jobs=n_jobs)( delayed(_parallel_confidence_map) (pixels[starts[k]:starts[k + 1]], _Y[starts[k]:starts[k + 1]], boxes, width, height, pyxit.base_estimator.n_classes_[0], pyxit_target_width, pyxit_target_height) for k in xrange(n_jobs)) votes_class = all_votes_class[0] for v in all_votes_class[1:]: votes_class += v # optimized code> logging.info("Delete borders") # Delete predictions at borders for k in xrange(0, width): for j in xrange(0, half_subwindow_height): votes_class[j, k, :] = [1, 0] for j in xrange(height - half_subwindow_height, height): votes_class[j, k, :] = [1, 0] for j in xrange(0, height): for k in xrange(0, half_subwindow_width): votes_class[j, k, :] = [1, 0] for k in xrange(width - half_subwindow_width, width): votes_class[j, k, :] = [1, 0] votes = np.argmax(votes_class, axis=2) * 255 # only predict in roi region based on roi mask votes[np.logical_not(roi_mask)] = 0 # process mask votes = process_mask(votes) votes = votes.astype(np.uint8) # Save of confidence map locally logging.info("Creating output tile file locally") output = Image.fromarray(votes) outputfilename = "%s/%d-zoom_%d-tile_%d_xxOUTPUT-%dx%d.png" \ % (working_path, image.id, zoom, wsi, pyxit_target_width, pyxit_target_height) output.save(outputfilename, "PNG") logging.debug("Tile OUTPUT file: %s", outputfilename) # Convert and transfer annotations of current tile logging.info("Find components") components = ObjectFinder(votes).find_components() components = reader.convert_to_real_coordinates( components) polygons = [ Polygon(component[0], component[1]) for component in components ] logging.info("Uploading annotations...") logging.debug("Number of polygons: %d" % len(polygons)) start = time.time() for poly in polygons: geometry = poly.wkt if not poly.is_valid: logging.warning( "Invalid geometry, try to correct it with buffer" ) logging.debug( "Geometry prior to modification: %s", geometry) new_poly = poly.buffer(0) if not new_poly.is_valid: logging.error( "Failed to make valid geometry, skipping this polygon" ) continue geometry = new_poly.wkt logging.debug("Uploading geometry %s", geometry) startsingle = time.time() while True: try: # TODO: save collection of annotations annot = Annotation( geometry, image.id, [parameters.cytomine_predict_term ]).save() if not annot: logging.error( "Annotation could not be saved ; location = %s", geometry) break except socket.timeout, socket.error: logging.error( "socket timeout/error add_annotation") time.sleep(1) endsingle = time.time() logging.debug( "Elapsed time for adding single annotation: %d", endsingle - startsingle) # current time end = time.time() logging.debug( "Elapsed time for adding all annotations: %d", end - start) # Delete current tile annotation (progress visualization) current_tile_annotation.delete() wsi += 1 if not reader.next() or (reader.window_position.x > endx and reader.window_position.y > endy): break # end of browsing the whole slide # Postprocessing to remove small/large annotations according to min/max area if parameters.cytomine_postproc: logging.info("Post-processing before union...") job.update(progress=progress + progress_delta / 4, statusComment="Post-processing image {}...".format( image_str)) while True: try: annotations = AnnotationCollection(id_user=job.userJob, id_image=image.id, showGIS=True) break except socket.timeout, socket.error: logging.error( "Socket timeout/error when fetching annotations") time.sleep(1) # remove/edit useless annotations start = time.time() for annotation in annotations: if (annotation.area == 0 or annotation.area < parameters.cytomine_min_size or annotation.area > parameters.cytomine_max_size): annotation.delete() else: logging.debug("Keeping annotation %d", annotation.id) end = time.time() logging.debug( "Elapsed time for post-processing all annotations: %d" % (end - start)) # Segmentation model was applied on individual tiles. We need to merge geometries generated from each tile. # We use a groovy/JTS script that downloads annotation geometries and perform union locally to relieve the # Cytomine server if parameters.cytomine_union: logging.info("Union of polygons for image %s", image.instanceFilename) job.update( progress=progress + progress_delta / 3, statusComment="Union of polygons in image {}...".format( image_str)) start = time.time() union_command = ( "groovy -cp \"lib/jars/*\" lib/union4.groovy " + "%s %s %s %d %d %d %d %d %d %d %d %d %d" % (cyto_job._base_url(False), parameters.publicKey, parameters.privateKey, image.id, job.userJob, parameters.cytomine_predict_term, parameters.cytomine_union_min_length, parameters.cytomine_union_bufferoverlap, parameters.cytomine_union_min_point_for_simplify, parameters.cytomine_union_min_point, parameters.cytomine_union_max_point, parameters.cytomine_union_nb_zones_width, parameters.cytomine_union_nb_zones_height)) logging.info("Union command: %s", union_command) os.system(union_command) end = time.time() logging.info("Elapsed time union: %d s", end - start) # Perform classification of detected geometries using a classification model (pkl) if parameters.pyxit_post_classification: logging.info("Post classification of all candidates") job.update( progress=progress + progress_delta * 2 / 3, statusComment="Post-classification in image {}...".format( image_str)) # Retrieve locally annotations from Cytomine core produced by the segmentation job as candidates candidate_annotations = AnnotationCollection( user=job.userJob, image=image.id, showWKT=True, showMeta=True).fetch() folder_name = "%s/crops-candidates-%d/zoom-%d/" % ( working_path, image.id, zoom) if not os.path.exists(folder_name): os.makedirs(folder_name) dest_pattern = os.path.join(folder_name, "{id}.png") for annotation in candidate_annotations: annotation.dump(dest_pattern, mask=True, alpha=True) # np_image = cv2.imread(annotation.filename, -1) # if np_image is not None: # alpha = np.array(np_image[:, :, 3]) # image = np.array(np_image[:, :, 0:3]) # image[alpha == 0] = (255,255,255) # to replace surrounding by white # cv2.imwrite(annotation.filename, image) logging.debug("Building attributes from %s", folder_name) # Extract subwindows from all candidates x, y = build_from_dir(folder_name) post_fp = open(parameters.pyxit_post_classification_save_to, "r") classes = pickle.load(post_fp) pyxit = pickle.load(post_fp) logging.debug(pyxit) # pyxit parameters are in the model file y_proba = pyxit.predict_proba(x) y_predict = classes.take(np.argmax(y_proba, axis=1), axis=0) y_rate = np.max(y_proba, axis=1) # We classify each candidate annotation and keep only those predicted as cytomine_predict_term for annotation in candidate_annotations: j = np.where(x == annotation.filename)[0][0] new_term = int(y_predict[j]) accepted = (new_term == parameters.cytomine_predict_term) logging.debug( "Annotation %d %s during post-classification (class: %d proba: %d)", annotation.id, "accepted" if accepted else "rejected", int(y_predict[j]), y_rate[j]) if not accepted: AlgoAnnotationTerm( annotation.id, parameters.cytomine_predict_term).delete() AlgoAnnotationTerm(annotation.id, new_term).save() logging.info("End of post-classification") # ... # Perform stats (counting) in roi area if parameters.cytomine_count and parameters.cytomine_roi_term: logging.info("Compute statistics") # Count number of annotations in roi area # Get Rois roi_annotations = AnnotationCollection( image=image.id, term=parameters.cytomine_roi_term, showGIS=True).fetch() # Count included annotations (term = predict_term) in each ROI for roi_annotation in roi_annotations: included_annotations = AnnotationCollection( image=image.id, user=job.userJob, bboxAnnotation=roi_annotation.id).fetch() logging.info( "Stats of image %s: %d annotations included in ROI %d (%d %s)", image.instanceFilename, len(included_annotations), roi_annotation.id, roi_annotation.area, roi_annotation.areaUnit) logging.info("Finished processing image %s", image.instanceFilename) progress += progress_delta
parser.add_argument( '--opencv', dest='opencv', action='store_true', help= "Print the annotation geometry using OpenCV coordinate system for points." ) params, other = parser.parse_known_args(sys.argv[1:]) with Cytomine(host=params.host, public_key=params.public_key, private_key=params.private_key) as cytomine: if params.opencv: image_instances = ImageInstanceCollection().fetch_with_filter( "project", params.id_project) # We want all annotations in a given project. annotations = AnnotationCollection() annotations.project = params.id_project # Add a filter: only annotations from this project # You could add other filters: # annotations.image = id_image => Add a filter: only annotations from this image # annotations.images = [id1, id2] => Add a filter: only annotations from these images # annotations.user = id_user => Add a filter: only annotations from this user # ... annotations.showWKT = True # Ask to return WKT location (geometry) in the response annotations.showMeta = True # Ask to return meta information (id, ...) in the response annotations.showGIS = True # Ask to return GIS information (perimeter, area, ...) in the response # ... # => Fetch annotations from the server with the given filters. annotations.fetch()
def main(argv): with CytomineJob.from_cli(argv) as conn: # with Cytomine(argv) as conn: print(conn.parameters) conn.job.update(status=Job.RUNNING, progress=0, statusComment="Initialization...") base_path = "{}".format(os.getenv("HOME")) # Mandatory for Singularity working_path = os.path.join(base_path, str(conn.job.id)) # with Cytomine(host=params.host, public_key=params.public_key, private_key=params.private_key, # verbose=logging.INFO) as cytomine: # ontology = Ontology("classPNcells"+str(conn.parameters.cytomine_id_project)).save() # ontology_collection=OntologyCollection().fetch() # print(ontology_collection) # ontology = Ontology("CLASSPNCELLS").save() # terms = TermCollection().fetch_with_filter("ontology", ontology.id) terms = TermCollection().fetch_with_filter( "project", conn.parameters.cytomine_id_project) conn.job.update(status=Job.RUNNING, progress=1, statusComment="Terms collected...") print(terms) # term_P = Term("PositiveCell", ontology.id, "#FF0000").save() # term_N = Term("NegativeCell", ontology.id, "#00FF00").save() # term_P = Term("PositiveCell", ontology, "#FF0000").save() # term_N = Term("NegativeCell", ontology, "#00FF00").save() # Get all the terms of our ontology # terms = TermCollection().fetch_with_filter("ontology", ontology.id) # terms = TermCollection().fetch_with_filter("ontology", ontology) # print(terms) # #Loading pre-trained Stardist model # np.random.seed(17) # lbl_cmap = random_label_cmap() # #Stardist H&E model downloaded from https://github.com/mpicbg-csbd/stardist/issues/46 # #Stardist H&E model downloaded from https://drive.switch.ch/index.php/s/LTYaIud7w6lCyuI # model = StarDist2D(None, name='2D_versatile_HE', basedir='/models/') #use local model file in ~/models/2D_versatile_HE/ #Select images to process images = ImageInstanceCollection().fetch_with_filter( "project", conn.parameters.cytomine_id_project) conn.job.update(status=Job.RUNNING, progress=2, statusComment="Images gathered...") list_imgs = [] if conn.parameters.cytomine_id_images == 'all': for image in images: list_imgs.append(int(image.id)) else: list_imgs = [ int(id_img) for id_img in conn.parameters.cytomine_id_images.split(',') ] print(list_imgs) #Go over images conn.job.update(status=Job.RUNNING, progress=10, statusComment="Running PN classification on image...") #for id_image in conn.monitor(list_imgs, prefix="Running PN classification on image", period=0.1): for id_image in list_imgs: roi_annotations = AnnotationCollection() roi_annotations.project = conn.parameters.cytomine_id_project roi_annotations.term = conn.parameters.cytomine_id_cell_term roi_annotations.image = id_image #conn.parameters.cytomine_id_image roi_annotations.job = conn.parameters.cytomine_id_annotation_job roi_annotations.user = conn.parameters.cytomine_id_user_job roi_annotations.showWKT = True roi_annotations.fetch() print(roi_annotations) #Go over ROI in this image #for roi in conn.monitor(roi_annotations, prefix="Running detection on ROI", period=0.1): for roi in roi_annotations: #Get Cytomine ROI coordinates for remapping to whole-slide #Cytomine cartesian coordinate system, (0,0) is bottom left corner print( "----------------------------Cells------------------------------" ) roi_geometry = wkt.loads(roi.location) # print("ROI Geometry from Shapely: {}".format(roi_geometry)) # print("ROI Bounds") # print(roi_geometry.bounds) minx = roi_geometry.bounds[0] miny = roi_geometry.bounds[3] #Dump ROI image into local PNG file # roi_path=os.path.join(working_path,str(roi_annotations.project)+'/'+str(roi_annotations.image)+'/'+str(roi.id)) roi_path = os.path.join( working_path, str(roi_annotations.project) + '/' + str(roi_annotations.image) + '/') # print(roi_path) roi_png_filename = os.path.join(roi_path + str(roi.id) + '.png') conn.job.update(status=Job.RUNNING, progress=20, statusComment=roi_png_filename) # print("roi_png_filename: %s" %roi_png_filename) roi.dump(dest_pattern=roi_png_filename, alpha=True) #roi.dump(dest_pattern=os.path.join(roi_path,"{id}.png"), mask=True, alpha=True) # im=Image.open(roi_png_filename) J = cv2.imread(roi_png_filename, cv2.IMREAD_UNCHANGED) J = cv2.cvtColor(J, cv2.COLOR_BGRA2RGBA) [r, c, h] = J.shape # print("J: ",J) if r < c: blocksize = r else: blocksize = c # print("blocksize:",blocksize) rr = np.zeros((blocksize, blocksize)) cc = np.zeros((blocksize, blocksize)) zz = [*range(1, blocksize + 1)] # print("zz:", zz) for i in zz: rr[i - 1, :] = zz # print("rr shape:",rr.shape) zz = [*range(1, blocksize + 1)] for i in zz: cc[:, i - 1] = zz # print("cc shape:",cc.shape) cc1 = np.asarray(cc) - 16.5 rr1 = np.asarray(rr) - 16.5 cc2 = np.asarray(cc1)**2 rr2 = np.asarray(rr1)**2 rrcc = np.asarray(cc2) + np.asarray(rr2) weight = np.sqrt(rrcc) # print("weight: ",weight) weight2 = 1. / weight # print("weight2: ",weight2) # print("weight2 shape:",weight2.shape) coord = [c / 2, r / 2] halfblocksize = blocksize / 2 y = round(coord[1]) x = round(coord[0]) # Convert the RGB image to HSV Jalpha = J[:, :, 3] Jalphaloc = Jalpha / 255 Jrgb = cv2.cvtColor(J, cv2.COLOR_RGBA2RGB) Jhsv = cv2.cvtColor(Jrgb, cv2.COLOR_RGB2HSV_FULL) Jhsv = Jhsv / 255 Jhsv[:, :, 0] = Jhsv[:, :, 0] * Jalphaloc Jhsv[:, :, 1] = Jhsv[:, :, 1] * Jalphaloc Jhsv[:, :, 2] = Jhsv[:, :, 2] * Jalphaloc # print("Jhsv: ",Jhsv) # print("Jhsv size:",Jhsv.shape) # print("Jhsv class:",Jhsv.dtype) currentblock = Jhsv[0:blocksize, 0:blocksize, :] # print("currentblock: ",currentblock) # print(currentblock.dtype) currentblockH = currentblock[:, :, 0] currentblockV = 1 - currentblock[:, :, 2] hue = sum(sum(currentblockH * weight2)) val = sum(sum(currentblockV * weight2)) # print("hue:", hue) # print("val:", val) if hue < 2: cellclass = 1 elif val < 15: cellclass = 2 else: if hue < 30 or val > 40: cellclass = 1 else: cellclass = 2 # tags = TagCollection().fetch() # tags = TagCollection() # print(tags) if cellclass == 1: # print("Positive (H: ", str(hue), ", V: ", str(val), ")") id_terms = conn.parameters.cytomine_id_positive_term # tag = Tag("Positive (H: ", str(hue), ", V: ", str(val), ")").save() # print(tag) # id_terms=Term("PositiveCell", ontology.id, "#FF0000").save() elif cellclass == 2: # print("Negative (H: ", str(hue), ", V: ", str(val), ")") id_terms = conn.parameters.cytomine_id_negative_term # for t in tags: # tag = Tag("Negative (H: ", str(hue), ", V: ", str(val), ")").save() # print(tag) # id_terms=Term("NegativeCell", ontology.id, "#00FF00").save() # First we create the required resources cytomine_annotations = AnnotationCollection() # property_collection = PropertyCollection(uri()).fetch("annotation",id_image) # property_collection = PropertyCollection().uri() # print(property_collection) # print(cytomine_annotations) # property_collection.append(Property(Annotation().fetch(id_image), key="Hue", value=str(hue))) # property_collection.append(Property(Annotation().fetch(id_image), key="Val", value=str(val))) # property_collection.save() # prop1 = Property(Annotation().fetch(id_image), key="Hue", value=str(hue)).save() # prop2 = Property(Annotation().fetch(id_image), key="Val", value=str(val)).save() # prop1.Property(Annotation().fetch(id_image), key="Hue", value=str(hue)).save() # prop2.Property(Annotation().fetch(id_image), key="Val", value=str(val)).save() # for pos, polygroup in enumerate(roi_geometry,start=1): # points=list() # for i in range(len(polygroup[0])): # p=Point(minx+polygroup[1][i],miny-polygroup[0][i]) # points.append(p) annotation = roi_geometry # tags.append(TagDomainAssociation(Annotation().fetch(id_image, tag.id))).save() # association = append(TagDomainAssociation(Annotation().fetch(id_image, tag.id))).save() # print(association) cytomine_annotations.append( Annotation( location=annotation.wkt, #location=roi_geometry, id_image=id_image, #conn.parameters.cytomine_id_image, id_project=conn.parameters.cytomine_id_project, id_terms=[id_terms])) print(".", end='', flush=True) #Send Annotation Collection (for this ROI) to Cytomine server in one http request ca = cytomine_annotations.save() conn.job.update(status=Job.TERMINATED, progress=100, statusComment="Finished.")
def main(argv): with CytomineJob.from_cli(argv) as conn: conn.job.update(progress=0, statusComment="Initialization..") base_path = "{}".format(os.getenv("HOME")) # Mandatory for Singularity working_path = os.path.join(base_path, str(conn.job.id)) # Load pretrained model (assume the best of all) conn.job.update(progress=0, statusComment="Loading segmentation model..") with open("/models/resnet50b_fpn256/config.json") as f: config = json.load(f) model = FPN.build_resnet_fpn( name=config['name'], input_size=conn.parameters.dataset_patch_size, # must be / by 16 input_channels=1 if config['input']['mode'] == 'grayscale' else 3, output_channels=config['fpn']['out_channels'], num_classes=2, # legacy in_features=config['fpn']['in_features'], out_features=config['fpn']['out_features']) model.to(_DEVICE) model_dict = torch.load(config['weights'], map_location=torch.device(_DEVICE)) model.load_state_dict(model_dict['model']) # Select images to process images = ImageInstanceCollection().fetch_with_filter( "project", conn.parameters.cytomine_id_project) if conn.parameters.cytomine_id_images != 'all': images = [ _ for _ in images if _.id in map(lambda x: int(x.strip()), conn.parameters.cytomine_id_images.split(',')) ] images_id = [image.id for image in images] # Download selected images into "working_directory" img_path = os.path.join(working_path, "images") os.makedirs(img_path) for image in conn.monitor( images, start=2, end=50, period=0.1, prefix="Downloading images into working directory.."): fname, fext = os.path.splitext(image.filename) if image.download(dest_pattern=os.path.join( img_path, "{}{}".format(image.id, fext))) is not True: print("Failed to download image {}".format(image.filename)) # create a file that lists all images (used by PatchBasedDataset conn.job.update(progress=50, statusComment="Preparing data for execution..") images = os.listdir(img_path) images = list(map(lambda x: x + '\n', images)) with open(os.path.join(working_path, 'images.txt'), 'w') as f: f.writelines(images) # Prepare dataset and dataloader objects ImgTypeBits = {'.dcm': 16} channel_bits = ImgTypeBits.get(fext.lower(), 8) mean, std = compute_mean_and_std(img_path, bits=channel_bits) dataset = InferencePatchBasedDataset( path=working_path, subset='images', patch_size=conn.parameters.dataset_patch_size, mode=config['input']['mode'], bits=channel_bits, mean=mean, std=std) dataloader = DataLoader( dataset=dataset, batch_size=conn.parameters.model_batch_size, drop_last=False, shuffle=False, num_workers=0, collate_fn=InferencePatchBasedDataset.collate_fn) # Go over images conn.job.update(status=Job.RUNNING, progress=55, statusComment="Running inference on images..") results = inference_on_segmentation( model, dataloader, conn.parameters.postprocess_p_threshold) for id_image in conn.monitor( images_id, start=90, end=95, prefix="Deleting old annotations on images..", period=0.1): # Delete old annotations del_annotations = AnnotationCollection() del_annotations.image = id_image del_annotations.user = conn.job.id del_annotations.project = conn.parameters.cytomine_id_project del_annotations.term = conn.parameters.cytomine_id_predict_term, del_annotations.fetch() for annotation in del_annotations: annotation.delete() conn.job.update( status=Job.RUNNING, progress=95, statusComment="Uploading new annotations to Cytomine server..") annotations = AnnotationCollection() for instance in results: idx, _ = os.path.splitext(instance['filename']) width, height = instance['size'] for box in instance['bbox']: points = [ Point(box[0], height - 1 - box[1]), Point(box[0], height - 1 - box[3]), Point(box[2], height - 1 - box[3]), Point(box[2], height - 1 - box[1]) ] annotation = Polygon(points) annotations.append( Annotation( location=annotation.wkt, id_image=int(idx), id_terms=[conn.parameters.cytomine_id_predict_term], id_project=conn.parameters.cytomine_id_project)) annotations.save() conn.job.update(status=Job.TERMINATED, status_comment="Finish", progress=100)
def main(): with CytomineJob.from_cli(sys.argv) as conn: base_path = "{}".format(os.getenv("HOME")) working_path = os.path.join(base_path, str(conn.job.id)) in_path = os.path.join(working_path, "in/") out_path = os.path.join(working_path, "out/") tr_working_path = os.path.join(base_path, str(conn.parameters.model_to_use)) tr_out_path = os.path.join(tr_working_path, "out/") if not os.path.exists(working_path): os.makedirs(working_path) os.makedirs(in_path) images = ImageInstanceCollection().fetch_with_filter( "project", conn.parameters.cytomine_id_project) list_imgs = [] if conn.parameters.images_to_predict == 'all': for image in images: list_imgs.append(int(image.id)) image.dump(os.path.join(in_path, '%d.jpg' % (image.id))) else: list_imgs = [ int(id_img) for id_img in conn.parameters.images_to_predict.split(',') ] for image in images: if image.id in list_imgs: image.dump(os.path.join(in_path, '%d.jpg' % (image.id))) annotation_collection = AnnotationCollection() train_job = Job().fetch(conn.parameters.model_to_use) properties = PropertyCollection(train_job).fetch() str_terms = "" for prop in properties: if prop.fetch(key='id_terms') != None: str_terms = prop.fetch(key='id_terms').value term_list = [int(x) for x in str_terms.split(' ')] attached_files = AttachedFileCollection(train_job).fetch() for id_term in conn.monitor(term_list, start=10, end=90, period=0.05, prefix="Finding landmarks for terms..."): model_file = find_by_attribute(attached_files, "filename", "%d_model.joblib" % id_term) model_filepath = os.path.join(in_path, "%d_model.joblib" % id_term) model_file.download(model_filepath, override=True) cov_file = find_by_attribute(attached_files, 'filename', '%d_cov.joblib' % id_term) cov_filepath = os.path.join(in_path, "%d_cov.joblib" % id_term) cov_file.download(cov_filepath, override=True) parameters_file = find_by_attribute( attached_files, 'filename', '%d_parameters.joblib' % id_term) parameters_filepath = os.path.join( in_path, '%d_parameters.joblib' % id_term) parameters_file.download(parameters_filepath, override=True) model = joblib.load(model_filepath) [mx, my, cm] = joblib.load(cov_filepath) parameters_hash = joblib.load(parameters_filepath) feature_parameters = None if parameters_hash['feature_type'] in ['haar', 'gaussian']: fparameters_file = find_by_attribute( attached_files, 'filename', "%d_fparameters.joblib" % id_term) fparametersl_filepath = os.path.join( in_path, "%d_fparameters.joblib" % id_term) fparameters_file.download(fparametersl_filepath, override=True) feature_parameters = joblib.load(fparametersl_filepath) for id_img in list_imgs: (x, y) = searchpoint_cytomine( in_path, id_img, model, mx, my, cm, 1. / (2.**np.arange(parameters_hash['model_depth'])), parameters_hash['window_size'], parameters_hash['feature_type'], feature_parameters, 'jpg', parameters_hash['model_npred']) circle = Point(x, y) annotation_collection.append( Annotation(location=circle.wkt, id_image=id_img, id_terms=[id_term], id_project=conn.parameters.cytomine_id_project)) annotation_collection.save()