From 574aa1c6c0a0677d88b6136cfc4109bcd26a0e94 Mon Sep 17 00:00:00 2001 From: jaknapper Date: Tue, 8 Apr 2025 17:52:55 +0100 Subject: [PATCH] Only grab a JPEG from the stream to get scanning working --- .../things/smart_scan.py | 126 +++++++----------- 1 file changed, 49 insertions(+), 77 deletions(-) diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index cdd0780f..d1038a38 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -576,58 +576,8 @@ class SmartScanThing(Thing): ) as f: json.dump(data, f, ensure_ascii=False, indent=4) - # We will capture images and process them with this function, defined once here. - # Most of the variables it needs will be "baked in" so the arguments are just the ones - # that change each iteration. - # We also pre-calculate a normalisation image based on the LST and white balance - raw_image = cam.capture_array(stream_name="raw") - # TODO: assert the image is 10-bit packed, or deal with other formats! - rgb = rggb2rgb(raw2rggb(raw_image)) - lst = dict(cam.lens_shading_tables) - lum = np.array(lst["luminance"]) - Cr = np.array(lst["Cr"]) - Cb = np.array(lst["Cb"]) - gr, gb = cam.colour_gains - G = 1 / lum - R = ( - G / Cr / gr * np.min(Cr) - ) # The extra /np.max(Cr) emulates the quirky handling of Cr in - B = G / Cb / gb * np.min(Cb) # the picamera2 pipeline - white_norm_lores = np.stack([R, G, B], axis=2) - zoom_factors = [ - i / n for i, n in zip(rgb[..., :3].shape, white_norm_lores.shape) - ] - white_norm = zoom(white_norm_lores, zoom_factors, order=1)[ - : rgb.shape[0], : rgb.shape[1], : - ] # Could use some work - colour_correction_matrix = np.array(cam.colour_correction_matrix).reshape( - (3, 3) - ) - contrast_algorithm = cam.tuning["algorithms"][9]["rpi.contrast"] - gamma = np.array(contrast_algorithm["gamma_curve"]).reshape((-1, 2)) - gamma_8bit = interp1d(gamma[:, 0] / 255, gamma[:, 1] / 255) - - def process_raw_image(img): - normed = img / white_norm - corrected = np.dot( - colour_correction_matrix, normed.reshape((-1, 3)).T - ).T.reshape(normed.shape) - corrected[corrected < 0] = 0 - corrected[corrected > 255] = 255 - return gamma_8bit(corrected) - - logger.info( - f"Generated normalisation image with shape {white_norm.shape}, " - f"max {white_norm.max(axis=(0, 1))}, min {white_norm.min(axis=(0, 1))}" - ) - norm_inputs = { - "luminance": lum, - "Cr": Cr, - "Cb": Cb, - "gain_red": gr, - "gain_blue": gb, - } - + # This is the function we'll use to grab (or later capture) an image + # and save it with metadata def capture_and_save(acquired: Event, name: str) -> None: """Capture an image and save it to disk @@ -636,38 +586,60 @@ class SmartScanThing(Thing): """ try: capture_start = time.time() - metadata = metadata_getter() - raw_image = cam.capture_array(stream_name="raw") + image, metadata = capture_image() acquired.set() acquisition_time = time.time() - # Save the raw image - np.savez( - os.path.join(raw_images_folder, name + ".npz"), - raw_image=raw_image, - **norm_inputs, - ) - # Process it into 8 bit RGB - processed = process_raw_image(rggb2rgb(raw2rggb(raw_image))) - processed[processed > 255] = 255 - processed[processed < 0] = 0 - img = Image.fromarray(processed.astype(np.uint8), mode="RGB") - img.save( - os.path.join(images_folder, name), quality=95, subsampling=0 - ) - exif_dict = piexif.load(os.path.join(images_folder, name)) - exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps( - metadata - ).encode("utf-8") - piexif.insert( - piexif.dump(exif_dict), os.path.join(images_folder, name) - ) + save_capture(name, image, metadata) save_time = time.time() + acquisition_duration = round(acquisition_time - capture_start, 1) + saving_duration = round(save_time - acquisition_time, 1) logger.info( - f"Acquired {name} in {acquisition_time - capture_start:.1f}s then {save_time - acquisition_time:.1f}s saving to disk" + "Acquired {} in {}s then {}s saving to disk".format( + name, acquisition_duration, saving_duration + ) ) except Exception as e: logger.error( - f"An error occurred while saving {name}: {e}", exc_info=e + "An error occurred while saving {}: {}".format(name, e), + exc_info=e, + ) + + def capture_image(): + """Capture an image in memory and return it with metadata + + This will set the event `acquired` once the image has been acquired, so + that the stage may be moved while it's saved. + """ + try: + metadata = metadata_getter() + image = cam.capture_array()[..., :3] + return image, metadata + except Exception as e: + logger.error( + "An error occurred while capturing: {}".format(e), exc_info=e + ) + return 0, 0 + + def save_capture(name, image, metadata): + try: + jpeg_path = os.path.join(images_folder, name) + PIL_image = Image.fromarray(image.astype("uint8"), "RGB").save( + jpeg_path, quality=95, subsampling=0 + ) + try: + exif_dict = piexif.load(os.path.join(images_folder, name)) + exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps( + metadata + ).encode("utf-8") + piexif.insert( + piexif.dump(exif_dict), os.path.join(images_folder, name) + ) + except: + pass + except Exception as e: + logger.error( + "An error occurred while saving {}: {}".format(name, e), + exc_info=e, ) # At the start of the loop, we simultaneously capture an image and move to the next scan point.