Exemple #1
0
def _retrieve_instance_frames(args):
    '''Retrieves frames for an individual instances and either
    writes them to standard output or files on disk or displays them in a GUI
    (depending on the requested content type).
    Frames can only be saved and shown if they are retrieved using
    image media types.
    '''
    client = DICOMwebClient(args.url,
                            username=args.username,
                            password=args.password,
                            ca_bundle=args.ca_bundle,
                            cert=args.cert)
    pixel_data = client.retrieve_instance_frames(
        args.study_instance_uid,
        args.series_instance_uid,
        args.sop_instance_uid,
        args.frame_numbers,
        media_types=args.media_types,
    )

    for i, data in enumerate(pixel_data):
        if args.save or args.show:
            try:
                image = Image.open(BytesIO(data))
            except Exception:
                try:
                    import jpeg_ls
                    image = jpeg_ls.decode(np.fromstring(data, dtype=np.uint8))
                except Exception:
                    raise IOError('Cannot load retrieved frame as an image.')
            if args.save:
                filename = (
                    '{sop_instance_uid}_{frame_number}.{extension}'.format(
                        sop_instance_uid=args.sop_instance_uid,
                        frame_number=args.frame_numbers[i],
                        extension=image.format.lower()))
                filepath = os.path.join(args.output_dir, filename)
                _save_image(image, filepath)
            elif args.show:
                _show_image(image)
        else:
            _print_pixel_data(data)
#     sop_instance_uid='1.2.840.113704.7.1.0.192317454216202.1587243260.27398',
# )

# print('instance')
# print(instance)
# print()

# print("Writing test file")
# instance.save_as('teste-dicom-exame10')
# print("File saved.")

frames = client.retrieve_instance_frames(
    study_instance_uid=
    '1.2.840.113704.7.1.0.253219132234129147.1587168555.1281',
    series_instance_uid=
    '1.2.840.113704.7.32.004.7.1.0.253219132234129147.1587168635.3928',
    sop_instance_uid='1.2.840.113704.7.1.0.192317454216202.1587243260.27398',
    frame_numbers=[1],
    media_types=('image/jpeg', ),
)

print('frames', len(frames))
# print(frames)
print()

out_f = 'teste-pac-server'

for i, frame in enumerate(frames):
    filename = f"{out_f}/frame-{i}.jpeg"

    with open(filename, 'wb') as outfile:
