SpeciesFileGroup/taxonworks

View on GitHub
lib/tools/image_matrix.rb

Summary

Maintainability
D
2 days
Test Coverage
# Contains methods used to build an image_matrix table
#
# endpoint: http://localhost:3000/tasks/observation_matrices/image_matrix/36/key?observation_matrix_id=36&page=5
# http://localhost:3000/tasks/observation_matrices/image_matrix/0/key?otu_filter=30947|22978|23065
#
class Tools::ImageMatrix

  ##### FILTER PARAMETERS #####

  # @!observation_matrix_id
  #   @return [String]
  # Required attribude to build the key
  attr_accessor :observation_matrix_id

  # @!project_id
  #   @return [String]
  # Required attribute to build the key
  attr_accessor :project_id

  # @!language_id
  #   @return [String or null]
  # Optional attribute to display the descriptors and character_states in a particular language (when translations are available)
  attr_accessor :language_id

  # @!keyword_ids
  #   @return [String or null]
  # Optional attribute to provide a list of tagIDs to limit the set of descriptors to those taged: "keyword_ids=1|5|15"
  attr_accessor :keyword_ids

  # @!row_filter
  #   @return [String or null]
  # Optional attribute to provide a list of rowIDs to limit the set "row_filter=1|5|10"
  attr_accessor :row_filter

  # @!otu_filter
  #   @return [String or null] like "otu_filter=1|5|10"
  # Optional attribute to provide a list of rowIDs to limit the set
  attr_accessor :otu_filter

  # @!eliminate_unknown
  #   @return [Boolean or null]
  # Optional attribute to eliminate taxa with no scores on a used descriptor: 'false' - default or 'true'
  # If true, the rows without scores will be eliminated
  attr_accessor :eliminate_unknown

  # @!identified_to_rank
  #   @return [String or null]
  # Optional attribute to limit identification to OTU or a particular nomenclatural rank. Valid values are 'otu', 'species', 'genus', etc.
  attr_accessor :identified_to_rank

  # @!per
  #   @return [Integer or null]
  # Optional attribute. Number of rows displayed per page
  attr_accessor :per

  #
  ##### RETURNED DATA ######
  #

  # @!observation_matrix
  #   @return [Object]
  # Returns observation_matrix as an object
  attr_accessor :observation_matrix

  # @!observation_matrix_citation
  #   @return [Object]
  # Returns observation_matrix_citation as an object
  attr_accessor :observation_matrix_citation

  # @!descriptors
  #   @return [null]
  # Temporary attribute. Used for validation.
  attr_accessor :descriptors

  # @!descriptor_available_keywords
  #   @return [Array of Objects or null]
  # Returns the list of all Tags used with the descriptors. Descriptors could be filtered by tag_id
  attr_accessor :descriptor_available_keywords

  # @!language_to_use
  #   @return [Object or null]
  # Returns Language as an object if the language_id was provided (used to display descriptors in a particular language)
  attr_accessor :language_to_use

  # @!descriptors_with_filter
  #   @return [null]
  # Temporary attribute. Used for validation. List of descriptors reduced by keyword_ids
  attr_accessor :descriptors_with_filter

  # @!descriptor_available_languages
  #   @return [Array of Objects or null]
  # Returns the list of available Languages used as translations for descriptors (in translations are available)
  attr_accessor :descriptor_available_languages

  # @!rows_with_filter
  #   @return [null]
  # Temporary attribute. Used for validation. list of rows to be included into the matrix
  attr_accessor :rows_with_filter

  # @!row_id_filter_array
  #   @return [array]
  # Array of row_ids in the @row_filter
  attr_accessor :row_id_filter_array

  # @!otu_id_filter_array
  #   @return [array]
  # Array of otu_ids in the @otu_filter
  attr_accessor :otu_id_filter_array

  # @!list_of_descriptors
  #   @return [Hash]
  # Return the list of descriptors and their states. Translated (if needed) and Sorted
  attr_accessor :list_of_descriptors

  # @!depiction_matrix
  #   @return [Hash of Arrays]
  # Returns the table of observations with images.
  attr_accessor :depiction_matrix

  # @!image_hash
  #   @return [Hash]
  # Returns the hash with image attributes
  attr_accessor :image_hash

  # @!row_hash
  #   @return [null]
  # Temporary hash of rows; used for calculation of remaining and eliminated rows
  attr_accessor :row_hash

  # @!descriptors_hash
  #   @return [null]
  #temporary hash of descriptors; used for calculation of useful and not useful descriptors and their states
  attr_accessor :descriptors_hash

  # @! list_of_image_ids
  #   @return [null]
  #temporary array of image.ids; used to build the @image_hash
  attr_accessor :list_of_image_ids

  # @!pagination_page
  #   @return [Integer]
  # Returns the page number
  attr_accessor :pagination_page

  # @!pagination_next_page
  #   @return [Integer or null]
  # Returns the next page number
  attr_accessor :pagination_next_page

  # @!pagination_previous_page
  #   @return [Integer or null]
  # Returns the previous page number
  attr_accessor :pagination_previous_page

  # @!pagination_per_page
  #   @return [Integer or null]
  # Returns number of records per page
  attr_accessor :pagination_per_page

  # @!pagination_total
  #   @return [Integer or null]
  # Returns total number of records
  attr_accessor :pagination_total

  # @!pagination_total_pages
  #   @return [Integer or null]
  # Returns total number of pages
  attr_accessor :pagination_total_pages

  def initialize(
    observation_matrix_id: nil,
    project_id: nil,
    language_id: nil,
    keyword_ids: nil,
    row_filter: nil,
    otu_filter: nil,
    identified_to_rank: nil,
    per: nil,
    page: nil,
    pagination_page: nil,
    pagination_next_page: nil,
    pagination_previous_page: nil,
    pagination_per_page: nil,
    pagination_total: nil,
    pagination_total_pages: nil)

    @observation_matrix_id = observation_matrix_id
    @project_id = project_id
    @observation_matrix = find_observation_matrix
    @observation_matrix_citation = @observation_matrix&.source
    @language_id = language_id
    @keyword_ids = keyword_ids
    @per = per.blank? ? 250 : per
    @page = page.blank? ? 1 : page
    @descriptor_available_keywords = descriptor_available_keywords
    @row_filter = row_filter
    @otu_filter = otu_filter
    @row_id_filter_array = row_filter_array
    @otu_id_filter_array = otu_filter_array

    # Memoized now
    # @rows_with_filter = get_rows_with_filter

    @identified_to_rank = identified_to_rank
    @row_hash = row_hash_initiate

    @descriptors_with_filter = descriptors_with_keywords

    @descriptor_available_languages = descriptor_available_languages_list
    @language_to_use = language_to_use


    ###main_logic
    @list_of_image_ids = []

    # Initiate on getter, memoized, only breaks then when requested
    # @list_of_descriptors = build_list_of_descriptors

    @depiction_matrix = descriptors_hash_initiate
    @image_hash = build_image_hash

    ###delete temporary data
    @row_hash = nil
    @rows_with_filter = []
    @list_of_image_ids = nil
    @descriptors_with_filter = nil
  end

  def find_observation_matrix
    ObservationMatrix.where(id: observation_matrix_id.to_i, project_id: project_id).first
  end

  def descriptors
    return nil if observation_matrix.nil? # Might be more universal if Descriptors.none
    observation_matrix.descriptors.where("descriptors.type = 'Descriptor::Media'").not_weight_zero
  end

  def descriptor_available_languages_list
    return nil if descriptors_with_filter.nil?
    descriptor_ids = descriptors_with_filter.collect{|i| i.id}
    languages = Language.joins(:alternate_value_translations)
      .where(alternate_values: {alternate_value_object_type: 'Descriptor', type: 'AlternateValue::Translation'})
      .where('alternate_values.alternate_value_object_id IN (?)', descriptor_ids ).order('languages.english_name').distinct.to_a
    unless languages.empty?
      languages = Language.where(english_name: 'English').to_a + languages
    end
    languages
  end

  def language_to_use
    return nil if language_id.blank?
    l = Language.where(id: language_id).first
    return nil if l.nil? || !descriptor_available_languages.to_a.include?(l)
    l
  end

  def descriptor_available_keywords
    return nil if descriptors.nil?
    descriptor_ids = descriptors.pluck(:id)
    tags = Keyword.joins(:tags)
      .where(tags: {tag_object_type: 'Descriptor'})
      .where('tags.tag_object_id IN (?)', descriptor_ids ).order('name').distinct.to_a
  end

  def descriptors_with_keywords
    if observation_matrix_id.to_i == 0 && !otu_filter.blank?
      d = observation_depictions_from_otu_filter.pluck(:descriptor_id).uniq
      ds = Descriptor.where("descriptors.type = 'Descriptor::Media' AND descriptors.id IN (?)", d).not_weight_zero
    elsif keyword_ids
      ds = descriptors.joins(:tags).where('tags.keyword_id IN (?)', keyword_ids.to_s.split('|').map(&:to_i) )
    else
      ds = descriptors
    end
    return [] if ds.nil? || ds.empty?
    ds = ds.sort{|a,b| a.observation_matrix_columns.first.try(:position).to_i <=> b.observation_matrix_columns.first.try(:position).to_i}
    ds
  end

  def row_filter_array
    row_filter.blank? ? nil : row_filter.to_s.split('|').map(&:to_i)
  end

  def otu_filter_array
    otu_filter.blank? ? nil : otu_filter.to_s.split('|').map(&:to_i)
  end

  def rows_with_filter
    return @rows_with_filter if !@rows_with_filter.nil?
    @rows_with_filter = [] if observation_matrix.nil?
    if !row_id_filter_array.nil?
      @rows_with_filter ||= observation_matrix.observation_matrix_rows.where(id: row_id_filter_array).order(:position)
    elsif !otu_id_filter_array.nil?
      @rows_with_filter ||= observation_matrix.observation_matrix_rows
        .where(observation_object_type: 'Otu')
        .where(observation_object_id: otu_id_filter_array)
        .order(:position)
    else
      @rows_with_filter ||= observation_matrix.observation_matrix_rows.order(:position)
    end
  end

  def row_hash_initiate
    h = {}
    rows = nil # Of either Otu or ObservationMatrixRow of type Otu !! TODO:
    if observation_matrix_id.to_i == 0 && !otu_filter.blank?

      o = observation_depictions_from_otu_filter.where("observations.observation_object_type = 'Otu'").pluck(:observation_object_id).uniq

      @otu_id_filter_array = otu_id_filter_array & o

      rows = Otu.where(id: otu_id_filter_array)
    else
      rows = rows_with_filter
    end

    i = 0
    per = @per.to_i
    page = @page.to_i

    @pagination_page = page
    @pagination_total = rows.count
    @pagination_total_pages = (@pagination_total.to_f / per).ceil
    @pagination_next_page = @pagination_total_pages > @pagination_page ? @pagination_page + 1 : nil
    @pagination_previous_page = @pagination_page > 1 ? @pagination_page - 1 : nil
    @pagination_per_page = per
    rows.each do |r|
      i += 1
      next if i < per * (page - 1) + 1
      break if i > per * page

      case r.class.to_s
      when 'Otu'
        otu_collection_object = 'Otu' + r.id.to_s
        # otu_collection_object = r.id.to_s + '|'
      when 'ObservationMatrixRow'
        otu_collection_object = r.observation_object_type + r.observation_object_id.to_s # r.otu_id.to_s + '|' + r.collection_object_id.to_s
      end

      h[otu_collection_object] = {}
      h[otu_collection_object][:object] = r

      if identified_to_rank == 'otu'
        case r.class.to_s
        when 'Otu'
          h[otu_collection_object][:object_at_rank] = r
        when 'ObservationMatrixRow'
          h[otu_collection_object][:object_at_rank] = r.current_otu || r
        end
      elsif identified_to_rank
        case r.class.to_s
        when 'Otu'
          h[otu_collection_object][:object_at_rank] = r&.taxon_name&.valid_taxon_name&.ancestor_at_rank(identified_to_rank, inlude_self = true) || r
        when 'ObservationMatrixRow'
          h[otu_collection_object][:object_at_rank] = r&.current_taxon_name&.ancestor_at_rank(identified_to_rank, inlude_self = true) || r
        end
      else
        h[otu_collection_object][:object_at_rank] = r
      end
      h[otu_collection_object][:otu_id] = r.class.to_s == 'Otu' ? r.id : r.observation_object_id # otu_id
    end
    h
  end

  ## descriptors_hash: {descriptor.id: {:descriptor,    ### (descriptor)
  ##                                    :observations,  ### (array of observations for )
  ##                                    :state_ids,     ### {hash of state_ids used in the particular matrix}
  ##                                    }}
  def descriptors_hash_initiate
    h = {}

    # Depictions is depictions with other attributes added
    depictions = nil

    if observation_matrix_id.to_i == 0 && !otu_filter.blank?
      depictions = observation_depictions_from_otu_filter
    else
      return h if observation_matrix.nil?
      depictions = observation_matrix.observation_depictions
    end

    descriptors_count = list_of_descriptors.count

    otu_ids = {}
    row_hash.each do |r_key, r_value|
      if (row_id_filter_array.nil? && otu_id_filter_array.nil?) ||
          (row_id_filter_array && row_id_filter_array.include?(r_value[:object].id)) ||
          (otu_id_filter_array && otu_id_filter_array.include?(r_value[:otu_id]))
        h[r_key] = {object: r_value[:object_at_rank],
                    row_id: r_value[:object].id,
                    otu_id: r_value[:otu_id],
                    depictions: Array.new(descriptors_count) {Array.new},
        } if h[r_key].nil?
        otu_ids[r_value[:otu_id]] = true
      end
    end

    depictions.each do |o|
      if (o.observation_object_type == 'Otu' && otu_ids[o.observation_object_id].nil?) ||
        (otu_id_filter_array && (o.observation_object_type == 'Otu' && !otu_id_filter_array.include?(o.observation_object_id)))
        next
      end

      otu_collection_object = o.observation_object_type + o.observation_object_id.to_s # id.to_s + '|' + o.collection_object_id.to_s
      if h[otu_collection_object]
        descriptor_index = list_of_descriptors[o.descriptor_id][:index]
        h[otu_collection_object][:depictions][descriptor_index] += [o]
        @list_of_image_ids.append(o.image_id)
      end
    end
    h
  end

  # TODO: CHANGED FLAG REMOVE
  # @return [Depiction scope]
  def observation_depictions_from_otu_filter
    Depiction.select('depictions.*, observations.descriptor_id, observations.observation_object_id, observations.observation_object_type, sources.id AS source_id, sources.cached_author_string, sources.year, sources.cached AS source_cached')
      .joins("INNER JOIN observations ON observations.id = depictions.depiction_object_id")
      .joins("INNER JOIN images ON depictions.image_id = images.id")
      .joins("LEFT OUTER JOIN citations ON citations.citation_object_id = images.id AND citations.citation_object_type = 'Image' AND citations.is_original IS TRUE")
      .joins("LEFT OUTER JOIN sources ON citations.source_id = sources.id")
      .where("observations.type = 'Observation::Media' AND observations.observation_object_id IN (?)", otu_id_filter_array)
      .where('observations.project_id = (?)', project_id)
      .order('depictions.position')
  end

  # returns {123: ['1', '3'], 125: ['3', '5'], 135: ['2'], 136: ['true'], 140: ['5-10']}
  def selected_descriptors_hash_initiate
    # "123:1|3||125:3|5||135:2"
    h = {}
    return h if selected_descriptors.blank?
    a = selected_descriptors.include?('||') ? selected_descriptors.to_s.split('||') : [selected_descriptors]
    a.each do |i|
      d = i.split(':')
      h[d[0].to_i] = d[1].include?('|') ? d[1].split('|') : [d[1]]
    end
    h
  end

  def list_of_descriptors
    return @list_of_descriptors if !@list_of_descriptors.nil?
    language = language_id.blank? ? nil : language_id.to_i
    n = 0
    h = {}
    descriptors_with_filter.each do |d|
      descriptor = {}
      descriptor[:index] = n
      descriptor[:id] = d.id
      descriptor[:type] = d.type
      descriptor[:name] = d.target_name(:key, language)
      descriptor[:weight] = d.weight
      descriptor[:description] = d.description
      h[d.id] = descriptor
      n += 1
    end
    @list_of_descriptors = h
  end

  def build_image_hash
    #    if !otu_filter.blank? || !row_filter.blank?
    #      img_ids = observation_depictions_from_otu_filter.pluck(:image_id).uniq
    #    else
    #      img_ids = observation_matrix.observation_depictions.pluck(:image_id).uniq
    #    end
    h = {}

    imgs = Image.where('id IN (?)', list_of_image_ids )
    imgs.each do |d|
      i = {}
      i[:global_id] = d.to_global_id.to_s
      i[:image_file_file_name] = d.image_file_file_name
      i[:image_file_file_size] = d.image_file_file_size
      i[:image_file_content_type] = d.image_file_content_type
      i[:user_file_name] = d.user_file_name
      i[:height] = d.height
      i[:width] = d.width
      i[:original_url] = d.image_file.url
      i[:medium_url] = d.image_file.url(:medium)
      i[:thumb_url] = d.image_file.url(:thumb)
      i[:citations] = []
      h[d.id] = i
    end

    #cit = Citation.where(citation_object_type: 'Image').where('citation_object_id IN (?)', img_ids )
    cit = Citation.select('citations.*, sources.cached, sources.cached_author_string, sources.year')
      .joins(:source)
      .where(citation_object_type: 'Image')
      .where('citation_object_id IN (?)', list_of_image_ids )

    cit.each do |c|
      i = {}
      i[:id] = c.id
      i[:source_id] = c.source_id
      i[:pages] = c.pages
      i[:is_original] = c.is_original
      i[:cached] = c.cached
      i[:cached_author_string] = c.cached_author_string
      i[:year] = c.year
      i[:global_id] = c.to_global_id.to_s
      #i[:citation_object_id] = c.citation_object_id
      #i[:citation_object_type] = c.citation_object_type
      h[c.citation_object_id][:citations].push(i)
    end
    h
  end

end