def extract_substack_no_rotation( job ): """ Extracts a sub-stack as specified in the passed job without respecting rotation requests. A list of pgmagick images is returned -- one for each slice, starting on top. """ # The actual bounding boxes used for creating the images of each stack # depend not only on the request, but also on the translation of the stack # wrt. the project. Therefore, a dictionary with bounding box information for # each stack is created. s_to_bb = {} for stack in job.stacks: # Retrieve translation relative to current project translation = ProjectStack.objects.get( project_id=job.project_id, stack_id=stack.id).translation x_min_t = job.x_min - translation.x x_max_t = job.x_max - translation.x y_min_t = job.y_min - translation.y y_max_t = job.y_max - translation.y z_min_t = job.z_min - translation.z z_max_t = job.z_max - translation.z # Calculate the slice numbers and pixel positions # bound to the stack data. px_x_min = to_x_index(x_min_t, job) px_x_max = to_x_index(x_max_t, job) px_y_min = to_y_index(y_min_t, job) px_y_max = to_y_index(y_max_t, job) px_z_min = to_z_index(z_min_t, job) px_z_max = to_z_index(z_max_t, job) # Because it might be that the cropping goes over the # stack bounds, we need to calculate the unbounded height, # with and an offset. px_x_min_nobound = to_x_index(x_min_t, job, False) px_x_max_nobound = to_x_index(x_max_t, job, False) px_y_min_nobound = to_y_index(y_min_t, job, False) px_y_max_nobound = to_y_index(y_max_t, job, False) width = px_x_max_nobound - px_x_min_nobound height = px_y_max_nobound - px_y_min_nobound px_x_offset = abs(px_x_min_nobound) if px_x_min_nobound < 0 else 0 px_y_offset = abs(px_y_min_nobound) if px_y_min_nobound < 0 else 0 # Create a dictionary entry with a simple object class BB: pass bb = BB() bb.px_x_min = px_x_min bb.px_x_max = px_x_max bb.px_y_min = px_y_min bb.px_y_max = px_y_max bb.px_z_min = px_z_min bb.px_z_max = px_z_max bb.px_x_offset = px_x_offset bb.px_y_offset = px_y_offset bb.width = width bb.height = height s_to_bb[stack.id] = bb # Get number of wanted slices px_z_min = to_z_index(job.z_min, job) px_z_max = to_z_index(job.z_max, job) n_slices = px_z_max + 1 - px_z_min # The images are generated per slice, so most of the following # calculations refer to 2d images. # Each stack to export is treated as a separate channel. The order # of the exported dimensions is XYCZ. This means all the channels of # one slice are exported, then the next slice follows, etc. cropped_stack = [] # Accumulator for estimated result size estimated_total_size = 0 # Iterate over all slices for nz in range(n_slices): for stack in job.stacks: bb = s_to_bb[stack.id] # Shortcut for tile width and height tile_width = stack.tile_width tile_height = stack.tile_height # Get indices for bounding tiles (0 indexed) tile_x_min = int(bb.px_x_min / tile_width) tile_x_max = int(bb.px_x_max / tile_width) tile_y_min = int(bb.px_y_min / tile_height) tile_y_max = int(bb.px_y_max / tile_height) # Get the number of needed tiles for each direction num_x_tiles = tile_x_max - tile_x_min + 1 num_y_tiles = tile_y_max - tile_y_min + 1 # Associate image parts with all tiles image_parts = [] x_dst = bb.px_x_offset for nx, x in enumerate( range(tile_x_min, tile_x_max + 1) ): # The min x,y for the image part in the current tile are 0 # for all tiles except the first one. cur_px_x_min = 0 if nx > 0 else bb.px_x_min - x * tile_width # The max x,y for the image part of current tile are the tile # size minus one except for the last one. if nx < (num_x_tiles - 1): cur_px_x_max = tile_width - 1 else: cur_px_x_max = bb.px_x_max - x * tile_width # Reset y destination component y_dst = bb.px_y_offset for ny, y in enumerate( range(tile_y_min, tile_y_max + 1) ): cur_px_y_min = 0 if ny > 0 else bb.px_y_min - y * tile_height if ny < (num_y_tiles - 1): cur_px_y_max = tile_height - 1 else: cur_px_y_max = bb.px_y_max - y * tile_height # Create an image part definition z = bb.px_z_min + nz path = job.get_tile_path(stack, (x, y, z)) try: part = ImagePart(path, cur_px_x_min, cur_px_x_max, cur_px_y_min, cur_px_y_max, x_dst, y_dst) image_parts.append( part ) except: # ignore failed slices pass # Update y component of destination position y_dst += cur_px_y_max - cur_px_y_min # Update x component of destination position x_dst += cur_px_x_max - cur_px_x_min # Write out the image parts and make sure the maximum allowed file # size isn't exceeded. cropped_slice = None for ip in image_parts: # Get (correctly cropped) image image = ip.get_image() # Estimate total file size and abort if this exceeds the # maximum allowed file size. estimated_total_size = estimated_total_size + ip.estimated_size if estimated_total_size > settings.GENERATED_FILES_MAXIMUM_SIZE: raise ValueError("The estimated size of the requested image " "region is larger than the maximum allowed " "file size: %0.2f > %s Bytes" % \ (estimated_total_size, settings.GENERATED_FILES_MAXIMUM_SIZE)) # It is unfortunately not possible to create proper composite # images based on a canvas image newly created like this: # cropped_slice = Image( Geometry(bb.width, bb.height), Color("black")) # Therefore, this workaround is used. if not cropped_slice: cropped_slice = Image(image) cropped_slice.backgroundColor("black") cropped_slice.erase() # The '!' makes sure the aspect ration is ignored cropped_slice.scale('%sx%s!' % (bb.width, bb.height)) # Draw the image onto result image cropped_slice.composite( image, ip.x_dst, ip.y_dst, co.OverCompositeOp ) # Delete tile image - it's not needed anymore del image if cropped_slice: # Optionally, use only a single channel if job.single_channel: cropped_slice.channel( ChannelType.RedChannel ) # Add the image to the cropped stack cropped_stack.append( cropped_slice ) return cropped_stack
def extract_substack_no_rotation(job) -> List: """ Extracts a sub-stack as specified in the passed job without respecting rotation requests. A list of pgmagick images is returned -- one for each slice, starting on top. """ # The actual bounding boxes used for creating the images of each stack # depend not only on the request, but also on the translation of the stack # wrt. the project. Therefore, a dictionary with bounding box information for # each stack is created. s_to_bb = {} for stack_mirror in job.stack_mirrors: stack = stack_mirror.stack # Retrieve translation relative to current project translation = ProjectStack.objects.get(project_id=job.project_id, stack_id=stack.id).translation x_min_t = job.x_min - translation.x x_max_t = job.x_max - translation.x y_min_t = job.y_min - translation.y y_max_t = job.y_max - translation.y z_min_t = job.z_min - translation.z z_max_t = job.z_max - translation.z # Calculate the slice numbers and pixel positions # bound to the stack data. px_x_min = to_x_index(x_min_t, stack, job.zoom_level) px_x_max = to_x_index(x_max_t, stack, job.zoom_level) px_y_min = to_y_index(y_min_t, stack, job.zoom_level) px_y_max = to_y_index(y_max_t, stack, job.zoom_level) px_z_min = to_z_index(z_min_t, stack, job.zoom_level) px_z_max = to_z_index(z_max_t, stack, job.zoom_level) # Because it might be that the cropping goes over the # stack bounds, we need to calculate the unbounded height, # with and an offset. px_x_min_nobound = to_x_index(x_min_t, stack, job.zoom_level, False) px_x_max_nobound = to_x_index(x_max_t, stack, job.zoom_level, False) px_y_min_nobound = to_y_index(y_min_t, stack, job.zoom_level, False) px_y_max_nobound = to_y_index(y_max_t, stack, job.zoom_level, False) width = px_x_max_nobound - px_x_min_nobound height = px_y_max_nobound - px_y_min_nobound px_x_offset = abs(px_x_min_nobound) if px_x_min_nobound < 0 else 0 px_y_offset = abs(px_y_min_nobound) if px_y_min_nobound < 0 else 0 # Create a dictionary entry with a simple object bb = BB() bb.px_x_min = px_x_min bb.px_x_max = px_x_max bb.px_y_min = px_y_min bb.px_y_max = px_y_max bb.px_z_min = px_z_min bb.px_z_max = px_z_max bb.px_x_offset = px_x_offset bb.px_y_offset = px_y_offset bb.width = width bb.height = height s_to_bb[stack.id] = bb # Get number of wanted slices px_z_min = to_z_index(job.z_min, job.ref_stack, job.zoom_level) px_z_max = to_z_index(job.z_max, job.ref_stack, job.zoom_level) n_slices = px_z_max + 1 - px_z_min # The images are generated per slice, so most of the following # calculations refer to 2d images. # Each stack to export is treated as a separate channel. The order # of the exported dimensions is XYCZ. This means all the channels of # one slice are exported, then the next slice follows, etc. cropped_stack = [] # Accumulator for estimated result size estimated_total_size = 0 # Iterate over all slices for nz in range(n_slices): for mirror in job.stack_mirrors: stack = mirror.stack bb = s_to_bb[stack.id] # Shortcut for tile width and height tile_width = mirror.tile_width tile_height = mirror.tile_height # Get indices for bounding tiles (0 indexed) tile_x_min = int(bb.px_x_min / tile_width) tile_x_max = int(bb.px_x_max / tile_width) tile_y_min = int(bb.px_y_min / tile_height) tile_y_max = int(bb.px_y_max / tile_height) # Get the number of needed tiles for each direction num_x_tiles = tile_x_max - tile_x_min + 1 num_y_tiles = tile_y_max - tile_y_min + 1 # Associate image parts with all tiles image_parts = [] x_dst = bb.px_x_offset for nx, x in enumerate(range(tile_x_min, tile_x_max + 1)): # The min x,y for the image part in the current tile are 0 # for all tiles except the first one. cur_px_x_min = 0 if nx > 0 else bb.px_x_min - x * tile_width # The max x,y for the image part of current tile are the tile # size minus one except for the last one. if nx < (num_x_tiles - 1): cur_px_x_max = tile_width - 1 else: cur_px_x_max = bb.px_x_max - x * tile_width # Reset y destination component y_dst = bb.px_y_offset for ny, y in enumerate(range(tile_y_min, tile_y_max + 1)): cur_px_y_min = 0 if ny > 0 else bb.px_y_min - y * tile_height if ny < (num_y_tiles - 1): cur_px_y_max = tile_height - 1 else: cur_px_y_max = bb.px_y_max - y * tile_height # Create an image part definition z = bb.px_z_min + nz path = job.get_tile_path(stack, mirror, (x, y, z)) try: part = ImagePart(path, cur_px_x_min, cur_px_x_max, cur_px_y_min, cur_px_y_max, x_dst, y_dst) image_parts.append(part) except: # ignore failed slices pass # Update y component of destination position y_dst += cur_px_y_max - cur_px_y_min # Update x component of destination position x_dst += cur_px_x_max - cur_px_x_min # Write out the image parts and make sure the maximum allowed file # size isn't exceeded. cropped_slice = Image(Geometry(bb.width, bb.height), ColorRGB(0, 0, 0)) for ip in image_parts: # Get (correctly cropped) image image = ip.get_image() # Estimate total file size and abort if this exceeds the # maximum allowed file size. estimated_total_size = estimated_total_size + ip.estimated_size if estimated_total_size > settings.GENERATED_FILES_MAXIMUM_SIZE: raise ValueError("The estimated size of the requested image " "region is larger than the maximum allowed " "file size: %0.2f > %s Bytes" % \ (estimated_total_size, settings.GENERATED_FILES_MAXIMUM_SIZE)) # Draw the image onto result image cropped_slice.composite(image, ip.x_dst, ip.y_dst, co.OverCompositeOp) # Delete tile image - it's not needed anymore del image if cropped_slice: # Optionally, use only a single channel if job.single_channel: cropped_slice.channel(ChannelType.RedChannel) # Add the image to the cropped stack cropped_stack.append(cropped_slice) return cropped_stack
def extract_substack_no_rotation(job): """ Extracts a sub-stack as specified in the passed job without respecting rotation requests. A list of pgmagick images is returned -- one for each slice, starting on top. """ # The actual bounding boxes used for creating the images of each stack # depend not only on the request, but also on the translation of the stack # wrt. the project. Therefore, a dictionary with bounding box information for # each stack is created. s_to_bb = {} for stack in job.stacks: # Retrieve translation relative to current project translation = ProjectStack.objects.get(project_id=job.project_id, stack_id=stack.id).translation x_min_t = job.x_min - translation.x x_max_t = job.x_max - translation.x y_min_t = job.y_min - translation.y y_max_t = job.y_max - translation.y z_min_t = job.z_min - translation.z z_max_t = job.z_max - translation.z # Calculate the slice numbers and pixel positions # bound to the stack data. px_x_min = to_x_index(x_min_t, job) px_x_max = to_x_index(x_max_t, job) px_y_min = to_y_index(y_min_t, job) px_y_max = to_y_index(y_max_t, job) px_z_min = to_z_index(z_min_t, job) px_z_max = to_z_index(z_max_t, job) # Because it might be that the cropping goes over the # stack bounds, we need to calculate the unbounded height, # with and an offset. px_x_min_nobound = to_x_index(x_min_t, job, False) px_x_max_nobound = to_x_index(x_max_t, job, False) px_y_min_nobound = to_y_index(y_min_t, job, False) px_y_max_nobound = to_y_index(y_max_t, job, False) width = px_x_max_nobound - px_x_min_nobound height = px_y_max_nobound - px_y_min_nobound px_x_offset = abs(px_x_min_nobound) if px_x_min_nobound < 0 else 0 px_y_offset = abs(px_y_min_nobound) if px_y_min_nobound < 0 else 0 # Create a dictionary entry with a simple object class BB: pass bb = BB() bb.px_x_min = px_x_min bb.px_x_max = px_x_max bb.px_y_min = px_y_min bb.px_y_max = px_y_max bb.px_z_min = px_z_min bb.px_z_max = px_z_max bb.px_x_offset = px_x_offset bb.px_y_offset = px_y_offset bb.width = width bb.height = height s_to_bb[stack.id] = bb # Get number of wanted slices px_z_min = to_z_index(job.z_min, job) px_z_max = to_z_index(job.z_max, job) n_slices = px_z_max + 1 - px_z_min # The images are generated per slice, so most of the following # calculations refer to 2d images. # Each stack to export is treated as a separate channel. The order # of the exported dimensions is XYCZ. This means all the channels of # one slice are exported, then the next slice follows, etc. cropped_stack = [] # Iterate over all slices for nz in range(n_slices): for stack in job.stacks: bb = s_to_bb[stack.id] # Shortcut for tile width and height tile_width = stack.tile_width tile_height = stack.tile_height # Get indices for bounding tiles (0 indexed) tile_x_min = int(bb.px_x_min / tile_width) tile_x_max = int(bb.px_x_max / tile_width) tile_y_min = int(bb.px_y_min / tile_height) tile_y_max = int(bb.px_y_max / tile_height) # Get the number of needed tiles for each direction num_x_tiles = tile_x_max - tile_x_min + 1 num_y_tiles = tile_y_max - tile_y_min + 1 # Associate image parts with all tiles image_parts = [] x_dst = bb.px_x_offset for nx, x in enumerate(range(tile_x_min, tile_x_max + 1)): # The min x,y for the image part in the current tile are 0 # for all tiles except the first one. cur_px_x_min = 0 if nx > 0 else bb.px_x_min - x * tile_width # The max x,y for the image part of current tile are the tile # size minus one except for the last one. if nx < (num_x_tiles - 1): cur_px_x_max = tile_width - 1 else: cur_px_x_max = bb.px_x_max - x * tile_width # Reset y destination component y_dst = bb.px_y_offset for ny, y in enumerate(range(tile_y_min, tile_y_max + 1)): cur_px_y_min = 0 if ny > 0 else bb.px_y_min - y * tile_height if ny < (num_y_tiles - 1): cur_px_y_max = tile_height - 1 else: cur_px_y_max = bb.px_y_max - y * tile_height # Create an image part definition z = bb.px_z_min + nz path = job.get_tile_path(stack, (x, y, z)) try: part = ImagePart(path, cur_px_x_min, cur_px_x_max, cur_px_y_min, cur_px_y_max, x_dst, y_dst) image_parts.append(part) except: # ignore failed slices pass # Update y component of destination position y_dst += cur_px_y_max - cur_px_y_min # Update x component of destination position x_dst += cur_px_x_max - cur_px_x_min # write out the image parts cropped_slice = None for ip in image_parts: # Get (correctly cropped) image image = ip.get_image() # It is unfortunately not possible to create proper composite # images based on a canvas image newly created like this: # cropped_slice = Image( Geometry(bb.width, bb.height), Color("black")) # Therefore, this workaround is used. if not cropped_slice: cropped_slice = Image(image) cropped_slice.backgroundColor("black") cropped_slice.erase() # The '!' makes sure the aspect ration is ignored cropped_slice.scale('%sx%s!' % (bb.width, bb.height)) # Draw the image onto result image cropped_slice.composite(image, ip.x_dst, ip.y_dst, co.OverCompositeOp) # Delete tile image - it's not needed anymore del image # Optionally, use only a single channel if job.single_channel: cropped_slice.channel(ChannelType.RedChannel) # Add the image to the cropped stack cropped_stack.append(cropped_slice) return cropped_stack
def extract_substack_no_rotation( job ): """ Extracts a sub-stack as specified in the passed job without respecting rotation requests. A list of pgmagick images is returned -- one for each slice, starting on top. """ # Calculate the slice numbers and pixel positions # bounded to the stack data. px_x_min = to_x_index(job.x_min, job) px_x_max = to_x_index(job.x_max, job) px_y_min = to_y_index(job.y_min, job) px_y_max = to_y_index(job.y_max, job) px_z_min = to_z_index(job.z_min, job) px_z_max = to_z_index(job.z_max, job) # Because it might be that the cropping goes over the # stack bounds, we need to calculate the unbounded height, # with and an offset. px_x_min_nobound = to_x_index(job.x_min, job, False) px_x_max_nobound = to_x_index(job.x_max, job, False) px_y_min_nobound = to_y_index(job.y_min, job, False) px_y_max_nobound = to_y_index(job.y_max, job, False) width = px_x_max_nobound - px_x_min_nobound height = px_y_max_nobound - px_y_min_nobound px_x_offset = abs(px_x_min_nobound) if px_x_min_nobound < 0 else 0 px_y_offset = abs(px_y_min_nobound) if px_y_min_nobound < 0 else 0 # The images are generated per slice, so most of the following # calculations refer to 2d images. # Each stack to export is treated as a seperate channel. The order # of the exported dimensions is XYCZ. This means all the channels of # one slice are exported, then the next slice follows, etc. cropped_stack = [] # Iterate over all slices for z in range( px_z_min, px_z_max + 1): for stack in job.stacks: tile_width = stack.tile_width tile_height = stack.tile_height # Get indices for bounding tiles (0 indexed) tile_x_min = int(px_x_min / tile_width) tile_x_max = int(px_x_max / tile_width) tile_y_min = int(px_y_min / tile_height) tile_y_max = int(px_y_max / tile_height) # Get the number of needed tiles for each direction num_x_tiles = tile_x_max - tile_x_min + 1 num_y_tiles = tile_y_max - tile_y_min + 1 # Associate image parts with all tiles image_parts = [] x_dst = px_x_offset for nx, x in enumerate( range(tile_x_min, tile_x_max + 1) ): # The min x,y for the image part in the current tile are 0 # for all tiles except the first one. cur_px_x_min = 0 if nx > 0 else px_x_min - x * tile_width # The max x,y for the image part of current tile are the tile # size minus one except for the last one. if nx < (num_x_tiles - 1): cur_px_x_max = tile_width - 1 else: cur_px_x_max = px_x_max - x * tile_width # Reset y destination component y_dst = px_y_offset for ny, y in enumerate( range(tile_y_min, tile_y_max + 1) ): cur_px_y_min = 0 if ny > 0 else px_y_min - y * tile_height if ny < (num_y_tiles - 1): cur_px_y_max = tile_height - 1 else: cur_px_y_max = px_y_max - y * tile_height # Create an image part definition path = get_tile_path(job, stack, [x, y, z]) try: part = ImagePart(path, cur_px_x_min, cur_px_x_max, cur_px_y_min, cur_px_y_max, x_dst, y_dst) image_parts.append( part ) except: # ignore failed slices pass # Update y component of destination postition y_dst += cur_px_y_max - cur_px_y_min # Update x component of destination postition x_dst += cur_px_x_max - cur_px_x_min # Create a result image slice, painted black cropped_slice = Image( Geometry( width, height ), Color("black") ) # write out the image parts for ip in image_parts: # Get (correcly cropped) image image = ip.get_image() # Draw the image onto result image cropped_slice.composite( image, ip.x_dst, ip.y_dst, co.OverCompositeOp ) # Delete tile image - it's not needed anymore del image # Optionally, use only a single channel if job.single_channel: cropped_slice.channel( ChannelType.RedChannel ) # Add the imag to the cropped stack cropped_stack.append( cropped_slice ) return cropped_stack