Exemple #3
0
class DICOMWindow(Gtk.Window):
    def __init__(self, title="DICOM Viewer"):

        self.client = DICOMwebClient(
            url='http://dev-demo.sectra.se',
            qido_url_prefix='SectraQidoRs/mrnissuers/all',
            wado_url_prefix='SectraWadoRs/mrnissuers/all',
            stow_url_prefix='SectraStowRs/mrnissuers/all')

        # construct top window
        Gtk.Window.__init__(
            self,
            title=title,
            vexpand=True,
            hexpand=True,
            border_width=0,
        )

        # create top grid
        self.grid = Gtk.Grid(vexpand=True,
                             hexpand=True,
                             column_homogeneous=True,
                             row_homogeneous=True)
        self.add(self.grid)  # add it to parent window

        # STUDIES
        self.study_store = StudyStore(client=self.client)
        self.current_study_filter = None
        self.study_filter = self.study_store.filter_new()
        self.study_filter.set_visible_func(self.study_view_filter_func)
        self.study_view = Gtk.TreeView.new_with_model(self.study_filter)
        self.study_view.connect("row-activated",
                                self.on_study_view_row_activated
                                )  # upon double-click or return press
        self.study_selection = self.study_view.get_selection()
        self.study_selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        self.study_selection.connect("changed",
                                     self.on_study_selection_changed)
        for i, column_title in enumerate([
                "Study UID",
                "Study Name",
                "Person Name",
                "Study Date",
                "Study Time",
        ]):
            if i == 0:
                continue  #skip "Study UID"
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            column.set_sort_column_id(i)  #trying to make columns sortable
            self.study_view.append_column(column)
        self.study_view_scroller = Gtk.ScrolledWindow(vexpand=True,
                                                      hexpand=True)
        self.study_view_scroller.add(self.study_view)
        self.grid.attach(self.study_view_scroller,
                         left=0,
                         top=0,
                         width=1,
                         height=1)

        # SERIESES
        self.series_store = SeriesStore(client=self.client)
        self.current_series_filter = None
        self.series_filter = self.series_store.filter_new()
        self.series_filter.set_visible_func(self.series_view_filter_func)
        self.series_view = Gtk.TreeView.new_with_model(self.series_filter)
        self.series_view.connect("row-activated",
                                 self.on_series_view_row_activated
                                 )  # upon double-click or return press
        self.series_selection = self.series_view.get_selection()
        self.series_selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        self.series_selection.connect("changed",
                                      self.on_series_selection_changed)
        for i, column_title in enumerate(["Series UID", "Series Modality"]):
            if i == 0:
                continue  # skip "Series UID"
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.series_view.append_column(column)
        self.series_view_scroller = Gtk.ScrolledWindow(vexpand=True,
                                                       hexpand=True)
        self.series_view_scroller.add(self.series_view)
        self.grid.attach(self.series_view_scroller,
                         left=1,
                         top=0,
                         width=1,
                         height=1)

        # INSTANCES
        self.instance_store = InstanceStore(client=self.client)
        self.current_instance_filter = None
        self.instance_filter = self.instance_store.filter_new()
        self.instance_filter.set_visible_func(self.instance_view_filter_func)
        self.instance_view = Gtk.TreeView.new_with_model(self.instance_filter)
        self.instance_view.connect("row-activated",
                                   self.on_instance_view_row_activated
                                   )  # upon double-click or return press
        self.instance_selection = self.instance_view.get_selection()
        self.instance_selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        self.instance_selection.connect("changed",
                                        self.on_instance_selection_changed)
        for i, column_title in enumerate(["SOP Instance UID", "SOP Class"]):
            if i == 0:
                continue  # skip "SOP Instance UID"
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.instance_view.append_column(column)
        self.instance_view_scroller = Gtk.ScrolledWindow(vexpand=True,
                                                         hexpand=True)
        self.instance_view_scroller.add(self.instance_view)
        self.grid.attach(self.instance_view_scroller,
                         left=2,
                         top=0,
                         width=1,
                         height=1)

        self.image_view = Gtk.Image()
        self.image_view_scroller = Gtk.ScrolledWindow(vexpand=True,
                                                      hexpand=True)
        self.image_view_scroller.add(self.image_view)
        self.grid.attach(self.image_view_scroller,
                         left=0,
                         top=1,
                         width=3,
                         height=3)

        self.show_all()

    def on_study_view_row_activated(self, view, row_index, column):
        print("on_study_view_row_activated:", view, " row_index:", row_index)

        # clear in reverse order
        self.clear_current_image_and_pixel_data()
        self.clear_current_instance_store_and_view()
        self.clear_current_series_store_and_view()

        model, treeiter = view.get_selection().get_selected_rows()
        self.study_instance_uid = model[row_index][0]
        self.series_store.update_using_study(
            study_instance_uid=self.study_instance_uid)

    def on_study_selection_changed(self, selection):
        # clear in reverse order
        self.clear_current_image_and_pixel_data()
        self.clear_current_instance_store_and_view()
        self.clear_current_series_store_and_view()
        # print('on_study_selection_changed: seletion:{}, selection_count={}'.format(selection,
        #                                                                            selection.count_selected_rows()))
        # TODO visually present:
        # study_metadata = self.client.retrieve_study_metadata(self.study_instance_uid)

    def on_series_view_row_activated(self, view, row_index, column):
        print("on_series_view_row_activated:", view, " row_index:", row_index)

        # clear in reverse order
        self.clear_current_image_and_pixel_data()
        self.clear_current_instance_store_and_view()

        model, treeiter = view.get_selection().get_selected_rows()
        self.series_instance_uid = model[row_index][0]
        self.instance_store.update_using_study_and_series(
            study_instance_uid=self.study_instance_uid,
            series_instance_uid=self.series_instance_uid)

    def on_series_selection_changed(self, selection):
        '''This is function is called too often so we cannot do any heavy work here.'''
        # print('on_series_selection_changed: seletion:{}, selection_count={}'.format(selection,
        #                                                                            selection.count_selected_rows()))

        # clear in reverse order
        self.clear_current_image_and_pixel_data()
        self.clear_current_instance_store_and_view()

        # TODO visually present:
        # series_metadata = self.client.retrieve_series_metadata(self.series_instance_uid)

    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)

    def on_instance_selection_changed(self, selection):
        '''This is function is called too often so we cannot do any heavy work here.'''
        pass
        # print('on_instance_selection_changed: seletion:{}, selection_count={}'.format(selection,
        #                                                                             selection.count_selected_rows()))
        # TODO visually present:
        # instance_metadata = self.client.retrieve_instance_metadata(self.instance_instance_uid)

    def clear_current_series_store_and_view(self):
        self.series_view.get_selection().unselect_all()
        self.series_store.clear()

    def clear_current_instance_store_and_view(self):
        self.instance_view.get_selection().unselect_all()
        self.instance_store.clear()

    def clear_current_image_and_pixel_data(self):
        self.image_view.clear()  # remove image before
        self.rgb_frames = None  # drop pixel data

    def study_view_filter_func(self, model, iter, data):
        return True  # accept everything for now

    def series_view_filter_func(self, model, iter, data):
        return True  # accept everything for now

    def instance_view_filter_func(self, model, iter, data):
        return True  # accept everything for now