Grid of images with the same height?

Hi! I’m working on a book recommendation system based on a LDA model and want to show the book options on one page. Right now I’m using st.image() and passing the list of images which comes from a column in the dataframe, and it looks like this:

st.image(filteredImages, width=150, caption=caption)

I’m able to set the width but not the height, so all of them have different heights. I tried using the pillow library to resize each image but the run time increases by a lot and I don’t get the desired image quality:

 for image in filteredImages:
    r = requests.get(image)
    img = Image.open(BytesIO(r.content))
    resizedImg = img.resize((225, 325), Image.ANTIALIAS)
    resizedImages.append(resizedImg)

I also tried using a beta_container and I get something similar to my desired output but it’s repeating the same picture across the entire row:

with st.beta_container():
        for col in st.beta_columns(4):
            col.image(filteredImages, width=150)

Any thoughts on how I can get the same height across every picture in a grid?

2 Likes

Hi @vclugoar,

First, Welcome to the Streamlit community! :partying_face: :partying_face: :tada: :star2: :tada:

I think your nearly there on a solution honestly! My first question is will you be recommending the same number of books each time? I see 8 books recommended, 2 rows of 4, If that number is the same always, then I think we can work out a relatively simple solution using columns.

I have done something similar to this before in this post:

In your case, you don’t need the st.beta_container(), just the st.beta_columns(). I also think my answer here could be improved by making a list/iterable of the filtered images (I think that’s your image names?) that has the correct shape…

For example, if you always recommend 8 then you have 4 columns and 2 rows. So you would make a list/iterable like this:

list = [[column_1_row_1_image, column_1_row_2_image ], 
          [column_2_row_1_image, column_2_row_2_image],
          [column_3_row_1_image, column_3_row_2_image],
          [column_4_row_1_image, column_4_row_2_image] ]

and then you can systematically select the correct image this way!

This will line all the images up by their tops, but won’t make all the images the same height. The only way to do that is to run some image processing (Streamlit won’t crop your images off for you).

But, people expect books (and covers) to be different sizes, So I think if you can get them lining up at the tops and nice and clean then this will look a lot better and solve your problem!

Happy Streamlit-ing!
Marisa

thank you @Marisa_Smith!

I will not be showing the same number of books each time :frowning: I’m filtering by author and each author has different number of books, and eventually will be showing only related books based on the LDA model. Some books will have 10 similar books, others might have only 3.

and yes! filteredImages is my list of images, but the image files are not labeled systematically. Do you recommend renaming all the image files?

Based on your examples I was thinking of doing something like this:

 for idx, img in enumerate(filteredImages): 
        cols = st.beta_columns(4) 
        
        cols[0].image(filteredImages[idx], use_column_width=True)
        idx+=1
        cols[1].image(filteredImages[idx], use_column_width=True)
        idx+=idx
        cols[2].image(filteredImages[idx], use_column_width=True)
        idx+=idx
        cols[3].image(filteredImages[idx], use_column_width=True)
        idx+=idx

which gets me to this:

Now it’s showing a different image per col in the same row, but the other rows the images get repeated

Hey @vclugoar,

Popsicle sticks! This won’t make it impossible, just a pain :rofl:

I would make a little function that counts the number of books you’re looking to recommend and then figures out the number of rows needed to show that number of book recommendations. You will have to put something in place to handle partially filled rows as well. I would make the number of columns constant (say always display 4 or 5 books across) as this will make it easier.

No, not, in this case, I feel like you might run into overwriting errors and issues in your directory. I think the best way is to try my idea of making a list of just the images you want to show, and putting them in a shape that can be easily incorporated into the loop.

what does filteredImages look like and contain? The reason that this is happening is that you need a list of lists. Cause its working and cycling through but at the end of a column, it’s going back to the 2nd item and not continuing with item 5.

(this is hard to explain over text hopefully you see what i am trying to say here! )

thank you again @Marisa_Smith!

you were right about the cycling through! I ended up doing something like this:

   idx = 0 
   while idx < len(filteredImages):
        for _ in range(len(filteredImages)):
            cols = st.beta_columns(4) 
            cols[0].image(filteredImages[idx], width=150, caption=caption[idx])
            idx+=1
            cols[1].image(filteredImages[idx], width=150, caption=caption[idx])
            idx+=1
            cols[2].image(filteredImages[idx], width=150, caption= caption[idx])
            idx+=1
            cols[3].image(filteredImages[idx], width=150, caption= caption[idx])
            idx = idx + 1

which got me SUPER close to what I want:

but I’m getting this error which I think was related to what you mentioned with having a counter function:

Screen Shot 2021-03-11 at 4.04.35 PM

Hey @vclugoar,

Awesome! I think it’s really starting to come together here!

No worries! I actually expected this, and it’s a simple fix with just a bit of python management. If you recall here:

