def __init__(self, data, size, palette): if not data: # dummy image self.pixbuf = Pixbuf.new(Colorspace.RGB, False, 8, size[0] * 8 * 2, size[1] * 8 * 2) self.pixbuf.fill(0xAAAAAAFF) return # slice into blocks blocks = cut(data, BLOCK) # slice block content blocks = [cut(b, ROW) for b in blocks] # rearrange into blockrows (y/x coordinates) blocks = cut(blocks, size[0]) bytestring = [] # for each block row for y in range(0, size[1]): # for each final row for i in range(0, int(BLOCK / ROW)): # for each block column for x in range(0, size[0]): r = blocks[y][x][i] # extract pixels from rows for j in range(4): bytestring.append(r[j] & 0x0F) # first (....AAAA) bytestring.append(r[j] >> 4) # second (BBBB....) # apply palette result = [] for i in bytestring: result += palette.colors[i] # get result in binary format result = b''+bytearray(result) # create image self.pixbuf = Pixbuf.new_from_data( result, 0, False, 8, 8 * size[0], 8 * size[1], 8 * size[0] * 3, None, None) self.pixbuf = self.pixbuf.scale_simple( 8 * size[0] * 2, 8 * size[1] * 2, InterpType.NEAREST)
def mask_pixbuf(self, pb, width, height): """ Mask the pixbuf so there is no offscreen garbage on multimonitor setups """ geometries = self.get_monitor_geometries() mask = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) mask_cr = cairo.Context(mask) # fill transparent mask_cr.set_source_rgba(0, 0, 0, 0) mask_cr.fill() mask_cr.paint() for geo in geometries: mask_cr.rectangle(geo.x, geo.y, geo.width, geo.height) mask_cr.set_source_rgba(1, 1, 1, 1) mask_cr.fill() img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) cr = cairo.Context(img) # fill with the dafault color cr.set_source_rgba(0, 0, 0, 1) cr.fill() cr.paint() # use the mask to paint from the pixbuf gdk.cairo_set_source_pixbuf(cr, pb, 0, 0) cr.mask_surface(mask, 0, 0) cr.fill() stride = img.get_stride() pixels = img.get_data() data = bgra2rgba(pixels, width, height) new_pb = pixbuf.new_from_data(data, colorspace.RGB, True, 8, width, height, stride) return new_pb
def on_instance_view_row_activated(self, view, row_index, column): print("on_instance_view_row_activated:", view, " row_index:", row_index) # clear in reverse order self.clear_current_image_and_pixel_data() model, treeiter = view.get_selection().get_selected_rows() self.sop_instance_uid = model[row_index][0] if True: # TODO visually present more parts of: instance_metadata = self.client.retrieve_instance_metadata( study_instance_uid=self.study_instance_uid, series_instance_uid=self.series_instance_uid, sop_instance_uid=self.sop_instance_uid)[0] self.image_height = try_get_attr(instance_metadata, '00280010') # rows self.image_width = try_get_attr(instance_metadata, '00280011') # columns self.image_bits_per_pixel = try_get_attr( instance_metadata, '00280100') # bits per pixel self.frame_count = try_get_attr( instance_metadata, '00280008', '1' ) # only present for multi-frame image instances so default to 1 transfer_syntax_uid = try_get_attr(instance_metadata, '00020010') if transfer_syntax_uid is not None: transfer_type = DICOM_TRANSFER_SYNTAXES[transfer_syntax_uid] image_format = None # possible values are None (uncompressed/raw), 'jpeg', or 'jp2' try: start = time.time() frames = self.client.retrieve_instance_frames( study_instance_uid=self.study_instance_uid, series_instance_uid=self.series_instance_uid, sop_instance_uid=self.sop_instance_uid, frame_numbers=[1], image_format=image_format) stop = time.time() print("Retrieving frame took {}".format(stop - start)) except requests.exceptions.HTTPError: frames = [] # no frames pass # need to store frames because Pixbuf.new_from_data doesn't keep own # reference to data so Python will destroy it without Gtk knowing about it self.rgb_frames = len(frames) * [None] for image_index, frame in enumerate(frames): pixel_count = (self.image_width * self.image_height) rgb_frame_temp = bytearray( 3 * pixel_count) # pre-allocate RGB pixels if image_format is None: # raw pixels start = time.time() if self.image_bits_per_pixel == 8: for j in range(0, pixel_count): grey8 = frame[j] rgb_frame_temp[3 * j + 0] = grey8 rgb_frame_temp[3 * j + 1] = grey8 rgb_frame_temp[3 * j + 2] = grey8 if self.image_bits_per_pixel == 16: for j in range(0, pixel_count): # 16-bit grey pixel value grey16 = (256 * frame[2 * j + 1] + frame[2 * j + 0]) grey8 = int(grey16 / 128) # 16-bit to 8-bit conversion rgb_frame_temp[3 * j + 0] = grey8 rgb_frame_temp[3 * j + 1] = grey8 rgb_frame_temp[3 * j + 2] = grey8 else: raise Exception("Cannot handle bits_per_pixel being" + str(self.image_bits_per_pixel)) stop = time.time() print("Converting frame to grey-scale RGB-image took {}". format(stop - start)) # Ref: https://lazka.github.io/pgi-docs/#GdkPixbuf-2.0/classes/Pixbuf.html#GdkPixbuf.Pixbuf.new_from_data # this better than `Pixbuf.new_from_bytes` which requires # the data parameter to be wrapped in a GLib.Bytes object # WARNING: be aware of that `new_from_data` is buggy. See for instance: # https://stackoverflow.com/questions/29501835/gtk3-gdk-pixbuf-new-from-data-gives-segmentation-fault-core-dumped-error-13 start = time.time() self.rgb_frames[image_index] = bytes( rgb_frame_temp ) # bytearray needs to be wrapped in a `bytes` and store here in order to enot loose ref pixbuf = Pixbuf.new_from_data( self.rgb_frames[image_index], # data Colorspace.RGB, # colorspace False, # has_alpha 8, # bits_per_sample self.image_width, # width self.image_height, # height self.image_width * 3, # rowstride, 3 because RGB has 3 bytes per pixel None, None) # rely on Python for deallocation if image_index == 0: self.image_view.set_from_pixbuf(pixbuf) stop = time.time() print("Display image took {}".format(stop - start)) elif image_format == 'jp2': print("TODO Decode jp2 image...") if False: input_stream = Gio.MemoryInputStream.new_from_data( frame, None) pixbuf = Pixbuf.new_from_stream(input_stream, None) if image_index == 0: self.image_view.set_from_pixbuf(pixbuf)