This is exactly what this error is from, and we can tackle that right now!

So this is because in some instances your last row won’t have enough book recommendations to fill all the slots, and when you try to access the filteredImage list at that idx it bounces you. I think we have a couple of options:

  • add to filteredImage length: I’m not sure how you implemented your solution but, with a bit of math we can find out how many actual suggestions are in the last row. (I would use the python mod operator with 4 and then you can find the remaining images in the last row.) Then we just need to add to the filteredImages (and captions list!) to create spots at the remaining indexes. For example, say in your last row you have only 2 images to display (and you want the last 2 columns to “be empty”). Then the last 4 entries of your filtered list and captions list would look like:
# pass a name to a file where you store your images to a block white image 
# or (ideally) to an image of just an invisible background
filteredList[n-4:n] = [ ... [2nd_last_image_name, last_image_name, 
name_of_empty/blank_image, name_of_empty/blank_image]]

# pass 2 empty strings into the captions list so nothing appears under 
# your "blank image" and there is no index error
captions[n-4:n] = [ ... [2nd_last_caption, last_caption, "", "" ] ] 

  • add a logic statement to handle the exception: I think there is a way to add an internal logic statement inside your while idx loop, because you increment the idx 4 times per loop. It could look something like this: (This is untested as I don’t have your files or anything to test with but hopefully with only a few small tweaks it can work!)
   idx = 0 
   while idx < len(filteredImages):
        for _ in range(len(filteredImages)):
            cols = st.beta_columns(4) 

# first, going to loop over the columns, 
            for col_num in range(4): 

# next check that idx is in range, if it is then we add an image to the 
# column number we are on. If idx > len(filteredImages), it should
# skip those columns, they will exist but we just wont put
# anything in them
                if idx <= len(filteredImages): 
                    cols[col_num].image(filteredImages[idx], 
                         width=150, caption=caption[idx])
                    idx+=1
          
  • add a try-catch loop: This would be very similar to the solution above, you would use the IndexError that is returned and change the behaviour of the app based on that. I have never really properly use try-catch’s, mostly because I am a bit of a lazy programmer :rofl: :shushing_face: and I can usually find a solution by just using an if statement to hit the edge cases.

I think all of these can work, it’s just doing the final finesse on them to get them coded properly in your app!
Hope this helps!
Marisa

thank you! I used a way less elegant way to get that done:

idx = 0 
    for _ in range(len(filteredImages)-1): 
        cols = st.beta_columns(4) 
        
        if idx < len(filteredImages): 
            cols[0].image(filteredImages[idx], width=150, caption=caption[idx])
        idx+=1
        
        if idx < len(filteredImages):
            cols[1].image(filteredImages[idx], width=150, caption=caption[idx])
        idx+=1

        if idx < len(filteredImages):
            cols[2].image(filteredImages[idx], width=150, caption=caption[idx])
        idx+=1 
        if idx < len(filteredImages): 
            cols[3].image(filteredImages[idx], width=150, caption=caption[idx])
            idx = idx + 1
        else:
            break

which led me to this:

but will incorporate your feedback to make this better :slight_smile:

3 Likes

Hey @vclugoar!

It’s looking so good!!! And hey if it works it works! :sunglasses: :rofl:

Happy Streamlit-ing!
Marisa

I know it’s quite late but I just want to share my solution for this.

import streamlit as st
from itertools import cycle

filteredImages = [] # your images here
caption = [] # your caption here
cols = cycle(st.columns(4)) # st.columns here since it is out of beta at the time I'm writing this
for idx, filteredImage in enumerate(filteredImages):
    next(cols).image(filteredImage, width=150, caption=caption[idx])

This is a much simpler ways and you don’t have to check for the out of bound index everytime.
Hope it helps! :grin:

9 Likes

Thank you so much. This has worked for me

1 Like

I want to add hyperlink to the caption how can i do this …like below every image there is a caption with hyperlink…pls tell.

Here is a custom solution I came up with involving custom CSS in case anyone finds this helpful.

import streamlit as st
import pandas as pd
import numpy as np

st.markdown(
    """
<style>

    div[data-testid="stHorizontalBlock"] {
        display: flex !important;
    }

    div[data-testid="column"] {
        flex: 1 !important; /* additionally, equal width */
        padding: 1em !important;
        border: solid !important;
        border-radius: 10px !important;
        border-color: grey !important;
    }
</style>
""",
    unsafe_allow_html=True,
)


col1, col2, col3 = st.columns(3)

with col1:
   with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")

with col2:
    with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")

with col3:
    with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")



col4, col5, col6 = st.columns(3)

with col4:
   with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")

with col5:
    with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")
        st.write("This is inside the container")

with col6:
    with st.container(border=False):
        st.write("This is inside the container")
        st.write("This is inside the container")